假设我们有这个 DataFrame:
df = pd.DataFrame(columns=["value"], data=[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3, 5, 7, 10])
我想根据值的频率将它们分成 5 个容器,条件是 没有值位于多个容器中。 您可能会说:“
pd.qcut
就是正确的方法!”但 qcut
的问题是,它要么不会创建唯一的垃圾箱,要么如果您使用 duplicates="drop"
那么它会减少您想要的垃圾箱数量。我知道有人问过类似的问题here,但似乎没有一个答案可以直接回答这个问题,而且我问的问题有点复杂。
如果我们使用
pd.qcut
,会发生以下情况:
df["bins"] = pd.qcut(df["value"], q=5, duplicates="drop")
:这为我们提供了 3 个垃圾箱,而不是 5 个。df["bins"] = pd.qcut(df["value"].rank(method='first'), q=5)
:这给了我们 5 个 bin,但 0 的值被分成两个 bin,1 的值也是如此。这就是我们想要的:
如果我们运行
df["value"].value_counts(normalize=True)
,我们会看到:
因此,从百分比细分和每个值仅在一个容器中的条件来看,我们希望容器 1 包含所有 0(且仅 0),容器 2 包含所有 1(且仅 1),容器 3 包含所有 2(且仅 2),bin 4 包含 5&7,bin 5 包含 7&10。这是因为我们想要 5 个 bin,因此每个 bin 应该包含 20% 的值。但超过 20% 的值是 0,因此 0 应该是它自己的 bin(1 也一样)。然后,一旦分配了这两个 bin,就会有 3 个剩余的 bin 来分割其余的值。因此,每个 bin 应该包含其余值的 33%,并且超过 33% 的剩余值是2,所以 2 也应该是它自己的 bin。最后剩下 2 个箱用于剩余值,这些箱被分成 50-50。
import pandas as pd
import numpy as np
df = pd.DataFrame(columns=["value"], data=[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3, 5, 7, 10])
value_counts = df['value'].value_counts(normalize=True)
sorted_values = value_counts.sort_index()
total_bins = 5
bins = pd.Series(index=sorted_values.index, dtype='int32')
current_bin = 1
accumulated = 0
for value, fraction in sorted_values.items():
if accumulated + fraction > (1 / total_bins) and current_bin < total_bins:
current_bin += 1
accumulated = fraction
else:
accumulated += fraction
bins[value] = current_bin
if current_bin < total_bins:
bins = bins.fillna(total_bins)
df['bins'] = df['value'].map(bins)
print(df)
print("\nBinning assignment based on value:")
print(bins)
这会给你
value bins
0 0 2.0
1 0 2.0
2 0 2.0
3 0 2.0
4 0 2.0
5 1 3.0
6 1 3.0
7 1 3.0
8 1 3.0
9 1 3.0
10 2 4.0
11 2 4.0
12 2 4.0
13 3 5.0
14 5 5.0
15 7 5.0
16 10 5.0
Binning assignment based on value:
value
0 2.0
1 3.0
2 4.0
3 5.0
5 5.0
7 5.0
10 5.0
dtype: float64