-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
DOC: Update and consolidate Custom Tick Formatter for Time Series example #21873
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,83 @@ | ||||||||||
""" | ||||||||||
===================================== | ||||||||||
Custom tick formatter for time series | ||||||||||
===================================== | ||||||||||
|
||||||||||
.. redirect-from:: /gallery/text_labels_and_annotations/date_index_formatter | ||||||||||
.. redirect-from:: /gallery/ticks/date_index_formatter2 | ||||||||||
|
||||||||||
When plotting daily data, e.g., financial time series, one often wants | ||||||||||
to leave out days on which there is no data, for instance weekends, so that | ||||||||||
the data are plotted at regular intervals without extra spaces for the days | ||||||||||
with no data. | ||||||||||
The example shows how to use an 'index formatter' to achieve the desired plot. | ||||||||||
""" | ||||||||||
|
||||||||||
import numpy as np | ||||||||||
import matplotlib.pyplot as plt | ||||||||||
import matplotlib.cbook as cbook | ||||||||||
import matplotlib.lines as ml | ||||||||||
from matplotlib.dates import DateFormatter, DayLocator | ||||||||||
from matplotlib.ticker import Formatter | ||||||||||
|
||||||||||
|
||||||||||
# Load a numpy record 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', np_load=True)['price_data'] | ||||||||||
.view(np.recarray)) | ||||||||||
r = r[:9] # get the first 9 days | ||||||||||
|
||||||||||
fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(6, 6), | ||||||||||
constrained_layout={'hspace': .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-') | ||||||||||
Comment on lines
+59
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe just be explicit rather than make the user go back and parse the comment?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made this change on purpose to underline/explain that this is an index plot which by definition plots the values versus an index number. In my view, the plot command is more legible/clearer without the range definition as the first argument. Besides, this kind of plot is explicitly advertised in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think, however, that most of the core devs consider this a nasty holdover from the Matlab days, and prefer the more explicit interface. Certainly when I was reading this I was confused about what In general, I wonder if it is more confusing than not to keep There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
hm, I find this feature quite handy, but if this usage is not recommended it should be described as discouraged in the docs, shouldn't it
Alternatives (pandas, xarray, ...) require additional dependencies, csv requires additional date conversion, so why not leave it as a structured array (given that the data structure is described in detail in the comment at lines 24-27)? |
||||||||||
|
||||||||||
ax2.set_title("Plot y at Index Coordinates Using Custom Formatter") | ||||||||||
ax2.xaxis.set_major_formatter(format_date) # internally creates FuncFormatter | ||||||||||
|
||||||||||
############################################################################# | ||||||||||
# Instead of passing a function into `.Axis.set_major_formatter` you can use | ||||||||||
# any other callable, e.g. an instance of a class that implements __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')) |
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.