第二个仪表盘:统计电影类型数量,探究类型分布规律
欢迎来到 “Python Dash 进阶教程”!
在之前的教程中,我们已经学习了如何利用 Dash 构建一个基础的 IMDB 数据仪表盘,了解了 Dash 的核心概念和一些常用功能。而现在,随着对 Dash 组件和 Python 的更深入了解,接下来我将带你一步步进入一个更复杂、更具互动性的数据可视化仪表盘。
我们还是使用之前的数据:IMDB Movie Dataset
在分析电影数据时,电影类型分布是一个重要的维度。例如,哪些类型的电影数量最多?哪些类型是近几年比较热门的?但在原始数据中,一个电影可能属于多个类型(如既是动作片又是喜剧片)。为了准确统计各类型的数量,我们需要对数据进行处理。
我们将通过以下步骤完成:
- 加载数据并清洗无关列。
- 处理电影收入和类型数据,拆分多类型为独立行。
- 统计每种类型的电影数量。
第一步:加载数据并移除无用列
import pandas as pd
# 加载 IMDB 数据
df = pd.read_csv('imdb_movie_dataset.csv')
# 删除无关列 'Rank' 和 'Metascore',这些信息不影响我们的类型统计。
df.drop(['Rank', 'Metascore'], axis=1, inplace=True)
第二步:清洗收入列
电影收入存储在 ‘Revenue (Millions)’ 列中,但有些可能缺失或格式异常。我们需要将其转换为数值类型,并清理缺失值:
# 转换收入为数值类型,无法转换的部分变为 NaN
df['Revenue (Millions)'] = pd.to_numeric(df['Revenue (Millions)'], errors='coerce')
# 删除缺失值所在行,确保数据完整性
df.dropna(inplace=True)
第三步:处理电影类型
每个电影可能属于多个类型,我们需要将这些类型拆分成独立行:
# 拆分 'Genre' 列,将逗号分隔的类型转换为列表
df['Genre'] = df['Genre'].str.split(',')
# 使用 explode 将类型拆分成多行
df = df.explode('Genre')
# 去掉类型前后的空格,确保干净整齐
df['Genre'] = df['Genre'].str.strip()
第四步:统计类型数量
通过 value_counts() 方法,统计每种类型的电影数量:
# 按类型统计电影数量
genre_counts = df['Genre'].value_counts()
通过上述操作,我们已经成功统计出了每种类型的电影数量,我们可以运行以下代码查看结果:
# 打印结果
print(genre_counts)
前期的数据处理工作都已完成,接下来,我们将开始用dash实现仪表盘的制作与交互。
首先我们初始化 Dash 应用
from dash import dcc, html, Dash
# 创建 Dash 应用
app = Dash(__name__)
页面布局定义了仪表盘的“骨架”,决定页面内容的排列方式和显示效果。在这个仪表盘中,我们需要添加以下组件:
• 标题:使用 html.H1 设置。
• 复选框筛选器:使用 dcc.Checklist 创建,用户可以选择需要分析的电影类型。
• 图表区域:通过 dcc.Graph 添加柱状图和饼图。
# 定义页面布局
app.layout = html.Div([
# 页面标题
# html.H1 用于创建一级标题,用来描述仪表盘的主题
html.H1("Dashboard for Movie Genre Count Statistics", # 标题内容
style={
'text-align': 'center'}), # 设置标题居中显示
# 类型筛选器
# Checklist 是一个复选框组件,允许用户选择多个选项
dcc.Checklist(
id='genre-checklist', # 组件的唯一 ID,用于回调函数引用
options=[{
'label': genre, 'value': genre} for genre in genre_counts.index], # 动态生成所有电影类型
value=genre_counts.index.tolist(), # 默认选中所有类型
inline=True), # 设置选项横向排列
# 图表容器,用于展示两个图表
html.Div([
# 柱状图:展示电影类型的数量
# dcc.Graph 是一个用于绘制交互式图表的组件
dcc.Graph(id='bar-chart', # 图表的唯一 ID,用于后续回调函数
style={
'display': 'inline-block', 'width': '49%'}), # 设置柱状图宽度为页面的一半,并与饼图并排显示
# 饼图:展示电影类型的占比
dcc.Graph(id='pie-chart', # 图表的唯一 ID,用于后续回调函数
style={
'display': 'inline-block', 'width': '49%'}) # 设置饼图宽度为页面的一半,并与柱状图并排显示
]) # 结束图表容器
]) # 结束页面布局
PS:这里只是先定义了页面的布局,包括了大标题、用户交互的选项及位置、 两个子图的图名、位置,具体的绘图功能在接下来的回调函数中实现,这也是 dash 的灵魂所在。
Dash 回调函数与动态交互
在 Dash 中,回调函数是实现页面动态交互的核心机制。简单来说,回调函数会监听页面中某些组件的变化(如复选框被点击),并通过更新函数自动更新页面内容。这里@app.callback()是 dash 回调函数的标准写法,各位记住就好,不必深究。Output()以及 Input()中,填写的是组件的 id,也就是上文中 dcc.Checklist。这里提到的 id=‘genre-checklist’, checklist是一类组件的名称,表示可复选的按钮列,而 genre-checklist 是该类的一个具体的组件的名字,二者的关系就像是水果与苹果一样,一个是抽象的类型概念,一个是具体的实例物体。
from dash.dependencies import Input, Output
import plotly.express as px
# 回调函数
# 使用 @app.callback 装饰器定义回调函数,绑定输入与输出
@app.callback(
# 定义两个输出组件,分别为柱状图和饼图
[Output('bar-chart', 'figure'), Output('pie-chart', 'figure')],
# 定义一个输入组件,当复选框的值发生变化时触发回调
[Input('genre-checklist', 'value')])
在 dash 中,一个回调函数之后必定跟随一个更新函数(两者需要同时运行),这两个函数是耦合出现的,更新函数的作用是根据用户的交互更新页面内容。更新函数如下:
# 更新函数
# 该函数将在回调触发时自动调用,并生成新的图表
def update_charts(selected_genres):
# 根据用户选择的电影类型,动态更新柱状图和饼图。
# 过滤数据:保留用户选择的类型数据
filtered_df = df[df['Genre'].isin(selected_genres)]
# 统计类型数量:计算每种类型的电影数量
filtered_counts = filtered_df['Genre'].value_counts()
# 创建柱状图
bar_chart = px.bar(
filtered_counts, # 数据
x=filtered_counts.index, # X轴为电影类型
y=filtered_counts.values, # Y轴为每种类型的电影数量
title="Movie Genre Count Statistics (Bar Chart)",
labels={
'y': 'Number of Movies', 'x': 'Movie Genre'}) # 图表标题
# 创建饼图
pie_chart = px.pie(
filtered_counts, # 数据
names=filtered_counts.index, # 饼图的名称为电影类型
values=filtered_counts.values, # 饼图的大小为每种类型的数量
title="Movie Genre Count Statistics (Pie Chart)") # 图表标题
# 返回生成的两个图表
return bar_chart, pie_chart
在更新函数里面,我们基于 plotly.express 库函数编写了柱状图以及饼状图的绘制函数,并返回两个图。与上一章的绘图函数不同,这里的绘图函数不直接被调用,而是与回调函数绑定,当回调函数的 Input 中的组件发生变动(即用户产生点击等交互动作时),回调函数会自动调用更新函数,将用户交互后所要呈现的图返回到页面上,实现页面与用户的实时交互。
回调与更新的工作原理:
- 用户在复选框中选择或取消某些类型;
- 复选框的值变化触发回调函数;
- 回调函数调用更新函数,将过滤后的数据重新绘制成柱状图和饼图;
- 页面实时更新,展示新的图表内容。
最后,我们运行该应用:
# 运行应用
if __name__ == '__main__':
app.run_server(debug=True)
我们通过点击上方的按钮,可以选择自己想要查看的电影类型及占比,如我们想查看除去Drama类之后,实现的电影类型的数量及占比。
为了让仪表盘更美观并支持动态主题切换,我们使用了 dash_bootstrap_templates 库。其中,ThemeChangerAIO:提供动态主题切换的全局组件。template_from_url:从指定的 URL 加载主题模板,用于Plotly图表。
首先,在终端或命令行运行以下命令安装 dash-bootstrap-templates:
!pip install dash-bootstrap-templates
以下是需要被改动的代码(需要改动的部分用注释标注):
from dash_bootstrap_templates import ThemeChangerAIO, template_from_url # 引入动态主题相关模块
import dash_bootstrap_components as dbc # 引入 Dash 的 Bootstrap 组件库
# 创建 Dash 应用,并引入 Bootstrap 外部样式表
# external_stylesheets 中添加了两个样式表:
# 1. dbc.themes.BOOTSTRAP:基础的 Bootstrap 主题
# 2. dbc.icons.FONT_AWESOME:用于提供图标支持
app = Dash(__name__,
external_stylesheets=[dbc.themes.BOOTSTRAP,
dbc.icons.FONT_AWESOME]
)
# 使用 dbc.Container 替代原来的 html.Div,以支持 Bootstrap 样式
# Container 提供了响应式的布局容器,可以更好地适应不同屏幕尺寸
app.layout = dbc.Container([
html.H1("Dashboard for Movie Genre Count Statistics"),
# 添加主题切换组件 ThemeChangerAIO
# aio_id:组件的唯一标识符
# radio_props:设置单选按钮的属性,默认选中 Bootstrap 主题
# button_props:设置按钮的样式,这里使用 primary 颜色
ThemeChangerAIO(
aio_id="theme",
radio_props={
"value": dbc.themes.BOOTSTRAP},
button_props={
"color": "primary"}
),
dcc.Checklist(
id='genre-checklist',
options=[{
'label': genre, 'value': genre} for genre in genre_counts.index],
value=genre_counts.index.tolist(),
inline=True
),
html.Div([
dcc.Graph(id='bar-chart', style={
'display': 'inline-block', 'width': '49%'}),
dcc.Graph(id='pie-chart', style={
'display': 'inline-block', 'width': '49%'})
])
])
# 在回调函数中添加主题切换的输入
# 除了原有的 genre-checklist 输入外,还添加了主题切换的输入
# ThemeChangerAIO.ids.radio("theme") 用于获取当前选择的主题
@app.callback(
[Output('bar-chart', 'figure'), Output('pie-chart', 'figure')],
[Input('genre-checklist', 'value'),
Input(ThemeChangerAIO.ids.radio("theme"), 'value')]
)
# 在更新函数中添加主题参数并应用到图表
# theme 参数会接收当前选择的主题值
def update_charts(selected_genres, theme):
filtered_df = df[df['Genre'].isin(selected_genres)]
filtered_counts = filtered_df['Genre'].value_counts()
# 在创建图表时应用选择的主题
# template_from_url(theme) 将选择的主题转换为 Plotly 可用的模板
bar_chart = px.bar(
filtered_counts,
x=filtered_counts.index,
y=filtered_counts.values,
title="Movie Genre Count Statistics (Bar Chart)",
template=template_from_url(theme), # 应用主题到柱状图
labels={
'y': 'Number of Movies'<