Plot in subplot matplotlib with custom (external) function - python

I have a similar question to what has been answered before (matplotlib: make plots in functions and then add each to a single subplot figure). However, I want to have more advanced plots. I'm using this function for plotting (taken from https://towardsdatascience.com/an-introduction-to-bayesian-inference-in-pystan-c27078e58d53):
def plot_trace(param, param_name='parameter', ax=None, **kwargs):
"""Plot the trace and posterior of a parameter."""
# Summary statistics
mean = np.mean(param)
median = np.median(param)
cred_min, cred_max = np.percentile(param, 2.5), np.percentile(param, 97.5)
# Plotting
#ax = ax or plt.gca()
plt.subplot(2,1,1)
plt.plot(param)
plt.xlabel('samples')
plt.ylabel(param_name)
plt.axhline(mean, color='r', lw=2, linestyle='--')
plt.axhline(median, color='c', lw=2, linestyle='--')
plt.axhline(cred_min, linestyle=':', color='k', alpha=0.2)
plt.axhline(cred_max, linestyle=':', color='k', alpha=0.2)
plt.title('Trace and Posterior Distribution for {}'.format(param_name))
plt.subplot(2,1,2)
plt.hist(param, 30, density=True); sns.kdeplot(param, shade=True)
plt.xlabel(param_name)
plt.ylabel('density')
plt.axvline(mean, color='r', lw=2, linestyle='--',label='mean')
plt.axvline(median, color='c', lw=2, linestyle='--',label='median')
plt.axvline(cred_min, linestyle=':', color='k', alpha=0.2, label='95% CI')
plt.axvline(cred_max, linestyle=':', color='k', alpha=0.2)
plt.gcf().tight_layout()
plt.legend()
and I would like to have those two subplots for different parameters. If I use this code, it simply overwrites the plot and ignores the ax parameter. Would you please help me how to make it work also for more than just 2 parameters?
params = [mu_a,mu_tau]
param_names = ['A mean','tau mean']
fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(16,16))
fig.subplots_adjust(hspace=0.5)
fig.suptitle('Convergence and distribution of parameters')
plot_trace(params[0], param_name=param_names[0], ax = ax1)
plot_trace(params[1], param_name=param_names[1], ax = ax2)
The expected result would be to have those plots next to each other
How one subplot should look like (what the function makes). Thank you.

As suggested by both #Sebastian-R and #ImportanceOfBeingErnest, the problem is that you are creating the subplots inside the functions instead of using what is passed to the ax= keyword. In addition, if you want to plot on two different subplots, you need to pass two axes instances to your function
There are two ways to correct the problem:
the first solution would be to re-write your function to use the object-oriented API of matplotlib (this is the solution that I recommend, although it requires more work)
code:
def plot_trace(param, axs, param_name='parameter', **kwargs):
"""Plot the trace and posterior of a parameter."""
# Summary statistics
(...)
# Plotting
ax1 = axs[0]
ax1.plot(param)
ax1.set_xlabel('samples') # notice the difference in notation here
(...)
ax2 = axs[1]
ax2.hist(...); sns.kdeplot(..., ax=ax2)
(...)
the second solution keeps your current code, with the exception that, since the plt.xxx() functions work on the current axes, you need to make the axes that you passed as an argument the current axes:
code:
def plot_trace(param, axs, param_name='parameter', **kwargs):
"""Plot the trace and posterior of a parameter."""
# Summary statistics
(...)
# Plotting
plt.sca(axs[0])
plt.plot(param)
plt.xlabel('samples')
(...)
plt.sca(axs[1])
plt.hist(...)
(...)
Then you need to create the number of subplots that you need at the end (so if you want to plot two parameters, you'll have to create 4 subplots) and call your function as you had wanted:
params = [mu_a,mu_tau]
param_names = ['A mean','tau mean']
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2, figsize=(16,16))
fig.subplots_adjust(hspace=0.5)
fig.suptitle('Convergence and distribution of parameters')
plot_trace(params[0], axs=[ax1, ax3], param_name=param_names[0])
plot_trace(params[1], axs=[ax2, ax4], param_name=param_names[1])

Related

Trying to plot 2 charts side-by-side, but one of them always comes out empty

I have two plots that I generated from my data:
Here the second plot shows the distribution of results from the first one.
What I want is to plot them side-by-side so you could see both the data and the distribution on the same plot. And I want plots to share y-axis as well.
I tried to do the following:
fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(40, 15), sharey=True)
ax1 = sns.lineplot(plotting_df.index, plotting_df.error, color=('#e65400'), lw=2, label='random forest residual error')
ax1 = sns.lineplot(plotting_df.index, plotting_df.val, color=('#9b9b9b'), lw=1, label='current model residual error')
ax1 = sns.lineplot(plotting_df.index, 0, color=('#2293e3'), lw=1)
ax1.xaxis.set_visible(False)
ax1.set_ylabel('Residual Fe bias', fontsize=16)
ax1.set_title('Models residual error comparison', fontsize=20, fontweight='bold')
sns.despine(ax=ax1, top=True, bottom=True, right=True)
ax2 = sns.distplot(results_df.error, hist=True, color=('#e65400'), bins=81,
label='Random forest model', vertical=True)
ax2 = sns.distplot(plotting_df.val, hist=True, color=('#9b9b9b'),
bins=81, label='Rolling averages model', vertical=True)
ax2.set_title('Error distribution comparison between models', fontsize=20, fontweight='bold')
sns.despine(ax=ax2, top=True, right=True)
fig.savefig("blabla.png", format='png')
But when I do run it I get strange results - the first chart is in the second column, whereas I wanted it on the left and the second chart is completely blank. Not sure what I did wrong here.
Both lineplot and distplot accept a matplotlib axes object as an argument, which tells it which axes to plot onto. If no axes is passed into it, then the plot is placed onto the current axes.
You create a figure and 2 axes using :
fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(40, 15), sharey=True)
Therefore, ax2 will be the current axes. So your distplot is being plotted on top of your lineplot, both in ax2.
You need to pass the axes into the seaborn plotting functions.
sns.lineplot(..., ax=ax1)
sns.distplot(..., ax=ax2)

No handles with labels found to put in legend

I'm trying to create a parallelogram in PyPlot. I'm not up to drawing the parallelogram--first I'm putting in the vector arrows--using the following code:
fig = plt.figure()
ax = fig.add_subplot(111)
ax.spines['left'].set_position('zero')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_color('none')
plt.axis([-5,5,-5,5])
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
plt.grid()
plt.arrow(0,0, 3,1, head_width=0.2, color='r', length_includes_head=True, label='u')
plt.arrow(0,0, 1,3, head_width=0.2, color='r', length_includes_head=True, label='v')
plt.arrow(0,0, 4,4, head_width=0.2, color='r', length_includes_head=True, label='u+v')
plt.legend()
This returns the following error:
No handles with labels found to put in legend.
I'm not sure why, because, based on the documentation for plt.arrow(), label is an acceptable kwarg, and plt.legend() should ostensibly be reading that. The rest of the figure draws fine; it's just missing the legend.
It might be late but for anyone with the same issue the solution is using the method legend() for the corresponding ax not as for plt
fig = plt.figure()
ax = fig.add_subplot(111)
ax.spines['left'].set_position('zero')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_color('none')
plt.axis([-5,5,-5,5])
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
plt.grid()
plt.arrow(0,0, 3,1, head_width=0.2, color='r', length_includes_head=True, label='u')
plt.arrow(0,0, 1,3, head_width=0.2, color='r', length_includes_head=True, label='v')
plt.arrow(0,0, 4,4, head_width=0.2, color='r', length_includes_head=True, label='u+v')
ax.legend()
You can explicitly define the elements in the legend.
For full control of which artists have a legend entry, it is possible to pass an iterable of legend artists followed by an iterable of legend labels respectively. Reference
Example:
arr1 = plt.arrow(0,0, 3,1, head_width=0.2, color='r', length_includes_head=True)
arr2 = plt.arrow(0,0, 1,3, head_width=0.2, color='g', length_includes_head=True)
arr3 = plt.arrow(0,0, 4,4, head_width=0.2, color='b', length_includes_head=True)
plt.xlim(0,5)
plt.ylim(0,5)
plt.legend([arr1, arr2, arr3], ['u','v','u+v'])
The error is thrown because you haven't specified the label text
Either do something like this
plt.hist([x01, x02,x03], color=["lightcoral","lightskyblue","slategrey"], stacked=True,
label=['Supressed','Active','Resolved'])
plt.legend()
Or
Do not use plt.legend() if you haven't specified the label text as in the following WRONG example:
plt.hist([x01])
plt.legend()
The above will throw the same error, so either remove legend function or provide what it needs -> label.
Side note: Here x01 is just a list of number for which I am creating a histogram, in the first example they are three list of numbers to create stacked bar chart
The bottom line is this error is thrown because of not specifying legend text and calling/initializing a legend
I had this error when using labels which started with an underscore
plt.plot(x, y, label = '_bad_name')
Removing the front underscore from the labels solved the issue
Assuming you have 2 plots ax and ax2, we can:
get the labels from each y-axis via ax.get_label()
.legend allows an array to be ingested
fig.legend([ax.get_ylabel(), ax2.get_ylabel()], loc='upper right')
I had this same issue and solved it with an understanding that .legend() function has to be after all the instructions that deal with the label attribute. This includes both plt and ax.
So moving ax.legend(*) as the last command.
I hope this helps you too.
Change
ax.plot(-trip_df.stop_lat, -trip_df.stop_lon, label = trip_id)
plt.legend()
to
ax.plot(-trip_df.stop_lat, -trip_df.stop_lon, label = trip_id)
ax.legend()
plt.legend()
I face similar problem like No handles with labels found to put in legend.
First My code look like
figure, axis = pyplot.subplots(nrows=1,ncols=2, figsize=(15, 6), tight_layout=True)
axis[0].legend(title='Country', title_fontsize = 12) #this line
axis[0].pie(x=piechart_result['value_eur'],labels=piechart_result['short_name'])
axis[1].pie(x=piechart_result['value_eur'],labels=piechart_result['short_name')
pyplot.show()
Then I changed to
figure, axis = pyplot.subplots(nrows=1,ncols=2, figsize=(15, 6), tight_layout=True)
axis[0].pie(x=piechart_result['value_eur'],labels=piechart_result['short_name'])
axis[0].legend(title='Country', title_fontsize = 12) # this line
axis[1].pie(x=piechart_result['value_eur'],labels=piechart_result['short_name')
pyplot.show()
this work for me in colab notebook

Subplot problem: how to plot for each plot a histogram by categorical values?

I have a DataFrame with three numerical variables Porosity, Perm and AI. I would like to make a subplot and in each plot, I would like the histogram of the three variables, by a categorical variable 'Facies'. Facies can take only two values: Sand and Shale.
In summary, each subplot needs a histogram and each histogram must be drawn based in the categorical variable Facies, to make a comparison between facies.
So far, I can make it work, but I cannot add the axis title to each subplot.
plt.subplot(311)
plt.hist(df_sd['Porosity'].values, label='Sand', bins=30, alpha=0.6)
plt.hist(df_sh['Porosity'].values, label='Shale', bins=30, alpha=0.6)
ax.set(xlabel='Porosity (fraction)', ylabel='Density', title='Porosity
Histogram')
plt.legend()
plt.subplot(312)
plt.hist(df_sd['log10Perm'].values, label='Sand', bins=30, alpha=0.6,)
plt.hist(df_sh['log10Perm'].values, label='Shale', bins=30, alpha=0.6)
ax.set(xlabel='Permeability (mD)', ylabel='Density', title='Permeability
Histogram')
plt.legend()
plt.subplot(313)
plt.hist(df_sd['AI'].values, label='Sand', bins=30, alpha=0.6)
plt.hist(df_sh['AI'].values, label='Shale', bins=30, alpha=0.6)
ax.set(xlabel='AI (units)', ylabel='Density', title='Acoustic Impedance
Histogram')
plt.legend()
plt.subplots_adjust(left=0.0, bottom=0.0, right=1.5, top=3.5, wspace=0.1,
hspace=0.2);
#I have tried with:
fig, axs = plt.subplots(2, 1)
but when I code
axs[0].hist(df_sd['Porosity'].values, label='Sand', bins=30, alpha=0.6)
axs[0].hist(df_sd['Porosity'].values, label='Shale', bins=30, alpha=0.6)
#But the histogram for shale overrides the histogram for Sand.
I would like to have this result but with both x and y axis with label names. Furthermore, it would be helpful to have a title for each subplot.
I just did a subplot with contours, but I think the framework will be very similar:
fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax, extend in zip(axs.ravel(), extends):
cs = ax.contourf(X, Y, Z, levels, cmap=cmap, extend=extend, origin=origin)
fig.colorbar(cs, ax=ax, shrink=0.9)
ax.set_title("extend = %s" % extend)
ax.locator_params(nbins=4)
plt.show()
I think the main point to note (and this I learned from the link below) is their use of zip(axs.ravel()) in the for loop to establish each ax and then plot what you wish on that ax. I'm fairly certain you can adapt this for your uses.
The full example is available at: https://matplotlib.org/gallery/images_contours_and_fields/contourf_demo.html#sphx-glr-gallery-images-contours-and-fields-contourf-demo-py
I have found an answer:
fig = plt.figure()
ax = fig.add_subplot(111)
ax1 = fig.add_subplot(311)
ax2 = fig.add_subplot(312)
ax2 = fig.add_subplot(313)
plt.subplot(311)
ax1.hist(df_sd['Porosity'].values, label='Sand', bins=30, alpha=0.6)
ax1.hist(df_sh['Porosity'].values, label='Shale', bins=30, alpha=0.6)
ax1.set(xlabel='Porosity (fraction)', ylabel='Density', title='Porosity Histogram')
ax1.legend()

figure/subplot confusion with regard to x/y limits

I am trying to set the x and y limits on a subplot but am having difficultly. I suspect that the difficultly stems from my fundamental lack of understanding of how figures and subplots work. I have read these two questions:
question 1
question 2
I tried to use that approach, but neither had any effect on the x and y limits. Here's my code:
fig = plt.figure(figsize=(9,6))
ax = plt.subplot(111)
ax.hist(sub_dict['b'], bins=30, color='r', alpha=0.3)
ax.set_ylim=([0,200])
ax.set_xlim=([0,100])
plt.xlabel('x')
plt.ylabel('y')
plt.title('title')
plt.show()
I am confused as whether to apply commands to fig or ax? For instance .xlabel and .title don't seem to be available for ax. Thanks
Why don't you do:
Ax = fig.add_subplot(111)
import matplotlib.pyplot as plt
import numpy as np
mu, sigma = 100, 15
x = mu + sigma*np.random.randn(100)
fig = plt.figure(figsize=(9,6))
ax = fig.add_subplot(111)
ax.hist(x, bins=30, color='r', alpha=0.3)
ax.set_ylim=(0, 200)
ax.set_xlim=(0, 100)
plt.xlabel('x')
plt.ylabel('y')
plt.title('title')
plt.show()
I've run your code on some sample code, and I'm attaching the screenshot. I'm not sure this is the desired result but this is what I got.
For a multiplot, where you have subplots in a single figure, you can have several xlabel and one title
fig.title("foobar")
ax.set_xlabel("x")
This is explained in great detail here on the Matplotlib website.
You in your case, use a subplot for just a single plot. This is possible, just doesn't make a lot of sense. Plots like the one below are supposed to be created with the subplot feature:
To answer your question: you can set the x- and y-limits on a per-subplot and per-axis basis by simply addressing the respective subplot directly (ax for subplot 1) and them calling the set_xlabel member function to set the label on the x-axis.
EDIT
For your updated question:
Use this code as inspiration, I had to generate some data on my own so no guarantees:
import matplotlib.pyplot as plt
plt.hist(sub_dict['b'], bins=30, color='r', alpha=0.3)
plt.ylim(0,200)
plt.xlim(0,100)
plt.xlabel('x')
plt.ylabel('y')
plt.title('title')
plt.show()
Bit more googling and I got the following that has worked:
sub_dict = subset(data_dict, 'b', 'a', greater_than, 10)
fig = plt.figure(figsize=(9,6))
ax = fig.add_subplot(111)
ax.hist(sub_dict['b'], bins=30, color='r', alpha=0.3)
plt.ylim(0,250)
plt.xlim(0,100)
plt.xlabel('x')
plt.ylabel('y')
plt.title('title')
plt.show()

Matplotlib subplots: legend and axis-scale

I am plotting 4 subplots (i.e 2 rows 2 columns) in this way:
fig1= plt.figure(figsize=(8,6))
ax1 = fig1.add_subplot(221)
ax1.errorbar((r1),(xi1),fmt='',yerr=(low_err_1,upp_err_1),ls='none',color='black')
ax1.scatter((r1),(xi1),c='red',marker="o",s=30,label= r'$\xi(r)$ $0.0<z<0.5$')
ax1.plot((r1),(curve_y_1),'--',label='fit $0.0<z<0.5$')
ax1.set_xscale('log')
ax1.set_yscale('log')
ax2 = fig1.add_subplot(222)
ax2.errorbar((r2),(xi2),fmt='',yerr=(low_err_2,upp_err_2),ls='none',color='black')
ax2.scatter((r2),(xi2),c='blue',marker="o",s=30,label=r'$\xi(r)$ $0.5<z<1.0$')
ax2.plot((r2),(curve_y_2),'--',label='fit $0.5<z<1.0$')
ax2.set_xscale('log')
ax2.set_yscale('log')
ax3 = fig1.add_subplot(223)
ax3.errorbar((r3),(xi3),fmt='',yerr=(low_err_3,upp_err_3),ls='none',color='black')
ax3.scatter((r3),(xi3),c='yellow',marker="o",s=30,label=r'$\xi(r)$ $1.0<z<1.5$')
ax3.plot((r3),(curve_y_3),'--',label='fit $1.0<z<1.5$')
ax3.set_xscale('log')
ax3.set_yscale('log')
ax4 = fig1.add_subplot(224)
ax4.errorbar((r4),(xi4),fmt='',yerr=(low_err_4,upp_err_4),ls='none',color='black')
ax4.scatter((r4),(xi4),c='black',marker="o",s=30,label=r'$\xi(r)$ $1.5<z<2.0$')
ax4.plot((r4),(curve_y_4),'--',label='fit $1.5<z<2.0$')
ax4.set_xscale('log')
ax4.set_yscale('log')
My questions are:
Is there a way to add legends to all these subplots using a single (common) command, instead of typing ax1.legend(loc = 'best'), ax2.legend(loc = 'best') and so on separately for each subplot?
I would like to set log-scaling for each subplot using a single (common) command. As you can see, now I am setting the axis-scales separately to log for each subplot.
Just define a axes formatting function:
def style_ax(ax):
ax.legend(loc='best')
ax.set_yscale('log')
ax.set_xscale('log')
And than call it when finished:
for ax in [ax1, ax2, ax3, ax4]:
style_ax(ax)

Categories

Resources