Есть ли метод запроса или аналогичный для серии панд (pandas.Series.query())?

Метод pandas.DataFrame.query() отлично подходит для (до/после) фильтрации данных при загрузке или построении графика. Это особенно удобно для цепочки методов.

Мне часто хочется применить ту же логику к pandas.Series, например. после выполнения такого метода, как df.value_counts, который возвращает pandas.Series.

Пример

Предположим, есть огромная таблица со столбцами Player, Game, Points, и я хочу построить гистограмму игроков с более чем 14 умножениями на 3 очка. Сначала я должен суммировать очки каждого игрока (groupby -> agg), что вернет серию из ~ 1000 игроков и их общее количество очков. Применяя логику .query, это будет выглядеть примерно так:

df = pd.DataFrame({
    'Points': [random.choice([1,3]) for x in range(100)], 
    'Player': [random.choice(["A","B","C"]) for x in range(100)]})

(df
     .query("Points == 3")
     .Player.values_count()
     .query("> 14")
     .hist())

Единственные решения, которые я нахожу, заставляют меня выполнять ненужное задание и разбивать цепочку методов:

(points_series = df
     .query("Points == 3")
     .groupby("Player").size()
points_series[points_series > 100].hist()

Цепочка методов, а также метод запроса помогают сохранить разборчивость кода, в то время как фильтрация подмножеств может довольно быстро запутаться.

# just to make my point :)
series_bestplayers_under_100[series_prefiltered_under_100 > 0].shape

Пожалуйста, помогите мне в моей дилемме! Спасибо


person dmeu    schedule 21.10.2016    source источник
comment
Я не уверен, что SO — лучшее место для этого вопроса, так как это вопрос к разработчикам этой библиотеки, лучше опубликовать на github ИМО   -  person EdChum    schedule 21.10.2016
comment
Изменено название вопроса, чтобы быть более похожим на SO   -  person dmeu    schedule 21.10.2016


Ответы (3)


IIUC вы можете добавить query("Points > 100"):

df = pd.DataFrame({'Points':[50,20,38,90,0, np.Inf],
                   'Player':['a','a','a','s','s','s']})

print (df)
  Player     Points
0      a  50.000000
1      a  20.000000
2      a  38.000000
3      s  90.000000
4      s   0.000000
5      s        inf

points_series = df.query("Points < inf").groupby("Player").agg({"Points": "sum"})['Points']
print (points_series)     
a = points_series[points_series > 100]
print (a)     
Player
a    108.0
Name: Points, dtype: float64


points_series = df.query("Points < inf")
                  .groupby("Player")
                  .agg({"Points": "sum"})
                  .query("Points > 100")

print (points_series)     
        Points
Player        
a        108.0

Другим решением является Выбор по вызываемому:

points_series = df.query("Points < inf")
                  .groupby("Player")
                  .agg({"Points": "sum"})['Points']
                  .loc[lambda x: x > 100]

print (points_series)     
Player
a    108.0
Name: Points, dtype: float64

Отредактированный ответ по отредактированному вопросу:

np.random.seed(1234)
df = pd.DataFrame({
    'Points': [np.random.choice([1,3]) for x in range(100)], 
    'Player': [np.random.choice(["A","B","C"]) for x in range(100)]})

print (df.query("Points == 3").Player.value_counts().loc[lambda x: x > 15])
C    19
B    16
Name: Player, dtype: int64

print (df.query("Points == 3").groupby("Player").size().loc[lambda x: x > 15])
Player
B    16
C    19
dtype: int64
person jezrael    schedule 21.10.2016
comment
Ого, плохой пример с моей стороны! На самом деле groupby возвращает DataFrame. Но напр. a pd.DataFrame.value_counts() возвращает серию, в которой ваше решение пригодится! Selection By Callable, я не знал об этом - спасибо - person dmeu; 21.10.2016
comment
Хорошо, я добавляю ответ на вашу новую проблему - вызываемые редко используются, потому что это новая функциональность. - person jezrael; 21.10.2016
comment
Хорошо, я вижу, что это было добавлено только недавно. Хорошее дополнение! - person dmeu; 21.10.2016

Почему бы не преобразовать Series в DataFrame, выполнить запрос, а затем преобразовать обратно.

df["Points"] = df["Points"].to_frame().query('Points > 100')["Points"]

Здесь .to_frame() преобразуется в DataFrame, а конечный ["Points"] преобразуется в Series.

Затем метод .query() можно использовать последовательно, независимо от того, имеет ли объект Pandas 1 или более столбцов.

person Martin    schedule 15.11.2016

Вместо запроса вы можете использовать pipe:

s.pipe(lambda x: x[x>0]).pipe(lambda x: x[x<10])
person Ilya Prokin    schedule 05.12.2018
comment
Это дает вам только логическую маску. Он не запрашивает/фильтрует серию. - person Michele Piccolini; 12.03.2021