¿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?


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.


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 

¿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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.