【问题标题】:How can I plot many thousands of circles quickly?如何快速绘制数千个圆圈?
【发布时间】:2015-12-03 07:49:57
【问题描述】:

我正在尝试绘制几个(数千个)圆形对象 - 我没有太多使用 python 的经验。我有兴趣指定位置、半径和颜色。有没有更有效的方法来达到同样的效果?:

import matplotlib.pyplot as plt

xvals = [0,.1,.2,.3]
yvals = [0,.1,.2,.3]
rvals = [0,.1,.1,.1]

c1vals = [0,.1,0..1]
c2vals = [.1,0,.1,0]
c3vals = [.1,.1,.1,.1]

for q in range(0,4):
    circle1=plt.Circle((xvals[q], yvals[q]), rvals[q], color=[0,0,0])
    plt.gcf().gca().add_artist(circle1)

【问题讨论】:

标签: python performance object matplotlib


【解决方案1】:

这里的关键是使用Collection。在您的情况下,您想创建一个PatchCollection

Matplotlib 通过使用集合来优化绘制许多相似的艺术家。它比单独绘制每个要快得多。此外,该情节不会包含成千上万的个人艺术家,只有一个收藏。这加快了每次绘制情节时需要对每个艺术家进行的许多其他杂项操作。

scatter 实际上比您当前的方法快很多,因为它会添加一个集合而不是单独的艺术家。但是,它也会绘制尺寸不在数据坐标中的标记。

要解决这个问题,您可以使用与 scatter 相同的方法,但手动创建集合。

举个例子:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.collections

num = 5000
sizes = 0.2 * np.random.random(num)
xy = 50 * np.random.random((num, 2))

# Note that the patches won't be added to the axes, instead a collection will
patches = [plt.Circle(center, size) for center, size in zip(xy, sizes)]

fig, ax = plt.subplots()

coll = matplotlib.collections.PatchCollection(patches, facecolors='black')
ax.add_collection(coll)

ax.margins(0.01)
plt.show()

这对我来说非常顺利。只是为了证明圆圈在数据坐标中,注意如果我们放大一个窄矩形会发生什么(注意:这假设绘图的方面设置为auto):


如果您真的很注重速度,可以按照@tcaswell 的建议使用EllipseCollection

EllipseCollection 只会创建 一个 路径,但会在绘制时将其缩放并转换为您指定的位置/大小。

缺点是虽然大小可以在数据坐标中,但圆始终是圆,即使图的纵横比不是 1。(即圆不会像在上图)。

优点是速度快。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.collections

num = 5000
sizes = 0.4 * np.random.random(num)
xy = 50 * np.random.random((num, 2))

fig, ax = plt.subplots()

coll = matplotlib.collections.EllipseCollection(sizes, sizes,
                                                np.zeros_like(sizes),
                                                offsets=xy, units='x',
                                                transOffset=ax.transData,
                                                **kwargs)
ax.add_collection(coll)
ax.margins(0.01)
plt.show()

当我们放大与第二个图相似的区域时,请注意差异。圆圈变大(大小以数据坐标为单位),但保持圆圈而不是拉长。它们不是“数据”空间中圆圈的准确表示。

为了说明时差,下面是使用三种方法中的每一种创建和绘制具有相同 5000 个圆圈的图形的时间:

In [5]: %timeit time_plotting(circles)
1 loops, best of 3: 3.84 s per loop

In [6]: %timeit time_plotting(patch_collection)
1 loops, best of 3: 1.37 s per loop

In [7]: %timeit time_plotting(ellipse_collection)
1 loops, best of 3: 228 ms per loop

【讨论】:

  • 这个问题也提示github.com/matplotlib/matplotlib/pull/5035,因为它似乎CircleCollection应该做你想做的事,但它被硬编码为以点为单位^2。
  • 这是一个很好的答案。我打算用计时赛来编辑,但你打败了我:)。 FWIW,我正在比较 circles 与 patch_collection 并得到相似的数字比率。谢谢!
  • @tcaswell - 不错!谢谢!
  • 有人可以告诉如何使用上面的 PathCollection 制作未填充的圆圈
【解决方案2】:

scatter 可能比 plt.Circle 更适合您,尽管它不会让任何东西运行得更快。

for i in range(4):
    mp.scatter(xvals[i], yvals[i], s=rvals[i])

如果您可以处理相同大小的圆圈,那么mp.plot(xvals[i], yvals[i], marker='o') 的性能会更高。

但这可能是matplotlib 限制,而不是语言限制。有优秀的 JavaScript 库可以有效地绘制数千个数据点 (d3.js)。也许这里有人会知道你可以从 Python 调用的一个。

【讨论】:

  • 不幸的是,scatter 不允许您以“数据”单位指定半径,而是以像素为单位进行输入。
【解决方案3】:

您当然希望将...gca() 移出您的循环。您也可以使用列表推导。

fig = plt.figure()
ax = plt.gcf().gca()

[ax.add_artist(plt.Circle((xvals[q],yvals[q]),rvals[q],color=[0,0,0])) 
 for q in xrange(4)]  # range(4) for Python3

以下是使用各种方法生成 4,000 个圆圈的一些测试:

xvals = [0,.1,.2,.3] * 1000
yvals = [0,.1,.2,.3] * 1000
rvals = [0,.1,.1,.1] * 1000

%%timeit -n5 fig = plt.figure(); ax = plt.gcf().gca()
for q in range(4000):
    circle1=plt.Circle((xvals[q], yvals[q]), rvals[q], color=[0,0,0])
    plt.gcf().gca().add_artist(circle1)
5 loops, best of 3: 792 ms per loop

%%timeit -n5 fig = plt.figure(); ax = plt.gcf().gca()
for q in xrange(4000):
    ax.add_artist(plt.Circle((xvals[q],yvals[q]),rvals[q],color=[0,0,0]))
5 loops, best of 3: 779 ms per loop

%%timeit -n5 fig = plt.figure(); ax = plt.gcf().gca()
[ax.add_artist(plt.Circle((xvals[q],yvals[q]),rvals[q],color=[0,0,0])) for q in xrange(4000)]
5 loops, best of 3: 730 ms per loop

【讨论】:

  • 这看起来是我想要的正确方向......列表理解到底是什么?
  • 列表推导通常比for 循环更有效。 docs.python.org/2/tutorial/…
【解决方案4】:

不确定您真正想要做什么,或者您的问题或顾虑是什么,但这里有一种完全不同的绘制圆圈的方法...制作一个像这样的SVG 文件并将其命名为circles.svg

<?xml version="1.0" standalone="no"?>
<svg width="500" height="300" version="1.1" xmlns="http://www.w3.org/2000/svg">
  <circle cx="100" cy="175" r="200" stroke="lime"   fill="coral"  stroke-width="28"/>
  <circle cx="25"  cy="75"  r="80"  stroke="red"    fill="yellow" stroke-width="5"/>
  <circle cx="400" cy="280" r="20"  stroke="black"  fill="blue"   stroke-width="10"/>
</svg>

并将其传递给 ImageMagick 以制作成这样的 PNG 文件:

convert circles.svg result.png

【讨论】:

  • 感谢您的建议。这是一个更大的项目的一部分,我想在其中操作数据,所以我更愿意将它保存在 Python 中。
  • 致投反对票的人...如果您要投反对票,您至少可以礼貌地解释一下原因,以便我们都能学到一些东西。在 OP 问题的参数范围内,这是一个完全合理的答案。
猜你喜欢
  • 2014-12-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-21
  • 2019-09-28
  • 1970-01-01
  • 2021-06-21
  • 1970-01-01
相关资源
最近更新 更多