En una entrada anterior se explicó cómo filtrar registros en DataFrames de Pandas en base a los valores de los registros. Para lo que se utilizaron ejemplos únicamente numéricos. En los comentarios de la entrada varios lectores preguntasteis cómo hacer el filtrado de cadenas de texto en DataFrame, ya que esta es una tarea también habitual.
En la entrada de hoy nos vamos a centrar en explicar cómo realizar operaciones de filtrado en base al contenido de las cadenas de texto. Para lo que utilizaremos principalmente el método str.contains()
que existe en las series de Pandas.
Tabla de contenidos
Conjunto de datos de ejemplo
Al igual que en la entrada anterior vamos a utilizar el conjunto de datos de exoplanetas que se puede encontrar en la librería Seaborn. Un conjunto de datos que contiene en una de las series cadenas de texto. La importación del conjunto de datos se puede realizar con el siguiente código.
import seaborn as sb planets = sb.load_dataset('planets') planets.head()
method number orbital_period mass distance year 0 Radial Velocity 1 269.300 7.10 77.40 2006 1 Radial Velocity 1 874.774 2.21 56.95 2008 2 Radial Velocity 1 763.000 2.60 19.84 2011 3 Radial Velocity 1 326.030 19.40 110.62 2007 4 Radial Velocity 1 516.220 10.50 119.47 2009
La columna method
contiene el nombre de los métodos mediante los que se descubrió cada uno de los exoplanetas. Podemos comprobar fácilmente que existen 10 métodos y ver estos con el siguiente código.
planets.method.unique()
['Radial Velocity' 'Imaging' 'Eclipse Timing Variations' 'Transit' 'Astrometry' 'Transit Timing Variations' 'Orbital Brightness Modulation' 'Microlensing' 'Pulsar Timing' 'Pulsation Timing Variations']
Buscar que contiene una cadena literal
En primer lugar, se pueden buscar los registros que contengan literalmente la cadena de texto que deseamos. Para lo que se puede utilizar el operador igualdad (==
). Por ejemplo, para buscar los planetas que han sido descubiertos mediante el método “Pulsar Timing” se puede usar.
planets[planets.method == 'Pulsar Timing']
method number orbital_period mass distance year 941 Pulsar Timing 3 25.262000 NaN NaN 1992 942 Pulsar Timing 3 66.541900 NaN NaN 1992 943 Pulsar Timing 3 98.211400 NaN NaN 1994 944 Pulsar Timing 1 36525.000000 NaN NaN 2003 945 Pulsar Timing 1 0.090706 NaN 1200.0 2011
En este caso es necesario indicar el nombre de forma exacta, si nos equivocamos el código anterior nos devolverá un DataFrame vacío.
Buscar cadenas de texto con una subcadena
Ampliando el ejemplo anterior, puede ser interesante localizar los planetas descubiertos mediante los métodos que contengan “Pulsa”. Conociendo los valores se puede hacer con el operador igualdad indicando que contenga una cadena u otra. Pero en un caso genérico esta no es una buena aproximación. La solución para esto es utilizar el método str.contains()
como se muestra en el siguiente ejemplo.
planets[planets.method.str.contains('Pulsa')]
method number orbital_period mass distance year 941 Pulsar Timing 3 25.262000 NaN NaN 1992 942 Pulsar Timing 3 66.541900 NaN NaN 1992 943 Pulsar Timing 3 98.211400 NaN NaN 1994 944 Pulsar Timing 1 36525.000000 NaN NaN 2003 945 Pulsar Timing 1 0.090706 NaN 1200.0 2011 958 Pulsation Timing Variations 1 1170.000000 NaN NaN 2007
En dónde se han obtenido los cinco planetas de que se han descubierto mediante “Pulsar Timing” y el que se ha descubierto mediante “Pulsation Timing Variations”. Si queremos buscar los que tiene la cadena “Timing” reemplazando una cadena por otra.
Ignorar diferencias entre mayúsculas y minúsculas
Por defecto el método str.contains()
diferencia entre mayúsculas y minúsculas, por lo que si escribimos “pulsa” en lugar de “Pulsa” se obtendremos una cadena vacía.
planets[planets.method.str.contains('pulsa')]
Para evitar esto se puede igualar la opción case
a falso para que ignore la diferencias entre mayúsculas y minúsculas.
planets[planets.method.str.contains('pulsa', case=False)]
method number orbital_period mass distance year 941 Pulsar Timing 3 25.262000 NaN NaN 1992 942 Pulsar Timing 3 66.541900 NaN NaN 1992 943 Pulsar Timing 3 98.211400 NaN NaN 1994 944 Pulsar Timing 1 36525.000000 NaN NaN 2003 945 Pulsar Timing 1 0.090706 NaN 1200.0 2011 958 Pulsation Timing Variations 1 1170.000000 NaN NaN 2007
Buscar más de una subcadena
Si queremos buscar más de una subcadena se puede utilizar |
para separar las cadenas a buscar. En este caso lo que se obtiene todos los registros que contenga una u otra subcadena. Por ejemplo, se pueden buscar los métodos que contenga “Pulsar” u “Orbital” en sus nombres.
planets[planets.method.str.contains('Pulsar|Orbital')]
method number orbital_period mass distance year 787 Orbital Brightness Modulation 2 0.240104 NaN 1180.0 2011 788 Orbital Brightness Modulation 2 0.342887 NaN 1180.0 2011 792 Orbital Brightness Modulation 1 1.544929 NaN NaN 2013 941 Pulsar Timing 3 25.262000 NaN NaN 1992 942 Pulsar Timing 3 66.541900 NaN NaN 1992 943 Pulsar Timing 3 98.211400 NaN NaN 1994 944 Pulsar Timing 1 36525.000000 NaN NaN 2003 945 Pulsar Timing 1 0.090706 NaN 1200.0 2011
Expresiones regulares
El método str.contains()
puede emplearse también expresiones regulares, lo que ofrece múltiples posibilidades.
planets[planets.method.str.contains('Pulsa?', regex=True)]
method number orbital_period mass distance year 941 Pulsar Timing 3 25.262000 NaN NaN 1992 942 Pulsar Timing 3 66.541900 NaN NaN 1992 943 Pulsar Timing 3 98.211400 NaN NaN 1994 944 Pulsar Timing 1 36525.000000 NaN NaN 2003 945 Pulsar Timing 1 0.090706 NaN 1200.0 2011 958 Pulsation Timing Variations 1 1170.000000 NaN NaN 2007
Conclusiones
En esta entrada se ha visto cómo se puede utilizar el método str.contains()
para realizar el filtrado de cadenas de texto en DataFrame con Pandas.
Imágenes: Pixabay (Nicole)
Ezequiel canseco dice
Como filtrar con pandas de tal forma que solo me consulte los registros que contengan palabras que inicien con una letra o un numero en especifico. Por ejemplo, filtrar todas claves geograficas pertenecientes al estado de yucatan, es decir las claves geograficas que inicien con 31… ya probe con .str.contains(‘31%’) y tambien con .filter(‘31%’)
Daniel Rodríguez dice
Ese problema se puede resolver con la propiedad
.str.startswith('31')
daniel colmenares dice
Hola,
Si quiero que la cadena encontrada me la coloque en otra columna, hay una forma sencilla? O tendría que aplicar una función (lambda…)
Gracias.
Daniel Rodríguez dice
Hola, creo que la solución es algo tan sencillo como
planets['newcol'][planets.method == 'Radial Velocity'] = planets['method'][planets.method == 'Radial Velocity']
Aunque es necesario que la serie
newcol
exista en el DataFrameFelipe dice
Genial el tutorial, me ha sido muy útil, solo quedo con una pequeña consulta y agradecería cualquier ayuda, la verdad no me funciono intentando evaluar un array de substrings con una columna de referencia, si alguien pudiera dar un aporte a este problema sería genial. La verdad he intentado pero no he encontrado solución.
Saludos y gracias
Daniel Rodríguez dice
Entiendo que los que necesitas es algo como esto
planets[planets.method.str.contains('Pulsar|Orbital')]
pero con las cadenas en un vector en lugar de una cadena separada por |. La solución se puede conseguir fácilmente con el método join que tienen las cadenas de texto en Python
'|'.join(['Pulsar', 'Orbital'])
Genera
'Pulsar|Orbital'
Así que si tenemos un vector con cadenas de texto se puede usar seleccionar todas las que tienen alguna de la lista con algo como
substr = ['Pulsar', 'Orbital']
planets[planets.method.str.contains('|'.join(substr))]
Rodrigo dice
Tengo un dataframe en donde una columna son los SKU de los productos, algunos están mal escritos ya que contienen _ u otro dato que no sea numérico (ej: 615237_), necesito eliminar las filas cuyo SKU contenga un _ u otro elemento que no sea numérico (int) por favor
Daniel Rodríguez dice
Una opción es usar la expresión ^\d+$ regular para buscar cadenas con uno o más dígitos.
Pedro dice
Hola Daniel, me ha parecido muy interesante tu post, estoy trabajando en algo similar y tengo una pregunta: tengo 2 dataframes (2 cdv en realidad) cada uno tiene una columna “común” solo que uno tiene el dato perfecto, puro y otro lo tiene con morralla, por ejemplo:
columnaDF1: dafhf23432HOLA, h2d23kasdADIOS y el otro columnaDF1: HOLA, ADIOS
pero yo desconozco esos datos puros, es decir no puedo tirar por expresiones regulares,
así que lo que quiero es saber cómo podría buscar las coincidencias de una columna de un dataframe en la columna del otro y si hay coincidencia imprimirla (con morrala incluida)
muchas gracias
Daniel Rodríguez dice
El problema es realmente complicado, la primera idea que se me ocurre es dividir la cadena limpia en palabras y buscar aquellos registros que contengan todas las subcadenas. Aunque posiblemente aparezcan muchos falsos positivos.
Victor Manuel dice
Hola Daniel,
sobre la respuesta que diste el dia 15 de mayo:
“Entiendo que los que necesitas es algo como esto planets[planets.method.str.contains(‘Pulsar|Orbital’)] pero con las cadenas en un vector en lugar de una cadena separada por |. La solución se puede conseguir fácilmente con el método join que tienen las cadenas de texto en Python ‘|’.join([‘Pulsar’, ‘Orbital’]) Genera ‘Pulsar|Orbital’
Así que si tenemos un vector con cadenas de texto se puede usar seleccionar todas las que tienen alguna de la lista con algo como substr = [‘Pulsar’, ‘Orbital’]planets[planets.method.str.contains(‘|’.join(substr))]”
Como puedo hacer que ese texto encontrado también aparezca en una nueva columna???