【问题标题】:Saving interactive Matplotlib figures保存交互式 Matplotlib 图形
【发布时间】:2010-12-03 18:41:11
【问题描述】:

有没有办法保存 Matplotlib 图形,以便可以重新打开并恢复典型的交互? (像 MATLAB 中的 .fig 格式?)

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

【问题讨论】:

    标签: python matplotlib


    【解决方案1】:

    我刚刚发现了如何做到这一点。 @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 那里得到了很好的提示。

    【讨论】:

    • 我认为这对版本更改等不可靠,并且在 py2.x 和 py3.x 之间不兼容。我还认为pickle 文档指出我们需要设置与对象被腌制(保存)时类似的环境,但我发现你不需要import matplotlib.pyplot as plt 在解封(加载)时 - 它保存了腌制文件中的导入语句。
    • 您应该考虑关闭打开的文件:例如with open('FigureObject.fig.pickle', 'rb') as file: figx = pickle.load(file)
    • 我刚刚得到:'AttributeError: 'Figure' object has no attribute '_cachedRenderer''
    • 如果您不希望脚本继续运行并可能在figx.show() 之后立即终止,您应该调用plt.show(),它是阻塞的。
    • 很好的答案!不幸的是,截至目前,最后一个链接(使用 Pickle 保存 Matplotlib 图形)已失效。
    【解决方案2】:

    从 Matplotlib 1.2 开始,我们现在有实验性的 pickle 支持。试一试,看看它是否适合您的情况。如果您有任何问题,请通过 Matplotlib mailing list 或通过在 github.com/matplotlib/matplotlib 上打开问题告诉我们。

    【讨论】:

    • 任何理由都可以将此有用的功能添加到图形的“另存为”本身。添加 .pkl 可能吗?
    • 好主意@dashesy。如果您想尝试实施它,我会支持?
    • 这仅适用于后端子集吗?当我尝试在 OSX 中腌制一个简单的数字时,我得到PicklingError: Can't pickle <type '_macosx.GraphicsContext'>: it's not found as _macosx.GraphicsContext。
    • 上述PicklingError 仅在您在泡菜之前调用plt.show() 时才会发生。所以只需将plt.show() 放在pickle.dump() 之后。
    • 在我的 MacOS 10.11 上的 py3.5 上,fig.show() 的顺序似乎无关紧要 - 也许这个错误已修复。我可以在show() 之前/之后腌制没有问题。
    【解决方案3】:

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

    我建议 (a) 将数据与生成图形分开处理(以唯一名称保存数据)并编写图形生成脚本(加载已保存数据的指定文件)并根据需要进行编辑或 (b) 保存为 PDF/SVG/PostScript 格式并在一些花哨的图形编辑器中进行编辑,例如 Adobe Illustrator(或 Inkscape)。

    2012 年秋季后编辑:正如其他人在下面指出的那样(尽管在这里提到这是公认的答案),自 1.2 版以来的 Matplotlib 允许您腌制数字。作为release notes state,它是一项实验性功能,不支持在一个matplotlib 版本中保存图形并在另一个版本中打开。从不受信任的来源恢复泡菜通常也是不安全的。

    对于共享/稍后编辑绘图(需要首先进行大量数据处理,并且可能需要在几个月后进行调整,例如在科学出版物的同行评审期间),我仍然建议 (1) 的工作流程具有之前的数据处理脚本生成绘图会将处理后的数据(进入绘图)保存到文件中,并且 (2) 有一个单独的绘图生成脚本(您可以根据需要进行调整)来重新创建绘图。这样,对于每个绘图,您都可以快速运行脚本并重新生成它(并使用新数据快速复制绘图设置)。也就是说,腌制图形可能便于进行短期/交互式/探索性数据分析。

    【讨论】:

    • 有点惊讶这没有实现。但是好的,我会将处理过的数据保存在一个中间文件中,并将它和一个用于绘图的脚本发送给同事。谢谢。
    • 我怀疑实现起来很困难,这就是为什么它在 MATLAB 上运行得如此糟糕的原因。回到我使用它的时候,数字会导致 MATLAB 崩溃,甚至稍微不同的版本也无法读取彼此的 .fig 文件。
    • pickle 现在适用于 MPL 图形,因此可以这样做并且看起来工作得很好——几乎就像 Matlab 的“.fig”图形文件。请参阅下面的答案(现在?),了解如何做到这一点。
    • @Demis:正如 ptomato 在下面的回答中指出的那样,它当时已经存在。
    • @strpeter - 正如this comment 中所指出的,2010 年的 Matlab 数据不可腌制。实验功能添加了matplotlib 1.2 released in Fall 2012。如那里所述,您不应该期望它在 matplotlib 版本之间工作,也不应该打开来自不受信任来源的泡菜。
    【解决方案4】:

    为什么不直接发送 Python 脚本? MATLAB 的 .fig 文件要求接收方拥有 MATLAB 才能显示它们,因此这大约相当于发送需要 Matplotlib 显示的 Python 脚本。

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

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

    【讨论】:

    • 不幸的是,matplotlib 数字不可腌制,因此这种方法行不通。在幕后,有太多不支持酸洗的 C 扩展。我完全同意只发送脚本+数据,虽然......我想我从来没有真正看到过matlab保存的.fig的点,所以我从来没有使用过它们。无论如何,根据我的经验,从长远来看,向某人发送独立的代码和数据是最简单的。不过,如果 matplotlib 的图形对象可以腌制,那就太好了。
    • 即使我们的预处理数据也有些大,绘图过程也很复杂。不过看起来是唯一的办法。谢谢。
    • 数字显然现在可以腌制了——效果很好!下面的例子。
    • pickle 的好处是您不必以编程方式调整所有图形/子图间距/位置。您可以使用 MPL 绘图的 GUI 使图形看起来不错等,然后保存 MyPlot.fig.pickle 文件 - 保留以后根据需要调整绘图演示的能力。这也是 Matlab 的 .fig 文件的优点。当您需要更改无花果的大小/纵横比(用于插入演示文稿/论文)时特别有用。
    【解决方案5】:

    好问题。这是来自pylab.save的文档文本:

    pylab 不再提供保存功能,虽然旧的 pylab 函数仍然可以作为 matplotlib.mlab.save 使用(你仍然可以 在 pylab 中将其称为“mlab.save”)。但是,对于纯文本 文件,我们推荐 numpy.savetxt。为了保存 numpy 数组, 我们推荐 numpy.save 和它的模拟 numpy.load,它们是 在 pylab 中可用作 np.save 和 np.load。

    【讨论】:

    • 这会保存来自 pylab 对象的数据,但不允许您重新生成图形。
    • 正确。我应该澄清答案不是建议使用pylab.save。事实上,从文档文本来看,似乎应该不使用它。
    • 是否有任何外部方法可以发送 3D 图?甚至可能是一个简单的 GUI 来执行..
    【解决方案6】:

    我想出了一种相对简单的方法(但有点不合常规)来保存我的 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 图。

    【讨论】:

      【解决方案7】:

      如果您希望将 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")
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-09-03
        • 2012-07-30
        • 2017-10-26
        • 1970-01-01
        • 2018-04-28
        • 2013-04-17
        相关资源
        最近更新 更多