How to stop xlim updating when subplots share x-axis - python

If I have
import matplotlib.pyplot as plt
plt.plot([0,1], [0,1])
plt.plot([0,2], [0,1], scalex=False)
plotting the second line does not update the axes xlim:
However, if I create subplots with a shared x-axis, the scalex kwarg appears to have no effect:
fig, ax_arr = plt.subplots(2, 1, sharex=True)
for ax in ax_arr.flat:
ax.plot([0,1], [0,1])
ax.plot([0,2], [0,1], scalex=False)
Is there another kwarg or setting somewhere that can be used to stop a plotted line affecting the axes xlim in this example?

scalex affects the autoscaling in the moment the plot is created. It will not be stored to take effect in further calls to autoscale.
An option is to turn autoscaling off in general for all but the first axes.
import matplotlib.pyplot as plt
fig, ax_arr = plt.subplots(2, 1, sharex=True)
ax_arr[1].set_autoscalex_on(False)
for ax in ax_arr.flat:
ax.plot([0,1], [0,1])
ax.plot([0,2], [0,1], scalex=False)
plt.show()

I've accepted ImportanceOfBeingErnest's answer as it does address my specific minimal example above. As my "real" example involves subplots where the first plot on each axes will not be the same, I include this further answer in case it's of use to anyone else:
fig, ax_arr = plt.subplots(2, 1, sharex=True)
ax_arr.flat[0].plot([0,1], [0,1])
ax_arr.flat[1].plot([-1,0], [0,1])
for ax in ax_arr.flat:
ax.set_autoscalex_on(False)
ax.plot([0,2], [0,1])

Related

Share y axes for subplots that are dynamically created

Working example
import matplotlib.pyplot as plt
names = ['one','two','three']
upper = [[79,85,88],[79,85,88],[79,85,88]]
lower = [[73,72,66],[73,72,66],[73,72,66]]
fig = plt.figure(1)
for idx,lane in enumerate(names):
ax = fig.add_subplot(1,len(names)+1,idx+1)
ax.plot(upper[idx], color='tab:blue', marker='x', linestyle="None")
ax.plot(lower[idx], color='tab:blue', marker='x', linestyle="None")
ax.set_title(lane)
plt.show()
This generates 3 plots dynamically. It works I could very well not be using the best practices for dynamically generating plots. The goal is to have all the plots generated share the Y-axis so that it will give it a cleaner look. All the examples I've looked up show that you can assign the shared axis to the previously used axis but in my case all the plots are created dynamically. Is there a way to just lump all the subplots in a figure into sharing the same y axis?
The common approach to creating a figure with multiple axes is plt.subplots, which accepts a sharey = True argument.
Example:
import numpy as np
import matplotlib.pyplot as plt
xdata = np.linspace(0, 10, 100)
ydata_1 = np.sin(xdata)
ydata_2 = np.cos(xdata)
fig, (ax1, ax2) = plt.subplots(1, 2, sharey = True, figsize = (8, 4))
ax1.plot(xdata, ydata_1)
ax2.plot(xdata, ydata_2)
This outputs:
For less space between the plots, you can also use a tight_layout = True argument.
Using your data, you could maybe rewrite it to something like
fig, axes = plt.subplots(1, len(names), sharey = True, tight_layout = True)
for idx, (ax, name) in enumerate(zip(axes, names)):
ax.plot(upper[idx], color='tab:blue', marker='x', linestyle="None")
ax.plot(lower[idx], color='tab:blue', marker='x', linestyle="None")
ax.set_title(name)
plt.show()

GridSpec on Seaborn Subplots

I currently have 2 subplots using seaborn:
import matplotlib.pyplot as plt
import seaborn.apionly as sns
f, (ax1, ax2) = plt.subplots(2, sharex=True)
sns.distplot(df['Difference'].values, ax=ax1) #array, top subplot
sns.boxplot(df['Difference'].values, ax=ax2, width=.4) #bottom subplot
sns.stripplot([cimin, cimax], color='r', marker='d') #overlay confidence intervals over boxplot
ax1.set_ylabel('Relative Frequency') #label only the top subplot
plt.xlabel('Difference')
plt.show()
Here is the output:
I am rather stumped on how to make ax2 (the bottom figure) to become shorter relative to ax1 (the top figure). I was looking over the GridSpec (http://matplotlib.org/users/gridspec.html) documentation but I can't figure out how to apply it to seaborn objects.
Question:
How do I make the bottom subplot shorter compared to the top
subplot?
Incidentally, how do I move the plot's title "Distrubition of Difference" to go above the top
subplot?
Thank you for your time.
As #dnalow mentioned, seaborn has no impact on GridSpec, as you pass a reference to the Axes object to the function. Like so:
import matplotlib.pyplot as plt
import seaborn.apionly as sns
import matplotlib.gridspec as gridspec
tips = sns.load_dataset("tips")
gridkw = dict(height_ratios=[5, 1])
fig, (ax1, ax2) = plt.subplots(2, 1, gridspec_kw=gridkw)
sns.distplot(tips.loc[:,'total_bill'], ax=ax1) #array, top subplot
sns.boxplot(tips.loc[:,'total_bill'], ax=ax2, width=.4) #bottom subplot
plt.show()
If you're using a FacetGrid (either directly or through something like catplot, which uses it indirectly), then you can pass gridspec_kws.
Here is an example using a catplot, where "var3" has two values, i.e. there are two subplots, which I am displaying at a ratio of 3:8, with un-shared x-axes.
g = sns.catplot(data=data, x="bin", y="y", col="var3", hue="var4", kind="bar",
sharex=False,
facet_kws={
'gridspec_kws': {'width_ratios': [3, 8]}
})
# Make the first subplot have a custom `xlim`:
g.axes[0][0].set_xlim(right=2.5)
Result, with labels hidden because I just copied my actual data's output, so the labels wouldn't make sense.

How to sharex and sharey axis in for loop

I'm trying to share the x-axis and y-axis of my sumplots, I've tried using the sharey and sharex several different ways but haven't gotten the correct result.
ax0 = plt.subplot(4,1,1)
for i in range(4):
plt.subplot(4,1,i+1,sharex = ax0)
plt.plot(wavelength[i],flux)
plt.xlim([-1000,1000])
plt.ylim([0,1.5])
plt.subplots_adjust(wspace=0, hspace=0)
plt.show()
If I understood you correctly, want to have four stacked plots, sharing the x-axis and the y-axis. This you can do with plt.subplots and the keywords sharex=True and sharey=True. See example below:
import numpy as np
import matplotlib.pyplot as plt
fig, axlist = plt.subplots(4, 1, sharex=True, sharey=True)
for ax in axlist:
ax.plot(np.random.random(100))
plt.show()

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:

GridSpec with shared axes in Python

This solution to another thread suggests using gridspec.GridSpec instead of plt.subplots. However, when I share axes between subplots, I usually use a syntax like the following
fig, axes = plt.subplots(N, 1, sharex='col', sharey=True, figsize=(3,18))
How can I specify sharex and sharey when I use GridSpec ?
First off, there's an easier workaround for your original problem, as long as you're okay with being slightly imprecise. Just reset the top extent of the subplots to the default after calling tight_layout:
fig, axes = plt.subplots(ncols=2, sharey=True)
plt.setp(axes, title='Test')
fig.suptitle('An overall title', size=20)
fig.tight_layout()
fig.subplots_adjust(top=0.9)
plt.show()
However, to answer your question, you'll need to create the subplots at a slightly lower level to use gridspec. If you want to replicate the hiding of shared axes like subplots does, you'll need to do that manually, by using the sharey argument to Figure.add_subplot and hiding the duplicated ticks with plt.setp(ax.get_yticklabels(), visible=False).
As an example:
import matplotlib.pyplot as plt
from matplotlib import gridspec
fig = plt.figure()
gs = gridspec.GridSpec(1,2)
ax1 = fig.add_subplot(gs[0])
ax2 = fig.add_subplot(gs[1], sharey=ax1)
plt.setp(ax2.get_yticklabels(), visible=False)
plt.setp([ax1, ax2], title='Test')
fig.suptitle('An overall title', size=20)
gs.tight_layout(fig, rect=[0, 0, 1, 0.97])
plt.show()
Both Joe's choices gave me some problems: the former, related with direct use of figure.tight_layout instead of figure.set_tight_layout() and, the latter, with some backends (UserWarning: tight_layout : falling back to Agg renderer). But Joe's answer definitely cleared my way toward another compact alternative. This is the result for a problem close to the OP's one:
import matplotlib.pyplot as plt
fig, axes = plt.subplots(nrows=2, ncols=1, sharex='col', sharey=True,
gridspec_kw={'height_ratios': [2, 1]},
figsize=(4, 7))
fig.set_tight_layout({'rect': [0, 0, 1, 0.95], 'pad': 1.5, 'h_pad': 1.5})
plt.setp(axes, title='Test')
fig.suptitle('An overall title', size=20)
plt.show()
I made a function where you input a list or array of axes and it shares x or y along the rows and cols as specified. Not fully tested but here's the gist of it:
def share_axes(subplot_array, sharex, sharey, delete_row_ticklabels = 1, delete_col_ticklabels = 1):
shape = np.array(subplot_array).shape
if len(shape) == 1:
for i, ax in enumerate(subplot_array):
if sharex:
ax.get_shared_x_axes().join(ax, subplot_array[0])
if delete_row_ticklabels and not(i==len(subplot_array)-1):
ax.set_xticklabels([])
if sharey:
ax.get_shared_x_axes().join(ax, subplot_array[0])
if delete_col_ticklabels and not(i==0):
ax.set_yticklabels([])
elif len(shape) == 2:
for i in range(shape[0]):
for j in range(shape[1]):
ax = subplot_array[i,j]
if sharex in ('rows', 'both'):
ax.get_shared_x_axes().join(ax, subplot_array[-1,j])
if delete_row_ticklabels and not(i==shape[0]-1):
ax.set_xticklabels([])
if sharey in ('rows', 'both'):
ax.get_shared_y_axes().join(ax, subplot_array[-1,j])
if sharex in ('cols', 'both'):
ax.get_shared_x_axes().join(ax, subplot_array[i,0])
if sharey in ('cols', 'both'):
if delete_col_ticklabels and not(j==0):
ax.set_yticklabels([])
ax.get_shared_y_axes().join(ax, subplot_array[i,0])

Categories