Home > other >  Matplotlib - highlighting weekends on x axis?
Matplotlib - highlighting weekends on x axis?

Time:11-09

I've a time series (typically energy usage) recorded over a range of days. Since usage tends to be different over the weekend I want to highlight the weekends.

I've done what seems sensible:

import pandas as pd
import matplotlib.pyplot as plt
import datetime
import random

#Create dummy data.

start=datetime.datetime(2022,10,22,0,0)
finish=datetime.datetime(2022,11,7,0,0)

def randomWalk(): 
    i=0
    while True: 
        i=i random.random()-0.5
        yield i

s = pd.Series({i: next(randomWalk()) for i in pd.date_range(start, finish,freq='h')})

# Plot it.

plt.figure(figsize=[12, 8]);
s.plot();

# Color the labels according to the day of week.

for label, day in zip(plt.gca().xaxis.get_ticklabels(which='minor'), 
            pd.date_range(start,finish,freq='d')):
       label.set_color('red' if day.weekday() > 4 else 'black')

But what I get is wrong. Two weekends appear one off, and the third doesn't show at all.

The X axis

I've explored the 'label' objects, but their X coordinate is just an integer, and doesn't seem meaningful. Using DateFormatter just gives nonsense.

How would be best to fix this, please?

CodePudding user response:

OK - since matplotlib only provides the information we need to the Tick Label Formatter functions, that's what we have to use:

minorLabels=plt.gca().xaxis.get_ticklabels(which='minor')
majorLabels=plt.gca().xaxis.get_ticklabels(which='major')

def MinorFormatter(dateInMinutes, index):
    # Formatter: first param is value (date in minutes, would you believe), second is which item in order.
    day=pd.to_datetime(np.datetime64(int(dateInMinutes),'m'))
    minorLabels[index].set_color('red' if day.weekday()==6 else 'black') # Sunday
    return day.day

def MajorFormatter(dateInMinutes, index):
    day=pd.to_datetime(np.datetime64(int(dateInMinutes),'m'))
    majorLabels[index].set_color('red' if day.weekday()==6 else 'black') # Sunday
    return "" if (index==0 or index==len(majorLabels)-1) else day.strftime("%d\n%b\n%Y")

plt.gca().xaxis.set_minor_formatter(MinorFormatter)
plt.gca().xaxis.set_major_formatter(MajorFormatter)

Pretty clunky, but it works. Could be fragile, though - anyone got a better answer?

CodePudding user response:

Matplotlib is meant for scientific use and although technically styling is possible, it's really hard and not worth the effort.

Consider using Plotly instead of Matplotlib as below:

#pip install plotly in terminal
import plotly.express as px
# read plotly express provided sample dataframe 
df = px.data.tips()
# create plotly figure with color_discrete_map property specifying color per day
fig = px.bar(df, x="day", y="total_bill", color='day', 
            color_discrete_map={"Sat": "orange", "Sun": "orange", "Thur": "blue", "Fri": "blue"}
            )
# send to browser
fig.show()

Solves your problem using a lot fewer lines. Only thing here is you need to make sure your data is in a Pandas DataFrame rather than Series with column names which you can pass into plotly.express.bar or scatter plot.

enter image description here

  • Related