Problem with using major xticks on python matplotlib - python

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).

Related

Setting xticklabels and x-axis limits in a bar plot with matplotlib

I want to plot a bar graph with a variable amount of values along the x-axis. For the data, I have a set of labels which I want to show on the x-axis under the bars. I also want the x-axis limits to start at -1, since otherwise, only half of the first bar at index 0 would be visible. I've tried multiple alternatives for achieving that, none of them worked, because the xticklabels are always one or more off. And IF they work for a given set of data, with another set of data (with more or less bars) it does not work again. See minimum code example below
from matplotlib import pyplot as plt
from matplotlib import ticker
import numpy as np
randData = np.random.rand(100)
xValues = np.linspace(0, len(randData)-1, num=len(randData))
labels = []
for i in range(len(randData)):
labels.append('label' + str(i))
fig, ax = plt.subplots()
ax.bar(np.linspace(0, len(randData)-1, num=len(randData)), randData)
ax.xaxis.set_major_locator(ticker.MultipleLocator(1))
# Alternative 1
# Use an empty string for index -1, set labels, then set new xlim
labels.insert(0, '')
ax.set_xticklabels(labels, size='x-small', rotation=90)
plt.xlim(-1, len(randData))
# Alternative 2
# Use an empty string for index -1, set new xlim, then set labels
labels.insert(0, '')
plt.xlim(-1, len(randData))
ax.set_xticklabels(labels, size='x-small', rotation=90)
# Alternative 3
# Setting limits with ax.set_xlim
ax.set_xticklabels(labels, size='x-small', rotation=90)
ax.set_xlim([-1, len(randData)])
# Alternative 4
# Setting limits with plt.xlim
ax.set_xticklabels(labels, size='x-small', rotation=90)
plt.xlim(-1, len(randData))
plt.show()
None of the variants worked so far. One part of the problem is that the pyplot automatically sets its xlimits depending on the amount of bar graphs (sometimes it starts at -1, with more values it might sometimes start at -4).
One of the faulty results is shown below:
Any help would be appreciated.
P.S.: If I may, I'd like to add a little side question: How can I remove the Warning "UserWarning: FixedFormatter should only be used together with FixedLocator" when setting the xticklabels? Nothing from this answer worked for me.

plotting sales and profit on one chart in python [duplicate]

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

Multiple x labels on Pyplot

Below is my code for a line graph. I would like another x label under the current one (so I can show the days of the week).
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns;sns.set()
sns.set()
data = pd.read_csv("123.csv")
data['DAY']=["01","02","03","04","05","06","07","08","09","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31"]
plt.figure(figsize=(15,8))
plt.plot('DAY','SWST',data=data,linewidth=2,color="k")
plt.plot('DAY','WMID',data=data,linewidth=2,color="m")
plt.xlabel('DAY', fontsize=20)
plt.ylabel('VOLUME', fontsize=20)
plt.legend()
EDIT: After following the documentation, I have 2 issues. The scale has changed from 31 to 16, and the days of the week do not line up with the day number.
data['DAY']=["01","02","03","04","05","06","07","08","09","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31"]
tick_labels=['1','\n\nThu','2','\n\nFri','3','\n\nSat','4','\n\nSun','5','\n\nMon','6','\n\nTue','7','\n\nWed','8','\n\nThu','9','\n\nFri','10','\n\nSat','11','\n\nSun','12','\n\nMon','13','\n\nTue','14','\n\nWed','15','\n\nThu','16','\n\nFri','17','\n\nSat','18','\n\nSun','19','\n\nMon','20','\n\nTue','21','\n\nWed','22','\n\nThu','23','\n\nFri','24','\n\nSat','25','\n\nSun','26','\n\nMon','27','\n\nTue','28','\n\nWed','29','\n\nThu','30','\n\nFri','31','\n\nSat']
tick_locations = np.arange(31)
plt.figure(figsize=(15,8))
plt.xticks(tick_locations, tick_labels)
plt.plot('DAY','SWST',data=data,linewidth=2,color="k")
plt.plot('DAY','WMID',data=data,linewidth=2,color="m")
plt.xlabel('DAY', fontsize=20)
plt.ylabel('VOLUME', fontsize=20)
plt.legend()
plt.show()
The pyplot function you are looking for is plt.xticks(). This is essentially a combination of ax.set_xticks() and ax.set_xticklabels()
From the documentation:
Parameters:
ticks : array_like
A list of positions at which ticks should be placed. You can pass an
empty list to disable xticks.
labels:
array_like, optional A list of explicit labels to place at the given
locs.
You would want something like the below code. Note you should probably explicitly set the tick locations as well as the labels to avoid setting labels in the wrong positions:
tick_labels = ['1','\n\nThu','2',..., '31','\n\nSat')
plt.xticks(tick_locations, tick_labels)
Note that the object-orientated API (i.e. using ax.) allows for more customisable plots.
Update
After the edit, I see that the labels you want to go below are part of the same list. Therefore your label list actually has a length of 62. So you need to join every 2 elements of your list together:
tick_labels=['1','\n\nThu','2','\n\nFri','3','\n\nSat','4','\n\nSun','5','\n\nMon','6','\n\nTue','7','\n\nWed','8',
'\n\nThu','9','\n\nFri','10','\n\nSat','11','\n\nSun','12','\n\nMon','13','\n\nTue','14','\n\nWed','15',
'\n\nThu','16','\n\nFri','17','\n\nSat','18','\n\nSun','19','\n\nMon','20','\n\nTue','21','\n\nWed','22',
'\n\nThu','23','\n\nFri','24','\n\nSat','25','\n\nSun','26','\n\nMon','27','\n\nTue','28','\n\nWed','29',
'\n\nThu','30','\n\nFri','31','\n\nSat']
tick_locations = np.arange(31)
new_labels = [ ''.join(x) for x in zip(tick_labels[0::2], tick_labels[1::2]) ]
plt.figure(figsize=(15, 8))
plt.xticks(tick_locations, new_labels)
plt.show()
Never use ax.set_xticklabels without setting the locations of the ticks as well. This can be done via ax.set_xticks.
ax.set_xticks(...)
ax.set_xticklabels(...)
Of course you may do the same with pyplot
ax = plt.gca()
ax.set_xticks(...)
ax.set_xticklabels(...)

Matplotlib: How to get same "base" and "offset" parameters for axis ticks and axis tick labels

I want to plot a series of values against a date range in matplotlib. I changed the tick base parameter to 7, to get one tick at the beginning of every week (plticker.IndexLocator, base = 7). The problem is that the set_xticklabels function does not accept a base parameter. As a result, the second tick (representing day 8 on the beginning of week 2) is labelled with day 2 from my date range list, and not with day 8 as it should be (see picture).
How to give set_xticklabelsa base parameter?
Here is the code:
my_data = pd.read_csv("%r_filename_%s_%s_%d_%d.csv" % (num1, num2, num3, num4, num5), dayfirst=True)
my_data.plot(ax=ax1, color='r', lw=2.)
loc = plticker.IndexLocator(base=7, offset = 0) # this locator puts ticks at regular intervals
ax1.set_xticklabels(my_data.Date, rotation=45, rotation_mode='anchor', ha='right') # this defines the tick labels
ax1.xaxis.set_major_locator(loc)
Here is the plot:
Plot
Many thanks - your solution perfectly works. For the case that other people run into the same issue in the future: i have implemented the above-mentioned solution but also added some code so that the tick labels keep the desired rotation and also align (with their left end) to the respective tick. May not be pythonic, may not be best-practice, but it works
x_fmt = mpl.ticker.IndexFormatter(x)
ax.set_xticklabels(my_data.Date, rotation=-45)
ax.tick_params(axis='x', pad=10)
ax.xaxis.set_major_formatter(x_fmt)
labels = my_data.Date
for tick in ax.xaxis.get_majorticklabels():
tick.set_horizontalalignment("left")
The reason your ticklabels went bad is that setting manual ticklabels decouples the labels from your data. The proper approach is to use a Formatter according to your needs. Since you have a list of ticklabels for each data point, you can use an IndexFormatter. It seems to be undocumented online, but it has a help:
class IndexFormatter(Formatter)
| format the position x to the nearest i-th label where i=int(x+0.5)
| ...
| __init__(self, labels)
| ...
So you just have to pass your list of dates to IndexFormatter. With a minimal, pandas-independent example (with numpy only for generating dummy data):
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
# create dummy data
x = ['str{}'.format(k) for k in range(20)]
y = np.random.rand(len(x))
# create an IndexFormatter with labels x
x_fmt = mpl.ticker.IndexFormatter(x)
fig,ax = plt.subplots()
ax.plot(y)
# set our IndexFormatter to be responsible for major ticks
ax.xaxis.set_major_formatter(x_fmt)
This should keep your data and labels paired even when tick positions change:
I noticed you also set the rotation of the ticklabels in the call to set_xticklabels, you would lose this now. I suggest using fig.autofmt_xdate to do this instead, it seems to be designed exactly for this purpose, without messing with your ticklabel data.

matplotlib: draw major tick labels under minor labels

This seems like it should be easy - but I can't see how to do it:
I have a plot with time on the X-axis. I want to set two sets of ticks, minor ticks showing the hour of the day and major ticks showing the day/month. So I do this:
# set date ticks to something sensible:
xax = ax.get_xaxis()
xax.set_major_locator(dates.DayLocator())
xax.set_major_formatter(dates.DateFormatter('%d/%b'))
xax.set_minor_locator(dates.HourLocator(byhour=range(0,24,3)))
xax.set_minor_formatter(dates.DateFormatter('%H'))
This labels the ticks ok, but the major tick labels (day/month) are drawn on top of the minor tick labels:
How do I force the major tick labels to get plotted below the minor ones? I tried putting newline escape characters (\n) in the DateFormatter, but it is a poor solution as the vertical spacing is not quite right.
Any advice would be appreciated!
You can use axis method set_tick_params() with the keyword pad. Compare following example.
import datetime
import random
import matplotlib.pyplot as plt
import matplotlib.dates as dates
# make up some data
x = [datetime.datetime.now() + datetime.timedelta(hours=i) for i in range(100)]
y = [i+random.gauss(0,1) for i,_ in enumerate(x)]
# plot
plt.plot(x,y)
# beautify the x-labels
plt.gcf().autofmt_xdate()
ax = plt.gca()
# set date ticks to something sensible:
xax = ax.get_xaxis()
xax.set_major_locator(dates.DayLocator())
xax.set_major_formatter(dates.DateFormatter('%d/%b'))
xax.set_minor_locator(dates.HourLocator(byhour=range(0,24,3)))
xax.set_minor_formatter(dates.DateFormatter('%H'))
xax.set_tick_params(which='major', pad=15)
plt.show()
PS: This example is borrowed from moooeeeep
Here's how the above snippet would render:

Categories

Resources