有没有一种方法可以保存 Matplotlib 图形,以便重新打开并恢复典型的交互?(类似于 MATLAB 中的 .fig 格式?)
我发现自己多次运行相同的脚本来生成这些交互式图形。或者我向同事发送多个静态 PNG 文件以展示绘图的不同方面。我希望能够发送图形对象,并让他们自己与之交互。
有没有一种方法可以保存 Matplotlib 图形,以便重新打开并恢复典型的交互?(类似于 MATLAB 中的 .fig 格式?)
我发现自己多次运行相同的脚本来生成这些交互式图形。或者我向同事发送多个静态 PNG 文件以展示绘图的不同方面。我希望能够发送图形对象,并让他们自己与之交互。
# Plot something
import matplotlib.pyplot as plt
fig,ax = plt.subplots()
ax.plot([1,2,3],[10,-10,30])
在您交互调整后,将图形对象保存为二进制文件:
import pickle
pickle.dump(fig, open('FigureObject.fig.pickle', 'wb')) # This is for Python 3 - py2 may need `file` instead of `open`
稍后打开该图,并应保存调整并保留GUI交互性:
import pickle
figx = pickle.load(open('FigureObject.fig.pickle', 'rb'))
figx.show() # Show the figure, edit it, etc.!
您甚至可以从图表中提取数据:
data = figx.axes[0].lines[0].get_data()
(它适用于线条、pcolor和imshow - pcolormesh使用一些技巧来重构扁平化的数据。)
我从使用Pickle保存Matplotlib图形获得了出色的提示。
从Matplotlib 1.2开始,我们现在拥有实验性的pickle支持。尝试一下,看看它是否适用于您的情况。如果您有任何问题,请在Matplotlib邮件列表上告诉我们,或在github.com/matplotlib/matplotlib上开启一个问题。
fig.show()
的顺序似乎并不重要 - 或许那个bug已经被修复了。我可以在show()
之前/之后进行pickle而没有问题。 - Demis这将是一个很棒的功能,但据我所知,Matplotlib尚未实现,由于图形存储的方式,自己实现可能会很困难。
我建议要么(a)分别处理数据并生成图表(将数据保存为唯一名称),编写一个图表生成脚本(加载指定的已保存数据文件),可根据需要进行编辑,或者(b)保存为PDF/SVG/PostScript格式,并在一些高级图形编辑器中进行编辑,如Adobe Illustrator(或Inkscape)。
2012年秋季更新: 如下面其他人所指出的(虽然在此作为被接受的答案提到),自Matplotlib 1.2版本以来,它允许您拾取图形。正如发布说明所述,这是一个实验性功能,不支持在一个Matplotlib版本中保存一个图形并在另一个版本中打开。从不受信任的来源恢复pickle也通常是不安全的。
对于共享/稍后编辑的绘图(需要进行重要数据处理并且可能需要在以后几个月内进行调整,例如在科学出版物的同行评审期间),我仍然建议使用以下工作流程:(1)编写一个数据处理脚本,在生成图表之前将处理好的数据(用于您的图表)保存到文件中;(2)拥有单独的图表生成脚本(根据需要进行调整)来重新创建图表。这样,对于每个图表,您可以快速运行脚本并重新生成它(并快速复制您的新数据的绘图设置)。尽管如此,pickling一个图表对于短期/交互式/探索性数据分析可能是方便的。
pickle
现在可以用于MPL图形,因此可以这样做,并且似乎工作得相当不错 - 就像Matlab的".fig"图形文件。 请参见下面的答案(目前)以了解如何执行此操作的示例。 - Demisimport pickle
output = open('interactive figure.pickle', 'wb')
pickle.dump(gcf(), output)
output.close()
很好的问题。这里是来自pylab.save
的文档:
尽管旧版的pylab函数仍然可用(您仍可以在pylab中引用它作为“mlab.save”),但现在的pylab不再提供保存函数。然而,对于纯文本文件,我们建议使用numpy.savetxt。对于保存numpy数组,我们建议使用numpy.save及其类似物numpy.load,它们在pylab中可用作np.save和np.load。
pylab.save
。实际上,从文档中可以看出,不应该使用它。 - Steve Tjoa我找到了一个相对简单的方法(虽然有点不寻常)来保存我的matplotlib图形。它的工作原理如下:
import libscript
import matplotlib.pyplot as plt
import numpy as np
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2*np.pi*t)
#<plot>
plt.plot(t, s)
plt.xlabel('time (s)')
plt.ylabel('voltage (mV)')
plt.title('About as simple as it gets, folks')
plt.grid(True)
plt.show()
#</plot>
save_plot(fileName='plot_01.py',obj=sys.argv[0],sel='plot',ctx=libscript.get_ctx(ctx_global=globals(),ctx_local=locals()))
具有以下定义的函数save_plot
(简化版本以便理解逻辑):
def save_plot(fileName='',obj=None,sel='',ctx={}):
"""
Save of matplolib plot to a stand alone python script containing all the data and configuration instructions to regenerate the interactive matplotlib figure.
Parameters
----------
fileName : [string] Path of the python script file to be created.
obj : [object] Function or python object containing the lines of code to create and configure the plot to be saved.
sel : [string] Name of the tag enclosing the lines of code to create and configure the plot to be saved.
ctx : [dict] Dictionary containing the execution context. Values for variables not defined in the lines of code for the plot will be fetched from the context.
Returns
-------
Return ``'done'`` once the plot has been saved to a python script file. This file contains all the input data and configuration to re-create the original interactive matplotlib figure.
"""
import os
import libscript
N_indent=4
src=libscript.get_src(obj=obj,sel=sel)
src=libscript.prepend_ctx(src=src,ctx=ctx,debug=False)
src='\n'.join([' '*N_indent+line for line in src.split('\n')])
if(os.path.isfile(fileName)): os.remove(fileName)
with open(fileName,'w') as f:
f.write('import sys\n')
f.write('sys.dont_write_bytecode=True\n')
f.write('def main():\n')
f.write(src+'\n')
f.write('if(__name__=="__main__"):\n')
f.write(' '*N_indent+'main()\n')
return 'done'
或者像这样定义函数save_plot
(使用zip压缩生成较轻的图形文件,更好的版本):
def save_plot(fileName='',obj=None,sel='',ctx={}):
import os
import json
import zlib
import base64
import libscript
N_indent=4
level=9#0 to 9, default: 6
src=libscript.get_src(obj=obj,sel=sel)
obj=libscript.load_obj(src=src,ctx=ctx,debug=False)
bin=base64.b64encode(zlib.compress(json.dumps(obj),level))
if(os.path.isfile(fileName)): os.remove(fileName)
with open(fileName,'w') as f:
f.write('import sys\n')
f.write('sys.dont_write_bytecode=True\n')
f.write('def main():\n')
f.write(' '*N_indent+'import base64\n')
f.write(' '*N_indent+'import zlib\n')
f.write(' '*N_indent+'import json\n')
f.write(' '*N_indent+'import libscript\n')
f.write(' '*N_indent+'bin="'+str(bin)+'"\n')
f.write(' '*N_indent+'obj=json.loads(zlib.decompress(base64.b64decode(bin)))\n')
f.write(' '*N_indent+'libscript.exec_obj(obj=obj,tempfile=False)\n')
f.write('if(__name__=="__main__"):\n')
f.write(' '*N_indent+'main()\n')
return 'done'
这里使用了我自己的一个模块libscript
,它主要依赖于inspect
和ast
模块。如果有兴趣的话,我可以尝试在Github上分享它(首先需要进行一些清理工作并开始使用Github)。
save_plot
函数和libscript
模块背后的想法是获取创建图形的Python指令(使用inspect
模块),分析它们(使用ast
模块)以提取所有变量、函数和模块导入所依赖的内容,从执行上下文中提取这些内容,并将它们序列化为Python指令(变量的代码将类似于t=[0.0,2.0,0.01]
,模块的代码将类似于import matplotlib.pyplot as plt
...),并将其作为前缀添加到图形指令中。生成的Python指令被保存为一个Python脚本,执行该脚本将重新构建原始的matplotlib图形。
正如您所想象的那样,这对于大多数(如果不是全部)matplotlib图形都有效。
如果您想将 Python 绘图保存为交互式图以便像 MATLAB .fig 文件那样进行修改并与他人共享,则可以尝试使用以下代码。在这里,z_data.values
仅是一个 numpy ndarray,因此您可以使用相同的代码来绘制和保存自己的数据,无需使用 pandas。
此处生成的文件可以通过单击它并在 Chrome/Firefox/Edge 等浏览器中打开,任何人都可以进行交互式修改,无论是否具有 python。
import plotly.graph_objects as go
import pandas as pd
z_data=pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/api_docs/mt_bruno_elevation.csv')
fig = go.Figure(data=[go.Surface(z=z_data.values)])
fig.update_layout(title='Mt Bruno Elevation', autosize=False,
width=500, height=500,
margin=dict(l=65, r=50, b=65, t=90))
fig.show()
fig.write_html("testfile.html")
pickle
文档中指出我们需要设置环境与对象被pickled(保存)时类似,但我发现在unpickling(加载)时不需要import matplotlib.pyplot as plt
- 它会将导入语句保存在pickled文件中。 - Demiswith open('FigureObject.fig.pickle', 'rb') as file: figx = pickle.load(file)
。 - strpeterfigx.show()
后继续运行并可能立即终止,您应该调用plt.show()
,它是阻塞的。 - maechler