How to set xticks in subplots - python

If I plot a single imshow plot I can use
fig, ax = plt.subplots()
ax.imshow(data)
plt.xticks( [4, 14, 24], [5, 15, 25] )
to replace my xtick labels.
Now, I am plotting 12 imshow plots using
f, axarr = plt.subplots(4, 3)
axarr[i, j].imshow(data)
How can I change my xticks just for one of these subplots? I can only access the axes of the subplots with axarr[i, j]. How can I access plt just for one particular subplot?

There are two ways:
Use the axes methods of the subplot object (e.g. ax.set_xticks and ax.set_xticklabels) or
Use plt.sca to set the current axes for the pyplot state machine (i.e. the plt interface).
As an example (this also illustrates using setp to change the properties of all of the subplots):
import matplotlib.pyplot as plt
fig, axes = plt.subplots(nrows=3, ncols=4)
# Set the ticks and ticklabels for all axes
plt.setp(axes, xticks=[0.1, 0.5, 0.9], xticklabels=['a', 'b', 'c'],
yticks=[1, 2, 3])
# Use the pyplot interface to change just one subplot...
plt.sca(axes[1, 1])
plt.xticks(range(3), ['A', 'Big', 'Cat'], color='red')
fig.tight_layout()
plt.show()

See the (quite) recent answer on the matplotlib repository, in which the following solution is suggested:
If you want to set the xticklabels:
ax.set_xticks([1,4,5])
ax.set_xticklabels([1,4,5], fontsize=12)
If you want to only increase the fontsize of the xticklabels, using the default values and locations (which is something I personally often need and find very handy):
ax.tick_params(axis="x", labelsize=12)
To do it all at once:
plt.setp(ax.get_xticklabels(), fontsize=12, fontweight="bold",
horizontalalignment="left")`

Related

set_markersize not working for right side axis

I'm messing around with some plot styles and ran into a curiosity. I have a plot with twinx() to produce ticks on the right-hand side as well as the left. I want to stagger some ticks, some going farther out that others.
I can add padding to any tick on any axes and push out the text via ax.yaxis.get_major_ticks()[1].set_pad(), but when I try to lengthen the tick via ax.yaxis.get_major_ticks()[1].tick1line.set_markersize(), it works for all axes EXCEPT the right side. Any insight?
Please see the code below. I've tried switching up the axis (ax1, ax2) and index.
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
t = np.linspace(0,5)
x = np.exp(-t)*np.sin(2*t)
fig, ax1 = plt.subplots()
ax1.plot(t, x, alpha=0.0)
ax2 = ax1.twinx()
ax2.plot(t, x, alpha=1.0)
ax1.set_xticks([0,1,2])
ax1.set_yticks([0.1, 0.2])
ax2.set_yticks([0.3, 0.4, 0.5])
ax2.set_xticks([1,2,3])
ax1.grid(True, color='lightgray')
ax2.grid(True, color='lightgray')
for a in [ax1, ax2]:
a.spines["top"].set_visible(False)
a.spines["right"].set_visible(False)
a.spines["left"].set_visible(False)
a.spines["bottom"].set_visible(False)
ax1.set_axisbelow(True)
ax2.set_axisbelow(True)
ax1.xaxis.get_major_ticks()[1].set_pad(15) #
ax1.xaxis.get_major_ticks()[1].tick1line.set_markersize(15)
ax1.yaxis.get_major_ticks()[1].set_pad(15) #
ax1.yaxis.get_major_ticks()[1].tick1line.set_markersize(15)
ax2.yaxis.get_major_ticks()[1].set_pad(15) #
ax2.yaxis.get_major_ticks()[1].tick1line.set_markersize(15)
plt.savefig('fig.pdf')
plt.show()
You need to use tick2line instead of tick1line, since that's the one referring to the top/right axis, according to the documentation.
Change ax2.yaxis.get_major_ticks()[1].tick1line.set_markersize(15) for:
ax2.yaxis.get_major_ticks()[1].tick2line.set_markersize(15)
Result:

using sharex with odd number of subplots in matplotlib

I have an odd number of subplots like so:
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, sharex=True)
for i, ax in enumerate(axes.flat):
ax.plot(range(10))
fig.delaxes(axes.flat[-1])
I want them all to have the same x-axis, but easily add the x-ticks back to the plot on the right, since there is no longer a 4th plot.
It seems like there should be an easier/cleaner solution than adding each subplot manually (similar to this answer), but I can't seem to find anything. Thanks.
you can use setp to make the xtick labels visible for ax[0][1] like this
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, sharex=True)
for i, ax in enumerate(axes.flat):
ax.plot(range(10))
# for matploltib version 2.0.1
plt.setp(axes[0][1].get_xticklabels(), visible=True)
# for matplotlib version 2.1.1
axes[0][1].xaxis.set_tick_params(which='both', labelbottom=True, labeltop=False)
fig.delaxes(axes.flat[-1])
plt.show()
which will result in

matplotlib does not show legend in scatter plot

I am trying to work on a clustering problem for which I need to plot a scatter plot for my clusters.
%matplotlib inline
import matplotlib.pyplot as plt
df = pd.merge(dataframe,actual_cluster)
plt.scatter(df['x'], df['y'], c=df['cluster'])
plt.legend()
plt.show()
df['cluster'] is the actual cluster number. So I want that to be my color code.
It shows me a plot but it does not show me the legend. it does not give me error as well.
Am I doing something wrong?
EDIT:
Generating some random data:
from scipy.cluster.vq import kmeans2
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
n_clusters = 10
df = pd.DataFrame({'x':np.random.randn(1000), 'y':np.random.randn(1000)})
_, df['cluster'] = kmeans2(df, n_clusters)
Update
Use seaborn.relplot with kind='scatter' or use seaborn.scatterplot
Specify hue='cluster'
# figure level plot
sns.relplot(data=df, x='x', y='y', hue='cluster', palette='tab10', kind='scatter')
# axes level plot
fig, axes = plt.subplots(figsize=(6, 6))
sns.scatterplot(data=df, x='x', y='y', hue='cluster', palette='tab10', ax=axes)
axes.legend(loc='center left', bbox_to_anchor=(1, 0.5))
Original Answer
Plotting (matplotlib v3.3.4):
fig, ax = plt.subplots(figsize=(8, 6))
cmap = plt.cm.get_cmap('jet')
for i, cluster in df.groupby('cluster'):
_ = ax.scatter(cluster['x'], cluster['y'], color=cmap(i/n_clusters), label=i, ec='k')
ax.legend(loc='center left', bbox_to_anchor=(1, 0.5))
Result:
Explanation:
Not going too much into nitty gritty details of matplotlib internals, plotting one cluster at a time sort of solves the issue.
More specifically, ax.scatter() returns a PathCollection object which we are explicitly throwing away here but which seems to be passed internally to some sort of legend handler. Plotting all at once generates only one PathCollection/label pair, while plotting one cluster at a time generates n_clusters PathCollection/label pairs. You can see those objects by calling ax.get_legend_handles_labels() which returns something like:
([<matplotlib.collections.PathCollection at 0x7f60c2ff2ac8>,
<matplotlib.collections.PathCollection at 0x7f60c2ff9d68>,
<matplotlib.collections.PathCollection at 0x7f60c2ff9390>,
<matplotlib.collections.PathCollection at 0x7f60c2f802e8>,
<matplotlib.collections.PathCollection at 0x7f60c2f809b0>,
<matplotlib.collections.PathCollection at 0x7f60c2ff9908>,
<matplotlib.collections.PathCollection at 0x7f60c2f85668>,
<matplotlib.collections.PathCollection at 0x7f60c2f8cc88>,
<matplotlib.collections.PathCollection at 0x7f60c2f8c748>,
<matplotlib.collections.PathCollection at 0x7f60c2f92d30>],
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'])
So actually ax.legend() is equivalent to ax.legend(*ax.get_legend_handles_labels()).
NOTES:
If using Python 2, make sure i/n_clusters is a float
Omitting fig, ax = plt.subplots() and using plt.<method> instead
of ax.<method> works fine, but I always prefer to explicitly
specify the Axes object I am using rather then implicitly use the
"current axes" (plt.gca()).
OLD SIMPLE SOLUTION
In case you are ok with a colorbar (instead of discrete value labels), you can use Pandas built-in Matplotlib functionality:
df.plot.scatter('x', 'y', c='cluster', cmap='jet')
This is a question that bothers me for so long. Now, I want to provide another simple solution. We do not have to write any loops!!!
def vis(ax, df, label, title="visualization"):
points = ax.scatter(df[:, 0], df[:, 1], c=label, label=label, alpha=0.7)
ax.set_title(title)
ax.legend(*points.legend_elements(), title="Classes")

My matplotlib.pyplot legend is being cut off

I'm attempting to create a plot with a legend to the side of it using matplotlib. I can see that the plot is being created, but the image bounds do not allow the entire legend to be displayed.
lines = []
ax = plt.subplot(111)
for filename in args:
lines.append(plt.plot(y_axis, x_axis, colors[colorcycle], linestyle='steps-pre', label=filename))
ax.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
This produces:
Eventhough that it is late, I want to refer to a nice recently introduced alternative:
New matplotlib feature: The tight bounding box
If you are interested in the output file of plt.savefig: in this case the flag bbox_inches='tight' is your friend!
import matplotlib.pyplot as plt
fig = plt.figure(1)
plt.plot([1, 2, 3], [1, 0, 1], label='A')
plt.plot([1, 2, 3], [1, 2, 2], label='B')
plt.legend(loc='center left', bbox_to_anchor=(1, 0))
fig.savefig('samplefigure', bbox_inches='tight')
I want to refer also to a more detailed answer: Moving matplotlib legend outside of the axis makes it cutoff by the figure box
Advantages
There is no need to adjust the actual data/picture.
It is compatible with plt.subplots as-well where as the others are not!
It applies at least to the mostly used output files, e.g. png, pdf.
As pointed by Adam, you need to make space on the side of your graph.
If you want to fine tune the needed space, you may want to look at the add_axes method of matplotlib.pyplot.artist.
Below is a rapid example:
import matplotlib.pyplot as plt
import numpy as np
# some data
x = np.arange(0, 10, 0.1)
y1 = np.sin(x)
y2 = np.cos(x)
# plot of the data
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.6, 0.75])
ax.plot(x, y1,'-k', lw=2, label='black sin(x)')
ax.plot(x, y2,'-r', lw=2, label='red cos(x)')
ax.set_xlabel('x', size=22)
ax.set_ylabel('y', size=22)
ax.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
plt.show()
and the resulting image:
Just use plt.tight_layout()
import matplotlib.pyplot as plt
fig = plt.figure(1)
plt.plot([1, 2, 3], [1, 0, 1], label='A')
plt.plot([1, 2, 3], [1, 2, 2], label='B')
plt.legend(loc='center left', bbox_to_anchor=(1, 0))
plt.tight_layout()
This is probably introduced in the newer matplotlib version and neatly does the job.
Here is another way of making space (shrinking an axis):
# get the current axis
ax = plt.gca()
# Shink current axis by 20%
box = ax.get_position()
ax.set_position([box.x0, box.y0, box.width * 0.8, box.height])
where 0.8 scales the width of the axis by 20%. On my win7 64 machine, using a factor greater than 1 will make room for the legend if it's outside the plot.
This code was referenced from: How to put the legend out of the plot
Edit: #gcalmettes posted a better answer.
His solution should probably be used instead of the method shown below.
Nonetheless I'll leave this since it sometimes helps to see different ways of doing things.
As shown in the legend plotting guide, you can make room for another subplot and place the legend there.
import matplotlib.pyplot as plt
ax = plt.subplot(121) # <- with 2 we tell mpl to make room for an extra subplot
ax.plot([1,2,3], color='red', label='thin red line')
ax.plot([1.5,2.5,3.5], color='blue', label='thin blue line')
ax.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
plt.show()
Produces:
Store your legend call instance to a variable. e.g:
rr = sine_curve_plot.legend(loc=(0.0,1.1))
Then, include the bbox_extra_artists, bbox_inches keyword argument to plt.savefig. i.e:
plt.savefig('output.pdf', bbox_inches='tight', bbox_extra_artists=(rr))
bbox_extra_artists accepts an iterable, so you can include as many legend instances into it. The bbox_extra_artists automatically tells plt to cover every extra info passed into bbox_extra_artists.
DISCLAIMER: The loc variable simply defines the position of the legend, you can tweak the values for better flexibility in positioning. Of course, strings like upper left, upper right, etc. are also valid.

twinx kills tick label color

I am plotting a double plot with two y-axes. The second axis ax2 is created by twinx. The problem is that the coloring of the second y-axis via yticks is not working anymore. Instead I have to set_color the labels individually. Here is the relevant code:
fig = plt.figure()
fill_between(data[:,0], 0, (data[:,2]), color='yellow')
yticks(arange(0.2,1.2,0.2), ['.2', '.4', '.6', '.8', ' 1'], color='yellow')
ax2 = twinx()
ax2.plot(data[:,0], (data[:,1]), 'green')
yticks(arange(0.1,0.6,0.1), ['.1 ', '.2', '.3', '.4', '.5'], color='green')
# color='green' has no effect here ?!
# instead this is needed:
for t in ax2.yaxis.get_ticklabels(): t.set_color('green')
show()
Resulting in:
This issue only occurs if I set the tick strings.
yticks(arange(0.1,0.6,0.1), ['.1 ', '.2', '.3', '.4', '.5'], color='green')
Omit it, like here
yticks(arange(0.1,0.6,0.1), color='green')
and the coloring works fine.
Is that a bug (could not find any reports to this), a feature (?!) or
am I missing something here? I am using python 2.6.5 with matplotlib 0.99.1.1 on ubuntu.
For whatever it's worth, you code works fine on my system even without the for loop to set the label colors. Just as a reference, here's a stand-alone example trying to follow essentially exactly what you posted:
import matplotlib.pyplot as plt
import numpy as np
# Generate some data
num = 200
x = np.linspace(501, 1200, num)
yellow_data, green_data = np.random.random((2,num))
green_data -= np.linspace(0, 3, yellow_data.size)
# Plot the yellow data
plt.fill_between(x, yellow_data, 0, color='yellow')
plt.yticks([0.0, 0.5, 1.0], color='yellow')
# Plot the green data
ax2 = plt.twinx()
ax2.plot(x, green_data, 'g-')
plt.yticks([-4, -3, -2, -1, 0, 1], color='green')
plt.show()
My guess is that your problem is mostly coming from mixing up references to different objects. I'm guessing that your code is a bit more complex, and that when you call plt.yticks, ax2 is not the current axis. You can test that idea by explicitly calling sca(ax2) (set the current axis to ax2) before calling yticks and see if that changes things.
Generally speaking, it's best to stick to either entirely the matlab-ish state machine interface or the OO interface, and don't mix them too much. (Personally, I prefer just sticking to the OO interface. Use pyplot to set up figure objects and for show, and use the axes methods otherwise. To each his own, though.)
At any rate, with matplotlib >= 1.0, the tick_params function makes this a bit more convenient. (I'm also using plt.subplots here, which is only in >= 1.0, as well.)
import matplotlib.pyplot as plt
import numpy as np
# Generate some data
yellow_data, green_data = np.random.random((2,2000))
yellow_data += np.linspace(0, 3, yellow_data.size)
green_data -= np.linspace(0, 3, yellow_data.size)
# Plot the data
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.plot(yellow_data, 'y-')
ax2.plot(green_data, 'g-')
# Change the axis colors...
ax1.tick_params(axis='y', labelcolor='yellow')
ax2.tick_params(axis='y', labelcolor='green')
plt.show()
The equivalent code for older versions of matplotlib would look more like this:
import matplotlib.pyplot as plt
import numpy as np
# Generate some data
yellow_data, green_data = np.random.random((2,2000))
yellow_data += np.linspace(0, 3, yellow_data.size)
green_data -= np.linspace(0, 3, yellow_data.size)
# Plot the data
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
ax2 = ax1.twinx()
ax1.plot(yellow_data, 'y-')
ax2.plot(green_data, 'g-')
# Change the axis colors...
for ax, color in zip([ax1, ax2], ['yellow', 'green']):
for label in ax.yaxis.get_ticklabels():
label.set_color(color)
plt.show()

Categories

Resources