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

ger*_*imo 50 python pandas

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

在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日更新:

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

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
Run Code Online (Sandbox Code Playgroud)

luc*_*uca 57

问题是 pandas.qcut选择了bin,这样你在每个bin/quantile中都有相同数量的记录,但是相同的值不能落在多个bin/quantiles中.

解决方案是:

1 - 使用具有此修复程序的pandas> = 0.20.0.他们添加了一个选项duplicates='raise'|'drop'来控制是否在重复的边缘上进行提升或者删除它们,这将导致比指定的更少的bin,以及一些更大(具有更多的元素)比其他更多.

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

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"
Run Code Online (Sandbox Code Playgroud)

然后使用它代替:

pd.qcut(df.rank(method='first'), nbins)
Run Code Online (Sandbox Code Playgroud)

  • @par 如果您有太多“0”,则“pd.qcut”被迫将“0”放置在多个箱/分位数中,以保持每个箱/分位数的项目数相等。同时,相同的值(在本例中为“0”)不应出现在多个分位数/箱中,这就是“pd.qcut”失败的原因 (2认同)

mgo*_*ser 19

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

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)
Run Code Online (Sandbox Code Playgroud)

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

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.        ])
Run Code Online (Sandbox Code Playgroud)

  • 如果我们想要将重复值随机分配到容器中(许多现实场景中都是这种情况),则此解决方案特别有用。 (2认同)

OYR*_*YRM 17

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

bins = algos.quantile(x, quantiles)

其中,因为您的数据是50%0s,导致在任何大于2的分位数值的情况下返回具有值0的多个bin边缘的bin.我看到两种可能的分辨率.在第一个中,分割的空间被均匀地分割,在第一个箱子中分箱所有0,但不仅仅是0.在第二个中,对于大于0的值,可以均匀地划分分段空间,在第一个bin中对所有0和仅0进行分箱.

import numpy as np
import pandas as pd
import pandas.core.algorithms as algos
from pandas import Series
Run Code Online (Sandbox Code Playgroud)

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

zs = np.zeros(300)
rs = np.random.randint(1, 100, size=300)
arr=np.concatenate((zs, rs))
ser = Series(arr)
Run Code Online (Sandbox Code Playgroud)

解决方案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)
Run Code Online (Sandbox Code Playgroud)

结果是

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
Run Code Online (Sandbox Code Playgroud)

解决方案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)
Run Code Online (Sandbox Code Playgroud)

结果是:

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
Run Code Online (Sandbox Code Playgroud)

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


小智 11

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

  1. 对您的值进行排名,使用 method='first'让 python 为您的所有记录分配一个唯一的排名。如果存在重复值(即排名并列),此方法将选择它遇到的第一条记录并按该顺序排名。

df['rank'] = df['value'].rank(method='first')

  1. 在排名上使用 qcut 来确定大小相等的分位数。下面的示例创建十分位数(bins=10)。

df['decile'] = pd.qcut(df['rank'].values, 10).codes


ash*_*gal 1

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

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