60 votes

Comment mettre en évidence des plages de valeurs x spécifiques

Je suis en train de réaliser une visualisation de données boursières historiques pour un projet, et j'aimerais mettre en évidence les régions où il y a eu des baisses. Par exemple, lorsque l'action subit une baisse importante, j'aimerais la mettre en évidence par une zone rouge.

Puis-je le faire automatiquement ou dois-je dessiner un rectangle ou autre chose ?

152voto

Joe Kington Points 68089

Jetez un coup d'œil sur axvspan (et axhspan pour mettre en évidence une région de l'axe des y).

import matplotlib.pyplot as plt

plt.plot(range(10))
plt.axvspan(3, 6, color='red', alpha=0.5)
plt.show()

enter image description here

Si vous utilisez des dates, vous devrez convertir vos valeurs min et max x en dates matplotlib. Utiliser matplotlib.dates.date2num para datetime objets ou matplotlib.dates.datestr2num pour diverses chaînes de temps.

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import datetime as dt

t = mdates.drange(dt.datetime(2011, 10, 15), dt.datetime(2011, 11, 27),
                  dt.timedelta(hours=2))
y = np.sin(t)

fig, ax = plt.subplots()
ax.plot_date(t, y, 'b-')
ax.axvspan(*mdates.datestr2num(['10/27/2011', '11/2/2011']), color='red', alpha=0.5)
fig.autofmt_xdate()
plt.show()

enter image description here

12voto

Patrick FitzGerald Points 1347

Voici une solution qui utilise axvspan pour dessiner plusieurs points saillants dont les limites sont fixées à l'aide des indices des données boursières correspondant aux pics et aux creux.

Les données sur les stocks contiennent généralement une variable temporelle discontinue où les week-ends et les jours fériés ne sont pas pris en compte. Leur représentation graphique dans matplotlib ou pandas produira des écarts le long de l'axe des x pour les week-ends et les jours fériés lorsqu'il s'agit de cours boursiers quotidiens. Ce phénomène peut ne pas être perceptible lorsque les plages de dates sont longues et/ou que les chiffres sont petits (comme dans cet exemple), mais il deviendra apparent si vous effectuez un zoom avant et c'est peut-être quelque chose que vous souhaitez éviter.

C'est pourquoi je partage ici un exemple complet qui présente des caractéristiques :

  • Un ensemble de données réalistes comprenant un échantillon discontinu DatetimeIndex sur la base du calendrier des transactions de la Bourse de New York importé avec la pandas_market_calendars ainsi que de fausses données boursières qui ressemblent aux vraies.
  • A pandas plot créé avec use_index=False qui supprime les écarts pour les week-ends et les jours fériés en utilisant à la place une plage d'entiers pour l'axe des abscisses. Le résultat obtenu ax est utilisé de manière à éviter d'avoir à importer matplotlib.pyplot (à moins que vous n'ayez besoin de plt.show ).
  • Une détection automatique des drawdowns sur l'ensemble de la plage de dates en utilisant le signal scipy.signal. find_peaks qui renvoie les indices nécessaires pour tracer les points saillants à l'aide de la fonction axvspan . Le calcul des drawdowns d'une manière plus correcte nécessiterait une définition claire de ce qui est considéré comme un drawdown et conduirait à un code plus compliqué, ce qui est un sujet à traiter dans le cadre d'une étude de cas. autre question .
  • Des ticks correctement formatés sont créés en parcourant en boucle les horodatages de l'élément DatetimeIndex étant donné que tous les matplotlib.dates les localisateurs et formateurs de tiques ainsi que les DatetimeIndex des propriétés telles que .is_month_start ne peut pas être utilisé dans ce cas.

Créer un échantillon de données

import numpy as np                        # v 1.19.2
import pandas as pd                       # v 1.1.3
import pandas_market_calendars as mcal    # v 1.6.1
from scipy.signal import find_peaks       # v 1.5.2

# Create datetime index with a 'trading day end' frequency based on the New York Stock
# Exchange trading hours (end date is inclusive)
nyse = mcal.get_calendar('NYSE')
nyse_schedule = nyse.schedule(start_date='2019-10-01', end_date='2021-02-01')
nyse_dti = mcal.date_range(nyse_schedule, frequency='1D').tz_convert(nyse.tz.zone)

# Create sample of random data for daily stock closing price
rng = np.random.default_rng(seed=1234)  # random number generator
price = 100 + rng.normal(size=nyse_dti.size).cumsum()
df = pd.DataFrame(data=dict(price=price), index=nyse_dti)

df.head()

#                                    price
#   2019-10-01 16:00:00-04:00    98.396163
#   2019-10-02 16:00:00-04:00    98.460263
#   2019-10-03 16:00:00-04:00    99.201154
#   2019-10-04 16:00:00-04:00    99.353774
#   2019-10-07 16:00:00-04:00   100.217517

Tracer les points saillants pour les tirages avec des ticks correctement formatés

# Plot stock price
ax = df['price'].plot(figsize=(10, 5), use_index=False, ylabel='Price')
ax.set_xlim(0, df.index.size-1)
ax.grid(axis='x', alpha=0.3)

# Highlight drawdowns using the indices of stock peaks and troughs: find peaks and 
# troughs based on signal analysis rather than an algorithm for drawdowns to keep
# example simple. Width and prominence have been handpicked for this example to work.
peaks, _ = find_peaks(df['price'], width=7, prominence=4)
troughs, _ = find_peaks(-df['price'], width=7, prominence=4)
for peak, trough in zip(peaks, troughs):
    ax.axvspan(peak, trough, facecolor='red', alpha=.2)

# Create and format monthly ticks
ticks = [idx for idx, timestamp in enumerate(df.index)
         if (timestamp.month != df.index[idx-1].month) | (idx == 0)]
ax.set_xticks(ticks)
labels = [tick.strftime('%b\n%Y') if df.index[ticks[idx]].year
          != df.index[ticks[idx-1]].year else tick.strftime('%b')
          for idx, tick in enumerate(df.index[ticks])]
ax.set_xticklabels(labels)
ax.figure.autofmt_xdate(rotation=0, ha='center')

ax.set_title('Drawdowns are highlighted in red', pad=15, size=14);

highlights_drawdowns

Dans un souci d'exhaustivité, il convient de noter que vous pouvez obtenir exactement le même résultat en utilisant la fonction fill_between bien qu'il faille quelques lignes de code supplémentaires :

ax.set_ylim(*ax.get_ylim())  # remove top and bottom gaps with plot frame
drawdowns = np.repeat(False, df['price'].size)
for peak, trough in zip(peaks, troughs):
    drawdowns[np.arange(peak, trough+1)] = True
ax.fill_between(np.arange(df.index.size), *ax.get_ylim(), where=drawdowns,
                facecolor='red', alpha=.2)

Vous utilisez l'interface interactive de matplotlib et souhaitez avoir des ticks dynamiques lorsque vous zoomez ? Vous devrez alors utiliser les localisateurs et les formateurs de la base de données matplotlib.ticker module. Vous pouvez, par exemple, conserver les tics majeurs fixes comme dans cet exemple et ajouter des tics mineurs dynamiques pour afficher les jours ou les semaines de l'année lorsque vous zoomez. Vous trouverez un exemple de cette méthode à la fin de la section cette réponse .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X