1

i plotted a daily line plot for flights and i would like to highlight all the saturdays and sundays. I'm trying to do it with axvspan but i'm struggling with the use of it? Any suggestions on how can this be coded?

(flights.loc[flights['date'].dt.month.between(1, 2), 'date']
         .dt.to_period('D')
         .value_counts()
         .sort_index()
         .plot(kind="line",figsize=(12,6))
 )

enter image description here

Thx in advance for any help provided

vestland
  • 55,229
  • 37
  • 187
  • 305
laminado
  • 69
  • 1
  • 3
  • 11
  • There are some very similar questions [here](https://stackoverflow.com/questions/48973471/how-to-highlight-weekends-for-time-series-line-plot-in-python) and [here](https://stackoverflow.com/questions/61287041/how-to-highlight-weekends-in-matplotlib-plots). – Patrick FitzGerald Feb 06 '21 at 10:34

2 Answers2

2

You can use pandas' dt.weekday to get an integer corresponding to the weekday of a given date. 5 equals to Saturday and 6 to Sunday (Monday is 0). You can use this information as an additional way to slice your dataframe and filter those entries that belong to either Saturdays or Sundays. As you mentioned they can be highlighted with axvspan and matplotlib versions >3 are able to use the datetime objects as an input. 1 day has to be added via datetime.timedelta, because no rectangle will be drawn if xmin=xmax. Here is the code

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import datetime

#create sample data and dataframe
datelist = pd.date_range(start="2014-12-09",end="2015-03-02").tolist()
datelist += datelist #test to see whether it works with multiple entries having the same date
flights = pd.DataFrame(datelist, columns=["date"])

#plot command, save object in variable
plot = flights.loc[flights['date'].dt.month.between(1, 2), 'date'].dt.to_period('D').value_counts().sort_index().plot(kind="line",figsize=(12,6))

#filter out saturdays and sundays from the date range needed
weekends = flights.loc[(flights['date'].dt.month.between(1, 2)) & ((flights['date'].dt.weekday == 5) | (flights['date'].dt.weekday == 6)), 'date']
#5 = Saturday, 6 = Sunday

#plot axvspan for every sat or sun, set() to get unique dates
for day in set(weekends.tolist()):
    plot.axvspan(day, day + datetime.timedelta(days=1))
BenB
  • 658
  • 2
  • 10
2

Using a date column of type pandas timestamp, you can get the weekday of a date directly using pandas.Timestamp.weekday. Then you can use df.iterrows() to check whether or not each date is a saturday or sunday and include a shape in the figure like this:

for index, row in df.iterrows():
    if row['date'].weekday() == 5 or row['date'].weekday() == 6:
        fig.add_shape(...)

With a setup like this, you would get a line indicating whether or not each date is a saturday or sunday. But given that you're dealing with a continuous time series, it would probably make sense to illustrate these periods as an area for the whole period instead of highlighting each individual day. So just identify each saturday and set the whole period to each saturday plus pd.DateOffset(1) to get this:

enter image description here

Complete code with sample data

# imports
import numpy as np
import pandas as pd
import plotly.graph_objects as go
import plotly.express as px
import datetime

pd.set_option('display.max_rows', None)

# data sample
cols = ['signal']
nperiods = 20
np.random.seed(12)
df = pd.DataFrame(np.random.randint(-2, 2, size=(nperiods, len(cols))),
                  columns=cols)
datelist = pd.date_range(datetime.datetime(2020, 1, 1).strftime('%Y-%m-%d'),periods=nperiods).tolist()
df['date'] = datelist 
df = df.set_index(['date'])
df.index = pd.to_datetime(df.index)
df.iloc[0] = 0
df = df.cumsum().reset_index()
df['signal'] = df['signal'] + 100

# plotly setup
fig = px.line(df, x='date', y=df.columns[1:])
fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='rgba(0,0,255,0.1)')
fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='rgba(0,0,255,0.1)')

for index, row in df.iterrows():
    if row['date'].weekday() == 5: #or row['date'].weekday() == 6:
        fig.add_shape(type="rect",
                        xref="x",
                        yref="paper",
                        x0=row['date'],
                        y0=0,
#                         x1=row['date'],
                        x1=row['date'] + pd.DateOffset(1),
                        y1=1,
                        line=dict(color="rgba(0,0,0,0)",width=3,),
                        fillcolor="rgba(0,0,0,0.1)",
                        layer='below') 
fig.show()
vestland
  • 55,229
  • 37
  • 187
  • 305