Wie gruppiert man eine Spalte, die in verschiedenen Intervallen von einer anderen in Pandas vorgespannt ist?

Dec 12 2020

Ich habe den folgenden 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

Ich möchte es so schnell wie möglich konvertieren zu:

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

Wo oben definiere ich ein Intervall, 0.1dessen Anwendung ich auf die Spalte 0des sourceDatenrahmens anwende und die 1Spalte desselben Datenrahmens summiere. Die Idee ist, dass dies mit unterschiedlichen Intervallen funktionieren sollte.

Was ich versucht habe:

  1. Ich habe darüber nachgedacht, pd.cutaber das scheint nicht das zu sein, wonach ich suche.

  2. Ich weiß, dass ich, wenn ich eine neue Spalte sourcemit doppelten Werten von 0,9, 0,8, 0,7 und 0,6 in den entsprechenden Zeilen hinzufüge, groupbydiese neue Spalte verwenden kann sum, aber ich frage mich, ob es einen saubereren und schnelleren Weg gibt um dies zu tun? zB so etwas:

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

Das Obige würde jedoch nicht funktionieren, wenn ich beispielsweise die Intervallform von 0,1 auf 0,05 ändern würde.

Jede Hilfe wäre dankbar.

Antworten

3 PierreD Dec 12 2020 at 21:28

Für die Geschwindigkeit: Versuchen Sie immer, alles zu vektorisieren, was Sie können, und vermeiden Sie apply so viel wie möglich.

Hier ist ein schnellerer Weg (Dank an @DavidErickson für 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

Der Geschwindigkeitsunterschied kann für große ziemlich dramatisch sein df.

Versuchen Sie es mit 1 Million Zeilen, gruppiert in 10K-Bins:

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)

Mit einem applystattdessen:

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

(50x langsamer).

1 DavidErickson Dec 12 2020 at 20:55

Sie können eine custom_roundFunktion verwenden, von der ich 3 Änderungen vorgenommen habehttps://stackoverflow.com/a/40372261/6366770::

  1. Ich habe np.floorstatt roundwie du runter gehen willst.
  2. Dies bringt Werte durcheinander, die sich am "Rand" eines Fachs befinden, also füge ich hinzu + base/100(so 0.9wäre es 0.9 + .009 = 0.909und runde auf 0,9 statt falsch auf 0,8 ab), so dass es direkt über dem Rand liegt und korrekt abgerundet wird. Ich denke, das wird dich bedecken. Sie können tun 1 / 1000, um sicher zu sein.
  3. Die Antwort, die ich teile, war gesucht int, also entfernt int, da wir uns mit runden Schwimmern befassen

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)

Auf meinem Computer ist die akzeptierte Antwort langsamer:

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