为什么使用pandas qcut返回值error:Bin edges必须是唯一的?

2024-05-14 03:21:00 发布

您现在位置:Python中文网/ 问答频道 /正文

我有数据集:

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.])

如何解决这个问题?


Tags: 数据gthttpdataframedf原始数据floatcodes
2条回答

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

如果使用pandas,>;=0.20.0,则会添加一个选项duplicates='raise'|'drop'来控制是在重复边上提升还是删除它们,这将导致比指定的存储箱更少,并且某些存储箱(包含更多元素)比其他存储箱更大。

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

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

这样你就可以得到相同的值进入不同的分位数。这可能正确,也可能不正确,这取决于您的特定需要(如果这不是您想要的,您可能希望查看pandas.cut,它根据值本身选择间隔均匀的存储箱,而pandas.qcut选择存储箱,以便每个存储箱中的记录数相同)

我在Jupyter中运行这个文件,并将exampledata.txt放在与笔记本相同的目录中。

请注意第一行:

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

加载数据文件中未定义的列'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%的数字1。这就破坏了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()

这一行定义了一个helper函数,该函数将应用于下一行中的另一个序列。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的箱子编号。这一系列箱子编号与您试图获得的相同:

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)

相关问题 更多 >