Similarly to this question, I am using the subplots keyword in matplotlib except I am drawing pie charts and using pandas.
The labels on my subplots crash with the slice labels when the labels are close to horizontal:
first = pd.Series({'True':2316, 'False': 64})
second = pd.Series({'True':2351, 'False': 29})
df = pd.concat([first, second], axis=1, keys=['First pie', 'Second pie'])
axes = df.plot(kind='pie', subplots=True)
for ax in axes:
ax.set_aspect('equal')
I can alleviate this somewhat by doing as the docs do and adding an explicit figsize, but it still looks pretty cramped:
axes = df.plot(kind='pie', figsize=[10, 4], subplots=True)
for ax in axes:
ax.set_aspect('equal')
Is there a nice way to do better. Something to do with tight_layout maybe?
I think you need padding of lables in y-axis so use labelpad i.e ax.yaxis.labelpad = 20
axes = df.plot(kind='pie', figsize=[10, 4], subplots=True)
for ax in axes:
ax.set_aspect('equal')
ax.yaxis.labelpad = 20
You can move the label to the left using ax.yaxis.set_label_coords(), and then adjust the coords to a value that suits you.
The two inputs to set_label_coords are the x and y coordinate of the label, in Axes fraction coordinates.
For your plot, I found (-0.15, 0.5) to work well (i.e. x=-0.15 means 15% of the axes width to the left of the axes, and y=0.5 means half way up the axes). In general then, assuming you always want the label to be centered on the y axis, you only need to adjust the x coordinate.
I also added some space between the plots using subplots_adjust(wspace=0.5) so that the axes label didn't then overlap with the False label from the other pie.
import pandas as pd
import matplotlib.pyplot as plt
first = pd.Series({'True':2316, 'False': 64})
second = pd.Series({'True':2351, 'False': 29})
df = pd.concat([first, second], axis=1, keys=['First pie', 'Second pie'])
axes = df.plot(kind='pie', subplots=True)
for ax in axes:
ax.set_aspect('equal')
ax.yaxis.set_label_coords(-0.15, 0.5)
plt.subplots_adjust(wspace=0.5)
plt.show()
Related
Say I have data that I want to box plot and overlay with a swarm plot in seaborn, whose colors of the points add additional information on the data.
Question: How can I get box plots to be close to each other for a given x axis value (as is done in hue) without refactorizing x to the hue value and the x axis value?
For example, here I want to overlay the points to the box plot and want the points further colored by ‘sex’. Example:
plt.figure(figsize = (5, 5))
sns.boxplot(x = 'class', y = 'age',
hue = 'embarked', dodge = True, data = df)
sns.swarmplot(x = 'class', y = 'age',
dodge = True,
color = '0.25',
hue = 'sex', data = df)
plt.legend(bbox_to_anchor = (1.5, 1))
EDIT:
The idea would be to have something that looks like the 'S' box for 'Third' in the plot (I made a fake example in powerpoint, so hue in both boxplot and swarmplot are the same to overlay the points on the appropriate boxes).
Is there a way to make this plot without first refactorizing the x-axis to ‘first-S’, ‘first-C’, ‘first-Q’, ‘second-S’, etc and then add hue by ’sex’ in both plots?
Using original x as col and hue as x
To work with two types of hue, seaborn's alternative is to create a FacetGrid. The original x= then becomes the col= (or the row=), and one of the hues becomes the new x=.
Here is an example. Note that aspect= controls the width of the individual subplots (the width being height*aspect).
from matplotlib import pyplot as plt
import seaborn as sns
df = sns.load_dataset('titanic')
g = sns.catplot(kind='box', x='embarked', y='age', hue='sex', col='class',
dodge=True, palette='spring',
height=5, aspect=0.5, data=df)
g.map_dataframe(sns.swarmplot, x='embarked', y='age', hue='sex', palette=['0.25'] * 2, size=2, dodge=True)
for ax in g.axes.flat:
# use title as x-label
ax.set_xlabel(ax.get_title())
ax.set_title('')
# remove y-axis except for the left-most columns
if len(ax.get_ylabel()) == 0:
ax.spines['left'].set_visible(False)
ax.tick_params(axis='y', left=False)
plt.subplots_adjust(wspace=0)
plt.show()
Only using hue for the swarmplot, without dodge
Here is a variant, where the boxplot doesn't use hue, but the swarmplot does. A bit more padding can be added inside the subplots, and the boxplots can be made touching via width=1. Suppressing the outliers of the boxplot looks cleaner, as they would overlap with the outlier of the swarmplot.
from matplotlib import pyplot as plt
import seaborn as sns
import pandas as pd
df = sns.load_dataset('titanic')
df['embarked'] = pd.Categorical(df['embarked'], ['S', 'C', 'Q']) # force a strict order
g = sns.catplot(kind='box', x='embarked', y='age', col='class',
dodge=True, palette='summer', width=1, showfliers=False,
height=5, aspect=0.5, data=df)
g.map_dataframe(sns.swarmplot, x='embarked', y='age', hue='sex', palette=['b', 'r'], size=2, dodge=False)
g.add_legend()
for ax in g.axes.flat:
# use title as x-label
ax.set_xlabel(ax.get_title())
ax.set_title('')
# remove y-axis except for the left-most columns
if len(ax.get_ylabel()) == 0:
ax.spines['left'].set_visible(False)
ax.tick_params(axis='y', left=False)
xmin, xmax = ax.get_xlim()
ax.set_xlim(xmin - 0.2, xmax + 0.2) # add a bit more spacing between the groups
plt.subplots_adjust(wspace=0)
plt.show()
I have the below plot, however, I am struggling with the 3 questions below....
How can I move X-axis labels (1-31) to the top of the plot?
How can I change formating of the color bar from (7000 to 7k etc.)
How can I change the color from gray to another cmap like "Reds"?
Can I change the figure size? plt.figure(figsize=(20,10)) does not work?
data1 = pd.read_csv("a2data/data1.csv")
data2 = pd.read_csv("a2data/data2.csv")
merged_df = pd.concat([data1, data2])
merged_df.set_index(['month', 'day'], inplace=True)
merged_df.sort_index(inplace=True)
merged_df2=merged_df.groupby(['month', 'day']).deaths.mean().unstack('day')
plt.imshow(merged_df2)
plt.xticks(np.arange(merged_df2.shape[1]), merged_df2.columns)
plt.yticks(np.arange(merged_df2.shape[0]), merged_df2.index)
plt.colorbar(orientation="horizontal")
plt.show()
Let's try:
# create a single subplot to access the axis
fig, ax = plt.subplots()
# passing the `cmap` for custom color
plt.imshow(df, cmap='hot', origin='upper')
# draw the colorbar
cb = plt.colorbar(orientation="horizontal")
# extract the ticks on colorbar
ticklabels = cb.get_ticks()
# reformat the ticks
cb.set_ticks(ticklabels)
cb.set_ticklabels([f'{int(x//1000)}K' for x in ticklabels])
# move x ticks to the top
ax.xaxis.tick_top()
plt.show()
Output:
Try this to invert the y axis:
ax = plt.yticks(np.arange(merged_df2.shape[0]), merged_df2.index)
plt.colorbar(orientation="horizontal")
ax.invert_yaxis()
plt.show()
I think for the color, you can find better in the pyplot documentation, https://matplotlib.org/3.3.1/api/_as_gen/matplotlib.pyplot.plot.html#matplotlib.pyplot.plot
I am trying to make the below grid of plots a little bit cleaner. I don't want the tick marks on the left side and the bottom to overlap. I have tried to despine the axes by trying the below code, but it doesn't seem to work. Anyone have any suggestions?
fig, ax = plt.subplots(figsize=(15,10))
cols = ['x6', 'x7', 'x16', 'x17']
subset = df[cols]
normed_df = (subset-subset.min())/(subset.max()-subset.min())
style.use('seaborn-darkgrid')
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(False)
for sp in range(4):
ax = fig.add_subplot(2,2, sp+1)
ax.hist(normed_df[cols[sp]], density=True)
normed_df[cols[sp]].plot.kde(ax=ax)
ax.tick_params(bottom="off", top="off", left="off", right="off")
After running the above code, I am getting the following plots, however, the ticks are still overlapping.
either do what #Arne suggested:
fig, ax = plt.subplots(rows, cols) #makes a grid of subplots
or make your first two lines this:
fig, ax = plt.subplots(figsize=(15,10))
ax.axis('off')
this will remove the axis around the entire subplot before adding your additional subplots
When you call plt.subplots() without specifying a grid, it creates those axes across the whole figure whose tick marks and labels interfere with your subplot tick labels in the final plot. So change your first line of code to this:
fig, ax = plt.subplots(2, 2, figsize=(15,10))
Im trying to plot a scatter matrix. I'm building on the example given in this thread Is there a function to make scatterplot matrices in matplotlib?. Here I have just modified the code slightly to make the axis visible for all the subplots. The modified code is given below
import itertools
import numpy as np
import matplotlib.pyplot as plt
def main():
np.random.seed(1977)
numvars, numdata = 4, 10
data = 10 * np.random.random((numvars, numdata))
fig = scatterplot_matrix(data, ['mpg', 'disp', 'drat', 'wt'],
linestyle='none', marker='o', color='black', mfc='none')
fig.suptitle('Simple Scatterplot Matrix')
plt.show()
def scatterplot_matrix(data, names, **kwargs):
"""Plots a scatterplot matrix of subplots. Each row of "data" is plotted
against other rows, resulting in a nrows by nrows grid of subplots with the
diagonal subplots labeled with "names". Additional keyword arguments are
passed on to matplotlib's "plot" command. Returns the matplotlib figure
object containg the subplot grid."""
numvars, numdata = data.shape
fig, axes = plt.subplots(nrows=numvars, ncols=numvars, figsize=(8,8))
fig.subplots_adjust(hspace=0.05, wspace=0.05)
for ax in axes.flat:
# Hide all ticks and labels
ax.xaxis.set_visible(True)
ax.yaxis.set_visible(True)
# # Set up ticks only on one side for the "edge" subplots...
# if ax.is_first_col():
# ax.yaxis.set_ticks_position('left')
# if ax.is_last_col():
# ax.yaxis.set_ticks_position('right')
# if ax.is_first_row():
# ax.xaxis.set_ticks_position('top')
# if ax.is_last_row():
# ax.xaxis.set_ticks_position('bottom')
# Plot the data.
for i, j in zip(*np.triu_indices_from(axes, k=1)):
for x, y in [(i,j), (j,i)]:
axes[x,y].plot(data[x], data[y], **kwargs)
# Label the diagonal subplots...
for i, label in enumerate(names):
axes[i,i].annotate(label, (0.5, 0.5), xycoords='axes fraction',
ha='center', va='center')
# Turn on the proper x or y axes ticks.
for i, j in zip(range(numvars), itertools.cycle((-1, 0))):
axes[j,i].xaxis.set_visible(True)
axes[i,j].yaxis.set_visible(True)
fig.tight_layout()
plt.xticks(rotation=45)
fig.show()
return fig
main()
I cant seem to be able to rotate the x-axis text of all the subplots. As it can be seen, i have tried the plt.xticks(rotation=45) trick. But this seems to perform the rotation for the last subplot alone.
Just iterate through the axes tied to the figure, set the active axes to the iterated object, and modify:
for ax in fig.axes:
matplotlib.pyplot.sca(ax)
plt.xticks(rotation=90)
plt only acts on the current active axes. You should bring it inside your last loop where you set some of the labels visibility to True:
# Turn on the proper x or y axes ticks.
for i, j in zip(range(numvars), itertools.cycle((-1, 0))):
axes[j,i].xaxis.set_visible(True)
axes[i,j].yaxis.set_visible(True)
for tick in axes[i,j].get_xticklabels():
tick.set_rotation(45)
for tick in axes[j,i].get_xticklabels():
tick.set_rotation(45)
for ax in fig.axes:
ax.tick_params(labelrotation=90)
I have this gridspec subplot in python:
It is a 3x3 Gridspec matrix of seaborn heatmaps with a single colorbar occupying the whole third column. I would like to make the colorbar look shorter. The way I see it, there are two choices:
a. Either I make the plot of the colorbar shorter
b. I manage to reduce the available space for the last column in the gridspec.
Unfortunately, I haven't found a proper way to do it. Could anyone help me? Here the code for clarity.
Thank you very much in advance.
fig=plt.figure(figsize=(10,10))
gs = gridspec.GridSpec(2, 3, width_ratios=[1,1,0.1], height_ratios=[1,1])
gs.update(left=0.1, right=0.95, wspace=0.2, hspace=0.4)
#(0,0) PLOT
axh=plt.subplot( gs[0,0] )
sns.heatmap(M11,cmap="RdBu_r",cbar=False,linewidths=.5,xticklabels=True,yticklabels=True)
axh.set_xlabel('x_axis',fontsize=15);
axh.set_ylabel('y_axis',fontsize=15)
#(0,1) PLOT
ax0=plt.subplot( gs[0,1] )
sns.heatmap(M12,vmin=0,vmax=1,annot=False,cmap="RdBu_r",cbar=False,linewidths=.5,xticklabels=True,yticklabels=False)
ax0.set_xlabel('x_axis',fontsize=15);
#(1,0) PLOT
axh1=plt.subplot( gs[1,0],sharex=axh )
sns.heatmap(M21,cmap="RdBu_r",cbar=False,linewidths=.5,xticklabels=True,yticklabels=True)
axh1.set_xlabel('x_axis',fontsize=15);
axh1.set_ylabel('y_axis',fontsize=15)
#(1,1) PLOT
ax3=plt.subplot( gs[1,1] )
sns.heatmap(M22,vmin=0,vmax=1,annot=False,cmap="RdBu_r",cbar=False,linewidths=.5,xticklabels=True,yticklabels=False)
ax3.set_xlabel('x_axis',fontsize=15);
#(:,4) PLOT: COLORBAR
ax6=plt.subplot(gs[:,2] )
cb1 = matplotlib.colorbar.ColorbarBase(ax6, cmap="RdBu_r")
You don't need to plot the colorbar in an extra subplot grid
I would recommend to look here: https://matplotlib.org/3.1.1/gallery/axes_grid1/demo_colorbar_with_inset_locator.html
You can plot the colorbar like:
fig = plt.figure(figsize=(6, 6))
grid = plt.GridSpec(4, 4, hspace=0, wspace=0)
main_ax = fig.add_subplot(grid[:-1, 1:])
y_hist = fig.add_subplot(grid[:-1, 0])
x_hist = fig.add_subplot(grid[-1, 1:]
im = main_ax.imshow(array, **kwags) # <- your plots here
y_hist.plot(x,y)
x_hist.plot(x,y)
axins = inset_axes(x_hist, # here using axis of the lowest plot
width="5%", # width = 5% of parent_bbox width
height="340%", # height : 340% good for a (4x4) Grid
loc='lower left',
bbox_to_anchor=(1.05, 0.3, 1, 1),
bbox_transform=x_hist.transAxes,
borderpad=0,
)
cb = fig.colorbar(im, cax=axins)
Result of axins setting for my plot