¿Cómo probar si una cadena contiene una de las subcadenas en una lista, en pandas?

119

¿Existe alguna función que sea equivalente a una combinación de df.isin()y df[col].str.contains()?

Por ejemplo, digamos que tengo la serie s = pd.Series(['cat','hat','dog','fog','pet'])y quiero encontrar todos los lugares donde scontiene cualquiera de ['og', 'at'], me gustaría obtener todo menos 'mascota'.

Tengo una solución, pero es bastante poco elegante:

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

¿Hay una mejor manera de hacer esto?

ari
fuente
Nota : Hay una solución descrita por @unutbu que es más eficiente que usar pd.Series.str.contains. Si el rendimiento es un problema, vale la pena investigarlo.
jpp
Recomiendo encarecidamente consultar esta respuesta para la búsqueda de cadenas parciales utilizando varias palabras clave / expresiones regulares (desplácese hacia abajo hasta el subtítulo " Búsqueda de subcadenas múltiples ").
cs95

Respuestas:

219

Una opción es simplemente usar el |carácter regex para intentar hacer coincidir cada una de las subcadenas en las palabras de su Serie s(todavía se usa str.contains).

Puede construir la expresión regular uniendo las palabras searchforcon |:

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

Como @AndyHayden señaló en los comentarios a continuación, tenga cuidado si sus subcadenas tienen caracteres especiales como $y ^que desea hacer coincidir literalmente. Estos caracteres tienen significados específicos en el contexto de expresiones regulares y afectarán la coincidencia.

Puede hacer que su lista de subcadenas sea más segura escapando los caracteres no alfanuméricos con re.escape:

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

Las cadenas de esta nueva lista coincidirán literalmente con cada carácter cuando se usen con str.contains.

Alex Riley
fuente
4
tal vez sea bueno agregar este enlace pandas.pydata.org/pandas-docs/stable/… también. A partir de 0,15 pandas, las operaciones de cadenas son aún más fácil
goofd
6
una cosa con la que debe tener cuidado es si una cadena en searchfor tiene caracteres regex especiales (puede mapear con re.escape ).
Andy Hayden
@AndyHayden Gracias, he mejorado mi respuesta para tener en cuenta esta complicación.
Alex Riley
No sé por qué su método no funciona con "str.startswith ('|' .join (searchfor))"
Doo Hyun Shin
48

Puede usar str.containssolo con un patrón de expresiones regulares usando OR (|):

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

O puede agregar la serie a y dataframeluego usar str.contains:

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

Salida:

0 cat
1 hat
2 dog
3 fog 
l'l'l
fuente
¿cómo hacerlo para Y?
JacoSolari
1
@JacoSolari echa un vistazo a esta respuesta stackoverflow.com/questions/37011734/…
James
1
@James sí, gracias. Para completar, aquí está el delineador más votado en esa respuesta. df.col.str.contains(r'(?=.*apple)(?=.*banana)',regex=True)
JacoSolari
1

Aquí hay una lambda de una línea que también 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

Aplicar Lambda:

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

Salida:

    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
fuente