You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Plotly交互式堆叠折线图切换年龄组时类别重叠的问题修复咨询

解决Plotly堆叠折线图切换年龄组时的异常问题

嘿,我帮你定位到问题啦!你的代码里有两个核心错误,导致切换年龄组按钮时混入了其他组的数据,咱们一步步来修正:

问题1:生成年龄组数据时未过滤对应年龄的记录

你在循环生成type_by_time_age的时候,没有筛选当前年龄组的患者数据——不管是20岁组还是30岁组,都是用全量的df来做分组统计,这就导致每个年龄组的df其实都是全年龄的数据,切换自然会出错。

修正方式:在循环里先筛选出对应年龄组的记录(假设你的age字段是类似20s、30s的格式,用str.startswith匹配):

type_by_time_age = []
df = p_info
for age_group in ['20','30','40','50','60','70','80']:
    # 关键:筛选当前年龄组的患者数据
    age_filtered_df = df[df['age'].str.startswith(age_group)]
    new_type_by_time = age_filtered_df.groupby(['week', 'infection_type']).size().unstack().fillna(0)
    type_by_time_age.append(new_type_by_time)

问题2:按钮切换的可见性数组计算错误

你原来的代码里,每个按钮对应9个trace可见,但实际上:

  • 「All」组是5个感染类别的trace
  • 每个年龄组也各是5个trace
    总共有5 + 7*5 = 40个trace,你用9来计算可见范围,完全不对,导致显示了错误的trace集合。

修正方式:重新构建每个按钮对应的visible数组,按5个trace为一组来控制可见性:

menus = []
total_traces = 5 + len(type_by_time_age)*5  # 总trace数:5(All) + 7*5(各年龄组)
for i, name in enumerate(['All','20','30','40','50','60','70','80']):
    visible = [False]*total_traces
    if i == 0:
        # 「All」组:前5个trace可见
        visible[:5] = [True]*5
    else:
        # 年龄组:对应索引范围的5个trace可见
        start_idx = 5 + (i-1)*5
        visible[start_idx:start_idx+5] = [True]*5
    d = dict(
        label=name, 
        method="update", 
        args=[
            {"visible": visible}, 
            {"title": f"Where do most people get infected? (Age: {name})"}
        ]
    )
    menus.append(d)

完整修正后的代码

import pandas as pd
import plotly.graph_objects as go
import plotly.express as px

p_info = pd.read_csv('PatientInfo.csv',parse_dates=['symptom_onset_date', 'confirmed_date', 'released_date', 'deceased_date'])
p_info = p_info.dropna(subset=['age'])
p_info = p_info.dropna(subset=['infection_case'])

# 分组感染类型
def group(x):
    if "Shincheonji Church" in x or "Onchun Church" in x or "Dongan Church" in x or "Geochang Church" in x or "SMR Newly Planted Churches Group" in x or "Pilgrimage to Israel" in x or "River of Grace Community Church" in x or "Biblical Language study meeting" in x:
        return "Church"
    elif "etc" in x:
        return "etc"
    elif "contact with patient" in x:
        return "Direct Contact with Patient"
    elif "overseas inflow" in x:
        return "Overseas"
    else:
        return "Group"

p_info['infection_type'] = p_info['infection_case'].apply(group)
p_info['week'] = p_info['confirmed_date'].dt.weekofyear

# 全年龄组数据
type_by_time = p_info.groupby(['week', 'infection_type']).size().unstack().fillna(0)

# 各年龄组数据(修正:添加年龄筛选)
type_by_time_age = []
df = p_info
for age_group in ['20','30','40','50','60','70','80']:
    age_filtered_df = df[df['age'].str.startswith(age_group)]
    new_type_by_time = age_filtered_df.groupby(['week', 'infection_type']).size().unstack().fillna(0)
    type_by_time_age.append(new_type_by_time)

colors = px.colors.qualitative.Light24
x = type_by_time.index.tolist()
categories = ['Church', 'Direct Contact with Patient', 'Group', 'Overseas', 'etc']

fig = go.Figure()

# 添加全年龄组的trace
for i, cat in enumerate(categories):
    fig.add_trace(go.Scatter(
        x=x, 
        y=type_by_time[cat], 
        hoverinfo='x+y', 
        mode='lines', 
        line=dict(width=0.5, color=colors[i]), 
        name=cat, 
        stackgroup='one', 
        groupnorm='percent' 
    ))

# 添加各年龄组的trace
for age_idx, df in enumerate(type_by_time_age):
    for i, cat in enumerate(categories):
        fig.add_trace(go.Scatter(
            x=df.index.tolist(), 
            y=df[cat], 
            hoverinfo='y', 
            mode='lines', 
            line=dict(width=0.5, color=colors[i]), 
            name=cat, 
            stackgroup=f"age_{age_idx}",  # 用唯一的stackgroup标识
            groupnorm='percent', 
            visible=False
        ))

fig.update_layout(
    title='Where do most people get infected?',
    showlegend=True,
    xaxis=dict(
        range=[4, 19],
        ticksuffix=' week'
    ),
    yaxis=dict(
        type='linear',
        range=[1, 100],
        ticksuffix='%'
    ),
    xaxis_title="weeks",
    yaxis_title="% in group of people who get infected",
)

# 修正按钮的可见性控制
menus = []
total_traces = 5 + len(type_by_time_age)*5
for i, name in enumerate(['All','20','30','40','50','60','70','80']):
    visible = [False]*total_traces
    if i == 0:
        visible[:5] = [True]*5
    else:
        start_idx = 5 + (i-1)*5
        visible[start_idx:start_idx+5] = [True]*5
    d = dict(
        label=name, 
        method="update", 
        args=[
            {"visible": visible}, 
            {"title": f"Where do most people get infected? (Age: {name})"}
        ]
    )
    menus.append(d)

fig.update_layout(
    updatemenus=[
        dict(
            type="buttons",
            direction="right",
            active=0,
            x=1,
            y=1.2,
            buttons=menus,
        )
    ]
)

fig.show()

现在你再运行代码,点击不同年龄组按钮时,就只会显示对应年龄组的5个感染类型类别,hover时数据总和也会保持100%啦!

内容的提问来源于stack exchange,提问作者KensukeSuzuki

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 08:47:42