我有数据集:
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.])
如何解决这个问题?
答案 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
的系列,其中每个元素都是True
或False
,具体取决于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选择垃圾箱,以便你在每个垃圾箱中有相同数量的记录)