我的问题与前一个问题相同:
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
答案 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)
如果要强制执行相同大小的分档,即使存在重复值,也可以使用以下两步过程:
df['rank'] = df['value'].rank(method='first')
df['decile'] = pd.qcut(df['rank'].values, 10).codes
答案 4 :(得分:1)
我在qcut上也遇到了很多问题,所以我使用了Series.rank函数并结合使用这些结果创建自己的bin。我的代码在Github上:
答案 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