3

I frequently plot multiple timeseries data from different sources on a single plot, some of which require using matplotlib. When formatting the x-axis, I use matplotlib's autofmt_xdate(), but I much prefer the auto formatting of pandas. I'm aware I can manually set the format using set_major_formatter(), but the plots I create vary from years, to days in total range, so I would need to adjust the formatting based on each plot. Is there a way to set matplotlib to auto format the x-axis with dates similar to pandas?

I also use interactive plotting, and when using pandas df.plot() the x-axis updates when zooming to the respective ranges as shown below, which I would also like to achieve using matplotlib:

pandas format month pandas format day pandas format inter-day

Versions:

Python: 3.7.1
Pandas: 0.23.3
Matplotlib: 2.2.2

Desired Format:

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

ix = pd.date_range('1/1/2017', '11/1/2018', freq='D')
vals = np.random.randn(len(ix))
df = pd.DataFrame({'Values': vals}, index=ix)

fig, ax = plt.subplots(1, 1, figsize=[8,6])
df.plot(ax=ax, lw=1)
plt.show()

desired pandas format

Current Format:

fig, ax = plt.subplots(1, 1, figsize=[8,6])
ax.plot(df, lw=1)
fig.autofmt_xdate()
plt.show()

Current matplotlib format

3
  • 1
    Can't reproduce, your first code snippet gives me your "desired" format. Commented Nov 26, 2018 at 22:54
  • Yes, this is a simple example just to show the format I want to achieve using matplotlib without requiring df.plot() Commented Nov 26, 2018 at 23:09
  • MPL really needs to have a copy_formatter() function. It should be easy to implement when the indexes are equivalent. Did you solve it in any way? Commented Oct 7, 2020 at 22:45

1 Answer 1

5

An option to show the years in a second row is to use the major and minor ticklabels.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.dates import MonthLocator, YearLocator, DateFormatter

ix = pd.date_range('1/1/2017', '11/1/2018', freq='D')
vals = np.random.randn(len(ix))
s = pd.DataFrame({'Values': vals}, index=ix)

fig, ax = plt.subplots(figsize=[8,6])
ax.plot(s, lw=1)

ax.xaxis.set_major_locator(YearLocator())
ax.xaxis.set_major_formatter(DateFormatter("\n%Y"))

ax.xaxis.set_minor_locator(MonthLocator((1,4,7,10)))
ax.xaxis.set_minor_formatter(DateFormatter("%b"))

plt.show()

If you need the minor ticks for something else, the following would format the major ticks alone - with the same result. Here you would use a FuncFormatter to determine the format depending on the month.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.dates import MonthLocator, DateFormatter
from matplotlib.ticker import FuncFormatter

ix = pd.date_range('1/1/2017', '11/1/2018', freq='D')
vals = np.random.randn(len(ix))
s = pd.DataFrame({'Values': vals}, index=ix)

fig, ax = plt.subplots(figsize=[8,6])
ax.plot(s, lw=1)

monthfmt = DateFormatter("%b")
yearfmt = DateFormatter("%Y")

def combinedfmt(x,pos):
    string = monthfmt(x)
    if string == "Jan":
        string += "\n" + yearfmt(x)
    return string

ax.xaxis.set_major_locator(MonthLocator((1,4,7,10)))
ax.xaxis.set_major_formatter(FuncFormatter(combinedfmt))

plt.show()

The result is in both cases the same:

enter image description here

Sign up to request clarification or add additional context in comments.

2 Comments

While this works for plots when the full range is years, it fails to work when the range changes to one month or one day. I also frequently use interactive plots, and this method would not update the x-axis when zooming in. I've updated my question to better reflect this.
It's sure possible to write your own complete formatter. In case you are interested in that you may read through this discussion about a new general purpose formatter.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.