Como testar se uma string contém uma das substrings em uma lista, em pandas?

119

Existe alguma função que seria equivalente a uma combinação de df.isin()e df[col].str.contains()?

Por exemplo, digamos que eu tenha a série s = pd.Series(['cat','hat','dog','fog','pet'])e desejo encontrar todos os lugares onde scontenha qualquer um de ['og', 'at'], gostaria de obter tudo, exceto 'animal de estimação'.

Eu tenho uma solução, mas é bastante deselegante:

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

Existe uma maneira melhor de fazer isso?

ari
fonte
Nota : Existe uma solução descrita por @unutbu que é mais eficiente do que usar pd.Series.str.contains. Se o desempenho for um problema, vale a pena investigar isso.
jpp
É altamente recomendável verificar esta resposta para pesquisa de sequência parcial usando várias palavras-chave / regexes (role para baixo até o subtítulo " Pesquisa de várias substring ").
cs95

Respostas:

219

Uma opção é apenas usar o |caractere regex para tentar corresponder cada uma das substrings nas palavras em sua série s(ainda usando str.contains).

Você pode construir a regex juntando as palavras searchforcom |:

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

Como @AndyHayden observou nos comentários abaixo, tome cuidado se suas substrings tiverem caracteres especiais como $e ^que você deseja corresponder literalmente. Esses caracteres têm significados específicos no contexto de expressões regulares e afetarão a correspondência.

Você pode tornar sua lista de substrings mais segura escapando caracteres não alfanuméricos com re.escape:

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

As strings com nesta nova lista corresponderão literalmente a cada caractere quando usadas com str.contains.

Alex Riley
fonte
4
talvez seja bom adicionar este link pandas.pydata.org/pandas-docs/stable/… também. A partir do pandas 0.15, as operações com strings são ainda mais fáceis
estúpido
6
uma coisa que você deve tomar cuidado é se uma string em searchfor tem caracteres regex especiais (você pode mapear com re.escape ).
Andy Hayden
@AndyHayden Obrigado, melhorei minha resposta para levar essa complicação em consideração.
Alex Riley
Não sei por que seu método não funciona com "str.startswith ('|' .join (searchfor))"
Doo Hyun Shin
48

Você pode usar str.containssozinho com um padrão regex usando OR (|):

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

Ou você pode adicionar a série a um dataframee usar str.contains:

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

Resultado:

0 cat
1 hat
2 dog
3 fog 
eu vou
fonte
como fazer isso para E?
JacoSolari
1
@JacoSolari, verifique esta resposta stackoverflow.com/questions/37011734/…
James,
1
@James sim, obrigado. Para conclusão, aqui está a linha de votação com mais votos nessa resposta. df.col.str.contains(r'(?=.*apple)(?=.*banana)',regex=True)
JacoSolari
1

Aqui está um lambda de uma linha que também funciona:

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

Entrada:

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

Aplique Lambda:

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

Resultado:

    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
fonte