Matplotlib 以奇数间隔日期 YearLocator

Posted

技术标签:

【中文标题】Matplotlib 以奇数间隔日期 YearLocator【英文标题】:Matplotlib dates YearLocator with odd intervals 【发布时间】:2018-07-03 20:48:46 【问题描述】:

现在,当我的时间序列在十年之初(即 1990、2000、2010 等)开始时,我有一些可以按照我的规范格式化的工作代码,但我不知道如何调整我的当我的时间序列从非偶数年(即 1993 年)开始时,代码具有正确的格式。

import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import dates

def format_xaxis(fig):

     years = dates.YearLocator(10,month=1,day=1)
     years1=dates.YearLocator(2,month=1,day=1)
     dfmt = dates.DateFormatter('%Y')
     dfmt1 = dates.DateFormatter('%y')

     [i.xaxis.set_major_locator(years) for i in fig.axes]
     [i.xaxis.set_minor_locator(years1) for i in fig.axes]
     [i.xaxis.set_major_formatter(dfmt) for i in fig.axes]
     [i.xaxis.set_minor_formatter(dfmt1) for i in fig.axes]
     [i.get_xaxis().set_tick_params(which='major', pad=15) for i in fig.axes]

     for t in fig.axes:
         for tick in t.xaxis.get_major_ticks():
             tick.label1.set_horizontalalignment('center')
         for label in t.get_xmajorticklabels() :
             label.set_rotation(0)
             label.set_weight('bold')
         for label in t.xaxis.get_minorticklabels():
             label.set_fontsize('small')
         for label in t.xaxis.get_minorticklabels()[::5]:
             label.set_visible(False)


df = pd.DataFrame.from_dict('Y': 0: 0.15,  1: 0.18,  2: 0.23,  3: 0.15,  4: 0.15,  5: 0.15,  6: 0.17,  7: 0.175,  8: 0.212,  9: 0.184,  10: 0.18,  11: 0.18,  12: 0.21,  13: 0.139,  14: 0.15,  15: 0.128,  16: 0.126,  17: 0.1,  18: 0.11,  19: 0.183,  20: 0.14,  21: 0.12,  22: 0.155,  23: 0.245,  24: 0.248,  25: 0.262,  26: 0.17,  27: 0.143,  28: 0.13,  29: 0.102,  30: 0.258,  31: 0.293,  32: 0.196,  33: 0.21,  34: 0.14,  35: 0.17, 
                             'Date': 0: '1990-06-10 00:00:00',  1: '1991-07-26 00:00:00',  2: '1992-10-15 00:00:00',  3: '1993-10-08 00:00:00',  4: '1994-04-07 00:00:00',  5: '1994-11-20 00:00:00',  6: '1995-04-24 00:00:00',  7: '1996-02-13 00:00:00',  8: '1996-04-15 00:00:00',  9: '1996-09-12 00:00:00',  10: '1997-02-13 00:00:00',  11: '1997-04-20 00:00:00',  12: '1997-08-23 00:00:00',  13: '1997-11-06 00:00:00',  14: '1998-04-15 00:00:00',  15: '1999-05-04 00:00:00',  16: '2000-03-17 00:00:00',  17: '2000-06-01 00:00:00',  18: '2001-10-05 00:00:00',  19: '2002-09-20 00:00:00',  20: '2003-04-25 00:00:00',  21: '2003-09-20 00:00:00',  22: '2005-05-07 00:00:00',  23: '2006-10-07 00:00:00',  24: '2007-10-13 00:00:00',  25: '2008-02-02 00:00:00',  26: '2008-03-28 00:00:00',  27: '2008-10-10 00:00:00',  28: '2009-10-10 00:00:00',  29: '2011-10-05 00:00:00',  30: '2012-10-03 00:00:00',  31: '2013-09-21 00:00:00',  32: '2014-09-23 00:00:00',  33: '2015-09-22 00:00:00',  34: '2016-10-01 00:00:00',  35: '2017-09-29 00:00:00')

df['Date'] = pd.to_datetime(df['Date'], format='%Y-%m-%d %H:%M:%S')

fig, ax = plt.subplots()

df.plot('Date','Y',ax=ax,marker='x',ls='-')
ax.set_xlim(pd.datetime(1990, 1, 1), pd.datetime(2018, 1, 1))

format_xaxis(fig)

这会产生如下图:

如何使用从 1993 年开始的时间序列重新创建上述情节?我仍然希望每两年设置一次次要刻度标签(即 95,97,99,01,....)。当时间序列图从奇数年开始时,是否可以使用matplotlib.dates.YearLocator 格式化日期?

【问题讨论】:

有点偏题,但看起来您可以通过像 plt.setp(plt.gca().get_xticklabels(), rotation=45, fontsize=10) 这样的调用来简化代码。 【参考方案1】:

您可以继承 YearLocator 以拥有您的自定义 OffsetYearLocator

ma​​tplotlib

的版本
from matplotlib import dates

class OffsetYearLocator(dates.YearLocator):
    def __init__(self, *args, **kwargs):
        self.offset = kwargs.pop("offset", 0)
        dates.YearLocator.__init__(self,*args, **kwargs)
    def tick_values(self, vmin, vmax):
        ymin = self.base.le(vmin.year)-self.offset
        ymax = self.base.ge(vmax.year)+(self.base._base-self.offset)
        ticks = [vmin.replace(year=ymin, **self.replaced)]
        while True:
            dt = ticks[-1]
            if dt.year >= ymax:
                return dates.date2num(ticks)
            year = dt.year + self.base.get_base()
            ticks.append(dt.replace(year=year, **self.replaced))

ma​​tplotlib >= 3.1

的版本
from matplotlib import dates

class OffsetYearLocator(dates.YearLocator):
    def __init__(self, *args, **kwargs):
        self.offset = kwargs.pop("offset", 0)
        dates.YearLocator.__init__(self,*args, **kwargs)
    def tick_values(self, vmin, vmax):
        ymin = self.base.le(vmin.year) * self.base.step - self.offset
        ymax = self.base.ge(vmax.year) * self.base.step + (self.base.step-self.offset)
        ticks = [vmin.replace(year=ymin, **self.replaced)]
        while True:
            dt = ticks[-1]
            if dt.year >= ymax:
                return dates.date2num(ticks)
            year = dt.year + self.base.step
            ticks.append(dt.replace(year=year, **self.replaced))

这可以处理一个额外的参数offset,它是从年份中减去的。 在这种情况下,可以将base 保留为2(每两年一次),但使用1 的偏移量。

years1 = OffsetYearLocator(2, month=1, day=1, offset=1)

完整示例(使用 matplotlib >= 3.1 的版本):

import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import dates

class OffsetYearLocator(dates.YearLocator):
    def __init__(self, *args, **kwargs):
        self.offset = kwargs.pop("offset", 0)
        dates.YearLocator.__init__(self,*args, **kwargs)
    def tick_values(self, vmin, vmax):
        ymin = self.base.le(vmin.year) * self.base.step - self.offset
        ymax = self.base.ge(vmax.year) * self.base.step + (self.base.step-self.offset)
        ticks = [vmin.replace(year=ymin, **self.replaced)]
        while True:
            dt = ticks[-1]
            if dt.year >= ymax:
                return dates.date2num(ticks)
            year = dt.year + self.base.step
            ticks.append(dt.replace(year=year, **self.replaced))

def format_xaxis(ax):

    years = dates.YearLocator(10,month=1,day=1)
    years1=OffsetYearLocator(2,month=1,day=1, offset=1)
    dfmt = dates.DateFormatter('%Y')
    dfmt1 = dates.DateFormatter('%y')

    ax.xaxis.set_major_locator(years)
    ax.xaxis.set_minor_locator(years1)
    ax.xaxis.set_major_formatter(dfmt)
    ax.xaxis.set_minor_formatter(dfmt1)
    ax.get_xaxis().set_tick_params(which='major', pad=15)

    plt.setp(ax.get_xmajorticklabels(), rotation=0, weight="bold", ha="center")


df = pd.DataFrame.from_dict('YData': 0: 0.15,  1: 0.18,  2: 0.23,  3: 0.15,  4: 0.15,  5: 0.15,  6: 0.17,  7: 0.175,  8: 0.212,  9: 0.184,  10: 0.18,  11: 0.18,  12: 0.21,  13: 0.139,  14: 0.15,  15: 0.128,  16: 0.126,  17: 0.1,  18: 0.11,  19: 0.183,  20: 0.14,  21: 0.12,  22: 0.155,  23: 0.245,  24: 0.248,  25: 0.262,  26: 0.17,  27: 0.143,  28: 0.13,  29: 0.102,  30: 0.258,  31: 0.293,  32: 0.196,  33: 0.21,  34: 0.14,  35: 0.17, 
                             'Date': 0: '1990-06-10 00:00:00',  1: '1991-07-26 00:00:00',  2: '1992-10-15 00:00:00',  3: '1993-10-08 00:00:00',  4: '1994-04-07 00:00:00',  5: '1994-11-20 00:00:00',  6: '1995-04-24 00:00:00',  7: '1996-02-13 00:00:00',  8: '1996-04-15 00:00:00',  9: '1996-09-12 00:00:00',  10: '1997-02-13 00:00:00',  11: '1997-04-20 00:00:00',  12: '1997-08-23 00:00:00',  13: '1997-11-06 00:00:00',  14: '1998-04-15 00:00:00',  15: '1999-05-04 00:00:00',  16: '2000-03-17 00:00:00',  17: '2000-06-01 00:00:00',  18: '2001-10-05 00:00:00',  19: '2002-09-20 00:00:00',  20: '2003-04-25 00:00:00',  21: '2003-09-20 00:00:00',  22: '2005-05-07 00:00:00',  23: '2006-10-07 00:00:00',  24: '2007-10-13 00:00:00',  25: '2008-02-02 00:00:00',  26: '2008-03-28 00:00:00',  27: '2008-10-10 00:00:00',  28: '2009-10-10 00:00:00',  29: '2011-10-05 00:00:00',  30: '2012-10-03 00:00:00',  31: '2013-09-21 00:00:00',  32: '2014-09-23 00:00:00',  33: '2015-09-22 00:00:00',  34: '2016-10-01 00:00:00',  35: '2017-09-29 00:00:00')

df['Date'] = pd.to_datetime(df['Date'], format='%Y-%m-%d %H:%M:%S')

fig, ax = plt.subplots()

ax.plot('Date','YData', data=df, marker='x',ls='-')
ax.set_xlim(pd.datetime(1990,1,1), pd.datetime(2018,1,1))

format_xaxis(ax)

plt.show()

【讨论】:

哇!。我有 99% 的把握我不会得到任何答案。我最终能够调整您的解决方案以获得我想要的格式!具体来说,我必须使用years = OffsetYearLocator(10,month=1,day=1, offset=7)years1=OffsetYearLocator(2,month=1,day=1, offset=1)。非常感谢!! @ImportanceOfBeingErnest 代码pd.datetime(1990,01,01) 中的这部分如何为您不产生 SyntaxError? @ImportanceOfBeingErnest 在 python 控制台和 Jupiter 中:pd.datetime(1990,01,01) ^ SyntaxError: invalid token。字符^ 指向01。 PS。你使用 Python 2 还是 3? @dubbbdan 是的,您已更新到 matplotlib 3.1。由于原始代码使用了私有属性,如有更改,恕不另行通知,因此突然中断。我用新版本更新了答案。 @dubbbdan 是的,你是对的,这里使用 pandas 绘图令人困惑。我也改了。【参考方案2】:

为了禁用一些次要刻度,您可以更改次要刻度的间隔:

years1=dates.YearLocator(1) 

并将它们的每一秒的可见性设置为 False:

for tick in t.xaxis.get_minor_ticks()[1::2]:
    tick.set_visible(False)

在您的代码中删除次要标签可见性选项后:

for label in t.xaxis.get_minorticklabels()[::5]:
    label.set_visible(False)

xlim 更改为 1993 年之后,您会看到下一张图片:


更复杂和灵活的解决方案是创建一个新的 Formatter 类:
# Format year minor ticks
class MinorYearFormatter(dates.DateFormatter):
    def __init__(self, fmt):
        dates.DateFormatter.__init__(self, fmt)
    def __call__(self, x, pos):
        # Disable tick labels for some years
        if pd.Timestamp.fromordinal(int(x)).year % 2 == 0:
            return ''
        else:
            return dates.DateFormatter.__call__(self, x, pos)

并将dfmt1 重新分配给新的格式化程序:

dfmt1 = MinorYearFormatter('%y')

【讨论】:

此解决方案更改格式,而不是刻度的位置。如果您不介意标记的刻度之间的额外刻度,它仍然很有用。我想你可以省略MinorYearFormatter__init__ 函数,因为它实际上并没有做任何事情。 是的,__init__ 是对的,谢谢。它只是自动......在创建子类时编写init......顺便说一句,“每两年设置一次小刻度标签” - 我猜它没有限制隐式格式修改。我认为这只是关于知名度。

以上是关于Matplotlib 以奇数间隔日期 YearLocator的主要内容,如果未能解决你的问题,请参考以下文章

条形图 Matplotlib:日期间隔(xaxis)问题与 twinx

我怎样才能让 xticks 在我的 matplotlib 图上有一个每月间隔而不是每天

如何使用 Matplotlib 调整图形的 x 轴“日期”标签?

python matplotlib以日期为x轴作图

02:奇数单增序列

Matplotlib 奇数子图