matplotlib HourLocator steals my x labels - python

When I plot my half hourly time series, my axis labels are odd (like 16:33:12h or so...)
When I use HourLocator to fix this (16:33h -> 16:00h), then my x label disappear completely.
My code is:
from datetime import date, timedelta, datetime, time
from matplotlib.dates import DayLocator, HourLocator
import matplotlib.pyplot as plt
start = time(0, 0, 0)
delta = timedelta(minutes=30)
times = []
for i in range(len(day_load)):
dt = datetime.combine(date.today(), time(0, 0)) + delta * i
times.append(dt.time())
load = [i/48 for i in range(48)]
fig, ax = plt.subplots()
ax.plot_date(times, load)
ax.xaxis.set_major_locator(HourLocator())
plt.show()
How can I achieve "even" labels (in a best practice way - I don't want to rewrite code for every other plot again).
When I comment second last line, I get normal "odd" labels :(
Thanks for answers!

There are two main issues:
You need to work with complete datetime objects, not only with time. So instead of dt.time() you should append dt directly.
You not only need a locator, but also a formatter to produce nice ticklabels. Here you may use a DateFormatter("%H:%M") to show hours and minutes.
Complete code:
from datetime import date, timedelta, datetime, time
from matplotlib.dates import DayLocator, HourLocator,DateFormatter
import matplotlib.pyplot as plt
start = time(0, 0, 0)
delta = timedelta(minutes=30)
times = []
n=48
for i in range(n):
# use complete datetime object, not only time
dt = datetime.combine(date.today(), time(0, 0)) + delta * i
times.append(dt)
load = [i/float(n) for i in range(n)]
fig, ax = plt.subplots()
ax.plot_date(times, load)
# set a locator, as well as a formatter
ax.xaxis.set_major_locator(HourLocator())
ax.xaxis.set_major_formatter(DateFormatter("%H:%M"))
#optionally rotate the labels and make more space for them
fig.autofmt_xdate()
plt.show()

This is an old question, but for anyone else facing this problem: you can leave the data types what they should be and use matplotlib.ticker.IndexLocator to get the axis ticks located nicely.
For example,
locator = mpl.ticker.IndexLocator(base=2 * 60 * 60, offset=0)
ax.xaxis.set_major_locator(locator)
places ticks at every two full hours, i.e. it uses the total number of seconds since midnight, regardless of the length of the intervals in the data.

Your code doesn't run, because day_load is undefined and I get other issues as well.
Not an answer, but I think you're better of using pandas. It makes it easy to create a date_range, and plotting is handled pretty well without adjustments.
from scipy import stats
import pandas as pd
n = 20
index = pd.date_range(start = '2016-01-01', periods = n, freq='1H')
df = pd.DataFrame(index = index)
df["value"] = stats.norm().rvs(n)
df.plot()

Related

Matplotlib x-axis ticks, fixed location for dates

In the timeline plot I’m making, I want date tickers to show only specified dates. (In my example I show tickers for events ‘A’, but it can be any list on tickers). I found how to do it when x-axis data is numeric (upper subplot in my example), but this won’t work with timestamp date type (bottom plot).
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.ticker as ticker
myData = pd.DataFrame({'date':['2019-01-15','2019-02-10','2019-03-20','2019-04-17','2019-05-23','2019-06-11'],'cnt':range(6),'event':['a','b','a','b','a','b']})
myData['date'] = [pd.Timestamp(j) for j in myData['date']]
start = pd.Timestamp('2019-01-01')
stop = pd.Timestamp('2019-07-01')
inxa = myData.loc[myData['event'] == 'a'].index
inxb = myData.loc[myData['event'] == 'b'].index
# create two plots, one with 'cnt' as x-axis, the other 'dates' on x-axis.
fig, ax = plt.subplots(2,1,figsize=(16,9))
ax[0].plot((0,6),(0,0), 'k')
ax[1].plot((start, stop),(0,0))
for g in inxa:
ax[0].plot((myData.loc[g,'cnt'],myData.loc[g,'cnt']),(0,1),c='r')
ax[1].plot((myData.loc[g,'date'],myData.loc[g,'date']),(0,1),c='r')
for g in inxb:
ax[0].plot((myData.loc[g,'cnt'],myData.loc[g,'cnt']),(0,2),c='b')
ax[1].plot((myData.loc[g,'date'],myData.loc[g,'date']),(0,2),c='b')
xlist0 = myData.loc[myData['event']=='a','cnt']
xlist1 = myData.loc[myData['event']=='a','date']
ax[0].xaxis.set_major_locator(ticker.FixedLocator(xlist0))
# ax[1].xaxis.set_major_locator(**???**)
Couldn't find a sufficient duplicate, maybe I didn't look hard enough. There are a number of ways to do this:
Converting to numbers first or using the underlying values of a Pandas DateTime Series
xticks = [mdates.date2num(z) for z in xlist1]
# or
xticks = xlist1.values
and at least a couple ways to use it/them
ax[1].xaxis.set_major_locator(ticker.FixedLocator(xticks))
ax[1].xaxis.set_ticks(xticks)
Date tick labels
How to set the xticklabels for date in matplotlib
how to get ticks every hour?
...

how to deal with plot x axis datetime not continue

Here is my example code:
import sys
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import matplotlib.dates as md
import pymysql
import numpy as np
import datetime as dt
import matplotlib.ticker as ticker
import pylab as pl
t=[4100,4105,4100,4105,4100,4105,4100,4105,4100,4105,4100,4105]
s=[dt.datetime.now(),dt.datetime.now()+dt.timedelta(seconds=1),dt.datetime.now()+dt.timedelta(seconds=2),dt.datetime.now()+dt.timedelta(seconds=3),
dt.datetime.now()+dt.timedelta(seconds=4),
dt.datetime.now()+dt.timedelta(seconds=5),
dt.datetime.now()+dt.timedelta(seconds=6),
dt.datetime.now()+dt.timedelta(seconds=7),
dt.datetime.now()+dt.timedelta(seconds=8),
dt.datetime.now()+dt.timedelta(seconds=9),
dt.datetime.now()+dt.timedelta(seconds=10),
dt.datetime.now()+dt.timedelta(minutes=1)]
N = len(s)
ind = np.arange(N) # the evenly spaced plot indices
def format_date(x, pos=None):
thisind = np.clip(int(x+0.5), 0, N-1)
return pl.num2date(x).strftime('%Y-%m-%d %H:%M:%S')
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(s, t)
ax.xaxis.set_major_formatter(ticker.FuncFormatter(format_date))
fig.autofmt_xdate()
plt.show()
Please notice the last datetime element is
dt.datetime.now()+dt.timedelta(minutes=1)
This means the x axis does not continue between the last element and it's previous element, this time range has no data.
When I see the plot above code draw, the result image is just like w character, except the last element, because it's time interval is not same as others, so it's far away from it's previous element.
But what I really need is: skip the empty time range of x-axis, and let the last element have same interval in x-axis as others, and remain signal it's time correctly .
I learnt this code mainly from:
https://matplotlib.org/2.1.2/gallery/api/date_index_formatter.html
but this example seems only deal with skip weekend whole day, cannot skip exactly time range, for example skip 1 hour 20 min in x-axis, can anybody tell me how to finish it ? Thanks.

Using datetime in Python

I have an amount of times written in the format hh:mm:ss, if I use the code below and print what x is I get 1900, 1, 1, 10, 29, 34 for every timestamp. How can I take away the year, month and date? As I want to have the time in the format hh:mm:ss
EDIT: Updated with the whole code as it looks now with help from comments.
import matplotlib.pyplot as plt
import matplotlib.ticker
import time
import datetime
x = ['10:29:55', '10:34:44']
sy1 = [679.162, 679.802]
x_labels = [datetime.datetime.strptime(elem, '%H:%M:%S') for elem in x]
formatter = matplotlib.ticker. FixedFormatter(x_labels)
plt.gca().xaxis.set_major_formatter(formatter)
plt.gca().xaxis.set_minor_formatter(formatter)
plt.plot(x_labels, sy1, 'ro')
plt.xlabel('Time')
plt.ylabel('Position')
plt.show()
But obviously it displays the time when taking into account the year, month and date too.
Plotting (wrong) time against y values
If I use strftime instead of strptime I get a TypeError: descriptor 'strftime' requires a 'datetime.date' object but received a 'str'
Ok. There are a few ways to get what you want.
If you're willing to settle for having microseconds, you should be really close to what you need:
import matplotlib.pyplot as plt
import datetime
time_strings = ['10:29:55', '10:34:44']
sy1 = [679.162, 679.802]
times = [datetime.datetime.strptime(elem, '%H:%M:%S') for elem in time_strings]
plt.plot(times, sy1, 'ro')
plt.xlabel('Time')
plt.ylabel('Position')
plt.show()
This should show the times you want in a plot, just with microseconds in the formatting. The microseconds make it all ugly, but my only changes were ones for clarity - I didn't import time or import matplotlib.ticker, I changed your x to a more accurate variable name, and created the datetimes as you did. To get rid of the microseconds, things get uglier. You can't just use the FixedFormatter because we only set 2 values, and the standard plot has more than 2 ticks; you have to find a way to get the FuncFormatter to work. This works as desired, but is still too noisy, so I'm adding in the plt.gcf().autofmt_xdate() as well.
import matplotlib.pyplot as plt
import matplotlib.ticker
import datetime
import pylab
time_strings = ['10:29:55', '10:34:44']
sy1 = [679.162, 679.802]
times = [datetime.datetime.strptime(elem, '%H:%M:%S') for elem in time_strings]
plt.plot(times, sy1, 'ro')
formatter = matplotlib.ticker.FuncFormatter(lambda tick_value, _: datetime.datetime.strftime(pylab.num2date(tick_value), '%H:%M:%S'))
plt.gca().xaxis.set_major_formatter(formatter)
plt.gca().xaxis.set_minor_formatter(formatter)
plt.xlabel('Time')
plt.ylabel('Position')
plt.gcf().autofmt_xdate()
plt.show()
The line defining the FuncFormatter is messy. I define a lambda, which is a function defined on a single line. FuncFormatter expects it to take 2 arguments. The first one is the tick_value, and we don't really care what the second one is, so I gave it the standard variable name of _ to show we don't care. The tick values are datetimes or timestamps. The way we get from the tick value to a datetime is by calling pylab.num2date.
You'll find that this second solution is just what you need. The key thing you needed to do was keep track of what your variable types were, and what variable types were needed where.

Trying to plot timedelta, but the yaxis is in 1e14, want format HH:MM:SS

I am plotting date time on the xaxis (which is actual dates) and then timedelta on the yaxis, which is actually time spans, or amount of time. Originally I was using date time for the yaxis, but I came across the usecase where the time values went over 24 hours, and then it broke the code. So instead I had to use timedelta in order to accommodate these values. But when I try to plot it using plot_date, the yaxis with the timedelta values comes out funny.
I have my information stored in a dataframe originally, and then change the values to a timedelta. This is the code I have to output this graph
import datetime as dt
import matplotlib.dates as mdates
import matplotlib
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import matplotlib as mpl
from matplotlib.backends.backend_pdf import PdfPages
plt.close('all')
#put data into dataframe
location='D:\CAT'
csvpath=location+('\metrics_summaryTEST.csv')
print csvpath
df=pd.read_csv(csvpath)
#setup plot/figure
media = set(df.mediaNumber.values)
num_plots = len(media)
ax = plt.gca()
pdfpath=location+('\metrics_graphs.pdf')
pp = PdfPages(pdfpath)
#declaring some variables
publishTimevals=np.zeros(len(df.publishTime.values),dtype="S20")
xdates=np.zeros(len(df.publishTime.values),dtype="S20")
ytimes=np.zeros(len(df.totalProcessTime.values),dtype="S8")
for f in sorted(media):
name = f
plt.figure(f)
plt.clf()
color = next(ax._get_lines.color_cycle)
#PROCESS PUBLISHTIME
publishTimevals= df.loc[df['mediaNumber']==f,['publishTime']]
xdates = map(lambda x: mpl.dates.date2num(dt.datetime.strptime(x, '%Y-%m-%d %H:%M')),publishTimevals.publishTime)
#PROCESS TOTALPROCESSTIME
totalProcessTimevals= df.loc[df['mediaNumber']==f,['totalProcessTime']]
ytimes = pd.to_timedelta(totalProcessTimevals.totalProcessTime)
plt.plot_date(xdates,ytimes,'o-',label='totalProcessTime',color=color)
print ytimes
plt.show()
#format the plot
plt.gcf().autofmt_xdate()
plt.xlabel('publishTime')
plt.ylabel('ProcessTime HH:MM:SS')
plt.legend(loc=8, bbox_to_anchor=(0.5,-0.3),ncol=3,prop={'size':9})
ax.grid('on')
plt.title('%s Processing Time' % (f))
plt.margins(0.05)
#plt.grid('on')
plt.minorticks_on()
plt.grid(which = 'minor', alpha = 0.3)
plt.grid(which = 'major', alpha = 0.7)
plt.show()
Could anyone point out what's going on here?

Plotting chart with epoch time x axis using matplotlib

I have the following code to plot a chart with matplotlib
#!/usr/bin/env python
import matplotlib.pyplot as plt
import urllib2
import json
req = urllib2.urlopen("http://localhost:17668/retrieval/data/getData.json? pv=LNLS:ANEL:corrente&donotchunk")
data = json.load(req)
secs = [x['secs'] for x in data[0]['data']]
vals = [x['val'] for x in data[0]['data']]
plt.plot(secs, vals)
plt.show()
The secs arrays is epoch time.
What I want is to plot the data in the x axis (secs) as a date (DD-MM-YYYY HH:MM:SS).
How can I do that?
To plot date-based data in matplotlib you must convert the data to the correct format.
One way is to first convert your data to datetime objects, for an epoch timestamp you should use datetime.datetime.fromtimestamp().
You must then convert the datetime objects to the right format for matplotlib, this can be handled using matplotlib.date.date2num.
Alternatively you can use matplotlib.dates.epoch2num and skip converting your date to datetime objects in the first place (while this will suit your use-case better initially, I would recommend trying to keep date based date in datetime objects as much as you can when working, it will save you a headache in the long run).
Once you have your data in the correct format you can plot it using plot_date.
Finally to format your x-axis as you wish you can use a matplotlib.dates.DateFormatter object to choose how your ticks will look.
import matplotlib.pyplot as plt
import matplotlib.dates as mdate
import numpy as np
# Generate some random data.
N = 40
now = 1398432160
raw = np.array([now + i*1000 for i in range(N)])
vals = np.sin(np.linspace(0,10,N))
# Convert to the correct format for matplotlib.
# mdate.epoch2num converts epoch timestamps to the right format for matplotlib
secs = mdate.epoch2num(raw)
fig, ax = plt.subplots()
# Plot the date using plot_date rather than plot
ax.plot_date(secs, vals)
# Choose your xtick format string
date_fmt = '%d-%m-%y %H:%M:%S'
# Use a DateFormatter to set the data to the correct format.
date_formatter = mdate.DateFormatter(date_fmt)
ax.xaxis.set_major_formatter(date_formatter)
# Sets the tick labels diagonal so they fit easier.
fig.autofmt_xdate()
plt.show()
You can change the ticks locations and formats on your plot:
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
import time
secs = [10928389,102928123,383827312,1238248395]
vals = [12,8,4,12]
plt.plot(secs,vals)
plt.gcf().autofmt_xdate()
plt.gca().xaxis.set_major_locator(mtick.FixedLocator(secs))
plt.gca().xaxis.set_major_formatter(
mtick.FuncFormatter(lambda pos,_: time.strftime("%d-%m-%Y %H:%M:%S",time.localtime(pos)))
)
plt.tight_layout()
plt.show()

Categories