Using setp to hide axes spines - python-3.x

I am trying to use setp in matplotlib to set the visibility of spines to False, but I get the error "AttributeError: 'str' object has no attribute 'update'".
As far as I understand, with setp we can change the properties of iterable objects, and want to execute it with spines.
What is the correct syntax to effectively use setp?
Hier a MWE:
import matplotlib.pyplot as plt
x = range(0,10)
y = [i*i for i in x]
plt.plot(x,y) #Plotting x against y
axes = plt.gca() #Getting the current axis
axes.spines['top'].set_visible(False) #It works
plt.setp(axes.spines, visible=False) #It rises error
plt.show() #Showing the plot
Versions: python3.8.2, Matplotlib 3.2.1

axes.spines is an OrderedDict. When you iterate over a Dict or OrderedDict like this:
for key in axes.spines:
print(type(key))
You are iterating over the keys, which are strings and have no update method. Here you can see what parameters can be set with plt.setp() by just passing in the iterable or object like so.
plt.setp(axes.spines)
This returns None, because its referring to the keys, which are strings and have no update method.
Along this line of logic if we try this:
plt.setp(axes.spines.values())
we see that this does return possible arguments.
So in summary, changing plt.setp(axes.spines, visible=False) to plt.setp(axes.spines.values(), visible=False) will remove all spines since it is iterating through the objects and not the keys.
Full code:
import matplotlib.pyplot as plt
x = range(0,10)
y = [i*i for i in x]
plt.plot(x,y) #Plotting x against y
axes = plt.gca() #Getting the current axis
axes.spines['top'].set_visible(False)
plt.setp(axes.spines.values(), visible=False)
plt.show() #Showing the plot

I will post my desperate solution, only for the record, and if it might help somebody. Though #axe319 answer can hardly be trumped.
I just had to iterate over the names of the spines names:
spine_names = ('top','right', 'bottom', 'left')
for spine_name in spine_names:
axes.spines[spine_name].set_visible(False)
It works, but is not so elegant and flexible, and, obviously, gives up on using setp :-\
Warning:
Somebody might think that an alternative solution is
axes.set_frame_on(False)
But, not at all. I tried it. Although it certainly hides all axes at once as using set_visible(False), afterwards the command axes.spines[spine_name].set_visible(True) does not work!

Related

Can I see all attributes of a pyplot without showing the graph?

I am working on developing homework as a TA for a course at my university.
We are using Otter Grader (an extension of OKPy) to grade student submissions of guided homework we provide through Jupyter Notebooks.
Students are being asked to plot horizontal lines on their plots using matplotlib.pyplot.axhline(), and I am hoping to use an assert call to determine whether they added the horizontal line to their plots.
Is there a way to see all attributes that have been added to a pyplot in matplotlib?
I don't believe there is a way to see if the axhline attribute has been used or not, but there is a way to see if the lines are horizontal by accessing all the line2D objects using the lines attribute.
import matplotlib.pyplot as plt
import numpy as np
def is_horizontal(line2d):
x, y = line2d.get_data()
y = np.array(y) # The axhline method does not return data as a numpy array
y_bool = y == y[0] # Returns a boolean array of True or False if the first number equals all the other numbers
return np.all(y_bool)
t = np.linspace(-10, 10, 1000)
plt.plot(t, t**2)
plt.plot(t, t)
plt.axhline(y=5, xmin=-10, xmax=10)
ax = plt.gca()
assert any(map(is_horizontal, ax.lines)), 'There are no horizontal lines on the plot.'
plt.show()
This code will raise the error if there is not at least one line2D object that contains data in which all the y values are the same.
Note that in order for the above to work, the axhline attribute has to be used instead of the hlines method. The hlines method does not add the line2D object to the axes object.

Legend overwritten by plot - matplotlib

I have a plot that looks as follows:
I want to put labels for both the lineplot and the markers in red. However the legend is not appearning because its the plot is taking out its space.
Update
it turns out I cannot put several strings in plt.legend()
I made the figure bigger by using the following:
fig = plt.gcf()
fig.set_size_inches(18.5, 10.5)
However now I have only one label in the legend, with the marker appearing on the lineplot while I rather want two: one for the marker alone and another for the line alone:
Updated code:
plt.plot(range(len(y)), y, '-bD', c='blue', markerfacecolor='red', markeredgecolor='k', markevery=rare_cases, label='%s' % target_var_name)
fig = plt.gcf()
fig.set_size_inches(18.5, 10.5)
# changed this over here
plt.legend()
plt.savefig(output_folder + fig_name)
plt.close()
What you want to do (have two labels for a single object) is not completely impossible but it's MUCH easier to plot separately the line and the rare values, e.g.
# boilerplate
import numpy as np
import matplotlib.pyplot as plt
# synthesize some data
N = 501
t = np.linspace(0, 10, N)
s = np.sin(np.pi*t)
rare = np.zeros(N, dtype=bool); rare[:20]=True; np.random.shuffle(rare)
plt.plot(t, s, label='Curve')
plt.scatter(t[rare], s[rare], label='rare')
plt.legend()
plt.show()
Update
[...] it turns out I cannot put several strings in plt.legend()
Well, you can, as long as ① the several strings are in an iterable (a tuple or a list) and ② the number of strings (i.e., labels) equals the number of artists (i.e., thingies) in the plot.
plt.legend(('a', 'b', 'c'))

matplotlib histogram bins not reflecting data [duplicate]

I can't figure out how to rotate the text on the X Axis. Its a time stamp, so as the number of samples increase, they get closer and closer until they overlap. I'd like to rotate the text 90 degrees so as the samples get closer together, they aren't overlapping.
Below is what I have, it works fine with the exception that I can't figure out how to rotate the X axis text.
import sys
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import datetime
font = {'family' : 'normal',
'weight' : 'bold',
'size' : 8}
matplotlib.rc('font', **font)
values = open('stats.csv', 'r').readlines()
time = [datetime.datetime.fromtimestamp(float(i.split(',')[0].strip())) for i in values[1:]]
delay = [float(i.split(',')[1].strip()) for i in values[1:]]
plt.plot(time, delay)
plt.grid(b='on')
plt.savefig('test.png')
This works for me:
plt.xticks(rotation=90)
Many "correct" answers here but I'll add one more since I think some details are left out of several. The OP asked for 90 degree rotation but I'll change to 45 degrees because when you use an angle that isn't zero or 90, you should change the horizontal alignment as well; otherwise your labels will be off-center and a bit misleading (and I'm guessing many people who come here want to rotate axes to something other than 90).
Easiest / Least Code
Option 1
plt.xticks(rotation=45, ha='right')
As mentioned previously, that may not be desirable if you'd rather take the Object Oriented approach.
Option 2
Another fast way (it's intended for date objects but seems to work on any label; doubt this is recommended though):
fig.autofmt_xdate(rotation=45)
fig you would usually get from:
fig = plt.gcf()
fig = plt.figure()
fig, ax = plt.subplots()
fig = ax.figure
Object-Oriented / Dealing directly with ax
Option 3a
If you have the list of labels:
labels = ['One', 'Two', 'Three']
ax.set_xticks([1, 2, 3])
ax.set_xticklabels(labels, rotation=45, ha='right')
In later versions of Matplotlib (3.5+), you can just use set_xticks alone:
ax.set_xticks([1, 2, 3], labels, rotation=45, ha='right')
Option 3b
If you want to get the list of labels from the current plot:
# Unfortunately you need to draw your figure first to assign the labels,
# otherwise get_xticklabels() will return empty strings.
plt.draw()
ax.set_xticks(ax.get_xticks())
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha='right')
As above, in later versions of Matplotlib (3.5+), you can just use set_xticks alone:
ax.set_xticks(ax.get_xticks(), ax.get_xticklabels(), rotation=45, ha='right')
Option 4
Similar to above, but loop through manually instead.
for label in ax.get_xticklabels():
label.set_rotation(45)
label.set_ha('right')
Option 5
We still use pyplot (as plt) here but it's object-oriented because we're changing the property of a specific ax object.
plt.setp(ax.get_xticklabels(), rotation=45, ha='right')
Option 6
This option is simple, but AFAIK you can't set label horizontal align this way so another option might be better if your angle is not 90.
ax.tick_params(axis='x', labelrotation=45)
Edit:
There's discussion of this exact "bug" but a fix hasn't been released (as of 3.4.0):
https://github.com/matplotlib/matplotlib/issues/13774
Easy way
As described here, there is an existing method in the matplotlib.pyplot figure class that automatically rotates dates appropriately for you figure.
You can call it after you plot your data (i.e.ax.plot(dates,ydata) :
fig.autofmt_xdate()
If you need to format the labels further, checkout the above link.
Non-datetime objects
As per languitar's comment, the method I suggested for non-datetime xticks would not update correctly when zooming, etc. If it's not a datetime object used as your x-axis data, you should follow Tommy's answer:
for tick in ax.get_xticklabels():
tick.set_rotation(45)
Try pyplot.setp. I think you could do something like this:
x = range(len(time))
plt.xticks(x, time)
locs, labels = plt.xticks()
plt.setp(labels, rotation=90)
plt.plot(x, delay)
Appart from
plt.xticks(rotation=90)
this is also possible:
plt.xticks(rotation='vertical')
I came up with a similar example. Again, the rotation keyword is.. well, it's key.
from pylab import *
fig = figure()
ax = fig.add_subplot(111)
ax.bar( [0,1,2], [1,3,5] )
ax.set_xticks( [ 0.5, 1.5, 2.5 ] )
ax.set_xticklabels( ['tom','dick','harry'], rotation=45 ) ;
If you want to apply rotation on the axes object, the easiest way is using tick_params. For example.
ax.tick_params(axis='x', labelrotation=90)
Matplotlib documentation reference here.
This is useful when you have an array of axes as returned by plt.subplots, and it is more convenient than using set_xticks because in that case you need to also set the tick labels, and also more convenient that those that iterate over the ticks (for obvious reasons)
If using plt:
plt.xticks(rotation=90)
In case of using pandas or seaborn to plot, assuming ax as axes for the plot:
ax.set_xticklabels(ax.get_xticklabels(), rotation=90)
Another way of doing the above:
for tick in ax.get_xticklabels():
tick.set_rotation(45)
My answer is inspired by cjohnson318's answer, but I didn't want to supply a hardcoded list of labels; I wanted to rotate the existing labels:
for tick in ax.get_xticklabels():
tick.set_rotation(45)
The simplest solution is to use:
plt.xticks(rotation=XX)
but also
# Tweak spacing to prevent clipping of tick-labels
plt.subplots_adjust(bottom=X.XX)
e.g for dates I used rotation=45 and bottom=0.20 but you can do some test for your data
import pylab as pl
pl.xticks(rotation = 90)
To rotate the x-axis label to 90 degrees
for tick in ax.get_xticklabels():
tick.set_rotation(45)
It will depend on what are you plotting.
import matplotlib.pyplot as plt
x=['long_text_for_a_label_a',
'long_text_for_a_label_b',
'long_text_for_a_label_c']
y=[1,2,3]
myplot = plt.plot(x,y)
for item in myplot.axes.get_xticklabels():
item.set_rotation(90)
For pandas and seaborn that give you an Axes object:
df = pd.DataFrame(x,y)
#pandas
myplot = df.plot.bar()
#seaborn
myplotsns =sns.barplot(y='0', x=df.index, data=df)
# you can get xticklabels without .axes cause the object are already a
# isntance of it
for item in myplot.get_xticklabels():
item.set_rotation(90)
If you need to rotate labels you may need change the font size too, you can use font_scale=1.0 to do that.

Add labels to each box in seaborn's factorplot boxplot

I know there are similar answers such as this one, but that one applies to seaborn's boxplot and it's not working for me with seaborn's factorplot. On a simple factorplot:
import seaborn as sns
tips = sns.load_dataset("tips")
means = tips.groupby(["sex","smoker","time"])["tip"].mean().values
means_labels = [str(int(s)) for s in means]
with sns.plotting_context("notebook",font_scale=2):
g = sns.factorplot(x="sex", y="total_bill", hue="smoker",\
col="time", data=tips, kind="box", size=6, aspect=.7)
How can one add an annotation (in the example above, the means_labels) below each box, like this:
As I said, I tried using the answer above to at least try to get the position of each box:
import matplotlib.pyplot as plt
ax = plt.gca()
pos = range(len(means))
for tick,label in zip(pos,ax.get_xticklabels()):
ax.text(pos[tick], means[tick] + 0.5, meanslabels[tick],
horizontalalignment='center', color='r', weight='semibold')
But this produces:
I believe this is because I'm passing the whole plot's axes instead of the "factorplot" axes. But I couldn't find a way to do so (if instead of ax=plt.gca() I use, like in the example, ax=sns.factorplot(...), I get the error: AttributeError: module 'seaborn' has no attribute 'gca').

Matplotlib - Axes collision warning when setting aspect ratio

I am using matplotlib to plot a hexbin. As a simple example-
import matplotlib.pyplot as plt
import numpy as np
x = np.random.rand(100)
y = np.random.rand(100)
plt.hexbin(x, y, gridsize = 15, cmap='inferno')
plt.gca().invert_yaxis() # To make top left corner as origin
plt.axes().set_aspect('equal', 'datalim')
plt.show()
I get the following warning-
"MatplotlibDeprecationWarning: Adding an axes using the same arguments as a previous axes currently reuses the earlier instance."
I think it is due to the line-
plt.axes().set_aspect('equal', 'datalim')
How can I use different arguments in this case. The version of matplotlibis 2.1.1
It doesn't seem like you want to create a new axes anyways. So don't use plt.axes() here. Instead get the current axes in the usual way (plt.gca()) and use any of its methods.
plt.gca().set_aspect('equal', 'datalim')

Resources