панди: фільтрують рядки 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.