数据分析(案例实战)

TMDB Top300 电影榜单可视化报表设计思路------一块画布上的四宫格

写在前面

学完折线图、柱状图、饼图,到了第十章实战:基于 movies.csv 里的 TMDB Top300 数据,在一张画布上搞个 2×2 四宫格,给业务部门做份数据报表。

这篇记录一下我是怎么从零把这张报表"想"出来又"敲"出来的。


1. 图表选型与画布骨架

动手前先定图型,四个维度性质不同,配图得对症下药:

  • 时间趋势(每年上映数量) → 折线图

  • 分类对比(语言、类型) → 柱状图

  • 占比构成(评分分布) → 饼图

骨架部分,直接用 plt.subplots() 切出四个独立的坐标系,后续画图互不干扰:

import pandas as pd

import matplotlib.pyplot as plt

配置中文显示,解决负号乱码

plt.rcParams'font.sans-serif' = 'SimHei'

plt.rcParams'axes.unicode_minus' = False

创建 2×2 子图画布,解开成四个对象方便单点操作

fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(14, 10), dpi=100)

ax1, ax2 = axes0 # 第一行左右

ax3, ax4 = axes1 # 第二行左右

movies = pd.read_csv("data/movies.csv")

2. 数据清洗:能用数据救的,绝不轻易 dropna

"年份"列有缺失,最省事的是直接 dropna(),但这会让那一年的统计数据失真。既然有"上映时间",年份完全是可以截取出来的。我选择补全:

截取"上映时间"前4位作为年份,格式不对的给 NaN(不报错)

year_from_date = pd.to_numeric(movies"上映时间".astype(str).str:4, errors="coerce")

只填补缺失值,原有的正确年份不动

movies"年份" = movies"年份".fillna(year_from_date)

两步校验:先查异常(<0或>3000),再查缺失是否清零

print("异常年份数:", len(movies(movies\["年份" < 0) | (movies"年份" > 3000)]))

print("填充后缺失数:", movies"年份".isnull().sum())

验证通过才转 int,否则 NaN 转 int 会报错

movies"年份" = movies"年份".astype(int)

3. 折线图:连续年份 + 补零,专治"断线"

groupby 统计有一个坑:某一年没电影上榜,那年就不会出现在索引里。直接画图,折线会"跳接"。

解决办法是强行把 X 轴和 Y 轴拉齐:X 轴用 range 生成完整年份序列,Y 轴去查字典,查不到直接补 0。

year_counts = movies.groupby("年份")"电影名".count()

min_year, max_year = year_counts.index.min(), year_counts.index.max()

x_years = list(range(min_year, max_year + 1)) # 连续X轴

y_counts = year_counts.get(year, 0) for year in x_years # 没数据补0

ax1.plot(x_years, y_counts, color="green")

ax1.set_title("每一年上映电影数量变化", fontsize=14)

ax1.set_xticks(range(min_year, max_year + 1, 8)) # 每隔8年标一个,防拥挤

ax1.set_ylim(0, 30)

ax1.grid(True, linestyle="--", alpha=0.5)

4. 柱状图:排序与"拆行"魔法

语言图 :核心是让业务一眼看明白谁排第一,所以加上 sort_values 降序,并用 bar_label 把数字标在柱顶。网格只开横向(axis="y"),免得竖线穿过柱子辣眼睛。

lang_counts = movies.groupby("语言")"电影名".count().sort_values(ascending=False)

ax2.bar(lang_counts.index, lang_counts.values, color="skyblue", width=0.6)

ax2.bar_label(ax2.containers0)

ax2.grid(True, axis="y", linestyle="--", alpha=0.5)

类型图 :一部电影可能有多个类型(如"剧情,犯罪")。如果直接 groupby,它会变成一个独立类型,统计就废了。

用 str.split(",").explode() 把一行炸成多行,一部电影有几个类型就计入几次,最后 value_counts 统计。

genre_series = movies"类型".str.split(",").explode().str.strip()

genre_counts = genre_series.value_counts()

ax3.bar(genre_counts.index, genre_counts.values, color="orange", width=0.6)

ax3.bar_label(ax3.containers0)

ax3.tick_params(axis="x", rotation=45) # 标签倾斜防重叠

ax3.grid(True, axis="y", linestyle="--", alpha=0.5)

5. 饼图:统计与展示分离

"评分"列存的是 87、86,实际代表 8.7、8.6。如果直接用原始值当标签会误导读者。

所以分组用原始整数做键(干净),生成标签时再除以 10 还原,顺手用 int(score) 消除类型检查器的警告。

rating_counts = movies.groupby("评分")"电影名".count()

rating_labels = \[\]

for score in rating_counts.index:

display_score = int(score) / 10

rating_labels.append(f"{display_score:.1f}")

ax4.pie(rating_counts.values, labels=rating_labels, autopct="%1.1f%%", startangle=90)

ax4.set_title("不同电影评分占比统计", fontsize=14)

6. 收尾与布局防重叠

加总标题时最怕跟子图标题打架,tight_layout 的 rect 参数派上用场,把图区压缩到画布下方 94%,给上方留出物理空间。

fig.suptitle("TMDB Top300 电影榜单数据统计", fontsize=20, fontweight="bold", y=0.98)

rect 给总标题预留空间,且必须传元组,传列表会报警告

plt.tight_layout(rect=(0, 0, 1, 0.94))

plt.show()

7. 回头看:沉淀的通用流水线

这个四宫格写完,其实摸出了一套通用的数据分析流水线:

读数据 → 查缺失 → 补全验证 → 按口径统计(多值先拆) → 补齐坐标轴 → 选图绘制 → 局部美化 → 布局收尾。

以后换个数据集(比如销售、天气),变的只是列名和具体业务口径,这套流水线直接套用就行。画图本身是小事,怎么把脏数据洗干净、怎么把统计口径搞对 ,才是报表能不能用的关键。

相关推荐
霍格沃兹测试学院-小舟畅学3 小时前
京东JoyCode公开Testing Agent后,仓库级回归该怎样设计?
人工智能·数据挖掘·回归
追风少年ii4 小时前
文献分享--多种感染在转录水平重编程肠上皮及上皮-免疫细胞相互作用
数据分析·空间·单细胞
Asa121385 小时前
NC|泥炭地微生物群落次级代谢基因簇高度多样但原位表达有限
数据分析
会议咨询5 小时前
2026年应用经济学、数据分析与社会发展国际会议(ADSD 2026)
数据分析·应用经济学·社会发展
机器学习之心5 小时前
给地铁牵引系统做一次“体检“:灰色聚类 + 正态云模型的MATLAB完整实现
matlab·数据挖掘·聚类
成为深度学习高手6 小时前
xLSTM-Mixer:用标量记忆同时混合时间与变量,把循环网络带回多元时序预测
人工智能·python·深度学习·数据挖掘·transformer
SL_staff20 小时前
告别报表重复开发:JVS-BI 模板市场的参数化继承实践
java·数据分析·数据可视化
SelectDB21 小时前
时间旅行与快照能力速查:Doris 侧三条落地路径
大数据·数据库·数据分析