Filtrar pandas DataFrame por criterios de subcadena

Resuelto euforia asked hace 12 años • 0 respuestas

Tengo un DataFrame de pandas con una columna de valores de cadena. Necesito seleccionar filas en función de coincidencias de cadenas parciales.

Algo como este modismo:

re.search(pattern, cell_in_question) 

devolviendo un booleano. Estoy familiarizado con la sintaxis de df[df['A'] == "hello world"]pero parece que no puedo encontrar una manera de hacer lo mismo con una coincidencia de cadena parcial, por ejemplo 'hello'.

euforia avatar Jul 06 '12 01:07 euforia
Aceptado

Los métodos de cadena vectorizados (es decir Series.str, ) le permiten hacer lo siguiente:

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

Esto está disponible en pandas 0.8.1 y versiones posteriores.

Garrett avatar Jul 17 '2012 21:07 Garrett

Estoy usando pandas 0.14.1 en macos en el cuaderno ipython. Probé la línea propuesta arriba:

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

y obtuve un error:

no se puede indexar con un vector que contenga valores NA/NaN

pero funcionó perfectamente cuando se agregó una condición "==Verdadero", como esta:

df[df['A'].str.contains("Hello|Britain")==True]
sharon avatar Nov 10 '2014 17:11 sharon

¿Cómo selecciono por cadena parcial de un DataFrame de pandas?

Esta publicación está destinada a lectores que quieran

  • buscar una subcadena en una columna de cadena (el caso más simple) como endf1[df1['col'].str.contains(r'foo(?!$)')]
  • buscar múltiples subcadenas (similar a isin), por ejemplo, condf4[df4['col'].str.contains(r'foo|baz')]
  • coincidir con una palabra completa del texto (por ejemplo, "azul" debe coincidir con "el cielo es azul" pero no con "arrendajo azul"), por ejemplo, condf3[df3['col'].str.contains(r'\bblue\b')]
  • unir varias palabras completas
  • Comprenda el motivo detrás de "ValueError: no se puede indexar con un vector que contiene valores NA/NaN" y corríjalo constr.contains('pattern',na=False)

...y me gustaría saber más sobre qué métodos deberían preferirse a otros.

(PD: he visto muchas preguntas sobre temas similares, pensé que sería bueno dejar esto aquí).

Descargo de responsabilidad amistoso , esta publicación es larga .


Búsqueda básica de subcadenas

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

      col
0     foo
1  foobar
2     bar
3     baz

str.containsse puede utilizar para realizar búsquedas de subcadenas o búsquedas basadas en expresiones regulares. La búsqueda se basa de forma predeterminada en expresiones regulares a menos que la deshabilite explícitamente.

A continuación se muestra un ejemplo de búsqueda basada en expresiones regulares,

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

      col
1  foobar

A veces no se requiere la búsqueda de expresiones regulares, así que especifíquela regex=Falsepara deshabilitarla.

#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 cuanto al rendimiento, la búsqueda de expresiones regulares es más lenta que la búsqueda de subcadenas:

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)

Evite utilizar la búsqueda basada en expresiones regulares si no la necesita.

Abordar ValueErrors
A veces, realizar una búsqueda de subcadenas y filtrar el resultado dará como resultado

ValueError: cannot index with vector containing NA / NaN values

Esto generalmente se debe a datos mixtos o NaN en la columna de su objeto,

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)

A cualquier cosa que no sea una cadena no se le pueden aplicar métodos de cadena, por lo que el resultado es NaN (naturalmente). En este caso, especifique na=Falseignorar los datos que no sean cadenas,

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

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

¿Cómo aplico esto a varias columnas a la vez?
La respuesta está en la pregunta. Usar 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

Todas las soluciones siguientes se pueden "aplicar" a varias columnas utilizando el método de columnas apply(lo cual está bien en mi libro, siempre y cuando no tenga demasiadas columnas).

Si tiene un DataFrame con columnas mixtas y desea seleccionar solo las columnas de objeto/cadena, eche un vistazo a select_dtypes.


Búsqueda de subcadenas múltiples

Esto se logra más fácilmente mediante una búsqueda de expresiones regulares utilizando la tubería OR de expresiones regulares.

# 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

También puedes crear una lista de términos y luego unirlos:

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

          col
0     foo abc
1  foobar xyz
3      baz 45

A veces, es aconsejable escapar de los términos en caso de que tengan caracteres que puedan interpretarse como metacaracteres de expresiones regulares . Si sus términos contienen alguno de los siguientes caracteres...

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

Luego, necesitarás usar re.escapepara escapar de ellos:

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

          col
0     foo abc
1  foobar xyz
3      baz 45

re.escapetiene el efecto de escapar de los caracteres especiales para que sean tratados literalmente.

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

Coincidencia de palabras enteras

De forma predeterminada, la búsqueda de subcadenas busca la subcadena/patrón especificado independientemente de si es una palabra completa o no. Para hacer coincidir solo palabras completas, necesitaremos usar expresiones regulares aquí; en particular, nuestro patrón deberá especificar límites de palabras ( \b).

Por ejemplo,

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

                     col
0        the sky is blue
1  bluejay by the window
 

Ahora considere,

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

Búsqueda múltiple de palabras completas

Similar a lo anterior, excepto que agregamos un límite de palabra ( \b) al patrón unido.

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

       col
0  foo abc
3   baz 45

¿ Dónde pse ve así?

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

Una gran alternativa: ¡use listas por comprensión !

¡Porque tú puedes! ¡Y deberías hacerlo! Generalmente son un poco más rápidos que los métodos de cadena, porque los métodos de cadena son difíciles de vectorizar y generalmente tienen implementaciones descabelladas.

En lugar de,

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

Utilice el inoperador dentro de una lista de compilación,

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

       col
0  foo abc
1   foobar

En lugar de,

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

Utilice re.compile(para almacenar en caché su expresión regular) + Pattern.searchdentro de una lista de compilación,

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

      col
1  foobar

Si "col" tiene NaN, entonces en lugar de

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

Usar,

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
 

Más opciones para la coincidencia parcial de cadenas: np.char.find, np.vectorize, DataFrame.query.

Además de str.containslas listas por comprensión, también puede utilizar las siguientes alternativas.

np.char.find
Admite búsquedas de subcadenas (léase: sin expresiones regulares) únicamente.

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

          col
0     foo abc
1  foobar xyz

np.vectorize
Este es un contenedor alrededor de un bucle, pero con menos gastos generales que la mayoría de los strmétodos de pandas.

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

Posibles soluciones de expresiones regulares:

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
Admite métodos de cadena a través del motor Python. Esto no ofrece beneficios de rendimiento visibles, pero aun así es útil para saber si necesita generar sus consultas de forma dinámica.

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

      col
0     foo
1  foobar

Puede encontrar más información sobre queryuna familia de métodos en Evaluar dinámicamente una expresión a partir de una fórmula en Pandas .eval


Prioridad de uso recomendada

  1. (Primero) str.contains, por su sencillez y facilidad de manejo de NaNs y datos mixtos
  2. Lista por comprensión, por su rendimiento (especialmente si sus datos son puramente cadenas)
  3. np.vectorize
  4. (Último)df.query
cs95 avatar Mar 25 '2019 09:03 cs95