Python - Filtrar pandas DataFrame por criterios de subcadena

CorePress2024-01-25  10

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 valor booleano. Estoy familiarizado con la sintaxis de df[df['A'] == "hola mundo"] pero parece que no puedo encontrar una manera de hacer lo mismo con una coincidencia de cadena parcial, digamos 'hola'.



------------------------------------

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 superiores.

aRespondido

17 de julio de 2012 a las 21:52

Garret

Garrett

48,1k

6

6 insignias de oro

63

63 insignias de plata

51

51 bronce insignias

11

7

¿Cómo hacemos para decir "Hola"? y "Gran Bretaña" si quiero encontrarlos con "O" condición.

- Alma Solitaria

27/06/2013 a las 16:41

137

Dado que los métodos str.* tratan el patrón de entrada como una expresión regular, puedes usar df[df['A'].str.contains("Hello|Britain")]

- Garrett

27/06/2013 a las 19:20

17

¿Es posible convertir .str.contains para usar la API .query()?

- zyxue

1 de marzo de 2017 a las 17:25

5

@zyxue Selecciona filas mediante consulta de cadena parcial con pandas

- Franck Dernoncourt

5/07/2017 a las 18:01

10

df[df['value'].astype(str).str.contains('1234.+')] para filtrar columnas que no son de tipo cadena.

- François Leblanc

13/02/2018 a las 20:22



------------------------------------

Estoy usando pandas 0.14.1 en macos en ipython notebook. 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 agrega un mensaje "==True" Se agregó una condición, así:

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

2

19

df[df['A'].astype(str).str.contains("Hello|Britain")] también funcionó

Nagabhushan S N

5 febrero 2020 a las 15:05

2

Otra solución sería: ``` df[df["A"].str.contains("Hola|Gran Bretaña") == True] ```

-Allan

15 de septiembre de 2021 a las 19:36



------------------------------------

¿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 (lacaso más simple) como en df1[df1['col'].str.contains(r'foo(?!$)')] busque múltiples subcadenas (similar a isin), por ejemplo, con df4[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, con df3[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 con str.contains('pattern',na=False)

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

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

Descargo de responsabilidad, esta publicación es larga.

Mar de subcadena básicarch
# setup
df1 = pd.DataFrame({'col': ['foo', 'foobar', 'bar', 'baz']})
df1

      col
0     foo
1  foobar
2     bar
3     baz

str.contains se 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 deshabilites explícitamente.

Aquí hay 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 especifique regex=False para desactivarla.

#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 errores de valor 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 suele deberse 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, especifiquena=False para ignorar 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. Utilice 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 usando el método de aplicación por columnas (lo cual está bien en mi opinión, siempre y cuando no tengas 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 tuLos términos contienen cualquiera de los siguientes caracteres...

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

Luego, necesitarás usar re.escape para 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.escape tiene 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

Donde p se ve así,

p
# '\b(?:foo|baz)\b'
Una gran alternativa: U¡Enumere las comprensiones!

¡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 operador in 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)]

Usa re.compile (para almacenar en caché tu expresión regular) + Pattern.search dentro 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)]

Uso,

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 coincidencia parcial de cadenas: np.char.find, np.vectorize, DataFrame.query.

Además de str.contains y listas por comprensión, también puedes utilizar las siguientes alternativas.

np.char.buscar 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.vectorizar Esta es una envoltura alrededor de unbucle, pero con menos gastos generales que la mayoría de los métodos str 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

Soluciones Regex posibles:

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

Marco de datos.consulta 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 la familia de métodos de consulta y evaluación en Evaluar dinámicamente una expresión a partir de una fórmula en Pandas.

Prioridad de uso recomendada (Primero) str.contains, por su simplicidad y facilidad de manejo de NaN y datos mixtos Lista por comprensión, por su rendimiento (especialmente si sus datos son puramente cadenas) np.vectorizar (Último) df.queryRespondido al

25 de marzo de 2019 a las 9:58

cs95

cs95

388k

98

98 insignias de oro

716

716 insignias de plata

770

770 insignias de bronce

16

1

¿Podrías editar el método correcto a utilizar al buscar una cadena en dos o más columnas? Básicamente: any(aguja en un pajar para punzonar en ['foo', 'bar'] y pajar en (df['col'], df[' ;col2'])) yvAriations Probé todos los estranguladores (se queja de any() y con razón... Pero el documento no tiene muy claro cómo hacer tal consulta.

- Denis de Bernardy

16/07/2019 a las 11:37

1

@DenisdeBernardy df[['col1', 'col2']].apply(lambda x: x.str.contains('foo|bar') ).cualquier(eje=1)

-cs95

28/07/2019 a las 6:30

1

@00schneider r en este caso se utiliza para indicar un literal de cadena sin formato. Estos facilitan la escritura de cadenas de expresiones regulares. stackoverflow.com/q/2081640

-cs95

13/08/2019 a las 13:11

1

@arno_v ¡Es bueno saberlo, parece que el rendimiento de los pandas está mejorando!

-cs95

5 de agosto de 2021 a las 9:53

1

¡¡Extremadamente útil!! Especialmente el 'reimportación' Las características cambian las reglas del juego. ¡Chapeau!

- Lorenzo Bassetti

7 de septiembre de 2021 a las 18:35



------------------------------------

Si alguien se pregunta cómo realizar un problema relacionado: "Seleccionar columna por cadena parcial"

Uso:

df.filter(like='hello')  # select columns which contain the word hello

Y para seleccionar filas por coincidencia parcial de cadenas, pase axis=0 para filtrar:

# selects rows which contain the word hello in their index label
df.filter(like='hello', axis=0)  
Respondido

12/10/2016 a las 21:04

Philipp Schwarz

Philipp Schwarz

18,8k

5

5 insignias de oro

33

33 insignias de plata

36

36 bronce insignias

4

8

Esto se puede resumir en: df.loc[:, df.columns.str.contains('a')]

-elPastor

17/06/2017 a las 21:53

21

que se puede resumir en df.filter(like='a')

- Ted Petrou

25/10/2017 a las 2:57

Esta debería ser una pregunta + respuesta propia, ya la han buscado 50 personas...

PV8

9 enero 2020 a las 9:35

2

La pregunta de @PV8 ya existe: stackoverflow.com/questions/31551412/…. Perocuando busco en Google "pandas Seleccionar columna por cadena parcial", este hilo aparece primero

- Philipp Schwarz

9 enero 2020 a las 9:37



------------------------------------

Nota rápida: si desea realizar una selección basada en una cadena parcial contenida en el índice, intente lo siguiente:

df['stridx']=df.index
df[df['stridx'].str.contains("Hello|Britain")]

2

5

Puedes simplemente df[df.index.to_series().str.contains('LLChit')]

- Yury Bayda

8 de mayo de 2015 a las 21:27

1

Para ser aún más conciso, to_series no es necesario: df[df.index.str.contains('Hello|Britain')]

-tdy

2 de noviembre de 2021 a las 12:05



------------------------------------

Si necesita realizar una búsqueda que no distinga entre mayúsculas y minúsculas para una cadena en una columna del marco de datos de pandas:

df[df['A'].str.contains("hello", case=False)]



------------------------------------

Supongamos que tiene el siguiente DataFrame:

>>> df = pd.DataFrame([['hello', 'hello world'], ['abcd', 'defg']], columns=['a','b'])
>>> df
       a            b
0  hello  hello world
1   abcd         defg

Siempre puedes utilizar el operador in en una expresión lambda para crear tu filtro.

>>> df.apply(lambda x: x['a'] in x['b'], axis=1)
0     True
1    False
dtype: bool

El truco aquí consiste en utilizar la opción axis=1 en apply para pasar elementos a la función lambda fila por fila, en lugar de columna por columna.

2

¿Cómo modifico lo anterior para decir que x['a'] existe solo al principio de x['b']?

- Datos complejos

18/10/2016 a las 20:23

1

aplicar es una mala idea aquí en términos de rendimiento y memoria. Vea esta respuesta.

-cs95

25/03/2019 a las 10:27



------------------------------------

Puedes intentar considerarlos como una cadena como:

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

30 de marzo de 2021 a las 12:06

Angeline Kingsteena

Angeline Kingsteena

111

1

1 insignia plateada

7

7 insignias de bronce



------------------------------------

Esto es lo que terminé haciendo para coincidencias de cadenas parciales. Si alguien tiene una forma más eficiente dehaciendo esto por favor házmelo saber.

def stringSearchColumn_DataFrame(df, colName, regex):
    newdf = DataFrame()
    for idx, record in df[colName].iteritems():

        if re.search(regex, record):
            newdf = concat([df[df[colName] == record], newdf], ignore_index=True)

    return newdf

4

3

Debería ser de 2 a 3 veces más rápido si compilas regex antes del bucle: regex = re.compile(regex) y luego si regex.search(record)

-MarkokraM

10/04/2014 a las 13:56

1

@MarkokraM docs.python.org/3.6/library/re.html#re.compile dice que las expresiones regulares más recientes se almacenan en caché para usted, por lo que no necesita compilarlas usted mismo.

- Tipi

20/06/2018 a las 19:36

No utilice iteritems para iterar sobre un DataFrame. Ocupa el último lugar en términos de pandorabilidad y rendimiento

-cs95

25/03/2019 a las 10:26

la iteración sobre marcos de datos anula todo el propósito depandas. Utilice la solución de Garrett en su lugar

-dhruvm

22 de julio de 2020 a las 2:12



------------------------------------

El uso de contiene no funcionó bien para mi cadena con caracteres especiales. Aunque encontré que funcionó.

df[df['A'].str.find("hello") != -1]



------------------------------------

Un ejemplo más generalizado: si busca partes de una palabra O palabras específicas en una cadena:

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

Partes específicas de una oración o palabra:

searchfor = '.*cat.*hat.*|.*the.*dog.*'

Crear columna que muestre las filas afectadas (siempre se puede filtrareliminar según sea necesario)

df["TrueFalse"]=df['col1'].str.contains(searchfor, regex=True)

    col1             col2           TrueFalse
0   cat andhat       1000.0         True
1   hat              2000000.0      False
2   the small dog    1000.0         True
3   fog              330000.0       False
4   pet 3            30000.0        False



------------------------------------

Tal vez quieras buscar texto en todas las columnas del marco de datos de Pandas, y no solo en el subconjunto de ellas. En este caso, el siguiente código será de ayuda.

df[df.apply(lambda row: row.astype(str).str.contains('String To Find').any(), axis=1)]

Advertencia. Este método es relativamente lento, aunque conveniente.



------------------------------------

Algo similar a la respuesta de @cs95, pero aquí no es necesario especificar un motor:

df.query('A.str.contains("hello").values')



------------------------------------

Hay respuestas anteriores a esta que logran la función solicitada; de todos modos, me gustaría mostrar la forma más general:

df.filter(regex=".*STRING_YOU_LOOK_FOR.*")

De esta manera obtendremos la columna que buscas independientemente de la forma en que esté escrita.

(Obviamente, debes escribir la expresión regular adecuada para cada caso)

2

1

Esto filtra los encabezados de las columnas. No es general, es incorrecto.

-cs95

23 de junio de 2019 a las 5:18

@MicheldeRuiter eso sigue siendo incorrecto, ¡en su lugar se filtraría por etiquetas de índice!

-cs95

30 dic 2019 a las 18:35



------------------------------------

Mi valor de 2c:

Hice lo siguiente:

sale_method = pd.DataFrame(model_data['Sale Method'].str.upper())
sale_method['sale_classification'] = \
    np.where(sale_method['Sale Method'].isin(['PRIVATE']),
             'private',
             np.where(sale_method['Sale Method']
                      .str.contains('AUCTION'),
                      'auction',
                      'other'
             )
    )
Respondido

24 de junio de 2021 a las 13:40

GenDemo

GenDemo

748

1

1 insignia de oro

8

8 insignias de plata

25

25 insignias de bronce



------------------------------------

-------------- ------------------
df[df['A'].str.contains("hello", case=False)]
Respondido al

4 de octubre de 2022 a las 11:41

Usman Abbasi

Usman Abbasi

87

9

9 binsignias de oro

1

2

Considere agregar una explicación a su código sobre cómo funciona y cómo responde a la pregunta del OP.

Buddemat

10 de octubre de 2022 a las 6:12



------------------------------------

API de consulta

Como se mencionó en otras respuestas, puede usar la consulta para filtrar filas haciendo una llamada a str.contains dentro de la expresión. Lo bueno es que, a diferencia del índice booleanoxing, no obtendrás el molesto SettingWithCopyWarning después de usarlo. También puede pasar el patrón definido localmente (o en otro lugar) usando @. Kwargs también útiles:

case=False: realiza una búsqueda que no distingue entre mayúsculas y minúsculas na=False: complete False para los valores faltantes, p. NaN, NA, Ninguno, etc.
df = pd.DataFrame({'col': ['foo', 'foobar', 'bar', 'baZ', pd.NA]})
pat = r'f|z'
df.query('col.str.contains(@pat, case=False, na=False)')    # case-insensitive and return False if NaN

# or pass it as `local_dict`
df.query('col.str.contains(@pattern, case=False, na=False)', local_dict={'pattern': r'f|z'})

Como se muestra arriba, puede manejar valores NaN en la columna pasando na=False. Esto es menos propenso a errores (y más rápido) que convertir la columna a tipo str o realizar otras comprobaciones booleanas como se hace en algunas respuestas de esta página.

Actuación

Dado que los métodos de cadena de Python no están optimizados, a menudo es más rápido pasar a Python básico y realizar cualquier tarea que tenga usando un bucle explícito. Entonces, si desea un buen rendimiento, use la comprensión de listas en lugar de str.contains vectorizado. Como puedes vere del siguiente punto de referencia (probado en Python 3.12.0 y pandas 2.1.1), str.contains, aunque conciso, es aproximadamente un 20% más lento que la comprensión de una lista (incluso con el operador ternario para el manejo de NaN). Dado que str.contains es una implementación de bucle de todos modos, esta brecha existe para cualquier tamaño que tenga el DataFrame.

import re
import pandas as pd
df = pd.DataFrame({'col': ['foo', 'foobar', 'bar', 'baZ', pd.NA]*100000})
pat = re.compile(r'f|z', flags=re.I)

%timeit df[df['col'].str.contains(pat, na=False)]
# 375 ms ± 15.4 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit df[[bool(pat.search(x)) if (x==x) is True else False for x in df['col'].tolist()]]
# 318 ms ± 14 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Su guía para un futuro mejor - libreflare
Su guía para un futuro mejor - libreflare