Finden der größten Fläche unter einer Kurve | Pandas, Matplotlib

Sep 15 2020

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:

Das ist der größte Bereich unter der 0 im Bild, aber im vollständigen Datensatz kann es sich um einen beliebigen Bereich handeln. Ich weiß, wie ich es integrieren kann, wenn ich die Grenzen des betreffenden Gebiets finde, aber ich habe Mühe, einen effizienten Weg zu finden, dies zu tun.

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

1 MichaelSzczesny Sep 15 2020 at 16:47

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)