ploting two pie charts in one figure with space - pandas

wanted to draw two pie charts beside each other, but the text from right figure goes iside let fig. I tried subplots_adjust, but did not work. I will appreciate if you suggest a way to draw the figure properly. Also, is there a way to label each individual pie chart?
labes1,values1=[0, 1], [0.7099673202614379, 0.2900326797385621]
labels2,values2=['Pass', 'Withdrawn', 'Fail', 'Distinction'],[0.3510392609699769,0.33102386451116245,0.2763664357197845,0.04157043879907621]
fig = plt.figure(figsize=(5,5),dpi=300)
ax1 = fig.add_subplot(121)
ax1.pie(values1,radius=1.5,autopct='%0.2f%%',labels=labels1,label="")
ax2 = fig.add_subplot(122)
ax2.pie(values2,radius=1.5,autopct='%0.2f%%',labels=labels2,label="")
plt.subplots_adjust(left=0.1,
bottom=0.1,
right=0.9,
top=0.9,
wspace=0.4,
hspace=0.9)
plt.show()

You can do this:
import matplotlib.pyplot as plt
import pandas as pd
labels1, values1 = [0, 1], [0.7099673202614379, 0.2900326797385621]
labels2, values2 = ['Pass', 'Withdrawn', 'Fail', 'Distinction'], [0.3510392609699769, 0.33102386451116245, 0.2763664357197845, 0.04157043879907621]
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14,5), dpi=300, gridspec_kw={'width_ratios': [1, 1.5]})
ax1.pie(values1, radius=1.5, autopct='%0.2f%%', labels=labels1)
ax1.set_title('Chart 1')
ax2.pie(values2, radius=1.5, autopct='%0.2f%%', labels=labels2)
ax2.set_title('Chart 2')
plt.show()
which gives

Related

How to align a single legend over two seaborn barplots?

I would like to have a single legend that nicely fits on top of both the subplots (doesn't necessarily need to span the entire width of the plots, but needs to be outside the plot). I know you can work with bbox_to_anchor() but somehow this doesn't seem to work nicely. It always moves one subplot away.
fig, ax = plt.subplots(1, 2)
sns.barplot(x="day", y="total_bill", hue="sex", data=tips, ax = ax[0])
ax[0].legend_.remove()
sns.barplot(x="day", y="total_bill", hue="sex", data=tips, ax = ax[1])
sns.move_legend(ax[1], loc = "center", bbox_to_anchor=(-0.5, 1.1), ncol=2, title=None, frameon=False)
fig.tight_layout()
There are a couple of ways that I would approach closing the gap.
1: Use a sns.catplot:
This potentially requires doubling your data, though if you're plotting different variables in each subplot you may be able to melt your data
import pandas as pd
import seaborn as sns
# Load the dataset twice
tips_a = sns.load_dataset("tips")
tips_b = sns.load_dataset("tips")
# Add a dummy facet variable
tips_a["col"] = "A"
tips_b["col"] = "B"
# Concat them
tips = pd.concat([tips_a, tips_b])
# Use the dummy variable for the `col` param
g = sns.catplot(x="day", y="total_bill", hue="sex", data=tips, kind="bar", col="col")
# Remove the titles and move the legend
g.set_titles("")
sns.move_legend(g, loc="upper center", ncol=2, title=None, frameon=False)
2: autoscale the axes
This still requires a little bit of bbox_to_anchor fiddling and you probably want to change the right y-axis label (and ticks/ticklabels).
import matplotlib.pyplot as plt
import seaborn as sns
fig, ax = plt.subplots(1, 2, figsize=(7, 4))
sns.barplot(x="day", y="total_bill", hue="sex", data=tips, ax=ax[0])
ax[0].legend_.remove()
sns.barplot(x="day", y="total_bill", hue="sex", data=tips, ax=ax[1])
sns.move_legend(
ax[1],
loc="upper center",
bbox_to_anchor=(-0.1, 1.1),
ncol=2,
title=None,
frameon=False,
)
ax[0].autoscale()
ax[1].autoscale()

Matplotlib: different width subplots sharing same x-axis

I want 3 rows of subplots each of different widths, but which all share the same X-axis, such as in the rough mock-up below. How can I do this? Can I use sharex=True even in GridSpec-adjusted plots?
You can place the axes by hand, or another method is to use an inset_axes:
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(3, 1, constrained_layout=True, sharex=True, sharey=True)
ylim=[-3, 3]
axs[2].plot(np.random.randn(500))
axs[2].set_ylim(ylim)
xlim = axs[2].get_xlim()
ax0 = axs[0].inset_axes([300, ylim[0], xlim[1]-300, ylim[1]-ylim[0]], transform=axs[0].transData)
ax0.set_ylim(ylim)
ax0.set_xlim([300, xlim[1]])
axs[0].axis('off')
ax0.plot(np.arange(300, 500), np.random.randn(200))
ax1 = axs[1].inset_axes([150, ylim[0], xlim[1] - 150, ylim[1]-ylim[0]], transform=axs[1].transData)
ax1.set_ylim(ylim)
ax1.set_xlim([150, xlim[1]])
axs[1].axis('off')
ax1.plot(np.arange(150, 500), np.random.randn(350))
plt.show()
You can pass which axes to use as reference for sharing axes when you create your subplot
fig = plt.figure()
gs = matplotlib.gridspec.GridSpec(3,3, figure=fig)
ax1 = fig.add_subplot(gs[0,2])
ax2 = fig.add_subplot(gs[1,1:], sharex=ax1)
ax3 = fig.add_subplot(gs[2,:], sharex=ax1)
ax1.plot([1,5,0])

Matplotlib: combining two bar charts

I'm trying to generate 'violin'-like bar charts, however i'm running in several difficulties described bellow...
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
# init data
label = ['aa', 'b', 'cc', 'd']
data1 = [5, 7, 6, 9]
data2 = [7, 3, 6, 1]
data1_minus = np.array(data1)*-1
gs = gridspec.GridSpec(1, 2, top=0.95, bottom=0.07,)
fig = plt.figure(figsize=(7.5, 4.0))
# adding left bar chart
ax1 = fig.add_subplot(gs[0])
ax1.barh(pos, data1_minus)
ax1.yaxis.tick_right()
ax1.yaxis.set_label(label)
# adding right bar chart
ax2 = fig.add_subplot(gs[1], sharey=ax1)
ax2.barh(pos, data2)
Trouble adding 'label' as labels for both charts to share.
Centering the labels between the both plots (as well as vertically in the center of each bar)
Keeping just the ticks on the outer yaxis (not inner, where the labels would go)
If I understand the question correctly, I believe these changes accomplish what you're looking for:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
# init data
label = ['aa', 'b', 'cc', 'd']
data1 = [5, 7, 6, 9]
data2 = [7, 3, 6, 1]
data1_minus = np.array(data1)*-1
gs = gridspec.GridSpec(1, 2, top=0.95, bottom=0.07,)
fig = plt.figure(figsize=(7.5, 4.0))
pos = np.arange(4)
# adding left bar chart
ax1 = fig.add_subplot(gs[0])
ax1.barh(pos, data1_minus, align='center')
# set tick positions and labels appropriately
ax1.yaxis.tick_right()
ax1.set_yticks(pos)
ax1.set_yticklabels(label)
ax1.tick_params(axis='y', pad=15)
# adding right bar chart
ax2 = fig.add_subplot(gs[1], sharey=ax1)
ax2.barh(pos, data2, align='center')
# turn off the second axis tick labels without disturbing the originals
[lbl.set_visible(False) for lbl in ax2.get_yticklabels()]
plt.show()
This yields this plot:
As for keeping the actual numerical ticks (if you want those), the normal matplotlib interface ties the ticks pretty closely together when the axes are shared (or twinned). However, the axes_grid1 toolkit can allow you more control, so if you want some numerical ticks you can replace the entire ax2 section above with the following:
from mpl_toolkits.axes_grid1 import host_subplot
ax2 = host_subplot(gs[1], sharey=ax1)
ax2.barh(pos, data2, align='center')
par = ax2.twin()
par.set_xticklabels('')
par.set_yticks(pos)
par.set_yticklabels([str(x) for x in pos])
[lbl.set_visible(False) for lbl in ax2.get_yticklabels()]
which yields:

matplotlib top bottom ticks different

Is there a way to have top ticks in and bottom tick out in matplotlib plots?
Sometimes I have data hiding ticks and I would like to set ticks out only for the side that is affected.
The following code will affect both top and bottom or both right and left.
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot( 111 )
ax.plot( [0, 1, 3], 'o' )
ax.tick_params( direction = 'out' )
plt.show()
With the upgrade from #11859 for matplotlib>=3.1.0 we can now use a Secondary Axis via secondary_xaxis and secondary_yaxis to achieve independent tick directions:
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot( 111 )
ax.plot( [0, 1, 3], 'o' )
ax.tick_params( direction = 'out' )
ax_r = ax.secondary_yaxis('right')
ax_t = ax.secondary_xaxis('top')
ax_r.tick_params(axis='y', direction='in')
ax_t.tick_params(axis='x', direction='inout')
which produces this figure:
You can have twin axes, then you can set the properties for each side separately:
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot([0, 1, 3], 'o')
axR = ax.twinx()
axT = ax.twiny()
ax.tick_params(direction = 'out')
axR.tick_params(direction = 'in')
ax.tick_params(direction = 'out')
axT.tick_params(direction = 'in')
plt.show()

Matplotlib: Don't show errorbars in legend

I'm plotting a series of data points with x and y error but do NOT want the errorbars to be included in the legend (only the marker). Is there a way to do so?
Example:
import matplotlib.pyplot as plt
import numpy as np
subs=['one','two','three']
x=[1,2,3]
y=[1,2,3]
yerr=[2,3,1]
xerr=[0.5,1,1]
fig,(ax1)=plt.subplots(1,1)
for i in np.arange(len(x)):
ax1.errorbar(x[i],y[i],yerr=yerr[i],xerr=xerr[i],label=subs[i],ecolor='black',marker='o',ls='')
ax1.legend(loc='upper left', numpoints=1)
fig.savefig('test.pdf', bbox_inches=0)
You can modify the legend handler. See the legend guide of matplotlib.
Adapting your example, this could read:
import matplotlib.pyplot as plt
import numpy as np
subs=['one','two','three']
x=[1,2,3]
y=[1,2,3]
yerr=[2,3,1]
xerr=[0.5,1,1]
fig,(ax1)=plt.subplots(1,1)
for i in np.arange(len(x)):
ax1.errorbar(x[i],y[i],yerr=yerr[i],xerr=xerr[i],label=subs[i],ecolor='black',marker='o',ls='')
# get handles
handles, labels = ax1.get_legend_handles_labels()
# remove the errorbars
handles = [h[0] for h in handles]
# use them in the legend
ax1.legend(handles, labels, loc='upper left',numpoints=1)
plt.show()
This produces
Here is an ugly patch:
pp = []
colors = ['r', 'b', 'g']
for i, (y, yerr) in enumerate(zip(ys, yerrs)):
p = plt.plot(x, y, '-', color='%s' % colors[i])
pp.append(p[0])
plt.errorbar(x, y, yerr, color='%s' % colors[i])
plt.legend(pp, labels, numpoints=1)
Here is a figure for example:
The accepted solution works in simple cases but not in general. In particular, it did not work in my own more complex situation.
I found a more robust solution, which tests for ErrorbarContainer, which did work for me. It was proposed by Stuart W D Grieve and I copy it here for completeness
import matplotlib.pyplot as plt
from matplotlib import container
label = ['one', 'two', 'three']
color = ['red', 'blue', 'green']
x = [1, 2, 3]
y = [1, 2, 3]
yerr = [2, 3, 1]
xerr = [0.5, 1, 1]
fig, (ax1) = plt.subplots(1, 1)
for i in range(len(x)):
ax1.errorbar(x[i], y[i], yerr=yerr[i], xerr=xerr[i], label=label[i], color=color[i], ecolor='black', marker='o', ls='')
handles, labels = ax1.get_legend_handles_labels()
handles = [h[0] if isinstance(h, container.ErrorbarContainer) else h for h in handles]
ax1.legend(handles, labels)
plt.show()
It produces the following plot (on Matplotlib 3.1)
I works for me if I set the label argument as a None type.
plt.errorbar(x, y, yerr, label=None)