Python: как сделать гистограмму с одинаковыми* размером * ячейками

у меня есть набор данных, и я хочу сделать из него гистограмму. Мне нужно, чтобы в мусорных баках было то же самое в размере, под которым я подразумеваю, что они должны содержать одинаковое количество объектов, а не более распространенные (numpy.гистограмма) проблема наличия равноотстоящих закромах. Это, естественно, будет происходить за счет ширины бункеров, которая может - и в целом будет - отличаться.

Я укажу количество желаемых бункеров и набор данных, получив бункеры ребра взамен.

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 бункеров плюс один, если N%M не 0.

этот кусок кода - это какой-то cruft, который я написал, который отлично работал для небольших наборов данных. Что, если я ... 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)

4 ответов


используя ваш случай примера (ящики 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])

Я хотел бы также упомянуть о существовании pandas.qcut, который делает equi-заполненный binning довольно эффективным способом. В вашем случае это будет работать что-то вроде

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)

обновление для искаженных дистрибутивов:

я столкнулся с той же проблемой, что и @astabada, желая создать бункеры, каждый из которых содержит равное количество образцов. Применяя предложенное решение @aganders3, я обнаружил, что оно не особенно хорошо работает для искаженных распределений. В случае искаженных данных (например, что-то с большим количеством нулей),stats.mstats.mquantiles для предопределенного количества квантилей не гарантирует равное количество выборок в каждом бункере. Вы получите bin края, которые выглядят так:

[0. 0. 4. 9.]

в этом случае первый ящик будет пустым.

чтобы иметь дело с перекошенными случаями, я создал функцию, которая вызывает stats.mstats.mquantiles и затем динамически изменяет количество ячеек, если выборки не равны в пределах определенного допуска (30% от наименьшего размера выборки в примере кода). Если образцы не равны между ящиков, код уменьшает количество равных расстояниях квантили на 1 и звонки stats.mstats.mquantiles снова до размеров образца существует равный или только один bin.

Я жестко закодировал допуск в Примере, но при желании его можно изменить на аргумент ключевого слова.

Я также предпочитаю давать число одинаково разнесенных квантилей в качестве аргумента моей функции вместо того, чтобы давать определенные пользователем квантили 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

просто отсортируйте данные и разделите их на фиксированные ячейки по длине! Очевидно, что вы никогда не сможете разделить точно на одинаково заполненные ячейки, если количество выборок не делится точно на количество ячеек.

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])