DYZ
DYZ

Reputation: 57033

Plotting time data with different frequencies (matplotlib, pandas)

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()

Example

Upvotes: 2

Views: 2407

Answers (2)

andrew_reece
andrew_reece

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:

enter image description here

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)

enter image description here

Upvotes: 2

R.yan
R.yan

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")

output enter image description here

Upvotes: 1

Related Questions