Passing same x axis label to matplotlib subplots of barplots - python

I'm trying to create subplots of barplots. matplotlib is funny with labeling the x axis of barplots so you need to pass an index then use the xticks function to pass the labels. I want to create 2 subplots that each have the same x axis labels but with the code below I can only pass the labels onto the last bar plot. My question is how can I pass the labels to both bar plots?
fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(18,15))
r1 = axes[0].bar(idx, data1, align='center')
axes[0].set_title('title1')
r2 = axes[1].bar(idx, data2, align='center')
axes[1].set_title('title2')
plt.xticks(idx, idx_labels, rotation=45)

plt.xticks as all other pyplot commands relate to the currently active axes. Having produced several axes at once via plt.subplots(), the last of them is the current axes, and thus the axes for which plt.xticks would set the ticks.
You may set the current axes using plt.sca(axes):
import matplotlib.pyplot as plt
idx, data1, data2, idx_labels = [1,2,3], [3,4,2], [2,5,4], list("ABC")
fig, axes = plt.subplots(nrows=2, ncols=1)
r1 = axes[0].bar(idx, data1, align='center')
axes[0].set_title('title1')
plt.sca(axes[0])
plt.xticks(idx, idx_labels, rotation=45)
r2 = axes[1].bar(idx, data2, align='center')
axes[1].set_title('title2')
plt.sca(axes[1])
plt.xticks(idx, idx_labels, rotation=45)
plt.show()
However, when working with subplots, it is often easier to use the object-oriented API of matplotlib instead of the pyplot statemachine. In that case you'd use ax.set_xticks and ax.set_xticklabels, where ax is the axes for which you want to set some property. This is more intuitive, since it is easily seen from the code which axes it being worked on.
import matplotlib.pyplot as plt
idx, data1, data2, idx_labels = [1,2,3], [3,4,2], [2,5,4], list("ABC")
fig, axes = plt.subplots(nrows=2, ncols=1)
r1 = axes[0].bar(idx, data1, align='center')
axes[0].set_title('title1')
r2 = axes[1].bar(idx, data2, align='center')
axes[1].set_title('title2')
for ax in axes:
ax.set_xticks(idx)
ax.set_xticklabels(idx_labels, rotation=45)
plt.show()

Related

Ticks and Labels on Twin Axes

How can I set the labels on the extra axes?
The ticks and labels should be the same on all 4 axes. I'm doing something wrong... Thanks!
import matplotlib.pyplot as plt
plt.rcParams['text.usetex'] = True
plt.figure(figsize=(5,5))
f, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax3 = ax1.twiny()
plt.show()
# create reusable ticks and labels
ticks = [0,1/2,3.14159/4,3.14159/2,1]
labels = [r"$0$", r"$\displaystyle\frac{1}{2}$", r"$\displaystyle\frac{\pi}{4}$", r"$\displaystyle\frac{\pi}{2}$", r"$1$"]
# Version 1: twinx() + xaxis.set_ticks()
plt.figure(figsize=(5,5))
f, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax3 = ax1.twiny()
ax1.xaxis.set_ticks(ticks, labels=labels)
ax1.yaxis.set_ticks(ticks, labels=labels)
ax2.xaxis.set_ticks(ticks, labels=labels)
ax3.yaxis.set_ticks(ticks, labels=labels)
plt.show()
# Version 2: twinx() + set_xticklabels)()
plt.figure(figsize=(5,5))
f, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax3 = ax1.twiny()
ax1.set_xticks(ticks)
ax1.set_xticklabels(labels)
ax1.set_yticks(ticks)
ax1.set_yticklabels(labels)
ax2.set_xticks(ticks)
ax2.set_xticklabels(labels)
ax3.set_yticks(ticks)
ax3.set_yticklabels(labels)
plt.show()
Confused: How come ax1 has both xaxis and yaxis, while ax2, ax3 do not appear to?
A unintuitive solution based on matplotlib.axes.Axes.twinx:
Create a new Axes with an invisible x-axis and an independent y-axis
positioned opposite to the original one (i.e. at right).
This means unintuitively (at least for me) you have to switch x/y at the .twin call.
unintuitively not concerning the general matplotlib twinx functionality, but concerning such a manual ticks and label assignment
To highlight that a bit more I used ax2_x and ax3_y in the code.
Disclaimer: Not sure if that will break your plot intention when data is added.
Probably at least you have to take special care with the data assignment to those twin axes - keeping that "axis switch" in mind.
Also keep that axis switch" in mind when assigning different ticks and labels to the x/y axis.
But for now I think that's the plot you were looking for:
Code:
import matplotlib.pyplot as plt
plt.rcParams['text.usetex'] = True
# create reusable ticks and labels
ticks = [0,1/2,3.14159/4,3.14159/2,1]
labels = [r"$0$", r"$\displaystyle\frac{1}{2}$", r"$\displaystyle\frac{\pi}{4}$", r"$\displaystyle\frac{\pi}{2}$", r"$1$"]
plt.figure(figsize=(5,5))
f, ax1 = plt.subplots()
ax1.xaxis.set_ticks(ticks, labels=labels)
ax1.yaxis.set_ticks(ticks, labels=labels)
ax2_x = ax1.twiny() # switch
ax3_y = ax1.twinx() # switch
ax2_x.xaxis.set_ticks(ticks, labels=labels)
ax3_y.yaxis.set_ticks(ticks, labels=labels)
plt.show()
Or switch the x/yaxis.set_ticks - with the same effect:
On second thought, I assume that's the preferred way to do it, especially when data comes into play.
ax2_x = ax1.twinx()
ax3_y = ax1.twiny()
ax2_x.yaxis.set_ticks(ticks, labels=labels) # switch
ax3_y.xaxis.set_ticks(ticks, labels=labels) # switch
In case you don't intend to use the twin axis functionality (that means having different data with different scaling assigned to those axis) but 'only' want the ticks and labels on all 4 axis for better plot readability:
Solution based on answer of ImportanceOfBeingErnest with the same plot result:
import matplotlib.pyplot as plt
plt.rcParams['text.usetex'] = True
# create reusable ticks and labels
ticks = [0,1/2,3.14159/4,3.14159/2,1]
labels = [r"$0$", r"$\displaystyle\frac{1}{2}$", r"$\displaystyle\frac{\pi}{4}$", r"$\displaystyle\frac{\pi}{2}$", r"$1$"]
plt.figure(figsize=(5,5))
f, ax1 = plt.subplots()
ax1.xaxis.set_ticks(ticks, labels=labels)
ax1.yaxis.set_ticks(ticks, labels=labels)
ax1.tick_params(axis="x", bottom=True, top=True, labelbottom=True, labeltop=True)
ax1.tick_params(axis="y", left=True, right=True, labelleft=True, labelright=True)
plt.show()
ax2 = ax1.twinx() shares the x-axis with ax1.
ax3 = ax1.twiny() shares the y-axis with ax1.
As a result, the two lines where you set ax2.xaxis and ax3.yaxis's ticks and ticklabels are redundant with the changes you already applied on ax1.
import matplotlib.pyplot as plt
plt.rcParams['text.usetex'] = False # My computer doesn't have LaTeX, don't mind me.
# Create reusable ticks and labels.
ticks = [0, 1/2, 3.14159/4, 3.14159/2, 1]
labels = [r"$0$", r"$\frac{1}{2}$", r"$\frac{\pi}{4}$", r"$\frac{\pi}{2}$", r"$1$"]
# Set the ticks and ticklabels for each axis.
fig = plt.figure(figsize=(5,5))
ax1 = fig.add_subplot()
ax2 = ax1.twinx()
ax3 = ax1.twiny()
for axis in (ax1.xaxis,
ax1.yaxis,
ax2.yaxis,
ax3.xaxis):
axis.set_ticks(ticks)
axis.set_ticklabels(labels)
fig.show()
Notice that if I comment out the work on ax2 and ax3, we get exactly what you have in your question:
for axis in (ax1.xaxis, ax1.yaxis,
# ax2.yaxis,
# ax3.xaxis,
):
axis.set_ticks(ticks)
axis.set_ticklabels(labels)
Now let's ruin ax1 via modifications on ax2, just to show that the bound between twins works well:
ax2.xaxis.set_ticks(range(10))
ax2.xaxis.set_ticklabels(tuple("abcdefghij"))

How to share facetgrid x and y axis using seaborn

Running this below code produces seaborn facetgrid graphs.
merged1=merged[merged['TEST'].isin(['VL'])]
merged2=merged[merged['TEST'].isin(['CD4'])]
g = sns.relplot(data=merged1, x='Days Post-ART', y='Log of VL and CD4', col='PATIENT ID',col_wrap=4, kind="line", height=4, aspect=1.5,
color='b', facet_kws={'sharey':True,'sharex':True})
for patid, ax in g.axes_dict.items(): # axes_dict is new in seaborn 0.11.2
ax1 = ax.twinx()
sns.lineplot(data=merged2[merged2['PATIENT ID'] == patid], x='Days Post-ART', y='Log of VL and CD4', color='r')
I've used the facet_kws={'sharey':True, 'sharex':True} to share the x-axis and y-axis but it's not working properly. Can someone assist?
As stated in the comments, the FacetGrid axes are shared by default. However, the twinx axes are not. Also, the call to twinx seems to reset the default hiding of the y tick labels.
You can manually share the twinx axes, and remove the unwanted tick labels.
Here is some example code using the iris dataset:
from matplotlib import pyplot as plt
import seaborn as sns
import numpy as np
iris = sns.load_dataset('iris')
g = sns.relplot(data=iris, x='petal_length', y='petal_width', col='species', col_wrap=2, kind="line",
height=4, aspect=1.5, color='b')
last_axes = np.append(g.axes.flat[g._col_wrap - 1::g._col_wrap], g.axes.flat[-1])
shared_right_y = None
for species, ax in g.axes_dict.items():
ax1 = ax.twinx()
if shared_right_y is None:
shared_right_y = ax1
else:
shared_right_y.get_shared_y_axes().join(shared_right_y, ax1)
sns.lineplot(data=iris[iris['species'] == species], x='petal_length', y='sepal_length', color='r', ax=ax1)
if not ax in last_axes: # remove tick labels from secondary axis
ax1.yaxis.set_tick_params(labelleft=False, labelright=False)
ax1.set_ylabel('')
if not ax in g._left_axes: # remove tick labels from primary axis
ax.yaxis.set_tick_params(labelleft=False, labelright=False)
plt.tight_layout()
plt.show()

Two Y axis Bar plot: custom xticks

I am trying to add custom xticks to a relatively complicated bar graph plot and I am stuck.
I am plotting from two data frames, merged_90 and merged_15:
merged_15
Volume y_err_x Area_2D y_err_y
TripDate
2015-09-22 1663.016032 199.507503 1581.591701 163.473202
merged_90
Volume y_err_x Area_2D y_err_y
TripDate
1990-06-10 1096.530711 197.377497 1531.651913 205.197493
I want to create a bar graph with two axes (i.e. Area_2D and Volume) where the Area_2D and Volume bars are grouped based on their respective data frame. An example script would look like:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import scipy
fig = plt.figure()
ax1 = fig.add_subplot(111)
merged_90.Volume.plot(ax=ax1, color='orange', kind='bar',position=2.5, yerr=merged_90['y_err_x'] ,use_index=False , width=0.1)
merged_15.Volume.plot(ax=ax1, color='red', kind='bar',position=0.9, yerr=merged_15['y_err_x'] ,use_index=False, width=0.1)
ax2 = ax1.twinx()
merged_90.Area_2D.plot(ax=ax2,color='green', kind='bar',position=3.5, yerr=merged_90['y_err_y'],use_index=False, width=0.1)
merged_15.Area_2D.plot(ax=ax2,color='blue', kind='bar',position=0, yerr=merged_15['y_err_y'],use_index=False, width=0.1)
ax1.set_xlim(-0.5,0.2)
x = scipy.arange(1)
ax2.set_xticks(x)
ax2.set_xticklabels(['2015'])
plt.tight_layout()
plt.show()
The resulting plot is:
One would think I could change:
x = scipy.arange(1)
ax2.set_xticks(x)
ax2.set_xticklabels(['2015'])
to
x = scipy.arange(2)
ax2.set_xticks(x)
ax2.set_xticklabels(['1990','2015'])
but that results in:
I would like to see the ticks ordered in chronological order (i.e. 1990,2015)
Thanks!
Have you considered dropping the second axis and plotting them as follows:
ind = np.array([0,0.3])
width = 0.1
fig, ax = plt.subplots()
Rects1 = ax.bar(ind, [merged_90.Volume.values, merged_15.Volume.values], color=['orange', 'red'] ,width=width)
Rects2 = ax.bar(ind + width, [merged_90.Area_2D.values, merged_15.Area_2D.values], color=['green', 'blue'] ,width=width)
ax.set_xticks([.1,.4])
ax.set_xticklabels(('1990','2015'))
This produces:
I omitted the error and colors but you can easily add them. That would produce a readable graph given your test data. As you mentioned in comments you would still rather have two axes, presumably for different data with proper scales. To do this you could do:
fig = plt.figure()
ax1 = fig.add_subplot(111)
merged_90.Volume.plot(ax=ax, color='orange', kind='bar',position=2.5, use_index=False , width=0.1)
merged_15.Volume.plot(ax=ax, color='red', kind='bar',position=1.0, use_index=False, width=0.1)
ax2 = ax1.twinx()
merged_90.Area_2D.plot(ax=ax,color='green', kind='bar',position=3.5,use_index=False, width=0.1)
merged_15.Area_2D.plot(ax=ax,color='blue', kind='bar',position=0,use_index=False, width=0.1)
ax1.set_xlim([-.45, .2])
ax2.set_xlim(-.45, .2])
ax1.set_xticks([-.35, 0])
ax1.set_xticklabels([1990, 2015])
This produces:
Your problem was with resetting just one axis limit and not the other, they are created as twins but do not necessarily follow the changes made to one another.

wrong y axis range using matplotlib subplots and seaborn

I'm playing with seaborn for the first time, trying to plot different columns of a pandas dataframe on different plots using matplotlib subplots. The simple code below produces the expected figure but the last plot does not have a proper y range (it seems linked to the full range of values in the dataframe).
Does anyone have an idea why this happens and how to prevent it? Thanks.
import matplotlib.pyplot as plt
import numpy as np
import pandas as pds
import seaborn as sns
X = np.arange(0,10)
df = pds.DataFrame({'X': X, 'Y1': 4*X, 'Y2': X/2., 'Y3': X+3, 'Y4': X-7})
fig, axes = plt.subplots(ncols=2, nrows=2)
ax1, ax2, ax3, ax4 = axes.ravel()
sns.set(style="ticks")
sns.despine(fig=fig)
sns.regplot(x='X', y='Y1', data=df, fit_reg=False, ax=ax1)
sns.regplot(x='X', y='Y2', data=df, fit_reg=False, ax=ax2)
sns.regplot(x='X', y='Y3', data=df, fit_reg=False, ax=ax3)
sns.regplot(x='X', y='Y4', data=df, fit_reg=False, ax=ax4)
plt.show()
Update: I modified the above code with:
fig, axes = plt.subplots(ncols=2, nrows=3)
ax1, ax2, ax3, ax4, ax5, ax6 = axes.ravel()
If I plot data on any axis but the last one I obtain what I'm looking for:
Of course I don't want the empty frames. All plots present the data with a similar visual aspect.
When data is plotted on the last axis, it gets a y range that is too wide like in the first example. Only the last axis seems to have this problem. Any clue?
If you want the scales to be the same on all axes you could create subplots with this command:
fig, axes = plt.subplots(ncols=2, nrows=2, sharey=True, sharex=True)
Which will make all plots to share relevant axis:
If you want manually to change the limits of that particular ax, you could add this line at the end of plotting commands:
ax4.set_ylim(top=5)
# or for both limits like this:
# ax4.set_ylim([-2, 5])
Which will give something like this:

Use matplotlib: plot error bars on two y axes

I'd like to plot a series with x and y error bars, then plot a second series with x and y error bars on a second y axis all on the same subplot. Can this be done with matplotlib?
import matplotlib.pyplot as plt
plt.figure()
ax1 = plt.errorbar(voltage, dP, xerr=voltageU, yerr=dPU)
ax2 = plt.errorbar(voltage, current, xerr=voltageU, yerr=currentU)
plt.show()
Basically, I'd like to put ax2 on a second axis and have the scale on the right side.
Thanks!
twinx() is your friend for adding a secondary y-axis, e.g.:
import matplotlib.pyplot as pl
import numpy as np
pl.figure()
ax1 = pl.gca()
ax1.errorbar(np.arange(10), np.arange(10), xerr=np.random.random(10), yerr=np.random.random(10), color='g')
ax2 = ax1.twinx()
ax2.errorbar(np.arange(10), np.arange(10)+5, xerr=np.random.random(10), yerr=np.random.random(10), color='r')
There is not a lot of documentation except for:
matplotlib.pyplot.twinx(ax=None)
Make a second axes that shares the x-axis. The new axes will overlay ax (or the current axes if ax is None). The ticks for ax2 will be placed on the right, and the ax2 instance is returned.
I was struggling to share the x-axis, but thank you #Bart you saved me!
The simple solution is use twiny instead of twinx
ax1.errorbar(layers, scores_means[str(epoch)][h,:],np.array(scores_stds[str(epoch)][h,:]))
# Make the y-axis label, ticks and tick labels match the line color.
ax1.set_xlabel('depth', color='b')
ax1.tick_params('x', colors='b')
ax2 = ax1.twiny()
ax2.errorbar(hidden_dim, scores_means[str(epoch)][:,l], np.array(scores_stds[str(epoch)][:,l]))
ax2.set_xlabel('width', color='r')
ax2.tick_params('x', colors='r')
fig.tight_layout()
plt.show()

Categories