如何使用非唯一的箱子边缘进行qcut?

2024-04-29 13:45:03 发布

您现在位置:Python中文网/ 问答频道 /正文

我的问题和前一个一样:

Binning with zero values in pandas

但是,我仍然希望在分形中包含0值。有办法吗?换言之,如果我有600个值,其中50%是0,其余的值在1到100之间,我将如何对分形1中的所有0值进行分类,然后对分形标签2到10中的其余非零值进行分类(假设我需要10个分形)。我可以将0转换为nan,qcut剩余的非nan数据为9个分位数(1到9),然后将1添加到每个标签(现在是2到10)并手动将所有0值标记为分位数1吗?即使这样也很棘手,因为在我的数据集中,除了600个值之外,我还有另外的几百个值,在我将0转换成nan之前,它们可能已经是nan了。

2014年1月26日更新:

我想出了下面的临时解决办法。不过,这段代码的问题是,如果高频值不在分布的边缘,那么它会在现有的一组存储箱中间插入一个额外的存储箱,并将所有内容都丢弃一点(或很多)。

def fractile_cut(ser, num_fractiles):
    num_valid = ser.valid().shape[0]
    remain_fractiles = num_fractiles
    vcounts = ser.value_counts()
    high_freq = []
    i = 0
    while vcounts.iloc[i] > num_valid/ float(remain_fractiles):
        curr_val = vcounts.index[i]
        high_freq.append(curr_val)
        remain_fractiles -= 1
        num_valid = num_valid - vcounts[i]
        i += 1
    curr_ser = ser.copy()
    curr_ser = curr_ser[~curr_ser.isin(high_freq)]
    qcut = pd.qcut(curr_ser, remain_fractiles, retbins=True)
    qcut_bins = qcut[1]
    all_bins = list(qcut_bins)
    for val in high_freq:
        bisect.insort(all_bins, val)
    cut = pd.cut(ser, bins=all_bins)
    ser_fractiles = pd.Series(cut.labels + 1, index=ser.index)
    return ser_fractiles

Tags: valnannumser分形freqhighcut
3条回答

问题是pandas.qcut选择存储箱,以便在每个存储箱/分位数中具有相同数量的记录,但具有相同值的记录不能放入不同的存储箱/分位数。

解决方案是:

1-使用pandas>;=0.20.0具有this fix。他们添加了一个选项duplicates='raise'|'drop'来控制是在重复的边上提升还是丢弃它们,这将导致比指定的更少的存储箱,以及一些比其他存储箱更大(包含更多元素)的存储箱。

2-使用pandas.cut根据值本身选择要均匀分布的存储箱,而pandas.qcut选择存储箱,以便每个存储箱中的记录数相同

3-减少分位数的数量。更少的分位数意味着每个分位数有更多的元素

4-指定自定义分位数范围,例如[0、.50、.75、1.]以获得每个分位数不相等的项数

5-用DataFrame.Rank(method='first')对数据进行排序。排名为数据帧中的每个元素(排名)分配一个唯一的值,同时保持元素的顺序(除了相同的值,这些值将按照它们在数组中的出现顺序排列,请参见method='first')。这解决了这个问题,但您可能有相同的(预排序)值进入不同的分位数,这可能是正确的或不取决于您的意图。

示例:

pd.qcut(df, nbins) <-- this generates "ValueError: Bin edges must be unique"

然后改用这个:

pd.qcut(df.rank(method='first'), nbins)

另一种方法是引入最小的噪声,这将人为地创建唯一的垃圾箱边缘。下面是一个例子:

a = pd.Series(range(100) + ([0]*20))

def jitter(a_series, noise_reduction=1000000):
    return (np.random.random(len(a_series))*a_series.std()/noise_reduction)-(a_series.std()/(2*noise_reduction))

# and now this works by adding a little noise
a_deciles = pd.qcut(a + jitter(a), 10, labels=False)

我们可以使用以下方法重新创建原始错误:

a_deciles = pd.qcut(a, 10, labels=False)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/site-packages/pandas/tools/tile.py", line 173, in qcut
    precision=precision, include_lowest=True)
  File "/usr/local/lib/python2.7/site-packages/pandas/tools/tile.py", line 192, in _bins_to_cuts
    raise ValueError('Bin edges must be unique: %s' % repr(bins))
ValueError: Bin edges must be unique: array([  0.        ,   0.        ,   0.        ,   3.8       ,
        11.73333333,  19.66666667,  27.6       ,  35.53333333,
        43.46666667,  51.4       ,  59.33333333,  67.26666667,
        75.2       ,  83.13333333,  91.06666667,  99.        ])

你问关于用非唯一的箱子边缘装箱,我有一个相当简单的答案。在您的示例中,qcut的意图和行为在定义bin的pandas.tools.tile.qcut函数中的位置发生了变化:

bins = algos.quantile(x, quantiles)

因为您的数据是50%0s,所以对于任何大于2的分位数值,都会返回多个具有0值的bin边的bin。我看到两个可能的解决方案。在第一种情况下,分形空间被均匀地划分,在第一种情况下将所有的0(而不仅仅是0)都装箱。在第二种情况下,对于大于0的值,将分形空间平均分割,在第一个存储箱中存储所有0个值,并且仅存储0个值。

import numpy as np
import pandas as pd
import pandas.core.algorithms as algos
from pandas import Series

在这两种情况下,我将创建一些随机样本数据,以符合您对50%零和1到100之间的剩余值的描述

zs = np.zeros(300)
rs = np.random.randint(1, 100, size=300)
arr=np.concatenate((zs, rs))
ser = Series(arr)

解决方案1:bin 1同时包含0和低值

bins = algos.quantile(np.unique(ser), np.linspace(0, 1, 11))
result = pd.tools.tile._bins_to_cuts(ser, bins, include_lowest=True)

结果是

In[61]: result.value_counts()
Out[61]: 
[0, 9.3]        323
(27.9, 38.2]     37
(9.3, 18.6]      37
(88.7, 99]       35
(57.8, 68.1]     32
(68.1, 78.4]     31
(78.4, 88.7]     30
(38.2, 48.5]     27
(48.5, 57.8]     26
(18.6, 27.9]     22
dtype: int64

解决方案2:bin1仅包含0

mx = np.ma.masked_equal(arr, 0, copy=True)
bins = algos.quantile(arr[~mx.mask], np.linspace(0, 1, 11))
bins = np.insert(bins, 0, 0)
bins[1] = bins[1]-(bins[1]/2)
result = pd.tools.tile._bins_to_cuts(arr, bins, include_lowest=True)

结果是:

In[133]: result.value_counts()
Out[133]: 
[0, 0.5]        300
(0.5, 11]        32
(11, 18.8]       28
(18.8, 29.7]     30
(29.7, 39]       35
(39, 50]         26
(50, 59]         31
(59, 71]         31
(71, 79.2]       27
(79.2, 90.2]     30
(90.2, 99]       30
dtype: int64

我认为解决方案2可以做一些工作来使它更漂亮一些,但是您可以看到屏蔽数组是实现您的目标的有用工具。

相关问题 更多 >