2016-03-11 18 views
5

Lassen Sie sich verwenden, sagt, dass ich eine Textdatei, die wie folgt aussieht:Wie eckige Klammern als Anführungszeichen in Pandas.read_csv

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] 

Was ich in der Lage sein mag, dass in lesen wird mit pandas.read_csv, aber die zweite Zeile wird einen Fehler auslösen. Hier ist der Code, den ich zur Zeit bin mit:

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

Ich habe versucht zu setzen quotechar auf „[“, aber das offensichtlich isst nur bis die Linien bis zur nächsten offenen Klammer und das Hinzufügen einer schließende Klammer ergibt eine "String der Länge 2 gefunden" Fehler. Jede Einsicht würde sehr geschätzt werden. Vielen Dank!

aktualisieren

gibt drei primäre Lösungen waren, die angeboten wurden: 1) Geben Sie eine lange Reihe von Namen in den Datenrahmen alle Daten zu ermöglichen, werden eingelesen und dann nachbearbeiten die Daten, 2) Finden Werte in eckigen Klammern und setzen Sie es in Anführungszeichen, oder 3) ersetzen Sie die erste n Kommata durch Semikolons.

Insgesamt glaube ich nicht, dass Option 3 eine brauchbare Lösung im Allgemeinen ist (wenn auch nur für meine Daten), a) was ist, wenn ich Werte in einer Spalte zitieren, die Kommas enthalten, und b) was wenn meine Spalte mit eckigen Klammern ist nicht die letzte Spalte? Das lässt die Lösungen 1 und 2 zurück. Ich denke, Lösung 2 ist besser lesbar, aber Lösung 1 war effizienter und lief in nur 1,38 Sekunden, verglichen mit Lösung 2, die in 3,02 Sekunden lief. Die Tests wurden in einer Textdatei ausgeführt, die 18 Spalten und mehr als 208.000 Zeilen enthielt.

+2

Keine der CSV-Implementierungen, die ich gesehen habe, unterscheiden Open/Close-Anführungszeichen, was das Hauptproblem hier ist. Am besten ist es, wenn Sie die Datei vorverarbeiten und die Klammern durch ausgeglichene Anführungszeichen ersetzen. Dies kann ziemlich einfach mit regulären Ausdrücken geschehen (in Python oder in einem Streaming-Tool wie 'awk'). –

+0

einfach ersetzen] bis [, vor dem Übergeben an CSV loader? – YOU

Antwort

1

Ich denke, man kann replace erste 3 Vorkommen von , in jeder Zeile der Datei zu ; und dann Parameter verwendet sep=";" in 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] 

Oder kannst diesen komplizierten Ansatz versuchen, weil Hauptproblem ist, Separator , zwischen Werten in lists ist dasselbe wie Trennzeichen anderer Spaltenwerte.

So müssen Sie Post - Bearbeitung:

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] 
+0

Ja, es ist ein anderer Weg. Aber wenn der Separator zwischen den Spalten anders ist als ',' z.B. ';' oder '|', das ist eine andere Lösung. – jezrael

+0

Das ist kein schlechter Ansatz, da keine Daten außer dem Ort Kommas enthalten sollten. Ich werde diesen Ansatz ebenfalls berücksichtigen. – brittenb

1

Ich kann nicht einen Weg finden, die CSV-Parser zu akzeptieren unterschiedliche Öffnen/Schließen Anführungszeichen zu betrügen, aber man kann weg mit einem recht einfachen Vorverarbeitungsschritt:

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 

Diese Sie wie

012.351 zu einem Resultat
  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] 

bearbeiten Wenn der Speicher kein Problem ist, dann sind Sie besser dran, die Daten in der Masse Vorverarbeitung anstatt Zeile für Zeile, wie in Max's answer erfolgt.

# 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)) 

Wenn Sie im Voraus wissen, dass die einzigen Klammern in dem Dokument sind diejenigen, die Lage Umgebung koordiniert, und dass sie garantiert ausgeglichen sein, dann können Sie es vereinfachen noch weiter (Max schlägt eine line-by -Linie Version davon, aber ich denke, die Iteration nicht erforderlich ist):

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

df = pd.read_csv(io.StringIO(data)) 

Nachfolgend finden Sie das Timing Ergebnisse sind I durch 3-Spalten-Datensatz mit einer 200k-Reihe bekam. Jedes Mal wird über 10 Versuche gemittelt.

  • Datenrahmen Nachverarbeitung (jezrael's solution): 2.19s
  • zeilen regex: 1.36s
  • bulk regex: 0.39s
  • bulk Zeichenfolge ersetzt werden: 0.14 s
+0

Ich bekomme diesen Fehler mit dem Code: 'TypeError: Unicode-Argument erwartet, bekam 'str'' – brittenb

+0

Fixed, musste die transformierten Zeilen in' Unicode' wickeln, so dass 'StringIO' nicht klagen. Tut mir leid, ich habe dies mit einem etwas anderen Setup getestet (verwendet 'StringIO' als Eingabe, ohne die Datei von der Festplatte zu lesen). –

+0

Das funktioniert, ist aber leider langsamer als der ursprüngliche Ansatz von @ Jezrael. Das Timing bei diesem Ansatz mit 203.845 Zeilen beträgt 3,04 Sekunden auf meiner Maschine, verglichen mit 1,34 Sekunden bei Jezrael. – brittenb

1

Wir können verwenden einfacher Trick - Zitat ausgeglichen eckige Klammern mit Anführungszeichen:

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) 

Ausgang:

----------------------------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] 

UPDATE: wenn Sie sicher sind, dass alle eckigen Klammern Salden sind wir RegEx die nicht verwenden :

+0

@brittenb, könnten Sie bitte auch meine aktualisierte Version gegen Ihre Daten testen? Vielen Dank! Es sollte auch für alle Spalten mit eckigen Klammern funktionieren, unabhängig von deren Positionen ... – MaxU

+0

Gibt es einen Tippfehler in Ihrem aktualisierten Code? Würde 'fo = six.StringIO()' nicht innerhalb der 'for'-Schleife eingefügt, um jedes Mal eine neue Datei zu erstellen? Ich frage nur, weil ich das 'Six'-Modul vorher noch nie benutzt habe, also ist es vielleicht anders. – brittenb

+0

@brittenb, danke für die Korrektur! Ich habe meine Antwort aktualisiert. Ich benutze 'six' nur aus Kompatibilitätsgründen sp, dass dieser Code mit beiden Python-Versionen funktioniert - Python 2 und Python 3. Sie können stattdessen io.StringIO verwenden. – MaxU

Verwandte Themen