在 pandas 数据框中再次销售相同商品的概率
Probability of selling the same items again in a pandas dataframe
我需要根据如下格式的销售历史记录一起销售相似商品的概率:
pd.DataFrame({"sale_id": [1, 1, 1, 2, 2, 3, 3, 3, 3, 4],
"item": ["A", "B", "C", "A", "C", "A", "D", "E", "C", "B"],
"qty": [1, 4, 3, 2, 8, 3, 6, 5, 12, 9]})
sale_id Item Qty
1 A 1
1 B 4
1 C 3
2 A 2
2 C 8
3 A 3
3 D 6
3 E 5
3 C 12
4 B 9
我想构建这样的矩阵:
我尝试旋转数据框并使用带有自定义可调用项的 pd.DataFrame.corr(),但我 运行 通过调用 RAM 不足:
pd.pivot_table(df, index = "sales_id", columns = "item")
我使用的实际数据框长 700,000 行,有 20,000 个不同的项目。
我相信协同过滤的标准算法应该是这样的:
- 首先,您需要按 sale_id 对数据进行分组,然后合并项目列中的值。
- 然后,您需要为每个组创建一组一起购买的商品。
- 最后,您需要将现有项目的所有可能组合创建为一个集合,并与您的实际项目集进行交集
这就是它为我寻找的一切。这应该具有线性 space 复杂度,我相信它仍然可以改进,但它可以工作。
from itertools import combinations
import pandas as pd
df = pd.DataFrame({"sale_id": [1, 1, 1, 2, 2, 3, 3, 3, 3, 4],
"item": ["A", "B", "C", "A", "C", "A", "D", "E", "C", "B"],
"qty": [1, 4, 3, 2, 8, 3, 6, 5, 12, 9]})
# we don't care about quantity
df = df.loc[:, ['sale_id', 'item']]
# Get all the unique sets of items sold
grp = df.groupby('sale_id').transform(lambda x: ''.join(x))
purchases = grp['item'].apply(lambda x: ''.join(set(x))).unique()
# create all possible two-item pairs, then iterate over them
# adding 1 to the value of dictionary when the purchase
# matches the combination
unique_items = df.item.unique()
res = {}
for c in combinations(unique_items, 2):
c = set(c)
res[frozenset(c)] = 0
for i in purchases:
if c.intersection(i) == c:
res[frozenset(c)] += 1
# get percentages
for k, v in res.items():
res[k] = v / purchases.shape[0]
输出:
{frozenset({'A', 'B'}): 0.25,
frozenset({'A', 'C'}): 0.75,
frozenset({'A', 'D'}): 0.25,
frozenset({'A', 'E'}): 0.25,
frozenset({'B', 'C'}): 0.25,
frozenset({'B', 'D'}): 0.0,
frozenset({'B', 'E'}): 0.0,
frozenset({'C', 'D'}): 0.25,
frozenset({'C', 'E'}): 0.25,
frozenset({'D', 'E'}): 0.25}
我开始寻找一种纯粹的 Pandas 做事方式,最后也对 Pavel 的方法进行了大量优化。
在我做任何事情之前:
df = pd.DataFrame({"sale_id": [1, 1, 1, 2, 2, 3, 3, 3, 3, 4],
"item": ["A", "B", "C", "A", "C", "A", "D", "E", "C", "B"],
"qty": [1, 4, 3, 2, 8, 3, 6, 5, 12, 9]})
df.drop('qty', axis=1, inplace=True)
然后,timing/memory 跟踪开始:
- Pandas唯一方法:
values = df.groupby('sale_id')['item'].agg(lambda x: [*combinations(sorted(x), 2)]).explode().value_counts()
denom = df['sale_id'].nunique()
output = (values/denom).reindex(combinations(df['item'].unique(), 2), fill_value=0)
output
输出、时序和内存使用:
(A, B) 0.25
(A, C) 0.75
(A, D) 0.25
(A, E) 0.25
(B, C) 0.25
(B, D) 0.00
(B, E) 0.00
(C, D) 0.25
(C, E) 0.25
(D, E) 0.25
Name: item, dtype: float64
475 µs ± 13.9 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
(End, Peek) Memory: (5442, 16440)
- Pavel 方法的优化版本:
唯一的区别是只从 groupby 中提取 item
,因此生成 Series
而不是 DataFrame
,使用 agg
而不是 transform
,而不使用 set
,因为在 agg
.
之后没有必要
grp = df.groupby('sale_id')['item'].agg(lambda x: ''.join(x))
purchases = grp.apply(lambda x: ''.join(x)).unique()
unique_items = df.item.unique()
res = {}
for c in combinations(unique_items, 2):
c = set(c)
res[frozenset(c)] = 0
for i in purchases:
if c.intersection(i) == c:
res[frozenset(c)] += 1
for k, v in res.items():
res[k] = v / purchases.shape[0]
res
输出、时序和内存使用:
{frozenset({'A', 'B'}): 0.25,
frozenset({'A', 'C'}): 0.75,
frozenset({'A', 'D'}): 0.25,
frozenset({'A', 'E'}): 0.25,
frozenset({'B', 'C'}): 0.25,
frozenset({'B', 'D'}): 0.0,
frozenset({'B', 'E'}): 0.0,
frozenset({'C', 'D'}): 0.25,
frozenset({'C', 'E'}): 0.25,
frozenset({'D', 'E'}): 0.25}
276 µs ± 6.59 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
(End, Peek) Memory: (7643, 19402)
- 原方法:
grp = df.groupby('sale_id').transform(lambda x: ''.join(x))
purchases = grp['item'].apply(lambda x: ''.join(set(x))).unique()
unique_items = df.item.unique()
res = {}
for c in combinations(unique_items, 2):
c = set(c)
res[frozenset(c)] = 0
for i in purchases:
if c.intersection(i) == c:
res[frozenset(c)] += 1
for k, v in res.items():
res[k] = v / purchases.shape[0]
res
时间和内存使用:
1.01 ms ± 30.6 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
(End, Peek) Memory: (15847, 27630)
使用的方法是 %timeit
在 Jupyter Notebook 中,tracemalloc
用于内存跟踪。
我需要根据如下格式的销售历史记录一起销售相似商品的概率:
pd.DataFrame({"sale_id": [1, 1, 1, 2, 2, 3, 3, 3, 3, 4],
"item": ["A", "B", "C", "A", "C", "A", "D", "E", "C", "B"],
"qty": [1, 4, 3, 2, 8, 3, 6, 5, 12, 9]})
sale_id Item Qty
1 A 1
1 B 4
1 C 3
2 A 2
2 C 8
3 A 3
3 D 6
3 E 5
3 C 12
4 B 9
我想构建这样的矩阵:
我尝试旋转数据框并使用带有自定义可调用项的 pd.DataFrame.corr(),但我 运行 通过调用 RAM 不足:
pd.pivot_table(df, index = "sales_id", columns = "item")
我使用的实际数据框长 700,000 行,有 20,000 个不同的项目。
我相信协同过滤的标准算法应该是这样的:
- 首先,您需要按 sale_id 对数据进行分组,然后合并项目列中的值。
- 然后,您需要为每个组创建一组一起购买的商品。
- 最后,您需要将现有项目的所有可能组合创建为一个集合,并与您的实际项目集进行交集
这就是它为我寻找的一切。这应该具有线性 space 复杂度,我相信它仍然可以改进,但它可以工作。
from itertools import combinations
import pandas as pd
df = pd.DataFrame({"sale_id": [1, 1, 1, 2, 2, 3, 3, 3, 3, 4],
"item": ["A", "B", "C", "A", "C", "A", "D", "E", "C", "B"],
"qty": [1, 4, 3, 2, 8, 3, 6, 5, 12, 9]})
# we don't care about quantity
df = df.loc[:, ['sale_id', 'item']]
# Get all the unique sets of items sold
grp = df.groupby('sale_id').transform(lambda x: ''.join(x))
purchases = grp['item'].apply(lambda x: ''.join(set(x))).unique()
# create all possible two-item pairs, then iterate over them
# adding 1 to the value of dictionary when the purchase
# matches the combination
unique_items = df.item.unique()
res = {}
for c in combinations(unique_items, 2):
c = set(c)
res[frozenset(c)] = 0
for i in purchases:
if c.intersection(i) == c:
res[frozenset(c)] += 1
# get percentages
for k, v in res.items():
res[k] = v / purchases.shape[0]
输出:
{frozenset({'A', 'B'}): 0.25,
frozenset({'A', 'C'}): 0.75,
frozenset({'A', 'D'}): 0.25,
frozenset({'A', 'E'}): 0.25,
frozenset({'B', 'C'}): 0.25,
frozenset({'B', 'D'}): 0.0,
frozenset({'B', 'E'}): 0.0,
frozenset({'C', 'D'}): 0.25,
frozenset({'C', 'E'}): 0.25,
frozenset({'D', 'E'}): 0.25}
我开始寻找一种纯粹的 Pandas 做事方式,最后也对 Pavel 的方法进行了大量优化。
在我做任何事情之前:
df = pd.DataFrame({"sale_id": [1, 1, 1, 2, 2, 3, 3, 3, 3, 4],
"item": ["A", "B", "C", "A", "C", "A", "D", "E", "C", "B"],
"qty": [1, 4, 3, 2, 8, 3, 6, 5, 12, 9]})
df.drop('qty', axis=1, inplace=True)
然后,timing/memory 跟踪开始:
- Pandas唯一方法:
values = df.groupby('sale_id')['item'].agg(lambda x: [*combinations(sorted(x), 2)]).explode().value_counts()
denom = df['sale_id'].nunique()
output = (values/denom).reindex(combinations(df['item'].unique(), 2), fill_value=0)
output
输出、时序和内存使用:
(A, B) 0.25
(A, C) 0.75
(A, D) 0.25
(A, E) 0.25
(B, C) 0.25
(B, D) 0.00
(B, E) 0.00
(C, D) 0.25
(C, E) 0.25
(D, E) 0.25
Name: item, dtype: float64
475 µs ± 13.9 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
(End, Peek) Memory: (5442, 16440)
- Pavel 方法的优化版本:
唯一的区别是只从 groupby 中提取 item
,因此生成 Series
而不是 DataFrame
,使用 agg
而不是 transform
,而不使用 set
,因为在 agg
.
grp = df.groupby('sale_id')['item'].agg(lambda x: ''.join(x))
purchases = grp.apply(lambda x: ''.join(x)).unique()
unique_items = df.item.unique()
res = {}
for c in combinations(unique_items, 2):
c = set(c)
res[frozenset(c)] = 0
for i in purchases:
if c.intersection(i) == c:
res[frozenset(c)] += 1
for k, v in res.items():
res[k] = v / purchases.shape[0]
res
输出、时序和内存使用:
{frozenset({'A', 'B'}): 0.25,
frozenset({'A', 'C'}): 0.75,
frozenset({'A', 'D'}): 0.25,
frozenset({'A', 'E'}): 0.25,
frozenset({'B', 'C'}): 0.25,
frozenset({'B', 'D'}): 0.0,
frozenset({'B', 'E'}): 0.0,
frozenset({'C', 'D'}): 0.25,
frozenset({'C', 'E'}): 0.25,
frozenset({'D', 'E'}): 0.25}
276 µs ± 6.59 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
(End, Peek) Memory: (7643, 19402)
- 原方法:
grp = df.groupby('sale_id').transform(lambda x: ''.join(x))
purchases = grp['item'].apply(lambda x: ''.join(set(x))).unique()
unique_items = df.item.unique()
res = {}
for c in combinations(unique_items, 2):
c = set(c)
res[frozenset(c)] = 0
for i in purchases:
if c.intersection(i) == c:
res[frozenset(c)] += 1
for k, v in res.items():
res[k] = v / purchases.shape[0]
res
时间和内存使用:
1.01 ms ± 30.6 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
(End, Peek) Memory: (15847, 27630)
使用的方法是 %timeit
在 Jupyter Notebook 中,tracemalloc
用于内存跟踪。