Comment tester si une chaîne contient l'une des sous-chaînes d'une liste, dans les pandas?

119

Y a-t-il une fonction qui équivaudrait à une combinaison de df.isin()et df[col].str.contains()?

Par exemple, disons que j'ai la série s = pd.Series(['cat','hat','dog','fog','pet'])et que je veux trouver tous les endroits où scontient l'un des ['og', 'at'], je voudrais tout obtenir sauf «animal de compagnie».

J'ai une solution, mais c'est plutôt inélégant:

searchfor = ['og', 'at']
found = [s.str.contains(x) for x in searchfor]
result = pd.DataFrame[found]
result.any()

Y a-t-il une meilleure manière de faire cela?

ari
la source
Remarque : il existe une solution décrite par @unutbu qui est plus efficace que l'utilisation pd.Series.str.contains. Si les performances sont un problème, cela peut valoir la peine d'être étudié.
jpp
Je recommande vivement de vérifier cette réponse pour une recherche de chaîne partielle à l'aide de plusieurs mots-clés / expressions régulières (faites défiler jusqu'à la sous-rubrique « Recherche de sous- chaînes multiples »).
cs95

Réponses:

219

Une option consiste simplement à utiliser le |caractère regex pour essayer de faire correspondre chacune des sous-chaînes dans les mots de votre série s(toujours en utilisant str.contains).

Vous pouvez construire l'expression régulière en joignant les mots searchforavec |:

>>> searchfor = ['og', 'at']
>>> s[s.str.contains('|'.join(searchfor))]
0    cat
1    hat
2    dog
3    fog
dtype: object

Comme @AndyHayden l'a noté dans les commentaires ci-dessous, faites attention si vos sous-chaînes ont des caractères spéciaux tels que $et ^que vous voulez faire correspondre littéralement. Ces caractères ont des significations spécifiques dans le contexte des expressions régulières et affecteront la correspondance.

Vous pouvez rendre votre liste de sous-chaînes plus sûre en échappant les caractères non alphanumériques avec re.escape:

>>> import re
>>> matches = ['$money', 'x^y']
>>> safe_matches = [re.escape(m) for m in matches]
>>> safe_matches
['\\$money', 'x\\^y']

Les chaînes avec dans cette nouvelle liste correspondront littéralement à chaque caractère lorsqu'elles sont utilisées avec str.contains.

Alex Riley
la source
4
peut-être bon d'ajouter ce lien pandas.pydata.org/pandas-docs/stable/… aussi. À partir de pandas 0.15, les opérations sur les chaînes sont encore plus faciles
goofd
6
une chose dont vous devez faire attention est si une chaîne dans searchfor a des caractères spéciaux de regex (vous pouvez mapper avec re.escape ).
Andy Hayden
@AndyHayden Merci, j'ai amélioré ma réponse pour prendre en compte cette complication.
Alex Riley
Je ne sais pas pourquoi votre méthode ne fonctionne pas avec "str.startswith ('|' .join (searchfor))"
Doo Hyun Shin
48

Vous pouvez utiliser str.containsseul avec un modèle regex en utilisant OR (|):

s[s.str.contains('og|at')]

Ou vous pouvez ajouter la série à un dataframepuis utiliser str.contains:

df = pd.DataFrame(s)
df[s.str.contains('og|at')] 

Production:

0 cat
1 hat
2 dog
3 fog 
je vais
la source
comment le faire pour ET?
JacoSolari
1
@JacoSolari consultez cette réponse stackoverflow.com/questions/37011734/…
James
1
@James oui, merci. Pour terminer, voici le oneliner le plus voté dans cette réponse. df.col.str.contains(r'(?=.*apple)(?=.*banana)',regex=True)
JacoSolari
1

Voici un lambda d'une ligne qui fonctionne également:

df["TrueFalse"] = df['col1'].apply(lambda x: 1 if any(i in x for i in searchfor) else 0)

Contribution:

searchfor = ['og', 'at']

df = pd.DataFrame([('cat', 1000.0), ('hat', 2000000.0), ('dog', 1000.0), ('fog', 330000.0),('pet', 330000.0)], columns=['col1', 'col2'])

   col1  col2
0   cat 1000.0
1   hat 2000000.0
2   dog 1000.0
3   fog 330000.0
4   pet 330000.0

Appliquer Lambda:

df["TrueFalse"] = df['col1'].apply(lambda x: 1 if any(i in x for i in searchfor) else 0)

Production:

    col1    col2        TrueFalse
0   cat     1000.0      1
1   hat     2000000.0   1
2   dog     1000.0      1
3   fog     330000.0    1
4   pet     330000.0    0
Grant Shannon
la source