Eliminar partes no deseadas de cadenas en una columna

129

Estoy buscando una manera eficiente de eliminar partes no deseadas de las cadenas en una columna DataFrame.

Los datos se ven así:

    time    result
1    09:00   +52A
2    10:00   +62B
3    11:00   +44a
4    12:00   +30b
5    13:00   -110a

Necesito recortar estos datos para:

    time    result
1    09:00   52
2    10:00   62
3    11:00   44
4    12:00   30
5    13:00   110

Lo intenté .str.lstrip('+-')y. str.rstrip('aAbBcC'), pero recibí un error:

TypeError: wrapper() takes exactly 1 argument (2 given)

Cualquier sugerencia sería muy apreciada!

Yannan Wang
fuente

Respuestas:

167
data['result'] = data['result'].map(lambda x: x.lstrip('+-').rstrip('aAbBcC'))
eumiro
fuente
¡Gracias! eso funciona. Todavía estoy pensando en el mapa (), no estoy seguro de cuándo usarlo o no ...
Yannan Wang
Me complació ver que este método también funciona con la función de reemplazo.
BKay
@eumiro, ¿cómo aplicas este resultado si iteras cada columna?
medev21
¿Puedo usar esta función para reemplazar un número como el número 12? Si hago x.lstrip ('12 ') elimina todos los 1 y 2s.
Dave
76

¿Cómo elimino las partes no deseadas de las cadenas en una columna?

6 años después de que se publicó la pregunta original, los pandas ahora tienen un buen número de funciones de cadena "vectorizadas" que pueden realizar de manera sucinta estas operaciones de manipulación de cadenas.

Esta respuesta explorará algunas de estas funciones de cadena, sugerirá alternativas más rápidas y realizará una comparación de tiempos al final.


.str.replace

Especifique la subcadena / patrón para que coincida y la subcadena para reemplazarlo.

pd.__version__
# '0.24.1'

df    
    time result
1  09:00   +52A
2  10:00   +62B
3  11:00   +44a
4  12:00   +30b
5  13:00  -110a

df['result'] = df['result'].str.replace(r'\D', '')
df

    time result
1  09:00     52
2  10:00     62
3  11:00     44
4  12:00     30
5  13:00    110

Si necesita convertir el resultado en un entero, puede usar Series.astype,

df['result'] = df['result'].str.replace(r'\D', '').astype(int)

df.dtypes
time      object
result     int64
dtype: object

Si no desea modificar dfen el lugar, use DataFrame.assign:

df2 = df.assign(result=df['result'].str.replace(r'\D', ''))
df
# Unchanged

.str.extract

Útil para extraer la (s) subcadena (s) que desea conservar.

df['result'] = df['result'].str.extract(r'(\d+)', expand=False)
df

    time result
1  09:00     52
2  10:00     62
3  11:00     44
4  12:00     30
5  13:00    110

Con extract, es necesario especificar al menos un grupo de captura. expand=Falsedevolverá una serie con los elementos capturados del primer grupo de captura.


.str.split y .str.get

La división funciona suponiendo que todas sus cadenas sigan esta estructura consistente.

# df['result'] = df['result'].str.split(r'\D').str[1]
df['result'] = df['result'].str.split(r'\D').str.get(1)
df

    time result
1  09:00     52
2  10:00     62
3  11:00     44
4  12:00     30
5  13:00    110

No lo recomiende si está buscando una solución general.


Si está satisfecho con las str soluciones sucintas y legibles basadas en accesores anteriores, puede detenerse aquí. Sin embargo, si está interesado en alternativas más rápidas y con mejor rendimiento, siga leyendo.


Optimización: Lista de comprensiones

En algunas circunstancias, las comprensiones de listas deben ser favorecidas sobre las funciones de cadena de pandas. La razón es que las funciones de cadena son inherentemente difíciles de vectorizar (en el verdadero sentido de la palabra), por lo que la mayoría de las funciones de cadena y expresión regular son solo envoltorios alrededor de bucles con más sobrecarga.

Mi artículo, ¿son realmente malos los bucles for pandas? ¿Cuándo debería importarme?, entra en mayor detalle.

La str.replaceopción puede reescribirse usandore.sub

import re

# Pre-compile your regex pattern for more performance.
p = re.compile(r'\D')
df['result'] = [p.sub('', x) for x in df['result']]
df

    time result
1  09:00     52
2  10:00     62
3  11:00     44
4  12:00     30
5  13:00    110

El str.extractejemplo puede reescribirse usando una lista de comprensión con re.search,

p = re.compile(r'\d+')
df['result'] = [p.search(x)[0] for x in df['result']]
df

    time result
1  09:00     52
2  10:00     62
3  11:00     44
4  12:00     30
5  13:00    110

Si NaNs o no coincide son una posibilidad, deberá volver a escribir lo anterior para incluir alguna comprobación de errores. Hago esto usando una función.

def try_extract(pattern, string):
    try:
        m = pattern.search(string)
        return m.group(0)
    except (TypeError, ValueError, AttributeError):
        return np.nan

p = re.compile(r'\d+')
df['result'] = [try_extract(p, x) for x in df['result']]
df

    time result
1  09:00     52
2  10:00     62
3  11:00     44
4  12:00     30
5  13:00    110

También podemos reescribir las respuestas de @eumiro y @MonkeyButter usando listas de comprensión:

df['result'] = [x.lstrip('+-').rstrip('aAbBcC') for x in df['result']]

Y,

df['result'] = [x[1:-1] for x in df['result']]

Se aplican las mismas reglas para manejar NaN, etc.


Comparación de rendimiento

ingrese la descripción de la imagen aquí

Gráficos generados usando perfplot . Listado completo de códigos, para su referencia. Las funciones relevantes se enumeran a continuación.

Algunas de estas comparaciones son injustas porque aprovechan la estructura de los datos de OP, pero toman de ella lo que quieran. Una cosa a tener en cuenta es que cada función de comprensión de la lista es más rápida o comparable que su variante de pandas equivalente.

Las funciones

def eumiro(df):
    return df.assign(
        result=df['result'].map(lambda x: x.lstrip('+-').rstrip('aAbBcC')))

def coder375(df):
    return df.assign(
        result=df['result'].replace(r'\D', r'', regex=True))

def monkeybutter(df):
    return df.assign(result=df['result'].map(lambda x: x[1:-1]))

def wes(df):
    return df.assign(result=df['result'].str.lstrip('+-').str.rstrip('aAbBcC'))

def cs1(df):
    return df.assign(result=df['result'].str.replace(r'\D', ''))

def cs2_ted(df):
    # `str.extract` based solution, similar to @Ted Petrou's. so timing together.
    return df.assign(result=df['result'].str.extract(r'(\d+)', expand=False))

def cs1_listcomp(df):
    return df.assign(result=[p1.sub('', x) for x in df['result']])

def cs2_listcomp(df):
    return df.assign(result=[p2.search(x)[0] for x in df['result']])

def cs_eumiro_listcomp(df):
    return df.assign(
        result=[x.lstrip('+-').rstrip('aAbBcC') for x in df['result']])

def cs_mb_listcomp(df):
    return df.assign(result=[x[1:-1] for x in df['result']])
cs95
fuente
cualquier solución alternativa para evitar la configuración con advertencia de copia:Try using .loc[row_indexer,col_indexer] = value instead
PV8
@ PV8 no está seguro acerca de su código, pero consulte esto: stackoverflow.com/questions/20625582/…
cs95
Para cualquiera que sea nuevo en REGEX como yo, \ D es lo mismo que [^ \ d] (cualquier cosa que no sea un dígito) desde aquí . Así que básicamente estamos reemplazando todos los no dígitos de la cadena con nada.
Rishi Latchmepersad
56

Usaría la función de reemplazo de pandas, muy simple y potente, ya que puede usar regex. A continuación, estoy usando la expresión regular \ D para eliminar cualquier carácter que no sea un dígito, pero obviamente podrías ser bastante creativo con la expresión regular.

data['result'].replace(regex=True,inplace=True,to_replace=r'\D',value=r'')
Codificador375
fuente
Intenté esto, y no funciona. Me pregunto si solo funciona cuando desea reemplazar una cadena completa en lugar de simplemente reemplazar una parte de la subcadena.
bgenchel
@bgenchel: utilicé este método para reemplazar parte de una cadena en un pd df.loc[:, 'column_a'].replace(regex=True, to_replace="my_prefix", value="new_prefix"). Serie:. Esto convertirá una cadena como "my_prefixaaa" a "new_prefixaaa".
jakub
¿Qué hace la r en to_replace = r '\ D'?
Luca Guarro
@LucaGuarro de los documentos de Python: "El prefijo r, que hace que el literal sea un literal de cadena sin procesar, es necesario en este ejemplo porque las secuencias de escape en un literal de cadena" cocinado "normal que no son reconocidas por Python, a diferencia de las expresiones regulares, ahora resultará en una Advertencia de desaprobación y eventualmente se convertirá en un Error de sintaxis ".
Coder375
35

En el caso particular en el que conoce el número de posiciones que desea eliminar de la columna del marco de datos, puede usar la indexación de cadenas dentro de una función lambda para deshacerse de esas partes:

Último personaje:

data['result'] = data['result'].map(lambda x: str(x)[:-1])

Primeros dos personajes:

data['result'] = data['result'].map(lambda x: str(x)[2:])
prl900
fuente
Necesito recortar las coordenadas geográficas a 8 caracteres (incluidos (.), (-)) y, en caso de que sean inferiores a 8, necesito insertar '0' al final para que todas las coordenadas sean 8 caracteres. ¿Cuál es la forma más sencilla de hacerlo?
Sitz Blogz
No entiendo completamente su problema, pero es posible que deba cambiar la función lambda a algo como "{0: .8f}". Format (x)
prl900
Muchas gracias por la respuesta. En palabras simples, tengo un marco de datos con coordenadas geográficas: latitud y longitud como dos columnas. La longitud de los caracteres es de más de 8 caracteres y solo tenía 8 caracteres a partir del primero, que también deberían incluir (-) y (.).
Sitz Blogz
18

Hay un error aquí: actualmente no puede pasar argumentos a str.lstripy str.rstrip:

http://github.com/pydata/pandas/issues/2411

EDITAR: 2012-12-07 esto funciona ahora en la rama de desarrollo:

In [8]: df['result'].str.lstrip('+-').str.rstrip('aAbBcC')
Out[8]: 
1     52
2     62
3     44
4     30
5    110
Name: result
Wes McKinney
fuente
11

Un método muy simple sería usar el extractmétodo para seleccionar todos los dígitos. Simplemente proporcione la expresión regular '\d+'que extrae cualquier número de dígitos.

df['result'] = df.result.str.extract(r'(\d+)', expand=True).astype(int)
df

    time  result
1  09:00      52
2  10:00      62
3  11:00      44
4  12:00      30
5  13:00     110
Ted Petrou
fuente
7

A menudo uso listas de comprensión para este tipo de tareas porque a menudo son más rápidas.

Puede haber grandes diferencias en el rendimiento entre los diversos métodos para hacer cosas como esta (es decir, modificar cada elemento de una serie dentro de un Marco de datos). A menudo, la comprensión de la lista puede ser más rápida: consulte la carrera de código a continuación para esta tarea:

import pandas as pd
#Map
data = pd.DataFrame({'time':['09:00','10:00','11:00','12:00','13:00'], 'result':['+52A','+62B','+44a','+30b','-110a']})
%timeit data['result'] = data['result'].map(lambda x: x.lstrip('+-').rstrip('aAbBcC'))
10000 loops, best of 3: 187 µs per loop
#List comprehension
data = pd.DataFrame({'time':['09:00','10:00','11:00','12:00','13:00'], 'result':['+52A','+62B','+44a','+30b','-110a']})
%timeit data['result'] = [x.lstrip('+-').rstrip('aAbBcC') for x in data['result']]
10000 loops, best of 3: 117 µs per loop
#.str
data = pd.DataFrame({'time':['09:00','10:00','11:00','12:00','13:00'], 'result':['+52A','+62B','+44a','+30b','-110a']})
%timeit data['result'] = data['result'].str.lstrip('+-').str.rstrip('aAbBcC')
1000 loops, best of 3: 336 µs per loop
tim654321
fuente
4

Suponga que su DF también tiene esos caracteres adicionales entre números. La última entrada.

  result   time
0   +52A  09:00
1   +62B  10:00
2   +44a  11:00
3   +30b  12:00
4  -110a  13:00
5   3+b0  14:00

Puede intentar str.replace para eliminar caracteres no solo desde el principio y el final, sino también desde el medio.

DF['result'] = DF['result'].str.replace('\+|a|b|\-|A|B', '')

Salida:

  result   time
0     52  09:00
1     62  10:00
2     44  11:00
3     30  12:00
4    110  13:00
5     30  14:00
Rishi Bansal
fuente
0

Intenta esto usando una expresión regular:

import re
data['result'] = data['result'].map(lambda x: re.sub('[-+A-Za-z]',x)
Señor profeta
fuente