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?
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()
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.
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()
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With