查找主要包含零的数组的中位数位置

Finding the position of the median of an array containing mostly zeros

我有一个非常大的一维数组,其中大多数元素为零,而非零元素都聚集在一些由许多零分隔的岛屿周围:(这里是用于 MWE 的较小版本)

In [1]: import numpy as np

In [2]: A=np.array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,6,20,14,10,5,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,4,5,5,18,18,16,14,10,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,3,6,16,4,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])

我想根据每个岛的中值对应的指数求中值及其位置(甚至近似)。毫不奇怪,我得到的结果为零,这不是我想要的:

In [3]: np.median(A)
Out[3]: 0.0

In [4]: np.argsort(A)[len(A)//2]
Out[4]: 12

在非零元素的单个岛的情况下,为了解决这个警告并满足我的要求,即只有非零元素在物理上有意义,我首先删除所有零,然后取剩余元素的中值:

In [5]: masks = np.where(A>0)
In [6]: A[masks]
Out[6]: array([ 1,  3,  6, 20, 14, 10,  5,  1])

这一次,我正确地得到了新数组的中位数,但是位置(索引)不正确,因为它很明显并且在评论中也指出数学上定义不正确。

In [7]: np.median(A[masks])
Out[7]: 5.5

In [8]: np.argsort(A[masks])[len(A[masks])//2]
Out[8]: 2

根据这个近似值,我知道真正的中位数位于修改后的数组的第三个索引中,但我想将其转换回原始数组的格式,其中中位数的位置(索引)应该位于对应于较大索引的非零元素的第一个岛中间的某个位置(其中零索引都被正确计算)。评论中还回答了两个建议,提出了在零海中间给定一个非零元素岛的情况下中位数的位置。但是如果有不止一个这样的岛呢?怎么可能在原始直方图数组的情况下计算每个岛屿的中位数对应的索引,其中所有零都被计算在内?

我想知道是否有任何简单的方法来计算这种包含许多零的数组中的中位数位置。如果不是,在知道修改后的数组中的位置后,我还应该在我的代码行中添加什么以使其成为可能?非常感谢您的帮助。

根据评论 "A is actually a discrete histogram with many bins",我认为您想要的是所计算的值的中位数。如果 A 是计数的整数数组,那么中位数的精确公式(但可能非常低效,如果您的值高达 1e7)是

np.median(np.repeat(np.arange(len(A)), A))  # Do not use if A contains very large values!

或者,您可以使用

np.searchsorted(A.cumsum(), 0.5*A.sum())

这将是中位数的整数部分。

例如:

In [157]: A
Out[157]: 
array([ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
        0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  3,
        6, 20, 14, 10,  5,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
        0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
        0,  0,  0,  0])

In [158]: np.median(np.repeat(np.arange(len(A)), A))
Out[158]: 35.5

In [159]: np.searchsorted(A.cumsum(), 0.5*A.sum())
Out[159]: 35

另一个例子:

In [167]: B
Out[167]: 
array([  0,   0,   0,   1, 100,  21,   8,   3,   2,   1,   0,   0,   0,
         0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
         0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0])

In [168]: np.median(np.repeat(np.arange(len(B)), B))
Out[168]: 4.0

In [169]: np.searchsorted(B.cumsum(), 0.5*B.sum())
Out[169]: 4