时间序列的自定义刻度格式化器#

在绘制每日数据时,例如金融时间序列,通常希望排除没有数据的日子,例如周末,以便数据以固定间隔绘制,而不会为没有数据的日子留下额外的空白。该示例展示了如何使用'索引格式化器'来实现所需的绘图效果。

import matplotlib.pyplot as plt
import numpy as np

import matplotlib.cbook as cbook
from matplotlib.dates import DateFormatter, DayLocator
import matplotlib.lines as ml
from matplotlib.ticker import Formatter

# Load a structured numpy array from yahoo csv data with fields date, open, high,
# low, close, volume, adj_close from the mpl-data/sample_data directory. The
# record array stores the date as an np.datetime64 with a day unit ('D') in
# the date column (``r['date']``).
r = cbook.get_sample_data('goog.npz')['price_data']
r = r[:9]  # get the first 9 days

fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(6, 6), layout='constrained')
fig.get_layout_engine().set(hspace=0.15)

# First we'll do it the default way, with gaps on weekends
ax1.plot(r["date"], r["adj_close"], 'o-')

# Highlight gaps in daily data
gaps = np.flatnonzero(np.diff(r["date"]) > np.timedelta64(1, 'D'))
for gap in r[['date', 'adj_close']][np.stack((gaps, gaps + 1)).T]:
    ax1.plot(gap['date'], gap['adj_close'], 'w--', lw=2)
ax1.legend(handles=[ml.Line2D([], [], ls='--', label='Gaps in daily data')])

ax1.set_title("Plot y at x Coordinates")
ax1.xaxis.set_major_locator(DayLocator())
ax1.xaxis.set_major_formatter(DateFormatter('%a'))


# Next we'll write a custom index formatter. Below we will plot
# the data against an index that goes from 0, 1,  ... len(data).  Instead of
# formatting the tick marks as integers, we format as times.
def format_date(x, _):
    try:
        # convert datetime64 to datetime, and use datetime's strftime:
        return r["date"][round(x)].item().strftime('%a')
    except IndexError:
        pass

# Create an index plot (x defaults to range(len(y)) if omitted)
ax2.plot(r["adj_close"], 'o-')

ax2.set_title("Plot y at Index Coordinates Using Custom Formatter")
ax2.xaxis.set_major_formatter(format_date)  # internally creates FuncFormatter
Traceback (most recent call last):
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/sphinx_gallery/scrapers.py", line 377, in save_figures
    rst = scraper(block, block_vars, gallery_conf)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/doc/sphinxext/util.py", line 16, in matplotlib_reduced_latex_scraper
    return matplotlib_scraper(block, block_vars, gallery_conf, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/sphinx_gallery/scrapers.py", line 173, in matplotlib_scraper
    fig.savefig(image_path, **these_kwargs)
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/figure.py", line 3451, in savefig
    self.canvas.print_figure(fname, **kwargs)
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/backend_bases.py", line 2138, in print_figure
    self.figure.draw(renderer)
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/artist.py", line 94, in draw_wrapper
    result = draw(artist, renderer, *args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/artist.py", line 71, in draw_wrapper
    return draw(artist, renderer)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/figure.py", line 3212, in draw
    self.get_layout_engine().execute(self)
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/layout_engine.py", line 273, in execute
    return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/_constrained_layout.py", line 116, in do_constrained_layout
    make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/_constrained_layout.py", line 388, in make_layout_margins
    pos, bbox = get_pos_and_bbox(ax, renderer)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/_constrained_layout.py", line 641, in get_pos_and_bbox
    tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/artist.py", line 1402, in _get_tightbbox_for_layout_only
    return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/axes/_base.py", line 4502, in get_tightbbox
    ba = martist._get_tightbbox_for_layout_only(axis, renderer)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/artist.py", line 1402, in _get_tightbbox_for_layout_only
    return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/axis.py", line 1350, in get_tightbbox
    self._update_label_position(renderer)
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/axis.py", line 2391, in _update_label_position
    bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/axis.py", line 2184, in _get_tick_boxes_siblings
    tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/axis.py", line 1329, in _get_ticklabel_bboxes
    return ([tick.label1.get_window_extent(renderer)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/axis.py", line 1329, in <listcomp>
    return ([tick.label1.get_window_extent(renderer)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/text.py", line 962, in get_window_extent
    bbox, info, descent = self._get_layout(self._renderer)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/text.py", line 382, in _get_layout
    w, h, d = _get_text_metrics_with_cache(
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/text.py", line 69, in _get_text_metrics_with_cache
    return _get_text_metrics_with_cache_impl(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/text.py", line 77, in _get_text_metrics_with_cache_impl
    return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/backends/backend_agg.py", line 220, in get_text_width_height_descent
    font.set_text(s, 0.0, flags=get_hinting_flag())
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/_text_helpers.py", line 23, in warn_on_missing_glyph
    _api.warn_external(
  File "/Users/cw/baidu/code/fin_tool/github/matplotlib/venv/lib/python3.11/site-packages/matplotlib/_api/__init__.py", line 391, in warn_external
    warnings.warn(message, category, **kwargs)
UserWarning: Glyph 22235 (\N{CJK UNIFIED IDEOGRAPH-56DB}) missing from font(s) DejaVu Sans.

除了将函数传递给 Axis.set_major_formatter 之外,您还可以使用任何其他可调用对象,例如实现了 __call__ 方法的类实例:

class MyFormatter(Formatter):
    def __init__(self, dates, fmt='%a'):
        self.dates = dates
        self.fmt = fmt

    def __call__(self, x, pos=0):
        """Return the label for time x at position pos."""
        try:
            return self.dates[round(x)].item().strftime(self.fmt)
        except IndexError:
            pass


ax2.xaxis.set_major_formatter(MyFormatter(r["date"], '%a'))

plt.show()

由 Sphinx-Gallery 生成的图库