Press "Enter" to skip to content

使用Spark和Plotly Dash开发交互式和洞察性仪表板

Python下用于Web应用程序的交互式大规模数据可视化

作者照片

1. 引言

云数据湖被企业组织广泛采用作为可扩展和低成本的所有类型(结构化和非结构化)数据的存储库。分析大规模数据集以有效获取数据驱动决策的有意义见解存在许多挑战。一项挑战是数据集大小往往过大,无法适应一台机器。通常需要一组服务器来处理大数据集。另一个挑战是如何在与相关客户/股东的任何地方轻松和经济有效地共享数据分析结果。

本文使用与[1]相同的开源数据集,展示了一种基于开源的Web应用程序框架,使用Spark[2][3]和Plotly Dash[4]开发交互式和洞察力的仪表板。此框架允许我们在服务器上分析和可视化大规模数据集,并在任何地方共享数据分析和可视化结果作为仪表板。

如图1所示,新的Web应用程序框架由三个主要组件组成:

  • Spark SQL服务(例如DataFrame)用于分布式数据处理(请参见第2节)
  • Plotly绘图服务用于创建数据可视化图表作为仪表板(请参见第3节)
  • Dash Web服务用于服务器端Plotly绘图服务与仪表板客户端之间的交互(请参见第4节)
图1:高级应用程序框架架构。

2. 分布式数据处理的Spark SQL服务

如[2]中所述,PySpark(用于Spark的Python API)可轻松用于从Cloud数据湖,例如AWS S3中读取csv文件。为简单起见,在本文中假设数据集csv文件train_data.csv [1]可在本地计算机上使用而不损失一般性。

以下代码用于将csv文件作为Spark SQL DataFrame加载到内存中:

import pysparkfrom pyspark.sql import SparkSessionspark = SparkSession.builder.appName('hospital-stay').getOrCreate()spk_df = spark.read.csv('./data/train_data.csv',                         header = True, inferSchema = True)

加载数据后,可以创建全局临时视图以方便动态数据查询,如下所示。

spk_df.createOrReplaceTempView("dataset_view")

一旦创建了数据集视图,我们可以使用Spark SQL像从数据库中查询常规数据一样查询数据。例如,以下代码查询了年龄在[21,30]范围内的所有人员的所有行。

age = "21-30"sdf = spark.sql(f"SELECT * FROM dataset_view WHERE Age=='{age}'")

为了使用Plotly从Spark DataFrame sdf创建数据可视化图表,我们必须将其转换为Pandas DataFrame pdf,因为Plotly不直接支持Spark DataFrame。

pdf = sdf.toPandas()

3. 用于创建数据可视化图表的Plotly

Plotly支持生成许多不同类型的图表。其中一些适用于从连续数字特征创建图表,而其他适用于从离散分类特征创建图表。

本文使用Plotly Express库来创建以下常见图表以进行演示。

  • 用于数字特征的图表:散点图、直方图和线图
  • 用于分类特征的图表:条形图、直方图、线图和饼图

3.1 用于数字特征的图表

如前所述,数值特征的常见图形有三种:

  • 散点图
  • 直方图
  • 折线图

给定一对数值特征,散点图使用每对特征值作为坐标在二维平面上绘制点。例如,下图显示了21到30岁人群的两个数值特征患者ID和入院押金的散点图。特征入院类型用于颜色编码。

Figure 2: Sample scatter plot for a pair of numeric features.

假设仪表板用户选择了年龄范围[21,30],数值特征对x=patientid和y=Admission_Deposit,颜色编码特征=入院类型,则以下语句创建了上述散点图。

fig = px.scatter(dff, x = x, y = y, color = color_feature)

同样,以下语句用于创建相同数据的直方图:

fig = px.histogram(dff, x = x, y = y, color = color_feature)
Figure 3: Sample histogram for a pair of numeric features.

为了完整起见,以下语句用于创建折线图。

fig = px.ine(dff, x = x, y = y, color = color_feature)
Figure 4: Sample line chart for a pair of numeric features.

尽管我们可以轻松创建折线图,但是像上面那样的折线图并不会揭示有用的见解。良好使用折线图的方法是将其应用于以有意义的方式排序的数据集,例如按时间排序的数据序列或按计数排序的特征值列表,如第3.2节所示。

3.2 分类特征的图形

本小节展示了分类特征的四个常见图形:

  • 条形图
  • 直方图
  • 折线图
  • 饼图

假设仪表板用户选择了年龄= [21-30],分类特征=停留,颜色=purple,图形样式=条形,可以使用以下代码生成下面的条形图。

dff = spark.sql(f"SELECT * FROM dataset_view WHERE Age=='{age}'").toPandas()vc = dff[feature].value_counts()fig = px.bar(vc, x = vc.index, y = vc.values)

我注意到直方图与分类特征值计数的条形图具有相同的结果。

Figure 5: Sample bar chart for value counts of a categorical feature.

我们可以通过选择图形样式=线来创建以下折线图:

fig = px.line(vc, x = vc.index, y = vc.values)
Figure 6: Sample line chart for value counts of a categorical feature.

如前所述,线图适用于可视化分类特征的值计数。

以下代码是为 Stay 的相同分类特征创建饼图的代码。

fig = px.pie(vc, x = vc.index, y = vc.values)

该饼图使用自动颜色编码而不是选择的紫色。

Figure 7: Sample pie chart for value counts of a categorical feature.

4. 用于交互式数据可视化的Dash

前一节介绍了如何在Spark服务器集群上使用Plotly Express库创建不同类型的图表仪表板。本节介绍如何使用Dash共享仪表板,并允许客户端使用仪表板以交互方式可视化数据。

可以按照以下步骤开发Web应用程序的一个页面仪表板:

  • 步骤1:导入Dash库模块
  • 步骤2:创建Dash应用程序对象
  • 步骤3:定义HTML页面的仪表板布局
  • 步骤4:定义回调函数(Web服务端点)
  • 步骤5:启动服务器

4.1 导入Dash库模块

作为第一步,以下是导入Plotly Dash库模块的示例代码,用于本文的演示目的。

import plotly.express as pxfrom dash import Dash, dcc, html, callback, Input, Output

4.2 创建Dash应用程序对象

导入库模块后,下一步是创建Dash应用程序对象:

app = Dash(__name__)

4.3 定义仪表板布局

创建Dash应用程序对象后,我们需要定义一个仪表板布局作为HTML页面。

本文将仪表板HTML页面分为两部分:

  • 第1部分:数值特征的可视化
  • 第2部分:分类特征的可视化

仪表板布局的第1部分定义如下:

app.layout = html.Div([ # 仪表板布局    html.Div([ # 第1部分        html.Div([            html.Label(['Age:'], style={'font-weight': 'bold', "text-align": "center"}),            dcc.Dropdown(                ['0-10', '11-20', '21-30', '31-40', '41-50',                 '51-60', '61-70', '71-80', '81-90', '91-100',                'More than 100 Days'],                value='21-30',                id='numerical_age'            ),        ],        style={'width': '20%', 'display': 'inline-block'}),        html.Div([            html.Label(['Numerical Feature x:'], style={'font-weight': 'bold', "text-align": "center"}),            dcc.Dropdown(                ['patientid',                  'Hospital_code',                 'City_Code_Hospital'],                value='patientid',                id='axis_x',            )        ], style={'width': '20%', 'display': 'inline-block'}),        html.Div([            html.Label(['Numerical Feature y:'], style={'font-weight': 'bold', "text-align": "center"}),            dcc.Dropdown(                ['Hospital_code',                 'Admission_Deposit',                 'Bed Grade',                 'Available Extra Rooms in Hospital',                 'Visitors with Patient',                 'Bed Grade',                 'City_Code_Patient'],                value='Admission_Deposit',                id='axis_y'            ),        ], style={'width': '20%', 'display': 'inline-block'}),        html.Div([            html.Label(['Color Feature:'], style={'font-weight': 'bold', "text-align": "center"}),            dcc.Dropdown(                ['Severity of Illness',                 'Stay',                 'Department',                 'Ward_Type',                 'Ward_Facility_Code',                 'Type of Admission',                 'Hospital_region_code'],                value='Type of Admission',                id='color_feature'            ),        ], style={'width': '20%', 'display': 'inline-block'}),        html.Div([            html.Label(['Graph Style:'], style={'font-weight': 'bold', "text-align": "center"}),            dcc.Dropdown(                ['scatter',                 'histogram',                 'line'],                value='histogram',                id='numerical_graph_style'            ),        ], style={'width': '20%', 'float': 'right', 'display': 'inline-block'})    ],     style={        'padding': '10px 5px'    }),    html.Div([        dcc.Graph(id='numerical-graph-content')    ]),......

图2、图3和图4是使用仪表板布局的第一部分创建的。

仪表板布局的第二部分定义如下:

......html.Div([ # Part 2        html.Div([            html.Label(['年龄:'], style={'font-weight': 'bold', "text-align": "center"}),            dcc.Dropdown(                ['0-10', '11-20', '21-30', '31-40', '41-50',                 '51-60', '61-70', '71-80', '81-90', '91-100',                '100天以上'],                value='21-30',                id='categorical_age'            ),        ],        style={'width': '25%', 'display': 'inline-block'}),        html.Div([            html.Label(['分类特征:'], style={'font-weight': 'bold', "text-align": "center"}),            dcc.Dropdown(                ['病情严重程度',                 '住院时间',                 '科室',                 '病房类型',                 '病房设施代码',                 '入院类型',                 '医院地区编码'],                value='住院时间',                id='categorical_feature'            ),        ],         style={'width': '25%', 'display': 'inline-block'}),        html.Div([            html.Label(['颜色:'], style={'font-weight': 'bold', "text-align": "center"}),            dcc.Dropdown(                ['红色',                 '绿色',                 '蓝色',                 '橙色',                 '紫色',                 '黑色',                 '黄色'],                value='蓝色',                id='categorical_color'            ),        ],         style={'width': '25%', 'display': 'inline-block'}),        html.Div([            html.Label(['图形样式:'], style={'font-weight': 'bold', "text-align": "center"}),            dcc.Dropdown([                 '直方图',                 '条形图',                 '折线图',                 '饼图'],                value='条形图',                id='categorical_graph_style'            ),        ],         style={'width': '25%', 'float': 'right', 'display': 'inline-block'})    ],     style={        'padding': '10px 5px'    }),    html.Div([        dcc.Graph(id='categorical-graph-content')    ])]) # 仪表板布局结束

图5、图6和图7是使用仪表板布局的第二部分创建的。

4.4 定义回调函数

仪表板布局只创建一个仪表板的静态 HTML 页面。必须定义回调函数(即 Web 服务端点),以便将仪表板用户的操作作为 Web 服务请求发送到服务器端回调函数。换句话说,回调函数使仪表板用户与服务器端仪表板 Web 服务之间实现交互,例如在用户请求时创建新的图形(例如,选择下拉列表选择项)。

本文为仪表板布局的两个部分定义了两个回调函数。

仪表板布局第一部分的回调函数定义如下:

@callback(    Output('numerical-graph-content', 'figure'),    Input('axis_x', 'value'),    Input('axis_y', 'value'),    Input('numerical_age', 'value'),    Input('numerical_graph_style', 'value'),    Input('color_feature', 'value'))def update_numerical_graph(x, y, age, graph_style, color_feature):    dff = spark.sql(f"SELECT * FROM dataset_view WHERE Age=='{age}'").toPandas()    if graph_style == 'line':        fig = px.line(dff,                  x = x,                  y = y,                  color = color_feature                )    elif graph_style == 'histogram':        fig = px.histogram(dff,                  x = x,                  y = y,                  color = color_feature                )    else:        fig = px.scatter(dff,                  x = x,                  y = y,                  color = color_feature                )            fig.update_layout(        title=f"{x}与{y}之间的关系",    )    return fig

仪表板布局第二部分的回调函数定义如下:

@callback(    Output('categorical-graph-content', 'figure'),    Input('categorical_feature', 'value'),    Input('categorical_age', 'value'),    Input('categorical_graph_style', 'value'),    Input('categorical_color', 'value'))def update_categorical_graph(feature, age, graph_style, color):    dff = spark.sql(f"SELECT * FROM dataset_view WHERE Age=='{age}'").toPandas()    vc = dff[feature].value_counts()    if graph_style == 'bar':        fig = px.bar(vc,                  x = vc.index,                  y = vc.values                )    elif graph_style == 'histogram':        fig = px.histogram(vc,                  x = vc.index,                  y = vc.values                )    elif graph_style == 'line':        fig = px.line(vc,                  x = vc.index,                  y = vc.values                )    else:        fig = px.pie(vc,                  names = vc.index,                  values = vc.values                )            if graph_style == 'line':        fig.update_traces(line_color=color)    elif graph_style != 'pie':        fig.update_traces(marker_color=color)    fig.update_layout(        title=f"{feature}特征值计数",        xaxis_title=feature,        yaxis_title="计数"    )    return fig

每个回调函数都与注释 @ callback 相关联。与回调函数相关联的注释控制哪些 HTML 组件(例如,下拉菜单)在用户请求时向回调函数提供输入,哪个 HTML 组件(例如,div 标签内的图表)接收回调函数的输出。

4.5 启动服务器

Dash Web 应用程序的最后一步是启动 Web 服务服务器,如下所示:

if __name__ == "__main__":    app.run_server()

以下图示展示了仪表板的一个场景,当仪表板用户在仪表板中选择以下选项时:

  • 年龄在21岁至30岁之间
  • 数值特征患者 ID 和入院押金的一对
  • 颜色编码数值特征可视化的分类特征入院类型
  • 数值特征可视化的散点图
  • 分类特征逗留时间用于计算要素值计数
  • 蓝色用于条形图、直方图和线状图
  • 饼图,用于可视化分类特征值计数的自动颜色编码
Figure 8: One view of overall dashboard.

作为获得可能有用的见解的示例,上述仪表板场景揭示了以下见解:

  • 大多数21-30岁的患者,无论逗留多久,入院押金都在3000美元至6000美元之间
  • 大多数21-30岁的患者逗留在医院11-30天(27.6%)或21-30天(27.9%)

下图显示了仪表板的另一种场景,当仪表板用户在仪表板中选择以下选项时:

  • 年龄在21岁至30岁之间
  • 数值特征患者 ID 和入院押金的一对
  • 颜色编码数值特征可视化的分类特征入院类型
  • 数值特征可视化的直方图
  • 分类特征入院类型
  • 绿色用于条形图、直方图和线状图
  • 条形图,用于可视化分类特征值计数
Figure 9: Another view of the overall dashboard.

作为获得可能有用的见解的另一个示例,上述仪表板场景揭示了以下见解:

  • 急救部的患者押金总额高于其他入院类型的患者
  • 大多数患者被诊断为外伤

总之,仪表板允许用户以灵活的方式可视化数据,以交互方式获得各种有用的见解,包括:

  • 在给定年龄范围内(例如0-10、11-20等)可视化数值和分类特征
  • 在散点图、直方图和/或线状图中可视化任何一对数值特征
  • 使用任何分类特征值对数值特征可视化进行颜色编码
  • 将任何分类特征的值计数可视化为条形图/直方图、线状图和/或带有不同颜色编码的饼图

5. 结论

本文介绍了一种基于开源的 Python Web 应用程序框架,用于使用 Spark [3] 和 Plotly Dash[4] 开发交互式和有洞察力的仪表板。该框架允许我们从云数据湖中分析大规模数据集,在 Spark 服务器上创建交互式仪表板,并允许用户在任何地方与仪表板进行交互,以以灵活的方式可视化数据,获得各种有用的见解。

参考文献

[1] Yu Huang, Predicting Hospitalized Time of Covid-19 Patients

[2] PySpark AWS S3 Read Write Operations

[3] Apache Spark examples

[4] Dash Python User Guide

Leave a Reply

Your email address will not be published. Required fields are marked *