Python Dash 进阶教程:打造互动式数据可视化仪表盘

第二个仪表盘:统计电影类型数量,探究类型分布规律

欢迎来到 “Python Dash 进阶教程”!

在之前的教程中,我们已经学习了如何利用 Dash 构建一个基础的 IMDB 数据仪表盘,了解了 Dash 的核心概念和一些常用功能。而现在,随着对 Dash 组件和 Python 的更深入了解,接下来我将带你一步步进入一个更复杂、更具互动性的数据可视化仪表盘。

我们还是使用之前的数据:IMDB Movie Dataset

在分析电影数据时,电影类型分布是一个重要的维度。例如,哪些类型的电影数量最多?哪些类型是近几年比较热门的?但在原始数据中,一个电影可能属于多个类型(如既是动作片又是喜剧片)。为了准确统计各类型的数量,我们需要对数据进行处理。

我们将通过以下步骤完成:

  1. 加载数据并清洗无关列。
  2. 处理电影收入和类型数据,拆分多类型为独立行。
  3. 统计每种类型的电影数量。

第一步:加载数据并移除无用列

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 中的组件发生变动(即用户产生点击等交互动作时),回调函数会自动调用更新函数,将用户交互后所要呈现的图返回到页面上,实现页面与用户的实时交互。
回调与更新的工作原理:

  1. 用户在复选框中选择或取消某些类型;
  2. 复选框的值变化触发回调函数;
  3. 回调函数调用更新函数,将过滤后的数据重新绘制成柱状图和饼图;
  4. 页面实时更新,展示新的图表内容。
    最后,我们运行该应用:
# 运行应用
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'<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值