Cómo filtrar filas en pandas por regex

169

Me gustaría filtrar limpiamente un marco de datos usando expresiones regulares en una de las columnas.

Para un ejemplo artificial:

In [210]: foo = pd.DataFrame({'a' : [1,2,3,4], 'b' : ['hi', 'foo', 'fat', 'cat']})
In [211]: foo
Out[211]: 
   a    b
0  1   hi
1  2  foo
2  3  fat
3  4  cat

Quiero filtrar las filas a las que comienzan con el fuso de una expresión regular. Primero ve:

In [213]: foo.b.str.match('f.*')
Out[213]: 
0    []
1    ()
2    ()
3    []

Eso no es demasiado útil. Sin embargo, esto me dará mi índice booleano:

In [226]: foo.b.str.match('(f.*)').str.len() > 0
Out[226]: 
0    False
1     True
2     True
3    False
Name: b

Entonces podría hacer mi restricción:

In [229]: foo[foo.b.str.match('(f.*)').str.len() > 0]
Out[229]: 
   a    b
1  2  foo
2  3  fat

Sin embargo, eso me hace poner artificialmente un grupo en la expresión regular, y parece que tal vez no sea el camino limpio. ¿Hay una mejor manera de hacer esto?

justinvf
fuente
55
Si no estás casado con expresiones regulares, foo[foo.b.str.startswith("f")]funcionará.
DSM
En mi humilde opinión, creo que foo[foo.b.str.match('(f.*)').str.len() > 0]es una solución bastante buena! Más personalizable y útil que comienza porque contiene la versatilidad de expresiones regulares en él.
tumultous_rooster
3
Esto puede ser un poco tarde, pero en las versiones más recientes de pandas, el problema está solucionado. la línea foo[foo.b.str.match('f.*')]funciona en pandas 0.24.2 para mí.
Behzad Mehrtash el

Respuestas:

198

El uso contiene en su lugar:

In [10]: df.b.str.contains('^f')
Out[10]: 
0    False
1     True
2     True
3    False
Name: b, dtype: bool
waitingkuo
fuente
11
¿Cómo se puede invertir el booleano? Lo encontré: stackoverflow.com/questions/15998188/…
dmeu
44
¿Es posible obtener solo esas filas que tienen True?
onda de choque
2
@shockwave deberías usar:df.loc[df.b.str.contains('^f'), :]
Rafa
1
@shockwave También puedes usardf[df.b.str.contains('^f'), :]
David Jung
23

Ya existe una función de manejo de cadenas Series.str.startswith(). Deberías intentarlo foo[foo.b.str.startswith('f')].

Resultado:

    a   b
1   2   foo
2   3   fat

Pienso lo que esperas.

Alternativamente, puede usar contiene con la opción regex. Por ejemplo:

foo[foo.b.str.contains('oo', regex= True, na=False)]

Resultado:

    a   b
1   2   foo

na=False es para evitar errores en caso de que haya valores nan, nulos, etc.

Erkan Şirin
fuente
Modifiqué esto y funcionó para mídf[~df.CITY.str.contains('~.*', regex= True, na=False)]
Patty Jula
¡Gracias! esta es una gran solución
Kedar Joshi
20

Búsqueda de múltiples columnas con marco de datos:

frame[frame.filename.str.match('*.'+MetaData+'.*') & frame.file_path.str.match('C:\test\test.txt')]
lakshman senathirajah
fuente
2
frame? y 'C:\test\test.txt'? Parece que estás respondiendo una pregunta diferente.
tumultous_rooster
el marco es df. está relacionado con la misma pregunta, pero responde cómo filtrar varias columnas ('filename' y 'file_path') en un código de línea.
lakshman senathirajah
12

Esto puede ser un poco tarde, pero ahora es más fácil de hacer en Pandas. Puede llamar a match con as_indexer=Truepara obtener resultados booleanos. Esto está documentado (junto con la diferencia entre matchy contains) aquí .

Michael Siler
fuente
11

Gracias por la gran respuesta @ user3136169, aquí hay un ejemplo de cómo se podría hacer eso también eliminando los valores NoneType.

def regex_filter(val):
    if val:
        mo = re.search(regex,val)
        if mo:
            return True
        else:
            return False
    else:
        return False

df_filtered = df[df['col'].apply(regex_filter)]

También puede agregar expresiones regulares como un argumento:

def regex_filter(val,myregex):
    ...

df_filtered = df[df['col'].apply(res_regex_filter,regex=myregex)]
gorrión
fuente
1
gracias, debido a esto descubrí una forma de filtrar una columna por predicado arbitrario.
jman
9

Escriba una función booleana que verifique la expresión regular y use apply en la columna

foo[foo['b'].apply(regex_function)]
usuario3136169
fuente
1

Usando str rebanada

foo[foo.b.str[0]=='f']
Out[18]: 
   a    b
1  2  foo
2  3  fat
YOBEN_S
fuente