Seleccionar por cadena parcial de un dataframe de pandas

Tengo un DataFrame con 4 columnas de las cuales 2 contienen valores de cadena. Me preguntaba si habría una manera de seleccionar filas basadas en una cadena de concordancia parcial con una columna en particular.

En otras palabras, una función o función lambda que haría algo como

 re.search(pattern, cell_in_question) 

devolviendo un booleano. Estoy familiarizado con la syntax de df[df['A'] == "hello world"] pero parece que no puedo encontrar la manera de hacer lo mismo con una cadena de caracteres parcial que diga 'hello' .

¿Alguien podría apuntarme en la dirección correcta?

Según el número github # 620 , parece que pronto podrás hacer lo siguiente:

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

Actualización: los métodos de cadenas vectorizadas (es decir, Series.str) están disponibles en pandas 0.8.1 y superiores.

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

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

y consiguió un error:

 "cannot index with vector containing NA / NaN values" 

pero funcionó perfectamente cuando se agregó una condición “== True”, como esta:

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

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

Utilizar:

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

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

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

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")] 

Digamos que tienes el siguiente DataFrame :

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

Siempre puede usar el operador in en una expresión lambda para crear su filtro.

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

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

Esto es lo que terminé haciendo para coincidencias de cadenas parciales. Si alguien tiene una forma más eficiente de hacerlo, hágamelo 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 

Como he visto muchas preguntas sobre temas similares, pensé que sería bueno dejar esto aquí.

Búsqueda básica de subcadenas

 df1 = pd.DataFrame({'col': ['foo', 'foobar', 'bar', 'baz']}) df1 col 0 foo 1 foobar 2 bar 3 baz 

Para seleccionar todas las filas que contienen “foo”, use str.contains :

 df1[df1['col'].str.contains('foo')] col 0 foo 1 foobar 

Tenga en cuenta que esta es una búsqueda de subcadena pura, por lo que puede deshabilitar de forma segura la coincidencia basada en expresiones regulares.

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

En cuanto al rendimiento, esto hace una diferencia.

 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.

Nota
Las búsquedas de subcadenas parciales que están ancladas al comienzo o al final de las cadenas se pueden hacer usando str.startswith o str.endswith respectivamente.

Además, para las búsquedas basadas en str.match regulares ancladas al inicio, use str.match .

Búsqueda basada en Regex
La mayoría de los métodos str admiten expresiones regulares. Por ejemplo, para encontrar filas en df1 que contengan “foo” seguidas de otra cosa, podemos usar

 df1[df1['col'].str.contains(r'foo(?!$)')] col 1 foobar 

Coincidiendo con toda la palabra (s)

De forma predeterminada, la búsqueda de subcadenas busca la subcadena / patrón especificado, independientemente de si es palabra completa o no. Para unir 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 considera,

 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 de subcadena múltiple

Esto se logra más fácilmente a través de una búsqueda de expresiones regulares utilizando la expresión regular o la tubería.

 # 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, 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 sus términos en caso de que tengan caracteres que puedan interpretarse como metacaracteres de expresiones regulares . Si tus términos contienen alguno de los siguientes caracteres …

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

Entonces, 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\\^' 

Búsqueda múltiple de palabras completas

Similar al 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 ve así,

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

Una gran alternativa: ¡Usa listas de comprensión !

¡Porque tú puedes! ¡Y tú deberías! Por lo general, son un poco más rápidos que los métodos de cadena, ya que 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 comp,

 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)] 

Use re.compile (para almacenar en caché su expresión regular) + Pattern.search dentro de una lista comp,

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

Si “col” tiene NaNs, entonces en lugar de

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

Utilizar,

 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.contains y listas de comprensión, también puede utilizar las siguientes alternativas.

np.char.find
Admite búsquedas de subcadenas (leer: no regex) solamente.

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

np.vectorize
Se trata de una envoltura alrededor de un bucle, pero con una sobrecarga menor que la de la mayoría de los métodos de pandas str .

 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 Regex:

 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
Es compatible con los métodos de cadena a través del motor Python. Esto no ofrece beneficios de rendimiento visibles, pero aún así es útil saber si necesita generar dinámicamente sus consultas.

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

Se puede encontrar más información sobre la familia de métodos de query y eval en Dynamic Expression Evaluation en pandas usando pd.eval () .


Precedencia de uso recomendado

  1. (Primero) str.contains , por su simplicidad.
  2. Lista de comprensiones, por su desempeño.
  3. np.vectorize
  4. (Último) df.query

¿Por qué no lo intentas con df[df["COLUMN_ID"].str.contains("SUBSTRING")] .

Simplemente reemplace el COLUMN_ID con la cadena que se refiere a la columna donde desea buscar su SUBSTRING y reemplace la SUBSTRING con el texto que desea buscar (“Hola” en su caso).