查看全课大纲(第 101 / 101 节)
- 1.数据分析基本概念
- 2.学习数据分析的一般路线
- 3.数据分析的流程
- 4.数据类型
- 5.环境部署(1)
- 6.环境部署(2)
- 7.课程介绍
- 8.TXT文件操作
- 9.JSON文件操作
- 10.CSV文件操作
- 11.Excel文件操作
- 12.数据库及SQL常用语法
- 13.数据库基本操作
- 14.数据库多表连接
- 15.实战:欧洲职业足球数据库分析
- 16.爬虫简介
- 17.URL管理模块
- 18.网页下载模块
- 19.网页解析模块(1)
- 20.网页解析模块(2)
- 21.Scrapy简介
- 22.Scrapy使用步骤(1)
- 23.Scrapy使用步骤(2)
- 24.Scrapy使用步骤(3)
- 25.Scrapy使用步骤(4)
- 26.实战:获取国内城市空气质量指数数据
- 27.NumPy和SciPy介绍
- 28.多维数组
- 29.多维数组操作
- 30.NumPy的常用方法
- 31.向量化介绍
- 32.向量化及通用函数
- 33.实战:2016美国大选分析
- 34.数据结构-Series
- 35.数据结构-DataFrame
- 36.数据结构-Index
- 37.Series的索引操作
- 38.DataFrame的索引操作
- 39.索引操作总结
- 40.运算与对齐
- 41.函数应用操作(1) -- map
- 42.函数应用操作 (2) -- apply applymap
- 43.文件读写操作
- 44.排序操作
- 45.数据清洗--处理缺失数据
- 46.数据清洗--处理重复数据
- 47.数据清洗--替换数据
- 48.常用统计方法(1) -- describe quantile
- 49.常用统计方法(2) -- sum mean median count
- 50.常用统计方法(3) -- max min idxmax idxmin
- 51.常用统计方法(4) -- mad var std cumsum
- 52.实战:全球食品数据分析
- 53.层级索引
- 54.分组与聚合介绍
- 55.分组操作(1) -- GroupBy对象及常用聚合操作
- 56.分组操作(2) -- 自定义分组及聚合操作
- 57.透视表介绍
- 58.透视表操作
- 59.数据规整(1) -- 数据合并concat
- 60.数据规整(2) -- 数据连接merge
- 61.数据重构(3) -- 数据重构stack unstack
- 62.实战:互联网电影资料库分析
- 63.探索性数据分析EDA介绍
- 64.EDA的目的
- 65.EDA常用工具
- 66.Matplotlib绘图基本介绍
- 67.Matplotlib画布
- 68.散点图和柱状图的绘制
- 69.直方图的绘制
- 70.矩阵绘图
- 71.子图的使用
- 72.Matplotlib颜色、标记、线型
- 73.Matplotlib坐标刻度、标签、图例、标题
- 74.Seaborn介绍
- 75.数据集分布可视化(1) -- 单变量分布、双变量分布
- 76.数据集分布可视化(2) -- 变量关系可视化
- 77.类别数据可视化 -- 类别散布图、类别内数据分布、类别内统计图
- 78.交互式数据可视化工具Bokeh介绍
- 79.Bokeh绘制散点图、柱状图、盒子图、弦图
- 80.Bokeh绘制常用图形元素
- 81.3D绘图 -- mplot3d
- 82.3D曲线可视化
- 83.3D散点图可视化
- 84.3D柱状图可视化
- 85.Pandas绘图
- 86.实战:Lending Club借贷数据探索性分析及可视化
- 87.机器学习介绍及应用场景
- 88.机器学习建模介绍 (1) -- 分类
- 89.机器学习建模介绍 (2) -- 回归
- 90.机器学习建模介绍 (3) -- 聚类
- 91.机器学习分类
- 92.机器学习工具scikit-learn
- 93.使用scikit-learn的流程
- 94.数据集准备及划分
- 95.模型选择
- 96.数据预处理及特征工程
- 97.过拟合与欠拟合
- 98.模型调参介绍
- 99.模型调参方法
- 100.模型测试及评价
- 101.实战:通过移动设备行为数据预测性别和年龄
实战:通过移动设备行为数据预测性别和年龄
约 26 分钟
小象实战讲义 · Python数据分析实战
本节我们将通过一个来自Kaggle的真实竞赛案例,串联起数据分析的完整链路。我们将学习如何从多个关联的原始数据表中提取特征、进行数据清洗与探索性分析,并最终构建机器学习模型来预测用户的性别与年龄分组。这是一个典型的分类任务,能让你将前面学到的Pandas数据处理、特征工程、可视化以及scikit-learn建模等知识融会贯通。
💡 核心导读
- 项目背景:使用移动设备行为数据(APP使用、事件记录)预测用户的性别与年龄分组。
- 数据特点:数据由多个CSV文件关联而成,需要进行数据融合与特征提取。
- 核心步骤:数据解读与融合 → 特征提取与EDA → 特征归一化 → 多模型构建与调参 → 结果比较。
- 技术要点:Pandas的
merge与groupby操作、LabelEncoder编码、MinMaxScaler归一化、GridSearchCV调参以及K近邻、线性SVM、逻辑回归等分类模型的应用。
数据解读与项目任务
本次实战案例的数据来源于Kaggle竞赛“TalkingData Mobile User Demographics”。原始数据集规模较大(约20万用户),包含6个相互关联的CSV文件。我们的目标是根据用户使用移动设备的行为数据(如APP安装、事件类型),预测其所属的“性别+年龄”分组。
分组共有四类:
M29+: 男性,29岁及以上F29+: 女性,29岁及以上F28-: 女性,28岁及以下M28-: 男性,28岁及以下
这是一个四分类问题。在真实项目中,数据往往分散在多个表中,我们的首要任务就是理解数据关系,并将它们整合起来。
# 数据关系解读与模拟数据构造
import pandas as pd
# 模拟 gender_age 表:设备ID与分组标签
gender_age_data = {
'device_id': ['dev_001', 'dev_002', 'dev_003', 'dev_004'],
'group': ['M29+', 'F29+', 'F28-', 'M28-']
}
gender_age_df = pd.DataFrame(gender_age_data)
# 模拟 events 表:事件ID与发生该事件的设备ID
events_data = {
'event_id': [1001, 1002, 1003, 1004, 1005, 1006],
'device_id': ['dev_001', 'dev_001', 'dev_002', 'dev_003', 'dev_003', 'dev_004']
}
events_df = pd.DataFrame(events_data)
# 模拟 app_events 表:事件ID与在该事件中涉及的APP ID
app_events_data = {
'event_id': [1001, 1001, 1002, 1003, 1004, 1005, 1006],
'app_id': ['app_a', 'app_b', 'app_c', 'app_a', 'app_d', 'app_e', 'app_f']
}
app_events_df = pd.DataFrame(app_events_data)
print("1. 设备分组表 (gender_age):")
print(gender_age_df)
print("\n2. 事件表 (events):")
print(events_df)
print("\n3. 应用事件表 (app_events):")
print(app_events_df)1. 设备分组表 (gender_age):
device_id group
0 dev_001 M29+
1 dev_002 F29+
2 dev_003 F28-
3 dev_004 M28-
2. 事件表 (events):
event_id device_id
0 1001 dev_001
1 1002 dev_001
2 1003 dev_002
3 1004 dev_003
4 1005 dev_003
5 1006 dev_004
3. 应用事件表 (app_events):
event_id app_id
0 1001 app_a
1 1001 app_b
2 1002 app_c
3 1003 app_a
4 1004 app_d
5 1005 app_e
6 1006 app_f通过观察模拟数据,我们可以理清表间关系:
gender_age表提供了每个设备(device_id)对应的预测目标(group)。events表记录了每个事件(event_id)由哪个设备产生。app_events表记录了每个事件涉及了哪些APP。- 一个设备可以有多个事件,一个事件可以涉及多个APP。我们可以通过
event_id连接events和app_events表,再通过device_id与gender_age表关联,最终为每个设备统计出行为特征。
特征提取与数据融合
基于上述关系,我们为每个设备提取两个特征:
- 事件类型个数:该设备产生的不同事件(
event_id)的数量。 - 安装APP个数:该设备在所有事件中涉及的不同APP(
app_id)的数量。
下面我们使用Pandas的merge和groupby操作来完成数据融合与特征提取。
# 特征提取与数据融合演示
import pandas as pd
# 使用上一代码块构造的模拟数据
gender_age_data = {'device_id': ['dev_001', 'dev_002', 'dev_003', 'dev_004'], 'group': ['M29+', 'F29+', 'F28-', 'M28-']}
events_data = {'event_id': [1001, 1002, 1003, 1004, 1005, 1006], 'device_id': ['dev_001', 'dev_001', 'dev_002', 'dev_003', 'dev_003', 'dev_004']}
app_events_data = {'event_id': [1001, 1001, 1002, 1003, 1004, 1005, 1006], 'app_id': ['app_a', 'app_b', 'app_c', 'app_a', 'app_d', 'app_e', 'app_f']}
gender_age_df = pd.DataFrame(gender_age_data)
events_df = pd.DataFrame(events_data)
app_events_df = pd.DataFrame(app_events_data)
# 1. 合并事件与APP数据,并统计特征
# 将 app_events 和 events 通过 event_id 左连接合并
device_app = app_events_df.merge(events_df, how='left', on='event_id')
# 按 device_id 分组,统计唯一 event_id 和 app_id 的个数
stats_df = device_app.groupby('device_id')[['event_id', 'app_id']].nunique()
stats_df = stats_df.reset_index() # 将分组索引转为列
# 2. 将统计特征与分组标签数据合并,通过 device_id 内连接
all_df = gender_age_df.merge(stats_df, how='inner', on='device_id')
# 3. 重命名特征列
all_df.columns = ['device_id', 'group', 'n_events', 'n_apps']
print("融合并提取特征后的数据集:")
print(all_df)
print("\n特征说明:")
print("- n_events: 设备产生的事件类型数")
print("- n_apps: 设备涉及的APP数")融合并提取特征后的数据集:
device_id group n_events n_apps
0 dev_001 M29+ 2 3
1 dev_002 F29+ 1 1
2 dev_003 F28- 2 2
3 dev_004 M28- 1 1
特征说明:
- n_events: 设备产生的事件类型数
- n_apps: 设备涉及的APP数EDA、特征归一化与标签编码
在建模之前,我们需要对数据进行探索性分析(EDA),了解特征分布和类别情况。同时,机器学习模型通常要求特征处于同一量纲,并对分类标签进行数值编码。
# EDA 与特征归一化演示
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import LabelEncoder
import pandas as pd
# 构造演示数据
data = {
'device_id': ['dev_001', 'dev_002', 'dev_003', 'dev_004', 'dev_005', 'dev_006'],
'group': ['M29+', 'F29+', 'F28-', 'M28-', 'M29+', 'F28-'],
'n_events': [45, 120, 30, 200, 80, 60],
'n_apps': [12, 35, 8, 80, 25, 18]
}
df = pd.DataFrame(data)
print("数据集形状:", df.shape)
print("\n类别分布:")
print(df['group'].value_counts())
# 1. 标签编码:将字符串类别转为数字
label_enc = LabelEncoder()
df['group_encoded'] = label_enc.fit_transform(df['group'])
print("\n编码后的标签 (group_encoded):")
print(df[['group', 'group_encoded']].drop_duplicates().sort_values('group_encoded'))
# 2. 特征归一化 (Min-Max Scaling)
scaler = MinMaxScaler()
# 选择要归一化的特征列
features_to_scale = df[['n_events', 'n_apps']]
features_scaled = scaler.fit_transform(features_to_scale)
# 将归一化后的特征放回DataFrame
df[['n_events_scaled', 'n_apps_scaled']] = features_scaled
print("\n归一化前后的特征对比:")
print(df[['n_events', 'n_events_scaled', 'n_apps', 'n_apps_scaled']].head())
# 3. 简单的EDA可视化
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
# 类别分布条形图
sns.countplot(data=df, x='group', ax=axes[0])
axes[0].set_title('类别分布 (Group Distribution)')
# 特征散点图(按类别着色)
scatter = axes[1].scatter(df['n_events'], df['n_apps'], c=df['group_encoded'], cmap='viridis', alpha=0.7)
axes[1].set_xlabel('n_events')
axes[1].set_ylabel('n_apps')
axes[1].set_title('特征散点图 (Colored by Group)')
# 添加图例
legend_labels = label_enc.inverse_transform(sorted(df['group_encoded'].unique()))
for i, label in enumerate(legend_labels):
axes[1].scatter([], [], c=[plt.cm.viridis(i/3)], label=label)
axes[1].legend(title='Group')
plt.tight_layout()
# 在讲义中,我们仅展示绘图逻辑,实际运行时不保存文件
plt.show()数据集形状: (6, 4)
类别分布:
group
M29+ 2
F28- 2
F29+ 1
M28- 1
Name: count, dtype: int64
编码后的标签 (group_encoded):
group group_encoded
2 F28- 0
1 F29+ 1
3 M28- 2
0 M29+ 3
归一化前后的特征对比:
n_events n_events_scaled n_apps n_apps_scaled
0 45 0.088235 12 0.055556
1 120 0.529412 35 0.375000
2 30 0.000000 8 0.000000
3 200 1.000000 80 1.000000
4 80 0.294118 25 0.236111模型构建、调参与结果比较
现在,我们有了特征(n_events, n_apps)和数值化的标签,可以开始构建分类模型。我们将尝试三种常见的分类模型:K近邻(KNN)、线性支持向量机(Linear SVM)和逻辑回归(Logistic Regression)。对于每个模型,我们将使用GridSearchCV进行超参数调优,并比较特征归一化对模型性能的影响。
# 模型构建、调参与比较
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import LinearSVC
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.preprocessing import MinMaxScaler, LabelEncoder
import numpy as np
import pandas as pd
# 为了演示,我们构造一个稍大的模拟数据集,以便进行训练/测试分割
np.random.seed(42) # 固定随机种子,确保结果可复现
n_samples = 40
# 构造特征:假设 n_events 和 n_apps 与组别有一定关系
X_demo = np.random.randint(1, 100, size=(n_samples, 2)).astype(float)
# 构造标签:简单规则,根据特征和生成4类标签
y_demo = (X_demo[:, 0] / 25 + X_demo[:, 1] / 30).astype(int) % 4
# 分割数据集
X_train, X_test, y_train, y_test = train_test_split(X_demo, y_demo, test_size=0.25, random_state=10)
# 特征归一化
scaler = MinMaxScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 定义要比较的模型及其参数网格
model_dict = {
'kNN': (KNeighborsClassifier(), {'n_neighbors': [3, 5, 7]}),
'LinearSVM': (LinearSVC(max_iter=5000, random_state=42), {'C': [0.1, 1, 10]}),
'LR': (LogisticRegression(max_iter=500, random_state=42), {'C': [0.01, 0.1, 1]})
}
print("模型调参结果比较:")
print("-" * 50)
results = []
for model_name, (model, param_grid) in model_dict.items():
print(f"\n>>> 训练模型: {model_name}")
# 1. 使用未归一化的特征
gs = GridSearchCV(model, param_grid, cv=3, scoring='accuracy')
gs.fit(X_train, y_train)
test_acc_raw = gs.score(X_test, y_test)
print(f" 未归一化 -> 最优参数: {gs.best_params_}, 验证集最高得分: {gs.best_score_:.3f}, 测试集准确率: {test_acc_raw:.3f}")
# 2. 使用归一化的特征
gs_scaled = GridSearchCV(model, param_grid, cv=3, scoring='accuracy')
gs_scaled.fit(X_train_scaled, y_train)
test_acc_scaled = gs_scaled.score(X_test_scaled, y_test)
print(f" 归一化后 -> 最优参数: {gs_scaled.best_params_}, 验证集最高得分: {gs_scaled.best_score_:.3f}, 测试集准确率: {test_acc_scaled:.3f}")
results.append({
'Model': model_name,
'Not Scaled (%)': round(test_acc_raw * 100, 1),
'Scaled (%)': round(test_acc_scaled * 100, 1)
})
# 展示结果比较
results_df = pd.DataFrame(results).set_index('Model')
print("\n" + "="*50)
print("最终准确率比较 (%):")
print(results_df)模型调参结果比较:
--------------------------------------------------
>>> 训练模型: kNN
未归一化 -> 最优参数: {'n_neighbors': 3}, 验证集最高得分: 0.567, 测试集准确率: 0.600
归一化后 -> 最优参数: {'n_neighbors': 3}, 验证集最高得分: 0.567, 测试集准确率: 0.600
>>> 训练模型: LinearSVM
未归一化 -> 最优参数: {'C': 1}, 验证集最高得分: 0.367, 测试集准确率: 0.500
归一化后 -> 最优参数: {'C': 1}, 验证集最高得分: 0.400, 测试集准确率: 0.500
>>> 训练模型: LR
未归一化 -> 最优参数: {'C': 0.01}, 验证集最高得分: 0.367, 测试集准确率: 0.500
归一化后 -> 最优参数: {'C': 1}, 验证集最高得分: 0.367, 测试集准确率: 0.400
==================================================
最终准确率比较 (%):
Not Scaled (%) Scaled (%)
Model
kNN 60.0 60.0
LinearSVM 50.0 50.0
LR 50.0 40.0从演示结果可以看出,在这个模拟数据集上,特征归一化并未带来测试准确率的提升:KNN与线性SVM归一化前后持平(0.600与0.500),逻辑回归反而从0.500降至0.400。这验证了一个重要观点:特征归一化并不总是提升模型性能,其效果取决于具体模型和数据——本例中两个特征(事件类型数、APP数)本就处于相近量纲,归一化自然影响有限;只有当特征量纲差异巨大时,对依赖距离计算的模型(如KNN)和使用梯度下降类优化算法的模型(如SVM、逻辑回归)归一化才会明显受益。
在原课程的真实项目运行中,使用两个特征(事件类型数和APP数)在测试集上取得的最佳准确率约为38%。虽然看起来不高,但考虑到这是一个四分类问题(随机猜测的基线是25%),模型确实学习到了一些规律。若要进一步提升性能,可以尝试从提供的其他数据表(如手机品牌、APP类别表)中提取更多特征。
📝 动手练一练
- 概念辨析:为什么逻辑回归(Logistic Regression)名字里有“回归”,却是一个分类模型?特征归一化对逻辑回归模型的影响机制是什么?
👉 点击查看参考答案
- 逻辑回归的本质:逻辑回归的“回归”源于其历史和发展过程。它最初用于解决二分类问题,其核心是使用逻辑函数(Sigmoid函数)将线性回归的连续值输出映射到(0,1)区间,解释为属于正类的概率。因此,它本质上是一个概率型分类模型,而非预测连续值的回归模型。
- 归一化对逻辑回归的影响:逻辑回归的求解通常使用梯度下降等优化算法。如果特征量纲差异巨大,梯度更新时会在量纲大的特征方向上剧烈震荡,导致收敛缓慢甚至难以找到最优解。归一化使所有特征处于相近的范围,有助于优化算法更平稳、更快地收敛,从而可能提升模型性能与稳定性。不过在课程演示的小数据集上,两个特征本就量纲相近,实测归一化后逻辑回归的准确率反而从0.500降至0.400——这正印证了归一化的收益取决于数据本身的量纲差异程度。
- 动手实践:假设你拿到了
phone_brand_device_model表,其中包含device_id、phone_brand(手机品牌)和device_model(手机型号)信息。你如何将这个信息作为新的特征加入到我们现有的建模流程中?请简述你的思路(例如,是直接使用品牌名称,还是进行某种编码?)。
👉 点击查看参考答案
思路简述:
- 数据读取与合并:使用Pandas读取
phone_brand_device_model表,并通过device_id将其与我们已经融合好的all_df进行合并(例如左连接或内连接)。 - 特征编码:品牌和型号通常是类别型特征(字符串),不能直接输入数值模型。需要进行编码。
- 标签编码(Label Encoding):为每个品牌/型号分配一个唯一的整数。适用于树模型,但对线性模型可能引入错误的序关系。
- 独热编码(One-Hot Encoding):使用
pd.get_dummies()为每个品牌/型号创建新的二进制特征列。这是更通用的方法,尤其适用于线性模型,但可能会增加特征维度(维度灾难)。
- 特征选择:如果品牌/型号类别非常多,独热编码后特征会非常稀疏。可以考虑:
- 只保留出现频率最高的前N个品牌/型号进行编码。
- 将低频品牌/型号统一归为“其他”类别。
- 加入模型:将编码后的新特征列与原有的
n_events、n_apps特征拼接,形成新的特征矩阵,然后重新进行数据分割、归一化(注意:独热编码后的特征通常不需要归一化)和模型训练。
本章小结
本节我们完成了一个完整的数据分析及机器学习建模实战。我们从一个多表关联的真实场景出发,经历了从数据解读、特征提取、数据清洗、探索性分析到最终构建并评估多个分类模型的完整流程。
📋 行动清单
- 回顾数据融合:理解如何使用Pandas的
merge和groupby从多个关联表中提取聚合特征。 - 掌握预处理步骤:记住分类建模前,对特征进行归一化(
MinMaxScaler)和对标签进行编码(LabelEncoder)是标准步骤。 - 实践模型调优:使用
GridSearchCV对不同的分类模型(如KNN、SVM、逻辑回归)进行超参数搜索,并学会比较不同预处理下模型的性能。 - 思考特征工程:尝试构思如何利用案例中未使用的其他数据表(如手机品牌、APP类别)来构造更多特征,以提升模型预测能力。
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。
- 下载全套课件打包(第1-5章)ZIP · 12.8MB
- 下载全套课件打包(第6-8章)ZIP · 15MB
- 下载实战数据集:AppleStore 应用商城分析ZIP · 329KB
- 下载实战数据集:女性服装电商分析ZIP · 2.8MB
- 下载Python 数据分析环境搭建指南PDF · 2MB
- 下载Scrapy 安装教程PDF · 12.7MB
- 下载附加实战项目:AppleStore 应用商城数据分析ZIP · 0.3MB · ipynb + CSV 数据
- 下载附加实战项目:银行电话营销数据分析ZIP · 0.4MB · ipynb + CSV 数据
- 下载附加实战项目:女性服装电商评论数据分析ZIP · 2.7MB · ipynb + CSV 数据
- 下载附加实战项目:美国化学学会杂志数据分析ZIP · 34.2MB · ipynb + SQLite 数据库