查看全课大纲(第 62 / 101 节)
  1. 1.数据分析基本概念
  2. 2.学习数据分析的一般路线
  3. 3.数据分析的流程
  4. 4.数据类型
  5. 5.环境部署(1)
  6. 6.环境部署(2)
  7. 7.课程介绍
  8. 8.TXT文件操作
  9. 9.JSON文件操作
  10. 10.CSV文件操作
  11. 11.Excel文件操作
  12. 12.数据库及SQL常用语法
  13. 13.数据库基本操作
  14. 14.数据库多表连接
  15. 15.实战:欧洲职业足球数据库分析
  16. 16.爬虫简介
  17. 17.URL管理模块
  18. 18.网页下载模块
  19. 19.网页解析模块(1)
  20. 20.网页解析模块(2)
  21. 21.Scrapy简介
  22. 22.Scrapy使用步骤(1)
  23. 23.Scrapy使用步骤(2)
  24. 24.Scrapy使用步骤(3)
  25. 25.Scrapy使用步骤(4)
  26. 26.实战:获取国内城市空气质量指数数据
  27. 27.NumPy和SciPy介绍
  28. 28.多维数组
  29. 29.多维数组操作
  30. 30.NumPy的常用方法
  31. 31.向量化介绍
  32. 32.向量化及通用函数
  33. 33.实战:2016美国大选分析
  34. 34.数据结构-Series
  35. 35.数据结构-DataFrame
  36. 36.数据结构-Index
  37. 37.Series的索引操作
  38. 38.DataFrame的索引操作
  39. 39.索引操作总结
  40. 40.运算与对齐
  41. 41.函数应用操作(1) -- map
  42. 42.函数应用操作 (2) -- apply applymap
  43. 43.文件读写操作
  44. 44.排序操作
  45. 45.数据清洗--处理缺失数据
  46. 46.数据清洗--处理重复数据
  47. 47.数据清洗--替换数据
  48. 48.常用统计方法(1) -- describe quantile
  49. 49.常用统计方法(2) -- sum mean median count
  50. 50.常用统计方法(3) -- max min idxmax idxmin
  51. 51.常用统计方法(4) -- mad var std cumsum
  52. 52.实战:全球食品数据分析
  53. 53.层级索引
  54. 54.分组与聚合介绍
  55. 55.分组操作(1) -- GroupBy对象及常用聚合操作
  56. 56.分组操作(2) -- 自定义分组及聚合操作
  57. 57.透视表介绍
  58. 58.透视表操作
  59. 59.数据规整(1) -- 数据合并concat
  60. 60.数据规整(2) -- 数据连接merge
  61. 61.数据重构(3) -- 数据重构stack unstack
  62. 62.实战:互联网电影资料库分析
  63. 63.探索性数据分析EDA介绍
  64. 64.EDA的目的
  65. 65.EDA常用工具
  66. 66.Matplotlib绘图基本介绍
  67. 67.Matplotlib画布
  68. 68.散点图和柱状图的绘制
  69. 69.直方图的绘制
  70. 70.矩阵绘图
  71. 71.子图的使用
  72. 72.Matplotlib颜色、标记、线型
  73. 73.Matplotlib坐标刻度、标签、图例、标题
  74. 74.Seaborn介绍
  75. 75.数据集分布可视化(1) -- 单变量分布、双变量分布
  76. 76.数据集分布可视化(2) -- 变量关系可视化
  77. 77.类别数据可视化 -- 类别散布图、类别内数据分布、类别内统计图
  78. 78.交互式数据可视化工具Bokeh介绍
  79. 79.Bokeh绘制散点图、柱状图、盒子图、弦图
  80. 80.Bokeh绘制常用图形元素
  81. 81.3D绘图 -- mplot3d
  82. 82.3D曲线可视化
  83. 83.3D散点图可视化
  84. 84.3D柱状图可视化
  85. 85.Pandas绘图
  86. 86.实战:Lending Club借贷数据探索性分析及可视化
  87. 87.机器学习介绍及应用场景
  88. 88.机器学习建模介绍 (1) -- 分类
  89. 89.机器学习建模介绍 (2) -- 回归
  90. 90.机器学习建模介绍 (3) -- 聚类
  91. 91.机器学习分类
  92. 92.机器学习工具scikit-learn
  93. 93.使用scikit-learn的流程
  94. 94.数据集准备及划分
  95. 95.模型选择
  96. 96.数据预处理及特征工程
  97. 97.过拟合与欠拟合
  98. 98.模型调参介绍
  99. 99.模型调参方法
  100. 100.模型测试及评价
  101. 101.实战:通过移动设备行为数据预测性别和年龄

实战:互联网电影资料库分析

约 24 分钟

📺 正在播放小象学院官方高清录播(支持倍速与清晰度调节)

互联网电影资料库分析实战

小象实战讲义 · Python数据分析实战

在掌握了Pandas的分组聚合、数据规整等核心高阶功能后,本节我们将通过一个完整的实战项目——互联网电影资料库数据分析,来综合运用所学知识。我们将面对真实数据中常见的挑战:缺失值处理、多维度分组统计,以及如何将一行包含多个类别的数据(如电影类型)拆分为多行,以便进行更精细的分析。

💡 核心导读

  • 任务一:分组统计票房收入:学习如何按导演、主演以及导演-主演组合进行分组,并计算票房总和,找出最具票房号召力的个人与组合。
  • 任务二:电影类型数据分析:这是本节的难点与重点。我们将学习如何将“动作|冒险|奇幻|科幻”这样的单行字符串数据,通过str.split、concat和stack等方法,重构为每行一个类型的“长格式”数据,从而便于统计各类电影的分布。
  • 调试技巧:在复杂的数据转换过程中,我们将演示如何使用PyCharm的调试模式,逐行跟踪代码执行,查看中间变量,这对于理解数据流和排查错误至关重要。
  • 从分析到可视化:统计结果(如电影类型频次)天然适合用条形图呈现——本节先把统计表做对,具体画图留到下一章的数据可视化内容中实践,为它做好铺垫。

项目概述与数据初探

本项目使用一个电影数据集,包含每部电影的名称、导演、主演、类型、票房收入等属性。我们的核心任务有两个:

  1. 基于分组统计票房收入:例如,统计每位导演或演员的总票房,或分析导演与演员的最佳票房组合。
  2. 基于电影类型的数据分析:统计数据集中各类电影(如剧情、喜剧、动作等)的数量分布。

首先,我们加载数据并查看其基本情况。注意,真实数据往往包含缺失值,这是数据分析的第一步——数据清洗。

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”类别。

目标:将一行包含多个类型的数据,拆分为多行,每行只包含一个类型,同时保留电影的其他信息(如标题)。这个过程称为数据重构或从宽格式变为长格式。

思路:

  1. 分割:使用Series.str.split('|', expand=True)将genres列按分隔符拆分成多列。
  2. 合并与筛选:将拆分出的多列与原始数据(如电影标题)横向合并。
  3. 堆叠:使用stack()方法将多列“堆叠”成一列,即将列转换为行索引的下一级,从而实现一行变多行。
  4. 分组统计:对最终的单列类型数据进行分组计数。

下面我们通过一个完整的、可自包含的代码块来演示这一过程,并模拟使用调试模式查看中间结果。

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的调试模式。这里简述其核心步骤,供你在自己的环境中实践:

  1. 设置断点:在代码行号左侧点击,出现红点,程序运行到此处会暂停。
  2. 以Debug模式运行:在PyCharm中右键点击文件或代码区域,选择Debug '文件名',而非普通的Run。
  3. 控制执行流程:
    • Step Over (F8):执行当前行,但不进入函数内部。
    • Step Into My Code (Alt+Shift+F7):执行当前行,如果调用的是自己编写的函数,则进入该函数内部。
    • Resume Program (F9):继续运行,直到下一个断点或程序结束。
  4. 查看变量:在Debug工具窗口的Variables面板,可以查看当前作用域内所有变量的值。你也可以在Console中切换到Debug Console,交互式地输入命令查看变量(如输入df.head())。

通过调试,你可以清晰地看到genre_split_df、concat_df、stacked_series等中间变量的具体内容,从而深刻理解数据是如何一步步被重构的。

📝 动手练一练

  1. 概念辨析:在数据重构任务中,我们使用了stack()方法。请问,与stack()相对应的、将“长格式”数据变回“宽格式”的Pandas方法是什么?它通常在什么场景下使用?
👉 点击查看参考答案

与stack()相对应的方法是unstack()。

  • stack(): 将列索引的层级“堆叠”到行索引上,使数据变“长”(行数增加,列数减少)。
  • unstack(): 将行索引的层级“展开”到列索引上,使数据变“宽”(行数减少,列数增加)。

unstack()常用于将多级索引的聚合结果(例如,按年份和产品类别分组后的销售额)转换为更易读的交叉表形式,便于制作数据透视表或热力图。

  1. 动手实践:假设我们有一个微型数据集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高阶数据处理的核心技能:

  1. 灵活的分组聚合:使用groupby配合as_index=False和sum()、size()等聚合函数,轻松实现按单列或多列进行统计汇总。
  2. 复杂数据重构:面对一行多值的数据(如用分隔符连接的类别),掌握了标准处理流程:str.split → 重命名列 → concat合并 → set_index → stack堆叠 → 最终整理。这是数据分析中非常经典的“宽表变长表”操作。
  3. 调试的重要性:对于复杂的多步数据转换,利用IDE的调试工具(如PyCharm Debugger)逐步执行、查看中间变量,是理解代码逻辑、定位错误的必备技能。
  4. 分析到可视化的桥梁:统计结果(如电影类型频次)以表格呈现时不易比较——这类”类别 + 频次”的结果天然适合条形图,具体绘制方法将在下一章数据可视化中学习。

📋 行动清单

  • 尝试在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 数据库
    下载