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
Sugeruję dodanie tagu numpy. Pamiętam, że @Divakar wymyślił szybsze rozwiązania niż groupby z np.einsum. – ayhan
@ayhan, masz na myśli rozwiązanie [this] (http://stackoverflow.com/a/36810721/5741205)? – MaxU
@MaxU Tak, jednak był to bardzo konkretny przypadek (z łatwością przekształcony). Ogólne rozwiązanie może nie być proste. – ayhan