I'm plotting a time-series with MatplotLib. The time series values, x-axis, have the resolution '%d/%m/%y %H:%M', but only month and year are indicated in the mouse over.
My question is how can one override the default and set what datetime items that should be shown during mouse over?
My preference is to show at least day, month, and year
.....................................................................
For example, this is a screenshot where I did a mouse-over for one of the points:
As you can see the (bottom LHS corner) x value gives a date which indicated only month and year.
When zoomed in, day, month and year are shown:
The values that are shown on mouse-over are controlled by the ax.format_coord method, which is meant to be monkey-patched by a user-supplied method when customization is needed.
For example:
import matplotlib.pyplot as plt
def formatter(x, y):
return '{:0.0f} rainbows, {:0.0f} unicorns'.format(10*x, 10*y)
fig, ax = plt.subplots()
ax.format_coord = formatter
plt.show()
There are also the ax.format_xdata and ax.format_ydata which the default ax.format_coord calls, to allow easier customization of only the x or y components.
For example:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.format_xdata = '{:0.1f}meters'.format
plt.show()
Note that I passed in the string's format method, but it could have just as easily been a lambda or any arbitrary method that expects a single numeric argument.
By default, format_xdata and format_ydata use the axis's major tick formatter, which is why you're getting day-level resolution for your date axis.
However, you'll also need to convert matplotlib's internal numeric date format back to a "proper" datetime object. Therefore, you can control your formatting similar to the following:
import datetime as dt
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
fig, ax = plt.subplots()
ax.xaxis_date()
ax.set_xlim(dt.datetime(2015, 1, 1), dt.datetime(2015, 6, 1))
ax.format_xdata = lambda d: mdates.num2date(d).strftime('%d/%m/%y %H:%M')
plt.show()
Related
I am having an issue trying to get my date ticks rotated in matplotlib. A small sample program is below. If I try to rotate the ticks at the end, the ticks do not get rotated. If I try to rotate the ticks as shown under the comment 'crashes', then matplot lib crashes.
This only happens if the x-values are dates. If I replaces the variable dates with the variable t in the call to avail_plot, the xticks(rotation=70) call works just fine inside avail_plot.
Any ideas?
import numpy as np
import matplotlib.pyplot as plt
import datetime as dt
def avail_plot(ax, x, y, label, lcolor):
ax.plot(x,y,'b')
ax.set_ylabel(label, rotation='horizontal', color=lcolor)
ax.get_yaxis().set_ticks([])
#crashes
#plt.xticks(rotation=70)
ax2 = ax.twinx()
ax2.plot(x, [1 for a in y], 'b')
ax2.get_yaxis().set_ticks([])
ax2.set_ylabel('testing')
f, axs = plt.subplots(2, sharex=True, sharey=True)
t = np.arange(0.01, 5, 1)
s1 = np.exp(t)
start = dt.datetime.now()
dates=[]
for val in t:
next_val = start + dt.timedelta(0,val)
dates.append(next_val)
start = next_val
avail_plot(axs[0], dates, s1, 'testing', 'green')
avail_plot(axs[1], dates, s1, 'testing2', 'red')
plt.subplots_adjust(hspace=0, bottom=0.3)
plt.yticks([0.5,],("",""))
#doesn't crash, but does not rotate the xticks
#plt.xticks(rotation=70)
plt.show()
If you prefer a non-object-oriented approach, move plt.xticks(rotation=70) to right before the two avail_plot calls, eg
plt.xticks(rotation=70)
avail_plot(axs[0], dates, s1, 'testing', 'green')
avail_plot(axs[1], dates, s1, 'testing2', 'red')
This sets the rotation property before setting up the labels. Since you have two axes here, plt.xticks gets confused after you've made the two plots. At the point when plt.xticks doesn't do anything, plt.gca() does not give you the axes you want to modify, and so plt.xticks, which acts on the current axes, is not going to work.
For an object-oriented approach not using plt.xticks, you can use
plt.setp( axs[1].xaxis.get_majorticklabels(), rotation=70 )
after the two avail_plot calls. This sets the rotation on the correct axes specifically.
Solution works for matplotlib 2.1+
There exists an axes method tick_params that can change tick properties. It also exists as an axis method as set_tick_params
ax.tick_params(axis='x', rotation=45)
Or
ax.xaxis.set_tick_params(rotation=45)
As a side note, the current solution mixes the stateful interface (using pyplot) with the object-oriented interface by using the command plt.xticks(rotation=70). Since the code in the question uses the object-oriented approach, it's best to stick to that approach throughout. The solution does give a good explicit solution with plt.setp( axs[1].xaxis.get_majorticklabels(), rotation=70 )
An easy solution which avoids looping over the ticklabes is to just use
fig.autofmt_xdate()
This command automatically rotates the xaxis labels and adjusts their position. The default values are a rotation angle 30° and horizontal alignment "right". But they can be changed in the function call
fig.autofmt_xdate(bottom=0.2, rotation=30, ha='right')
The additional bottom argument is equivalent to setting plt.subplots_adjust(bottom=bottom), which allows to set the bottom axes padding to a larger value to host the rotated ticklabels.
So basically here you have all the settings you need to have a nice date axis in a single command.
A good example can be found on the matplotlib page.
Another way to applyhorizontalalignment and rotation to each tick label is doing a for loop over the tick labels you want to change:
import numpy as np
import matplotlib.pyplot as plt
import datetime as dt
now = dt.datetime.now()
hours = [now + dt.timedelta(minutes=x) for x in range(0,24*60,10)]
days = [now + dt.timedelta(days=x) for x in np.arange(0,30,1/4.)]
hours_value = np.random.random(len(hours))
days_value = np.random.random(len(days))
fig, axs = plt.subplots(2)
fig.subplots_adjust(hspace=0.75)
axs[0].plot(hours,hours_value)
axs[1].plot(days,days_value)
for label in axs[0].get_xmajorticklabels() + axs[1].get_xmajorticklabels():
label.set_rotation(30)
label.set_horizontalalignment("right")
And here is an example if you want to control the location of major and minor ticks:
import numpy as np
import matplotlib.pyplot as plt
import datetime as dt
fig, axs = plt.subplots(2)
fig.subplots_adjust(hspace=0.75)
now = dt.datetime.now()
hours = [now + dt.timedelta(minutes=x) for x in range(0,24*60,10)]
days = [now + dt.timedelta(days=x) for x in np.arange(0,30,1/4.)]
axs[0].plot(hours,np.random.random(len(hours)))
x_major_lct = mpl.dates.AutoDateLocator(minticks=2,maxticks=10, interval_multiples=True)
x_minor_lct = matplotlib.dates.HourLocator(byhour = range(0,25,1))
x_fmt = matplotlib.dates.AutoDateFormatter(x_major_lct)
axs[0].xaxis.set_major_locator(x_major_lct)
axs[0].xaxis.set_minor_locator(x_minor_lct)
axs[0].xaxis.set_major_formatter(x_fmt)
axs[0].set_xlabel("minor ticks set to every hour, major ticks start with 00:00")
axs[1].plot(days,np.random.random(len(days)))
x_major_lct = mpl.dates.AutoDateLocator(minticks=2,maxticks=10, interval_multiples=True)
x_minor_lct = matplotlib.dates.DayLocator(bymonthday = range(0,32,1))
x_fmt = matplotlib.dates.AutoDateFormatter(x_major_lct)
axs[1].xaxis.set_major_locator(x_major_lct)
axs[1].xaxis.set_minor_locator(x_minor_lct)
axs[1].xaxis.set_major_formatter(x_fmt)
axs[1].set_xlabel("minor ticks set to every day, major ticks show first day of month")
for label in axs[0].get_xmajorticklabels() + axs[1].get_xmajorticklabels():
label.set_rotation(30)
label.set_horizontalalignment("right")
Simply use
ax.set_xticklabels(label_list, rotation=45)
I am clearly late but there is an official example which uses
plt.setp(ax.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor")
to rotate the labels while keeping them correctly aligned with the ticks, which is both clean and easy.
Ref: https://matplotlib.org/stable/gallery/images_contours_and_fields/image_annotated_heatmap.html
I'm having with my xticks on my plot.
I have an hh:mm:ss format data on my x vector, but the xticks label are just eating up space on my x vector.
I'm trying to use only major xticks which would show the x vector label on 5 minutes basis.
but, the label not showing correctly.
right now this is the code that i wrote:
# -*- coding: utf-8 -*-
from os import listdir
from os.path import isfile, join
import pandas as pd
from Common import common as comm
from matplotlib.font_manager import FontProperties
import matplotlib.pyplot as plt
fp = FontProperties(fname="../templates/fonts/msgothic.ttc")
config = comm.configRead()
commonConf = comm.getCommonConfig(config)
peopleBhvConf = comm.getPeopleBhvConf(config)
files = [f for f in listdir(commonConf['resultFilePath']) if isfile(join(commonConf['resultFilePath'], f))]
waitTimeGraphInput = [s for s in files if peopleBhvConf['resultFileName'] in s]
waitTimeGraphFile = commonConf['inputFilePath'] + waitTimeGraphInput[0]
waitTimeGraph = pd.read_csv(waitTimeGraphFile)
# Create data
N = len(waitTimeGraph.index)
x = waitTimeGraph['ホール入時間']
y = waitTimeGraph['滞留時間(出-入sec)']
xTicks = pd.date_range(min(x), max(x), freq="5min")
fig, ax = plt.subplots()
ax.scatter(x, y)
ax.set_xticklabels(xTicks, rotation='vertical')
plt.axhline(y=100, xmin=min(x), xmax=max(x), linewidth=2, color = 'red')
plt.setp(ax.get_xticklabels(), visible=True, rotation=30, ha='right')
plt.savefig(commonConf['resultFilePath'] + '1人1人の待ち時間分布.png')
plt.show()
and this is the result:
as you can see, the labels are still being printed only on the front of my plotting.
I'm expecting it would being printed on my major xticks position only.
The problem
If I understand correctly what is going on, xTicks array is shorter than x, am I right? If so, this is the issue.
I don't see in your code where you set the tick position, but I guess you are showing all of them, one per each element of x. But since you set the tick labels manually with ax.set_xticklabels(xTicks, rotation='vertical'), matplotlib has no way to know at which ticks those labels should go, hence it fills the first available ticks, and if there are more ticks, they are left without labels.
If you were able to read the labes, you would see that the written dates do not correspond to the labelled positions on the axis.
How to fix it
The general rule, be sure when you set tick labels manually, that the array containing the label has the same length of the array of the ticks. Add empty strings for the ticks where you do not want to have a labels.
However, since you spoke of major ticks and minor ticks, I show you how to set them in your case, where you have dates on the x axis.
Drop the xTicks, is not needed. Don't set the tick labels manually, hence don't use ax.set_xticklabels().
Your code should be:
fig, ax = plt.subplots()
ax.scatter(x, y)
plt.axhline(y=100, xmin=min(x), xmax=max(x), linewidth=2, color = 'red')
ax.xaxis.set_major_locator(MinuteLocator(interval=5))
ax.xaxis.set_minor_locator(MinuteLocator(interval=1))
ax.xaxis.set_major_formatter(DateFormatter('%H:%M:%S'))
plt.setp(ax.get_xticklabels(), visible=True, rotation=30, ha='right')
plt.savefig(commonConf['resultFilePath'] + '1人1人の待ち時間分布.png')
Remember to import the locator and formatter:
from matplotlib.dates import MinuteLocator, DateFormatter
A brief explanation: MinuteLocator finds each minute interval in your x axis and place a tick. The parameter interval allows you to set a tick each N minutes. So in the above code a major tick is placed each 5 minutes, a minor tick each minute.
DateFormatter simply format the date accordingly to the string (here I choose the format hour, minute, second). Note that no formatter has been set for minor ticks, so by default matplotlib uses the null formatter (no labels for minor ticks).
Here the documentation on the dates module of matplotlib.
To give you an idea of the result, here is an image I created using the code above with random data (just look at the x axis).
I am new to matplotlib. And I copied code for simple pyqt-matplotlib example from here, and changed plot() of class PlotCanvas to make a Value-Time chart.
def plot(self):
dates = mdates.drange(dt.datetime(2010, 1, 1), dt.datetime(2010,1,2),
dt.timedelta(minutes=10))
y_val = [random.random() for i in range(len(dates))]
ax = self.figure.add_subplot(111)
ax.plot(dates, y_val)
use_major_formatter = True
if use_major_formatter:
ax.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d %H:%M'))
else:
ax.fmt_xdata = DateFormatter('%%m-%d %H:%M')
self.figure.autofmt_xdate()
ax.set_title('PyQt Matplotlib Example')
self.draw()
Above code requires theseimports:
import matplotlib.dates as mdates
import datetime as dt
from matplotlib.dates import DateFormatter
I tried set_major_formatter() and fmt_xdata. And I've found fmt_xdata doesn't work.
But why?
What's the difference with set_major_formatter() and fmt_xdata?
Notice: In the above code, I added if statement to test two methods.
ax.xaxis.set_major_formatter() and ax.fmt_xdata are entirely different things.
The first, ax.xaxis.set_major_formatter(), sets the formatter for the x axis in your plot. This is useful for showing the ticklabels in a required format.
The second, ax.fmt_xdata is a function that takes in a data coordinate and formats it. It will by default just return the same as the major formatter. It is used internally to format the numbers in the GUI that are shown when moving the mouse around.
You can replace the ax.fmt_xdata method by your own custom callable in case you want the GUI to show different numbers/strings than your axis.
I'm building a Seaborn barplot. The x-axis are dates, and the y-axis are integers.
I'd like to format major/minor ticks for the dates. I'd like Mondays' ticks to be bold and a different color (ie, "major ticks"), with the rest of the week less bold.
I have not been able to get major and minor tick formatting on the x-axis to work with Seaborn barplots. I'm stumped, and thus turning here for help.
I'm starting with the stackoverflow example that answered this question: Pandas timeseries plot setting x-axis major and minor ticks and labels
If I do a simple modification it to use a Seaborn barplot and I lose my X-axis ticks:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as dates
import seaborn as sns
idx = pd.date_range('2011-05-01', '2011-07-01')
s = pd.Series(np.random.randn(len(idx)), index=idx)
###########################################
## Swap out these two lines of code:
#fig, ax = plt.subplots()
#ax.plot_date(idx.to_pydatetime(), s, 'v-')
## with this one
ax = sns.barplot(idx.to_pydatetime(), s)
###########################################
ax.xaxis.set_minor_locator(dates.WeekdayLocator(byweekday=(1),
interval=1))
ax.xaxis.set_minor_formatter(dates.DateFormatter('%d\n%a'))
ax.xaxis.grid(True, which="minor")
ax.yaxis.grid()
ax.xaxis.set_major_locator(dates.MonthLocator())
ax.xaxis.set_major_formatter(dates.DateFormatter('\n\n\n%b\n%Y'))
plt.tight_layout()
## save the result to a png instead of plotting to screen:
myFigure = plt.gcf()
myFigure.autofmt_xdate()
myFigure.set_size_inches(11,3.8)
plt.title('Example Chart', loc='center')
plt.savefig('/tmp/chartexample.png', format='png', bbox_inches='tight')
I've tried a variety of approaches but something in Seaborn seems to be overriding or undoing any attempts at major and minor axis formatting that I've managed to cook up yet beyond some simple styling for all ticks when I use set_xticklabels().
I can sort of get formatting on just the major ticks by using MultipleLocator(), but I can't get any formatting on the minor ticks.
I've also experimented with myFigure.autofmt_xdate() to see if it would help, but it doesn't seem to like mixed major & minor ticks on the same axis either.
I came across this while trying to solve the same problem. Based on the useful pointer from #mwaskom (that categorical plots like boxplots lose their structure and just become date-named categories) and ended up doing the location and formatting in Python as so:
from datetime import datetime
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as dates
import seaborn as sns
idx = pd.date_range('2011-05-01', '2011-07-01')
s = pd.Series(np.random.randn(len(idx)), index=idx)
fig, ax = plt.subplots(figsize = (12,6))
ax = sns.barplot(idx.to_pydatetime(), s, ax = ax)
major_ticks = []
major_tick_labels = []
minor_ticks = []
minor_tick_labels = []
for loc, label in zip(ax.get_xticks(), ax.get_xticklabels()):
when = datetime.strptime(label.get_text(), '%Y-%m-%d %H:%M:%S')
if when.day == 1:
major_ticks.append(loc)
major_tick_labels.append(when.strftime("\n\n\n%b\n%Y"))
else:
minor_ticks.append(loc)
if when.weekday() == 0:
minor_tick_labels.append(when.strftime("%d\n%a"))
else:
minor_tick_labels.append(when.strftime("%d"))
ax.set_xticks(major_ticks)
ax.set_xticklabels(major_tick_labels)
ax.set_xticks(minor_ticks, minor=True)
ax.set_xticklabels(minor_tick_labels, minor=True)
Of course, you don't have to set the ticks based on parsing the labels which were installed from the data, if it's easier to start with the source data and just keep the indices aligned, but I prefer to have a single source of truth.
You can also mess with font weight, rotation, etc, on individual labels by getting the Text objects for the relevant label and calling set_ methods on it.
I have an index array (x) of dates (datetime objects) and an array of actual values (y: bond prices). Doing (in iPython):
plot(x,y)
Produces a perfectly fine time series graph with the x axis labeled with the dates. No problem so far. But I want to add text on certain dates. For example, at 2009-10-31 I wish to display the text "Event 1" with an arrow pointing to the y value at that date.
I have read trough the Matplotlib documentation on text() and annotate() to no avail. It only covers standard numbered x-axises, and I can´t infer how to work those examples on my problem.
Thank you
Matplotlib uses an internal floating point format for dates.
You just need to convert your date to that format (using matplotlib.dates.date2num or matplotlib.dates.datestr2num) and then use annotate as usual.
As a somewhat excessively fancy example:
import datetime as dt
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
x = [dt.datetime(2009, 05, 01), dt.datetime(2010, 06, 01),
dt.datetime(2011, 04, 01), dt.datetime(2012, 06, 01)]
y = [1, 3, 2, 5]
fig, ax = plt.subplots()
ax.plot_date(x, y, linestyle='--')
ax.annotate('Test', (mdates.date2num(x[1]), y[1]), xytext=(15, 15),
textcoords='offset points', arrowprops=dict(arrowstyle='-|>'))
fig.autofmt_xdate()
plt.show()