Seaborn renaming ticks in a heatmap [duplicate] - python
I want to make some modifications to a few selected tick labels in a plot.
For example, if I do:
label = axes.yaxis.get_major_ticks()[2].label
label.set_fontsize(size)
label.set_rotation('vertical')
the font size and the orientation of the tick label is changed.
However, if try:
label.set_text('Foo')
the tick label is not modified. Also if I do:
print label.get_text()
nothing is printed.
Here's some more strangeness. When I tried this:
import matplotlib.pyplot as plt
import numpy as np
axes = plt.figure().add_subplot(111)
t = np.arange(0.0, 2.0, 0.01)
s = np.sin(2*np.pi*t)
axes.plot(t, s)
for ticklabel in axes.get_xticklabels():
print(ticklabel.get_text())
Only empty strings are printed, but the plot contains ticks labeled as '0.0', '0.5', '1.0', '1.5', and '2.0'.
Caveat: Unless the ticklabels are already set to a string (as is usually the case in e.g. a boxplot), this will not work with any version of matplotlib newer than 1.1.0. If you're working from the current github master, this won't work. I'm not sure what the problem is yet... It may be an unintended change, or it may not be...
Normally, you'd do something along these lines:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
# We need to draw the canvas, otherwise the labels won't be positioned and
# won't have values yet.
fig.canvas.draw()
labels = [item.get_text() for item in ax.get_xticklabels()]
labels[1] = 'Testing'
ax.set_xticklabels(labels)
plt.show()
To understand the reason why you need to jump through so many hoops, you need to understand a bit more about how matplotlib is structured.
Matplotlib deliberately avoids doing "static" positioning of ticks, etc, unless it's explicitly told to. The assumption is that you'll want to interact with the plot, and so the bounds of the plot, ticks, ticklabels, etc will be dynamically changing.
Therefore, you can't just set the text of a given tick label. By default, it's re-set by the axis's Locator and Formatter every time the plot is drawn.
However, if the Locators and Formatters are set to be static (FixedLocator and FixedFormatter, respectively), then the tick labels stay the same.
This is what set_*ticklabels or ax.*axis.set_ticklabels does.
Hopefully that makes it slighly more clear as to why changing an individual tick label is a bit convoluted.
Often, what you actually want to do is just annotate a certain position. In that case, look into annotate, instead.
One can also do this with pylab and xticks
import matplotlib
import matplotlib.pyplot as plt
x = [0,1,2]
y = [90,40,65]
labels = ['high', 'low', 37337]
plt.plot(x,y, 'r')
plt.xticks(x, labels, rotation='vertical')
plt.show()
https://matplotlib.org/stable/gallery/ticks_and_spines/ticklabels_rotation.html
In newer versions of matplotlib, if you do not set the tick labels with a bunch of str values, they are '' by default (and when the plot is draw the labels are simply the ticks values). Knowing that, to get your desired output would require something like this:
>>> from pylab import *
>>> axes = figure().add_subplot(111)
>>> a=axes.get_xticks().tolist()
>>> a[1]='change'
>>> axes.set_xticklabels(a)
[<matplotlib.text.Text object at 0x539aa50>, <matplotlib.text.Text object at 0x53a0c90>,
<matplotlib.text.Text object at 0x53a73d0>, <matplotlib.text.Text object at 0x53a7a50>,
<matplotlib.text.Text object at 0x53aa110>, <matplotlib.text.Text object at 0x53aa790>]
>>> plt.show()
and the result:
and now if you check the _xticklabels, they are no longer a bunch of ''.
>>> [item.get_text() for item in axes.get_xticklabels()]
['0.0', 'change', '1.0', '1.5', '2.0']
It works in the versions from 1.1.1rc1 to the current version 2.0.
It's been a while since this question was asked. As of today (matplotlib 2.2.2) and after some reading and trials, I think the best/proper way is the following:
Matplotlib has a module named ticker that "contains classes to support completely configurable tick locating and formatting". To modify a specific tick from the plot, the following works for me:
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import numpy as np
def update_ticks(x, pos):
if x == 0:
return 'Mean'
elif pos == 6:
return 'pos is 6'
else:
return x
data = np.random.normal(0, 1, 1000)
fig, ax = plt.subplots()
ax.hist(data, bins=25, edgecolor='black')
ax.xaxis.set_major_formatter(mticker.FuncFormatter(update_ticks))
plt.show()
Caveat! x is the value of the tick and pos is its relative position in order in the axis. Notice that pos takes values starting in 1, not in 0 as usual when indexing.
In my case, I was trying to format the y-axis of a histogram with percentage values. mticker has another class named PercentFormatter that can do this easily without the need to define a separate function as before:
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import numpy as np
data = np.random.normal(0, 1, 1000)
fig, ax = plt.subplots()
weights = np.ones_like(data) / len(data)
ax.hist(data, bins=25, weights=weights, edgecolor='black')
ax.yaxis.set_major_formatter(mticker.PercentFormatter(xmax=1.0, decimals=1))
plt.show()
In this case xmax is the data value that corresponds to 100%. Percentages are computed as x / xmax * 100, that's why we fix xmax=1.0. Also, decimals is the number of decimal places to place after the point.
This works:
import matplotlib.pyplot as plt
fig, ax1 = plt.subplots(1,1)
x1 = [0,1,2,3]
squad = ['Fultz','Embiid','Dario','Simmons']
ax1.set_xticks(x1)
ax1.set_xticklabels(squad, minor=False, rotation=45)
The axes class has a set_yticklabels function which allows you to set the tick labels, like so:
#ax is the axes instance
group_labels = ['control', 'cold treatment',
'hot treatment', 'another treatment',
'the last one']
ax.set_xticklabels(group_labels)
I'm still working on why your example above didn't work.
This also works in matplotlib 3:
x1 = [0,1,2,3]
squad = ['Fultz','Embiid','Dario','Simmons']
plt.xticks(x1, squad, rotation=45)
If you do not work with fig and ax and you want to modify all labels (e.g. for normalization) you can do this:
labels, locations = plt.yticks()
plt.yticks(labels, labels/max(labels))
Try this :
fig,axis = plt.subplots(nrows=1,ncols=1,figsize=(13,6),sharex=True)
axis.set_xticklabels(['0', 'testing', '10000', '20000', '30000'],fontsize=22)
I noticed that all the solutions posted here that use set_xticklabels() are not preserving the offset, which is a scaling factor applied to the ticks values to create better-looking tick labels. For instance, if the ticks are on the order of 0.00001 (1e-5), matplotlib will automatically add a scaling factor (or offset) of 1e-5, so the resultant tick labels may end up as 1 2 3 4, rather than 1e-5 2e-5 3e-5 4e-5.
Below gives an example:
The x array is np.array([1, 2, 3, 4])/1e6, and y is y=x**2. So both are very small values.
Left column: manually change the 1st and 3rd labels, as suggested by #Joe Kington. Note that the offset is lost.
Mid column: similar as #iipr suggested, using a FuncFormatter.
Right column: My suggested offset-preserving solution.
Figure here:
Complete code here:
import matplotlib.pyplot as plt
import numpy as np
# create some *small* data to plot
x = np.arange(5)/1e6
y = x**2
fig, axes = plt.subplots(1, 3, figsize=(10,6))
#------------------The set_xticklabels() solution------------------
ax1 = axes[0]
ax1.plot(x, y)
fig.canvas.draw()
labels = [item.get_text() for item in ax1.get_xticklabels()]
# Modify specific labels
labels[1] = 'Testing'
labels[3] = 'Testing2'
ax1.set_xticklabels(labels)
ax1.set_title('set_xticklabels()')
#--------------FuncFormatter solution--------------
import matplotlib.ticker as mticker
def update_ticks(x, pos):
if pos==1:
return 'testing'
elif pos==3:
return 'testing2'
else:
return x
ax2=axes[1]
ax2.plot(x,y)
ax2.xaxis.set_major_formatter(mticker.FuncFormatter(update_ticks))
ax2.set_title('Func Formatter')
#-------------------My solution-------------------
def changeLabels(axis, pos, newlabels):
'''Change specific x/y tick labels
Args:
axis (Axis): .xaxis or .yaxis obj.
pos (list): indices for labels to change.
newlabels (list): new labels corresponding to indices in <pos>.
'''
if len(pos) != len(newlabels):
raise Exception("Length of <pos> doesn't equal that of <newlabels>.")
ticks = axis.get_majorticklocs()
# get the default tick formatter
formatter = axis.get_major_formatter()
# format the ticks into strings
labels = formatter.format_ticks(ticks)
# Modify specific labels
for pii, lii in zip(pos, newlabels):
labels[pii] = lii
# Update the ticks and ticklabels. Order is important here.
# Need to first get the offset (1e-6 in this case):
offset = formatter.get_offset()
# Then set the modified labels:
axis.set_ticklabels(labels)
# In doing so, matplotlib creates a new FixedFormatter and sets it to the xaxis
# and the new FixedFormatter has no offset. So we need to query the
# formatter again and re-assign the offset:
axis.get_major_formatter().set_offset_string(offset)
return
ax3 = axes[2]
ax3.plot(x, y)
changeLabels(ax3.xaxis, [1, 3], ['Testing', 'Testing2'])
ax3.set_title('With offset')
fig.show()
plt.savefig('tick_labels.png')
Caveat: it appears that solutions that use set_xticklabels(), including my own, relies on FixedFormatter, which is static and doesn't respond to figure resizing. To observe the effect, change the figure to a smaller size, e.g. fig, axes = plt.subplots(1, 3, figsize=(6,6)) and enlarge the figure window. You will notice that that only the mid column responds to resizing and adds more ticks as the figure gets larger. The left and right column will have empty tick labels (see figure below).
Caveat 2: I also noticed that if your tick values are floats, calling set_xticklabels(ticks) directly might give you ugly-looking strings, like 1.499999999998 instead of 1.5.
Here we are intending to modify some of the tick labels in Matplotlib but with no side effects, which works clean and which preserves offset scientific notations. None of the issues discussed in some of the other answers are faced in this solution.
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import rcParams
rcParams['axes.formatter.use_mathtext'] = True
class CustomScalarFormatter(matplotlib.ticker.ScalarFormatter):
def __init__(self, useOffset=None, useMathText=None, useLocale=None, replace_values=([],[])):
super().__init__(useOffset=None, useMathText=None, useLocale=None)
self.replace_values = replace_values
def __call__(self, x, pos=None):
"""
Return the format for tick value *x* at position *pos*.
"""
if len(self.locs) == 0:
return ''
elif x in self.replace_values[0]:
idx = self.replace_values[0].index(x)
return str(self.replace_values[1][idx])
else:
xp = (x - self.offset) / (10. ** self.orderOfMagnitude)
if abs(xp) < 1e-8:
xp = 0
return self._format_maybe_minus_and_locale(self.format, xp)
z = np.linspace(0, 5000, 100)
fig, ax = plt.subplots()
xmajorformatter = CustomScalarFormatter(replace_values=([2000,0],['$x_0$','']))
ymajorformatter = CustomScalarFormatter(replace_values=([1E7,0],['$y_0$','']))
ax.xaxis.set_major_formatter(xmajorformatter)
ax.yaxis.set_major_formatter(ymajorformatter)
ax.plot(z,z**2)
plt.show()
What we have done here is we created a derivative class of matplotlib.ticker.ScalarFormatter class which matplotlib uses by default to format the labels. The code is copied from matplotlib source but only __call__ function is copied and modified in it. Following
elif x in self.replace_values[0]:
idx = self.replace_values[0].index(x)
return str(self.replace_values[1][idx])
are the new lines added to the __call__ function which do the replacement job. The advantage of a derived class is that it inherits all the features from the base class like offset notation, scientific notation labels if values are large. The result is:
matplotlib.axes.Axes.set_xticks, or matplotlib.axes.Axes.set_yticks for the y-axis, can be used to change the ticks and labels beginning with matplotlib 3.5.0. These are for the object oriented interface.
If using the pyplot state-based interface, use plt.xticks or plt.yticks, as shown in other answers.
In general terms, pass a list / array of numbers to the ticks parameter, and a list / array strings to the labels parameter.
In this case, the x-axis is comprised of continuous numeric values, so there are no set Text labels, as thoroughly explained in this answer. This is not the case when plots have discrete ticks (e.g. boxplot, barplot).
[Text(0, 0, ''), Text(0, 0, ''), Text(0, 0, ''), Text(0, 0, ''), Text(0, 0, ''), Text(0, 0, ''), Text(0, 0, ''), Text(0, 0, ''), Text(0, 0, ''), Text(0, 0, ''), Text(0, 0, '')] is returned by ax.get_xticklabels()
[-0.25 0. 0.25 0.5 0.75 1. 1.25 1.5 1.75 2. 2.25] is returned by ax.get_xticks()
type(ax.get_xticks()) is <class 'numpy.ndarray'>
type(ax.get_xticks()[0]) is <class 'numpy.float64'>
Since the OP is trying to replace a numeric label with a str, all of the values in the ndarray must be converted to str type, and the value to be changed can be updated.
Tested in python 3.10 and matplotlib 3.5.2
import numpy as np
import matplotlib.pyplot as plt
# create figure and axes
fig, ax = plt.subplots(figsize=(8, 6))
# plot data
t = np.arange(0.0, 2.0, 0.01)
s = np.sin(2*np.pi*t)
# plot
ax.plot(t, s)
# get the xticks, which are the numeric location of the ticks
xticks = ax.get_xticks()
# get the xticks and convert the values in the array to str type
xticklabels = list(map(str, ax.get_xticks()))
# update the string to be changed
xticklabels[1] = 'Test'
# set the xticks and the labels
_ = ax.set_xticks(xticks, xticklabels)
Note the x-axis offset is not preserved when changing the xticklabels. However, the correct value is shown without the offset.
# create figure and axes
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 6), sharex=False)
# plot data
t = np.linspace(0, 1500000, 100)
s = t**2
# plot
ax1.plot(t, s)
ax2.plot(t, s)
# get the xticks, which are the numeric location of the ticks
xticks = ax2.get_xticks()
# get the xticks and convert the values in the array to str type
xticklabels = list(map(str, ax2.get_xticks()))
# update the string to be changed
xticklabels[1] = 'Test'
# set the xticks and the labels
_ = ax2.set_xticks(xticks, xticklabels, rotation=90)
you can do:
for k in ax.get_xmajorticklabels():
if some-condition:
k.set_color(any_colour_you_like)
draw()
Related
Irregular dates as int in xticks [duplicate]
I want to make some modifications to a few selected tick labels in a plot. For example, if I do: label = axes.yaxis.get_major_ticks()[2].label label.set_fontsize(size) label.set_rotation('vertical') the font size and the orientation of the tick label is changed. However, if try: label.set_text('Foo') the tick label is not modified. Also if I do: print label.get_text() nothing is printed. Here's some more strangeness. When I tried this: import matplotlib.pyplot as plt import numpy as np axes = plt.figure().add_subplot(111) t = np.arange(0.0, 2.0, 0.01) s = np.sin(2*np.pi*t) axes.plot(t, s) for ticklabel in axes.get_xticklabels(): print(ticklabel.get_text()) Only empty strings are printed, but the plot contains ticks labeled as '0.0', '0.5', '1.0', '1.5', and '2.0'.
Caveat: Unless the ticklabels are already set to a string (as is usually the case in e.g. a boxplot), this will not work with any version of matplotlib newer than 1.1.0. If you're working from the current github master, this won't work. I'm not sure what the problem is yet... It may be an unintended change, or it may not be... Normally, you'd do something along these lines: import matplotlib.pyplot as plt fig, ax = plt.subplots() # We need to draw the canvas, otherwise the labels won't be positioned and # won't have values yet. fig.canvas.draw() labels = [item.get_text() for item in ax.get_xticklabels()] labels[1] = 'Testing' ax.set_xticklabels(labels) plt.show() To understand the reason why you need to jump through so many hoops, you need to understand a bit more about how matplotlib is structured. Matplotlib deliberately avoids doing "static" positioning of ticks, etc, unless it's explicitly told to. The assumption is that you'll want to interact with the plot, and so the bounds of the plot, ticks, ticklabels, etc will be dynamically changing. Therefore, you can't just set the text of a given tick label. By default, it's re-set by the axis's Locator and Formatter every time the plot is drawn. However, if the Locators and Formatters are set to be static (FixedLocator and FixedFormatter, respectively), then the tick labels stay the same. This is what set_*ticklabels or ax.*axis.set_ticklabels does. Hopefully that makes it slighly more clear as to why changing an individual tick label is a bit convoluted. Often, what you actually want to do is just annotate a certain position. In that case, look into annotate, instead.
One can also do this with pylab and xticks import matplotlib import matplotlib.pyplot as plt x = [0,1,2] y = [90,40,65] labels = ['high', 'low', 37337] plt.plot(x,y, 'r') plt.xticks(x, labels, rotation='vertical') plt.show() https://matplotlib.org/stable/gallery/ticks_and_spines/ticklabels_rotation.html
In newer versions of matplotlib, if you do not set the tick labels with a bunch of str values, they are '' by default (and when the plot is draw the labels are simply the ticks values). Knowing that, to get your desired output would require something like this: >>> from pylab import * >>> axes = figure().add_subplot(111) >>> a=axes.get_xticks().tolist() >>> a[1]='change' >>> axes.set_xticklabels(a) [<matplotlib.text.Text object at 0x539aa50>, <matplotlib.text.Text object at 0x53a0c90>, <matplotlib.text.Text object at 0x53a73d0>, <matplotlib.text.Text object at 0x53a7a50>, <matplotlib.text.Text object at 0x53aa110>, <matplotlib.text.Text object at 0x53aa790>] >>> plt.show() and the result: and now if you check the _xticklabels, they are no longer a bunch of ''. >>> [item.get_text() for item in axes.get_xticklabels()] ['0.0', 'change', '1.0', '1.5', '2.0'] It works in the versions from 1.1.1rc1 to the current version 2.0.
It's been a while since this question was asked. As of today (matplotlib 2.2.2) and after some reading and trials, I think the best/proper way is the following: Matplotlib has a module named ticker that "contains classes to support completely configurable tick locating and formatting". To modify a specific tick from the plot, the following works for me: import matplotlib.pyplot as plt import matplotlib.ticker as mticker import numpy as np def update_ticks(x, pos): if x == 0: return 'Mean' elif pos == 6: return 'pos is 6' else: return x data = np.random.normal(0, 1, 1000) fig, ax = plt.subplots() ax.hist(data, bins=25, edgecolor='black') ax.xaxis.set_major_formatter(mticker.FuncFormatter(update_ticks)) plt.show() Caveat! x is the value of the tick and pos is its relative position in order in the axis. Notice that pos takes values starting in 1, not in 0 as usual when indexing. In my case, I was trying to format the y-axis of a histogram with percentage values. mticker has another class named PercentFormatter that can do this easily without the need to define a separate function as before: import matplotlib.pyplot as plt import matplotlib.ticker as mticker import numpy as np data = np.random.normal(0, 1, 1000) fig, ax = plt.subplots() weights = np.ones_like(data) / len(data) ax.hist(data, bins=25, weights=weights, edgecolor='black') ax.yaxis.set_major_formatter(mticker.PercentFormatter(xmax=1.0, decimals=1)) plt.show() In this case xmax is the data value that corresponds to 100%. Percentages are computed as x / xmax * 100, that's why we fix xmax=1.0. Also, decimals is the number of decimal places to place after the point.
This works: import matplotlib.pyplot as plt fig, ax1 = plt.subplots(1,1) x1 = [0,1,2,3] squad = ['Fultz','Embiid','Dario','Simmons'] ax1.set_xticks(x1) ax1.set_xticklabels(squad, minor=False, rotation=45)
The axes class has a set_yticklabels function which allows you to set the tick labels, like so: #ax is the axes instance group_labels = ['control', 'cold treatment', 'hot treatment', 'another treatment', 'the last one'] ax.set_xticklabels(group_labels) I'm still working on why your example above didn't work.
This also works in matplotlib 3: x1 = [0,1,2,3] squad = ['Fultz','Embiid','Dario','Simmons'] plt.xticks(x1, squad, rotation=45)
If you do not work with fig and ax and you want to modify all labels (e.g. for normalization) you can do this: labels, locations = plt.yticks() plt.yticks(labels, labels/max(labels))
Try this : fig,axis = plt.subplots(nrows=1,ncols=1,figsize=(13,6),sharex=True) axis.set_xticklabels(['0', 'testing', '10000', '20000', '30000'],fontsize=22)
I noticed that all the solutions posted here that use set_xticklabels() are not preserving the offset, which is a scaling factor applied to the ticks values to create better-looking tick labels. For instance, if the ticks are on the order of 0.00001 (1e-5), matplotlib will automatically add a scaling factor (or offset) of 1e-5, so the resultant tick labels may end up as 1 2 3 4, rather than 1e-5 2e-5 3e-5 4e-5. Below gives an example: The x array is np.array([1, 2, 3, 4])/1e6, and y is y=x**2. So both are very small values. Left column: manually change the 1st and 3rd labels, as suggested by #Joe Kington. Note that the offset is lost. Mid column: similar as #iipr suggested, using a FuncFormatter. Right column: My suggested offset-preserving solution. Figure here: Complete code here: import matplotlib.pyplot as plt import numpy as np # create some *small* data to plot x = np.arange(5)/1e6 y = x**2 fig, axes = plt.subplots(1, 3, figsize=(10,6)) #------------------The set_xticklabels() solution------------------ ax1 = axes[0] ax1.plot(x, y) fig.canvas.draw() labels = [item.get_text() for item in ax1.get_xticklabels()] # Modify specific labels labels[1] = 'Testing' labels[3] = 'Testing2' ax1.set_xticklabels(labels) ax1.set_title('set_xticklabels()') #--------------FuncFormatter solution-------------- import matplotlib.ticker as mticker def update_ticks(x, pos): if pos==1: return 'testing' elif pos==3: return 'testing2' else: return x ax2=axes[1] ax2.plot(x,y) ax2.xaxis.set_major_formatter(mticker.FuncFormatter(update_ticks)) ax2.set_title('Func Formatter') #-------------------My solution------------------- def changeLabels(axis, pos, newlabels): '''Change specific x/y tick labels Args: axis (Axis): .xaxis or .yaxis obj. pos (list): indices for labels to change. newlabels (list): new labels corresponding to indices in <pos>. ''' if len(pos) != len(newlabels): raise Exception("Length of <pos> doesn't equal that of <newlabels>.") ticks = axis.get_majorticklocs() # get the default tick formatter formatter = axis.get_major_formatter() # format the ticks into strings labels = formatter.format_ticks(ticks) # Modify specific labels for pii, lii in zip(pos, newlabels): labels[pii] = lii # Update the ticks and ticklabels. Order is important here. # Need to first get the offset (1e-6 in this case): offset = formatter.get_offset() # Then set the modified labels: axis.set_ticklabels(labels) # In doing so, matplotlib creates a new FixedFormatter and sets it to the xaxis # and the new FixedFormatter has no offset. So we need to query the # formatter again and re-assign the offset: axis.get_major_formatter().set_offset_string(offset) return ax3 = axes[2] ax3.plot(x, y) changeLabels(ax3.xaxis, [1, 3], ['Testing', 'Testing2']) ax3.set_title('With offset') fig.show() plt.savefig('tick_labels.png') Caveat: it appears that solutions that use set_xticklabels(), including my own, relies on FixedFormatter, which is static and doesn't respond to figure resizing. To observe the effect, change the figure to a smaller size, e.g. fig, axes = plt.subplots(1, 3, figsize=(6,6)) and enlarge the figure window. You will notice that that only the mid column responds to resizing and adds more ticks as the figure gets larger. The left and right column will have empty tick labels (see figure below). Caveat 2: I also noticed that if your tick values are floats, calling set_xticklabels(ticks) directly might give you ugly-looking strings, like 1.499999999998 instead of 1.5.
Here we are intending to modify some of the tick labels in Matplotlib but with no side effects, which works clean and which preserves offset scientific notations. None of the issues discussed in some of the other answers are faced in this solution. import matplotlib import matplotlib.pyplot as plt import numpy as np from matplotlib import rcParams rcParams['axes.formatter.use_mathtext'] = True class CustomScalarFormatter(matplotlib.ticker.ScalarFormatter): def __init__(self, useOffset=None, useMathText=None, useLocale=None, replace_values=([],[])): super().__init__(useOffset=None, useMathText=None, useLocale=None) self.replace_values = replace_values def __call__(self, x, pos=None): """ Return the format for tick value *x* at position *pos*. """ if len(self.locs) == 0: return '' elif x in self.replace_values[0]: idx = self.replace_values[0].index(x) return str(self.replace_values[1][idx]) else: xp = (x - self.offset) / (10. ** self.orderOfMagnitude) if abs(xp) < 1e-8: xp = 0 return self._format_maybe_minus_and_locale(self.format, xp) z = np.linspace(0, 5000, 100) fig, ax = plt.subplots() xmajorformatter = CustomScalarFormatter(replace_values=([2000,0],['$x_0$',''])) ymajorformatter = CustomScalarFormatter(replace_values=([1E7,0],['$y_0$',''])) ax.xaxis.set_major_formatter(xmajorformatter) ax.yaxis.set_major_formatter(ymajorformatter) ax.plot(z,z**2) plt.show() What we have done here is we created a derivative class of matplotlib.ticker.ScalarFormatter class which matplotlib uses by default to format the labels. The code is copied from matplotlib source but only __call__ function is copied and modified in it. Following elif x in self.replace_values[0]: idx = self.replace_values[0].index(x) return str(self.replace_values[1][idx]) are the new lines added to the __call__ function which do the replacement job. The advantage of a derived class is that it inherits all the features from the base class like offset notation, scientific notation labels if values are large. The result is:
matplotlib.axes.Axes.set_xticks, or matplotlib.axes.Axes.set_yticks for the y-axis, can be used to change the ticks and labels beginning with matplotlib 3.5.0. These are for the object oriented interface. If using the pyplot state-based interface, use plt.xticks or plt.yticks, as shown in other answers. In general terms, pass a list / array of numbers to the ticks parameter, and a list / array strings to the labels parameter. In this case, the x-axis is comprised of continuous numeric values, so there are no set Text labels, as thoroughly explained in this answer. This is not the case when plots have discrete ticks (e.g. boxplot, barplot). [Text(0, 0, ''), Text(0, 0, ''), Text(0, 0, ''), Text(0, 0, ''), Text(0, 0, ''), Text(0, 0, ''), Text(0, 0, ''), Text(0, 0, ''), Text(0, 0, ''), Text(0, 0, ''), Text(0, 0, '')] is returned by ax.get_xticklabels() [-0.25 0. 0.25 0.5 0.75 1. 1.25 1.5 1.75 2. 2.25] is returned by ax.get_xticks() type(ax.get_xticks()) is <class 'numpy.ndarray'> type(ax.get_xticks()[0]) is <class 'numpy.float64'> Since the OP is trying to replace a numeric label with a str, all of the values in the ndarray must be converted to str type, and the value to be changed can be updated. Tested in python 3.10 and matplotlib 3.5.2 import numpy as np import matplotlib.pyplot as plt # create figure and axes fig, ax = plt.subplots(figsize=(8, 6)) # plot data t = np.arange(0.0, 2.0, 0.01) s = np.sin(2*np.pi*t) # plot ax.plot(t, s) # get the xticks, which are the numeric location of the ticks xticks = ax.get_xticks() # get the xticks and convert the values in the array to str type xticklabels = list(map(str, ax.get_xticks())) # update the string to be changed xticklabels[1] = 'Test' # set the xticks and the labels _ = ax.set_xticks(xticks, xticklabels) Note the x-axis offset is not preserved when changing the xticklabels. However, the correct value is shown without the offset. # create figure and axes fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 6), sharex=False) # plot data t = np.linspace(0, 1500000, 100) s = t**2 # plot ax1.plot(t, s) ax2.plot(t, s) # get the xticks, which are the numeric location of the ticks xticks = ax2.get_xticks() # get the xticks and convert the values in the array to str type xticklabels = list(map(str, ax2.get_xticks())) # update the string to be changed xticklabels[1] = 'Test' # set the xticks and the labels _ = ax2.set_xticks(xticks, xticklabels, rotation=90)
you can do: for k in ax.get_xmajorticklabels(): if some-condition: k.set_color(any_colour_you_like) draw()
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 plots on common x axis in Matplotlib with common y-axis labeling
I have written the following minimal Python code in order to plot various functions of x on the same X-axis. import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt from cycler import cycler cycle = plt.rcParams['axes.prop_cycle'].by_key()['color'] xlabel='$X$'; ylabel='$Y$' ### Set tick features plt.tick_params(axis='both',which='major',width=2,length=10,labelsize=18) plt.tick_params(axis='both',which='minor',width=2,length=5) #plt.set_axis_bgcolor('grey') # Doesn't work if I uncomment! lines = ["-","--","-.",":"] Nlayer=4 f, axarr = plt.subplots(Nlayer, sharex=True) for a in range(1,Nlayer+1): X = np.linspace(0,10,100) Y = X**a index = a-1 + np.int((a-1)/Nlayer) axarr[a-1].plot(X, Y, linewidth=2.0+index, color=cycle[a], linestyle = lines[index], label='Layer = {}'.format(a)) axarr[a-1].legend(loc='upper right', prop={'size':6}) #plt.legend() # Axes labels plt.xlabel(xlabel, fontsize=20) plt.ylabel(ylabel, fontsize=20) plt.show() However, the plots don't join together on the X-axis and I failed to get a common Y-axis label. It actually labels for the last plot (see attached figure). I also get a blank plot additionally which I couldn't get rid of. I am using Python3.
The following code will produce the expected output : without blank plot which was created because of the two plt.tick_params calls before creating the actual fig with the gridspec_kw argument of subplots that allows you to control the space between rows and cols of subplots environment in order to join the different layer plots with unique and centered common ylabel using fig.text with relative positioning and rotation argument (same thing is done to xlabel to get an homogeneous final result). One may note that, it can also be done by repositioning the ylabel with ax.yaxis.set_label_coords() after an usual call like ax.set_ylabel(). import numpy as np import matplotlib.pyplot as plt cycle = plt.rcParams['axes.prop_cycle'].by_key()['color'] xlabel='$X$'; ylabel='$Y$' lines = ["-","--","-.",":"] Nlayer = 4 fig, axarr = plt.subplots(Nlayer, sharex='col',gridspec_kw={'hspace': 0, 'wspace': 0}) X = np.linspace(0,10,100) for i,ax in enumerate(axarr): Y = X**(i+1) ax.plot(X, Y, linewidth=2.0+i, color=cycle[i], linestyle = lines[i], label='Layer = {}'.format(i+1)) ax.legend(loc='upper right', prop={'size':6}) with axes labels, first option : fig.text(0.5, 0.01, xlabel, va='center') fig.text(0.01, 0.5, ylabel, va='center', rotation='vertical') or alternatively : # ax is here, the one of the last Nlayer plotted, i.e. Nlayer=4 ax.set_xlabel(xlabel) ax.set_ylabel(ylabel) # change y positioning to be in the horizontal center of all Nlayer, i.e. dynamically Nlayer/2 ax.yaxis.set_label_coords(-0.1,Nlayer/2) which gives : I also simplified your for loop by using enumerate to have an automatic counter i when looping over axarr.
Python plot x-axis display only select items
I have a python matplotlib graph showing up as below. There are over 100 items on the X-axis and I DO want to plot them all, but want only about 25 or so (maybe automatically) so that it is clear to look at. Can you please help? Thanks My code is also as follows: l1 = plt.plot(b) plt.setp(l1, linewidth=4, color='r') l2 = plt.plot(c) plt.setp(l2, linewidth=4, color='k') l3 = plt.plot(d) plt.setp(l3, linewidth=4, color='g') plt.xticks(range(len(a)), a) plt.xticks(rotation=30) plt.show() plt.savefig('a.png') NOTE: I also have the data column a (the X-axis variable) in the form u' 2016-02-29T00:01:30.000Z CHEPSTLC0007143 CDC-R114-DK' which throws this error invalid literal for float(). That is the reason I am using plt.xticks(range(len(a)), a).
This is a case where mpl is doing exactly what you told it to, but what you told it to do is sort of inconvenient. plt.xticks(range(len(a)), a) is telling mpl to put a tick at every integer and to use the strings in a to label the ticks (which it is correctly doing). I think instead you want to be doing something like import matplotlib.pyplot as plt import matplotlib.ticker as mticker # synthetic data a = list(range(45)) d = ['the label {}'.format(i) for i in range(45)] # make figure + axes fig, ax = plt.subplots(tight_layout=True) ax.set_xlabel('x label') ax.set_ylabel('y label') # draw one line ln1, = ax.plot(range(45), lw=4, color='r') # helper function for the formatter def listifed_formatter(x, pos=None): try: return d[int(x)] except IndexError: return '' # make and use the formatter mt = mticker.FuncFormatter(listifed_formatter) ax.xaxis.set_major_formatter(mt) # set the default ticker to only put ticks on the integers loc = ax.xaxis.get_major_locator() loc.set_params(integer=True) # rotate the labels [lab.set_rotation(30) for lab in ax.get_xticklabels()] If you pan/zoom the ticklabels will be correct and mpl will select a sensible number of ticks to show. [side note, this output is from the 2.x branch and shows some of the new default styling]
Just replace plt.xticks(range(len(a)), a) by plt.xticks(np.arange(0, len(a) + 1, 5)) and you are gonna reduce the number of x axis labels displayed.
If you want to show only 3 ticks, use the following code: axes = plt.axes() x_values = axes.get_xticks() y_values = axes.get_yticks() x_len = len(x_values) y_len = len(y_values) print(x_len) print(y_len) new_x = [x_values[i] for i in [0, x_len // 2, -1]] new_y = [y_values[i] for i in [0, y_len // 2, -1]] axes.set_xticks(new_x) axes.set_yticks(new_y) Similarly, if you want to show only 25 ticks, just pick up equally spaced 25 values from your get_xticks()
Placing ticks on specific values
Say I have a plot like the one below and I want to place Y ticks (and tick values) on specific locations. For example, only on the highest value (1.0) and lowest value (-1). How can I do that? t = np.arange(0.0, 100.0, 0.1) s = np.sin(0.1*np.pi*t)*np.exp(-t*0.01) fig, ax = plt.subplots() plt.plot(t,s)
To only place ticks on the minimum and maximum value you can use: import numpy as np import matplotlib.pyplot as plt t = np.arange(0.0, 100.0, 0.1) s = np.sin(0.1*np.pi*t)*np.exp(-t*0.01) fig, ax = plt.subplots() plt.plot(t,s) ylims = ax.get_ylim() ax.set_yticks(ylims) xlims = ax.get_xlim() ax.set_xticks(xlims) plt.show() ax.get_ylim() returns a tuple with the minimum and maximum values. You can then use ax.set_yticks() to choose the y-ticks (in this case I have simply used the min and max y-values). EDIT You mentioned the use of Locator and Formatter objects in your comment. I've included another example below which makes use of these to: Set the major tick positions; Set the minor tick positions (they are small but they are there); Format the major tick strings. The code is commented and so should be understandable, if you need any more help then let me know. import numpy as np import matplotlib.pyplot as plt from matplotlib.ticker import FixedLocator, LinearLocator, FormatStrFormatter t = np.arange(0.0, 100.0, 0.1) s = np.sin(0.1*np.pi*t)*np.exp(-t*0.01) fig, ax = plt.subplots() plt.plot(t, s) # Retrieve the limits of the x and y axis. xlims = ax.get_xlim() ylims = ax.get_ylim() # Create two FixedLocator objects. FixedLocator objects take a sequence # which then is translated into the tick-positions. In this case I have # simply given the x/y limits as the sequence. xmajorlocator = FixedLocator(xlims) ymajorlocator = FixedLocator(ylims) ax.xaxis.set_major_locator(xmajorlocator) ax.yaxis.set_major_locator(ymajorlocator) # Create two LinearLocator objects for use in the minor ticks. # LinearLocator objects take the number of ticks as an argument # and automagically calculate the appropriate tick positions. xminorlocator = LinearLocator(10) yminorlocator = LinearLocator(10) ax.xaxis.set_minor_locator(xminorlocator) ax.yaxis.set_minor_locator(yminorlocator) # Create two FormatStrFormatters to format the major ticks. # I've added this simply to complete the example, you can set # a fmt string using Python syntax to control how your ticks # look. In this example I've formatted them as floats with # 3 and 2 decimal places respectively. xmajorformatter = FormatStrFormatter('%.3f') ymajorformatter = FormatStrFormatter('%.2f') ax.xaxis.set_major_formatter(xmajorformatter) ax.yaxis.set_major_formatter(ymajorformatter) plt.show() I've also included the updated graph with new tick formatting, I'll remove the old one to save space.