Add custom minor ticks to plot with dates on the x-axis

Question:

I’m trying to add custom labels for some special x-values on a plot that has datetimes on the x-axis. Here is a sample of the plot:

from matplotlib import pyplot as plt
from matplotlib.dates import AutoDateLocator, ConciseDateFormatter
import numpy as np

x = np.array([1100, 1800, 2900], 'datetime64[ms]')
y = np.array([1.1, -0.9, 0.2])
labels = {k: v for k, v in zip(x, ['A', 'B', 'C'])}

fig, ax = plt.subplots(constrained_layout=True)
ax.plot(x, y)
locator = AutoDateLocator()
ax.xaxis.set_major_locator(locator)
ax.xaxis.set_major_formatter(ConciseDateFormatter(locator))

So far so good.

enter image description here

Now I want to add some ticks at the locations given by x with labels set to labels. When I try to use x as a datetime, I get a slew of errors:

from matplotlib.ticker import FixedLocator, FuncFormatter

ax.xaxis.set_minor_locator(FixedLocator(x))
Traceback (most recent call last):
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/axes/_base.py", line 4628, in get_tightbbox
    bb_xaxis = self.xaxis.get_tightbbox(
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/axis.py", line 1103, in get_tightbbox
    ticks_to_draw = self._update_ticks()
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/axis.py", line 1053, in _update_ticks
    minor_locs = self.get_minorticklocs()
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/axis.py", line 1292, in get_minorticklocs
    minor_locs = [
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/axis.py", line 1294, in <listcomp>
    if ~np.isclose(tr_loc, tr_major_locs, atol=tol, rtol=0).any()]
  File "<__array_function__ internals>", line 180, in isclose
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/numpy/core/numeric.py", line 2375, in isclose
    return within_tol(x, y, atol, rtol)
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/numpy/core/numeric.py", line 2356, in within_tol
    return less_equal(abs(x-y), atol + rtol * abs(y))
numpy.core._exceptions._UFuncBinaryResolutionError: ufunc 'subtract' cannot use operands with types dtype('<M8[ms]') and dtype('float64')

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/_constrained_layout.py", line 546, in get_pos_and_bbox
    tightbbox = ax.get_tightbbox(renderer=renderer, for_layout_only=True)
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/axes/_base.py", line 4632, in get_tightbbox
    bb_xaxis = self.xaxis.get_tightbbox(renderer)
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/axis.py", line 1103, in get_tightbbox
    ticks_to_draw = self._update_ticks()
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/axis.py", line 1053, in _update_ticks
    minor_locs = self.get_minorticklocs()
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/axis.py", line 1292, in get_minorticklocs
    minor_locs = [
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/axis.py", line 1294, in <listcomp>
    if ~np.isclose(tr_loc, tr_major_locs, atol=tol, rtol=0).any()]
  File "<__array_function__ internals>", line 180, in isclose
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/numpy/core/numeric.py", line 2375, in isclose
    return within_tol(x, y, atol, rtol)
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/numpy/core/numeric.py", line 2356, in within_tol
    return less_equal(abs(x-y), atol + rtol * abs(y))
numpy.core._exceptions._UFuncBinaryResolutionError: ufunc 'subtract' cannot use operands with types dtype('<M8[ms]') and dtype('float64')

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/axes/_base.py", line 4628, in get_tightbbox
    bb_xaxis = self.xaxis.get_tightbbox(
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/axis.py", line 1103, in get_tightbbox
    ticks_to_draw = self._update_ticks()
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/axis.py", line 1053, in _update_ticks
    minor_locs = self.get_minorticklocs()
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/axis.py", line 1292, in get_minorticklocs
    minor_locs = [
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/axis.py", line 1294, in <listcomp>
    if ~np.isclose(tr_loc, tr_major_locs, atol=tol, rtol=0).any()]
  File "<__array_function__ internals>", line 180, in isclose
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/numpy/core/numeric.py", line 2375, in isclose
    return within_tol(x, y, atol, rtol)
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/numpy/core/numeric.py", line 2356, in within_tol
    return less_equal(abs(x-y), atol + rtol * abs(y))
numpy.core._exceptions._UFuncBinaryResolutionError: ufunc 'subtract' cannot use operands with types dtype('<M8[ms]') and dtype('float64')

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/backends/backend_qt.py", line 477, in _draw_idle
    self.draw()
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py", line 436, in draw
    self.figure.draw(self.renderer)
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/artist.py", line 73, in draw_wrapper
    result = draw(artist, renderer, *args, **kwargs)
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/artist.py", line 50, in draw_wrapper
    return draw(artist, renderer)
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/figure.py", line 2828, in draw
    self.execute_constrained_layout(renderer)
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/figure.py", line 3186, in execute_constrained_layout
    return do_constrained_layout(fig, renderer, h_pad, w_pad,
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/_constrained_layout.py", line 110, in do_constrained_layout
    make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/_constrained_layout.py", line 315, in make_layout_margins
    pos, bbox = get_pos_and_bbox(ax, renderer)
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/_constrained_layout.py", line 548, in get_pos_and_bbox
    tightbbox = ax.get_tightbbox(renderer=renderer)
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/axes/_base.py", line 4632, in get_tightbbox
    bb_xaxis = self.xaxis.get_tightbbox(renderer)
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/axis.py", line 1103, in get_tightbbox
    ticks_to_draw = self._update_ticks()
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/axis.py", line 1053, in _update_ticks
    minor_locs = self.get_minorticklocs()
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/axis.py", line 1292, in get_minorticklocs
    minor_locs = [
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/matplotlib/axis.py", line 1294, in <listcomp>
    if ~np.isclose(tr_loc, tr_major_locs, atol=tol, rtol=0).any()]
  File "<__array_function__ internals>", line 180, in isclose
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/numpy/core/numeric.py", line 2375, in isclose
    return within_tol(x, y, atol, rtol)
  File "/home/madphysicist/myvenv/lib/python3.8/site-packages/numpy/core/numeric.py", line 2356, in within_tol
    return less_equal(abs(x-y), atol + rtol * abs(y))
numpy.core._exceptions._UFuncBinaryResolutionError: ufunc 'subtract' cannot use operands with types dtype('<M8[ms]') and dtype('float64')

When I try to use an integer or float representation of x, the statements run, but nothing shows up on the graph:

ax.xaxis.set_minor_locator(FixedLocator(x.astype(float)))
ax.xaxis.set_minor_locator(FixedLocator(x.astype(int)))
ax.xaxis.set_minor_locator(FixedLocator(x.astype(float)))
ax.xaxis.set_minor_locator(FixedLocator(x.astype(float) * 1e3))
ax.xaxis.set_minor_locator(FixedLocator(x.astype(float) * 1e-3))
ax.xaxis.set_minor_locator(FixedLocator(x.astype(float) * 1e6))
ax.xaxis.set_minor_locator(FixedLocator(x.astype(float) * 1e-6))
ax.xaxis.set_minor_locator(FixedLocator(x.astype(float) * 1e9))
ax.xaxis.set_minor_locator(FixedLocator(x.astype(float) * 1e-9))

How do I add date-based ticks as a set of minor ticks to a plot displaying dates on the x-axis?

The end goal is to be able to do something like this:

ax.xaxis.set_minor_locator(FixedLocator(<magic>)))
ax.xaxis.set_minor_formatter(FuncFormatter(lambda x, pos: labels[x]))
ax.tick_params('x', which='minor', rotation=-75)

Useful Resources After the fact

Basically, matplotlib stores dates as floats with units of days with a configurable epoch. Interestingly, the resolution of the result depends the distance from epoch. Resolution 52 years from epoch is ~0.21ms.

Asked By: Mad Physicist

||

Answers:

Convert the DateTime-values via matplotlib.dates.date2num first.

from matplotlib import pyplot as plt
from matplotlib.dates import AutoDateLocator, ConciseDateFormatter, date2num
from matplotlib.ticker import FixedLocator, FuncFormatter
import numpy as np

x = np.array([1100, 1800, 2900], 'datetime64[ms]')
y = np.array([1.1, -0.9, 0.2])
labels = {k: v for k, v in zip(date2num(x), ['A', 'B', 'C'])}

fig, ax = plt.subplots(constrained_layout=True)
ax.plot(x, y)

#major axis
locator = AutoDateLocator()
ax.xaxis.set_major_locator(locator)
ax.xaxis.set_major_formatter(ConciseDateFormatter(locator))

#minor axis
ax.xaxis.set_minor_locator(FixedLocator(date2num(x)))
ax.xaxis.set_minor_formatter(FuncFormatter(lambda x, p: labels[x]))
ax.tick_params('x', which='minor', rotation=-75)

plt.show()

Resulting Plot

Answered By: flyakite