2017-10-10 88 views
13

Jest to kolejne pytanie do this one, gdzie jezrael użył pandas.DataFrame.groupby, aby zwiększyć o współczynnik kilkuset szybkość tworzenia listy. Szczególnie, pozwala df być duża dataframe, następnieDlaczego groupby jest tak szybki?

index = list(set(df.index)) 
list_df = [df.loc(x) for x in index] 

i

list_df = [x for i,x in df.groupby(level=0, sort=False)] 

ten sam wynik, przy czym ten ostatni jest ponad 200 razy szybciej niż poprzednio, nawet pomijając etap tworzenia list. Czemu?

Byłbym bardzo zadowolony, gdyby ktoś mógł pozwolić mi zrozumieć, dlaczego istnieje tak ogromna różnica w wydajności. Z góry dziękuję!

Edytuj:, jak zasugerował Alex Riley w swoim komentarzu, potwierdzam, że testy zostały przeprowadzone na ramce danych z nieunikalnym i niemonotonicznym indeksem.

+0

Uważam, że jak jest bardziej interesująca niż dlaczego. –

+2

Wygląda na to, że masz nieunikalny indeks, być może również niemonotoniczny. W takich zdegenerowanych przypadkach, przy każdym wywołaniu 'loc', wierzę, że pandy muszą iterować nad * całym * indeksem, aby zbudować nową tablicę (o tej samej długości co indeks), która posłuży do indeksowania boolowskiego. OTOH, 'groupby' po prostu przeszukuje indeks i śledzi liczby całkowite dla każdej etykiety. Musiałbym dokładnie sprawdzić wszystko w źródle, żeby się upewnić. –

Odpowiedz

5

Ponieważ ramka danych nie jest posortowana w indeksie, co oznacza, że ​​wszystkie podzbiory muszą zostać wykonane z powolnym skanowaniem wektorowym i nie można zastosować szybkiego algorytmu, takiego jak wyszukiwanie binarne; Choć groupby zawsze uporządkować ramkę danych przez Grupę poprzez pierwszej zmiennej, można naśladować to zachowanie pisząc prosty algorytm sortowania indeksu, a następnie podzbiór, aby potwierdzić to:

def sort_subset(df): 
    # sort index and find out the positions that separate groups 
    df = df.sort_index() 
    split_indices = np.flatnonzero(np.ediff1d(df.index, to_begin=1, to_end=1)) 
    list_df = [] 
    for i in range(len(split_indices)-1): 
     start_index = split_indices[i] 
     end_index = split_indices[i+1] 
     list_df.append(df.iloc[start_index:end_index]) 
    return list_df 

Niektóre terminy:

import pandas as pd 
import numpy as np 
​ 
nrow = 1000000 
df = pd.DataFrame(np.random.randn(nrow), columns=['x'], index=np.random.randint(100, size=nrow)) 

index = list(set(df.index)) 
print('no of groups: ', len(index)) 
​ 
%timeit list_df_1 = [df.loc[x] for x in index] 
#no of groups: 100 
#13.6 s ± 228 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) 

%timeit list_df_2 = [x for i, x in df.groupby(level=0, sort=False)] 
#54.8 ms ± 1.36 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) 

# Not as fast because my algorithm is not optimized at all but the same order of magnitude 
%timeit list_df_3 = sort_subset(df) 
#102 ms ± 3.53 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) 

list_df_1 = [df.loc[x] for x in index] 
list_df_2 = [x for i, x in df.groupby(level=0, sort=False)] 
list_df_3 = sort_subset(df) 

Porównać wynik:

all(list_df_3[i].eq(list_df_2[i]).all().iat[0] for i in range(len(list_df_2))) 
# True 

zobaczyć znaczną prędkość nawet jeśli posortować indeks przed podzbioru także:

def sort_subset_with_loc(df): 
    df = df.sort_index() 
    list_df_1 = [df.loc[x] for x in index] 
    return list_df_1 

%timeit sort_subset_with_loc(df) 
# 25.4 ms ± 897 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)