how to avoid some function in the legend? - matplotlib

I need to include a line into a figure every time a button is clicked (I'm using pyqt4), this line has to be labeled and I also need to compare these lines with a constant function. Here is what I've tried:
labels = []
fig = plt.figure()
ax = fig.add_subplot(111, axisbg='white')
ax.hold(True)
def function(k):
x = np.linspace(0, 2, 100)
y = np.sin(k * np.pi * x) * np.exp(-5 * x)
labels.append('k = {}'.format(k))
ax.plot(x, y)
# reference line
plt.axhline(y=0.1, c='k', linestyle='--')
plt.legend(labels)
for i in range(0,5):
function(i)
plt.show()
The result:
There is a simple way to skip the constant line marker in the legend frame?

Maybe I'm not following but it doesn't look like your reference line axhline(y=0.1, ...) is included in the legend.
I would set this separately, no reason to redraw it every time you plot a new line. Also try passing the label inside the plot function
fig = plt.figure()
ax = fig.add_subplot(111, axisbg='white')
ax.hold(True)
# reference line - only draw this once
plt.axhline(y=0.1, c='k', linestyle='--')
def function(k):
x = np.linspace(0, 2, 100)
y = np.sin(k * np.pi * x) * np.exp(-5 * x)
ax.plot(x, y, linestyle='-', label='k = {}'.format(k)) # set label attribute for line
for i in range(0,5):
function(i)
plt.legend() # you only need to call this once, it will generate based on the label assigned to line objects
plt.show()
Note: If you want to do this interactively (i.e. draw on a button press) then you'll have to call plt.legend() upfront and call plt.draw() after each new line is added, that way it'll update the legend.

This is because there are actually 10 lines in your plot but your legend only shows 5 labels. If you check this by putting the label in the plot and axhline commands like this.
def function(k):
x = np.linspace(0, 2, 100)
y = np.sin(k * np.pi * x) * np.exp(-5 * x)
ax.plot(x, y, label='k = {}'.format(k))
# reference line
ax.axhline(y=0.1, c='k', linestyle='--', label='reference')
ax.legend()
print "number of lines in plot: {}".format(len(ax.lines))
Because you set the Axes.hold property to True, the Axes is not cleared, but a new line is added to Axes object every time you call these commands. This may be faster but you have to be careful to avoid adding duplicate artists. A simple solution would be to split the drawing in two functions: one to create an empty plot and one to add a line.
import matplotlib.pyplot as plt
import numpy as np
def init_plot(ax):
ax.hold(True)
ax.axhline(y=0.1, c='k', linestyle='--', label='reference')
ax.legend()
def add_line(ax, k):
x = np.linspace(0, 2, 100)
y = np.sin(k * np.pi * x) * np.exp(-5 * x)
ax.plot(x, y, label='k = {}'.format(k))
ax.legend()
def main():
fig = plt.figure()
ax = fig.add_subplot(111, axisbg='white')
init_plot(ax)
for i in range(0,5):
add_line(ax, i)
plt.show()
#raw_input('please press enter\n') # for OS-X
if __name__ == "__main__":
main()
I recommend to read the Artist tutorial and of course the Legend guide.

Related

Adding secondary axis and making grid lines equal

I am trying to add a secondary axis to a plot and make the grid lines equally spaced along y, but I the code below doesn't do what it is supposed to. y2A,y2B values are not right - they refer to xlim values not ylim. Any ideas?
import numpy as np
import matplotlib.pyplot as plt
def CtoF(y):
return y * 1.8 + 32
def FtoC(y):
return (y - 32) / 1.8
def setAxis2(ax1):
ax2 = ax1.secondary_yaxis('right', functions=(CtoF, FtoC))
ax2.set_ylabel('Fahrenheit')
return ax2
x = np.arange(100)
y = np.random.rand(100)
plt.plot(x,y)
ax1 = plt.gca()
ax1.set_ylabel('Celsius')
ax1.grid()
#Add the 2nd axis for Fahrenheit
ax2 = setAxis2(ax1)
#Get the ylimits and space them equally
[y1A,y1B] = ax1.get_ylim()
[y2A,y2B] = ax2.get_ylim()
ax1.set_yticks(np.linspace(y1A,y1B, 10))
ax2.set_yticks(np.linspace(y2A,y2B, 10)) #Doesn't work
print(y1A,y1B) #
print(y2A,y2B) #Doesn't output the expected values
I tried another method that works well (with the same versions of matplotlib), but the question remains about the issue above. The method that works is below:
ticks1 = ax1.get_yticks()
ticks2 = CtoF(ticks1)
ax2.set_yticks(ticks2)
Instead of getting y2A and y2B from the y-limits of ax2, we can calculate them directly with CtoF:
# Get the y-limits and space them equally.
y1A, y1B = ax1.get_ylim()
y2A, y2B = map(CtoF, (y1A, y1B))
n = 10
ax1.set_yticks(np.linspace(y1A, y1B, n))
ax2.set_yticks(np.linspace(y2A, y2B, n))

y and x axis subplots matplotlib

A quite basic question about ticks' labels for x and y-axis. According to this code
fig, axes = plt.subplots(6,12, figsize=(50, 24), constrained_layout=True, sharex=True , sharey=True)
fig.subplots_adjust(hspace = .5, wspace=.5)
custom_xlim = (-1, 1)
custom_ylim = (-0.2,0.2)
for i in range(72):
x_data = ctheta[i]
y_data = phi[i]
y_err = err_phi[i]
ax = fig.add_subplot(6, 12, i+1)
ax.plot(x_data_new, bspl(x_data_new))
ax.axis('off')
ax.errorbar(x_data,y_data, yerr=y_err, fmt="o")
ax.set_xlim(custom_xlim)
ax.set_ylim(custom_ylim)
I get the following output:
With y labels for plots on the first column and x labels for theone along the last line, although I call them off.
Any idea?
As #BigBen wrote in their comment, your issue is caused by you adding axes to your figure twice, once via fig, axes = plt.subplots() and then once again within your loop via fig.add_subplot(). As a result, the first set of axes is still visible even after you applied .axis('off') to the second set.
Instead of the latter, you could change your loop to:
for i in range(6):
for j in range(12):
ax = axes[i,j] # these are the axes created via plt.subplots(6,12,...)
ax.axis('off')
# … your other code here

How to make a gif out of subplot?

I'm using this code from matplotlib website to generate gif through list of images.
https://matplotlib.org/gallery/animation/dynamic_image2.html
However, I'm struggling to figure out how to make it work if I have subplot with two axes inside it. Thus, it is as if I have two images, which one should I append to the list?
EDIT: sample code:
ims = []
for i in range(60):
x += np.pi / 15.
y += np.pi / 20.
im = plt.imshow(f(x, y), animated=True)
ims.append([im])
ani = animation.ArtistAnimation(fig, ims, interval=50, blit=True,
repeat_delay=1000)
As explained in the page you linked, the array of artists passed to ArtistAnimation is a list of lists, each element of the list corresponds to one frame, where all the elements of the "inner" lists are updated.
Therefore
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, (ax1, ax2) = plt.subplots(1,2)
def f(x, y):
return np.sin(x) + np.cos(y)
x = np.linspace(0, 2 * np.pi, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
# ims is a list of lists, each row is a list of artists to draw in the
# current frame; here we are just animating one artist, the image, in
# each frame
ims = []
for i in range(60):
x += np.pi / 15.
y += np.pi / 20.
im1 = ax1.imshow(f(x, y), animated=True)
im2 = ax2.imshow(np.random.random(size=(100,120)))
ims.append([im1,im2])
ani = animation.ArtistAnimation(fig, ims, interval=50, blit=True,
repeat_delay=1000)

Matplotlib - how to combine a list of AxesSubplot into one figure with multiple subplots? [duplicate]

Looking at the matplotlib documentation, it seems the standard way to add an AxesSubplot to a Figure is to use Figure.add_subplot:
from matplotlib import pyplot
fig = pyplot.figure()
ax = fig.add_subplot(1,1,1)
ax.hist( some params .... )
I would like to be able to create AxesSubPlot-like objects independently of the figure, so I can use them in different figures. Something like
fig = pyplot.figure()
histoA = some_axes_subplot_maker.hist( some params ..... )
histoA = some_axes_subplot_maker.hist( some other params ..... )
# make one figure with both plots
fig.add_subaxes(histo1, 211)
fig.add_subaxes(histo1, 212)
fig2 = pyplot.figure()
# make a figure with the first plot only
fig2.add_subaxes(histo1, 111)
Is this possible in matplotlib and if so, how can I do this?
Update: I have not managed to decouple creation of Axes and Figures, but following examples in the answers below, can easily re-use previously created axes in new or olf Figure instances. This can be illustrated with a simple function:
def plot_axes(ax, fig=None, geometry=(1,1,1)):
if fig is None:
fig = plt.figure()
if ax.get_geometry() != geometry :
ax.change_geometry(*geometry)
ax = fig.axes.append(ax)
return fig
Typically, you just pass the axes instance to a function.
For example:
import matplotlib.pyplot as plt
import numpy as np
def main():
x = np.linspace(0, 6 * np.pi, 100)
fig1, (ax1, ax2) = plt.subplots(nrows=2)
plot(x, np.sin(x), ax1)
plot(x, np.random.random(100), ax2)
fig2 = plt.figure()
plot(x, np.cos(x))
plt.show()
def plot(x, y, ax=None):
if ax is None:
ax = plt.gca()
line, = ax.plot(x, y, 'go')
ax.set_ylabel('Yabba dabba do!')
return line
if __name__ == '__main__':
main()
To respond to your question, you could always do something like this:
def subplot(data, fig=None, index=111):
if fig is None:
fig = plt.figure()
ax = fig.add_subplot(index)
ax.plot(data)
Also, you can simply add an axes instance to another figure:
import matplotlib.pyplot as plt
fig1, ax = plt.subplots()
ax.plot(range(10))
fig2 = plt.figure()
fig2.axes.append(ax)
plt.show()
Resizing it to match other subplot "shapes" is also possible, but it's going to quickly become more trouble than it's worth. The approach of just passing around a figure or axes instance (or list of instances) is much simpler for complex cases, in my experience...
The following shows how to "move" an axes from one figure to another. This is the intended functionality of #JoeKington's last example, which in newer matplotlib versions is not working anymore, because axes cannot live in several figures at once.
You would first need to remove the axes from the first figure, then append it to the next figure and give it some position to live in.
import matplotlib.pyplot as plt
fig1, ax = plt.subplots()
ax.plot(range(10))
ax.remove()
fig2 = plt.figure()
ax.figure=fig2
fig2.axes.append(ax)
fig2.add_axes(ax)
dummy = fig2.add_subplot(111)
ax.set_position(dummy.get_position())
dummy.remove()
plt.close(fig1)
plt.show()
For line plots, you can deal with the Line2D objects themselves:
fig1 = pylab.figure()
ax1 = fig1.add_subplot(111)
lines = ax1.plot(scipy.randn(10))
fig2 = pylab.figure()
ax2 = fig2.add_subplot(111)
ax2.add_line(lines[0])
TL;DR based partly on Joe nice answer.
Opt.1: fig.add_subplot()
def fcn_return_plot():
return plt.plot(np.random.random((10,)))
n = 4
fig = plt.figure(figsize=(n*3,2))
#fig, ax = plt.subplots(1, n, sharey=True, figsize=(n*3,2)) # also works
for index in list(range(n)):
fig.add_subplot(1, n, index + 1)
fcn_return_plot()
plt.title(f"plot: {index}", fontsize=20)
Opt.2: pass ax[index] to a function that returns ax[index].plot()
def fcn_return_plot_input_ax(ax=None):
if ax is None:
ax = plt.gca()
return ax.plot(np.random.random((10,)))
n = 4
fig, ax = plt.subplots(1, n, sharey=True, figsize=(n*3,2))
for index in list(range(n)):
fcn_return_plot_input_ax(ax[index])
ax[index].set_title(f"plot: {index}", fontsize=20)
Outputs respect.
Note: Opt.1 plt.title() changed in opt.2 to ax[index].set_title(). Find more Matplotlib Gotchas in Van der Plas book.
To go deeper in the rabbit hole. Extending my previous answer, one could return a whole ax, and not ax.plot() only. E.g.
If dataframe had 100 tests of 20 types (here id):
dfA = pd.DataFrame(np.random.random((100,3)), columns = ['y1', 'y2', 'y3'])
dfB = pd.DataFrame(np.repeat(list(range(20)),5), columns = ['id'])
dfC = dfA.join(dfB)
And the plot function (this is the key of this whole answer):
def plot_feature_each_id(df, feature, id_range=[], ax=None, legend_bool=False):
feature = df[feature]
if not len(id_range): id_range=set(df['id'])
legend_arr = []
for k in id_range:
pass
mask = (df['id'] == k)
ax.plot(feature[mask])
legend_arr.append(f"id: {k}")
if legend_bool: ax.legend(legend_arr)
return ax
We can achieve:
feature_arr = dfC.drop('id',1).columns
id_range= np.random.randint(len(set(dfC.id)), size=(10,))
n = len(feature_arr)
fig, ax = plt.subplots(1, n, figsize=(n*6,4));
for i,k in enumerate(feature_arr):
plot_feature_each_id(dfC, k, np.sort(id_range), ax[i], legend_bool=(i+1==n))
ax[i].set_title(k, fontsize=20)
ax[i].set_xlabel("test nr. (id)", fontsize=20)

matplotlib get all axes that a given figure contains to apply some settings

I'm writing a function that modifies the axes size and position on a figure, but when comes twin axes it makes a problem:
import matplotlib.pyplot as plt
def fig_layout(fig, vspace = 0.3): # function to make space at the bottom for legend box and
#+ other text input
for ax in ~~~fig.axes~~~: # Here 'fig.axes' is not right, I need to find the exact syntax
#+ I need to put
box = ax.get_position()
ax.set_position([box.x0, box.y0 + box.height * vspace,
box.width, box.height * (1 - vspace)])
x = np.arange(10)
fig = plt.figure()
ax1 = fig.add_subplot(1, 1, 1)
n = 3
line = {}
for i in range(3):
line['lines'].append(ax1.plot(x, i*x**2))
line['labels'].append(r'$y = %i \cdot x^2$'%i)
ax1.set_title('example plot')
ax2 = ax1.twinx()
line['lines'].append(ax2.plot(x, x^-1, label = r'$y = x^-1$'))
line['labels'].append(r'$y = x^-1$')
leg = ax1.legend(line['lines'], line['labels'])
fig_layout(fig)
# I will put the legend box at the bottom of the axes with another function.
plt.show()
I think you can use fig.get_axes().
For example, to modify the title of the first sub-plot, you can do:
plt.gcf().get_axes()[0].set_title("example plot")