Matplotlib - 创建多个子图的快速方法?

5
我正在使用matplotlib创建一个包含许多小子图的图形(类似于4行8列)。我尝试了几种不同的方法,而使matplotlib制作轴的最快速度是约2秒。我看到了this post,其中提到只使用一个轴对象来显示许多小图像,但我想在轴上能够有刻度和标题。是否有任何方法可以加速这个过程,或者在matplotlib中制作轴只需要相当长的时间?
这是我迄今为止尝试过的:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import ImageGrid
import time

fig = plt.figure(figsize=(10,6))

plt.clf(); t = time.time()
grid = ImageGrid(fig, 111, 
                nrows_ncols = (4, 8))
print 'Time using ImageGrid: %.2f seconds'%(time.time()-t)

plt.clf(); t = time.time()
axes = plt.subplots(4,8)
print 'Time using plt.subplots: %.2f seconds'%(time.time()-t)

plt.clf(); t = time.time()
axes = []
for row in range(0,4):
    for col in range(1,9):
        ax = plt.subplot(4,8,row*8+col)
        axes.append(ax)
print 'Time using ptl.subplot loop: %.2f seconds'%(time.time()-t)

输出:

Time using ImageGrid: 4.25 seconds
Time using plt.subplots: 2.11 seconds
Time using ptl.subplot loop: 2.34 seconds

采用Joe Kington的建议,我试图将图形和轴pickle起来,这样每次运行脚本时就不需要重新创建它们。然而,加载文件实际上需要更长时间:

import matplotlib.pyplot as plt
import pickle
import time

t = time.time()
fig,axes = plt.subplots(4,8,figsize=(10,6))
print 'Time using plt.subplots: %.2f seconds'%(time.time()-t)

pickle.dump((fig,axes),open('fig.p','wb'))

t = time.time()
fig,axes = pickle.load(open('fig.p','rb'))

print 'Time to load pickled figure: %.2f seconds'%(time.time()-t)

输出:

Time using plt.subplots: 2.01 seconds
Time to load pickled figure: 3.09 seconds
1个回答

7
您不可能通过任何实质性的方式击败subplots。创建新的轴是一项相当昂贵的操作,每次您都要创建32个轴。但这只会发生一次。
如果创建新的图和轴确实是您的瓶颈,那么您可能正在做错什么。
如果您想要创建动画,请更新艺术家(例如image.set_data等)并重新绘制,而不是每次都创建一个新的图和轴。
(另外,axes = plt.subplots(4, 8)是不正确的。如您当前所写,axes将成为一个元组而不是一系列轴对象。 subplots返回一个图形实例和一组轴。应该是fig, axes = plt.subplots(...)。)
我在下面的评论中简单谈到了pickling:
当您更改无关参数时,缓存“昂贵”的结果通常很方便。这在科学“脚本”中特别常见,其中您通常会有一些相当慢的数据解析或中间计算以及您半交互地“调整”的相关参数集。
只需注释掉几行代码并明确保存/加载pickled结果即可。没有任何问题。
但是,我发现保留一个装饰器很方便,可以自动执行此操作。在许多情况下,有更好的方法来解决这个问题,但对于一种通用的解决方案,它非常方便:
import os
import cPickle as pickle
from functools import wraps

class Cached(object):
    def __init__(self, filename):
        self.filename = filename

    def __call__(self, func):
        @wraps(func)
        def new_func(*args, **kwargs):
            if not os.path.exists(self.filename):
                results = func(*args, **kwargs)
                with open(self.filename, 'w') as outfile:
                    pickle.dump(results, outfile, pickle.HIGHEST_PROTOCOL)
            else:
                with open(self.filename, 'r') as infile:
                    results = pickle.load(infile)
            return results
        return new_func

然后你可以像这样做(假设上面的类在utilities.py中):

import matplotlib.pyplot as plt
from utilities import Cached

@Cached('temp.pkl')
def setup():
    fig, axes = plt.subplots(8, 4)
    # And perhaps some other data parsing/whatever
    return fig, axes

fig, axes = setup()
for ax in axes.flat:
    ax.plot(range(10))
plt.show()

装饰函数只有在给定的文件名(“temp.pkl”)不存在时才会运行,并且否则将加载存储在temp.pkl中的缓存结果。这适用于任何可以被pickle的东西,不仅仅是matplotlib图形。
但要注意使用已保存的状态。如果更改setup的内容并重新运行,则会返回“旧”的结果!!如果更改包装函数的内容,请务必手动删除缓存文件。
当然,如果您只在一个地方使用它,那么这就是过度设计了。但是,如果您经常缓存中间结果,这将非常方便。

好的,知道我不能比plt.subplots()更好了。我不是在制作动画,只是一个有很多小图的图形。所以,每当我对图形进行更改时,我需要重新运行脚本并等待几秒钟来设置子图。这可能不会成为瓶颈,但仍然是我需要等待的烦人的几秒钟。谢谢你的回答! - DanHickstein
1
这很有道理。当你试图调整各种参数并重新运行时,几秒钟的延迟肯定会让人感到烦恼。你可以尝试使用 pickling 技术,将具备网格子图的“空白”图形保存为二进制文件,然后在下一次需要使用时加载该二进制文件,而不是每次都重新创建一个新的图形。在这种情况下,速度会更快一些。 - Joe Kington
我尝试了腌制方法,但实际上比创建子图花费更长的时间。但是,也许我没有正确使用pickle模块。我将其添加到问题的底部。 - DanHickstein
1
@DanHickstein - 尝试a)指定二进制pickle协议(例如pickle.dump(objects, file, pickle.HIGHEST_PROTOCOL))和b)如果您使用的是Python 2.x,则使用cPickle而不是Pickle。(只需使用import cPickle as pickle而不是import pickle。)在我的机器上,Pickling大约快3倍,但这将严重取决于I/O速度等因素,因此在您的系统上可能会更快或更慢。 - Joe Kington
使用 cPickle,时间缩短至 1.3 秒。使用 pickle.HIGHEST_PROTOCOL 进一步将时间缩短至 0.7 秒。谢谢! - DanHickstein
今天刚学习了装饰器,这是一个非常好的使用示例。谢谢! - James Owers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接