查看全课大纲(第 62 / 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.实战:通过移动设备行为数据预测性别和年龄
实战:互联网电影资料库分析
约 24 分钟
互联网电影资料库分析实战
小象实战讲义 · Python数据分析实战
在掌握了Pandas的分组聚合、数据规整等核心高阶功能后,本节我们将通过一个完整的实战项目——互联网电影资料库数据分析,来综合运用所学知识。我们将面对真实数据中常见的挑战:缺失值处理、多维度分组统计,以及如何将一行包含多个类别的数据(如电影类型)拆分为多行,以便进行更精细的分析。
💡 核心导读
- 任务一:分组统计票房收入:学习如何按导演、主演以及导演-主演组合进行分组,并计算票房总和,找出最具票房号召力的个人与组合。
- 任务二:电影类型数据分析:这是本节的难点与重点。我们将学习如何将“动作|冒险|奇幻|科幻”这样的单行字符串数据,通过
str.split、concat和stack等方法,重构为每行一个类型的“长格式”数据,从而便于统计各类电影的分布。 - 调试技巧:在复杂的数据转换过程中,我们将演示如何使用PyCharm的调试模式,逐行跟踪代码执行,查看中间变量,这对于理解数据流和排查错误至关重要。
- 从分析到可视化:统计结果(如电影类型频次)天然适合用条形图呈现——本节先把统计表做对,具体画图留到下一章的数据可视化内容中实践,为它做好铺垫。
项目概述与数据初探
本项目使用一个电影数据集,包含每部电影的名称、导演、主演、类型、票房收入等属性。我们的核心任务有两个:
- 基于分组统计票房收入:例如,统计每位导演或演员的总票房,或分析导演与演员的最佳票房组合。
- 基于电影类型的数据分析:统计数据集中各类电影(如剧情、喜剧、动作等)的数量分布。
首先,我们加载数据并查看其基本情况。注意,真实数据往往包含缺失值,这是数据分析的第一步——数据清洗。
import pandas as pd
# 设置显示选项,以便在控制台查看更多列
pd.set_option('display.max_columns', 10)
# 1. 加载数据(此处为演示,我们构造一个微型数据集)
data = {
'movie_title': ['Avatar', 'Pirates of the Caribbean', 'The Dark Knight', 'Toy Story'],
'director_name': ['James Cameron', 'Gore Verbinski', 'Christopher Nolan', 'John Lasseter'],
'actor_1_name': ['Sam Worthington', 'Johnny Depp', 'Christian Bale', 'Tom Hanks'],
'genres': ['Action|Adventure|Fantasy|Sci-Fi', 'Action|Adventure|Fantasy', 'Action|Crime|Drama|Thriller', 'Adventure|Animation|Comedy|Family'],
'gross': [2787965087, 963420425, 1004558444, 373554033] # 虚构票房数据,单位美元
}
df = pd.DataFrame(data)
print("=== 原始数据预览 ===")
print(df)
print(f"\n数据形状: {df.shape}")
print(f"列信息:\n{df.dtypes}")=== 原始数据预览 ===
movie_title director_name actor_1_name \
0 Avatar James Cameron Sam Worthington
1 Pirates of the Caribbean Gore Verbinski Johnny Depp
2 The Dark Knight Christopher Nolan Christian Bale
3 Toy Story John Lasseter Tom Hanks
genres gross
0 Action|Adventure|Fantasy|Sci-Fi 2787965087
1 Action|Adventure|Fantasy 963420425
2 Action|Crime|Drama|Thriller 1004558444
3 Adventure|Animation|Comedy|Family 373554033
数据形状: (4, 5)
列信息:
movie_title str
director_name str
actor_1_name str
genres str
gross int64
dtype: object任务一:分组统计票房收入
第一个任务相对直接,核心是使用groupby进行分组聚合。我们将编写一个通用的分析函数,可以按任意列或列的组合进行分组,并计算票房总和。
import pandas as pd
# 构造示例数据
data = {
'director_name': ['James Cameron', 'Gore Verbinski', 'Christopher Nolan', 'John Lasseter', 'James Cameron'],
'actor_1_name': ['Sam Worthington', 'Johnny Depp', 'Christian Bale', 'Tom Hanks', 'Arnold Schwarzenegger'],
'gross': [2787965087, 963420425, 1004558444, 373554033, 1500000000]
}
df_demo = pd.DataFrame(data)
def analyse_gross(df_data, groupby_columns, output_name="结果"):
"""
分析票房数据
"""
# 按指定列分组,不将分组列作为索引,然后对gross列求和
grouped_data = df_data.groupby(groupby_columns, as_index=False)['gross'].sum()
# 按票房总和降序排列
sorted_data = grouped_data.sort_values(by='gross', ascending=False)
print(f"\n{output_name}:")
print(sorted_data.to_string(index=False))
return sorted_data
# 按导演分组统计
director_gross = analyse_gross(df_demo, ['director_name'], "导演票房统计")
# 按演员分组统计
actor_gross = analyse_gross(df_demo, ['actor_1_name'], "演员票房统计")
# 按导演和演员组合分组统计
combo_gross = analyse_gross(df_demo, ['director_name', 'actor_1_name'], "导演-演员组合票房统计")导演票房统计:
director_name gross
James Cameron 4287965087
Christopher Nolan 1004558444
Gore Verbinski 963420425
John Lasseter 373554033
演员票房统计:
actor_1_name gross
Sam Worthington 2787965087
Arnold Schwarzenegger 1500000000
Christian Bale 1004558444
Johnny Depp 963420425
Tom Hanks 373554033
导演-演员组合票房统计:
director_name actor_1_name gross
James Cameron Sam Worthington 2787965087
James Cameron Arnold Schwarzenegger 1500000000
Christopher Nolan Christian Bale 1004558444
Gore Verbinski Johnny Depp 963420425
John Lasseter Tom Hanks 373554033代码解读:
groupby的as_index=False参数确保分组列在结果中仍作为普通列,而不是变成索引,这样后续处理更直观。sort_values(by='gross', ascending=False)将结果按票房降序排列,一眼就能看出谁是最卖座的。
任务二:电影类型数据分析(难点:数据重构)
第二个任务是本节的重点与难点。在数据集中,genres列存储的是用竖线|分隔的多个类型,如"Action|Adventure|Fantasy"。如果我们想统计“动作片有多少部”,直接对原始列分组会得到"Action|Adventure|Fantasy"这样的组合作为一个类别,而不是独立的“Action”类别。
目标:将一行包含多个类型的数据,拆分为多行,每行只包含一个类型,同时保留电影的其他信息(如标题)。这个过程称为数据重构或从宽格式变为长格式。
思路:
- 分割:使用
Series.str.split('|', expand=True)将genres列按分隔符拆分成多列。 - 合并与筛选:将拆分出的多列与原始数据(如电影标题)横向合并。
- 堆叠:使用
stack()方法将多列“堆叠”成一列,即将列转换为行索引的下一级,从而实现一行变多行。 - 分组统计:对最终的单列类型数据进行分组计数。
下面我们通过一个完整的、可自包含的代码块来演示这一过程,并模拟使用调试模式查看中间结果。
import pandas as pd
# 构造包含电影类型的数据
data_genres = {
'movie_title': ['Avatar', 'Pirates of the Caribbean', 'Toy Story'],
'genres': ['Action|Adventure|Fantasy|Sci-Fi', 'Action|Adventure|Fantasy', 'Adventure|Animation|Comedy|Family']
}
df_genres_raw = pd.DataFrame(data_genres)
print("=== 步骤1: 原始数据 ===")
print(df_genres_raw.to_string(index=False))
# 步骤2: 分割genres列
genre_split_df = df_genres_raw['genres'].str.split('|', expand=True)
print("\n=== 步骤2: 分割后的多列数据 (原始列名0,1,2...) ===")
print(genre_split_df)
# 步骤3: 为分割后的列设置更有意义的列名
n_cols = genre_split_df.shape[1]
new_col_names = [f'genre_{i}' for i in range(n_cols)]
genre_split_df.columns = new_col_names
print(f"\n=== 步骤3: 重命名列后 ===")
print(genre_split_df[new_col_names].to_string(index=False))
# 步骤4: 横向合并原始数据与分割后的类型数据
concat_df = pd.concat([df_genres_raw, genre_split_df], axis=1)
# 选择我们关心的列:电影标题和各类型列
cols_to_keep = ['movie_title'] + new_col_names
concat_df = concat_df[cols_to_keep].set_index('movie_title')
print("\n=== 步骤4: 合并并设置索引后 ===")
print(concat_df)
# 步骤5: 使用stack进行“堆叠”,将列转换为行
stacked_series = concat_df.stack()
print("\n=== 步骤5: stack() 操作后 (得到一个多级索引的Series) ===")
print(stacked_series)
# 步骤6: 将Series转换为DataFrame并重命名列
final_genres_df = stacked_series.to_frame(name='genre')
# 重置索引,将多级索引变回普通列
final_genres_df = final_genres_df.reset_index().drop(columns='level_1')
print("\n=== 步骤6: 最终重构后的数据 (每行一个电影一个类型) ===")
print(final_genres_df.to_string(index=False))
# 步骤7: 基于重构数据统计电影类型频次
genres_stats = final_genres_df.groupby('genre').size().sort_values(ascending=False)
print("\n=== 步骤7: 电影类型统计结果 ===")
print(genres_stats.to_string())=== 步骤1: 原始数据 ===
movie_title genres
Avatar Action|Adventure|Fantasy|Sci-Fi
Pirates of the Caribbean Action|Adventure|Fantasy
Toy Story Adventure|Animation|Comedy|Family
=== 步骤2: 分割后的多列数据 (原始列名0,1,2...) ===
0 1 2 3
0 Action Adventure Fantasy Sci-Fi
1 Action Adventure Fantasy NaN
2 Adventure Animation Comedy Family
=== 步骤3: 重命名列后 ===
genre_0 genre_1 genre_2 genre_3
Action Adventure Fantasy Sci-Fi
Action Adventure Fantasy NaN
Adventure Animation Comedy Family
=== 步骤4: 合并并设置索引后 ===
genre_0 genre_1 genre_2 genre_3
movie_title
Avatar Action Adventure Fantasy Sci-Fi
Pirates of the Caribbean Action Adventure Fantasy NaN
Toy Story Adventure Animation Comedy Family
=== 步骤5: stack() 操作后 (得到一个多级索引的Series) ===
movie_title
Avatar genre_0 Action
genre_1 Adventure
genre_2 Fantasy
genre_3 Sci-Fi
Pirates of the Caribbean genre_0 Action
genre_1 Adventure
genre_2 Fantasy
genre_3 NaN
Toy Story genre_0 Adventure
genre_1 Animation
genre_2 Comedy
genre_3 Family
dtype: str
=== 步骤6: 最终重构后的数据 (每行一个电影一个类型) ===
movie_title genre
Avatar Action
Avatar Adventure
Avatar Fantasy
Avatar Sci-Fi
Pirates of the Caribbean Action
Pirates of the Caribbean Adventure
Pirates of the Caribbean Fantasy
Pirates of the Caribbean NaN
Toy Story Adventure
Toy Story Animation
Toy Story Comedy
Toy Story Family
=== 步骤7: 电影类型统计结果 ===
genre
Adventure 3
Action 2
Fantasy 2
Animation 1
Comedy 1
Family 1
Sci-Fi 1关键操作解析:
str.split('|', expand=True):向量化字符串操作,将每个单元格的字符串按|分割,并展开为DataFrame的多个列。缺失部分在 pandas 3.0 中填充为NaN。注意旧版 pandas 的stack()会默认丢弃这些 NaN,但 pandas 3.0 的stack()保留 NaN(如步骤5、6所示);步骤7 的统计之所以干净,是因为groupby()默认排除 NaN 键(dropna=True),统计中不会出现 NaN 类别。pd.concat([...], axis=1):沿列方向(axis=1)拼接两个DataFrame,扩展数据宽度。.set_index('movie_title'):将电影标题设为索引,为后续stack操作做准备。.stack():这是实现“宽变长”的核心。它将列索引的层级“堆叠”到行索引上。对于一个以movie_title为索引、genre_0到genre_3为列的数据,stack后,每个(movie_title, genre_i)对成为一行,其值为对应的类型字符串。.to_frame(name='genre')和.reset_index():将堆叠后的Series转换回整洁的DataFrame。
调试技巧:使用PyCharm Debugger
对于像get_genres_data这样包含多步数据转换的复杂函数,理解每一步的数据形态至关重要。逐字稿中讲师演示了使用PyCharm的调试模式。这里简述其核心步骤,供你在自己的环境中实践:
- 设置断点:在代码行号左侧点击,出现红点,程序运行到此处会暂停。
- 以Debug模式运行:在PyCharm中右键点击文件或代码区域,选择
Debug '文件名',而非普通的Run。 - 控制执行流程:
- Step Over (F8):执行当前行,但不进入函数内部。
- Step Into My Code (Alt+Shift+F7):执行当前行,如果调用的是自己编写的函数,则进入该函数内部。
- Resume Program (F9):继续运行,直到下一个断点或程序结束。
- 查看变量:在Debug工具窗口的
Variables面板,可以查看当前作用域内所有变量的值。你也可以在Console中切换到Debug Console,交互式地输入命令查看变量(如输入df.head())。
通过调试,你可以清晰地看到genre_split_df、concat_df、stacked_series等中间变量的具体内容,从而深刻理解数据是如何一步步被重构的。
📝 动手练一练
- 概念辨析:在数据重构任务中,我们使用了
stack()方法。请问,与stack()相对应的、将“长格式”数据变回“宽格式”的Pandas方法是什么?它通常在什么场景下使用?
👉 点击查看参考答案
与stack()相对应的方法是unstack()。
stack(): 将列索引的层级“堆叠”到行索引上,使数据变“长”(行数增加,列数减少)。unstack(): 将行索引的层级“展开”到列索引上,使数据变“宽”(行数减少,列数增加)。
unstack()常用于将多级索引的聚合结果(例如,按年份和产品类别分组后的销售额)转换为更易读的交叉表形式,便于制作数据透视表或热力图。
- 动手实践:假设我们有一个微型数据集
df_test,包含'user'和'skills'两列,其中'skills'列是用逗号分隔的技能列表(如'Python,SQL,Excel')。请仿照电影类型分析的步骤,编写代码将其重构为每行一个(user, skill)的格式,并统计每种技能被多少用户掌握。
👉 点击查看参考答案
import pandas as pd
# 构造数据
df_test = pd.DataFrame({
'user': ['Alice', 'Bob', 'Charlie'],
'skills': ['Python,SQL', 'Python,Excel', 'SQL,Excel,Tableau']
})
# 重构数据(关键:合并后先剔除原始 skills 列,只保留 user 与各拆分列,
# 否则未拆分的组合串(如 'Python,SQL')也会被堆叠进结果、污染统计)
skills_split = df_test['skills'].str.split(',', expand=True)
skills_split.columns = [f'skill_{i}' for i in range(skills_split.shape[1])]
cols_to_keep = ['user'] + list(skills_split.columns)
concat_skills = pd.concat([df_test, skills_split], axis=1)[cols_to_keep].set_index('user')
long_format_series = concat_skills.stack()
long_format_df = long_format_series.to_frame(name='skill').reset_index().drop(columns='level_1')
print("重构后的数据:")
print(long_format_df.to_string(index=False))
# 统计技能
skill_count = long_format_df.groupby('skill').size().sort_values(ascending=False)
print("\n技能掌握人数统计:")
print(skill_count.to_string())重构后的数据:
user skill
Alice Python
Alice SQL
Alice NaN
Bob Python
Bob Excel
Bob NaN
Charlie SQL
Charlie Excel
Charlie Tableau
技能掌握人数统计:
skill
Excel 2
Python 2
SQL 2
Tableau 1本章小结
通过本节电影数据分析实战,我们综合运用了Pandas高阶数据处理的核心技能:
- 灵活的分组聚合:使用
groupby配合as_index=False和sum()、size()等聚合函数,轻松实现按单列或多列进行统计汇总。 - 复杂数据重构:面对一行多值的数据(如用分隔符连接的类别),掌握了标准处理流程:
str.split→ 重命名列 →concat合并 →set_index→stack堆叠 → 最终整理。这是数据分析中非常经典的“宽表变长表”操作。 - 调试的重要性:对于复杂的多步数据转换,利用IDE的调试工具(如PyCharm Debugger)逐步执行、查看中间变量,是理解代码逻辑、定位错误的必备技能。
- 分析到可视化的桥梁:统计结果(如电影类型频次)以表格呈现时不易比较——这类”类别 + 频次”的结果天然适合条形图,具体绘制方法将在下一章数据可视化中学习。
📋 行动清单
- 尝试在PyCharm或VS Code中,对你编写的
get_genres_data函数(或类似功能函数)设置断点,并使用调试模式运行,观察每一步数据形态的变化。 - 找一份你自己感兴趣的数据(如运动比赛记录、商品销售清单),其中包含用分隔符连接的字段,练习使用本节的数据重构方法对其进行分析。
- 思考:在任务一的票房分析中,如果我想计算每位导演的平均票房,而不是总和,代码应如何修改?如果我想同时看到总和、平均、电影数量三个指标呢?(提示:查阅
agg函数的使用)
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。
- 下载全套课件打包(第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 数据库