保存交互式 Matplotlib 图形

有没有一种方法可以保存 Matplotlib 图形,使其可以重新打开并恢复典型的交互?(喜欢。图形格式?)

我发现自己多次运行相同的脚本来生成这些交互式图形。或者我发送给我的同事多个静态 PNG 文件,以显示一个情节的不同方面。我宁愿发送图形对象,让他们与它自己交互。

106240 次浏览

问得好,这是来自 pylab.save的文档:

Pylab 不再提供保存函数,尽管旧的 pylab 函数仍然可以使用 matplotlib.mlab.save (您仍然可以使用 在 pylab 中将其称为“ mlab.save”) 对于保存 numpy 数组, 我们推荐 numpy.save 和它的模拟 numpy.load,它们是 可以在 pylab 中作为 np.save 和 np.load 使用。

这将是一个很棒的功能,但是 AFAIK 在 Matplotlib 并没有实现,而且由于数据的存储方式,很可能很难自己实现。

我建议(a)将处理数据与生成图形分开(这样可以保存具有唯一名称的数据) ,并编写一个图形生成脚本(加载所保存数据的指定文件) ,然后根据自己的需要进行编辑; 或者(b)保存为 PDF/SVG/PostScript格式,并在一些花哨的图形编辑器中进行编辑,如 Adobe Illustrator(或 墨迹)。

编辑后2012年秋季 : 正如其他人在下面指出的(虽然在这里提到这是公认的答案) ,Matplotlib 自1.2版本以来允许你腌制数字。作为 释放通知状态,它是一个实验性的特性,不支持在一个 matplotlib 版本中保存图形,而在另一个版本中打开图形。从不可信的来源恢复 pickle 通常也是不安全的。

对于共享/稍后编辑的情节(首先需要重要的数据处理,可能需要在几个月后为科学出版物进行同行评审) ,我仍然建议(1)有一个数据处理脚本,在生成情节之前将处理过的数据(进入你的情节)保存到文件中,和(2)有一个单独的情节生成脚本(你可以根据需要进行调整)来重建情节。通过这种方法,您可以快速运行一个脚本并重新生成它(并用新数据快速复制您的绘图设置)。也就是说,选择一个数字对于短期/交互/探索性数据分析来说是很方便的。

为什么不直接发送 Python 脚本呢?MATLAB 的。Fig 文件要求收件人使用 MATLAB 来显示它们,因此这相当于发送一个需要 Matplotlib 来显示的 Python 脚本。

或者(免责声明: 我还没有试过这个) ,你可以尝试腌制这个图形:

import pickle
output = open('interactive figure.pickle', 'wb')
pickle.dump(gcf(), output)
output.close()

至于 Matplotlib 1.2,我们现在有了实验性的 泡菜支持。试一下,看看对你的案子有没有帮助。如果您有任何问题,请让我们知道在 Matplotlib 邮件列表或通过打开一个问题在 Github.com/matplotlib/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 使用一些技巧来重建扁平化数据。)

我从 使用 Pickle 保存 Matplotlib 图形那里得到了很好的消息。

我想出了一个相对简单的方法来保存我的 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,它主要依赖于模块 inspectast。如果有兴趣,我可以尝试在 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)共享。然后你可以尝试使用下面的代码。在这里,z_data.values只是一个简单的 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")