Панды: фильтровать строки DataFrame с оператором цепочки


329

Большинство операций в pandasможет быть выполнены с помощью оператора цепочки ( groupby, aggregate, apply, и т.д.), но единственным способом я нашел для фильтра строк с помощью обычной индексации кронштейна

df_filtered = df[df['column'] == value]

Это непривлекательно, так как требует, чтобы я присвоил dfпеременную, прежде чем смогу фильтровать ее значения. Есть ли что-то похожее на следующее?

df_filtered = df.mask(lambda x: x['column'] == value)

df.queryи, pd.evalкажется, хорошо подходит для этого варианта использования. Для получения информации о pd.eval()семействе функций, их функциях и сценариях использования, пожалуйста, посетите Dynamic Expression Evaluation в pandas, используя pd.eval () .
CS95

Ответы:


384

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

«Цепная» фильтрация выполняется путем «цепочки» критериев в логическом индексе.

In [96]: df
Out[96]:
   A  B  C  D
a  1  4  9  1
b  4  5  0  2
c  5  5  1  0
d  1  3  9  6

In [99]: df[(df.A == 1) & (df.D == 6)]
Out[99]:
   A  B  C  D
d  1  3  9  6

Если вы хотите объединить методы, вы можете добавить свой собственный метод маски и использовать его.

In [90]: def mask(df, key, value):
   ....:     return df[df[key] == value]
   ....:

In [92]: pandas.DataFrame.mask = mask

In [93]: df = pandas.DataFrame(np.random.randint(0, 10, (4,4)), index=list('abcd'), columns=list('ABCD'))

In [95]: df.ix['d','A'] = df.ix['a', 'A']

In [96]: df
Out[96]:
   A  B  C  D
a  1  4  9  1
b  4  5  0  2
c  5  5  1  0
d  1  3  9  6

In [97]: df.mask('A', 1)
Out[97]:
   A  B  C  D
a  1  4  9  1
d  1  3  9  6

In [98]: df.mask('A', 1).mask('D', 6)
Out[98]:
   A  B  C  D
d  1  3  9  6

2
Отличный ответ! Итак (df.A == 1) & (df.D == 6), является ли "&" перегруженным оператором в Pandas?
Шон

2
на самом деле, смотрите также pandas.pydata.org/pandas-docs/stable/...
Wouter Overmeire

Это действительно хорошее решение - я даже не знал, что вы можете использовать такие методы в Python. Подобную функцию было бы очень неплохо иметь в самой Панде.
naught101

Единственная проблема, с которой я столкнулся, это использование pandas.. Вы должны import pandas as pd.
Дайсуке Арамаки

3
На самом деле import pandas as pdэто обычная практика сейчас. Я сомневаюсь, что это было, когда я ответил на вопрос.
Wouter Overmeire

108

Фильтры могут быть связаны с помощью запроса Pandas :

df = pd.DataFrame(np.random.randn(30, 3), columns=['a','b','c'])
df_filtered = df.query('a > 0').query('0 < b < 2')

Фильтры также могут быть объединены в один запрос:

df_filtered = df.query('a > 0 and 0 < b < 2')

3
Если вам нужно ссылаться на переменные python в вашем запросе, в документации говорится: «Вы можете ссылаться на переменные в среде, добавляя к ним префикс« @ », такой как @a + b». Обратите внимание , что следующие соотношения: df.query('a in list([1,2])'), s = set([1,2]); df.query('a in @s').
user3780389 15.11.16

2
С другой стороны, похоже, что оценка запроса не удастся, если имя вашего столбца содержит определенные специальные символы: например, «Place.Name».
user3780389

2
Цепочка - это то, для чего предназначен запрос.
piRSquared

66

Ответ от @lodagro великолепен. Я бы расширил его, обобщив функцию маски как:

def mask(df, f):
  return df[f(df)]

Тогда вы можете делать такие вещи, как:

df.mask(lambda x: x[0] < 0).mask(lambda x: x[1] > 0)

8
Полезное обобщение! Я хотел бы, чтобы это было интегрировано непосредственно в DataFrames уже!
Duckworthd

24

С версии 0.18.1.loc метод принимает вызываемый для выбора. Вместе с лямбда-функциями вы можете создавать очень гибкие цепные фильтры:

import numpy as np
import pandas as pd

df = pd.DataFrame(np.random.randint(0,100,size=(100, 4)), columns=list('ABCD'))
df.loc[lambda df: df.A == 80]  # equivalent to df[df.A == 80] but chainable

df.sort_values('A').loc[lambda df: df.A > 80].loc[lambda df: df.B > df.A]

Если все, что вы делаете, это фильтрация, вы также можете опустить .loc.


16

Я предлагаю это для дополнительных примеров. Это тот же ответ, что и https://stackoverflow.com/a/28159296/

Я добавлю другие правки, чтобы сделать этот пост более полезным.

pandas.DataFrame.query
queryбыл сделан именно для этой цели. Рассмотрим кадр данныхdf

import pandas as pd
import numpy as np

np.random.seed([3,1415])
df = pd.DataFrame(
    np.random.randint(10, size=(10, 5)),
    columns=list('ABCDE')
)

df

   A  B  C  D  E
0  0  2  7  3  8
1  7  0  6  8  6
2  0  2  0  4  9
3  7  3  2  4  3
4  3  6  7  7  4
5  5  3  7  5  9
6  8  7  6  4  7
7  6  2  6  6  5
8  2  8  7  5  8
9  4  7  6  1  5

Давайте использовать queryдля фильтрации всех строк, гдеD > B

df.query('D > B')

   A  B  C  D  E
0  0  2  7  3  8
1  7  0  6  8  6
2  0  2  0  4  9
3  7  3  2  4  3
4  3  6  7  7  4
5  5  3  7  5  9
7  6  2  6  6  5

Который мы цепи

df.query('D > B').query('C > B')
# equivalent to
# df.query('D > B and C > B')
# but defeats the purpose of demonstrating chaining

   A  B  C  D  E
0  0  2  7  3  8
1  7  0  6  8  6
4  3  6  7  7  4
5  5  3  7  5  9
7  6  2  6  6  5

Разве это не тот же самый ответ, что и stackoverflow.com/a/28159296 Чего- то не хватает в этом ответе, который, по вашему мнению, следует уточнить?
Bscan

9

У меня был тот же вопрос, за исключением того, что я хотел объединить критерии в условие ИЛИ. Формат, заданный Wouter Overmeire, объединяет критерии в условие AND, так что оба должны быть выполнены:

In [96]: df
Out[96]:
   A  B  C  D
a  1  4  9  1
b  4  5  0  2
c  5  5  1  0
d  1  3  9  6

In [99]: df[(df.A == 1) & (df.D == 6)]
Out[99]:
   A  B  C  D
d  1  3  9  6

Но я обнаружил, что если вы оберните каждое условие (... == True)и соедините критерии с каналом, критерии объединятся в условие ИЛИ, удовлетворяемое всякий раз, когда любое из них истинно:

df[((df.A==1) == True) | ((df.D==6) == True)]

12
Не было df[(df.A==1) | (df.D==6)]бы достаточно для того, что вы пытаетесь достичь?
eenblam

Нет, это не так, потому что он дает результаты по Боллеану (True vs False), а не так, как выше, которые фильтруют все данные, которые удовлетворяют условию. Надеюсь, что я дал понять.
MGB.py

8

Pandas предлагает две альтернативы ответу Wouter Overmeire, которые не требуют переопределения. Один .loc[.]с вызываемым, как в

df_filtered = df.loc[lambda x: x['column'] == value]

другой .pipe(), как в

df_filtered = df.pipe(lambda x: x['column'] == value)

7

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

df.pipe(lambda d: d[d['column'] == value])

ЭТОТ является то , что вы хотите , если вы хотите , чтобы команды цепи , такие какa.join(b).pipe(lambda df: df[df.column_to_filter == 'VALUE'])
DisplayName

4

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

pd.DataFrame = apply_masks()

Использование:

A = pd.DataFrame(np.random.randn(4, 4), columns=["A", "B", "C", "D"])
A.le_mask("A", 0.7).ge_mask("B", 0.2)... (May be repeated as necessary

Это немного странно, но может сделать вещи немного чище, если вы постоянно отбираете и меняете наборы данных в соответствии с фильтрами. Есть также фильтр общего назначения, адаптированный из Даниэля Велкова выше в функции gen_mask, который вы можете использовать с лямбда-функциями или иным образом, если хотите.

Файл для сохранения (я использую masks.py):

import pandas as pd

def eq_mask(df, key, value):
    return df[df[key] == value]

def ge_mask(df, key, value):
    return df[df[key] >= value]

def gt_mask(df, key, value):
    return df[df[key] > value]

def le_mask(df, key, value):
    return df[df[key] <= value]

def lt_mask(df, key, value):
    return df[df[key] < value]

def ne_mask(df, key, value):
    return df[df[key] != value]

def gen_mask(df, f):
    return df[f(df)]

def apply_masks():

    pd.DataFrame.eq_mask = eq_mask
    pd.DataFrame.ge_mask = ge_mask
    pd.DataFrame.gt_mask = gt_mask
    pd.DataFrame.le_mask = le_mask
    pd.DataFrame.lt_mask = lt_mask
    pd.DataFrame.ne_mask = ne_mask
    pd.DataFrame.gen_mask = gen_mask

    return pd.DataFrame

if __name__ == '__main__':
    pass

3

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

https://github.com/toobaz/generic_utils/blob/master/generic_utils/pandas/where.py

Вам не нужно загружать весь репозиторий: сохраните файл и сделайте

from where import where as W

должно хватить. Тогда вы используете это так:

df = pd.DataFrame([[1, 2, True],
                   [3, 4, False], 
                   [5, 7, True]],
                  index=range(3), columns=['a', 'b', 'c'])
# On specific column:
print(df.loc[W['a'] > 2])
print(df.loc[-W['a'] == W['b']])
print(df.loc[~W['c']])
# On entire - or subset of a - DataFrame:
print(df.loc[W.sum(axis=1) > 3])
print(df.loc[W[['a', 'b']].diff(axis=1)['b'] > 1])

Немного менее глупый пример использования:

data = pd.read_csv('ugly_db.csv').loc[~(W == '$null$').any(axis=1)]

Кстати, даже в случае, когда вы просто используете логические столбцы,

df.loc[W['cond1']].loc[W['cond2']]

может быть гораздо более эффективным, чем

df.loc[W['cond1'] & W['cond2']]

потому что он оценивает cond2только где cond1есть True.

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я сначала дал этот ответ в другом месте, потому что я не видел этого.


2

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

Код ниже может фильтровать строки по значению.

df_filtered = df.loc[df['column'] == value]

Немного изменив его, вы также можете фильтровать столбцы.

df_filtered = df.loc[df['column'] == value, ['year', 'column']]

Итак, почему мы хотим цепной метод? Ответ в том, что читать легко, если у вас много операций. Например,

res =  df\
    .loc[df['station']=='USA', ['TEMP', 'RF']]\
    .groupby('year')\
    .agg(np.nanmean)

2

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

df[df["column_name"] != 5].groupby("other_column_name")

Кажется, работает: вы также можете вложить []оператор. Может быть, они добавили его, так как вы задали вопрос.


1
Это не имеет большого смысла в цепочке, потому что dfтеперь не обязательно ссылается на вывод предыдущей части цепочки.
Даан Луттик

@DaanLuttik: согласен, это не цепочка, а вложение. Для тебя лучше?
серв-

1

Если вы настроите свои столбцы для поиска в качестве индексов, то вы можете использовать DataFrame.xs()для получения поперечного сечения. Это не так универсально, как queryответы, но может быть полезно в некоторых ситуациях.

import pandas as pd
import numpy as np

np.random.seed([3,1415])
df = pd.DataFrame(
    np.random.randint(3, size=(10, 5)),
    columns=list('ABCDE')
)

df
# Out[55]: 
#    A  B  C  D  E
# 0  0  2  2  2  2
# 1  1  1  2  0  2
# 2  0  2  0  0  2
# 3  0  2  2  0  1
# 4  0  1  1  2  0
# 5  0  0  0  1  2
# 6  1  0  1  1  1
# 7  0  0  2  0  2
# 8  2  2  2  2  2
# 9  1  2  0  2  1

df.set_index(['A', 'D']).xs([0, 2]).reset_index()
# Out[57]: 
#    A  D  B  C  E
# 0  0  2  2  2  2
# 1  0  2  1  1  0

1

Вы также можете использовать библиотеку NumPy для логических операций. Это довольно быстро.

df[np.logical_and(df['A'] == 1 ,df['B'] == 6)]
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.