0
votes

Currently I am charting data from some historical point to a point in current time. For example, January 2019 to TODAY (February 2021). However, my matplotlib chart only shows dates from January 2019 to January 2021 on the x-axis (with the last February tick missing) even though the data is charted to today's date on the actual plot.

Is there any way to ensure that the first and last month is always reflected on the x-axis chart? In other words, I would like the x-axis to have the range displayed (inclusive).

Picture of x axis (missing February 2021)

enter image description here The data charted here is from January 2019 to TODAY (February 12th).

Here is my code for the date format:

fig.autofmt_xdate()
date_format = mdates.DateFormatter("%b-%y")
ax.xaxis.set_major_formatter(date_format)

EDIT: The numbers after each month represent years.

2
It is difficult to establish what your desired output is. Even a monthly tick would not guarantee that all months appear as ticks (think range 29 Mar 2018 to 2 Mar 2020 - matplotlib has to choose a day of the month where to place the tick, so either the end or the start month will not be labeled). You could set a tick at the first and last data point and define the number of ticks in between. That would make the graph more difficult to read. You could add manually the last month, but this would make the graph look irregular. So, how do you imagine your final graph labeling?Mr. T
Does my answer provide the solution you were looking for?Patrick FitzGerald

2 Answers

0
votes

Matplotlib uses a limited number of ticks. It just happens that for February 2021 no tick is used. There are two things you could try. First try setting the axis limits to past today with:

ax.set_xlim(start_date, end_date)

What you could also try, is using even more ticks:

ax.set_xticks(np.arange(np.min(x), np.max(x), n_ticks))

Where n_ticks stands for the amount of ticks and x for the values on the x-axis.

0
votes

I am not aware of any way to do this other than by creating the ticks from scratch.

In the following example, a list of all first-DatetimeIndex-timestamp-of-the-month is created from the DatetimeIndex of a pandas dataframe, starting from the month of the first date (25th of Jan.) up to the start of the last ongoing month. An appropriate number of ticks is automatically selected by the step variable and the last month is appended and then removed with np.unique when it is a duplicate. The labels are formatted from the tick timestamps.

This solution works for any frequency smaller than yearly:

import numpy as np                 # v 1.19.2
import pandas as pd                # v 1.1.3
import matplotlib.pyplot as plt    # v 3.3.2

# Create sample dataset
start_date = '2019-01-25'
end_date = '2021-02-12'
rng = np.random.default_rng(seed=123) # random number generator
dti = pd.date_range(start_date, end_date, freq='D')
variable = 100 + rng.normal(size=dti.size).cumsum()
df = pd.DataFrame(dict(variable=variable), index=dti)

# Create matplotlib plot
fig, ax = plt.subplots(figsize=(10, 2))
ax.plot(df.index, df.variable)

# Create list of monthly ticks made of timestamp objects
monthly_ticks = [timestamp for idx, timestamp in enumerate(df.index)
                 if (timestamp.month != df.index[idx-1].month) | (idx == 0)]

# Select appropriate number of ticks and include last month
step = 1
while len(monthly_ticks[::step]) > 10:
    step += 1
ticks = np.unique(np.append(monthly_ticks[::step], monthly_ticks[-1]))

# Create tick labels from tick timestamps
labels = [timestamp.strftime('%b\n%Y') if timestamp.year != ticks[idx-1].year
          else timestamp.strftime('%b') for idx, timestamp in enumerate(ticks)]

plt.xticks(ticks, labels, rotation=0, ha='center');

first_last_months

As you can see, the first and last months are located at an irregular distance from the neighboring tick.


In case you are plotting a time series with a discontinous date range (e.g. weekend and holidays not included) and you are not using the DatetimeIndex for the x-axis (like this for example: ax.plot(range(df.index.size), df.variable)) so as to avoid gaps with straight lines showing up on short time series and/or very wide plots, then replace the last line of code with this:

plt.xticks([df.index.get_loc(tick) for tick in ticks], labels, rotation=0, ha='center');