How to fix 'RuntimeError: Locator ... exceeds Locator.MAXTICKS - matplotlib' - python-3.x

I'm plotting camapign data on a timeline, where only the time (rather than the date) sent is relevant hance the column contains only time data (imported from a csv)
It displays the various line graphs (spaghetti plot) however, when I want to add the labels to the x axis, I receive
RuntimeError: Locator attempting to generate 4473217 ticks from 30282.0 to 76878.0: exceeds Locator.MAXTICKS
I have 140 rows of data for this test file, the times are between 9:05 and 20:55 and my code is supposed to get a tick for every 15 minutes.
python: 3.7.1.final.0
python-bits: 64
OS: Windows
OS-release: 10
pandas: 0.23.4
matplotlib: 3.0.2
My actual code looks like:
import pandas
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from datetime import datetime
file_name = r'''C:\\Users\\A_B_testing.csv'''
df1 = pandas.read_csv(file_name, encoding='utf-8')
df_Campaign1 = df1[df1['DataSource ID'].str.contains('Campaign1')==True]
Campaign1_times = df_Campaign1['time sent'].tolist()
Campaign1_revenue = df_Campaign1['EstValue/sms'].tolist()
Campaign1_times = [datetime.strptime(slot,"%H:%M").time() for slot in Campaign1_times]
df_Campaign2 = df1[df1['DataSource ID'].str.contains('Campaign2')==True]
Campaign2_times = df_Campaign2['time sent'].tolist()
Campaign2_revenue = df_Campaign2['EstValue/sms'].tolist()
Campaign2_times = [datetime.strptime(slot,"%H:%M").time() for slot in Campaign2_times]
fig, ax = plt.subplots(1, 1, figsize=(16, 8))
xlocator = mdates.MinuteLocator(byminute=None, interval=15) # tick every 15 minutes
xformatter = mdates.DateFormatter('%H:%M')
ax.xaxis.set_major_locator(xlocator)
ax.xaxis.set_major_formatter(xformatter)
ax.minorticks_off()
plt.grid(True)
plt.plot(Campaign1_times, Campaign1_revenue, c = 'g', linewidth = 1)
plt.plot(Campaign2_times, Campaign2_revenue, c = 'y', linewidth = 2)
plt.show()
I tired to reduce the number of values to be plotted and it worked fine on a dummy set as follows:
from matplotlib import pyplot as plt
import matplotlib.dates as mdates
from matplotlib.dates import HourLocator, MinuteLocator, DateFormatter
from datetime import datetime
fig, ax = plt.subplots(1, figsize=(16, 6))
xlocator = MinuteLocator(interval=15)
xformatter = DateFormatter('%H:%M')
ax.xaxis.set_major_locator(xlocator)
ax.xaxis.set_major_formatter(xformatter)
ax.minorticks_off()
plt.grid(True, )
xvalues = ['9:05', '10:35' ,'12:05' ,'12:35', '13:05']
xvalues = [datetime.strptime(slot,"%H:%M") for slot in xvalues]
yvalues = [2.2, 2.4, 1.7, 3, 2]
zvalues = [3.2, 1.4, 1.8, 2.7, 2.2]
plt.plot(xvalues, yvalues, c = 'g')
plt.plot(xvalues, zvalues, c = 'b')
plt.show()
So I think that issue is related to the way I'm declaring the ticks, tried to find a relevant post here on but none has solved my problem. Can anyone please point me to the right direction? Thanks in advance.

I had a similar issue which got fixed by using datetime objects instead of time objects in the x-axis.
Similarly, in the code of the question, using the full datetime instead of just the time should fix the issue.
replace:
[datetime.strptime(slot,"%H:%M").time() for slot in ...
by:
[datetime.strptime(slot,"<full date format>") for slot in

Related

Get Seaborn legend location

I want to add comments under my legend. Here is a sample code doing what I want:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(0)
df1 = pd.DataFrame(np.random.normal(size=100))
df2 = pd.DataFrame(np.random.uniform(size=100))
fig,ax=plt.subplots()
sns.distplot(df1,ax=ax,label='foo')
sns.distplot(df2,ax=ax,label='bar')
hardlocy = 0.92
xmargin=0.02
xmin,xmax = ax.get_xlim()
xtxt=xmax-(xmax-xmin)*xmargin
leg = ax.legend()
plt.text(xtxt,hardlocy,"Comment",
horizontalalignment='right'
);
Result is:
As you can see, I rely on manual position setting, at least for y-axis. I would like to do it automatically.
As per this thread and this one, I have tried to access legend characteristics through p = leg.get_window_extent(), but I have obtain the following error message:
AttributeError: 'NoneType' object has no attribute 'points_to_pixels'
(which is very similar to this closed issue)
I run MacOS Catalina version 10.15.4 and I have performed a successful conda update --all a few minutes ago, without any result.
How can I automatically place my comments?
Thanks to #JohanC, from this question:
One needs to draw a figure for its legend to be worked out. Therefore, a working code here could be:
np.random.seed(0)
df1 = pd.DataFrame(np.random.normal(size=100))
df2 = pd.DataFrame(np.random.uniform(size=100))
fig,ax=plt.subplots()
sns.distplot(df1,ax=ax,label='foo')
sns.distplot(df2,ax=ax,label='bar')
ymargin=0.05
leg = ax.legend()
fig.canvas.draw()
bbox = leg.get_window_extent()
inv = ax.transData.inverted()
(xloc,yloc)=inv.transform((bbox.x1,bbox.y0))
ymin,ymax = ax.get_ylim()
yloc_margin=yloc-(ymax-ymin)*ymargin
ax.text(xloc,yloc_margin,"Comment",horizontalalignment='right')

matplotlib x-axis messed up [duplicate]

I have a series whose index is datetime that I wish to plot. I want to plot the values of the series on the y axis and the index of the series on the x axis. The Series looks as follows:
2014-01-01 7
2014-02-01 8
2014-03-01 9
2014-04-01 8
...
I generate a graph using plt.plot(series.index, series.values). But the graph looks like:
The problem is that I would like to have only year and month (yyyy-mm or 2016 March). However, the graph contains hours, minutes and seconds. How can I remove them so that I get my desired formatting?
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
# sample data
N = 30
drange = pd.date_range("2014-01", periods=N, freq="MS")
np.random.seed(365) # for a reproducible example of values
values = {'values':np.random.randint(1,20,size=N)}
df = pd.DataFrame(values, index=drange)
fig, ax = plt.subplots()
ax.plot(df.index, df.values)
ax.set_xticks(df.index)
# use formatters to specify major and minor ticks
ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m"))
ax.xaxis.set_minor_formatter(mdates.DateFormatter("%Y-%m"))
_ = plt.xticks(rotation=90)
You can try something like this:
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
df = pd.DataFrame({'values':np.random.randint(0,1000,36)},index=pd.date_range(start='2014-01-01',end='2016-12-31',freq='M'))
fig,ax1 = plt.subplots()
plt.plot(df.index,df.values)
monthyearFmt = mdates.DateFormatter('%Y %B')
ax1.xaxis.set_major_formatter(monthyearFmt)
_ = plt.xticks(rotation=90)
You should check out this native function of matplotlib:
fig.autofmt_xdate()
See examples on the source website Custom tick formatter

How to plot events with minute precision on hourly plots using matplotlib?

I have an hourly plot generated with matplotlib. I need to plot an event which goes for example, from 09:00 to 10:45. When I try to do it, using axvspan I obtain a bar from 9:00 to 10:00. How could I obtain the longer one?
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
import datetime as dt
import pandas as pd
now_date = dt.datetime(2018,10,1,9)
d_tw_ini = now_date - dt.timedelta(hours = 1)
d_tw_fin = now_date + dt.timedelta(hours = 3)
dts = pd.date_range(start=d_tw_ini, end=d_tw_fin, freq='1H', name='ini', closed='left')
data=pd.DataFrame({'val':[0.5,0.4,0.7,0.9]})
ev1=[dt.datetime(2018,10,1,9,5),dt.datetime(2018,10,1,10,50)]
data['t']=dts.values
data.set_index('t',inplace=True)
fig = plt.figure()
gs = GridSpec(1, 1)
ax_1 = fig.add_subplot(gs[0, 0])
data.plot(ax=ax_1, y='val')
ax_1.axvspan(ev1[0],ev1[1], alpha=0.3, color= 'red')
Result
Juan, it looks when you used pandas to plot, the hourly indexing seems to cause issues with how axvspan gets plotted.
I replaced
data.plot(ax=ax_1, y='val')
with
ax_1.plot(data.index, data['val'])
which generates the image below, but unfortunately you lose the automated x-axis formatting.
Adding the two lines below will result in the same date formatting as your example.
ax_1.set_xticks([x for x in data.index])
ax_1.set_xticklabels([str(x)[11:16] for x in data.index])
Below is the full code to produce the above plot.
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
import datetime as dt
import pandas as pd
now_date = dt.datetime(2018,10,1,9)
d_tw_ini = now_date - dt.timedelta(hours = 1)
d_tw_fin = now_date + dt.timedelta(hours = 3)
dts = pd.date_range(start=d_tw_ini, end=d_tw_fin, freq='1h', name='ini',
closed='left')
data=pd.DataFrame({'val':[0.5,0.4,0.7,0.9]})
ev1=[dt.datetime(2018,10,1,9,5,0),dt.datetime(2018,10,1,10,50,0)]
data['t']=dts.values
data.set_index('t',inplace=True)
fig = plt.figure()
gs = GridSpec(1, 1)
ax_1 = fig.add_subplot(gs[0, 0])
# modified section below
ax_1.plot(data.index, data['val'])
ax_1.axvspan(ev1[0],ev1[1], alpha=0.3, color= 'red')
ax_1.set_xticks([x for x in data.index])
ax_1.set_xticklabels([str(x)[11:16] for x in data.index])
plt.show()

Tick labels only displayed in one subplot

Need to display custom shared x-axis tick labels on both subplots, using two datasets with different dates.
from pandas import DataFrame, date_range, Timedelta
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.dates as mdates
#Dataset 1
rng1 = date_range(start='01-01-2015', periods=5, freq='1M')
df1 = DataFrame({'y':np.random.normal(size=len(rng1))}, index=rng1)
y1 = df1['y']
#Dataset 2
rng2 = date_range(start='01-01-2015', periods=5, freq='2M')
df2 = DataFrame({'y':np.random.normal(size=len(rng2))}, index=rng2)
y2 = df2['y']
#Figure
fig,(ax1,ax2) = plt.subplots(2,1,sharex=True)
y1.plot(ax=ax1)
y2.plot(ax=ax2)
plt.xticks(rotation=30)
ax1.xaxis.set_minor_formatter(plt.NullFormatter())
ax2.xaxis.set_major_locator(mdates.MonthLocator(interval=1))
ax2.xaxis.set_major_formatter(mdates.DateFormatter('%D'))
plt.show()
(Above) Creates figure without x-axis labels on the upper subplot
I expect adding the below code to display the same x-axis labels to the upper subplot, but they are not showing up. What am I doing wrong?
ax1.xaxis.set_major_locator(mdates.MonthLocator(interval=1))
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%D'))
Setting sharex=False does not work because the dates are different for each dataset.
First of all, if you want to use matplotlib.dates locators and formatters on a plot created via pandas you should use the x_compat=True argument in your plot, otherwise pandas may scale the axis rather arbitrarily.
Then '%D' is not a valid format string. Maybe you mean '%b'?
Now there are two options.
Use sharex=False, set your locators and formatters to both axes, and finally set the limits of the one plot to the limits of the other. In this case since the lower plot comprises a larger range,
ax1.set_xlim(ax2.get_xlim())
The other option is to use sharex=True and turn the labels visible again.
plt.setp(ax1.get_xticklabels(), visible=True)
Unfortunately this option is broken on the newest matplotlib version. I just opened a bug report about it.
Full code for the first option (since the second one is not working):
from pandas import DataFrame, date_range, Timedelta
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.dates as mdates
#Dataset 1
rng1 = date_range(start='2015-01-01', periods=5, freq='1M')
df1 = DataFrame({'y':np.random.normal(size=len(rng1))}, index=rng1)
y1 = df1['y']
#Dataset 2
rng2 = date_range(start='2015-01-01', periods=5, freq='2M')
df2 = DataFrame({'y':np.random.normal(size=len(rng2))}, index=rng2)
y2 = df2['y']
#Figure
fig,(ax1,ax2) = plt.subplots(2,1,sharex=False)
y1.plot(ax=ax1, x_compat=True)
y2.plot(ax=ax2, x_compat=True)
plt.xticks(rotation=30)
ax1.xaxis.set_major_locator(mdates.MonthLocator(interval=1))
ax1.xaxis.set_minor_locator(plt.NullLocator())
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%b'))
ax2.xaxis.set_major_locator(mdates.MonthLocator(interval=1))
ax2.xaxis.set_minor_locator(plt.NullLocator())
ax2.xaxis.set_major_formatter(mdates.DateFormatter('%b'))
ax1.set_xlim(ax2.get_xlim())
plt.show()
This seemed to work for me, just following the shared_axis_demo
fig = plt.figure()
ax1 = plt.subplot(211)
_ = plt.plot(y1)
plt.setp(ax1.get_xticklabels(), visible=True)
ax2 = plt.subplot(212, sharex=ax1)
_ = plt.plot(y2)
plt.show()

Timeserie datetick problems when using pandas.DataFrame.plot method

I just discovered something really strange when using plot method of pandas.DataFrame. I am using pandas 0.19.1. Here is my MWE:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import pandas as pd
t = pd.date_range('1990-01-01', '1990-01-08', freq='1H')
x = pd.DataFrame(np.random.rand(len(t)), index=t)
fig, axe = plt.subplots()
x.plot(ax=axe)
plt.show(axe)
xt = axe.get_xticks()
When I try to format my xticklabels I get strange beahviours, then I insepcted objects to understand and I have found the following:
t[-1] - t[0] = Timedelta('7 days 00:00:00'), confirming the DateTimeIndex is what I expect;
xt = [175320, 175488], xticks are integers but they are not equals to a number of days since epoch (I do not have any idea about what it is);
xt[-1] - xt[0] = 168 there are more like index, there is the same amount that len(x) = 169.
This explains why I cannot succed to format my axe using:
axe.xaxis.set_major_locator(mdates.HourLocator(byhour=(0,6,12,18)))
axe.xaxis.set_major_formatter(mdates.DateFormatter("%a %H:%M"))
The first raise an error that there is to many ticks to generate
The second show that my first tick is Fri 00:00 but it should be Mon 00:00 (in fact matplotlib assumes the first tick to be 0481-01-03 00:00, oops this is where my bug is).
It looks like there is some incompatibility between pandas and matplotlib integer to date conversion but I cannot find out how to fix this issue.
If I run instead:
fig, axe = plt.subplots()
axe.plot(x)
axe.xaxis.set_major_formatter(mdates.DateFormatter("%a %H:%M"))
plt.show(axe)
xt = axe.get_xticks()
Everything works as expected but I miss all cool features from pandas.DataFrame.plot method such as curve labeling, etc. And here xt = [726468. 726475.].
How can I properly format my ticks using pandas.DataFrame.plot method instead of axe.plot and avoiding this issue?
Update
The problem seems to be about origin and scale (units) of underlying numbers for date representation. Anyway I cannot control it, even by forcing it to the correct type:
t = pd.date_range('1990-01-01', '1990-01-08', freq='1H', origin='unix', units='D')
There is a discrepancy between matplotlib and pandas representation. And I could not find any documentation of this problem.
Is this what you are going for? Note I shortened the date_range to make it easier to see the labels.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as dates
t = pd.date_range('1990-01-01', '1990-01-04', freq='1H')
x = pd.DataFrame(np.random.rand(len(t)), index=t)
# resample the df to get the index at 6-hour intervals
l = x.resample('6H').first().index
# set the ticks when you plot. this appears to position them, but not set the label
ax = x.plot(xticks=l)
# set the display value of the tick labels
ax.set_xticklabels(l.strftime("%a %H:%M"))
# hide the labels from the initial pandas plot
ax.set_xticklabels([], minor=True)
# make pretty
ax.get_figure().autofmt_xdate()
plt.show()

Resources