Search code examples
pythonmatplotlibplottickerdateformatter

Plot with two rows label sticks using matplotlib


I would like to have a plot using matplotlib.pyplot with the xticks arrange in two rows for months and years like the image following. I did that plot, just using dataframe.plot(), i.e. the simplest plot of pandas. enter image description here

When I do the plot using this code (because I need to add another subplots and that is the reason to not use dataframe.plot()), how I can get the before settings for the xticks labels?

import matplotlib.pyplot as plt
figure, ax = plt.subplots()
ax.plot(xdata, ydata)

I get this xticks labels for the plot enter image description here

I tried using matplotlib.dates.DateFormatter and matplotlib.ticker but I can't find the right settings


Solution

  • You can get close to what you want with the major and minor locators and a DateFormatter like this:

    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import matplotlib.dates
    
    dr= pd.date_range("2014-01-01", "2017-06-30", freq="D")
    df = pd.DataFrame({"dates":dr, "num":np.cumsum(np.random.randn(len(dr)))})
    df["dates"] = pd.to_datetime(df["dates"])
    
    fig, ax = plt.subplots()
    ax.plot(df.dates, df.num)
    
    ax.xaxis.set_minor_locator(matplotlib.dates.MonthLocator())
    ax.xaxis.set_major_locator(matplotlib.dates.MonthLocator([1,7]))
    ax.xaxis.set_major_formatter(matplotlib.dates.DateFormatter("%b\n%Y"))
    plt.show()
    

    enter image description here

    To only show the years for January, but not for other months, you may then need to subclass the DateFormatter

    class MyMonthFormatter(matplotlib.dates.DateFormatter):
        def __init__(self, fmt="%b\n%Y", fmt2="%b", major=[1], tz=None):
            self.fmt2 = fmt2
            self.major=major
            matplotlib.dates.DateFormatter.__init__(self, fmt, tz=tz)
        def __call__(self, x, pos=0):
            if x == 0: raise ValueError('Error')
            dt = matplotlib.dates.num2date(x, self.tz)
            if dt.month in self.major: 
                return self.strftime(dt, self.fmt)
            else:
                return self.strftime(dt, self.fmt2)
    
    ax.xaxis.set_minor_locator(matplotlib.dates.MonthLocator())
    ax.xaxis.set_major_locator(matplotlib.dates.MonthLocator([1,7]))
    ax.xaxis.set_major_formatter(MyMonthFormatter())
    plt.show()
    

    enter image description here