备注
前往末尾 下载完整示例代码。
时间序列的自定义刻度格式化器#
在绘制每日数据时,例如金融时间序列,通常希望排除没有数据的日子,例如周末,以便数据以固定间隔绘制,而不会为没有数据的日子留下额外的空白。该示例展示了如何使用'索引格式化器'来实现所需的绘图效果。
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()