5

I have a script that generates matplotlib figures from data. Those plots are saved to disk as follows:

fig, ax = plt.subplots() # create the plot # ... pickle.dump(ax, open(of, 'wb'))

In another script, I want to join certain of these plots. I can read the data back using:

figures = [pickle.load(file) for file in files]

(FWIW, figures that I read back have the type AxesSubplot.)

So far so good. Now I want to put the data of two (or more) figures together, using either the largest or smallest scale of the available plots. Due to my lack of experience, I have absolutely no idea how to accomplish that. I did find questions about joining plots and the consensus was to plot in one figure in the first place. In my case that would be rather difficult as the plotting logic for a single data set is already complex. (There are other reasons why each dataset should be plotted on its own in a first step, and only then be potentially joined with others).

The plots I want to join represent their data in the same way - i.e. all of the plots are line plots or histograms (not really sure how to join those meaningfully) or QQPlots (see statsmodels.api). They may or may not have the same size of data.

How can I join the plots that are in different figures?

Max Beikirch
  • 2,053
  • 5
  • 25
  • 36

2 Answers2

7

I think you'll find it easier to save the data to a file from which you can later generate new plots. You could even use np.savez to save not only the data, but also the plot method and its arguments in one file. Here is how you could later load those files to generate "joined" plots in a new figure:

import matplotlib.pyplot as plt
import numpy as np

def join(ax, files):
    data = [np.load(filename) for filename in files]
    for datum in data:
        method = getattr(ax, datum['method'].item())
        args = tuple(datum['args'])
        kwargs = datum['kwargs'].item()
        method(*args, **kwargs)

x = np.linspace(-3, 3, 100)
y = np.exp(-x**2/2)/np.sqrt(2*np.pi)
a = np.random.normal(size=10000)

fig, ax = plt.subplots()
ax.plot(x, y)
plt.show()
np.savez('/tmp/a.npz', method='plot', args=(x, y), kwargs=dict())

fig, ax = plt.subplots()
ax.hist(a, bins=100, density=True)
plt.show()
np.savez('/tmp/b.npz', method='hist', args=(a,), 
         kwargs=dict(bins=100, density=True))

fig, ax = plt.subplots()
join(ax, ['/tmp/a.npz', '/tmp/b.npz'])
plt.show()

enter image description here


Above I used np.savez and np.load instead of pickle to save and restore the data. Alternatively, you could pickle a dict, tuple or list containing the data, the method and its arguments. However, since the data is mainly numeric, using np.savez is more efficient and less of a security risk than pickle.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
6

Create functions to plot to specific axes

You would usually put your plotting into a function which takes the axes to plot to as argument. You can then reuse this function whereever you like.

import numpy as np
import matplotlib.pyplot as plt

def myplot1(data, ax=None, show=False):
    if not ax:
        _, ax = plt.subplots()
    ax.plot(data[0], data[1])
    if show:
        plt.show()

def myplot2(data, ax=None, show=False):
    if not ax:
        _, ax = plt.subplots()
    ax.hist(data, bins=20, density=True)
    if show:
        plt.show()

x = np.linspace(-3, 3, 100)
y = np.exp(-x**2/2)/np.sqrt(2*np.pi)
a = np.random.normal(size=10000)

# create figure 1
myplot1((x,y))
#create figure 2
myplot2(a)

# create figure with both
fig, ax = plt.subplots()
myplot1((x,y), ax=ax)
myplot2(a, ax=ax)

plt.show()


Moving artists to new figure

To answer the question, yes you can move the artists from an unpickled figure, but that involves some hacking and may result in non-perfect results:

import matplotlib.pyplot as plt
import numpy as np
import pickle

x = np.linspace(-3, 3, 100)
y = np.exp(-x**2/2)/np.sqrt(2*np.pi)
a = np.random.normal(size=10000)

fig, ax = plt.subplots()
ax.plot(x, y)

pickle.dump(fig, open("figA.pickle","wb"))
#plt.show()

fig, ax = plt.subplots()
ax.hist(a, bins=20, density=True, ec="k")

pickle.dump(fig, open("figB.pickle","wb"))
#plt.show()

plt.close("all")

#### No unpickle the figures and create a new figure
#    then add artists to this new figure

figA = pickle.load(open("figA.pickle","rb"))
figB = pickle.load(open("figB.pickle","rb"))

fig, ax = plt.subplots()

for figO in [figA,figB]:
    lists = [figO.axes[0].lines, figO.axes[0].patches]
    addfunc = [ax.add_line, ax.add_patch]
    for lis, func in zip(lists,addfunc):
        for artist in lis[:]:
            artist.remove()
            artist.axes=ax
            artist.set_transform(ax.transData)
            artist.figure=fig
            func(artist)

ax.relim()
ax.autoscale_view()

plt.close(figA)
plt.close(figB)   
plt.show()

Here we loop through all lines and patches from the unpickled figures, remove them from the old figure and add them to the new figure. To this end, we need to set the axes, the transform and the figure to correctly match the new figure. This will of course get more and more complicated the more differnt kinds of artists there are in the figure and would require even more complex methods if those artists have transforms other than the data transform associated with them.

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • 1
    @unutbu Yes correct, that is because I ignored the basic principle of never manipulating a list while iterating over it. ;-) I corrected that. – ImportanceOfBeingErnest Feb 21 '18 at 21:43
  • I've posted a follow-up question regarding moving collections [here](https://stackoverflow.com/q/48916579/190597). – unutbu Feb 21 '18 at 22:28
  • @unutbu I did not think about the consequences when I wrote *"... and would require even more complex methods if those artists have transforms other than the data transform associated with them"* :-) – ImportanceOfBeingErnest Feb 21 '18 at 23:00
  • @ImportanceOfBeingErnest Thank you so much for your answer! It's almost working perfectly for me - one last question: how can I change the color of the plots? I' like to have each individual plot in its own color to make them distinguishable. I tried setting color on each artist, but that did not do the trick. – Max Beikirch Feb 24 '18 at 18:58