Python:如何制作具有相同“大小”箱的直方图

12
我有一组数据,想要制作直方图。我需要每个箱子具有相同的大小,也就是它们必须包含相同数量的对象,而不是通常的(numpy.histogram)问题,即具有等间距的箱子。
这自然会牺牲箱子的宽度,它们可以 - 并且通常会 - 不同。
我将指定所需箱子的数量和数据集,并获得箱子边缘的返回值。
Example:
data = numpy.array([1., 1.2, 1.3, 2.0, 2.1, 2.12])
bins_edges = somefunc(data, nbins=3)
print(bins_edges)
>> [1.,1.3,2.1,2.12]

所以所有的箱子都包含2个点,但它们的宽度(0.3,0.8,0.02)不同。

有两个限制: - 如果一组数据相同,则包含它们的箱子可能会更大。 - 如果有N个数据并且请求M个箱子,则如果N%M不为0,则将有N/M个箱子加一。

这段代码是我写的一些无用的东西,对于小型数据集效果很好。如果我有10 ** 9+个数据点并想加快处理速度怎么办?

  1 import numpy as np
  2 
  3 def def_equbin(in_distr, binsize=None, bin_num=None):
  4 
  5     try:
  6 
  7         distr_size = len(in_distr)
  8 
  9         bin_size = distr_size / bin_num
 10         odd_bin_size = distr_size % bin_num
 11 
 12         args = in_distr.argsort()
 13 
 14         hist = np.zeros((bin_num, bin_size))
 15 
 16         for i in range(bin_num):
 17             hist[i, :] = in_distr[args[i * bin_size: (i + 1) * bin_size]]
 18 
 19         if odd_bin_size == 0:
 20             odd_bin = None
 21             bins_limits = np.arange(bin_num) * bin_size
 22             bins_limits = args[bins_limits]
 23             bins_limits = np.concatenate((in_distr[bins_limits],
 24                                           [in_distr[args[-1]]]))
 25         else:
 26             odd_bin = in_distr[args[bin_num * bin_size:]]
 27             bins_limits = np.arange(bin_num + 1) * bin_size
 28             bins_limits = args[bins_limits]
 29             bins_limits = in_distr[bins_limits]
 30             bins_limits = np.concatenate((bins_limits, [in_distr[args[-1]]]))
 31 
 32         return (hist, odd_bin, bins_limits)

我可能没有正确理解,但是听起来你这样做最终会得到一个非常无聊(例如完全平坦)的直方图。你只是想找到一些数据的分位数吗? - aganders3
你理解得很正确。因为每个值都是一个星系的大小,我将能够查看每个单独的区间中其他属性的行为! - astabada
这更像是分位数,而不是直方图。 - ezod
4个回答

16

使用您的案例(2个数据点的箱子,总共6个数据点):

from scipy import stats
bin_edges = stats.mstats.mquantiles(data, [0, 2./6, 4./6, 1])
>> array([1. , 1.24666667, 2.05333333, 2.12])

啊啊,太棒了!我不知道它们被称为分位数,所以我花了很多时间在谷歌上搜索“等间距箱”之类的东西...非常感谢! - astabada
10
当你学会一个新的术语,然后突然间谷歌开始工作,这不是很好吗?我经常遇到这种情况。 - mrchampe
1
@mrchampe 最棒的。 - jimh

11

我还想提及 pandas.qcut 的存在,它可以以相当高效的方式进行等频分箱。在您的情况下,它可能会起到如下作用:

data = np.array([1., 1.2, 1.3, 2.0, 2.1, 2.12])
# parameter q specifies the number of bins
qc = pd.qcut(data, q=3, precision=1)

# bin definition
bins  = qc.categories
print(bins)
>> Index(['[1, 1.3]', '(1.3, 2.03]', '(2.03, 2.1]'], dtype='object')

# bin corresponding to each point in data
codes = qc.codes
print(codes)
>> array([0, 0, 1, 1, 2, 2], dtype=int8)

在最近的Pandas版本中,你需要使用qc.cat.categoriesqc.cat.codes代替qc.categoriesqc.codes - Darina

2

针对偏斜分布的更新 :

我遇到了和@astabada相同的问题,希望创建包含相等样本数量的箱子。当应用@aganders3提出的解决方案时,我发现它并不适用于偏斜分布。在偏斜数据的情况下(例如有很多零的数据),对于预定义数量的分位数stats.mstats.mquantiles将不能保证每个箱中具有相等数量的样本。您将得到这样的箱边缘:

[0. 0. 4. 9.]

如果情况是这样的,第一个箱子将是空的。

为了处理偏斜的情况,我创建了一个函数,调用stats.mstats.mquantiles,并在样本不相等时动态修改箱子数量(在示例代码中为最小样本大小的30%容差范围内)。如果箱子之间的样本不相等,则代码将减少等间距分位数的数量1次,并再次调用stats.mstats.mquantiles,直到样本大小相等或只存在一个箱子。

我在示例中硬编码了容差,但如果需要,可以将其修改为关键字参数。

我还喜欢将等间距分位数的数量作为参数提供给我的函数,而不是将用户定义的分位数提供给stats.mstats.mquantiles,以减少意外错误(例如像[0., 0.25, 0.7, 1.]这样的内容)。

以下是代码:

import numpy as np 
from scipy import stats

def equibins(dat, binnum, **kwargs):
    numin = binnum
    while numin>1.:
        qtls = np.linspace(0.,1.0,num=numin,endpoint=False)
        ebins =stats.mstats.mquantiles(dat,qtls,alphap=kwargs['alpha'],betap=kwargs['beta'])
        allhist, allbin   = np.histogram(dat, bins = ebins)
        if (np.unique(ebins).shape!=ebins.shape or tolerence(allhist,0.3)==False) and numin>2:
            numin= numin-1
            del qtls, ebins
        else:
            numin=0
    return ebins

def tolerence(narray, percent):
    if percent>1.0:
        per = percent/100.
    else:
        per = percent
    lev_tol  = per*narray.min()
    tolerate = np.all(narray[1:]-narray[0]<lev_tol)
    return tolerate

1
只需对数据进行排序,并按长度将其分成固定的区间!显然,如果样本数量不能被区间数整除,那么你永远无法将其分成完全相等的区间。
import math
import numpy as np
data = np.array([2,3,5,6,8,5,5,6,3,2,3,7,8,9,8,6,6,8,9,9,0,7,5,3,3,4,5,6,7])
data_sorted = np.sort(data)
nbins = 3
step = math.ceil(len(data_sorted)//nbins+1)
binned_data = []
for i in range(0,len(data_sorted),step):
    binned_data.append(data_sorted[i:i+step])

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