Як відфільтрувати рамку даних Pandas, використовуючи "in" і "not in", як у SQL


432

Як я можу досягти еквівалентів SQL INта NOT IN?

У мене є список з необхідними значеннями. Ось сценарій:

df = pd.DataFrame({'countries':['US','UK','Germany','China']})
countries = ['UK','China']

# pseudo-code:
df[df['countries'] not in countries]

Мій сучасний спосіб зробити це такий:

df = pd.DataFrame({'countries':['US','UK','Germany','China']})
countries = pd.DataFrame({'countries':['UK','China'], 'matched':True})

# IN
df.merge(countries,how='inner',on='countries')

# NOT IN
not_in = df.merge(countries,how='left',on='countries')
not_in = not_in[pd.isnull(not_in['matched'])]

Але це здається жахливим хитом. Чи може хтось покращити його?


1
Я думаю, що ваше рішення - найкраще рішення. Ваш може охоплювати IN, NOT_IN з декількох стовпців.
Брюс Юнг

Ви хочете протестувати в одній чи кількох колонках?
smci

1
Пов'язані (продуктивність / внутрішні панда): Продуктивність Pandas pd.Series.isin з набором проти масиву
jpp

Відповіді:


820

Можна використовувати pd.Series.isin.

Для "IN" використовуйте: something.isin(somewhere)

Або для "NOT IN": ~something.isin(somewhere)

Як відпрацьований приклад:

>>> df
  countries
0        US
1        UK
2   Germany
3     China
>>> countries
['UK', 'China']
>>> df.countries.isin(countries)
0    False
1     True
2    False
3     True
Name: countries, dtype: bool
>>> df[df.countries.isin(countries)]
  countries
1        UK
3     China
>>> df[~df.countries.isin(countries)]
  countries
0        US
2   Germany

1
Просто FYI, @LondonRob мав його як DataFrame, а ваш - серія. DataFrame's isinдодано у .13.
TomAugspurger

Будь-які пропозиції, як це зробити за допомогою панд 0,12.0? Це поточна випущена версія. (Можливо, мені варто просто почекати 0,13 ?!)
LondonRob

Якщо ви насправді маєте справу з одновимірними масивами (як у прикладі), тоді на першому рядку використовуйте серію замість DataFrame, як @DSM:df = pd.Series({'countries':['US','UK','Germany','China']})
TomAugspurger

2
@TomAugspurger: як зазвичай, я, мабуть, щось пропускаю. df, як моя, так і його, є DataFrame. countries- це список. df[~df.countries.isin(countries)]видає а DataFrame, а не Series, і, здається, працює навіть у 0.11.0.dev-14a04dd.
DSM

7
Ця відповідь є заплутаною, оскільки ви постійно використовуєте countriesзмінну. Ну, ОП це робить, і це успадковується, але те, що раніше щось робиться погано, не виправдовує робити це зараз погано.
ifly6

63

Альтернативне рішення, яке використовує метод .query () :

In [5]: df.query("countries in @countries")
Out[5]:
  countries
1        UK
3     China

In [6]: df.query("countries not in @countries")
Out[6]:
  countries
0        US
2   Germany

10
@LondonRob queryбільше не експериментальний.
Пол Рудьо

38

Як реалізувати "в" і "не в" для панд DataFrame?

Панди пропонує два методи: Series.isinі DataFrame.isinдля серій і DataFrames відповідно.


Фільтр DataFrame на основі одного стовпця (також стосується серії)

Найпоширеніший сценарій - застосування isinумови у певному стовпці для фільтрації рядків у DataFrame.

df = pd.DataFrame({'countries': ['US', 'UK', 'Germany', np.nan, 'China']})
df
  countries
0        US
1        UK
2   Germany
3     China

c1 = ['UK', 'China']             # list
c2 = {'Germany'}                 # set
c3 = pd.Series(['China', 'US'])  # Series
c4 = np.array(['US', 'UK'])      # array

Series.isinприймає різні типи як вхідні дані. Нижче наведено всі дійсні способи отримання бажаного:

df['countries'].isin(c1)

0    False
1     True
2    False
3    False
4     True
Name: countries, dtype: bool

# `in` operation
df[df['countries'].isin(c1)]

  countries
1        UK
4     China

# `not in` operation
df[~df['countries'].isin(c1)]

  countries
0        US
2   Germany
3       NaN

# Filter with `set` (tuples work too)
df[df['countries'].isin(c2)]

  countries
2   Germany

# Filter with another Series
df[df['countries'].isin(c3)]

  countries
0        US
4     China

# Filter with array
df[df['countries'].isin(c4)]

  countries
0        US
1        UK

Фільтр за МНОГО стовпцями

Іноді вам потрібно буде застосувати чек для членства за допомогою деяких пошукових термінів у кількох стовпцях,

df2 = pd.DataFrame({
    'A': ['x', 'y', 'z', 'q'], 'B': ['w', 'a', np.nan, 'x'], 'C': np.arange(4)})
df2

   A    B  C
0  x    w  0
1  y    a  1
2  z  NaN  2
3  q    x  3

c1 = ['x', 'w', 'p']

Щоб застосувати isinумову до обох стовпців "A" та "B", використовуйте DataFrame.isin:

df2[['A', 'B']].isin(c1)

      A      B
0   True   True
1  False  False
2  False  False
3  False   True

Звідси, щоб зберегти рядки, де є хоча б один стовпецьTrue , ми можемо використовувати anyвздовж першої осі:

df2[['A', 'B']].isin(c1).any(axis=1)

0     True
1    False
2    False
3     True
dtype: bool

df2[df2[['A', 'B']].isin(c1).any(axis=1)]

   A  B  C
0  x  w  0
3  q  x  3

Зауважте, що якщо ви хочете шукати кожен стовпець, ви просто опустіть крок вибору стовпця і зробіть

df2.isin(c1).any(axis=1)

Аналогічно, щоб зберегти рядки, де ВСІ стовпціTrue , використовуйте allтак само, як і раніше.

df2[df2[['A', 'B']].isin(c1).all(axis=1)]

   A  B  C
0  x  w  0

Примітним Згадки: numpy.isin, query, спискові (рядок даних)

На додаток до методів , описаних вище, ви можете також використовувати Numpy еквівалент: numpy.isin.

# `in` operation
df[np.isin(df['countries'], c1)]

  countries
1        UK
4     China

# `not in` operation
df[np.isin(df['countries'], c1, invert=True)]

  countries
0        US
2   Germany
3       NaN

Чому варто задуматися? Функції NumPy, як правило, трохи швидші, ніж їх еквіваленти панди через менші накладні витрати. Оскільки це поелементна операція, яка не залежить від вирівнювання індексу, існує дуже мало ситуацій, коли цей метод не є відповідною заміною пандам ' isin.

Процедури панд зазвичай ітеративні при роботі з рядками, оскільки рядкові операції важко векторизувати. Існує безліч доказів, що розуміння списків тут буде швидшим. . Ми вдаємося до inперевірки зараз.

c1_set = set(c1) # Using `in` with `sets` is a constant time operation... 
                 # This doesn't matter for pandas because the implementation differs.
# `in` operation
df[[x in c1_set for x in df['countries']]]

  countries
1        UK
4     China

# `not in` operation
df[[x not in c1_set for x in df['countries']]]

  countries
0        US
2   Germany
3       NaN

Однак, це набагато більш важко вказувати, тому не використовуйте це, якщо ви не знаєте, що робите.

Нарешті, є і те, DataFrame.queryщо було висвітлено у цій відповіді . numxpr FTW!


Мені це подобається, але що робити, якщо я хочу порівняти стовпчик у df3, що є у стовпці df1? Як би це виглядало?
Артур Д. Хоуленд

12

Я зазвичай роблю загальну фільтрацію за такими рядками:

criterion = lambda row: row['countries'] not in countries
not_in = df[df.apply(criterion, axis=1)]

10
FYI, це набагато повільніше, ніж векторизований @DSM soln
Jeff

@Jeff Я б очікував цього, але до цього я повертаюся, коли мені потрібно фільтрувати щось, що недоступне в пандах безпосередньо. (Я збирався сказати "як .startwith або regex зіставлення, але щойно дізнався про Series.str, який має все це!)
Кос


6

Збір можливих рішень з відповідей:

Для IN: df[df['A'].isin([3, 6])]

ДЛЯ НЕ В:

  1. df[-df["A"].isin([3, 6])]

  2. df[~df["A"].isin([3, 6])]

  3. df[df["A"].isin([3, 6]) == False]

  4. df[np.logical_not(df["A"].isin([3, 6]))]


3
Це здебільшого повторює інформацію з інших відповідей. Використання logical_not- це приємний еквівалент ~оператора.
cs95

3
df = pd.DataFrame({'countries':['US','UK','Germany','China']})
countries = ['UK','China']

реалізувати в :

df[df.countries.isin(countries)]

застосовувати не в країнах відпочинку:

df[df.countries.isin([x for x in np.unique(df.countries) if x not in countries])]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.