【发布时间】:2010-12-03 18:41:11
【问题描述】:
有没有办法保存 Matplotlib 图形,以便可以重新打开并恢复典型的交互? (像 MATLAB 中的 .fig 格式?)
我发现自己多次运行相同的脚本来生成这些交互式图形。或者我正在向我的同事发送多个静态 PNG 文件来显示情节的不同方面。我宁愿发送图形对象并让他们自己与之交互。
【问题讨论】:
标签: python matplotlib
有没有办法保存 Matplotlib 图形,以便可以重新打开并恢复典型的交互? (像 MATLAB 中的 .fig 格式?)
我发现自己多次运行相同的脚本来生成这些交互式图形。或者我正在向我的同事发送多个静态 PNG 文件来显示情节的不同方面。我宁愿发送图形对象并让他们自己与之交互。
【问题讨论】:
标签: python matplotlib
我刚刚发现了如何做到这一点。 @pelson 提到的“实验性泡菜支持”效果很好。
试试这个:
# 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 works with some tricks to reconstruct the flattened data。)
我从 Saving Matplotlib Figures Using Pickle 那里得到了很好的提示。
【讨论】:
pickle 文档指出我们需要设置与对象被腌制(保存)时类似的环境,但我发现你不需要import matplotlib.pyplot as plt 在解封(加载)时 - 它保存了腌制文件中的导入语句。
with open('FigureObject.fig.pickle', 'rb') as file: figx = pickle.load(file)
figx.show() 之后立即终止,您应该调用plt.show(),它是阻塞的。
从 Matplotlib 1.2 开始,我们现在有实验性的 pickle 支持。试一试,看看它是否适合您的情况。如果您有任何问题,请通过 Matplotlib mailing list 或通过在 github.com/matplotlib/matplotlib 上打开问题告诉我们。
【讨论】:
PicklingError: Can't pickle <type '_macosx.GraphicsContext'>: it's not found as _macosx.GraphicsContext。
PicklingError 仅在您在泡菜之前调用plt.show() 时才会发生。所以只需将plt.show() 放在pickle.dump() 之后。
fig.show() 的顺序似乎无关紧要 - 也许这个错误已修复。我可以在show() 之前/之后腌制没有问题。
这将是一个很棒的功能,但 AFAIK 它没有在 Matplotlib 中实现,并且由于数字的存储方式,您自己可能难以实现。
我建议 (a) 将数据与生成图形分开处理(以唯一名称保存数据)并编写图形生成脚本(加载已保存数据的指定文件)并根据需要进行编辑或 (b) 保存为 PDF/SVG/PostScript 格式并在一些花哨的图形编辑器中进行编辑,例如 Adobe Illustrator(或 Inkscape)。
2012 年秋季后编辑:正如其他人在下面指出的那样(尽管在这里提到这是公认的答案),自 1.2 版以来的 Matplotlib 允许您腌制数字。作为release notes state,它是一项实验性功能,不支持在一个matplotlib 版本中保存图形并在另一个版本中打开。从不受信任的来源恢复泡菜通常也是不安全的。
对于共享/稍后编辑绘图(需要首先进行大量数据处理,并且可能需要在几个月后进行调整,例如在科学出版物的同行评审期间),我仍然建议 (1) 的工作流程具有之前的数据处理脚本生成绘图会将处理后的数据(进入绘图)保存到文件中,并且 (2) 有一个单独的绘图生成脚本(您可以根据需要进行调整)来重新创建绘图。这样,对于每个绘图,您都可以快速运行脚本并重新生成它(并使用新数据快速复制绘图设置)。也就是说,腌制图形可能便于进行短期/交互式/探索性数据分析。
【讨论】:
pickle 现在适用于 MPL 图形,因此可以这样做并且看起来工作得很好——几乎就像 Matlab 的“.fig”图形文件。请参阅下面的答案(现在?),了解如何做到这一点。
为什么不直接发送 Python 脚本? MATLAB 的 .fig 文件要求接收方拥有 MATLAB 才能显示它们,因此这大约相当于发送需要 Matplotlib 显示的 Python 脚本。
或者(免责声明:我还没有尝试过),你可以尝试腌制这个数字:
import pickle
output = open('interactive figure.pickle', 'wb')
pickle.dump(gcf(), output)
output.close()
【讨论】:
MyPlot.fig.pickle 文件 - 保留以后根据需要调整绘图演示的能力。这也是 Matlab 的 .fig 文件的优点。当您需要更改无花果的大小/纵横比(用于插入演示文稿/论文)时特别有用。
好问题。这是来自pylab.save的文档文本:
pylab 不再提供保存功能,虽然旧的 pylab 函数仍然可以作为 matplotlib.mlab.save 使用(你仍然可以 在 pylab 中将其称为“mlab.save”)。但是,对于纯文本 文件,我们推荐 numpy.savetxt。为了保存 numpy 数组, 我们推荐 numpy.save 和它的模拟 numpy.load,它们是 在 pylab 中可用作 np.save 和 np.load。
【讨论】:
pylab.save。事实上,从文档文本来看,似乎应该不使用它。
我想出了一种相对简单的方法(但有点不合常规)来保存我的 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,因此您可以使用相同的代码来绘制和保存您自己的数据。那就不用熊猫了。
这里生成的文件可以被任何有或没有 python 的人打开和交互式修改,只需点击它并在 Chrome/Firefox/Edge 等浏览器中打开。
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")
【讨论】: