一、引言

在当今数据驱动的时代,数据可视化已成为数据分析和决策制定过程中不可或缺的一环。它能够将复杂的数据以直观、易懂的图形化方式呈现出来,帮助我们快速洞察数据背后的模式、趋势和关系。Python 作为一种强大且灵活的编程语言,拥有众多功能丰富的数据可视化库,其中 Plotly 和 Dash 的结合为创建交互式图表提供了卓越的解决方案。

Plotly 是一个开源的可视化库,能够生成高质量、交互式的图表,支持多种图表类型,如折线图、柱状图、散点图、热力图等。其丰富的交互功能,如悬停效果、数据筛选、缩放和平移等,使用户能够更深入地探索数据。Dash 则是基于 Flask、Plotly.js 和 React.js 构建的 Python 框架,专门用于创建交互式、响应式的网络应用,尤其适用于数据可视化和仪表盘开发。通过简单的 Python 代码,Dash 允许用户定义 UI 组件,并实现与底层数据的交互,从而动态展示数据。将 Plotly 和 Dash 相结合,我们可以充分发挥它们的优势,打造出高度交互性、实时数据更新且易于扩展的可视化应用。本文将详细介绍如何使用 Plotly 和 Dash 创建交互式图表,从基础的安装和设置开始,逐步深入到各种图表类型的创建、交互功能的添加以及 Dash 应用的构建,通过实际案例帮助读者掌握这些高级数据可视化技巧。

二、Plotly 基础

2.1 安装与设置

在开始使用 Plotly 之前,首先需要确保其已安装在你的 Python 环境中。可以通过 pip 包管理器进行安装,在命令行中输入以下命令:


pip install plotly

安装完成后,还可以安装 Plotly Express,它是一个高级 API,提供了更简洁的绘图方法,安装命令如下:


pip install plotly-express

导入 Plotly 库是使用它的第一步,在 Python 脚本中,通常这样导入:


import plotly.graph_objects as go

如果要使用 Plotly Express,则这样导入:


import plotly.express as px

2.2 创建基本图表

2.2.1 线形图

使用 Plotly 创建简单的线形图非常直观。假设我们有一组时间序列数据,例如一家公司过去几年的销售额,数据如下:


years = [2015, 2016, 2017, 2018, 2019, 2020]

sales = [100, 120, 150, 130, 180, 200]

可以通过以下代码创建一个线形图:


fig = go.Figure()

fig.add_trace(go.Scatter(x = years, y = sales, mode='lines', name='Sales'))

fig.update_layout(title='Company Sales Over Years',

xaxis_title='Year',

yaxis_title='Sales Amount')

fig.show()

在这段代码中,首先创建了一个Figure对象fig,然后使用add_trace方法添加了一个Scatter对象,通过设置mode='lines'指定为线形图。接着,使用update_layout方法设置图表的标题以及 x 轴和 y 轴的标题,最后通过show方法显示图表。

2.2.2 柱状图

创建柱状图也类似。比如我们要比较不同产品的销量,数据如下:


products = ['Product A', 'Product B', 'Product C', 'Product D']

sales_quantity = [50, 70, 40, 60]

代码如下:


fig = go.Figure(data=[go.Bar(x = products, y = sales_quantity)])

fig.update_layout(title='Product Sales Quantity Comparison',

xaxis_title='Product',

yaxis_title='Sales Quantity')

fig.show()

这里直接通过go.Bar创建柱状图数据,并将其作为列表传递给Figure的data参数,然后同样设置图表布局并显示。

2.2.3 饼图

假设我们有一个关于市场份额的数据,要创建饼图来展示,数据如下:


companies = ['Company X', 'Company Y', 'Company Z', 'Company W']

market_shares = [0.3, 0.25, 0.2, 0.25]

创建饼图的代码如下:


fig = go.Figure(data=[go.Pie(labels = companies, values = market_shares)])

fig.update_layout(title='Market Share Distribution')

fig.show()

通过go.Pie创建饼图,设置labels为公司名称,values为对应的市场份额,再设置标题后显示图表。

2.3 添加交互功能

2.3.1 悬停效果

Plotly 默认就为图表添加了悬停效果,当鼠标悬停在数据点上时,会显示该点的具体数据信息。例如在线形图中,当鼠标悬停在某个年份对应的销售额点上时,会显示该年份和具体的销售额数值。如果想要自定义悬停信息的显示内容,可以通过设置hovertemplate参数来实现。比如在线形图中,我们希望在悬停时除了显示年份和销售额,还显示销售额的增长率(假设我们已经计算出了增长率列表growth_rates),代码可以修改为:


fig = go.Figure()

fig.add_trace(go.Scatter(x = years, y = sales, mode='lines', name='Sales',

hovertemplate='Year: %{x}<br>Sales: %{y}<br>Growth Rate: %{customdata[0]}',

customdata=[growth_rates]))

fig.update_layout(title='Company Sales Over Years',

xaxis_title='Year',

yaxis_title='Sales Amount')

fig.show()

这里通过hovertemplate定义了悬停信息的格式,%{x}和%{y}分别表示 x 轴和 y 轴的数据值,%{customdata[0]}表示自定义数据列表customdata中的第一个元素,即增长率。

2.3.2 数据筛选

可以通过添加滑块等交互组件来实现数据筛选功能。例如,对于时间序列数据,我们希望能够通过滑块选择特定时间段内的数据进行查看。以之前的公司销售额数据为例,要添加一个时间滑块,代码如下:


fig = go.Figure()

fig.add_trace(go.Scatter(x = years, y = sales, mode='lines', name='Sales'))

# 添加滑块

fig.update_layout(

xaxis=dict(

rangeselector=dict(

buttons=list([

dict(count=1, label="1y", step="year", stepmode="backward"),

dict(count=3, label="3y", step="year", stepmode="backward"),

dict(count=5, label="5y", step="year", stepmode="backward"),

dict(step="all")

])

),

rangeslider=dict(

visible=True

),

type="date"

)

)

fig.update_layout(title='Company Sales Over Years',

xaxis_title='Year',

yaxis_title='Sales Amount')

fig.show()

在这段代码中,通过update_layout方法对xaxis进行设置,添加了一个rangeselector(范围选择器),其中包含了不同时间跨度的按钮,如 “1y” 表示过去 1 年,“3y” 表示过去 3 年等,还添加了一个可见的rangeslider(范围滑块),方便用户直观地选择时间范围。

2.3.3 缩放和平移

Plotly 图表默认支持缩放和平移操作。用户可以通过鼠标滚轮进行缩放,按住鼠标左键并拖动进行平移。如果想要对缩放和平移的行为进行更精细的控制,例如限制缩放的范围,可以通过设置xaxis和yaxis的range属性来实现。比如,对于上述公司销售额的线形图,我们希望将 x 轴的缩放范围限制在 2015 年到 2020 年之间,y 轴的缩放范围限制在 0 到 250 之间,代码可以修改为:


fig = go.Figure()

fig.add_trace(go.Scatter(x = years, y = sales, mode='lines', name='Sales'))

fig.update_layout(title='Company Sales Over Years',

xaxis_title='Year',

yaxis_title='Sales Amount',

xaxis=dict(range=[2015, 2020]),

yaxis=dict(range=[0, 250]))

fig.show()

这样设置后,用户在进行缩放操作时,x 轴和 y 轴的范围将被限制在指定的区间内。

三、Plotly Express 高级工具

3.1 快速绘图

Plotly Express 提供了一种更简洁、高效的方式来创建各种常见的图表。以绘制一个展示不同国家 GDP 随时间变化的折线图为例,假设我们有一个包含年份、国家和 GDP 数据的 DataFramedf:


import pandas as pd

df = pd.DataFrame({

'year': [2010, 2011, 2012, 2013, 2014, 2015],

'country': ['USA', 'USA', 'USA', 'China', 'China', 'China'],

'gdp': [15000, 16000, 17000, 8000, 9000, 10000]

})

使用 Plotly Express 创建折线图只需一行代码:


fig = px.line(df, x='year', y='gdp', color='country', title='GDP of Different Countries Over Years')

fig.show()

这里px.line自动根据df中的数据,以year为 x 轴,gdp为 y 轴,country为颜色区分,快速生成了一个折线图,并设置了标题。

3.2 更多图表类型

除了基本的折线图、柱状图和饼图,Plotly Express 还支持多种其他图表类型。

3.2.1 散点图矩阵

当我们有多个数值型变量,想要查看它们之间的两两关系时,散点图矩阵非常有用。假设我们有一个包含多个特征的数据集data,代码如下:


import seaborn as sns

iris = sns.load_dataset('iris')

fig = px.scatter_matrix(iris, dimensions=['sepal_length','sepal_width', 'petal_length', 'petal_width'], color='species')

fig.show()

这段代码使用px.scatter_matrix创建了一个散点图矩阵,dimensions参数指定了要展示的变量,color参数根据species对数据点进行颜色区分,展示了鸢尾花数据集不同特征之间的关系。

3.2.2 地理地图

对于地理数据的可视化,Plotly Express 也提供了便捷的方法。例如,要在地图上展示不同国家的人口密度数据,假设我们有一个包含国家名称和人口密度的 DataFramedf_population,并且有一个包含国家地理信息的 GeoJSON 文件world.json,代码如下:


import json

with open('world.json') as f:

world_geojson = json.load(f)

fig = px.choropleth(df_population, geojson = world_geojson, locations='country', color='population_density',

featureidkey='properties.name', projection='natural earth',

title='Population Density by Country')

fig.show()

这里px.choropleth创建了一个地理填充地图,geojson指定地理数据,locations指定 DataFrame 中与地理数据对应的国家名称列,color根据人口密度进行颜色映射,featureidkey指定 GeoJSON 文件中识别国家的属性,projection指定地图投影方式,生成了一个直观展示不同国家人口密度分布的地图。

3.2.3 箱线图

箱线图用于展示数据的分布情况,包括中位数、四分位数等。假设有一个包含不同类别数据的数据集df_categories,要绘制箱线图比较不同类别数据的分布,代码如下:


fig = px.box(df_categories, x='category', y='value', title='Box Plot of Different Categories')

fig.show()

通过px.box,以category为 x 轴类别,value为 y 轴数据值,创建了一个箱线图,方便观察不同类别数据的分布特征。

四、Dash 入门

4.1 Dash 基础结构

Dash 应用的基本结构包括导入必要的库、定义应用布局和设置回调函数。首先,需要导入dash、dash_core_components(简称dcc)和dash_html_components(简称html)等库:


import dash

import dash_core_components as dcc

import dash_html_components as html

然后创建一个 Dash 应用实例:


app = dash.Dash(__name__)

接下来定义应用的布局,布局是由各种 HTML 组件和 Dash 核心组件组成的层次结构。例如,一个简单的包含标题和一个折线图的布局可以这样定义:


app.layout = html.Div([

html.H1('My Dash App'),

dcc.Graph(id='my-graph')

])

这里html.Div创建了一个容器,包含一个一级标题html.H1和一个用于显示图表的dcc.Graph组件,id属性用于在后续回调函数中识别组件。

最后,通常需要在脚本末尾添加以下代码来运行应用:


if __name__ == '__main__':

app.run_server(debug=True)

debug=True表示在开发过程中启用调试模式,这样当代码发生变化时,应用会自动重新加载。

4.2 添加组件

4.2.1 文本输入框

在 Dash 中添加文本输入框非常简单。例如,我们希望用户能够输入一个年份,然后根据输入的年份显示相应的数据。可以在布局中添加一个文本输入框组件:


app.layout = html.Div([

html.H1('My Dash App'),

dcc.Input(id='year-input', type='text', placeholder='Enter a year'),

dcc.Graph(id='my-graph')

])

这里dcc.Input创建了一个文本输入框,id为year-input,type='text'表示是文本类型输入,placeholder设置了输入框的提示文本。

4.2.2 下拉菜单

下拉菜单可以让用户从多个选项中选择一个或多个值。假设我们有一个包含不同产品名称的列表products,要创建一个下拉菜单让用户选择产品,代码如下:


products = ['Product A', 'Product B', 'Product C', 'Product D']

app.layout = html.Div([

html.H1('My Dash App'),

dcc.Dropdown(

id='product-dropdown',

options=[{'label': product, 'value': product} for product in products],

value=products[0]

),

dcc.Graph(id='my-graph')

])

这里dcc.Dropdown创建了下拉菜单,options参数通过列表推导式生成了每个产品的选项,每个选项是一个包含label(显示在下拉菜单中的文本)和value(实际传递的值)的字典,value设置了默认选中的选项。

4.2.3 按钮

添加按钮可以触发特定的操作。比如我们希望有一个按钮,当用户点击它时,更新图表数据。在布局中添加按钮组件:


app.layout = html.Div([

html.H1('My Dash App'),

dcc.Input(id='year-input', type='text', placeholder='Enter a year'),

dcc.Dropdown(

id='product-dropdown',

options=[{'label': product, 'value': product} for product in products],

value=products[0]

),

html.Button('Update Chart', id='update-button'),

dcc.Graph(id='my-graph')

])

这里html.Button创建了一个按钮,id为update-button,按钮上显示的文本为Update Chart。

五、结合 Plotly 和 Dash 创建交互式应用

5.1 案例:交互式销售数据分析

假设我们有一个销售数据集,包含不同产品在不同地区、不同时间的销售数据。我们要创建一个 Dash 应用,用户可以通过下拉菜单选择产品和地区,通过文本输入框输入年份范围,然后点击按钮,在图表中显示相应的销售数据趋势。

首先,导入必要的库并读取数据(假设数据存储在一个 CSV 文件sales_data.csv中):


import dash

import dash_core_components as dcc

import dash_html_components as html

from dash.dependencies import Input, Output

import pandas as pd

import plotly.graph_objects as go

app = dash.Dash(__name__)

df = pd.read_csv('sales_data.csv')

# 获取产品和地区的唯一值列表

products = df['product'].unique().tolist()

regions = df['region'].unique().tolist()

# 定义应用布局

app.layout = html.Div([

html.H1("Interactive Sales Data Analysis"),

html.Div([

html.Label("Select Product"),

dcc.Dropdown(

id='product-dropdown',

options=[{'label': product, 'value': product} for product in products],

value=products[0]

)

]),

html.Div([

html.Label("Select Region"),

dcc.Dropdown(

id='region-dropdown',

options=[{'label': region, 'value': region} for region in regions],

value=regions[0]

)

]),

html.Div([

html.Label("Enter Year Range (e.g., 2018-2020)"),

dcc.Input(

id='year-range-input',

type='text',

placeholder='Enter year range'

)

]),

html.Button('Generate Chart', id='generate-button'),

dcc.Graph(id='sales-chart')

])

接下来,编写回调函数,根据用户的输入生成相应的图表:


@app.callback(

Output('sales-chart', 'figure'),

[Input('generate-button', 'n_clicks')],

[dash.dependencies.State('product-dropdown', 'value'),

dash.dependencies.State('region-dropdown', 'value'),

dash.dependencies.State('year-range-input', 'value')]

)

def update_chart(n_clicks, selected_product, selected_region, year_range):

if n_clicks:

if year_range:

start_year, end_year = map(int, year_range.split('-'))

filtered_df = df[(df['product'] == selected_product) &

(df['region'] == selected_region) &

(df['year'] >= start_year) &

(df['year'] <= end_year)]

else:

filtered_df = df[(df['product'] == selected_product) &

(df['region'] == selected_region)]

fig = go.Figure()

fig.add_trace(go.Line(

x=filtered_df['year'],

y=filtered_df['sales_amount'],

name='Sales Amount'

))

fig.update_layout(

title=f"{selected_product} Sales in {selected_region}",

xaxis_title='Year',

yaxis_title='Sales Amount',

hovermode='closest'

)

return fig

return go.Figure()

上述代码中,update_chart回调函数监听按钮的点击事件(n_clicks),并获取用户在产品下拉菜单、地区下拉菜单以及年份范围输入框中的选择和输入。根据这些输入条件对原始数据进行筛选,然后使用 Plotly 创建一个折线图展示筛选后的数据趋势,并设置图表的标题、轴标签和悬停模式。如果按钮未被点击,则返回一个空的图表对象。

运行该 Dash 应用,在浏览器中访问相应地址,即可看到交互式的销售数据分析界面。用户可以自由选择产品、地区,并输入年份范围,点击 “Generate Chart” 按钮后,动态生成符合条件的销售数据图表。

5.2 高级 Dash 技巧

5.2.1 多页面应用

在实际项目中,可能需要创建包含多个页面的 Dash 应用。可以使用dash-bootstrap-components库来实现多页面布局,该库提供了丰富的 Bootstrap 样式组件。首先安装dash-bootstrap-components:


pip install dash-bootstrap-components

然后,假设我们有两个页面,一个是销售数据分析页面,另一个是产品库存分析页面。可以按照以下方式构建多页面应用:


import dash

import dash_core_components as dcc

import dash_html_components as html

from dash.dependencies import Input, Output

import dash_bootstrap_components as dbc

import pandas as pd

import plotly.graph_objects as go

# 读取销售数据和库存数据

sales_df = pd.read_csv('sales_data.csv')

inventory_df = pd.read_csv('inventory_data.csv')

# 创建Dash应用实例

app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

# 定义销售数据分析页面布局

sales_page_layout = html.Div([

html.H1("Interactive Sales Data Analysis"),

# 类似上述销售数据分析的组件和布局...

])

# 定义产品库存分析页面布局

inventory_page_layout = html.Div([

html.H1("Product Inventory Analysis"),

# 库存分析相关的组件和布局...

])

# 定义应用布局

app.layout = html.Div([

dcc.Location(id='url', refresh=False),

html.Div(id='page-content')

])

# 回调函数,根据URL路径加载不同页面

@app.callback(Output('page-content', 'children'),

[Input('url', 'pathname')])

def display_page(pathname):

if pathname == '/sales':

return sales_page_layout

elif pathname == '/inventory':

return inventory_page_layout

else:

return html.Div([

html.H1("404: Not Found")

])

if __name__ == '__main__':

app.run_server(debug=True)

在这个示例中,通过dcc.Location组件获取当前 URL 路径,然后在display_page回调函数中根据路径返回相应的页面布局。如果路径不存在,则显示 404 错误页面。

5.2.2 实时数据更新

对于一些需要实时展示数据变化的场景,如股票行情、传感器数据等,可以实现 Dash 应用的实时数据更新。可以使用 Python 的APScheduler库来定时获取新数据并更新图表。首先安装APScheduler:


pip install apscheduler

假设我们有一个模拟的实时销售数据生成函数get_real_time_sales,代码如下:


import dash

import dash_core_components as dcc

import dash_html_components as html

from dash.dependencies import Input, Output

import pandas as pd

import plotly.graph_objects as go

from apscheduler.schedulers.background import BackgroundScheduler

import time

app = dash.Dash(__name__)

# 模拟实时数据生成函数

def get_real_time_sales():

# 这里可以替换为实际获取数据的逻辑,例如从数据库或API获取

return pd.DataFrame({

'time': [time.strftime("%Y-%m-%d %H:%M:%S")],

'sales_amount': [100 + pd.np.random.randint(1, 100)]

})

# 初始化数据

real_time_df = get_real_time_sales()

# 定义应用布局

app.layout = html.Div([

html.H1("Real-time Sales Data"),

dcc.Graph(id='real-time-chart'),

dcc.Interval(

id='interval-component',

interval=5 * 1000, # 更新间隔为5秒

n_intervals=0

)

])

# 回调函数,更新实时图表

@app.callback(Output('real-time-chart', 'figure'),

[Input('interval-component', 'n_intervals')])

def update_real_time_chart(n):

global real_time_df

new_data = get_real_time_sales()

real_time_df = pd.concat([real_time_df, new_data])

fig = go.Figure()

fig.add_trace(go.Line(

x=real_time_df['time'],

y=real_time_df['sales_amount'],

name='Sales Amount'

))

fig.update_layout(

title="Real-time Sales Data",

xaxis_title='Time',

yaxis_title='Sales Amount',

hovermode='closest',

xaxis=dict(type='category')

)

return fig

# 启动定时任务

scheduler = BackgroundScheduler()

scheduler.add_job(lambda: get_real_time_sales(), 'interval', seconds=5)

scheduler.start()

if __name__ == '__main__':

app.run_server(debug=True)

在这个示例中,通过dcc.Interval组件设置定时触发回调函数的间隔时间,在update_real_time_chart回调函数中获取新数据并更新图表。同时,使用APScheduler库启动一个定时任务,定期获取新的实时数据。

六、部署与分享

6.1 本地部署

在完成 Dash 应用的开发后,可以将其部署在本地服务器上,供局域网内的其他用户访问。首先确保在运行应用的机器上安装了必要的依赖库。然后,在命令行中运行 Dash 应用脚本,例如:


python app.py

默认情况下,Dash 应用会在http://127.0.0.1:8050地址上运行。如果想要修改运行的 IP 地址和端口,可以在app.run_server方法中指定参数,例如:


app.run_server(host='0.0.0.0', port=8888, debug=False)

这样,应用将在本地所有 IP 地址的 8888 端口上运行,并且关闭调试模式(在生产环境中建议关闭调试模式以提高安全性)。

6.2 云平台部署

为了让更多用户访问 Dash 应用,可以将其部署到云平台上,如 Heroku、AWS Elastic Beanstalk、Google App Engine 等。以 Heroku 为例,部署步骤如下:

  1. 创建requirements.txt文件:在应用的根目录下创建一个requirements.txt文件,列出应用所需的所有依赖库及其版本(如果不指定版本,会安装最新版本),例如:

dash==2.12.0

dash-bootstrap-components==1.4.1

plotly==5.14.1

pandas==1.5.3

  1. 创建Procfile文件:在应用根目录下创建一个Procfile文件(注意文件名大小写和无文件扩展名),内容为:

web: gunicorn app:server

这里假设 Dash 应用的脚本名为app.py,并且在app.py中有如下代码将app实例赋值给server变量:


server = app.server

  1. 注册 Heroku 账号并安装 Heroku CLI:如果还没有 Heroku 账号,先在Heroku 官网注册。然后根据操作系统安装 Heroku 命令行工具(CLI)。
  1. 登录 Heroku 并初始化应用:在命令行中进入应用的根目录,执行以下命令登录 Heroku:

heroku login

然后初始化应用:


heroku create

这将在 Heroku 上创建一个新的应用,并为其分配一个随机名称(也可以指定自定义名称,例如heroku create my-dash-app)。

5. 推送代码到 Heroku:将应用代码推送到 Heroku 远程仓库:


git init

git add.

git commit -m "Initial commit"

git push heroku master

Heroku 会自动检测应用的类型(基于requirements.txt和Procfile),安装依赖库并启动应用。应用部署完成后,可以通过 Heroku 提供的 URL 访问 Dash 应用。

七、总结与展望

通过使用 Plotly 和 Dash,我们能够创建出功能强大、交互性高的数据可视化应用。从 Plotly 的基础图表创建和交互功能添加,到 Plotly Express 的快速绘图和丰富图表类型,再到 Dash 的组件添加、交互逻辑实现以及多页面应用和实时数据更新等高级技巧,以及最后的应用部署与分享,涵盖了数据可视化从开发到上线的全流程。

随着数据科学和人工智能技术的不断发展,数据可视化的需求也在日益增长和多样化。未来,Plotly 和 Dash 可能会进一步优化性能,增加更多新颖的图表类型和交互方式,与其他数据处理和分析库的集成也将更加紧密。例如,与机器学习库结合,实现基于模型预测结果的动态可视化;与大数据处理框架集成,支持更大规模数据的可视化展示。对于开发者来说,不断学习和掌握这些工具的新特性和应用场景,将有助于更好地满足实际业务中的数据可视化需求,以更直观、高效的方式呈现数据,为决策提供有力支持。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐