7

I want to plot, in a specific few ways, dates on the x axis and time of day on the y axis, and then have either line plots or interval (floating bar) plots.

This SO answer helps

But I have a few differences from that plot and I can't get it to work. I'm actually getting the y axis to plot both the DATES as well as the time, so it is scrunching months' worth of timestamps on the y axis, when I just need about one day's worth. That example claims in the comments, "base date for yaxis can be anything, since information is in the time", but I don't understand how he is "throwing away" the base date information.

In any case, my needs are:

  1. I'd like the option for either 24 hour time (00:00 to 24:00) or am/pm style time for the y axis, with the axis ticks looking like 3:00pm, 11:00am, etc. This will be done with a FuncFormatter, I guess.

  2. For the intervals (time ranges) I don't want to use the error bars--the lines are way too thin. I'd like to use a (floating) bar/column plot.

My data are datetime strings of the format '2010-12-20 05:00:00'

Thanks for any help.

Community
  • 1
  • 1
Che M
  • 197
  • 1
  • 2
  • 5

2 Answers2

13

I think you're slightly confused as to exactly how matplotlib handles times and dates behind the scenes.

All datetimes in matplotlib are represented as simple floats. 1 day corresponds to a difference of 1.0, and the dates are in days since 1900 (if I remember correctly, anyway).

So, in order to plot just the time of a given date, you need to use a % 1.

I'm going to use points, but you can easily use bars. Look into using the bottom keyword argument if you do use plt.bar, so that the bottom of the bars will start at the start time of your intervals (and remember that the second argument is the height of the bar, not its top y-value).

For example:

import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
import datetime as dt

# Make a series of events 1 day apart
x = mpl.dates.drange(dt.datetime(2009,10,1), 
                     dt.datetime(2010,1,15), 
                     dt.timedelta(days=1))
# Vary the datetimes so that they occur at random times
# Remember, 1.0 is equivalent to 1 day in this case...
x += np.random.random(x.size)

# We can extract the time by using a modulo 1, and adding an arbitrary base date
times = x % 1 + int(x[0]) # (The int is so the y-axis starts at midnight...)

# I'm just plotting points here, but you could just as easily use a bar.
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot_date(x, times, 'ro')
ax.yaxis_date()
fig.autofmt_xdate()

plt.show()

Time vs. Date

Hopefully that helps a bit!

Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • Thank you! That does help me understand it. It seems part of my problem was I am using a custom autoscale_view that was showing many YEARS on the y axis, instead of 1 day. Disabling that shows one day. So my remaining issues now are: 1) how can I get a loose autoscale view on this so that the y axis view limits are some reasonable number of minutes past the max and min time points, and 2) How can I get the y axis to report in am/pm time? – Che M Jan 25 '11 at 21:09
  • Ahh...OK, I've got it. 1) The problem was that the autoscale view I was using needed to adjust the scale a *much* tinier amount (quite a few places to the right of the decimal point). 2) Here, in a custom formatter, I just converted the value to a Python datetime and used strftime to return it as 12-hour am/pm time. Thanks again. – Che M Jan 25 '11 at 22:01
  • @Che - Glad to hear you got it working! For what it's worth, the `DateFormatter` class does exactly that (Converts the value to a datetime and uses a given strftime string to format). You might find it a bit easier than using a custom formatter. For the first problem, a simple `ax.axis('tight')` might be a good way to deal with it... On the other hand, if what you have works, it works! – Joe Kington Jan 25 '11 at 22:50
  • 1
    In the year 2019, the yaxis may not be ideal out of the box. I found adding the following lines before plt.show() leads to a pleasing result. Live long and prosper (you may also want to increase the figsize or increase the interval on the hour locator) ax.yaxis.set_major_locator(mpl.dates.HourLocator()) ax.yaxis.set_major_formatter(mpl.dates.DateFormatter('%H %M')) – JoeyC Jan 12 '19 at 06:19
2

Follow Joe Kingston's response, but I'd add that you need to convert your date strings into datetime objects, which can easily be done with the datetime module, and then convert them to matplotlib dates represented as floats (which can then be formatted as desired).

import datetime as dt
import matplotlib as mpl

some_time_str = '2010-12-20 05:00:00'
some_time_dt = dt.datetime.strptime(some_time_str, '%Y-%m-%d %H:%M:%S')
some_time_num = mpl.dates.date2num(some_time_dt) # 734126.20833333337

If you start with an array filled with time strings, you can do something like:

time_str_list = ['2010-12-20 05:00:00','2010-12-20 05:30:00']
time_num_list = map(
    lambda x: mpl.dates.date2num(dt.datetime.strptime(x, '%Y-%m-%d %H:%M:%S')),
    time_str_list) #[734126.20833333337, 734126.22916666663]
dr jimbob
  • 17,259
  • 7
  • 59
  • 81
  • 2
    For what it's worth, matplotlib has a built-in function to do this. You can just do `matplotlib.dates.datestr2num(time_str_list)`. – Joe Kington Jan 25 '11 at 19:04
  • That, by the way, is quite convenient--didn't know about that. Thanks. – Che M Jan 25 '11 at 22:02