Cómo usar corchetes como un carácter de cita en Pandas.read_csv

Digamos que tengo un archivo de texto que se ve así:

Item,Date,Time,Location 1,01/01/2016,13:41,[45.2344:-78.25453] 2,01/03/2016,19:11,[43.3423:-79.23423,41.2342:-81242] 3,01/10/2016,01:27,[51.2344:-86.24432] 

Lo que me gustaría poder hacer es leerlo con pandas.read_csv , pero la segunda fila generará un error. Aquí está el código que estoy usando actualmente:

 import pandas as pd df = pd.read_csv("path/to/file.txt", sep=",", dtype=str) 

He intentado establecer quotechar en “[“, pero eso obviamente devora las líneas hasta el siguiente corchete abierto y agregar un corchete de cierre da como resultado un error de “cadena de longitud 2 encontrada”. Cualquier idea sería muy apreciada. ¡Gracias!

Actualizar

Se ofrecieron tres soluciones principales: 1) Asignar un amplio rango de nombres al dataframe para permitir que se lean todos los datos y luego procesar posteriormente los datos, 2) Encontrar valores entre corchetes y poner comillas a su alrededor. o 3) reemplazar el primer número n de comas con punto y coma.

En general, no creo que la opción 3 sea una solución viable en general (aunque está bien para mis datos) porque a) ¿qué pasa si he citado valores en una columna que contienen comas, yb) qué sucede si mi columna con corchetes es no la ultima columna? Eso deja a las soluciones 1 y 2. Creo que la solución 2 es más legible, pero la solución 1 fue más eficiente, se ejecutó en solo 1.38 segundos, en comparación con la solución 2, que se ejecutó en 3.02 segundos. Las pruebas se ejecutaron en un archivo de texto que contiene 18 columnas y más de 208,000 filas.

Creo que puede replace 3 primeras apariciones de , en cada línea de archivo a ; y luego use el parámetro sep=";" en read_csv :

 import pandas as pd import io with open('file2.csv', 'r') as f: lines = f.readlines() fo = io.StringIO() fo.writelines(u"" + line.replace(',',';', 3) for line in lines) fo.seek(0) df = pd.read_csv(fo, sep=';') print df Item Date Time Location 0 1 01/01/2016 13:41 [45.2344:-78.25453] 1 2 01/03/2016 19:11 [43.3423:-79.23423,41.2342:-81242] 2 3 01/10/2016 01:27 [51.2344:-86.24432] 

O puede probar este enfoque complicado, porque el problema principal es, separador , entre los valores en las lists es igual que el separador de otros valores de columna.

Así que necesitas post-procesamiento:

 import pandas as pd import io temp=u"""Item,Date,Time,Location 1,01/01/2016,13:41,[45.2344:-78.25453] 2,01/03/2016,19:11,[43.3423:-79.23423,41.2342:-81242,41.2342:-81242] 3,01/10/2016,01:27,[51.2344:-86.24432]""" #after testing replace io.StringIO(temp) to filename #estimated max number of columns df = pd.read_csv(io.StringIO(temp), names=range(10)) print df 0 1 2 3 4 \ 0 Item Date Time Location NaN 1 1 01/01/2016 13:41 [45.2344:-78.25453] NaN 2 2 01/03/2016 19:11 [43.3423:-79.23423 41.2342:-81242 3 3 01/10/2016 01:27 [51.2344:-86.24432] NaN 5 6 7 8 9 0 NaN NaN NaN NaN NaN 1 NaN NaN NaN NaN NaN 2 41.2342:-81242] NaN NaN NaN NaN 3 NaN NaN NaN NaN NaN 
 #remove column with all NaN df = df.dropna(how='all', axis=1) #first row get as columns names df.columns = df.iloc[0,:] #remove first row df = df[1:] #remove columns name df.columns.name = None #get position of column Location print df.columns.get_loc('Location') 3 #df1 with Location values df1 = df.iloc[:, df.columns.get_loc('Location'): ] print df1 Location NaN NaN 1 [45.2344:-78.25453] NaN NaN 2 [43.3423:-79.23423 41.2342:-81242 41.2342:-81242] 3 [51.2344:-86.24432] NaN NaN #combine values to one column df['Location'] = df1.apply( lambda x : ', '.join([e for e in x if isinstance(e, basestring)]), axis=1) #subset of desired columns print df[['Item','Date','Time','Location']] Item Date Time Location 1 1 01/01/2016 13:41 [45.2344:-78.25453] 2 2 01/03/2016 19:11 [43.3423:-79.23423, 41.2342:-81242, 41.2342:-8... 3 3 01/10/2016 01:27 [51.2344:-86.24432] 

Podemos usar el truco simple: citar los corchetes equilibrados con comillas dobles:

 import re import six import pandas as pd data = """\ Item,Date,Time,Location,junk 1,01/01/2016,13:41,[45.2344:-78.25453],[aaaa,bbb] 2,01/03/2016,19:11,[43.3423:-79.23423,41.2342:-81242],[0,1,2,3] 3,01/10/2016,01:27,[51.2344:-86.24432],[12,13] 4,01/30/2016,05:55,[51.2344:-86.24432,41.2342:-81242,55.5555:-81242],[45,55,65]""" print('{0:-^70}'.format('original data')) print(data) data = re.sub(r'(\[[^\]]*\])', r'"\1"', data, flags=re.M) print('{0:-^70}'.format('quoted data')) print(data) df = pd.read_csv(six.StringIO(data)) print('{0:-^70}'.format('data frame')) pd.set_option('display.expand_frame_repr', False) print(df) 

Salida:

 ----------------------------original data----------------------------- Item,Date,Time,Location,junk 1,01/01/2016,13:41,[45.2344:-78.25453],[aaaa,bbb] 2,01/03/2016,19:11,[43.3423:-79.23423,41.2342:-81242],[0,1,2,3] 3,01/10/2016,01:27,[51.2344:-86.24432],[12,13] 4,01/30/2016,05:55,[51.2344:-86.24432,41.2342:-81242,55.5555:-81242],[45,55,65] -----------------------------quoted data------------------------------ Item,Date,Time,Location,junk 1,01/01/2016,13:41,"[45.2344:-78.25453]","[aaaa,bbb]" 2,01/03/2016,19:11,"[43.3423:-79.23423,41.2342:-81242]","[0,1,2,3]" 3,01/10/2016,01:27,"[51.2344:-86.24432]","[12,13]" 4,01/30/2016,05:55,"[51.2344:-86.24432,41.2342:-81242,55.5555:-81242]","[45,55,65]" ------------------------------data frame------------------------------ Item Date Time Location junk 0 1 01/01/2016 13:41 [45.2344:-78.25453] [aaaa,bbb] 1 2 01/03/2016 19:11 [43.3423:-79.23423,41.2342:-81242] [0,1,2,3] 2 3 01/10/2016 01:27 [51.2344:-86.24432] [12,13] 3 4 01/30/2016 05:55 [51.2344:-86.24432,41.2342:-81242,55.5555:-81242] [45,55,65] 

ACTUALIZACIÓN : si está seguro de que todos los corchetes son saldos, no tenemos que usar RegEx:

 import io import pandas as pd with open('35948417.csv', 'r') as f: fo = io.StringIO() data = f.readlines() fo.writelines(line.replace('[', '"[').replace(']', ']"') for line in data) fo.seek(0) df = pd.read_csv(fo) print(df) 

No se me ocurre una manera de engañar al analizador CSV para que acepte distintos caracteres de comillas de apertura / cierre, pero puede salirse con un paso de preprocesamiento bastante simple:

 import pandas as pd import io import re # regular expression to capture contents of balanced brackets location_regex = re.compile(r'\[([^\[\]]+)\]') with open('path/to/file.txt', 'r') as fi: # replaced brackets with quotes, pipe into file-like object fo = io.StringIO() fo.writelines(unicode(re.sub(location_regex, r'"\1"', line)) for line in fi) # rewind file to the beginning fo.seek(0) # read transformed CSV into data frame df = pd.read_csv(fo) print df 

Esto le da un resultado como

  Date_Time Item Location 0 2016-01-01 13:41:00 1 [45.2344:-78.25453] 1 2016-01-03 19:11:00 2 [43.3423:-79.23423, 41.2342:-81242] 2 2016-01-10 01:27:00 3 [51.2344:-86.24432] 

Editar Si la memoria no es un problema, es mejor que preproceses los datos de forma masiva en lugar de línea por línea, como se hace en la respuesta de Max .

 # regular expression to capture contents of balanced brackets location_regex = re.compile(r'\[([^\[\]]+)\]', flags=re.M) with open('path/to/file.csv', 'r') as fi: data = unicode(re.sub(location_regex, r'"\1"', fi.read())) df = pd.read_csv(io.StringIO(data)) 

Si sabe de antemano que los únicos paréntesis en el documento son los que rodean las coordenadas de ubicación, y que se garantiza que estén equilibrados, entonces puede simplificarlo aún más (Max sugiere una versión línea por línea de esto, pero Creo que la iteración es innecesaria):

 with open('/path/to/file.csv', 'r') as fi: data = unicode(fi.read().replace('[', '"').replace(']', '"') df = pd.read_csv(io.StringIO(data)) 

A continuación se muestran los resultados de tiempo que obtuve con un conjunto de datos de 200k por 3 columnas. Cada vez se promedia más de 10 bashs.

  • post-procesamiento del dataframe ( solución de jezrael ): 2.19s
  • línea por línea regex: 1.36s
  • expresión regular a granel: 0.39s
  • cadena a granel reemplazar: 0.14s