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

ihs*_*sat 5 python pandas

我有数据集:

recency;frequency;monetary
21;156;41879955
13;88;16850284
8;74;79150488
2;74;26733719
9;55;16162365
...;...;...
Run Code Online (Sandbox Code Playgroud)

详细的原始数据 - > 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
Run Code Online (Sandbox Code Playgroud)

但它的返回错误

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

怎么解决这个?

piR*_*red 6

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

请注意第一行:

df = pd.DataFrame(datas, columns=['userid', 'recency', 'frequency', 'monetary'])
Run Code Online (Sandbox Code Playgroud)

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

说明

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

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

    edges = pd.Series([float(i) / n for i in range(n + 1)])
Run Code Online (Sandbox Code Playgroud)

该线根据所定义的所需箱数定义一系列百分位边n.在n = 5边缘的情况下[0.0, 0.2, 0.4, 0.6, 0.8, 1.0]

    f = lambda x: (edges >= x).argmax()
Run Code Online (Sandbox Code Playgroud)

此行定义了一个辅助函数,该函数将应用于下一行中的另一个系列. edges >= x将返回一个长度等于edges每个元素所在位置的系列,True或者False取决于是否x小于或等于该边缘.在的情况下,x = 0.14由此而来(edges >= x)[False, True, True, True, True, True].通过采取argmax()我已经确定了该系列的第一个索引True,在这种情况下1.

    return series.rank(pct=1).apply(f)
Run Code Online (Sandbox Code Playgroud)

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

pd.qcut(df['recency'].values, 5).codes + 1
Run Code Online (Sandbox Code Playgroud)

这带来的后果是,垃圾箱不再相等,垃圾箱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
Run Code Online (Sandbox Code Playgroud)

更新

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