【问题标题】:only writing visible points to disk of an overplotted scatterplot仅将可见点写入重叠散点图的磁盘
【发布时间】:2020-03-23 17:13:23
【问题描述】:

我正在创建大约 10000 个点的 matplotlib 散点图。在我使用的点大小下,这会导致过度绘制,即某些点将被绘制在它们上面的点隐藏。

虽然我不介意看不到隐藏点这一事实,但当我将图形以 pdf(或其他矢量格式)写入磁盘时,它们会被冗余写出,从而导致文件很大。

有没有办法创建只将可见点写入文件的矢量图像?这类似于照片编辑软件中“拼合”/合并图层的概念。 (我仍然喜欢将图像保留为矢量,因为我希望能够放大)。

示例图:

import numpy as np
import pandas as pd
import random
import matplotlib.pyplot as plt
random.seed(15)

df = pd.DataFrame({'x': np.random.normal(10, 1.2, 10000), 
                   'y': np.random.normal(10, 1.2, 10000), 
                   'color' : np.random.normal(10, 1.2, 10000)})
df.plot(kind = "scatter", x = "x", y = "y", c = "color", s = 80, cmap = "RdBu_r")
plt.show()

【问题讨论】:

  • 保存图时可以通过dpi关键字降低图像质量。实际上,这不是您问题的直接答案,但如果问题是文件的重量,这可能是一个简单的解决方案。
  • @AlessandroPeca 但它不再是矢量图形了,是吗?
  • DPI 对于矢量输出毫无意义。关于这个问题,计算起来会很昂贵。您可以将数据转换为英寸(或点)单位,这样您就可以使用 sqrt(80)/2 的半径以点为单位进行算术运算。然后,您需要找到一种廉价的方法来定义“重叠”,这种方法不需要检查一个点与每个其他点的每个组合。
  • @ImportanceOfBeingErnest 这似乎是一个很好的解决方案。鉴于我经常遇到这个问题,我想知道这是否已经在某个地方实现了?

标签: python-3.x matplotlib plot


【解决方案1】:

tl;博士

我不知道任何简单的解决方案,例如

RemoveOccludedCircles(C)

下面的算法需要一些实现,但应该不会太糟糕。

问题重新表述

虽然我们可以在添加新圆圈时尝试删除现有圆圈,但我发现反过来考虑问题更容易,以相反的顺序处理所有圆圈并假装在现有圆圈后面绘制每个新圆圈。

那么主要问题就变成了:如何有效地确定一个圆圈是否会被另一组圆圈完全隐藏?

条件

在下文中,我将描述一种算法,用于按大小对圆圈进行排序,以便将较大的圆圈放在较小的圆圈后面。这包括所有圆圈大小相同的特殊情况。对一般情况的扩展实际上要复杂得多,因为必须保持交叉点的三角剖分。此外,我将假设没有两个圆具有完全相同的属性(半径和位置)。这些相同的圆圈很容易被过滤掉。

数据结构

C:一组可见的圆圈

P:一组控制点

控制点的放置方式不会使新放置的圆圈可见,除非其中心位于现有圆圈之外或至少一个控制点位于新圆圈内。

问题可视化

为了更好地理解控制点的作用、维护和算法,请看下图: Processing 6 circles

在链接的图像中,活动控制点被涂成红色。在每一步之后移除的控制点被涂成绿色或蓝色,其中蓝色点是通过计算圆之间的交点来创建的。

在图像 g) 中,绿色区域突出显示了可以放置相同大小圆的中心的区域,这样相应的圆就会被现有的圆遮挡。该区域是通过在每个控制点上放置圆圈并从所有可见圆圈所覆盖的区域中减去所得区域得出的。

控制点维护

每当在画布上添加一个圆圈时,我们都会添加四个活动点,它们以等距的方式放置在圆圈的边框上。为什么是四个?因为在不包含四个控制点之一的情况下,不能将中心放在当前圆内的相同或更大尺寸的圆。

放置一个圆圈后,以下假设成立:如果

  1. 它的中心落在一个可见的圆圈内。
  2. 没有控制点严格位于新圈内。

为了在添加新圆时保持这一假设,每次添加可见圆后都需要更新控制点集:

  1. 为新圆添加 4 个新控制点,如前所述。

  2. 在新圆与现有可见圆的每个交点处添加新控制点。

  3. 删除严格位于任何可见圆圈内的所有控制点。

此规则将以如此密集的方式在可见圆的外边界保持控制点,以至于如果不“吃掉”至少一个控制点,就不能放置与现有圆相交的新可见圆。

伪代码

AllCircles <- All circles, sorted from front to back
C <- {} // the set of visible circles
P <- {} // the set of control points
for X in AllCircles {
  if (Inside(center(X), C) AND Outside(P, X)) {
    // ignore circle, it is occluded!
  } else {
    C <- C + X
    P <- P + CreateFourControlPoints(X)
    P <- P + AllCuttingPoints(X, C)
    RemoveHiddenControlPoints(P, C)
  }
}
DrawCirclesInReverseOrder(C)

'Inside' 和 'Outside' 函数在这里有点抽象,如果一个点包含在一个 seto 圆的一个或多个圆中,'Inside' 返回 true,如果一个集合中的所有点都返回,'Outside' 返回 true的点位于圆外。但是使用的函数应该都不是很难写出来的。

待解决的小问题

  1. 如何以数值稳定的方式确定一个点是否严格在圆内? -> 这应该不会太糟糕,因为所有点都不会比二次方程的解更复杂。但是,重要不要仅仅依赖浮点表示,因为这些表示在数值上是不够的,并且一些控制点可能会完全丢失,从而在最终绘图中有效地留下漏洞。因此,请保持控制点坐标的符号和精确表示。我会尝试SymPy 来解决这个问题,因为它似乎涵盖了所有必需的数学。相交圆的公式很容易在网上找到,例如here

  2. 如何有效地确定一个圆是否包含任何控制点或任何可见圆是否包含新圆的中心? -> 为了解决这个问题,我建议将 P 和 C 的所有元素保留在网格状结构中,其中每个网格元素的宽度和高度等于圆的半径。平均而言,每个网格单元的活动点和可见圆的数量应该在 O(1) 内,尽管可以使用每个网格单元的任意数量的元素构建人工设置,这将使整个算法从 O(N)为 O(N * N)。

运行时的想法

如上所述,我希望运行时间与平均圈数呈线性关系,因为每个网格单元中的可见圈数将在 O(N) 内,除非以邪恶的方式构建。

如果圆的半径不是太小,数据结构应该很容易在内存中维护,并且计算圆之间的交点也应该很快。我对最终计算时间很好奇,但我不认为它会比一次以天真的方式绘制所有圆圈要糟糕得多。

【讨论】:

    【解决方案2】:

    我最好的猜测是使用hexbin。请注意,使用散点图,最新绘制的点将是唯一可见的点。使用hexbin,将平均所有重合的点。

    如果有兴趣,可以使用六边形的中心再次创建仅显示最小值的散点图。

    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    
    np.random.seed(15)
    df = pd.DataFrame({'x': np.random.normal(10, 1.2, 10000),
                       'y': np.random.normal(10, 1.2, 10000),
                       'color': np.random.normal(10, 1.2, 10000)})
    
    fig, ax = plt.subplots(ncols=4, gridspec_kw={'width_ratios': [10,10,10,1]})
    
    norm = plt.Normalize(df.color.min(), df.color.max())
    df.plot(kind="scatter", x="x", y="y", c="color", s=10, cmap="RdBu_r", norm=norm, colorbar=False, ax=ax[0])
    
    hexb = ax[1].hexbin(df.x, df.y, df.color, cmap="RdBu_r", norm=norm, gridsize=80)
    
    centers = hexb.get_offsets()
    values = hexb.get_array()
    ax[2].scatter(centers[:,0], centers[:,1], c=values, s=10, cmap="RdBu_r", norm=norm)
    
    plt.colorbar(hexb, cax=ax[3])
    plt.show()
    

    这是另一个比较。点的数量减少了 10 倍,并且由于对重叠的点进行了平均,绘图更加“诚实”。

    【讨论】:

    • 这个答案是否对您的问题有所启发?
    • 如果您创建一个网格,您将失去强度值(这里用颜色表示)或不同区域中点的密度印象。在这种情况下,如果你按强度进行总结,你就会失去密度。因此,ImportanceOfBeingErnest 建议的“移除隐藏点”解决方案会更好,但并未在任何地方实施。我还在想是否有办法计算一个圆圈是否完全隐藏。
    • 是的,你松散了密度,因为这就是你的要求:画更少的圆圈。但是由于这些产生的圆圈略有重叠,背景仍然覆盖在相同的位置。另请注意, scatter 在屏幕空间中创建圆圈,而 hexbin 在数据空间中创建六边形。如果放大,散点图看起来会有所不同。
    • 问题不在于总密度会降低,而在于使用 hexbin 方法时,可见 密度将在整个图上均衡,这意味着您失去了有可能了解绘图的哪些区域比其他区域更密集。
    • 另一个答案是严重过度设计的明显案例。大量工作最终得到大致相同的图像大小(在 10000 点的情况下)。您的真实用例是关于看起来随机的颜色并且您想要显示混乱的颜色来模拟密度吗?您可能想在绘图下方或沿着绘图绘制热图。
    猜你喜欢
    • 2016-12-12
    • 2013-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多