如何根据数组的密度对数组进行二次采样? (去除频繁值,保留稀有值)

Posted

技术标签:

【中文标题】如何根据数组的密度对数组进行二次采样? (去除频繁值,保留稀有值)【英文标题】:How can I subsample an array according to its density? (Remove frequent values, keep rare ones) 【发布时间】:2019-05-01 19:08:16 【问题描述】:

我有一个问题,我想绘制一个数据分布,其中一些值经常出现,而另一些则非常罕见。总点数约为 30.000。渲染像 png 或(上帝保佑)pdf 这样的图需要很长时间,而且 pdf 太大而无法显示。

所以我想只为地块对数据进行二次抽样。我想要实现的是删除它们重叠的很多点(密度高),但保留密度低的点,概率几乎为 1。

现在,numpy.random.choice 允许指定一个概率向量,我已经根据数据直方图进行了一些调整来计算该向量。但是我似乎无法做出选择,以便真正保留稀有点。

我附上了一张数据图片;分布的右尾的点要少几个数量级,所以我想保留这些点。数据是 3d 的,但密度仅来自一维,所以我可以用它来衡量给定位置有多少点

【问题讨论】:

【参考方案1】:

一种可能的方法是使用kernel density estimation (KDE) 来构建数据的估计概率分布,然后根据每个点的估计概率密度的倒数进行采样(或其他一些函数,估计的概率密度越大,它变得越小) )。有a few tools to compute a (KDE) in Python,一个简单的是scipy.stats.gaussian_kde。这是这个想法的一个例子:

import numpy as np
import scipy.stats
import matplotlib.pyplot as plt

np.random.seed(100)
# Make some random Gaussian data
data = np.random.multivariate_normal([1, 1], [[1, 0], [0, 1]], size=1000)
# Compute KDE
kde = scipy.stats.gaussian_kde(data.T)
# Choice probabilities are computed from inverse probability density in KDE
p = 1 / kde.pdf(data.T)
# Normalize choice probabilities
p /= np.sum(p)
# Make sample using choice probabilities
idx = np.random.choice(np.arange(len(data)), size=100, replace=False, p=p)
sample = data[idx]
# Plot
plt.figure()
plt.scatter(data[:, 0], data[:, 1], label='Data', s=10)
plt.scatter(sample[:, 0], sample[:, 1], label='Sample', s=7)
plt.legend()

输出:

【讨论】:

【参考方案2】:

考虑以下函数。它将沿轴将数据分箱,并

如果 bin 中有一个或两个点,则接管这些点, 如果 bin 中有更多点,则取最小值和最大值。 附加第一个点和最后一个点以确保使用相同的数据范围。

这允许将原始数据保留在低密度区域,但显着减少要在高密度区域绘制的数据量。同时通过足够密集的分箱保留所有特征。

import numpy as np; np.random.seed(42)

def filt(x,y, bins):
    d = np.digitize(x, bins)
    xfilt = []
    yfilt = []
    for i in np.unique(d):
        xi = x[d == i]
        yi = y[d == i]
        if len(xi) <= 2:
            xfilt.extend(list(xi))
            yfilt.extend(list(yi))
        else:
            xfilt.extend([xi[np.argmax(yi)], xi[np.argmin(yi)]])
            yfilt.extend([yi.max(), yi.min()])
    # prepend/append first/last point if necessary
    if x[0] != xfilt[0]:
        xfilt = [x[0]] + xfilt
        yfilt = [y[0]] + yfilt
    if x[-1] != xfilt[-1]:
        xfilt.append(x[-1])
        yfilt.append(y[-1])
    sort = np.argsort(xfilt)
    return np.array(xfilt)[sort], np.array(yfilt)[sort]

为了说明这个概念,让我们使用一些玩具数据

x = np.array([1,2,3,4, 6,7,8,9, 11,14, 17, 26,28,29])
y = np.array([4,2,5,3, 7,3,5,5, 2, 4,  5,  2,5,3])
bins = np.linspace(0,30,7)

然后调用xf, yf = filt(x,y,bins) 并绘制原始数据和过滤后的数据给出:

具有大约 30000 个数据点的问题的用例如下所示。使用所提出的技术将允许将绘制点的数量从 30000 减少到大约 500。这个数字当然取决于使用的分箱 - 这里是 300 个分箱。在这种情况下,该函数需要大约 10 毫秒来计算。这不是超快,但与绘制所有点相比仍然是一个很大的改进。

import matplotlib.pyplot as plt

# Generate some data
x = np.sort(np.random.rayleigh(3, size=30000))
y = np.cumsum(np.random.randn(len(x)))+250
# Decide for a number of bins
bins = np.linspace(x.min(),x.max(),301)
# Filter data
xf, yf = filt(x,y,bins) 

# Plot results
fig, (ax1, ax2, ax3) = plt.subplots(nrows=3, figsize=(7,8), 
                                    gridspec_kw=dict(height_ratios=[1,2,2]))

ax1.hist(x, bins=bins)
ax1.set_yscale("log")
ax1.set_yticks([1,10,100,1000])

ax2.plot(x,y, linewidth=1, label="original data,  points".format(len(x)))

ax3.plot(xf, yf, linewidth=1, label="binned min/max,  points".format(len(xf)))

for ax in [ax2, ax3]:
    ax.legend()
plt.show()

【讨论】:

我想和@ImportanceOfBeingErnest bbands gmail 私聊

以上是关于如何根据数组的密度对数组进行二次采样? (去除频繁值,保留稀有值)的主要内容,如果未能解决你的问题,请参考以下文章

对numpy数组中的每个第n个条目进行二次采样

使用邻域和对 3D 数组进行二次采样

如何对二维多边形进行二次采样?

对一维 numpy 数组进行下采样

如何在 scikit-learn 中使用 KDE(核密度估计)进行一维数组聚类?

我如何采样音频文件说 .wav ,专门将其导入数组并将其分成块。 (在 Lua 中)