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

时间:2013-11-23 04:37:44

标签: python pandas

我的问题与前一个问题相同:

Binning with zero values in pandas

然而,我仍然希望将0值包含在一个分数中。有没有办法做到这一点?换句话说,如果我有600个值,其中50%为0,其余值为1到100之间,我将如何对fractile 1中的所有0值进行分类,然后将其余的非零值分类在分数标签2到10(假设我想要10个fractiles)。我可以将0转换为nan,将剩余的非纳米数据切换为9个fractiles(1到9),然后在每个标签上添加1(现在为2到10)并将所有0值标记为手动分离1?即使这很棘手,因为在我的数据集中除了600个值之外,在将0转换为nan之前,我还有另外几百个可能已经是nan。

2014年1月26日更新:

我提出了以下临时解决方案。但是,这个代码的问题是,如果高频值不在分布的边缘,那么它会在现有的一组箱子的中间插入一个额外的箱子,并抛弃一切(或很多)。“ / p>

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

6 个答案:

答案 0 :(得分:40)

问题是 pandas.qcut选择这些垃圾箱,这样你在每个垃圾箱/分位数中都有相同数量的记录,但是相同的值不能落在多个垃圾箱/分位数中。

解决方案

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

2 - 使用pandas.cut 选择根据值本身均匀分布的bin,而pandas.qcut选择bin,以便每个bin中具有相同数量的记录< / p>

3 - 降低 分位数的数量。分数越少意味着每个分位数的元素越多

4 - 指定自定义分位数范围,例如[0,.50,。75,1。]每个分位数得到不等数量的项目

5 - 使用DataFrame.rank对数据进行排名(方法=&#39;首先&#39;)。排名为数据框中的每个元素(排名)分配一个唯一值,同时保持元素的顺序(相同的值除外,它们将按照它们出现在数组中的顺序排列,请参阅method =&#39; first&# 39)。这解决了问题,但您可能会将相同(排名前)的值分配到不同的分位数,这可能是正确的,也可能不是,具体取决于您的意图。

示例:

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

然后改用:

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

答案 1 :(得分:15)

另一种方法是引入最小量的噪音,这将人为地创建独特的箱边。这是一个例子:

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

答案 2 :(得分:14)

您询问有关非唯一bin边缘的binning,我有一个相当简单的答案。在你的例子的情况下,你的意图和qcut的行为在pandas.tools.tile.qcut函数的哪里定义了bin:

bins = algos.quantile(x, quantiles)

其中,因为您的数据是50%0s,因此对于大于2的任何分位数值,导致返回具有值0的多个bin边缘的bin。我看到两种可能的分辨率。在第一个中,分割的空间被均匀地分割,在第一个箱子中将所有0,但不仅仅是0s合并。在第二个中,对于大于0的值,将分段空间均分,对第一个bin中的所有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进行一些工作,使其更漂亮,但您可以看到屏蔽数组是实现目标的有用工具。

答案 3 :(得分:4)

如果要强制执行相同大小的分档,即使存在重复值,也可以使用以下两步过程:

  1. 使用method ='first'对您的值进行排名,让python为您的所有记录分配唯一的排名。如果存在重复值(即排名中的平局),此方法将选择它所来的第一条记录并按该顺序排名。
  2. df['rank'] = df['value'].rank(method='first')

    1. 在排名上使用qcut来确定相等大小的分位数。以下示例创建十分位数(二进制数= 10)。
    2. df['decile'] = pd.qcut(df['rank'].values, 10).codes

答案 4 :(得分:1)

我在qcut上也遇到了很多问题,所以我使用了Series.rank函数并结合使用这些结果创建自己的bin。我的代码在Github上:

https://gist.github.com/ashishsingal1/e1828ffd1a449513b8f8

答案 5 :(得分:0)

我也有这个问题,所以我写了一个小函数,它只处理非零值,然后在原来不为0的地方插入标签。

def qcut2(x, n=10):
  x = np.array(x)
  x_index_not0 = [i for i in range(len(x)) if x[i] > 0]
  x_cut_not0 = pd.qcut(x[x > 0], n-1, labels=False) + 1
  y = np.zeros(len(x))
  y[x_index_not0] = x_cut_not0
  return y