Jaki jest najszybszy sposób (w granicach rozsądnej pythonowości) zliczania różnych wartości w kolumnach tego samego dtype
, dla każdego wiersza w DataFrame
?efektywna liczba różnych w kolumnach DataFrame, pogrupowanych według wierszy
Szczegóły: Mam DataFrame
kategorycznych wyników tematycznie (w rzędach) za dzień (w kolumnach), podobne do czegoś generowanego przez poniżej.
import numpy as np
import pandas as pd
def genSampleData(custCount, dayCount, discreteChoices):
"""generate example dataset"""
np.random.seed(123)
return pd.concat([
pd.DataFrame({'custId':np.array(range(1,int(custCount)+1))}),
pd.DataFrame(
columns = np.array(['day%d' % x for x in range(1,int(dayCount)+1)]),
data = np.random.choice(a=np.array(discreteChoices),
size=(int(custCount), int(dayCount)))
)], axis=1)
Na przykład, jeśli zbiór danych, który mówi nam pić każdy klient zamówił na każdej wizycie w sklepie, chciałbym znać liczbę odrębnych drinków klienta.
# notional discrete choice outcome
drinkOptions, drinkIndex = np.unique(['coffee','tea','juice','soda','water'],
return_inverse=True)
# integer-coded discrete choice outcomes
d = genSampleData(2,3, drinkIndex)
d
# custId day1 day2 day3
#0 1 1 4 1
#1 2 3 2 1
# Count distinct choices per subject -- this is what I want to do efficiently on larger DF
d.iloc[:,1:].apply(lambda x: len(np.unique(x)), axis=1)
#0 2
#1 3
# Note: I have coded the choices as `int` rather than `str` to speed up comparisons.
# To reconstruct the choice names, we could do:
# d.iloc[:,1:] = drinkOptions[d.iloc[:,1:]]
Co próbowałem: zbiorów danych w tym przypadku zastosowania będą miały o wiele więcej przedmiotów niż dni (przykład testDf
poniżej), więc starałem się znaleźć najbardziej efektywne row-mądry operacji:
testDf = genSampleData(100000,3, drinkIndex)
#---- Original attempts ----
%timeit -n20 testDf.iloc[:,1:].apply(lambda x: x.nunique(), axis=1)
# I didn't wait for this to finish -- something more than 5 seconds per loop
%timeit -n20 testDf.iloc[:,1:].apply(lambda x: len(x.unique()), axis=1)
# Also too slow
%timeit -n20 testDf.iloc[:,1:].apply(lambda x: len(np.unique(x)), axis=1)
#20 loops, best of 3: 2.07 s per loop
Aby poprawić na mojej oryginalnej próbie, możemy zauważyć, że pandas.DataFrame.apply() przyjmuje argument:
Jeśli
raw=True
p funkcja assed otrzyma zamiast tego obiekty ndarray. Jeśli tylko stosując funkcję redukcji NumPy ten osiągnie znacznie lepsza wydajność
To było wyciąć czasu pracy przez ponad pół:
%timeit -n20 testDf.iloc[:,1:].apply(lambda x: len(np.unique(x)), axis=1, raw=True)
#20 loops, best of 3: 721 ms per loop *best so far*
Byłem zaskoczony, że to czysty roztwór numpy, który wydaje się być równoważne z powyższym z raw=True
był faktycznie nieco wolniej:
%timeit -n20 np.apply_along_axis(lambda x: len(np.unique(x)), axis=1, arr = testDf.iloc[:,1:].values)
#20 loops, best of 3: 1.04 s per loop
Wreszcie, ja też próbowałem Transpo zaśpiewaj dane, aby wykonać column-wise count distinct, co moim zdaniem może być bardziej efektywne (przynajmniej dla DataFrame.apply()
, ale nie wydaje się, żeby było znaczącą różnicą.
%timeit -n20 testDf.iloc[:,1:].T.apply(lambda x: len(np.unique(x)), raw=True)
#20 loops, best of 3: 712 ms per loop *best so far*
%timeit -n20 np.apply_along_axis(lambda x: len(np.unique(x)), axis=0, arr = testDf.iloc[:,1:].values.T)
# 20 loops, best of 3: 1.13 s per loop
tej pory moim najlepszym rozwiązaniem jest dziwna mieszanka df.apply
z len(np.unique())
, ale co jeszcze powinienem spróbować?
jest przedstawicielem Ilość dni? Wydaje się znacznie wpływać na różnice występów. – ayhan
@ayhan ciekawe ... liczba dni jest reprezentatywna dla mojego konkretnego przypadku użycia, ale jeśli coś innego działa lepiej dla szerszych zestawów danych, które byłyby warte odnotowania dla innych użytkowników – C8H10N4O2
Wręcz przeciwnie. Wygląda na to, że porównywanie każdej kolumny z innymi jest znacznie szybsze, gdy masz małą liczbę kolumn. Wysłałem wyniki jako odpowiedź. – ayhan