1

I'm am trying to recreate a variant of this chart: chart generated using excel

The main difference being that I am creating a bar chart with x-axis being date time (in 3 hour increments), and the bar being coloured based on another series called intensity (low - green, high - red).

That said, one axis should be the the hours in a day, the second axis below it should group those times into the day they belong to.

What I have so far:

import matplotlib as mplt
import matplotlib.pyplot as plt
from matplotlib import dates
import numpy as np
import datetime as pdt
from datetime import datetime, timedelta
import seaborn as sns

start_dt = np.datetime64('today').astype(np.datetime64)
end_dt = np.datetime64('today') + np.timedelta64(6, 'D')

x = np.arange(start_dt,
                  end_dt, np.timedelta64(180, 'm'))
x = [i.astype(datetime) for i in x]

intensity = np.random.uniform(0, 10, len(x))

y = np.ones(shape=len(x))

plt.rcParams["figure.figsize"] = [17, 1.2]
plt.rcParams["figure.autolayout"] = True
sns.set_style("whitegrid")
sns.despine(bottom = True, left = True, top = True)
fig, ax = plt.subplots()

colors = []
for i in intensity:
    if 0 <= i <= 5:
        colors.append('#75FF71')
    elif 6 <= i < 8:
        colors.append('#FFC53D')
    else:
        colors.append('#FF5C5C')

graph = sns.barplot(x=x, y=y, palette=colors, width=1.0, linewidth=0)
graph.grid(False)
graph.set(yticklabels=[])
x_tick_label = []
for val in x:
    min_ts = min(x)
    diff_days = (val - min_ts).days
    diff_hours = (val - min_ts).seconds/3600
    total = diff_days*24 + int(diff_hours)
    if val.time() == pdt.time(0,0):
        # x_tick_label.append(val.strftime("%m/%d"))
        x_tick_label.append("")
    elif val.time() == pdt.time(6,0) or val.time() == pdt.time(12,0) or val.time() == pdt.time(18,0) :
    # elif val.time() == pdt.time(12,0):
        x_tick_label.append(f"{val.strftime('%-H')}:00")
    else:
        x_tick_label.append('')
graph.set(xticklabels=x_tick_label)
for ticklabel in graph.axes.get_xticklabels():
    ticklabel.set_color("#FFC53D")

ax2 = ax.axes.twiny()

ax2.spines['top'].set_position(('axes', -0.15))
ax2.spines['top'].set_visible(False)

# ax2.xaxis.set_major_formatter(day_locator)

plt.xticks(fontweight='light',ha='right', rotation=90)
plt.box(on=None)

          
plt.show()


output of the code above

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
AGS
  • 401
  • 5
  • 17

1 Answers1

1

Let me add the value to outdated answers mentioned by moderators. Let's use as much of modern API methods as possible. My approach is to leverage minor and major ticks, to do both: grouping and make the alternating pattern.

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.transforms import ScaledTranslation
import matplotlib.dates as mdates


## DATA

ts = pd.date_range('2023/01/01', '2023/06/30', freq='1H')
xs = np.arange(len(ts))
vals = np.random.normal(size=(len(ts),)).cumsum()
df = pd.DataFrame({'date':ts,'value':vals})

## Plot 
fig, ax = plt.subplots(figsize = (24,6), constrained_layout=True) 

month_locs = mdates.MonthLocator(interval=1)
month_locs_fmt = mdates.DateFormatter('%b')
ax.xaxis.set_major_locator(month_locs)
ax.xaxis.set_major_formatter(month_locs_fmt)

day_locs = mdates.DayLocator(interval=7)
day_locs_fmt = mdates.DateFormatter('%d')
ax.xaxis.set_minor_locator(day_locs,)
ax.xaxis.set_minor_formatter(day_locs_fmt)
ax.xaxis.set_tick_params(which='major', pad=-10, length=40)

ax.plot(df['date'], df['value'])




## Align

offset = ScaledTranslation(1.6, 0, fig.dpi_scale_trans)
for label in ax.xaxis.get_majorticklabels():
    label.set_transform(label.get_transform() + offset)

## Add alternating pattern

plt.grid(axis='x', which='major')
xticks = [t._loc for t in ax.xaxis.get_major_ticks()]
for x0, x1 in zip(xticks[::2], xticks[1::2]):
    ax.axvspan(x0, x1, color='black', alpha=0.1, zorder=0)


plt.show()

enter image description here

Maciej Skorski
  • 2,303
  • 6
  • 14