Finden der größten Fläche unter einer Kurve | Pandas, Matplotlib
Ich habe ein bisschen Probleme damit - um die notwendige Batteriekapazität zu finden, muss ich die Produktion / Nachfrage über Daten von einem Jahr analysieren. Dazu dachte ich, ich muss die größte Fläche unter der 0-Linie berechnen. Ich denke, ich muss die Start- / Endpunkte dieses Bereichs finden und alles mit dem jeweiligen y-Wert multiplizieren.
Hier ist eine verkürzte Version des Diagramms, das ich habe:
Mein Datenrahmen sieht folgendermaßen aus:
demand Production diff
Time
2019-01-01 00:15:01 17.25 32.907 15.657
2019-01-01 00:30:01 17.80 32.954 15.154
... ... ... ...
2019-01-16 22:15:02 17.34 27.704 10.364
2019-01-16 22:30:01 18.67 35.494 16.824
Ich benutze dieses Snippet, um die Länge in Zeitschritten des längsten Bereichs zu ermitteln, aber es fehlt mir, wenn es eine Möglichkeit gibt, die Punkte mit ihren y-Werten (diff) zu multiplizieren. Es ist jedoch technisch nicht korrekt , wenn man bedenkt, dass ein Bereich lang, aber schmal und ein anderer kürzer und höher sein kann, also mit einem insgesamt größeren Bereich.
def max0(sr):
return (sr >= 0).cumsum().value_counts().max() - (0 if (sr >= 0).cumsum().value_counts().idxmax() < 0 else 1)
Antworten
Sie finden den größten Bereich unter der 0-Linie. Ich habe meine eigenen Daten generiert
x = np.random.randn(100000)
x = x.cumsum()-x.mean()
plt.plot(x);
Berechnen Sie nun die Start- und Endpunkte für positive und negative Sequenzen. Jeder Wert in einer Sequenz erhält eine zunehmende Ganzzahl, um nach Sequenzen gruppieren zu können.
x1 = np.diff(x < 0).cumsum()
Verwenden Sie pandas groupby, um alle Bereiche zu berechnen und das größte Negativ zu finden
df = pd.DataFrame({
'value': x[1:],
'border': x1
})
dfg = df.groupby('border')
mingr = dfg.apply(lambda x: np.trapz(x.value)).idxmin()
plt.plot(x[1:])
plt.plot(
dfg.get_group(mingr).value
);
plt.title(
"position from {} to {}".format(
dfg.get_group(mingr).index[0],
dfg.get_group(mingr).index[-1]));
Wie das funktioniert
Ich erstelle einen Datensatz, dem man leichter folgen kann
x = np.array([3,4,4.5,3,2])
X = np.r_[x,-x,x,-x]+np.random.normal(0,.2,20)
plt.figure(figsize=(12,5))
plt.axhline(0, color='gray')
plt.plot(X, 'o--');
Ich möchte die Sequenzen mit aufeinanderfolgenden negativen oder positiven Werten kennen. Dies kann mit dem Filter X <0 archiviert werden.
df = pd.DataFrame({'value': X, 'lt_zero': X < 0})
df[:10]
value lt_zero
0 3.125986 False
1 3.885588 False
2 4.580410 False
3 2.998920 False
4 1.913088 False
5 -2.902447 True
6 -3.986654 True
7 -4.373026 True
8 -2.878661 True
9 -1.929964 True
Jetzt kann ich die Indizes finden, bei denen sich das Vorzeichen ändert, wenn ich jeden aufeinanderfolgenden Wert differenziere. Ich konzentriere ein False vor die Daten, um den ersten Wert nicht zu verlieren.
df['sign_switch'] = np.diff(np.r_[False, X < 0])
df[:10]
value lt_zero sign_switch
0 3.125986 False False
1 3.885588 False False
2 4.580410 False False
3 2.998920 False False
4 1.913088 False False
5 -2.902447 True True
6 -3.986654 True False
7 -4.373026 True False
8 -2.878661 True False
9 -1.929964 True False
Mit cumsum()bekomme ich für jede Sequenz einen ansteigenden ganzzahligen Wert. Jetzt habe ich für jede Sequenz eine Gruppierungsvariable.
df['sign_sequence'] = np.diff(np.r_[False, X < 0]).cumsum()
df[:10]
value lt_zero sign_switch sign_sequence
0 3.125986 False False 0
1 3.885588 False False 0
2 4.580410 False False 0
3 2.998920 False False 0
4 1.913088 False False 0
5 -2.902447 True True 1
6 -3.986654 True False 1
7 -4.373026 True False 1
8 -2.878661 True False 1
9 -1.929964 True False 1
Für jede Gruppe kann ich das Integral für die Werte in der Gruppe berechnen.
sign_groups = df.groupby('sign_sequence')
sign_groups.apply(lambda x: np.trapz(x.value))
sign_sequence
0 13.984455
1 -13.654547
2 14.370044
3 -14.549090
Sie können später auf jede Gruppe zugreifen und die Bereiche verwenden. Zum Beispiel, um die Bereiche zu zeichnen.
plt.figure(figsize=(12,5))
plt.plot(X,'o--')
plt.axhline(0, c='gray')
for e,group in enumerate(sign_groups):
plt.fill_between(group[1].index,0, group[1].value)
area = np.trapz(group[1].value)
plt.text((e)*5+1.5, np.sign(area) * 1.25, f'{area:.2f}', fontsize=12)