Eliminar partes no deseadas de cadenas en una columna

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

Los datos se parecen a

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 

Intenté .str.lstrip('+-') y. str.rstrip('aAbBcC') , pero obtuvo un error:

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

Cualquier puntero sería muy apreciado!

 data['result'] = data['result'].map(lambda x: x.lstrip('+-').rstrip('aAbBcC')) 

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

 data['result'].replace(regex=True,inplace=True,to_replace=r'\D',value=r'') 

En el caso particular en el que conoce el número de posiciones que desea eliminar de la columna de dataframe, 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]) 

Los dos primeros personajes:

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

Hay un error aquí: actualmente no se pueden pasar argumentos a str.lstrip y str.rstrip :

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

EDITAR: 2012-12-07 esto funciona ahora en la twig dev:

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

Un método muy simple sería utilizar el método de extract 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 

A menudo utilizo 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 DataFrame). A menudo, una 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 

Actualmente en v0.23 +, 6 años después de que se publicó la pregunta original …
Pandas ahora tiene un buen número de funciones de cadena “vectorizadas” que pueden realizar sucintamente estas operaciones de manipulación de cadena.

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


Series.str.replace
Elimina las subcadenas que no quieras.

 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 que el resultado se convierta 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 df en el lugar, use DataFrame.assign :

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

Series.str.extract
Útil para extraer la (s) subcadena (s) que desea mantener.

 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 el extract , es necesario especificar al menos un grupo de captura. expand=False devolverá una serie con los elementos capturados del primer grupo de captura.


Series.str.split y Series.str.get
La división de trabajos asume que todas sus cadenas siguen 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 recomiendo si está buscando una solución general.


Si está satisfecho con las soluciones basadas en str acceso str y legibles de arriba, puede detenerse aquí. Sin embargo, si está interesado en alternativas más rápidas y de mayor rendimiento, siga leyendo.

Optimización – Lista de Comprensiones
En algunas circunstancias, se debe favorecer la comprensión de listas sobre las funciones de cadena de pandas. La razón es que las funciones de cadena son intrínsecamente difíciles de vectorizar (en el verdadero sentido de la palabra), por lo que la mayoría de las funciones de cadena y expresiones regulares son solo envolturas alrededor de bucles con más sobrecarga.

Mi escrito, Para bucles con pandas – ¿Cuándo debería importarme? , entra en mayor detalle.

La opción str.replace se puede reescribir usando re.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 ejemplo de str.extract se puede volver a escribir utilizando 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 es posible que haya NaN o no coincidencias, 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 volver a escribir las respuestas de @ eumiro’s y @ MonkeyButter usando la lista 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 el manejo de NaN, etc.


Actuación
Estoy sincronizando cada respuesta escrita hasta ahora, incluyendo la mía. Los tiempos se han generado utilizando el módulo perfplot .

Configuración y tiempos

 df_ = pd.DataFrame({ 'time': ['09:00', '10:00', '11:00', '12:00', '13:00'], 'result': ['+52A', '+62B', '+44a', '+30b', '-110a'] }) kernels = [ eumiro, coder375, monkeybutter, wes, cs1, cs2_ted, cs1_listcomp, cs2_listcomp, cs_eumiro_listcomp, cs_mb_listcomp ] perfplot.show( setup=lambda n: pd.concat([df_] * n, ignore_index=True), kernels=kernels, labels=[k.__name__ for k in kernels], n_range=[2**k for k in range(1, 11)], xlabel='N', logy=True, equality_check=lambda x, y: (x == y).all(axis=None), ) 

introduzca la descripción de la imagen aquí

El gráfico traza el rendimiento relativo; El eje y aumenta logarítmicamente. Algunas de estas comparaciones son injustas porque aprovechan la estructura de los datos de OP, pero toman de ella lo que se quiere. Una cosa a tener en cuenta es que cada función de comprensión de lista es más rápida que su función equivalente de pandas.

Funciones

 import perfplot import pandas as pd import re p1 = re.compile(r'\D') p2 = re.compile(r'\d+') 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']])