Comment filtrer la trame de données Pandas en utilisant 'in' et 'not in' comme dans SQL

434

Comment puis-je atteindre les équivalents de SQL INetNOT IN ?

J'ai une liste avec les valeurs requises. Voici le scénario:

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

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

Ma façon actuelle de procéder est la suivante:

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

Mais cela semble être une horrible culpabilité. Quelqu'un peut-il l'améliorer?

LondonRob
la source
1
Je pense que votre solution est la meilleure solution. Le vôtre peut couvrir IN, NOT_IN de plusieurs colonnes.
Bruce Jung
Voulez-vous tester sur une seule colonne ou plusieurs colonnes?
smci
1
Connexes (performances / pandas internes): performances de Pandas pd.Series.isin avec set versus array
jpp

Réponses:

821

Vous pouvez utiliser pd.Series.isin .

Pour une utilisation "IN": something.isin(somewhere)

Ou pour "PAS DANS": ~something.isin(somewhere)

Comme exemple travaillé:

>>> 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
la source
1
Juste un FYI, le @LondonRob avait le sien comme DataFrame et le vôtre est une série. DataFrame a isinété ajouté en .13.
TomAugspurger
Des suggestions sur la façon de le faire avec les pandas 0.12.0? C'est la version actuelle. (Peut-être que je devrais juste attendre 0,13?!)
LondonRob
Si vous avez réellement affaire à des tableaux unidimensionnels (comme dans votre exemple), alors sur votre première ligne, utilisez une série au lieu d'un DataFrame, comme @DSM utilisé:df = pd.Series({'countries':['US','UK','Germany','China']})
TomAugspurger
2
@TomAugspurger: comme d'habitude, il me manque probablement quelque chose. df, le mien et le sien, est un DataFrame. countriesest une liste. df[~df.countries.isin(countries)]produit un DataFrame, pas un Series, et semble fonctionner même en arrière dans 0.11.0.dev-14a04dd.
DSM
7
Cette réponse prête à confusion car vous continuez à réutiliser la countriesvariable. Eh bien, le PO le fait, et c'est hérité, mais que quelque chose soit mal fait avant ne justifie pas de le faire mal maintenant.
ifly6
63

Solution alternative qui utilise la méthode .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
la source
10
@LondonRob queryn'est plus expérimental.
Paul Rougieux
39

Comment implémenter 'in' et 'not in' pour un pandas DataFrame?

Pandas propose deux méthodes: Series.isinet DataFrame.isinpour Series et DataFrames, respectivement.


Filtrer le DataFrame basé sur UNE colonne (s'applique également à la série)

Le scénario le plus courant consiste à appliquer une isincondition sur une colonne spécifique pour filtrer les lignes d'un 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.isinaccepte différents types d'entrée. Voici tous les moyens valables d'obtenir ce que vous voulez:

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

Filtrer sur BEAUCOUP de colonnes

Parfois, vous souhaiterez appliquer une vérification d'adhésion "in" avec certains termes de recherche sur plusieurs colonnes,

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

Pour appliquer la isincondition aux deux colonnes "A" et "B", utilisez DataFrame.isin:

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

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

De là, pour conserver les lignes où se trouve au moins une colonneTrue , nous pouvons utiliser le anylong du premier axe:

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

Notez que si vous souhaitez rechercher chaque colonne, vous omettez simplement l'étape de sélection de colonne et

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

De même, pour conserver les lignes là où se trouvent TOUTES les colonnesTrue , utilisez allde la même manière que précédemment.

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

   A  B  C
0  x  w  0

Notable Mentionne: numpy.isin, query, compréhensions liste (données de chaîne)

En plus des méthodes décrites ci - dessus, vous pouvez également utiliser l'équivalent 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

Pourquoi vaut-il la peine d'envisager? Les fonctions NumPy sont généralement un peu plus rapides que leurs équivalents pandas en raison d'une surcharge réduite. Comme il s'agit d'une opération élément par élément qui ne dépend pas de l'alignement des index, il existe très peu de situations où cette méthode ne remplace pas convenablement les pandas.isin .

Les routines Pandas sont généralement itératives lorsque vous travaillez avec des chaînes, car les opérations sur les chaînes sont difficiles à vectoriser. De nombreuses preuves suggèrent que la compréhension des listes sera plus rapide ici. . Nous avons recours à un inchèque maintenant.

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

Cependant, il est beaucoup plus difficile à spécifier, alors ne l'utilisez pas à moins de savoir ce que vous faites.

Enfin, il y a aussi DataFrame.queryce qui a été couvert dans cette réponse . numexpr FTW!

cs95
la source
J'aime ça, mais que faire si je veux comparer une colonne dans df3 qui est dans la colonne df1? À quoi cela ressemblerait-il?
Arthur D. Howland,
12

Je fais généralement un filtrage générique sur des lignes comme celle-ci:

criterion = lambda row: row['countries'] not in countries
not_in = df[df.apply(criterion, axis=1)]
Kos
la source
10
Pour info, c'est beaucoup plus lent que @DSM soln qui est vectorisé
Jeff
@Jeff Je m'y attendais, mais c'est à cela que je reviens quand j'ai besoin de filtrer directement quelque chose d'indisponible chez les pandas. (J'étais sur le point de dire "comme .startwith ou regex matching, mais je viens de découvrir Series.str qui a tout ça!)
Kos
7

Je voulais filtrer les lignes dfbc qui avaient un BUSINESS_ID qui se trouvait également dans le BUSINESS_ID de dfProfilesBusIds

dfbc = dfbc[~dfbc['BUSINESS_ID'].isin(dfProfilesBusIds['BUSINESS_ID'])]
Sam Henderson
la source
5
Vous pouvez nier l'isin (comme cela est fait dans la réponse acceptée) plutôt que de le comparer à False
OneCricketeer
6

Collecte des solutions possibles à partir des réponses:

Pour IN: df[df['A'].isin([3, 6])]

Pour PAS DANS:

  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
la source
3
Cela répète principalement des informations provenant d'autres réponses. L'utilisation logical_notest un équivalent bouchée de l' ~opérateur.
cs95
3
df = pd.DataFrame({'countries':['US','UK','Germany','China']})
countries = ['UK','China']

mettre en œuvre dans :

df[df.countries.isin(countries)]

mettre en œuvre pas dans comme dans des pays de repos:

df[df.countries.isin([x for x in np.unique(df.countries) if x not in countries])]
Ioannis Nasios
la source