1

Is there a way to copy an axis (subplot) to a new figure? This is straightforward in other languages, but I understand that not even a deep copy would work with python's matplotlib. E.g. if I have a figure with 6 subplots, how can I copy 2 of those subplots to a new figure, with all the settings? Labels, tickmarks, legend, grid, etc.

I have found this answer , which is actually 2 in 1: the first no longer works (deprecated syntax), while I couldn't get the second to work in my case.

I manage to recreate a new figure and to remove the subplots I don't want, but I am left with a 3x2 grid of subplots, in which 2 are drawn and 4 are empty. Any suggestions?

I have put together a toy example below; my charts are, of course, way more complex with way more settings.

Note I used seaborn but it should be irrelevant - I believe this is a matplotlib question and the answer is going to be the same regardless of whether seaborn is used.

UPDATE: Following the tips from @Earnest, I can change the geometry of the new figure so that it only has two subplots, doing:

axes.change_geometry(1,2,2)

What I don't know how to do is to move the subplots around. To recap:

  • I start with 6 subplots in a 3x2 grid.
  • I pickle to memory, then unpickle to a new figure.
  • In the new figure, I keep only subplots 1 and 2, i.e. those identified by [rows, columns] [0,1] and [1,0]; the other 4 subplots are now blank spaces.
  • I want a [1x2] grid layout; if I do axes.change_geometry(1,2,2) then I get that layout, but one of my two subplots disappears.

Any suggestions?

    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib
    import seaborn as sns
    import pickle
    import io


    sns.set(style='darkgrid')

    n=int(100)
    x=np.arange(0,n)

    fig, ax = plt.subplots(3,2)

    for i,a in enumerate(ax.flatten() ):
        y= np.random.rand(n)
        sns.lineplot( x, y , ax =a )
        a.set_title('Chart # ' + str(i+1))
        a.set_xlabel('my x')
        a.set_ylabel('my y')

    fig2, ax2 = plt.subplots(1,2)

    buf = io.BytesIO()
    pickle.dump(fig, buf)
    buf.seek(0)

    fig2=pickle.load(buf)

    tokeep=[1,2]

    # note that i == 1correponds to a[0,1]
    for i,a in enumerate(fig2.axes):
        if not i in(tokeep):
            fig2.delaxes(a)
        else:
            axes=a
Pythonista anonymous
  • 8,140
  • 20
  • 70
  • 112
  • Before diving into this, why don't you use the second approach from that answer, namely to create the plots from scratch in a new figure? – ImportanceOfBeingErnest Mar 29 '19 at 15:20
  • Because in my case they are really quite complex. I have more than 60 lines of code that chooses what to plot, in which subplot, how to scale it, how to label everything, which subplot to plot on a log scale, etc etc. For a very simple plot with not many setting it will probably be easier to just replot it and reapply whatever settings you had, but that's not my situation. – Pythonista anonymous Mar 29 '19 at 15:27
  • Maybe that is a poor design choice? One could simply put each kind of plot into a function that takes the necessary information about what and where to plot as arguments. Concerning the code in the quesiton, you may need to create a new gridspec and use `ax.set_position()` to position the axes on that new grid. – ImportanceOfBeingErnest Mar 29 '19 at 16:09
  • Mmm, my design is not ideal, but then all the subplots do share some settings; it wouldn't be ideal to do the same formatting in each function that draws a separate subplot, either. As for te rest of the answer, what exactly do you mean by new gridspec? Cretaing a new figure? Or deleting some subplots (which I do in my toy example)? – Pythonista anonymous Mar 29 '19 at 16:19
  • Check e.g. [this question](https://stackoverflow.com/questions/43937066/matplotlib-hide-subplot-and-fill-space-with-other-subplots/43944246#43944246). If the grid is regular, maybe `ax.change_geometry` is enough. – ImportanceOfBeingErnest Mar 29 '19 at 16:27
  • I also modified [the linked answer](https://stackoverflow.com/a/45812071/4124317), which after reading it again, might be exactly what you need actually. So maybe you can go into detail about why that would not work? – ImportanceOfBeingErnest Mar 29 '19 at 16:31
  • I have updated the question; my problem is I need to move subplots around, and I don't know how. – Pythonista anonymous Mar 29 '19 at 16:57
  • 1
    That sounds all good. Note that in your code you only keep a single `axes` around (the one where tokeep is `2`). You will need to store both axes and do `change_geometry` for both. – ImportanceOfBeingErnest Mar 29 '19 at 17:01
  • Understood - it works now, thanks a lot! If you want to write it into an answer, I'll accept it – Pythonista anonymous Mar 29 '19 at 17:47
  • Please feel free to write up your own answer, since you would know better than me in how far the answers in the links are not sufficient to solve this. – ImportanceOfBeingErnest Mar 29 '19 at 18:45

1 Answers1

0

This is really @Ernest's answer; he pointed me in the right direction. I am typing this here as an answer for future reference, in the hope it can be useful to others. Basically I had to use change_geometry after deleting the subplots I didn't need. I think ggplot in the R universe has a cleaner implementation, but, overall, this seems OK - not too cumbersome. I have commented the code below - hope it's clear enough:

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import pickle
import io

sns.set(style='darkgrid')

#create the subplots
n=int(100)
x=np.arange(0,n)
fig, ax = plt.subplots(3,2)

for i,a in enumerate(ax.flatten() ):
    y= np.random.rand(n)
    sns.lineplot( x, y , ax =a )
    a.set_title('Chart # ' + str(i+1))
    a.set_xlabel('my x')
    a.set_ylabel('my y')

# pickle the figure, then unpickle it to a new figure
buf = io.BytesIO()
pickle.dump(fig, buf)
buf.seek(0)
fig2=pickle.load(buf)

# sets which subplots to keep
# note that i == 1correponds to a[0,1]
tokeep=[1,2]
axestokeep=[]

for i,a in enumerate(fig2.axes):
    if not i in(tokeep):
        fig2.delaxes(a)
    else:
        axestokeep.extend([a])

axestokeep[0].change_geometry(1,2,1)
axestokeep[1].change_geometry(1,2,2)
Pythonista anonymous
  • 8,140
  • 20
  • 70
  • 112