如何用Plotly复刻Seaborn的分面血压折线图(适配Dash)
问题:用Plotly复刻Seaborn分面血压折线图
已通过Seaborn实现按VISIT字段分面(2列布局)的血压折线图,核心需求如下:
- 筛选ID为30的数据
- 每个子图同时绘制SBP(红色)和DBP(蓝色)折线
- 折线线型随
MODE字段变化 - 图例置于图外
尝试用Plotly复刻时,两种方案(合并trace、手动添加子图)均无法生成正确的分面布局,需调整代码实现目标效果。
Seaborn实现代码
# LINE PLOT r_30 = r[r['ID']==30] #choosing person 30 g = sns.FacetGrid(r_30, col="VISIT", sharex=False, sharey=False,aspect=2,col_wrap=2,legend_out=True) # 2 columns, legend is outside of plot g.map(sns.lineplot, "DATE_TIME", "SBP",color='red',label='SBP',style=r_30['MODE']) # style/shape of line changes with mode g.map(sns.lineplot, "DATE_TIME", "DBP",color='blue',label='DBP',style=r_30['MODE']) g.set_axis_labels("DATE_TIME", "Blood Pressure (mmHg)")
现有方案问题分析
- 合并trace方案:直接合并
px.scatter生成的trace会丢失分面布局的关联逻辑,导致所有数据混乱叠加,无法对应到各VISIT子图。 - 手动添加子图方案:硬编码行列位置添加trace的方式无法动态适配
VISIT的数量,且未正确关联MODE线型与血压类型(SBP/DBP)的颜色配置。
解决方案
方法1:Plotly Express 简洁实现(推荐)
利用Plotly Express的分面功能,配合数据长格式转换,快速复刻Seaborn的分面逻辑:
import plotly.express as px import pandas as pd from dash import Dash, html, dcc # 数据预处理:将SBP/DBP转为长格式,统一控制颜色和线型 r_30_melted = r_30.melt( id_vars=['ID', 'VISIT', 'DATE_TIME', 'MODE'], value_vars=['SBP', 'DBP'], var_name='BP_Type', value_name='Blood_Pressure' ) # 绘制分面折线图 fig = px.line( r_30_melted, x="DATE_TIME", y="Blood_Pressure", color="BP_Type", # 区分SBP/DBP颜色 line_dash="MODE", # 线型随MODE变化 facet_col="VISIT", # 按VISIT分面 facet_col_wrap=2, # 2列布局 color_discrete_map={'SBP': 'red', 'DBP': 'blue'}, # 指定固定颜色 labels={ 'Blood_Pressure': 'Blood Pressure (mmHg)', 'DATE_TIME': 'DATE_TIME' }, title='Blood Pressure by Visit and Mode' ) # 配置子图与图例 fig.update_layout( legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), facet_col_spacing=0.1 # 调整子图间距 ) # 取消坐标轴共享,确保每个子图独立显示刻度 fig.update_xaxes(matches=None, showticklabels=True) fig.update_yaxes(matches=None, showticklabels=True) # Dash应用集成 app = Dash(__name__) app.layout = html.Div([ html.H1('Blood Pressure Dashboard'), dcc.Graph(figure=fig) ]) if __name__ == '__main__': app.run_server(debug=True)
方法2:手动创建子图(自定义需求适配)
通过make_subplots动态生成子图,适合更复杂的自定义布局需求:
import plotly.graph_objects as go from plotly.subplots import make_subplots from dash import Dash, html, dcc # 获取唯一VISIT值并排序 unique_visits = sorted(r_30['VISIT'].unique()) num_visits = len(unique_visits) rows = (num_visits + 1) // 2 # 计算2列布局所需行数 cols = 2 # 创建子图框架 fig = make_subplots( rows=rows, cols=cols, subplot_titles=[f'Visit {v}' for v in unique_visits], shared_xaxes=False, shared_yaxes=False, vertical_spacing=0.1, horizontal_spacing=0.05 ) # 定义颜色与线型映射 color_map = {'SBP': 'red', 'DBP': 'blue'} dash_map = {mode: dash for mode, dash in zip( r_30['MODE'].unique(), ['solid', 'dash', 'dot', 'dashdot'] # 可根据MODE数量调整线型 )} # 遍历每个VISIT添加折线 for idx, visit in enumerate(unique_visits, 1): row = (idx - 1) // cols + 1 col = (idx - 1) % cols + 1 visit_data = r_30[r_30['VISIT'] == visit] # 添加SBP折线(按MODE区分线型) for mode in visit_data['MODE'].unique(): mode_data = visit_data[visit_data['MODE'] == mode] fig.add_trace( go.Scatter( x=mode_data['DATE_TIME'], y=mode_data['SBP'], name=f'SBP - {mode}', line=dict(color=color_map['SBP'], dash=dash_map[mode]), showlegend=(idx == 1) # 仅第一个子图显示图例,避免重复 ), row=row, col=col ) # 添加DBP折线(按MODE区分线型) for mode in visit_data['MODE'].unique(): mode_data = visit_data[visit_data['MODE'] == mode] fig.add_trace( go.Scatter( x=mode_data['DATE_TIME'], y=mode_data['DBP'], name=f'DBP - {mode}', line=dict(color=color_map['DBP'], dash=dash_map[mode]), showlegend=(idx == 1) ), row=row, col=col ) # 调整布局与坐标轴 fig.update_layout( legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), height=400 * rows, # 根据行数设置图表高度 title_text='Blood Pressure by Visit and Mode' ) fig.update_xaxes(title_text='DATE_TIME') fig.update_yaxes(title_text='Blood Pressure (mmHg)') # Dash应用集成 app = Dash(__name__) app.layout = html.Div([ html.H1('Blood Pressure Dashboard'), dcc.Graph(figure=fig) ]) if __name__ == '__main__': app.run_server(debug=True)
内容的提问来源于stack exchange,提问作者dspractician
相关产品推荐
相关产品推荐

