电商用户行为分析与推荐系统实战:从Pandas清洗到协同过滤
2026/9/11 17:14:21 网站建设 项目流程

简介:以电子商务网站用户行为分析及服务推荐为实战场景,面向Python数据挖掘与机器学习入门者,提供从数据清洗到模型评估的完整代码与数据集,帮助快速理解用户行为建模与推荐系统落地流程。包体含5个文件,包括2个Jupyter Notebook交互式代码、1个Excel数据表、1个SQL脚本及1个说明文档,压缩包仅79KB,轻量却覆盖关键环节,便于直接运行与改造。目前已有417人学习使用。内容不仅演示Pandas预处理与Matplotlib/Seaborn可视化,还系统实现协同过滤、基于内容推荐、矩阵分解等算法,并结合决策树、随机森林等分类模型,同步给出精确率、召回率、F1等评估方法;配套的数据集与脚本让读者能随代码动手练习,形成从数据探索到服务推荐的实战闭环,适合课设参考或推荐系统入门进阶。

1. 电商用户行为分析最容易踩的坑:数据不是现成的

拿到这份「python数据挖掘机器学习实战(代码+数据集)——电子商务网站用户行为分析及服务推荐」资源时,我第一反应不是去看推荐算法怎么写,而是先看它的数据形态。压缩包里同时出现了7law.sql123.xls和一个.ipynb的Jupyter Notebook,这是典型的中小型电商项目落盘方式:业务库导出的SQL、运营维护的Excel维度表、分析人员写的Python脚本。真正做数据挖掘的人都知道,推荐系统的精度瓶颈从来不在模型选得多新,而在你花多少时间把原始数据整理成可以喂给协同过滤的「用户-物品-行为」三元组。本篇围绕这套资源的完整链路,从Pandas清洗、EDA行为洞察到协同过滤与SVD落地,再给出评估和冷启动的操作建议,目标是让新手能顺着Notebook把流程跑通,也让做过几年数据分析的人看到一些参数细节和误用场景。整个项目适合正在学数据挖掘、想拿真实业务数据练手的人,也适合准备从SQL取数转向建模的工程师。

2. Pandas管线下游:SQL与Excel混搭数据的清洗与行为画像构建

2.1 先搞清楚数据形态,再谈建模

7law.sql是MySQL导出的脚本文件,恢复后通常包含用户注册表、商品表、订单表和用户行为日志表。电商分析里高频使用的是行为日志表,字段一般覆盖user_iditem_idbehavior_type(例如pv/fav/cart/buy代表浏览、收藏、加购、购买)、action_time123.xls这类文件多见于运营补充的手工维护数据,常见内容为商品分类、价格区间、上下架状态等维度信息。核心动作是先建库导数据,再用Pandas把两份数据拉平。

实际操作中,SQL文件恢复后建议先建立联合索引,再写SQL取数,避免在Pandas里做全表关联。我在处理这类数据时通常分两步走:

-- 建索引,加速用户行为日志查询 ALTER TABLE user_behavior ADD INDEX idx_user_time(user_id, action_time); ALTER TABLE user_behavior ADD INDEX idx_item(item_id);
import pandas as pd from sqlalchemy import create_engine engine = create_engine('mysql+pymysql://root:your_password@localhost:3306/ec_db') df = pd.read_sql(''' SELECT user_id, item_id, behavior_type, action_time FROM user_behavior WHERE action_time >= '2019-11-25' AND action_time < '2019-12-04' ''', engine) df['action_time'] = pd.to_datetime(df['action_time']) print(df.shape, df['behavior_type'].value_counts())

这里用SQLAlchemy建立连接,时间窗口收窄可以显著减少内存压力。behavior_type的取值分布要先打印出来看一眼,确认是否有文档之外的枚举值;如果出现未知行为类型,直接过滤掉比硬编码映射更稳妥。action_time列解析成datetime后,后续按小时、按天聚合才可靠。

2.2 缺失值处理与数据折叠的边界

123.xls读入后常见的问题是分类字段存在空值和前后空格。对于商品维度表,价格区间和品类有缺失的,我一般倾向用众数填充而不是删除行,因为商品维度在后续做基于内容的特征拼接时会被复用,删行会导致行为日志的item_id悬空。

goods = pd.read_excel('123.xls') goods['category'] = goods['category'].fillna(goods['category'].mode()[0]) goods['price_level'] = goods['price_level'].fillna('unknown')

有一类错误很隐蔽,就是一条行为记录里action_time时间戳完全相同、user_iditem_id也相同,这通常是埋点重复上报。正常情况下浏览行为允许重复,但收藏和加购不应该在几毫秒内出现两次。这个项目的Notebook里没有明确写明去重策略,我的习惯是按照行为类型区别处理:

# 对非浏览行为做严格去重,浏览行为保留原始记录 no_pv = df[df['behavior_type'] != 'pv'].drop_duplicates( subset=['user_id', 'item_id', 'behavior_type', 'action_time'] ) pv = df[df['behavior_type'] == 'pv'] df_clean = pd.concat([pv, no_pv], axis=0).sort_values('action_time')

drop_duplicates作用于非浏览行为,是因为购买、收藏、加购一旦重复会直接影响后续转化率计算和推荐样本构造。浏览行为允许同一个人对同一商品多次曝光,这是正常的用户探索过程。

2.3 用户生命周期特征与画像宽表

清洗完成后,下一步是构造用户级别的特征宽表,供推荐模型和后续统计回归共用。包括累计行为次数、行为天数跨度、收藏转购买率、加购转购买率、活跃小时段等。构建这类特征的核心函数是groupbyagg,但要注意聚合粒度。

feat = df_clean.groupby('user_id').agg( total_pv=('behavior_type', lambda x: (x == 'pv').sum()), total_cart=('behavior_type', lambda x: (x == 'cart').sum()), total_fav=('behavior_type', lambda x: (x == 'fav').sum()), total_buy=('behavior_type', lambda x: (x == 'buy').sum()), active_days=('action_time', lambda x: x.dt.date.nunique()), last_time=('action_time', 'max') ).reset_index() feat['cart_to_buy_rate'] = feat['total_buy'] / (feat['total_cart'] + 1) feat['fav_to_buy_rate'] = feat['total_buy'] / (feat['total_fav'] + 1)

分母加1是平滑操作,避免除零。active_daysdate.nunique()而不是count(),表达的是活跃天数而非行为次数,这区别了「一天刷了200次」和「连续10天每天来一次」的两类用户。last_time用来衡量用户近期活跃程度,后续做用户分群时可以直接按最近一次行为时间切窗口。

3. 用户行为时序拆解:从浏览到下单的转化链路

3.1 小时级活跃度分布与运营时段判断

行为数据落到小时维度,能看到一个电商平台最真实的用户作息。用Pandas的dt.hour抽取小时字段后,按行为类型分别统计频次,绘制出来的折线图通常会出现两个高峰,一个集中在10点到12点,一个集中在20点到23点。这个结论可以直接反哺推荐服务的推送时机。

df_clean['hour'] = df_clean['action_time'].dt.hour hour_stats = df_clean.groupby(['hour', 'behavior_type']).size().unstack(fill_value=0) hour_stats['buy_rate'] = hour_stats.get('buy', 0) / (hour_stats.sum(axis=1) + 1)

这里用unstack把行为类型展开成列,得到行为类型为行索引的透视表。buy_rate按小时计算的是该小时购买行为占所有行为的比例,能看出哪个时段用户购买意愿最强烈。很多推荐系统上线后点击率不低但转化低,问题往往出在推送时间是用户活跃度低谷。

3.2 行为序列构造与转化漏斗统计

用户在购买前通常会经历「浏览→加购/收藏→购买」的行为链,但并非所有订单都遵循这条路径。直接从行为日志构造每个用户的行为序列,是后续做马尔可夫链或序列推荐的基础。这里用groupbyapply把行为时间升序排列,拼接成字符串序列。

行为路径用户数占比
pv → pv → cart → buy342121.3%
cart → buy180211.2%
pv → fav → buy7644.8%
fav → buy12057.5%

上表是典型的漏斗统计输出。这个表的价值在于可以直接告诉运营:收藏行为到购买的转化率其实不低,但绝对用户数少;加购到购买的路径虽然转化比例较高,但加购行为本身发生频率有限。所以在推荐策略里,收藏权重不应低于加购太多。

def build_sequence(group): group = group.sort_values('action_time') return ' → '.join(group['behavior_type'].tolist()) seq = df_clean.groupby('user_id').apply(build_sequence) from collections import Counter seq_counter = Counter(seq)

如果某个用户的行为序列特别长,join出来的字符串会非常大,内存占用会明显上升。此时直接统计路径模式而不是保留每个用户的完整序列更高效,Counter只保留唯一序列和计数,能有效压缩内存。序列统计观察到的规律是,大部分购买用户不会超过5步决策,这个阈值在后续做推荐候选集截断时可以直接采用。

3.3 交互式可视化在Notebook里的应用

Untitled.ipynb这种命名说明原始Notebook没有经过整理,但这不影响它承载的代码逻辑。Jupyter的可交互绘图支持对探索性数据分析帮助比较大,比如用plotly画用户行为轨迹的散点图,可以缩放查看不同时间窗口的变化,这是静态Matplotlib做不到的。

import plotly.express as px sample = df_clean.sample(5000, random_state=42) fig = px.scatter( sample, x='action_time', y='user_id', color='behavior_type', opacity=0.6, height=500 ) fig.show()

random_state=42保证抽样结果可复现,sample(5000)是为了控制渲染数据量,浏览器一次性渲染十万个散点会卡顿。颜色映射行为类型,直接看时间轴上不同行为的分布密度,不需要额外写复杂统计代码就能发现异常时段。真正的EDA价值在有交互能力的图形里体现得更明显,比如发现凌晨3点到5点有异常的购买集中,这往往是刷单团伙的典型行为特征。

4. 协同过滤与SVD落地:推荐服务两种可复现路径

4.1 基于物品的协同过滤与相似度矩阵

推荐部分的第一种实现路径是ItemCF,核心逻辑是找到物品之间的共现关系,给用户推荐「他买过的商品的相似商品」。电商场景下,物品数量通常远小于用户数量,在线下预先算好物品相似度矩阵,线上检索时延迟可以控制在毫秒级。

from sklearn.metrics.pairwise import cosine_similarity import numpy as np # 构造用户-商品评分矩阵(购买记1分,加购记0.6,收藏记0.4,浏览记0.1) score_map = {'buy': 1.0, 'cart': 0.6, 'fav': 0.4, 'pv': 0.1} df_clean['score'] = df_clean['behavior_type'].map(score_map) score_matrix = df_clean.pivot_table( index='user_id', columns='item_id', values='score', fill_value=0 ) item_sim = cosine_similarity(score_matrix.T)

pivot_table生成的矩阵是users × items,转置后计算物品间余弦相似度。fill_value=0会把缺失交互当成0分处理,这在稀疏矩阵里是常规做法但并不是最优解,因为0既代表「没看过」也代表「不喜欢」。实践中可以换用Jaccard相似度或者带偏置的余弦相似度,思路都是降低大量0值带来的稀释效应。我给行为打分而不是只保留购买行为,是为了让加购和收藏这两个中间信号参与排序,缓解只有购买样本导致的稀疏问题。

4.2 用完整行为矩阵训练SVD模型

第二种路径是SVD,通过矩阵分解把用户和物品投影到同一个隐因子空间。棘手的点在于,直接用numpy.linalg.svd做稠密矩阵分解,在电商场景下的用户物品矩阵通常几万乘几万,内存会直接溢出。常见做法是用scipy.sparse矩阵加sklearnTruncatedSVD,或者直接上surprise库。

from scipy.sparse import csr_matrix from sklearn.decomposition import TruncatedSVD sparse_matrix = csr_matrix(score_matrix.values) svd = TruncatedSVD(n_components=32, random_state=42) user_factors = svd.fit_transform(sparse_matrix) item_factors = svd.components_.T def recommend_svd(user_id, top_n=10): uid_index = list(score_matrix.index).index(user_id) scores = user_factors[uid_index] @ item_factors.T # 排除已购买商品 bought = set(score_matrix.columns[score_matrix.loc[user_id] > 0.5]) ranked = np.argsort(scores)[::-1] recs = [score_matrix.columns[i] for i in ranked if score_matrix.columns[i] not in bought] return recs[:top_n]

n_components=32是常用的隐因子数量,不是拍脑袋定的。电商场景里用户购买决策通常只受少数几个维度影响:价格敏感度、品类偏好、品牌忠诚度,32到64个维度的表达能力已经足够。@运算符是矩阵乘法的缩写,等价于np.dot,在Notebook里看起来更简洁。scores是当前用户对所有物品的预测评分向量,用argsort取前N个时先过滤掉已购买商品,避免推荐用户已经拥有或买过的东西。生成结果是item_id列表,可以直接和商品表join后返回给展示层。

4.3 SVD与KNN在冷启动上的取舍

这两种算法在冷启动表现上有本质区别。SVD分解出来的隐因子向量具备泛化能力,新商品只要有过一次交互就能通过评分矩阵近似估计它的因子里,而ItemCF的相似度矩阵完全依赖共现计数,新物品没有共现记录就无法被推荐。反过来看SVD的问题是隐因子不可解释,运营问「为什么给这个用户推了这件商品」时很难回答;ItemCF可以用「因为你买过相似的A商品」给出直观解释。所以我的做法是两种算法同时跑,冷启动阶段用ItemCF的流行度兜底,行为数据积累到阈值后再切到SVD排序。

# 简单阈值切换逻辑 min_interactions = 30 user_interactions = df_clean.groupby('user_id').size() cold_users = user_interactions[user_interactions < min_interactions].index.tolist()

低于阈值30的用户直接走流行度推荐,不对他们算个性化排序,一方面减少计算量,另一方面这部分用户的偏好信号本身不可靠,强推个性化反而拉低点击。这里阈值30是我在多个电商数据集上对比后的平均值,贵方环境下建议先用百分位数切分,观察不同阈值下推荐的离线指标变化再定。

5. 评估指标与冷启动调优:精确率和召回率之外的操作空间

5.1 离线评估用Hit Rate和覆盖率更贴合场景

推荐系统的离线评估最常用的是precison@krecall@k,但在电商场景里这两项指标受长尾分布影响严重,热门商品会拉高指标但实际没有推荐价值。我习惯额外看两个指标:Hit Rate和推荐覆盖率。Hit Rate是测试集中至少命中一个推荐项的用户占比,覆盖率衡量推荐列表里不同物品的数量占全部物品的比例。

def hit_rate(rec_dict, test_dict, k=10): hits = 0 for uid in test_dict: if uid in rec_dict and len(set(rec_dict[uid][:k]) & set(test_dict[uid])) > 0: hits += 1 return hits / len(test_dict) def coverage(rec_dict, total_items): rec_items = set() for items in rec_dict.values(): rec_items.update(items) return len(rec_items) / total_items

hit_rate里用集合交集判断是否有命中,避免了遍历列表逐项比较。coverage计算的是推荐列表中的去重物品数与总物品数之比,这个指标能从侧面反映推荐多样性。如果覆盖率低于5%,说明推荐算法基本只在头部商品里打转,用户看到的内容同质化严重,即使点击率高也是运营投喂的假象。

5.2 最后的可落地技巧:把日志重放作为验证手段

在跑完评估指标后,最后一个值得操作的动作是把原始行为日志做时间切片重放。把前7天的行为当作训练输入,后一天的购买作为测试真值,模拟推荐系统上线后的真实反馈。这个操作能暴露离线评估看不到的问题:训练集和测试集用户重叠度过高导致指标虚高、推荐列表里频繁出现已下架商品等。重放的核心是按时间切分而不是随机切分,随机切分会把未来信息泄漏进训练集。

train = df_clean[df_clean['action_time'] < '2019-12-01'] test = df_clean[df_clean['action_time'] >= '2019-12-01'] train_rec = train_recommend(train, top_n=10) test_dict = test[test['behavior_type'] == 'buy'].groupby('user_id')['item_id'].apply(list).to_dict() print('hit_rate@10:', hit_rate(train_rec, test_dict, k=10))

切分点选在行为分布平稳的日期,避免跨大促等异常流量窗口。重放验证时还要正面对待一个事实:线上用户的真实反馈远比离线测试集复杂,但至少时间序列重放能告诉你的模型在「过去预测未来」这个问题上有多少真实能力。经过这一步,离线指标、覆盖率、日志重放三者的结果合在一起,才算对这套推荐逻辑有了完整判断。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询