панди: фільтрують рядки 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()сімейство функцій, їх особливості та випадки використання, будь ласка, відвідайте Динамічну оцінку вираження в пандах за допомогою 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. Таку функцію було б дуже приємно мати в самих Pandas.
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').
користувач3780389

2
З іншого боку, схоже, що оцінка запиту не вдасться, якщо назва вашого стовпця має певні спеціальні символи: напр., "Place.Name".
користувач3780389

2
Ланцюг - це те, для чого призначений запит.
piRSквартировано

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!
качка

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

Моя відповідь схожа на інші. Якщо ви не хочете створювати нову функцію, ви можете використовувати те, що панда вже визначила для вас. Використовуйте трубний метод.

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.