How to apply matplotlib dateformatter to pandas datetime64[ns] index?

Viewed 103

Matplotlib appears to misinterpret a Pandas datetime64[ns] index. My MVCE shows some current dates being interpreted as being in 1970. Is it simply not possible to use the mpl.dates formatters with pandas dates? ax.get_figure().autofmt_xdate() works fine, but returns the whole verbose date string.

import pandas as pd
import matplotlib as mpl
from pandas import Timestamp
print(f"pd:{pd.__version__} mpl:{mpl.__version__}")

df = pd.DataFrame({'Hourly Total': 
 {Timestamp('2022-04-28 11:00:00-0700', tz='Canada/Pacific'): 0.0012916667166666667,
  Timestamp('2022-04-28 12:00:00-0700', tz='Canada/Pacific'): 0.00383333365,
  Timestamp('2022-04-28 13:00:00-0700', tz='Canada/Pacific'): 0.00383333365,
  Timestamp('2022-04-28 14:00:00-0700', tz='Canada/Pacific'): 0.00383333365},
})

ax = df.plot(kind='bar')
formatter = mpl.dates.DateFormatter('%y-%m-%d')
ax.xaxis.set_major_formatter(formatter)

Plot showing wrong dates

1 Answers

This is a problem of the Pandas bar plotting function. If you change the DateFormatter to a StrMethodFormatter to check what values are passed, you can see that it just receives the scalar index (starting from 0) of the bars you are plotting:

# Here 'x' is the value used to format the tick labels.
ax.xaxis.set_major_formatter(mpl.ticker.StrMethodFormatter('{x} {pos}'))

Which shows:

data passed to the formatter by Pandas

Consequently, the DateFormatter is trying to convert 0, 1, 2, and 3 to a date, which gives the Epoch 1970-1-1 UTC. Unfortunately, there is no easy way around that using the default formatters provided by matplotlib, since they will always receive the wrong data.

Solution 1 As your comment suggests, one solution is to simply change the xticklabels manually.

ax.set_xticklabels(df.index.strftime("%Y-%m-%d"))

Solution 2 Use matplotlib instead of Pandas for the plotting. In this case, also note that DateFormatter ignores the timezone of your dates:

import matplotlib.pyplot as pet
import pytz

f, ax = plt.subplots()
ax.bar(df.index, df['Hourly Total'], width=0.03)
# Notice here we do need the timezone.
ax.xaxis.set_major_locator(mpl.dates.HourLocator(interval=1, tz=pytz.timezone('Canada/Pacific')))
ax.xaxis.set_major_formatter(mpl.dates.DateFormatter('%y-%m-%d %H:%M', tz=pytz.timezone('Canada/Pacific')))
f.autofmt_xdate()
plt.show()

matplotlib version

Related