733 votes

Sélection par chaîne partielle dans un DataFrame pandas

J'ai un DataFrame avec 4 colonnes dont 2 contiennent des valeurs de type chaîne. Je me demandais s'il existait un moyen de sélectionner des lignes sur la base d'une correspondance partielle de chaînes de caractères dans une colonne particulière ?

En d'autres termes, une fonction ou une fonction lambda qui ferait quelque chose comme

re.search(pattern, cell_in_question) 

renvoyant un booléen. Je connais la syntaxe de df[df['A'] == "hello world"] mais je n'arrive pas à trouver un moyen de faire de même avec une correspondance partielle de chaîne de caractères, par exemple 'hello' .

Quelqu'un pourrait-il m'indiquer la bonne direction ?

1176voto

Garrett Points 5477

Basé sur la question github #620 il semble que vous serez bientôt en mesure de faire ce qui suit :

df[df['A'].str.contains("hello")]

Mise à jour : méthodes de chaînes de caractères vectorisées (par exemple, Series.str) sont disponibles dans pandas 0.8.1 et plus.

5 votes

Comment faire pour trouver "Hello" et "Britain" si je veux les trouver avec la condition "OR".

103 votes

Puisque les méthodes str.* traitent le motif d'entrée comme une expression régulière, vous pouvez utiliser df[df['A'].str.contains("Hello|Britain")]

14 votes

Est-il possible de convertir .str.contains à utiliser .query() api ?

348voto

sharon Points 3418

J'ai essayé la solution proposée ci-dessus :

df[df["A"].str.contains("Hello|Britain")]

et j'ai obtenu une erreur :

ValueError : impossible de masquer avec un tableau contenant des valeurs NA / NaN

vous pouvez transformer les valeurs NA en False comme ceci :

df[df["A"].str.contains("Hello|Britain", na=False)]

14 votes

df[df['A'].astype(str).str.contains("Hello|Britain")] a également fonctionné

1 votes

Une autre solution serait : ``` df[df["A"].str.contains("Hello|Britain") == True] ```

266voto

coldspeed Points 111053

Comment sélectionner une chaîne partielle dans un DataFrame pandas ?

Ce billet est destiné aux lecteurs qui veulent

  • recherche d'une sous-chaîne dans une colonne de chaînes de caractères (le cas le plus simple)
  • recherche de sous-chaînes multiples (similaire à isin )
  • correspondre à un mot entier du texte (par exemple, "bleu" devrait correspondre à "le ciel est bleu" mais pas à "geai bleu")
  • associer plusieurs mots entiers
  • Comprendre la raison derrière "ValueError : cannot index with vector containing NA / NaN values" (Erreur de valeur : ne peut pas indexer avec un vecteur contenant des valeurs NA / NaN)

...et aimerait en savoir plus sur les méthodes à privilégier par rapport aux autres.

(P.S. : J'ai vu beaucoup de questions sur des sujets similaires, j'ai pensé qu'il serait bon de laisser ceci ici).

Avis de non-responsabilité ce poste est long .


Recherche de base par sous-chaîne

# setup
df1 = pd.DataFrame({'col': ['foo', 'foobar', 'bar', 'baz']})
df1

      col
0     foo
1  foobar
2     bar
3     baz

str.contains peut être utilisé pour effectuer des recherches par substrats ou des recherches basées sur des expressions géographiques. Par défaut, la recherche est basée sur l'expression géographique, sauf si vous la désactivez explicitement.

Voici un exemple de recherche basée sur les expressions rationnelles,

# find rows in `df1` which contain "foo" followed by something
df1[df1['col'].str.contains(r'foo(?!$)')]

      col
1  foobar

Parfois, la recherche par regex n'est pas nécessaire, alors spécifiez regex=False pour le désactiver.

#select all rows containing "foo"
df1[df1['col'].str.contains('foo', regex=False)]
# same as df1[df1['col'].str.contains('foo')] but faster.

      col
0     foo
1  foobar

En termes de performances, la recherche par expressions rationnelles est plus lente que la recherche par substrats :

df2 = pd.concat([df1] * 1000, ignore_index=True)

%timeit df2[df2['col'].str.contains('foo')]
%timeit df2[df2['col'].str.contains('foo', regex=False)]

6.31 ms ± 126 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.8 ms ± 241 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Évitez d'utiliser la recherche basée sur les expressions rationnelles si vous n'en avez pas besoin.

Adressez-vous à ValueError s
Parfois, en effectuant une recherche par sous-chaîne et en filtrant sur le résultat, on obtient

ValueError: cannot index with vector containing NA / NaN values

Cela est généralement dû à des données mixtes ou à des NaN dans votre colonne d'objets,

s = pd.Series(['foo', 'foobar', np.nan, 'bar', 'baz', 123])
s.str.contains('foo|bar')

0     True
1     True
2      NaN
3     True
4    False
5      NaN
dtype: object

s[s.str.contains('foo|bar')]
# ---------------------------------------------------------------------------
# ValueError                                Traceback (most recent call last)

Tout ce qui n'est pas une chaîne de caractères ne peut pas se voir appliquer des méthodes de chaîne de caractères, le résultat est donc NaN (naturellement). Dans ce cas, il faut spécifier na=False pour ignorer les données qui ne sont pas des chaînes de caractères,

s.str.contains('foo|bar', na=False)

0     True
1     True
2    False
3     True
4    False
5    False
dtype: bool

Comment puis-je appliquer cela à plusieurs colonnes à la fois ?
La réponse est dans la question. Utilisez DataFrame.apply :

# `axis=1` tells `apply` to apply the lambda function column-wise.
df.apply(lambda col: col.str.contains('foo|bar', na=False), axis=1)

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

Toutes les solutions ci-dessous peuvent être "appliquées" à plusieurs colonnes en utilisant la fonction "column-wise". apply (ce qui n'est pas un problème à mon avis, tant que vous n'avez pas trop de colonnes).

Si vous avez un DataFrame avec des colonnes mixtes et que vous souhaitez sélectionner uniquement les colonnes objet/chaîne, consultez la rubrique select_dtypes .


Recherche de sous-chaînes multiples

La méthode la plus simple consiste à effectuer une recherche regex à l'aide du tube regex OR.

# Slightly modified example.
df4 = pd.DataFrame({'col': ['foo abc', 'foobar xyz', 'bar32', 'baz 45']})
df4

          col
0     foo abc
1  foobar xyz
2       bar32
3      baz 45

df4[df4['col'].str.contains(r'foo|baz')]

          col
0     foo abc
1  foobar xyz
3      baz 45

Vous pouvez également créer une liste de termes, puis les joindre :

terms = ['foo', 'baz']
df4[df4['col'].str.contains('|'.join(terms))]

          col
0     foo abc
1  foobar xyz
3      baz 45

Parfois, il est judicieux d'échapper vos termes au cas où ils comporteraient des caractères pouvant être interprétés comme métacaractères regex . Si vos termes contiennent l'un des caractères suivants...

. ^ $ * + ? { } [ ] \ | ( )

Ensuite, vous devrez utiliser re.escape a s'échapper les :

import re
df4[df4['col'].str.contains('|'.join(map(re.escape, terms)))]

          col
0     foo abc
1  foobar xyz
3      baz 45

re.escape a pour effet d'échapper les caractères spéciaux afin qu'ils soient traités littéralement.

re.escape(r'.foo^')
# '\\.foo\\^'

Mot(s) entier(s) correspondant(s)

Par défaut, la recherche de sous-chaîne recherche la sous-chaîne/motif spécifié, qu'il s'agisse d'un mot complet ou non. Pour ne faire correspondre que des mots complets, nous devrons utiliser des expressions régulières. En particulier, notre motif devra spécifier les limites des mots ( \b ).

Par exemple,

df3 = pd.DataFrame({'col': ['the sky is blue', 'bluejay by the window']})
df3

                     col
0        the sky is blue
1  bluejay by the window

Maintenant, réfléchissez,

df3[df3['col'].str.contains('blue')]

                     col
0        the sky is blue
1  bluejay by the window

v/s

df3[df3['col'].str.contains(r'\bblue\b')]

               col
0  the sky is blue

Recherche de mots entiers multiples

Similaire à ce qui précède, sauf que nous ajoutons une limite de mot ( \b ) au motif joint.

p = r'\b(?:{})\b'.format('|'.join(map(re.escape, terms)))
df4[df4['col'].str.contains(p)]

       col
0  foo abc
3   baz 45

Donde p ressemble à ça,

p
# '\\b(?:foo|baz)\\b'

Une excellente alternative : Utilisez Compréhensions de listes !

Parce que vous le pouvez ! Et vous devriez ! Elles sont généralement un peu plus rapides que les méthodes de type "string", car ces dernières sont difficiles à vectoriser et ont généralement des implémentations bouclées.

Au lieu de,

df1[df1['col'].str.contains('foo', regex=False)]

Utilisez le in à l'intérieur d'une liste comp,

df1[['foo' in x for x in df1['col']]]

       col
0  foo abc
1   foobar

Au lieu de,

regex_pattern = r'foo(?!$)'
df1[df1['col'].str.contains(regex_pattern)]

Utilisez re.compile (pour mettre en cache votre regex) + Pattern.search à l'intérieur d'une liste comp,

p = re.compile(regex_pattern, flags=re.IGNORECASE)
df1[[bool(p.search(x)) for x in df1['col']]]

      col
1  foobar

Si "col" contient des NaN, alors au lieu de

df1[df1['col'].str.contains(regex_pattern, na=False)]

Utilisez,

def try_search(p, x):
    try:
        return bool(p.search(x))
    except TypeError:
        return False

p = re.compile(regex_pattern)
df1[[try_search(p, x) for x in df1['col']]]

      col
1  foobar

Plus d'options pour la correspondance partielle des chaînes : np.char.find , np.vectorize , DataFrame.query .

En plus de str.contains et les compréhensions de liste, vous pouvez également utiliser les alternatives suivantes.

np.char.find
Ne prend en charge que les recherches par substrats (lire : pas de regex).

df4[np.char.find(df4['col'].values.astype(str), 'foo') > -1]

          col
0     foo abc
1  foobar xyz

np.vectorize
Il s'agit d'une enveloppe autour d'une boucle, mais avec moins de surcharge que la plupart des pandas. str méthodes.

f = np.vectorize(lambda haystack, needle: needle in haystack)
f(df1['col'], 'foo')
# array([ True,  True, False, False])

df1[f(df1['col'], 'foo')]

       col
0  foo abc
1   foobar

Solutions Regex possibles :

regex_pattern = r'foo(?!$)'
p = re.compile(regex_pattern)
f = np.vectorize(lambda x: pd.notna(x) and bool(p.search(x)))
df1[f(df1['col'])]

      col
1  foobar

DataFrame.query
Supporte les méthodes de chaînes de caractères à travers le moteur python. Cela n'offre aucun avantage visible en termes de performances, mais il est néanmoins utile de le savoir si vous avez besoin de générer dynamiquement vos requêtes.

df1.query('col.str.contains("foo")', engine='python')

      col
0     foo
1  foobar

Plus d'informations sur query y eval La famille de méthodes se trouve à l'adresse suivante Évaluation dynamique d'expressions dans pandas avec pd.eval() .


Priorité d'utilisation recommandée

  1. (Première) str.contains pour sa simplicité et sa facilité à gérer les NaN et les données mixtes.
  2. Les compréhensions de listes, pour leurs performances (surtout si vos données sont purement des chaînes de caractères).
  3. np.vectorize
  4. (Dernière) df.query

1 votes

Pourriez-vous éditer la méthode correcte à utiliser lors de la recherche d'une chaîne dans deux colonnes ou plus ? En principe : any(needle in haystack for needling in ['foo', 'bar'] and haystack in (df['col'], df['col2'])) et des variations j'ai essayé tous les choke (il se plaint de any() et à juste titre... Mais la documentation n'est absolument pas claire sur la façon de faire une telle requête.

1 votes

@DenisdeBernardy df[['col1', 'col2']].apply(lambda x: x.str.contains('foo|bar')).any(axis=1)

0 votes

@cs95 Extraction des lignes dont la sous-chaîne contient un espace après + dans pandas df La réponse a été donnée rapidement, mais vous pourriez vouloir y jeter un coup d'œil.

61voto

Philipp Schwarz Points 4503

Si quelqu'un se demande comment résoudre un problème connexe : "Sélectionner la colonne par chaîne partielle"

Utilisez :

df.filter(like='hello')  # select columns which contain the word hello

Et pour sélectionner des lignes par correspondance partielle de chaînes de caractères, passez la commande axis=0 à filtrer :

# selects rows which contain the word hello in their index label
df.filter(like='hello', axis=0)

7 votes

Cela peut se résumer à : df.loc[:, df.columns.str.contains('a')]

19 votes

Qui peut être encore distillé en df.filter(like='a')

0 votes

Cela devrait être une question + réponse propre, déjà 50 personnes l'ont cherché...

29voto

Christian Points 31

Note rapide : si vous voulez faire une sélection basée sur une chaîne partielle contenue dans l'index, essayez ce qui suit :

df['stridx']=df.index
df[df['stridx'].str.contains("Hello|Britain")]

5 votes

Vous pouvez juste df[df.index.to_series().str.contains('LLChit')]

1 votes

Pour être encore plus concis, to_series n'est pas nécessaire : df[df.index.str.contains('Hello|Britain')]

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X