Plotting Time in Python with Matplotlib

Plotting time in Python with Matplotlib

Update:

This answer is outdated since matplotlib version 3.5. The plot function now handles datetime data directly. See https://matplotlib.org/3.5.1/api/_as_gen/matplotlib.pyplot.plot_date.html

The use of plot_date is discouraged. This method exists for historic
reasons and may be deprecated in the future.

datetime-like data should directly be plotted using plot.

If you need to plot plain numeric data as Matplotlib date format or
need to set a timezone, call ax.xaxis.axis_date / ax.yaxis.axis_date
before plot. See Axis.axis_date.


Old, outdated answer:

You must first convert your timestamps to Python datetime objects (use datetime.strptime). Then use date2num to convert the dates to matplotlib format.

Plot the dates and values using plot_date:

import matplotlib.pyplot
import matplotlib.dates

from datetime import datetime

x_values = [datetime(2021, 11, 18, 12), datetime(2021, 11, 18, 14), datetime(2021, 11, 18, 16)]
y_values = [1.0, 3.0, 2.0]

dates = matplotlib.dates.date2num(x_values)
matplotlib.pyplot.plot_date(dates, y_values)

Sample Image

How to plot time on the y axis correctly using python matplotlib?

This is because your sunrises are not numerical. I'm assuming you'd want them in a form such that "6:30" means 6.5. Which is calculated below:

import matplotlib.pyplot as plt

sunrises = ['06:30', '06:28', '06:27']
# This converts to decimals
sunrises = [float(x[0:2])+(float(x[-2:])/60) for x in sunrises]
dates = ['3.21', '3.22', '3.23']

plt.plot(sunrises, dates)
plt.xlabel('sunrises')
plt.ylabel('dates')
plt.show()

Plot


Note, your dates are being treated as decimals. Is this correct?

Plotting Time Series with Matplotlib: Using datetime.datetime() works but datetime.datetime.strptime(str, format) does not

The problem was caused by a datetime width for matplotlib having to be expressed in units of 1 day. So if width = 1, a bar width is equal to a day on the x-axis.

This was resolved by making the width be equal to a percentage of a day appropriate for the time scale used, in this case 3 seconds. For example, if you want the bar width to occupy 3 seconds on the x-axis make the width equal to 3 seconds as a percentage of a whole day,

#NB: There are 86400 seconds in a day and I want a width of 3 seconds. 
ax.plot(width = (1/(86400))*3)

If you wish for the bars to not touch you should reduce the width of the bars to less than the interval between timestamps as plotted on the x-axis. Further if you wish to dyamically determine the minimum interval between timestamps please look at this post.

Plot times of day in Matplotlib in Python

your problems sounds pretty much like you are looking for a histogram.

In the code I converted your stuff to datetime format and extracted the hours. Afterwards use the histogram function from matplotlib.

Of course you can make the matplotlib figure a lot nicer, but I hope you get the point.

Just a hint: I found all these things in the brilliant documentation of matplotlib and here on stackoverflow. Hope this helps:

import datetime as dt
import matplotlib.pyplot as plt

a = ['08:50:00', '08:50:00', '10:05:00', '10:30:00', '10:30:00', '10:46:00', '10:50:00', '10:52:00', '11:00:00', '11:10:00', '11:10:00', '12:10:00', '12:20:00', '15:50:00', '16:20:00', '16:30:00']

# Convert to datetime format and get hours
hours_list = [dt.datetime.strptime(date, '%H:%M:%S').hour for date in a]

# Plot histogram from 0h to 24h
n, bins, patches = plt.hist(hours_list, 12)
plt.xlabel('Hours')
plt.ylabel('Occurences')
plt.title('Events')
plt.axis([0, 24, 0, n.max()])
plt.grid(True)

plt.show()

wrong time and resolution axis when plotting time series (secs instead of min)

You should use matplotlib.dates to set ticks interval and format.

For example you could use:

ax.xaxis.set_major_locator(md.MinuteLocator(interval = 1))

in order to set a tick for each minute and

ax.xaxis.set_major_formatter(md.DateFormatter('%H:%M'))

in order to change tick label format to HOUR:MINUTE.

Eventually, you could also use

plt.setp(ax.xaxis.get_majorticklabels(), rotation = 90)

to rotate tick label by 90 degrees, in order to improve readibility.

You can change above parameters as you please in order to better plot your particoular data.

Complete Code

import matplotlib.dates as md
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

N = 3531
df = pd.DataFrame()
df['time'] = pd.date_range(start = '2022-05-16 19:59:25.69', end = '2022-05-16 20:24:54.570', periods = N)
df['Power'] = np.random.rand(N)

fig, ax = plt.subplots()

ax.plot(df['time'], df['Power'])

ax.xaxis.set_major_locator(md.MinuteLocator(interval = 1))
ax.xaxis.set_major_formatter(md.DateFormatter('%H:%M'))
plt.setp(ax.xaxis.get_majorticklabels(), rotation = 90)

plt.show()

Plot

Sample Image

Plotting two pandas time-series on the same axes with matplotlib - unexpected behavior

  • pandas bug: #43972
  • The issue is how pandas deals with the xticks for different spans of datetimes.
    • Currently dates2 is less than one month. As you can see on the plots with pandas.DataFrame.plot, when the span is less than a month, the format is different. If dates2 spans at least a month, the issue doesn't occur. (e.g. dates2 = ['2021-08-29', '2021-09-05', '2021-09-12', '2021-09-19', '2021-09-26', '2021-09-29']).
  • Using secondary_y=True affects how pandas manages the ticks, because axs[0] plots correctly if secondary_y=True is removed.
    • I don't know why df1 will work if df2 is first as in axs[1], but df2 won't work when df1 is first.
fig, axs = plt.subplots(1, 4, figsize=[15, 6], sharey=False, sharex=False)
axs = axs.flatten()

df1.plot(ax=axs[0])
print(f'axs[0]: {axs[0].get_xticks()}')
ax4 = axs[0].twiny()
df2.plot(ax=ax4, color='tab:orange')
print(f'ax4: {ax4.get_xticks()}')

df2.plot(ax=axs[1], color='tab:orange')
print(f'axs[1]: {axs[1].get_xticks()}')
df1.plot(ax=axs[1], secondary_y=True)
print(f'axs[1]: {axs[1].get_xticks()}')

df1.y1.plot(ax=axs[2])
print(f'axs[2]: {axs[2].get_xticks()}')

df2.y2.plot(ax=axs[3])
print(f'axs[3]: {axs[3].get_xticks()}')

plt.tight_layout()

[output]:
axs[0]: [18871. 18878. 18885. 18892. 18901. 18908.]
ax4: [2696 2697 2700]
axs[1]: [2696 2697 2700] # after plotting df2
axs[1]: [2696 2697 2701 2702] # after plotting df1
axs[2]: [18871. 18878. 18885. 18892. 18901. 18908.]
axs[3]: [2696 2697 2700]
  • Note the difference in the printed xticks, which are the locations on the axis for each tick.

Sample Image



  • Plotting with matplotlib.pyplot.plot treats the dataframe datetime index the same.
fig, axs = plt.subplots(2, 2, figsize=[20, 12], sharey=False, sharex=False)
axs = axs.flatten()

axs[0].plot(df1.index, df1.y1, marker='.', color='tab:blue')
print(f'axs[0]: {axs[0].get_xticks()}')
ax4 = axs[0].twinx()
ax4.plot(df2.index, df2.y2, marker='.', color='tab:orange')
print(f'ax4: {ax4.get_xticks()}')

axs[1].plot(df2.index, df2.y2, marker='.', color='tab:orange')
print(f'axs[1]: {axs[1].get_xticks()}')
ax5 = axs[1].twinx()
ax5.plot(df1.index, df1.y1, marker='.', color='tab:blue')
print(f'ax5: {ax5.get_xticks()}')

axs[2].plot(df1.index, df1.y1, marker='.', color='tab:blue')
print(f'axs[2]: {axs[2].get_xticks()}')
axs[3].plot(df2.index, df2.y2, marker='.', color='tab:orange')
print(f'axs[3]: {axs[3].get_xticks()}')

[output]:
axs[0]: [18871. 18878. 18885. 18892. 18901. 18908.]
ax4: [18871. 18878. 18885. 18892. 18901. 18908.]
axs[1]: [18868. 18871. 18875. 18879. 18883. 18887. 18891. 18895.]
ax5: [18871. 18878. 18885. 18892. 18901. 18908.]
axs[2]: [18871. 18878. 18885. 18892. 18901. 18908.]
axs[3]: [18868. 18871. 18875. 18879. 18883. 18887. 18891. 18895.]

Sample Image

Plotting y=times (as data) versus x=dates in matplotlib: How to format the y-axis for all dates?

If you take out the line where you set the y limits, you see that the y values are datetimes, not just times. So you have a couple of choices:

1) Set all the dates the same date for the time column.

You are hiding the date on the y axis, so this is the quickest replacement in your code. The snippet below only shows the added or changed lines from your code block.

import datetime
...
# make a new column with one date and the time from up_dt
df["up_time"] = df.up_dt.apply(lambda d: datetime.datetime(2021, 1, 1, d.hour, d.minute, d.second, d.microsecond))
...
# plot using the new time column for the y values
line1 = ax1.plot(df.up_dt.dt.date, df.up_time,
label='rise time',
marker='^', linewidth=0, color='b')
...
# use the new time column when finding the y limits
d1 = str(df.up_time.min().date())+' 00:00'
d2 = str(df.up_time.min().date())+' 23:59'

2) Use decimal representation of time

If you want to strip off just the time portion and not use a fake date for a place-holder, you need to convert it to a number, because time objects are not treated numerically by matplotlib, or for the standard datetime package. Using the pendulum package we can convert a time to decimal representation, here I am converting to hours since midnight. You can replace the tick labels with the clock representation strings.

import pendulum as pend
...
# make separate up and time columns
df["up_date"] = df.up_dt.apply(lambda d: d.date())
df["up_time_hr"] = df.up_dt.apply(lambda d: (pend.parse(d.isoformat()).time() - pend.time(0)).seconds/3600)

# plot time vs date using the new columns
fig, ax1 = plt.subplots(figsize=(9,7))
line1 = ax1.plot(df.up_date, df.up_time_hr)

# example of setting tick labels
ax1.set_yticks([ 6.5, 7, 7.5])
ax1.set_yticklabels(["6:30", "7:00", "7:30"])


Related Topics



Leave a reply



Submit