2016-03-11 21 views
5

Powiedzmy, że mamy plik tekstowy, który wygląda tak:Jak używać nawiasów kwadratowych jako postać cytując w 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] 

Co chciałbym być w stanie zrobić to, że odczytywane z pandas.read_csv, ale w drugim rzędzie pojawi się błąd. Oto kod obecnie używam:

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

Próbowałem ustawić quotechar do „[”, ale to oczywiście tylko zjada linii aż do następnego wolnego wspornika i dodanie wyników wspornika zamykającej w Znaleziono błąd "string of length 2". Każdy wgląd byłby bardzo doceniony. Dzięki!

Aktualizacja

Były trzy podstawowe rozwiązania, które oferowane były: 1) Daj długi szereg nazwisk do ramki danych w celu umożliwienia wszystkie dane należy czytać, a potem post-przetwarzania danych, 2) Znajdź wartości w nawiasach kwadratowych i umieść w cudzysłowie lub 3) zastąp pierwszy n liczbę przecinków średnikami.

Ogólnie rzecz biorąc, nie sądzę, że opcja 3 jest ogólnie dostępnym rozwiązaniem (choć dobrze dla moich danych), ponieważ a) co jeśli powołuję się na wartości w jednej kolumnie, które zawierają przecinki, i b) co jeśli moja kolumna w nawiasach kwadratowych nie jest ostatnią kolumną? To pozostawia rozwiązania 1 i 2. Myślę, że rozwiązanie 2 jest bardziej czytelne, ale rozwiązanie 1 było bardziej wydajne, działające w ciągu zaledwie 1,38 sekundy, w porównaniu do rozwiązania 2, które trwało 3,02 sekundy. Testy zostały przeprowadzone na pliku tekstowym zawierającym 18 kolumn i ponad 208 000 wierszy.

+2

Żadna z wdrożonych CSV nie odróżnia otwartych/zamkniętych znaków cudzysłowu, co jest tutaj głównym problemem. Najlepiej jest przetworzyć plik i zamienić nawiasy na zrównoważone cytaty. Można to zrobić po prostu za pomocą wyrażeń regularnych (w Pythonie lub w narzędziu do strumieniowania, takim jak 'awk'). –

+0

po prostu wymienić] na [, przed przejściem do programu ładującego csv? – YOU

Odpowiedz

1

myślę można replace 3 pierwsze wystąpienie , w każdej linii pliku do ; a następnie użyj parametru sep=";" w 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] 

Można spróbować tego skomplikowanego podejścia, ponieważ głównym problemem jest to, separator , między wartościami w lists jest taka sama jak separator innych wartości kolumn.

Więc trzeba Post - Przetwarzanie:

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

Tak, to jest inny sposób. Ale jeśli separator między kolumnami jest inny jako ',' np. ';' lub '|', to jest kolejne rozwiązanie. – jezrael

+0

To nie jest złe podejście, ponieważ żaden z danych, poza lokalizacją, nie powinien zawierać przecinków. Rozważę też to podejście. – brittenb

1

Nie mogę myśleć o sposób, aby oszukać parsera CSV do przyjęcia odrębnych otwierania/zamykania znaki cudzysłowu, ale można uciec z dość prostym kroku przeróbki:

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 

daje to wynik podobny

  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] 

Edit Jeśli pamięć nie jest problemem, to jesteś lepiej przerób dane luzem, a nie linia po linii, jak to się dzieje w Max's answer.

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

Jeśli wiesz z wyprzedzeniem, że jedynymi nawiasy w dokumencie są otaczający lokalizację współrzędne, i że są one gwarancją zrównoważony, to można uprościć jeszcze bardziej (Max proponuje linię po -line wersja tego, ale myślę, że iteracja jest niepotrzebna):

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

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

Poniżej znajdują się wyniki czasowe dostałem z 200k-rzędu przez 3 kolumnach zestawu danych. Za każdym razem uśrednia się 10 prób.

  • ramka danych post-procesor (jezrael's solution) 2.19s
  • linia po linii regex: regex 1.36s
  • nasypowa: 0.39s
  • luzem łańcuch wymienić: 0,14 s
+0

Otrzymuję ten błąd z kodem: 'TypeError: oczekiwano argumentu unicode, dostałem 'str'' – brittenb

+0

Naprawiono, musiałem zawinąć transformowane linie w' unicode', aby 'StringIO' nie narzekało. Przepraszam, testowałem to z nieco inną konfiguracją (użyłem 'StringIO' jako wejścia bez odczytu pliku z dysku). –

+0

To działa, ale jest niestety wolniejsze niż oryginalne podejście @ jezrael. Czas w tym podejściu z 203,845 liniami wynosi 3,04 sekundy na mojej maszynie, w porównaniu do 1,38 sekundy z Jezraelem. – brittenb

1

Możemy użyć Prosty trik - cytuję zbilansowane nawiasy kwadratowe z cudzysłowach:

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) 

wyjściowa:

----------------------------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: jeśli jesteś pewien, że wszystkie nawiasy kwadratowe są bilanse, nie trzeba używać RegEx na :

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

@brittenb, czy mógłbyś przetestować moją zaktualizowaną wersję na podstawie twoich danych? Dzięki! Powinien także działać dla każdej kolumny mającej nawiasy kwadratowe, niezależnie od ich pozycji ... – MaxU

+0

Czy w zaktualizowanym kodzie jest literówka? Czy wprowadzenie 'fo = six.StringIO()' wewnątrz pętli 'for' nie spowoduje utworzenia nowego pliku za każdym razem? Pytam tylko dlatego, że nigdy wcześniej nie korzystałem z modułu 'six', więc może jest inaczej. – brittenb

+0

@brittenb, dzięki za korektę! Zaktualizowałem swoją odpowiedź. Używam 'six' tylko ze względu na kompatybilność sp, że ten kod będzie działał z obydwoma wersjami Pythona - Python 2 i Python 3. Więc możesz użyć zamiast tego opcji io.StringIO. – MaxU