Jak pogrupować jedną kolumnę biasowaną w różnych odstępach czasu od drugiej w pandach?

Dec 12 2020

Mam następujący plik pd.DataFrame:

source = pd.DataFrame([[0.99, 0.98, 0.93, 0.81, 0.85, 0.71, 0.7, 0.69, 0.68, 0.66], 
              [100, 12, 312, 23, 2, 12, 32, 21, 21, 21]]).T

Chciałbym go jak najszybciej przekonwertować na:

desired_result = pd.DataFrame([[0.9, 0.8, 0.7, 0.6], [424, 25, 44, 63]]).T

Gdzie powyżej określam przedział, 0.1który stosuję do kolumny 0do sourceramki danych i sumuję 1kolumnę tej samej ramki danych. Chodzi o to, że powinno to działać w różnych odstępach czasu.

Co próbowałem:

  1. Myślałem o użyciu, pd.cutale to nie jest to, czego szukam.

  2. Wiem, że jeśli dodam nową kolumnę, do sourcektórej są zduplikowane wartości 0,9, 0,8, 0,7 i 0,6 w odpowiednich wierszach, to mogę użyć groupbytej nowej kolumny i wtedy sum, ale zastanawiam się, czy istnieje czystszy i szybszy sposób zrobić to? np. coś takiego:

interval = 0.1
source['ints'] = (source[0] / interval).astype(int)
result = source.groupby(source['ints']).sum().reset_index()
result

Jednak powyższe nie zadziałałoby, gdybym na przykład zmienił przedział z 0,1 na 0,05.

Każda pomoc będzie mile widziana.

Odpowiedzi

3 PierreD Dec 12 2020 at 21:28

Dla szybkości: zawsze staraj się wektoryzować wszystko, co możesz, i unikaj apply jak najwięcej.

Oto szybszy sposób (kredyt dla @DavidErickson za sort=False):

interval = 0.1
source.groupby(np.trunc(source[0] / interval) * interval, sort=False)[1].sum().reset_index()
# out:
     0      1
0  0.9  424.0
1  0.8   25.0
2  0.7   12.0
3  0.6   95.0

Różnica prędkości może być dość dramatyczna dla dużych df.

Spróbuj z 1 milionem wierszy pogrupowanych w 10 000 pojemnikach:

source = pd.DataFrame(np.random.normal(scale=1000, size=(int(1e6), 2)))

%%timeit
# ... (as above)
26.7 ms ± 292 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Ze applyzamiast:

1.51 s ± 11 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

(50x wolniej).

1 DavidErickson Dec 12 2020 at 20:55

Możesz użyć custom_roundfunkcji, z której zrobiłem 3 modyfikacjehttps://stackoverflow.com/a/40372261/6366770:

  1. Użyłem np.floorzamiast tego, roundjak chcesz zejść.
  2. Powoduje to zepsucie wartości znajdujących się na „granicy” kosza, więc dodaję + base/100(tak 0.9będzie 0.9 + .009 = 0.909i zaokrąglam w dół do .9 zamiast niepoprawnie do 0,8), tak że znajduje się tuż nad granicą i zaokrągla w dół poprawnie. Myślę, że to cię okryje. Możesz zrobić, 1 / 1000aby być bezpiecznym.
  3. Odpowiedź, której się dzieliłem, została intusunięta int, ponieważ patrzymy na zaokrąglanie pływaków

source = pd.DataFrame(np.random.normal(scale=1000, size=(int(1e6), 2)))

def custom_round(x, y, base):
    return source.groupby((base * np.floor((x + (base / 100)) / base)), sort=False)[y].sum()


%timeit custom_round(source[0], 1, .1)
89.8 ms ± 1.14 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Na moim komputerze zaakceptowana odpowiedź jest wolniejsza:

102 ms ± 1.86 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)