Recherche de la plus grande surface sous une courbe | pandas, matplotlib
J'ai un peu de mal dans celui-ci - afin de trouver la capacité de batterie nécessaire, j'ai besoin d'analyser la production / la demande sur une année de données. Pour ce faire, j'ai pensé que je devais calculer la plus grande surface sous la ligne 0. Je suppose que j'ai besoin de trouver les points de début / fin de cette zone et de tout multiplier par sa valeur y respective?
Voici une version abrégée du graphique que j'ai:
Mon dataframe ressemble à ceci:
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
J'utilise cet extrait de code pour trouver la longueur en pas de temps de la zone la plus longue, mais je manque s'il existe un moyen de multiplier les points par leurs valeurs y (diff). Ce n'est techniquement pas correct , cependant, étant donné qu'une zone peut être longue mais étroite et une autre peut être plus courte et plus grande, donc avec une zone globale plus grande.
def max0(sr):
return (sr >= 0).cumsum().value_counts().max() - (0 if (sr >= 0).cumsum().value_counts().idxmax() < 0 else 1)
Réponses
Vous pouvez trouver la plus grande zone sous la ligne 0. J'ai généré mes propres données
x = np.random.randn(100000)
x = x.cumsum()-x.mean()
plt.plot(x);
Calculez maintenant les points de début et de fin des séquences positives et négatives. Chaque valeur d'une séquence reçoit un entier croissant pour pouvoir être groupée par séquence.
x1 = np.diff(x < 0).cumsum()
Utilisez pandas groupby pour calculer toutes les zones et trouver le plus grand négatif
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]));
Comment ça marche
Je crée un jeu de données plus facile à suivre
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--');
Je veux connaître les séquences avec des valeurs négatives ou positives consécutives. Cela peut être archivé avec le filtre X <0.
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
Maintenant, je peux trouver les indices où le signe change, quand je diffère chaque valeur consécutive. Je concat un False avant les données pour ne pas perdre la première valeur.
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
Avec cumsum()j'obtiens pour chaque séquence une valeur entière croissante. Maintenant, j'ai une variable de regroupement pour chaque séquence.
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
Pour chaque groupe, je peux calculer l'intégrale des valeurs du groupe.
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
Vous pouvez accéder à chaque groupe ultérieurement et utiliser les zones. Par exemple pour tracer les zones.
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)