为什么使用pandas qcut返回ValueError:Bin边必须是唯一的?

时间:2016-04-27 04:43:57

标签: python pandas

我有数据集:

recency;frequency;monetary
21;156;41879955
13;88;16850284
8;74;79150488
2;74;26733719
9;55;16162365
...;...;...

详细原始数据 - > http://pastebin.com/beiEeS80 我输入DataFrame,这是我的完整代码:

df = pd.DataFrame(datas, columns=['userid', 'recency', 'frequency', 'monetary'])
df['recency'] = df['recency'].astype(float)
df['frequency'] = df['frequency'].astype(float)
df['monetary'] = df['monetary'].astype(float)

df['recency'] = pd.qcut(df['recency'].values, 5).codes + 1
df['frequency'] = pd.qcut(df['frequency'].values, 5).codes + 1
df['monetary'] = pd.qcut(df['monetary'].values, 5).codes + 1

但它的返回错误

df['frequency'] = pd.qcut(df['frequency'].values, 5).codes + 1
ValueError: Bin edges must be unique: array([   1.,    1.,    2.,    4.,    9.,  156.])

如何解决这个问题?

2 个答案:

答案 0 :(得分:6)

我在Jupyter中运行它并将exampledata.txt放在与笔记本相同的目录中。

请注意第一行:

df = pd.DataFrame(datas, columns=['userid', 'recency', 'frequency', 'monetary'])
当pum 'userid'未在数据文件中定义时,

加载。我删除了这个列名。

解决方案

import pandas as pd

def pct_rank_qcut(series, n):
    edges = pd.Series([float(i) / n for i in range(n + 1)])
    f = lambda x: (edges >= x).argmax()
    return series.rank(pct=1).apply(f)

datas = pd.read_csv('./exampledata.txt', delimiter=';')

df = pd.DataFrame(datas, columns=['recency', 'frequency', 'monetary'])

df['recency'] = df['recency'].astype(float)
df['frequency'] = df['frequency'].astype(float)
df['monetary'] = df['monetary'].astype(float)

df['recency'] = pct_rank_qcut(df.recency, 5)
df['frequency'] = pct_rank_qcut(df.frequency, 5)
df['monetary'] = pct_rank_qcut(df.monetary, 5)

解释

你看到的问题是pd.qcut假设有5个相同大小的箱子的结果。在您提供的数据中,'frequency'的数字超过了28%。这打破了qcut

我提供了一个新功能pct_rank_qcut来解决这个问题并将所有1推入第一个bin。

    edges = pd.Series([float(i) / n for i in range(n + 1)])

此行根据n定义的所需二进制数定义一系列百分位边。在n = 5的情况下,边缘将为[0.0, 0.2, 0.4, 0.6, 0.8, 1.0]

    f = lambda x: (edges >= x).argmax()

此行定义了一个辅助函数,该函数将应用于下一行中的另一个系列。 edges >= x会返回一个长度等于edges的系列,其中每个元素都是TrueFalse,具体取决于x是否小于或等于该边。在x = 0.14的情况下,生成的(edges >= x)将为[False, True, True, True, True, True]。通过argmax()我已确定系列为True的第一个索引,在本例中为1

    return series.rank(pct=1).apply(f)

此行接受输入series并将其转换为百分位排名。我可以将这些排名与我创建的边缘进行比较,这就是我使用apply(f)的原因。返回的内容应该是一系列编号为1到n的bin编号。这一系列的bin编号与你想要的相同:

pd.qcut(df['recency'].values, 5).codes + 1

这会导致垃圾箱不再相等,垃圾箱1完全从垃圾箱2借用。但必须做出一些选择。如果您不喜欢这个选择,请使用该概念来建立自己的排名。

示范

print df.head()

   recency  frequency  monetary
0        3          5         5
1        2          5         5
2        2          5         5
3        1          5         5
4        2          5         5

更新

pd.Series.argmax()现已弃用。只需切换到pd.Series.values.argmax()()即可更新!

def pct_rank_qcut(series, n):
    edges = pd.Series([float(i) / n for i in range(n + 1)])
    f = lambda x: (edges >= x).values.argmax()
    return series.rank(pct=1).apply(f)

答案 1 :(得分:1)

讨论了各种解决方案here,但简要说明了一下:

如果您使用pandas,> = 0.20.0,他们添加了一个选项duplicates =' raise' |' drop'控制是否在重复边缘上升或丢弃它们,这将导致比指定的更少的箱子,以及比其他更大的(更多的元素)。

对于以前的pandas版本,请尝试传递排名值而不是值本身:

pd.qcut(df['frequency'].rank(method='first').values, 5).codes + 1

通过这种方式,您可能会将相同的值放入不同的分位数中。这可能是正确的或不是取决于您的具体需求(如果这不是您想要的,您可能想要查看pandas.cut,选择根据值本身均匀分布的bin,而pandas.qcut选择垃圾箱,以便你在每个垃圾箱中有相同数量的记录)