How to sharex and sharey axis in for loop - python

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()

Related

Plotting a boxplot and histogram side by side with seaborn

I'm trying to plot a simple box plot next to a simple histogram in the same figure using seaborn (0.11.2) and pandas (1.3.4) in a jupyter notebook (6.4.5).
I've tried multiple approaches with nothing working.
fig, ax = plt.subplots(1, 2)
sns.boxplot(x='rent', data=df, ax=ax[0])
sns.displot(x='rent', data=df, bins=50, ax=ax[1])
There is an extra plot or grid that gets put next to the boxplot, and this extra empty plot shows up any time I try to create multiple axes.
Changing:
fig, ax = plt.subplots(2)
Gets:
Again, that extra empty plot next to the boxplot, but this time below it.
Trying the following code:
fig, (axbox, axhist) = plt.subplots(1,2)
sns.boxplot(x='rent', data=df, ax=axbox)
sns.displot(x='rent', data=df, bins=50, ax=axhist)
Gets the same results.
Following the answer in this post, I try:
fig, axs = plt.subplots(ncols=2)
sns.boxplot(x='rent', data=df, ax=axs[0])
sns.displot(x='rent', data=df, bins-50, ax=axs[1])
results in the same thing:
If I just create the figure and then the plots underneath:
plt.figure()
sns.boxplot(x='rent', data=df)
sns.displot(x='rent', data=df, bins=50)
It just gives me the two plots on top of each other, which I assume is just making two different figures.
I'm not sure why that extra empty plot shows up next to the boxplot when I try to do multiple axes in seaborn.
If I use pyplot instead of seaborn, I can get it to work:
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5))
ax1.hist(df['rent'], bins=50)
ax2.boxplot(df['rent'])
Results in:
The closest I've come is to use seaborn only on the boxplot, and pyplot for the histogram:
plt.figure(figsize=(8, 5))
plt.subplot(1, 2, 1)
sns.boxplot(x='rent', data=df)
plt.subplot(1, 2, 2)
plt.hist(df['rent'], bins=50)
Results:
What am I missing? Why can't I get this to work with two seaborn plots on the same figure, side by side (1 row, 2 columns)?
Try this function:
def creating_box_hist(column, df):
# creating a figure composed of two matplotlib.Axes objects (ax_box and ax_hist)
f, (ax_box, ax_hist) = plt.subplots(2, sharex=True, gridspec_kw={"height_ratios": (.15, .85)})
# assigning a graph to each ax
sns.boxplot(df[column], ax=ax_box)
sns.histplot(data=df, x=column, ax=ax_hist)
# Remove x axis name for the boxplot
ax_box.set(xlabel='')
plt.show()

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()

Is there a restriction on catplot with subplot?

Seaborn's catplot does not seem to be able to work with plt.subplots(). Am not sure whats the issue here but i dont seem to be able to put them side by side.
#Graph 1
plt.subplot(121)
sns.catplot(x="HouseStyle",y="SalePrice",data=df,kind="swarm")
#Graph 2
plt.subplot(122)
sns.catplot(x="LandContour",y="SalePrice",data=df,kind="swarm")
Output:
Catplot is a figure-level function whereas you cannot use axes. Try using stripplot instead.
fig, axs = plt.subplots (1, 2, figsize=(25, 15))
sns.stripplot(x='category_col', y='y_col_1', data=df, ax=axs[0])
sns.stripplot(x='category_col', y='y_col_2', data=df, ax=axs[1])
You need to pass the created axis to seaborn's catplot while plotting. Following is a sample answer demonstrating this. A couple of things
I would suggest using add_subplot to create subplots like yours
The catplot will still return an axis object which can be closed using plt.close() where the number inside the brackets correspond to the figure count. See this answer for more details on close()
Complete reproducible answer
import seaborn as sns
import matplotlib.pyplot as plt
exercise = sns.load_dataset("exercise")
fig = plt.figure()
ax1 = fig.add_subplot(121)
g = sns.catplot(x="time", y="pulse", hue="kind", data=exercise, ax=ax1) # pass ax1
ax2 = fig.add_subplot(122)
g = sns.catplot(x="time", y="pulse", hue="kind", data=exercise, ax=ax2) # pass ax2
plt.close(2)
plt.close(3)
plt.tight_layout()
Thank you Sheldore for giving an idea of using close(). I tried this way and it worked.
_, ax = plt.subplots(2, 3, figsize=(20,10))
for n, feat in enumerate(cat_feats):
sns.catplot(x='feat', kind='count', data=df, ax=ax[n//3][n%3])
plt.close()

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.

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:

Categories