保存交互式 Matplotlib 图形
- 2025-02-28 08:23:00
- admin 原创
- 64
问题描述:
有没有办法保存 Matplotlib 图形,以便可以重新打开并恢复典型的交互?(像 MATLAB 中的 .fig 格式?)
我发现自己要多次运行相同的脚本来生成这些交互式图形。或者我向同事发送多个静态 PNG 文件来展示情节的不同方面。我宁愿发送图形对象并让他们自己与之交互。
解决方案 1:
我刚刚发现了如何做到这一点。@pelson 提到的“实验性 pickle 支持”效果很好。
尝试一下:
# 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 图形中获得了很好的提示。
解决方案 2:
Matplotlib 自 1.2 版起允许您对图形进行 pickle。正如发行说明所述,这是一项实验性功能,不支持在一个 matplotlib 版本中保存图形并在另一个版本中打开。从不受信任的来源恢复 pickle 通常也是不安全的。
对于共享/稍后编辑图表(这需要首先进行大量数据处理,并且可能需要在几个月后进行调整,例如在科学出版物的同行评审期间),我仍然建议以下工作流程:(1)在生成图表之前使用数据处理脚本将处理后的数据(进入您的图表)保存到文件中,以及(2)使用单独的图表生成脚本(根据需要进行调整)来重新创建图表。这样,对于每个图表,您都可以快速运行脚本并重新生成它(并使用新数据快速复制您的图表设置)。也就是说,pickle 图表可能方便短期/交互式/探索性数据分析。
如果您使用的是尚未支持的旧版本,我建议(a)将数据处理与生成图形分开(使用唯一名称保存数据)并编写图形生成脚本(加载已保存数据的指定文件)并根据需要进行编辑或(b)另存为 PDF / SVG / PostScript格式并在一些花哨的图形编辑器(如Adobe Illustrator(或Inkscape ))中进行编辑。
解决方案 3:
为什么不直接发送 Python 脚本?MATLAB 的 .fig 文件要求收件人有 MATLAB 才能显示它们,因此这相当于发送需要 Matplotlib 才能显示的 Python 脚本。
或者(免责声明:我还没有尝试过),你可以尝试腌制这个数字:
import pickle
output = open('interactive figure.pickle', 'wb')
pickle.dump(gcf(), output)
output.close()
解决方案 4:
好问题。以下是文档文本pylab.save
:
pylab 不再提供保存函数,但旧的 pylab 函数仍可用作 matplotlib.mlab.save(您仍可以在 pylab 中将其称为“mlab.save”)。但是,对于纯文本文件,我们建议使用 numpy.savetxt。对于保存 numpy 数组,我们建议使用 numpy.save 及其类似函数 numpy.load,它们在 pylab 中可用作 np.save 和 np.load。
解决方案 5:
我想到了一个相对简单(但略显不寻常)的方法来保存我的 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='
'.join([' '*N_indent+line for line in src.split('
')])
if(os.path.isfile(fileName)): os.remove(fileName)
with open(fileName,'w') as f:
f.write('import sys
')
f.write('sys.dont_write_bytecode=True
')
f.write('def main():
')
f.write(src+'
')
f.write('if(__name__=="__main__"):
')
f.write(' '*N_indent+'main()
')
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
')
f.write('sys.dont_write_bytecode=True
')
f.write('def main():
')
f.write(' '*N_indent+'import base64
')
f.write(' '*N_indent+'import zlib
')
f.write(' '*N_indent+'import json
')
f.write(' '*N_indent+'import libscript
')
f.write(' '*N_indent+'bin="'+str(bin)+'"
')
f.write(' '*N_indent+'obj=json.loads(zlib.decompress(base64.b64decode(bin)))
')
f.write(' '*N_indent+'libscript.exec_obj(obj=obj,tempfile=False)
')
f.write('if(__name__=="__main__"):
')
f.write(' '*N_indent+'main()
')
return 'done'
libscript
这利用了我自己的模块,它主要依赖于模块inspect
和ast
。如果有人感兴趣,我可以尝试在 Github 上分享它(首先需要进行一些清理,然后我开始使用 Github)。
save_plot
此函数和模块背后的想法libscript
是获取创建图形的 Python 指令(使用 module inspect
),分析它们(使用 module ast
)以提取它所依赖的所有变量、函数和模块,从执行上下文中提取它们并将它们序列化为 Python 指令(变量的代码将类似于t=[0.0,2.0,0.01]
... ,模块的代码将类似于import matplotlib.pyplot as plt
... )添加到图形指令的前面。生成的 Python 指令将保存为 Python 脚本,其执行将重建原始 matplotlib 图形。
你可以想象,这对大多数(如果不是全部)matplotlib 图形都很有效。
解决方案 6:
如果您希望将 Python 图保存为交互式图形,以便修改并与其他人共享(如 MATLAB .fig 文件),那么您可以尝试使用以下代码。这里z_data.values
只是一个 numpy ndarray,因此您可以使用相同的代码来绘制和保存自己的数据。那么就不需要使用 pandas 了。
任何人(无论是否使用 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")
扫码咨询,免费领取项目管理大礼包!