Reputation: 57033
I am trying to combine a Pandas time series and a number of vertical segments (markers) in the same plot. The series has the frequency of 'Q-DEC' (quarter), which in this example is inferred from the dates, but in the real problem is a part of the dataset. The markers in general are not aligned with the series and may appear anywhere, not necessarily at the end of a quarter.
My problem is that if I first plot the series and then the markers, the markers' positions are rounded up to the next end of the quarter (the upper plot). If I plot the markers first, followed by the series, the markers are in the right positions but the x ticks labels are inappropriate (the lower plot).
Q: How can I plot the markers at the right positions over a time series plot?
import datetime
import pandas as pd
from pandas import Timestamp
import matplotlib.pyplot as plt
data = pd.DataFrame({0: {Timestamp('2017-03-31'): 1,
Timestamp('2017-06-30'): 2,
Timestamp('2017-09-30'): 3,
Timestamp('2017-12-31'): 3,
Timestamp('2018-03-31'): 2,
Timestamp('2018-06-30'): 1}})
ax = plt.subplot(2,1,1)
data[0].plot(ax=ax,style="-mo")
ax.axvline(pd.Timestamp(datetime.date(2017, 7, 1)), c='r')
ax.axvline(pd.Timestamp(datetime.date(2017, 8, 10)), c='g')
ax.axvline(pd.Timestamp(datetime.date(2017, 9, 20)), c='b')
ax.axvline(pd.Timestamp(datetime.date(2017, 11, 30)), c='k')
ax = plt.subplot(2,1,2)
ax.axvline(pd.Timestamp(datetime.date(2017, 7, 1)), c='r')
ax.axvline(pd.Timestamp(datetime.date(2017, 8, 10)), c='g')
ax.axvline(pd.Timestamp(datetime.date(2017, 9, 20)), c='b')
ax.axvline(pd.Timestamp(datetime.date(2017, 11, 30)), c='k')
data[0].plot(ax=ax,style="-mo")
plt.show()
Upvotes: 2
Views: 2407
Reputation: 21274
If you move the dates into the index as a PeriodIndex
, start with freq="M"
to ensure the lines get drawn correctly.
Then replace the ticks with the index values set to freq="Q-DEC"
.
data.set_index(pd.PeriodIndex(data.index, freq="M"), inplace=True)
ax = data[0].plot(style="-mo")
ax.axvline(pd.Timestamp(datetime.date(2017, 7, 1)), c='r')
ax.axvline(pd.Timestamp(datetime.date(2017, 8, 10)), c='g')
ax.axvline(pd.Timestamp(datetime.date(2017, 9, 20)), c='b')
ax.axvline(pd.Timestamp(datetime.date(2017, 11, 30)), c='k')
Now reset the ticks:
q_ticks = data.index.asfreq("Q-DEC")
ax.minorticks_off()
ax.set_xticks(q_ticks)
ax.set_xticklabels(q_ticks)
Output:
Note: If you don't remove the minor ticks with minorticks_off()
, you'll get some overlap with the original monthly ticks and the new quarterly ticks.
Update
If you really need to get the format exactly as it is in your example, you'll need to do a bit of maneuvering with major and minor tick locations and formats:
ax.get_xaxis().set_tick_params(which='major', pad=15)
q_ticks = data.index.asfreq("Q-DEC")
# extract only the year, and only the year's first listing
major_ticklabels = pd.Series(q_ticks.strftime("%Y"))
major_ticklabels[major_ticklabels.duplicated()] = ""
ax.set_xticks(q_ticks)
ax.set_xticklabels(major_ticklabels)
# format as Q[quarter number]
minor_ticklabels = q_ticks.strftime("Q%q")
ax.xaxis.set_ticks(q_ticks, minor=True)
ax.xaxis.set_ticklabels(minor_ticklabels, minor=True)
Upvotes: 2
Reputation: 2372
The following code can change your second subplot axis like the first subplot. Try it if you want the quarter as x-axis.
data = pd.DataFrame({0: {Timestamp('2017-03-31'): 1,
Timestamp('2017-06-30'): 2,
Timestamp('2017-09-30'): 3,
Timestamp('2017-12-31'): 3,
Timestamp('2018-03-31'): 2,
Timestamp('2018-06-30'): 1}})
xaxis = ['Q{}'.format((pd.to_datetime(date).month-1)//3+1) for date in data.index.values]
plt.xticks(data.index.values,xaxis)
plt.axvline(pd.Timestamp(datetime.date(2017, 7, 1)), c='r')
plt.axvline(pd.Timestamp(datetime.date(2017, 8, 10)), c='g')
plt.axvline(pd.Timestamp(datetime.date(2017, 9, 20)), c='b')
plt.axvline(pd.Timestamp(datetime.date(2017, 11, 30)), c='k')
data[0].plot(style="-mo")
Upvotes: 1