The image below shows, what i want, 3 different plots in one execution but using a function
enter image description here
enter image description here
I used the following code:
def box_hist_plot(data):
sns.set()
ax, fig = plt.subplots(1,3, figsize=(20,5))
sns.boxplot(x=data, linewidth=2.5, ax=fig[0])
plt.hist(x=data, bins=50, density=True, ax = fig[1])
sns.violinplot(x = data, ax=fig[2])
and i got this error:
inner() got multiple values for argument 'ax'
Besides the fact that you should not call a Figure object ax and an array of Axes object fig, your problem comes from the line plt.hist(...,ax=...). plt.hist() should not take an ax= parameter, but is meant to act on the "current" axes. If you want to specify which Axes you want to plot, you should use Axes.hist().
def box_hist_plot(data):
sns.set()
fig, axs = plt.subplots(1,3, figsize=(20,5))
sns.boxplot(x=data, linewidth=2.5, ax=axs[0])
axs[1].hist(x=data, bins=50, density=True)
sns.violinplot(x = data, ax=axs[2])
Related
I haven't been able to find a solution to this.. Say I define some plotting function so that I don't have to copy-paste tons of code every time I make similar plots...
What I'd like to do is use this function to create a few different plots individually and then put them together as subplots into one figure. Is this even possible? I've tried the following but it just returns blanks:
import numpy as np
import matplotlib.pyplot as plt
# function to make boxplots
def make_boxplots(box_data):
fig, ax = plt.subplots()
box = ax.boxplot(box_data)
#plt.show()
return ax
# make some data:
data_1 = np.random.normal(0,1,500)
data_2 = np.random.normal(0,1.1,500)
# plot it
box1 = make_boxplots(box_data=data_1)
box2 = make_boxplots(box_data=data_2)
plt.close('all')
fig, ax = plt.subplots(2)
ax[0] = box1
ax[1] = box2
plt.show()
I tend to use the following template
def plot_something(data, ax=None, **kwargs):
ax = ax or plt.gca()
# Do some cool data transformations...
return ax.boxplot(data, **kwargs)
Then you can experiment with your plotting function by simply calling plot_something(my_data) and you can specify which axes to use like so.
fig, (ax1, ax2) = plt.subplots(2)
plot_something(data1, ax1, color='blue')
plot_something(data2, ax2, color='red')
plt.show() # This should NOT be called inside plot_something()
Adding the kwargs allows you to pass in arbitrary parameters to the plotting function such as labels, line styles, or colours.
The line ax = ax or plt.gca() uses the axes you have specified or gets the current axes from matplotlib (which may be new axes if you haven't created any yet).
I would like to generate a centered figure legend for subplot(s), for which there is a single label. For my actual use case, the number of subplot(s) is greater than or equal to one; it's possible to have a 2x2 grid of subplots and I would like to use the figure-legend instead of using ax.legend(...) since the same single label entry will apply to each/every subplot.
As a brief and simplified example, consider the code just below:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(10)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y, color='orange', label='$f(x) = sin(x)$')
fig.subplots_adjust(bottom=0.15)
fig.legend(mode='expand', loc='lower center')
plt.show()
plt.close(fig)
This code will generate the figure seen below:
I would like to use the mode='expand' kwarg to make the legend span the entire width of the subplot(s); however, doing so prevents the label from being centered. As an example, removing this kwarg from the code outputs the following figure.
Is there a way to use both mode='expand' and also have the label be centered (since there is only one label)?
EDIT:
I've tried using the bbox_to_anchor kwargs (as suggested in the docs) as an alternative to mode='expand', but this doesn't work either. One can switch out the fig.legend(...) line for the line below to test for yourself.
fig.legend(loc='lower center', bbox_to_anchor=(0, 0, 1, 0.5))
The handles and labels are flush against the left side of the legend. There is no mechanism to allow for aligning them.
A workaround could be to use 3 columns of legend handles and fill the first and third with a transparent handle.
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(10)
y = np.sin(x)
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.15)
line, = ax.plot(x, y, color='orange', label='$f(x) = sin(x)$')
proxy = plt.Rectangle((0,0),1,1, alpha=0)
fig.legend(handles=[proxy, line, proxy], mode='expand', loc='lower center', ncol=3)
plt.show()
Here is a toy piece of code that illustrates my problem:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
xdata, ydata = [], []
ln, = plt.plot([], [], '-o', animated=True)
def init():
ax.set_xlim(0, 2*np.pi)
ax.set_ylim(-1, 1)
return ln,
def update(frame):
xdata.append(frame)
ydata.append(np.sin(frame))
ln.set_data(xdata, ydata)
ax.set_xlim(np.amin(xdata), np.amax(xdata))
return ln,
ani = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 128),
init_func=init, blit=True)
plt.show()
If I set blit=True then the data points are plotted just how I want them. However, the x-axis labels/ticks remain static.
If I set blit=False then the x-axis labels and ticks update just how I want them. However, none of the data points are ever plotted.
How can I get both the plotted data (sine curve) and the x-asis data to update"?
First concerning blitting: Blitting is only applied to the content of the axes. It will affect the inner part of the axes, but not the outer axes decorators. Hence if using blit=True the axes decorators are not updated. Or inversely put, if you want the scale to update, you need to use blit=False.
Now, in the case from the question this leads to the line not being drawn. The reason is that the line has its animated attribute set to True. However, "animated" artists are not drawn by default. This property is actually meant to be used for blitting; but if no blitting is performed it will result in the artist neither be drawn nor blitted. It might have been a good idea to call this property blit_include or something similar to avoid confusion from its name.
Unfortunately, it looks like it's also not well documented. You find however a comment in the source code saying
# if the artist is animated it does not take normal part in the
# draw stack and is not expected to be drawn as part of the normal
# draw loop (when not saving) so do not propagate this change
So in total, one can ignore the presence of this argument, unless you use blitting. Even when using blitting, it can be ignored in most cases, because that property is set internally anyways.
To conclude the solution here is to not use animated and to not use blit.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
xdata, ydata = [], []
ln, = plt.plot([], [], '-o')
def init():
ax.set_xlim(0, 2*np.pi)
ax.set_ylim(-1, 1)
def update(frame):
xdata.append(frame)
ydata.append(np.sin(frame))
ln.set_data(xdata, ydata)
ax.set_xlim(np.amin(xdata), np.amax(xdata))
ani = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 128),
init_func=init)
plt.show()
let say I have this code:
num_rows = 10
num_cols = 1
fig, axs = plt.subplots(num_rows, num_cols, sharex=True)
for i in xrange(num_rows):
ax = axs[i]
ax.plot(np.arange(10), np.arange(10)**i)
plt.show()
the result figure has too much info and now I want to pick 1 of the axes and draw it alone in a new figure
I tried doing something like this
def on_click(event):
axes = event.inaxes.get_axes()
fig2 = plt.figure(15)
fig2.axes.append(axes)
fig2.show()
fig.canvas.mpl_connect('button_press_event', on_click)
but it didn't quite work. what would be the correct way to do it? searching through the docs and throw SE gave hardly any useful result
edit:
I don't mind redrawing the chosen axes, but I'm not sure how can I tell which of the axes was chosen so if that information is available somehow then it is a valid solution for me
edit #2:
so I've managed to do something like this:
def on_click(event):
fig2 = plt.figure(15)
fig2.clf()
for line in event.inaxes.axes.get_lines():
xydata = line.get_xydata()
plt.plot(xydata[:, 0], xydata[:, 1])
fig2.show()
which seems to be "working" (all the other information is lost - labels, lines colors, lines style, lines width, xlim, ylim, etc...)
but I feel like there must be a nicer way to do it
thanks
Copying the axes
The inital answer here does not work, we keep it for future reference and also to see why a more sophisticated approach is needed.
#There are some pitfalls on the way with the initial approach.
#Adding an `axes` to a figure can be done via `fig.add_axes(axes)`. However, at this point,
#the axes' figure needs to be the figure the axes should be added to.
#This may sound a bit like running in circles but we can actually set the axes'
#figure as `axes.figure = fig2` and hence break out of this.
#One might then also position the axes in the new figure to take the usual dimensions.
#For this a dummy axes can be added first, the axes can change its position to the position
#of the dummy axes and then the dummy axes is removed again. In total, this would look as follows.
import matplotlib.pyplot as plt
import numpy as np
num_rows = 10
num_cols = 1
fig, axs = plt.subplots(num_rows, num_cols, sharex=True)
for i in xrange(num_rows):
ax = axs[i]
ax.plot(np.arange(10), np.arange(10)**i)
def on_click(event):
axes = event.inaxes
if not axes: return
fig2 = plt.figure()
axes.figure=fig2
fig2.axes.append(axes)
fig2.add_axes(axes)
dummy = fig2.add_subplot(111)
axes.set_position(dummy.get_position())
dummy.remove()
fig2.show()
fig.canvas.mpl_connect('button_press_event', on_click)
plt.show()
#So far so good, however, be aware that now after a click the axes is somehow
#residing in both figures, which can cause all sorts of problems, e.g. if you
# want to resize or save the initial figure.
Instead, the following will work:
Pickling the figure
The problem is that axes cannot be copied (even deepcopy will fail). Hence to obtain a true copy of an axes, you may need to use pickle. The following will work. It pickles the complete figure and removes all but the one axes to show.
import matplotlib.pyplot as plt
import numpy as np
import pickle
import io
num_rows = 10
num_cols = 1
fig, axs = plt.subplots(num_rows, num_cols, sharex=True)
for i in range(num_rows):
ax = axs[i]
ax.plot(np.arange(10), np.arange(10)**i)
def on_click(event):
if not event.inaxes: return
inx = list(fig.axes).index(event.inaxes)
buf = io.BytesIO()
pickle.dump(fig, buf)
buf.seek(0)
fig2 = pickle.load(buf)
for i, ax in enumerate(fig2.axes):
if i != inx:
fig2.delaxes(ax)
else:
axes=ax
axes.change_geometry(1,1,1)
fig2.show()
fig.canvas.mpl_connect('button_press_event', on_click)
plt.show()
Recreate plots
The alternative to the above is of course to recreate the plot in a new figure each time the axes is clicked. To this end one may use a function that creates a plot on a specified axes and with a specified index as input. Using this function during figure creation as well as later for replicating the plot in another figure ensures to have the same plot in all cases.
import matplotlib.pyplot as plt
import numpy as np
num_rows = 10
num_cols = 1
colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]
labels = ["Label {}".format(i+1) for i in range(num_rows)]
def myplot(i, ax):
ax.plot(np.arange(10), np.arange(10)**i, color=colors[i])
ax.set_ylabel(labels[i])
fig, axs = plt.subplots(num_rows, num_cols, sharex=True)
for i in xrange(num_rows):
myplot(i, axs[i])
def on_click(event):
axes = event.inaxes
if not axes: return
inx = list(fig.axes).index(axes)
fig2 = plt.figure()
ax = fig2.add_subplot(111)
myplot(inx, ax)
fig2.show()
fig.canvas.mpl_connect('button_press_event', on_click)
plt.show()
If you have, for example, a plot with three lines generated by the function plot_something, you can do something like this:
fig, axs = plot_something()
ax = axs[2]
l = list(ax.get_lines())[0]
l2 = list(ax.get_lines())[1]
l3 = list(ax.get_lines())[2]
plot(l.get_data()[0], l.get_data()[1])
plot(l2.get_data()[0], l2.get_data()[1])
plot(l3.get_data()[0], l3.get_data()[1])
ylim(0,1)
I've managed to make a set of subplots using hist2d and ImageGrid with the code below:
from mpl_toolkits.axes_grid1 import ImageGrid
fig = figure(figsize(20, 60))
grid = ImageGrid(fig, 111, nrows_ncols=(1, 3), axes_pad=0.25)
for soa, ax in zip(soalist, grid):
# grab my data from pandas DataFrame...
samps = allsubs[allsubs['soa'] == soa]
x, y = samps['x'], samps['y']
# calls hist2d and returns the Image returned by hist2d
img = gazemap(x, y, ax, std=True, mean=True)
ax.set_title("{0} ms".format(soa * 1000))
# attempt to show a colorbar for that image
grid.cbar_axes[-1].colorbar(img)
show() # threw this in for good measure, but doesn't help!
I get no explicit error (which is good, because I passed an Image to colorbar), but my colorbar does not appear. What gives?
Okay, I fixed it!
All I had to do was pass the cbar_mode and cbar_location kwargs to ImageGrid!