2016-07-01 16 views
6

Chcę poprawić czas groupby w Pythonie Pythona. mam ten kod:Groupby w Pythonie: Szybka droga

df["Nbcontrats"] = df.groupby(['Client', 'Month'])['Contrat'].transform(len) 

celem jest policzyć ile umowy klient ma w miesiącu i dodać tę informację w nowej kolumnie (Nbcontrats).

  • Client: kod klienta
  • Month: miesiąc ekstrakcji danych
  • Contrat: numer umowy

Chcę poprawić czas. Poniżej działam tylko z podzestawem moich rzeczywistych danych:

%timeit df["Nbcontrats"] = df.groupby(['Client', 'Month'])['Contrat'].transform(len) 
1 loops, best of 3: 391 ms per loop 

df.shape 
Out[309]: (7464, 61) 

Jak mogę poprawić czas realizacji?

+4

Sugeruję dodanie tagu numpy. Pamiętam, że @Divakar wymyślił szybsze rozwiązania niż groupby z np.einsum. – ayhan

+1

@ayhan, masz na myśli rozwiązanie [this] (http://stackoverflow.com/a/36810721/5741205)? – MaxU

+0

@MaxU Tak, jednak był to bardzo konkretny przypadek (z łatwością przekształcony). Ogólne rozwiązanie może nie być proste. – ayhan

Odpowiedz

2

Dzięki metodzie DataFrameGroupBy.size:

df.set_index(['Client', 'Month'], inplace=True) 
df['Nbcontrats'] = df.groupby(level=(0,1)).size() 
df.reset_index(inplace=True) 

Najbardziej praca idzie do przypisywania wynik z powrotem do kolumny DataFrame źródłowego.

9

Oto jeden ze sposobów, aby przejść:

  • Kawałek z odpowiednich kolumn (['Client', 'Month']) od dataframe wejściowego do tablicy numpy. Jest to głównie pomysł skoncentrowany na wydajności, ponieważ później będziemy używać funkcji NumPy, które są zoptymalizowane do pracy z tablicami NumPy.

  • Konwertuj dane dwóch kolumn z ['Client', 'Month'] do pojedynczej tablicy , która byłaby równoważnikiem liniowym, biorąc pod uwagę elementy z dwóch kolumn jako pary. Możemy więc przyjąć, że elementy z 'Client' reprezentują indeksy wierszy, podczas gdy elementy 'Month' są indeksami kolumn. To jest jak przejście z 2D do 1D. Ale kwestią byłoby ustalenie kształtu siatki 2D do wykonania takiego mapowania. Aby objąć wszystkie pary, jednym z bezpiecznych założeń byłoby przyjęcie siatki 2D, której wymiary są o jeden więcej niż maksimum wzdłuż każdej kolumny z powodu indeksowania opartego na 0 w Pythonie. W ten sposób uzyskalibyśmy indeksy liniowe.

  • Następnie oznaczamy każdy indeks liniowy na podstawie jego unikalności wśród innych. Myślę, że to by odpowiadało kluczom uzyskanym z grouby. Musimy również uzyskać liczbę wszystkich grup/kluczy unikalnych na całej długości tej tablicy 1D. Na koniec, indeksowanie do liczników za pomocą tych tagów powinno odwzorowywać dla każdego elementu odpowiednią liczbę.

To wszystko na ten temat!Oto realizacja -

# Save relevant columns as a NumPy array for performing NumPy operations afterwards 
arr_slice = df[['Client', 'Month']].values 

# Get linear indices equivalent of those columns 
lidx = np.ravel_multi_index(arr_slice.T,arr_slice.max(0)+1) 

# Get unique IDs corresponding to each linear index (i.e. group) and grouped counts 
unq,unqtags,counts = np.unique(lidx,return_inverse=True,return_counts=True) 

# Index counts with the unique tags to map across all elements with the counts 
df["Nbcontrats"] = counts[unqtags] 

Test Runtime

1) Określ funkcje:

def original_app(df): 
    df["Nbcontrats"] = df.groupby(['Client', 'Month'])['Contrat'].transform(len) 

def vectorized_app(df): 
    arr_slice = df[['Client', 'Month']].values 
    lidx = np.ravel_multi_index(arr_slice.T,arr_slice.max(0)+1) 
    unq,unqtags,counts = np.unique(lidx,return_inverse=True,return_counts=True) 
    df["Nbcontrats"] = counts[unqtags] 

2) Sprawdź wyniki:

In [143]: # Let's create a dataframe with 100 unique IDs and of length 10000 
    ...: arr = np.random.randint(0,100,(10000,3)) 
    ...: df = pd.DataFrame(arr,columns=['Client','Month','Contrat']) 
    ...: df1 = df.copy() 
    ...: 
    ...: # Run the function on the inputs 
    ...: original_app(df) 
    ...: vectorized_app(df1) 
    ...: 

In [144]: np.allclose(df["Nbcontrats"],df1["Nbcontrats"]) 
Out[144]: True 

3) wreszcie czas je:

In [145]: # Let's create a dataframe with 100 unique IDs and of length 10000 
    ...: arr = np.random.randint(0,100,(10000,3)) 
    ...: df = pd.DataFrame(arr,columns=['Client','Month','Contrat']) 
    ...: df1 = df.copy() 
    ...: 

In [146]: %timeit original_app(df) 
1 loops, best of 3: 645 ms per loop 

In [147]: %timeit vectorized_app(df1) 
100 loops, best of 3: 2.62 ms per loop 
+0

To niesamowite - 246 razy szybciej! Czy mógłbyś dodać krótkie wyjaśnienie dla numpy'ego? – MaxU

+0

@MaxU Właśnie dodałem trochę wyjaśnienia. Zrobiłem, co mogłem, ogólnie to do niego wkurzyłem :) – Divakar

+1

To jest idealne - dziękuję za nauczenie mnie numpy! Niestety nie mogę go upomnieć więcej niż jeden raz;) – MaxU