-1

Update: If you find yourself here with a similar problem, I found a solution that worked for me (see my response below) here; Adjust y-axis in Seaborn multiplot


The best is the enemy of the good...

I wrote a for loop to create a figure with two rows and four columns. The xaxis is shared by column (sharex='col'), the yaxis by row (sharey='row'). And it worked great...until I tried to use this inside my loop to force the yaxis range of the first row:

axes[0, i].set(yticks=range(0,70,10)) 

Now when I run the code, only the subplot axes[0,3] (i.e. the final subplot in this row) respects the axes[0, i].set(yticks=range(0,70,10)) line of code. The other three subplots to the left of it on the same row keep their default ytick values derived from the data (see image below).

This means that, despite the fact that sharey='row', axes[0,0], axes[0,1], and axes[0,2] share a yaxis that is not shared by axes[0,3].

If I set sharey=False, then all the subplots in the row respect the axes[0, i].set(yticks=range(0,70,10)) code, but not according to the same vertical scale, not to mention that sharey=False also messes up my second row of subplots.

Does anyone understand why axes[0, i].set(yticks=range(0,70,10)) is seemingly being applied only once in the loop, instead of four times? Thank you.

Edit: I can't provide data, but here's basically what my full code looks like:

fig, axes = plt.subplots(2, 4, figsize=(20, 12), sharex=True, sharey='row')

for i in range(4):
    sns.lineplot(ax = axes[0, i], x=x, y=y,
                    data=data)
    sns.despine()
    axes[0, i].grid(True, alpha=0.2)
    axes[0, i].tick_params(axis='both', which='major', labelsize=13)
    axes[0, i].axhline(y=50, color='k', dashes=(2, 1), zorder=0)
    axes[0, i].set(yticks=range(0,70,10))
axes[0, 0].set_ylabel('y axis row 0', fontsize=14)
    

for i in range(4):
    sns.lineplot(ax = axes[1, i], x=x1, y=y1,
                    data=data1)
    sns.despine()
    axes[1, i].grid(True, alpha=0.2)
    axes[1, i].set(xticks=[0, 1, 2, 3, 4])
    axes[1, i].axhline(y=0, color='red', dashes=(2, 1), zorder=0)
    axes[1, i].tick_params(axis='both', which='major', labelsize=13)    
axes[1, 0].set_ylabel('y axis row 1', fontsize=14)

And here's what I'm getting out. See that the top right subplot, axes[0,3], has the yaxis the way I want, but not the others on the same row: enter image description here

cjstevens
  • 131
  • 7
  • 1
    I cannot reproduce with basic matplotlib, so must be an issue with seaborn? – Jody Klymak Aug 14 '21 at 19:09
  • 1
    You may also want to remove `sns.despine` or actively set the axes it is acting on. No idea if that is the problem, but a non-object-specific call like that could be problematic – Jody Klymak Aug 14 '21 at 19:13
  • 1
    Could you try Seaborn's [`relplot`](https://seaborn.pydata.org/generated/seaborn.relplot.html) to set up your grids and ditch the loop? There is [a similar question here](https://stackoverflow.com/questions/67613774/how-to-add-a-mean-and-median-line-to-a-seaborn-displot) on adding lines to plots inside a FacetGrid. – a11 Aug 15 '21 at 00:47
  • @JodyKlymak Thanks for your suggestions. I had tried removing sns.despine but it changed nothing. But you are correct that it was not best practice of me to have a non-object-specific call inside the loop. – cjstevens Aug 15 '21 at 13:04
  • 1
    That still sounds like either a bug in seaborn or matplotlib. – Jody Klymak Aug 15 '21 at 14:44

2 Answers2

1

This is using Seaborn's stock FMRI dataset to show (1) how you can leverage the awesomeness that is Seaborn by using relplot here and perhaps get rid of your loop; and (2) how you can add an axhline. Basically, you let relplot create your subplots and then you have one simple little loop over each subplot (facet) to look at the data in the plot and then add your line based on your condition.

In this example the region field is the row identifier, but it looks like you would use your row #.

fmri = sns.load_dataset("fmri")
print(fmri)

g = sns.relplot(
    data=fmri, x="timepoint", y="signal", row="region",
    hue="event", style="event", kind="line",
)

for (row, col, hue_idx), data in g.facet_data():
    ax = g.facet_axis(row, col)
    myline = 0.2 if data['region'].unique() == "frontal" else -0.1
    col = 'red' if data['region'].unique() == "frontal" else 'black'
    ax.axhline(myline, c=col, dashes=(2, 1))

plt.show()

enter image description here

enter image description here

a11
  • 3,122
  • 4
  • 27
  • 66
  • Thanks for the suggestion. I actually found a seaborn-based solution that worked for me here: https://stackoverflow.com/questions/57101391/adjust-y-axis-in-seaborn-multiplot – cjstevens Aug 15 '21 at 13:31
  • 1
    Your solution is not really Seaborn-based, because you are explicitly defining your subplots in matplotlib. – a11 Aug 15 '21 at 15:24
  • Seaborn-compatible, I should have said, you're right. – cjstevens Aug 15 '21 at 17:10
0

Edit: I found a solution that worked for me in this solution here: https://stackoverflow.com/a/57101557/13917918

By adding g = sns.lineplot... and then placing a call to g.axes.set_ylim(0,70) inside the loop, I got the desired result. Example of working code:

fig, axes = plt.subplots(2, 4, figsize=(20, 12), sharex=True, sharey='row')

    for i in range(4):
        g = sns.lineplot(ax = axes[0, i], x=x, y=y,
                        data=data)
        g.axes.set_ylim(0,70)
        axes[0, i].grid(True, alpha=0.2)
        axes[0, i].tick_params(axis='both', which='major', labelsize=13)
        axes[0, i].axhline(y=50, color='k', dashes=(2, 1), zorder=0)
    axes[0, 0].set_ylabel('y0', fontsize=14)
    
    for i in range(4):
        sns.lineplot(ax = axes[1, i], x=x1, y=y1,
                        data=data1)
        sns.despine()
        axes[1, i].grid(True, alpha=0.2)
        axes[1, i].set(xticks=[0, 1, 2, 3, 4])
        axes[1, i].axhline(y=0, color='red', dashes=(2, 1), zorder=0)
        axes[1, i].tick_params(axis='both', which='major', labelsize=13)    
    axes[1, 0].set_ylabel('y1', fontsize=14) 
    
     

enter image description here

cjstevens
  • 131
  • 7