Cómo eliminar llaves, apóstrofes y corchetes de los diccionarios en un dataframe de Pandas (Python)

Tengo los siguientes datos en un archivo csv:

from StringIO import StringIO import pandas as pd the_data = """ ABC,2016-6-9 0:00,95,{'//PurpleCar': [115L], '//YellowCar': [403L], '//BlueCar': [16L], '//WhiteCar-XYZ': [0L]} ABC,2016-6-10 0:00,0,{'//PurpleCar': [219L], '//YellowCar': [381L], '//BlueCar': [90L], '//WhiteCar-XYZ': [0L]} ABC,2016-6-11 0:00,0,{'//PurpleCar': [817L], '//YellowCar': [21L], '//BlueCar': [31L], '//WhiteCar-XYZ': [0L]} ABC,2016-6-12 0:00,0,{'//PurpleCar': [80L], '//YellowCar': [2011L], '//BlueCar': [8888L], '//WhiteCar-XYZ': [0L]} ABC,2016-6-13 0:00,0,{'//PurpleCar': [32L], '//YellowCar': [15L], '//BlueCar': [4L], '//WhiteCar-XYZ': [0L]} DEF,2016-6-16 0:00,0,{'//PurpleCar': [32L], '//BlackCar': [15L], '//PinkCar': [4L], '//NPO-GreenCar': [0L]} DEF,2016-6-17 0:00,0,{'//PurpleCar': [32L], '//BlackCar': [15L], '//PinkCar': [4L], '//NPO-GreenCar': [0L]} DEF,2016-6-18 0:00,0,{'//PurpleCar': [32L], '//BlackCar': [15L], '//PinkCar': [4L], '//NPO-GreenCar': [0L]} DEF,2016-6-19 0:00,0,{'//PurpleCar': [32L], '//BlackCar': [15L], '//PinkCar': [4L], '//NPO-GreenCar': [0L]} DEF,2016-6-20 0:00,0,{'//PurpleCar': [32L], '//BlackCar': [15L], '//PinkCar': [4L], '//NPO-GreenCar': [0L]} """ 

Leí el archivo en un dataframe de Pandas, como sigue:

 df = pd.read_csv(StringIO(the_data), sep=',') 

Luego, agrego algunos encabezados de columna, como sigue:

 df.columns = ['Company', 'Date', 'Volume', 'Car1', 'Car2', 'Car3', 'Car4'] 

Veo que los datos están llegando de la siguiente manera:

 ABC,2016-6-9 0:00,95,{'//PurpleCar': [115L], '//YellowCar': [403L], '//BlueCar': [16L], '//WhiteCar-XYZ': [0L] 

Pero, me gustaría ver los datos sin ninguno de los siguientes:

a) las llaves ( "{" ) al principio y las llaves ( "{" "}" ) al final del diccionario

b) la “L” después de los valores numéricos

c) los corchetes ( "[" y "]" ) que rodean los valores numéricos

d) Las apóstrofes que rodean las llaves.

Idealmente, los datos se transformarían de la siguiente manera:

 ABC,2016-6-9 0:00,95,//PurpleCar: 115, //YellowCar: 403, //BlueCar: 16, //WhiteCar-XYZ: 0 

Intenté esto:

 df['Car1'] = df['Car1'].str.strip(['{', '}', '[', 'L]']) 

Pero, no funciona. Da como resultado que la columna ‘Car1’ se convierta en valores NaN.

¿Es posible transformar el dataframe de manera que cada fila del dataframe se lea como sigue?

 ABC,2016-6-9 0:00,95,//PurpleCar: 115, //YellowCar: 403, //BlueCar: 16, //WhiteCar-XYZ: 0 

¡Gracias!

ACTUALIZACIÓN :

Usando la siguiente expresión regular:

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

Resultados en esto:

 ABC,2016-6-9 0:00,95, 115 , //YellowCar: 403, //BlueCar: 16, //WhiteCar-XYZ: 0 

Perdemos ‘// PurpleCar’ y solo nos queda el valor numérico de 115. Es un buen comienzo, pero sería genial si pudiéramos ver la tecla ‘// PurpleCar’ también.

¿Algunas ideas?


ACTUALIZACIÓN 2:

Basado en los comentarios de piRSquared y HYRY, mi objective es poder trazar los resultados numéricos. Por lo tanto, me gustaría que el dataframe tenga el siguiente aspecto:

  Company Date PurpleCar YellowCar BlueCar WhiteCar 0 ABC 2016-6-9 0:00 115 403 16 0 1 ABC 2016-6-10 0:00 219 381 90 0 2 ABC 2016-6-11 0:00 817 21 31 0 3 ABC 2016-6-12 0:00 80 2011 8888 0 4 ABC 2016-6-13 0:00 32 15 4 0 5 DEF 2016-6-16 0:00 32 15 4 0 6 DEF 2016-6-17 0:00 32 15 4 0 7 DEF 2016-6-18 0:00 32 15 4 0 8 DEF 2016-6-19 0:00 32 15 4 0 9 DEF 2016-6-20 0:00 32 15 4 0 

* ACTUALIZACIÓN 3: *

Los datos publicados originalmente tuvieron un pequeño error. Aquí están los datos:

 the_data = """ ABC,2016-6-9 0:00,95,"{'//Purple': [115L], '//Yellow': [403L], '//Blue': [16L], '//White-XYZ': [0L]}" ABC,2016-6-10 0:00,0,"{'//Purple': [219L], '//Yellow': [381L], '//Blue': [90L], '//White-XYZ': [0L]}" ABC,2016-6-11 0:00,0,"{'//Purple': [817L], '//Yellow': [21L], '//Blue': [31L], '//White-XYZ': [0L]}" ABC,2016-6-12 0:00,0,"{'//Purple': [80L], '//Yellow': [2011L], '//Blue': [8888L], '//White-XYZ': [0L]}" ABC,2016-6-13 0:00,0,"{'//Purple': [32L], '//Yellow': [15L], '//Blue': [4L], '//White-XYZ': [0L]}" DEF,2016-6-16 0:00,0,"{'//Purple': [32L], '//Black': [15L], '//Pink': [4L], '//NPO-Green': [3L]}" DEF,2016-6-17 0:00,0,"{'//Purple': [32L], '//Black': [15L], '//Pink': [4L], '//NPO-Green': [0L]}" DEF,2016-6-18 0:00,0,"{'//Purple': [32L], '//Black': [15L], '//Pink': [4L], '//NPO-Green': [7L]}" DEF,2016-6-19 0:00,0,"{'//Purple': [32L], '//Black': [15L], '//Pink': [4L], '//NPO-Green': [14L]}" DEF,2016-6-20 0:00,0,"{'//Purple': [32L], '//Black': [15L], '//Pink': [4L], '//NPO-Green': [21L]}" """ 

La diferencia entre estos datos y los datos originales son los apóstrofes (") antes de la llave de apertura ( "{" ) y después de la llave de cierre ( "}" ).

Edición: el archivo parece ser en realidad un CSV escapado, por lo que no necesitamos un análisis personalizado para esta parte.

Como @Blckknght señala en el comentario, el archivo no es un CSV válido. Voy a hacer algunas suposiciones en mi respuesta. Son

  1. No controlas los datos y, por lo tanto, no puedes escapar correctamente de las comas.
  2. Las tres primeras columnas no contendrán ninguna coma.
  3. La tercera columna sigue la syntax de un dict de python.
  4. Siempre hay un valor en la lista que está en los valores de dict.

Primero, algunas importaciones.

 import ast import pandas as pd 

Solo dividiremos las filas por comas, ya que no tenemos que lidiar con ningún tipo de escape CSV (suposiciones # 1 y # 2).

 rows = (line.split(",", 3) for line in the_data.splitlines() if line.strip() != "") fixed_columns = pd.DataFrame.from_records(rows, columns=["Company", "Date", "Value", "Cars_str"]) 

 fixed_columns = pd.read_csv(..., names=["Company", "Date", "Value", "Cars_str"]) 

Las tres primeras columnas son fijas y las dejamos como están. En la última columna podemos analizar con ast.literal_eval porque es un dict (suposición # 3). Esto es IMO más legible y más flexible si el formato cambia que regex. También podrás detectar el cambio de formato anterior.

 cars = fixed_columns["Cars_str"].apply(ast.literal_eval) del fixed_columns["Cars_str"] 

Y esta parte responde más bien a tu otra pregunta .

Preparamos funciones para procesar las claves y los valores del dictado para que fallen si nuestras suposiciones sobre el contenido del dictado fallan.

 def get_single_item(list_that_always_has_single_item): v, = list_that_always_has_single_item return v def extract_car_name(car_str): assert car_str.startswith("//"), car_str return car_str[2:] 

Aplicamos las funciones y construimos pd.Series que nos permiten …

 dynamic_columns = cars.apply( lambda x: pd.Series({ extract_car_name(k): get_single_item(v) for k, v in x.items() })) 

… agregar las columnas al dataframe

 result = pd.concat([fixed_columns, dynamic_columns], axis=1) result 

Finalmente, conseguimos la mesa:

  Company Date Value BlackCar BlueCar NPO-GreenCar PinkCar \ 0 ABC 2016-6-9 0:00 95 NaN 16.0 NaN NaN 1 ABC 2016-6-10 0:00 0 NaN 90.0 NaN NaN 2 ABC 2016-6-11 0:00 0 NaN 31.0 NaN NaN 3 ABC 2016-6-12 0:00 0 NaN 8888.0 NaN NaN 4 ABC 2016-6-13 0:00 0 NaN 4.0 NaN NaN 5 DEF 2016-6-16 0:00 0 15.0 NaN 0.0 4.0 6 DEF 2016-6-17 0:00 0 15.0 NaN 0.0 4.0 7 DEF 2016-6-18 0:00 0 15.0 NaN 0.0 4.0 8 DEF 2016-6-19 0:00 0 15.0 NaN 0.0 4.0 9 DEF 2016-6-20 0:00 0 15.0 NaN 0.0 4.0 PurpleCar WhiteCar-XYZ YellowCar 0 115.0 0.0 403.0 1 219.0 0.0 381.0 2 817.0 0.0 21.0 3 80.0 0.0 2011.0 4 32.0 0.0 15.0 5 32.0 NaN NaN 6 32.0 NaN NaN 7 32.0 NaN NaN 8 32.0 NaN NaN 9 32.0 NaN NaN 

Esto debería funcionar

 s = pd.read_csv(StringIO(the_data), sep='|', header=None, squeeze=True) left = s.str.split(',').str[:3].apply(pd.Series) left.columns = ['Company', 'Date', 'Volume'] right = s.str.split(',').str[3:].str.join(',') \ .str.replace(r'[\[\]\{\}\']', '') \ .str.replace(r'(:\s+\d+)L', r'\1') \ .str.split(',', expand=True) right.columns = ['Car{}'.format(i) for i in range(1, 5)] pd.concat([left, right], axis=1) 

introduzca la descripción de la imagen aquí

Creo que es mejor convertir las cadenas en dos columnas:

 from io import StringIO import pandas as pd df = pd.read_csv(StringIO(the_data), sep=',', header=None) df.columns = ['Company','Date','Volume','Car1','Car2','Car3','Car4'] cars = ["Car1", "Car2", "Car3", "Car4"] pattern = r"//(?P.+?)':.*?(?P\d+)" df2 = pd.concat([df[col].str .extract(pattern) .assign(value=lambda self: pd.to_numeric(self["value"])) for col in cars], axis=1, keys=cars) 

el resultado:

  Car1 Car2 Car3 Car4 color value color value color value color value 0 PurpleCar 115 YellowCar 403 BlueCar 16 WhiteCar-XYZ 0 1 PurpleCar 219 YellowCar 381 BlueCar 90 WhiteCar-XYZ 0 2 PurpleCar 817 YellowCar 21 BlueCar 31 WhiteCar-XYZ 0 3 PurpleCar 80 YellowCar 2011 BlueCar 8888 WhiteCar-XYZ 0 4 PurpleCar 32 YellowCar 15 BlueCar 4 WhiteCar-XYZ 0 5 PurpleCar 32 BlackCar 15 PinkCar 4 NPO-GreenCar 0 6 PurpleCar 32 BlackCar 15 PinkCar 4 NPO-GreenCar 0 7 PurpleCar 32 BlackCar 15 PinkCar 4 NPO-GreenCar 0 8 PurpleCar 32 BlackCar 15 PinkCar 4 NPO-GreenCar 0 9 PurpleCar 32 BlackCar 15 PinkCar 4 NPO-GreenCar 0