2013-06-14 9 views
101

Pracuję z dużym plikiem csv, a następna kolumna ma ciąg tekstu, który chcę podzielić przez określony ogranicznik. Zastanawiam się, czy istnieje prosty sposób to zrobić przy użyciu pandy lub Pythona?pandy: Jak podzielić tekst w kolumnie na kilka wierszy?

CustNum CustomerName  ItemQty Item Seatblocks     ItemExt 
32363 McCartney, Paul  3  F04 2:218:10:4,6     60 
31316 Lennon, John  25  F01 1:13:36:1,12 1:13:37:1,13  300 

Chcę podzielić przez przestrzeń (' ') a następnie okrężnicy (':') w kolumnie Seatblocks, ale każda komórka spowodowałoby różnej liczbie kolumn. Mam funkcję przestawiania kolumn, aby kolumna Seatblocks znajdowała się na końcu arkusza, ale nie jestem pewien, co robić dalej. Mogę to zrobić w programie Excel z wbudowaną funkcją text-to-columns i szybkim makrem, ale mój zestaw danych ma zbyt wiele rekordów, aby mógł go obsłużyć.

Ostatecznie, chcę wziąć zapisy takie jak John Lennon i stworzyć wiele linii, z informacją z każdego zestawu miejsc w osobnej linii.

+0

to wielkie pytanie dotyczy FlatMap w pand, które obecnie nie istnieje – cdarlint

Odpowiedz

152

To dzieli bloki siedzeń za pomocą spacji i nadaje każdemu swój własny rząd.

In [43]: df 
Out[43]: 
    CustNum  CustomerName ItemQty Item     Seatblocks ItemExt 
0 32363 McCartney, Paul  3 F04    2:218:10:4,6  60 
1 31316  Lennon, John  25 F01 1:13:36:1,12 1:13:37:1,13  300 

In [44]: s = df['Seatblocks'].str.split(' ').apply(Series, 1).stack() 

In [45]: s.index = s.index.droplevel(-1) # to line up with df's index 

In [46]: s.name = 'Seatblocks' # needs a name to join 

In [47]: s 
Out[47]: 
0 2:218:10:4,6 
1 1:13:36:1,12 
1 1:13:37:1,13 
Name: Seatblocks, dtype: object 

In [48]: del df['Seatblocks'] 

In [49]: df.join(s) 
Out[49]: 
    CustNum  CustomerName ItemQty Item ItemExt Seatblocks 
0 32363 McCartney, Paul  3 F04  60 2:218:10:4,6 
1 31316  Lennon, John  25 F01  300 1:13:36:1,12 
1 31316  Lennon, John  25 F01  300 1:13:37:1,13 

Albo dać każdy ciąg oddzielonych dwukropkami we własnej kolumnie:

In [50]: df.join(s.apply(lambda x: Series(x.split(':')))) 
Out[50]: 
    CustNum  CustomerName ItemQty Item ItemExt 0 1 2  3 
0 32363 McCartney, Paul  3 F04  60 2 218 10 4,6 
1 31316  Lennon, John  25 F01  300 1 13 36 1,12 
1 31316  Lennon, John  25 F01  300 1 13 37 1,13 

To jest trochę brzydki, ale może ktoś będzie dostroić się z ładniejszej rozwiązania.

+7

@DanAllan podać indeks do serii, gdy wniosek; staną się nazwami kolumn – Jeff

+0

To jest świetna odpowiedź. Jednak '.str.split ('') .apply (Series, 1) .stack()' może być problematyczne, jeśli wywołanie split() nie zwraca listy (tj. Gdy ciąg znaków nie zawiera spacji), dtype obiektu kolumny będzie seria, a nie ciąg znaków. – tmarthal

+3

Podczas gdy to odpowiada na pytanie, warto wspomnieć, że (prawdopodobnie) split() tworzy listę dla każdego wiersza, który bardzo szybko wysuwa rozmiar 'DataFrame'. W moim przypadku uruchomienie kodu na tablicy ~ 200M spowodowało użycie ~ 10G pamięci (+ zamiana ...). –

47

Inaczej niż Dan, uważam jego odpowiedź za dość elegancką ... ale niestety jest ona również bardzo nieefektywna. Tak, ponieważ kwestia wspomniano „dużego pliku CSV”, pozwól mi zasugerować, aby spróbować w powłoce rozwiązanie Dana:

time python -c "import pandas as pd; 
df = pd.DataFrame(['a b c']*100000, columns=['col']); 
print df['col'].apply(lambda x : pd.Series(x.split(' '))).head()" 

... w porównaniu do tej alternatywy:

time python -c "import pandas as pd; 
from scipy import array, concatenate; 
df = pd.DataFrame(['a b c']*100000, columns=['col']); 
print pd.DataFrame(concatenate(df['col'].apply(lambda x : [x.split(' ')]))).head()" 

.. . i tak:

time python -c "import pandas as pd; 
df = pd.DataFrame(['a b c']*100000, columns=['col']); 
print pd.DataFrame(dict(zip(range(3), [df['col'].apply(lambda x : x.split(' ')[i]) for i in range(3)]))).head()" 

drugi prostu powstrzymuje się od przydzielania 100 000 Series, a to wystarczy, aby uczynić go około 10 razy szybciej. Ale trzecie rozwiązanie, które dość ironicznie marnuje wiele wywołań dla str.split() (nazywa się raz na kolumnę w wierszu, więc trzy razy więcej niż dla pozostałych dwóch rozwiązań), jest około 40 razy szybsze niż po pierwsze, ponieważ pozwala nawet uniknąć wprowadzenia 100 000 list. I tak, na pewno jest trochę brzydki ...

EDIT:this answer sugeruje, jak używać „to_list()” i aby uniknąć konieczności lambda. Rezultat jest podobny do tego, który jest jeszcze bardziej wydajny niż trzecie rozwiązanie, a na pewno znacznie bardziej elegancki.

EDIT: parzyste prostsze

time python -c "import pandas as pd; 
df = pd.DataFrame(['a b c']*100000, columns=['col']); 
print pd.DataFrame(list(df.col.str.split())).head()" 

działa zbyt, i jest prawie jako skuteczny.

EDYTOWANIE:even simpler! I obsługuje Nans (ale mniej wydajne):

time python -c "import pandas as pd; 
df = pd.DataFrame(['a b c']*100000, columns=['col']); 
print df.col.str.split(expand=True).head()" 
+0

Mam trochę problemów z ilością pamięci, którą ta metoda zużywa i zastanawiam się, czy mógłbyś mi dać jakąś radę. Mam DataFrame, która zawiera około 8000 wierszy, każdy z ciągiem zawierającym 9216 rozdzielanych spacjami 8-bitowych liczb całkowitych. To około 75 MB, ale kiedy zastosuję ostatnie rozwiązanie dosłownie, Python zjada 2 GB mojej pamięci. Czy możesz skierować mnie w stronę jakiegoś źródła, które powie mi, dlaczego tak jest i co mogę zrobić, aby go ominąć? Dzięki. –

+1

Masz wiele list i bardzo małych ciągów, co jest mniej więcej najgorszym przypadkiem użycia pamięci w pythonie (a krok pośredni ".split(). Tolist()" tworzy czyste obiekty Pythona). To, co prawdopodobnie zrobiłbym na twoim miejscu, to zrzucenie DataFrame do pliku, a następnie otwarcie go jako csv z read_csv (..., sep = ''). Ale aby pozostać na temacie: pierwsze rozwiązanie (wraz z trzecim, które powinno być strasznie wolne) może być tym, który oferuje ci najniższe użycie pamięci wśród 4, ponieważ masz stosunkowo małą liczbę stosunkowo długich rzędów. –

+0

Hej Pietro, wypróbowałem twoją propozycję zapisania do pliku i ponownego załadowania, i działało całkiem nieźle. Wpadłem na pewne kłopoty, gdy próbowałem to zrobić w obiekcie StringIO, a dobre rozwiązanie mojego problemu zostało opublikowane [tutaj] (http://stackoverflow.com/questions/24562869/pandas-unable-to-read- from-large-stringio-object). –

9
import pandas as pd 
import numpy as np 

df = pd.DataFrame({'ItemQty': {0: 3, 1: 25}, 
        'Seatblocks': {0: '2:218:10:4,6', 1: '1:13:36:1,12 1:13:37:1,13'}, 
        'ItemExt': {0: 60, 1: 300}, 
        'CustomerName': {0: 'McCartney, Paul', 1: 'Lennon, John'}, 
        'CustNum': {0: 32363, 1: 31316}, 
        'Item': {0: 'F04', 1: 'F01'}}, 
        columns=['CustNum','CustomerName','ItemQty','Item','Seatblocks','ItemExt']) 

print (df) 
    CustNum  CustomerName ItemQty Item     Seatblocks ItemExt 
0 32363 McCartney, Paul  3 F04    2:218:10:4,6  60 
1 31316  Lennon, John  25 F01 1:13:36:1,12 1:13:37:1,13  300 

Inne podobne rozwiązanie z łańcuchowych jest używać reset_index i rename:

print (df.drop('Seatblocks', axis=1) 
      .join 
      (
      df.Seatblocks 
      .str 
      .split(expand=True) 
      .stack() 
      .reset_index(drop=True, level=1) 
      .rename('Seatblocks')   
      )) 

    CustNum  CustomerName ItemQty Item ItemExt Seatblocks 
0 32363 McCartney, Paul  3 F04  60 2:218:10:4,6 
1 31316  Lennon, John  25 F01  300 1:13:36:1,12 
1 31316  Lennon, John  25 F01  300 1:13:37:1,13 

Jeśli w kolumnie są NIENaN wartości, najszybszym rozwiązaniem jest użycie list rozumienia z DataFrame Tor:

df = pd.DataFrame(['a b c']*100000, columns=['col']) 

In [141]: %timeit (pd.DataFrame(dict(zip(range(3), [df['col'].apply(lambda x : x.split(' ')[i]) for i in range(3)])))) 
1 loop, best of 3: 211 ms per loop 

In [142]: %timeit (pd.DataFrame(df.col.str.split().tolist())) 
10 loops, best of 3: 87.8 ms per loop 

In [143]: %timeit (pd.DataFrame(list(df.col.str.split()))) 
10 loops, best of 3: 86.1 ms per loop 

In [144]: %timeit (df.col.str.split(expand=True)) 
10 loops, best of 3: 156 ms per loop 

In [145]: %timeit (pd.DataFrame([ x.split() for x in df['col'].tolist()])) 
10 loops, best of 3: 54.1 ms per loop 

Ale jeśli kolumna zawiera NaN działa tylko str.split z parametrem expand=True które zwracają DataFrame (documentation), i wyjaśnić, dlaczego jest slowier:

df = pd.DataFrame(['a b c']*10, columns=['col']) 
df.loc[0] = np.nan 
print (df.head()) 
    col 
0 NaN 
1 a b c 
2 a b c 
3 a b c 
4 a b c 

print (df.col.str.split(expand=True)) 
    0  1  2 
0 NaN None None 
1 a  b  c 
2 a  b  c 
3 a  b  c 
4 a  b  c 
5 a  b  c 
6 a  b  c 
7 a  b  c 
8 a  b  c 
9 a  b  c 
+0

Może warto wspomnieć, że koniecznie potrzebujesz opcji 'expand = True' działającej z' pandas.DataFrames' podczas używania '.str.split()' na przykład. – holzkohlengrill

+0

@holzkohlengrill - dziękuję za komentarz, dodam go do odpowiedzi. – jezrael

Powiązane problemy