2016-07-05 14 views
8

Jeśli mam DataFrame takie, że:Rozwiń pandy kolumnę DataFrame w wielu wierszach

pd.DataFrame({"name" : "John", 
       "days" : [[1, 3, 5, 7]] 
       }) 

daje tę strukturę:

  days name 
0 [1, 3, 5, 7] John 

Jak rozszerzyć to na następujące?

days name 
0  1 John 
1  3 John 
2  5 John 
3  7 John 
+0

Nie do końca rozumiem, dlaczego chcesz to zrobić? Czy to dlatego, że masz taki słownik i chcesz go przekształcić w ramkę danych? A w kolumnie "nazwa" chcesz mieć tę samą wartość przez cały czas? –

Odpowiedz

7

Można użyć df.itertuples iterację każdego wiersza i użyć wyrażeń listowych do przekształcania danych w żądanej postaci:

import pandas as pd 

df = pd.DataFrame({"name" : ["John", "Eric"], 
       "days" : [[1, 3, 5, 7], [2,4]]}) 
result = pd.DataFrame([(d, tup.name) for tup in df.itertuples() for d in tup.days]) 
print(result 

plony

0  1 
0 1 John 
1 3 John 
2 5 John 
3 7 John 
4 2 Eric 
5 4 Eric 

Divakar's solution , using_repeat, jest najszybszy:

In [48]: %timeit using_repeat(df) 
1000 loops, best of 3: 834 µs per loop 

In [5]: %timeit using_itertuples(df) 
100 loops, best of 3: 3.43 ms per loop 

In [7]: %timeit using_apply(df) 
1 loop, best of 3: 379 ms per loop 

In [8]: %timeit using_append(df) 
1 loop, best of 3: 3.59 s per loop 

Oto konfiguracja wykorzystywane do powyższego wzorca:

import numpy as np 
import pandas as pd 

N = 10**3 
df = pd.DataFrame({"name" : np.random.choice(list('ABCD'), size=N), 
        "days" : [np.random.randint(10, size=np.random.randint(5)) 
           for i in range(N)]}) 

def using_itertuples(df): 
    return pd.DataFrame([(d, tup.name) for tup in df.itertuples() for d in tup.days]) 

def using_repeat(df): 
    lens = [len(item) for item in df['days']] 
    return pd.DataFrame({"name" : np.repeat(df['name'].values,lens), 
          "days" : np.concatenate(df['days'].values)}) 

def using_apply(df): 
    return (df.apply(lambda x: pd.Series(x.days), axis=1) 
      .stack() 
      .reset_index(level=1, drop=1) 
      .to_frame('day') 
      .join(df['name'])) 

def using_append(df): 
    df2 = pd.DataFrame(columns = df.columns) 
    for i,r in df.iterrows(): 
     for e in r.days: 
      new_r = r.copy() 
      new_r.days = e 
      df2 = df2.append(new_r) 
    return df2 
+0

Zawracam ci głowę, ponieważ właśnie zmodyfikowałem kopalnię, aby zastąpić 'np.concatenate' przez' np.hstack'. Wydaje się, że jest trochę szybciej. Czy masz coś przeciwko temu, aktualizując czasy? :) – Divakar

+0

@Divakar: Jestem zaskoczony. To spore ulepszenie! – unutbu

+0

Doceń aktualizacje! Cóż, jestem zaskoczony widząc np.hstack jest szybszy niż np.concatenate, ponieważ miałem wrażenie, że wszystkie te hstack i vstacks pochodzą z np.concatenate. Może z pandami robi optymalizacje? Niepewny! – Divakar

0

inne rozwiązanie:

In [139]: (df.apply(lambda x: pd.Series(x.days), axis=1) 
    .....: .stack() 
    .....: .reset_index(level=1, drop=1) 
    .....: .to_frame('day') 
    .....: .join(df['name']) 
    .....:) 
Out[139]: 
    day name 
0 1 John 
0 3 John 
0 5 John 
0 7 John 
4

Oto coś z NumPy -

lens = [len(item) for item in df['days']] 
df_out = pd.DataFrame({"name" : np.repeat(df['name'].values,lens), 
       "days" : np.hstack(df['days']) 
       }) 

Jak wskazano w @unutbu's solutionnp.concatenate(df['days'].values) byłaby szybsza niż np.hstack(df['days']).

Korzysta ze sprzężenia w pętli do wyodrębniania długości każdego elementu 'days', który musi być minimalny w czasie wykonywania.

Sample Run -

>>> df 
      days name 
0 [1, 3, 5, 7] John 
1  [2, 4] Eric 
>>> lens = [len(item) for item in df['days']] 
>>> pd.DataFrame({"name" : np.repeat(df['name'].values,lens), 
...    "days" : np.hstack(df['days']) 
...    }) 
    days name 
0  1 John 
1  3 John 
2  5 John 
3  7 John 
4  2 Eric 
5  4 Eric 
1

Pewnie jakoś tak:

df2 = pd.DataFrame(columns = df.columns) 
for i,r in df.iterrows(): 
    for e in r.days: 
     new_r = r.copy() 
     new_r.days = e 
     df2 = df2.append(new_r) 
df2 
1

A 'native' rozwiązanie pandy - my rozebranie stosu kolumnę w serii, a następnie dołączyć z powrotem na podstawie indeksu:

import pandas as pd #import 
x2 = x.days.apply(lambda x: pd.Series(x)).unstack() #make an unstackeded series, x2 
x.drop('days', axis = 1).join(pd.DataFrame(x2.reset_index(level=0, drop=True))) #drop the days column, join to the x2 series 
Powiązane problemy