Jak filtrować ramkę danych Pandas za pomocą „in” i „not in” jak w SQL

432

Jak mogę uzyskać odpowiedniki SQL INi NOT IN?

Mam listę z wymaganymi wartościami. Oto scenariusz:

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

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

Mój obecny sposób na zrobienie tego jest następujący:

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'])]

Ale to wygląda na okropną kludge. Czy ktoś może to poprawić?

LondonRob
źródło
1
Myślę, że twoje rozwiązanie jest najlepszym rozwiązaniem. Twój może obejmować IN, NOT_IN wielu kolumn.
Bruce Jung,
Czy chcesz przetestować jedną kolumnę czy wiele kolumn?
smci
1
Powiązane (wydajność / wewnętrzne pandy): Wydajność Pandas pd.Series.isin z zestawem kontra tablica
jpp

Odpowiedzi:

820

Możesz użyć pd.Series.isin.

Do „IN” użyj: something.isin(somewhere)

Lub dla „NOT IN”: ~something.isin(somewhere)

Jako działający przykład:

>>> 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
DSM
źródło
1
Tylko dla ciebie, @LondonRob miał jego jako DataFrame, a twoja to seria. DataFrame isindodano w .13.
TomAugspurger
Wszelkie sugestie, jak to zrobić z pandami 0.12.0? To jest aktualna wersja. (Może powinienem tylko poczekać na 0.13 ?!)
LondonRob
Jeśli faktycznie masz do czynienia z tablicami 1-wymiarowymi (jak w przykładzie), w pierwszej linii użyj Serii zamiast DataFrame, jak np. @DSM:df = pd.Series({'countries':['US','UK','Germany','China']})
TomAugspurger
2
@TomAugspurger: jak zwykle pewnie coś mi brakuje. df, zarówno mój, jak i jego, jest DataFrame. countriesto lista. df[~df.countries.isin(countries)]produkuje a DataFrame, a nie Seriesi wydaje się działać nawet w wersji 0.11.0.dev-14a04dd.
DSM
7
Ta odpowiedź jest myląca, ponieważ ciągle używasz countrieszmiennej. Cóż, OP robi to i jest to dziedziczone, ale to, że coś zostało wcześniej zrobione źle, nie usprawiedliwia teraz tego źle.
ifly6
63

Alternatywne rozwiązanie wykorzystujące metodę .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
MaxU
źródło
10
@LondonRob querynie jest już eksperymentalny.
Paul Rougieux,
38

Jak zaimplementować „in” i „not in” dla pand DataFrame?

Pandy oferują dwie metody: Series.isiniDataFrame.isin dla serii i DataFrames, odpowiednio.


Filtruj ramkę danych na podstawie JEDNEJ kolumny (dotyczy również serii)

Najczęstszym scenariuszem jest zastosowanie isinwarunku do określonej kolumny w celu filtrowania wierszy w ramce danych.

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.isinakceptuje różne typy jako dane wejściowe. Poniżej przedstawiono wszystkie ważne sposoby uzyskania tego, co chcesz:

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

Filtruj według WIELU kolumn

Czasami warto zastosować kontrolę członkostwa „w” z kilkoma wyszukiwanymi hasłami w wielu kolumnach,

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']

Aby zastosować isinwarunek do obu kolumn „A” i „B”, użyj DataFrame.isin:

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

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

Z tego, aby zachować wiersze, w których znajduje się co najmniej jedna kolumnaTrue , możemy użyć anywzdłuż pierwszej osi:

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

Pamiętaj, że jeśli chcesz przeszukać każdą kolumnę, po prostu pomiń krok wyboru kolumny i zrób to

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

Podobnie, aby zachować wiersze, w których znajdują się WSZYSTKIE kolumnyTrue , użyj ich allw taki sam sposób jak poprzednio.

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

   A  B  C
0  x  w  0

Godne uwagi jest mowa o: numpy.isin, query, listowych (ciąg danych)

Oprócz metod opisanych powyżej, można również użyć odpowiednik 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

Dlaczego warto to rozważyć? Funkcje NumPy są zwykle nieco szybsze niż ich odpowiedniki pandy z powodu niższego obciążenia. Ponieważ jest to operacja elementarna, która nie zależy od wyrównania indeksu, jest bardzo niewiele sytuacji, w których ta metoda nie jest odpowiednim zamiennikiem pandisin .

Procedury pand są zwykle iteracyjne podczas pracy z łańcuchami, ponieważ operacje na łańcuchach są trudne do wektoryzacji. Istnieje wiele dowodów sugerujących, że zrozumienie listy będzie tutaj szybsze. . Teraz uciekamy się do inczeku.

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

Jednak określenie tego jest nieporęczne, więc nie używaj go, chyba że wiesz, co robisz.

Wreszcie, istnieje również DataFrame.queryzostała pokryta w tej odpowiedzi . numexpr FTW!

cs95
źródło
Podoba mi się, ale co jeśli chcę porównać kolumnę w df3, która jest w kolumnie df1? Jakby to wyglądało?
Arthur D. Howland
12

Zwykle robiłem ogólne filtrowanie według wierszy takich jak to:

criterion = lambda row: row['countries'] not in countries
not_in = df[df.apply(criterion, axis=1)]
Kos
źródło
10
Do twojej wiadomości, jest to znacznie wolniejsze niż @DSM soln, który jest wektoryzowany
Jeff
@Jeff Spodziewałbym się tego, ale do tego wracam, gdy muszę bezpośrednio przefiltrować coś niedostępnego w pandach. (Chciałem powiedzieć „lubię .start z dopasowaniem wyrażenia regularnego, ale właśnie dowiedziałem się o Series.str, który ma to wszystko!)
Kos
7

Chciałem odfiltrować wiersze dfbc o identyfikatorze BUSINESS_ID, który również znajdował się w identyfikatorze BUSINESS_ID parametru dfProfilesBusIds

dfbc = dfbc[~dfbc['BUSINESS_ID'].isin(dfProfilesBusIds['BUSINESS_ID'])]
Sam Henderson
źródło
5
Możesz zanegować isin (jak to zrobiono w zaakceptowanej odpowiedzi) zamiast porównywać do False
OneCricketeer
6

Zestawienie możliwych rozwiązań z odpowiedzi:

Dla w: df[df['A'].isin([3, 6])]

Dla NOT IN:

  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]))]

Abhishek Gaur
źródło
3
To w większości powtarza informacje z innych odpowiedzi. Używanie logical_notjest kęsem ~operatora.
cs95
3
df = pd.DataFrame({'countries':['US','UK','Germany','China']})
countries = ['UK','China']

zaimplementować w :

df[df.countries.isin(countries)]

nie wdrażać jak w pozostałych krajach:

df[df.countries.isin([x for x in np.unique(df.countries) if x not in countries])]
Ioannis Nasios
źródło