Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to join two matplotlib figures

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?

like image 832
Max Beikirch Avatar asked Dec 10 '22 07:12

Max Beikirch


2 Answers

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.

like image 130
unutbu Avatar answered Dec 23 '22 04:12

unutbu


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.

like image 42
ImportanceOfBeingErnest Avatar answered Dec 23 '22 04:12

ImportanceOfBeingErnest