CoolFace
Apppublic

smartTranscend/ckd-markov-predictor

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
app.py2311 linesDownload Raw Back to root
1# -*- coding: utf-8 -*-2"""3CKD Markov 預測應用 - Streamlit 完整最終版4=======================================================================5功能完整版本:改進首頁 + 患者預測 + 智能AI對話6"""7 8import streamlit as st9import pandas as pd10import numpy as np11import plotly.graph_objects as go12import plotly.express as px13import os14from datetime import datetime15import json16from openai import OpenAI17 18# 自訂模塊19import sys20sys.path.insert(0, os.path.dirname(__file__))21 22from ckd_markov_final_predictor import CKDMarkovPredictor23 24# ============================================================25# 頁面配置26# ============================================================27st.set_page_config(28    page_title="CKD Markov 風險預測系統",29    page_icon="🏥",30    layout="wide",31    initial_sidebar_state="expanded"32)33 34st.markdown("""35    <style>36    .metric-card {37        background-color: #f0f2f6;38        padding: 20px;39        border-radius: 10px;40        margin: 10px 0;41    }42    </style>43""", unsafe_allow_html=True)44 45# ============================================================46# 初始化 session state47# ============================================================48if "messages" not in st.session_state:49    st.session_state.messages = []50 51if "current_patient_data" not in st.session_state:52    st.session_state.current_patient_data = None53 54if "intervention_results" not in st.session_state:55    st.session_state.intervention_results = None56 57if "prediction_results" not in st.session_state:58    st.session_state.prediction_results = None59 60if "visit_data" not in st.session_state:61    st.session_state.visit_data = []62 63# ============================================================64# 初始化模型65# ============================================================66@st.cache_resource67def load_model():68    """載入Markov模型"""69    theta_path = './10cov_theta.npy'70    q_path = './10cov_Q_params.csv'71    72    if os.path.exists(theta_path) and os.path.exists(q_path):73        return CKDMarkovPredictor(theta_path, q_path)74    else:75        st.error("⚠️ 模型文件未找到!")76        return None77 78# ============================================================79# 側邊欄 - 導航和設定80# ============================================================81def judge_ckd_state_kdigo(egfr, pro_coded):82    """83    根據 KDIGO 標準判斷 CKD 狀態84    85    蛋白尿分類:86    - A1: 正常 (-) → pro_coded < 187    - A2: 微量 (+/-) → pro_coded == 188    - A3: ≥1+ → pro_coded >= 289    """90    91    # 判斷蛋白尿分類92    if pro_coded < 1:93        albuminuria = "A1"94    elif pro_coded == 1:95        albuminuria = "A2"96    else:97        albuminuria = "A3"98    99    # 根據 KDIGO 表判斷狀態和等級100    if egfr >= 90:101        gfr_grade = "G1"102        if albuminuria == "A1":103            state_idx, state_name = 0, "Low"104        elif albuminuria == "A2":105            state_idx, state_name = 1, "Moderate"106        else:107            state_idx, state_name = 2, "High"108    elif egfr >= 60:109        gfr_grade = "G2"110        if albuminuria == "A1":111            state_idx, state_name = 0, "Low"112        elif albuminuria == "A2":113            state_idx, state_name = 1, "Moderate"114        else:115            state_idx, state_name = 2, "High"116    elif egfr >= 45:117        gfr_grade = "G3a"118        if albuminuria == "A1":119            state_idx, state_name = 1, "Moderate"120        elif albuminuria == "A2":121            state_idx, state_name = 2, "High"122        else:123            state_idx, state_name = 3, "VeryHigh"124    elif egfr >= 30:125        gfr_grade = "G3b"126        if albuminuria == "A1":127            state_idx, state_name = 2, "High"128        else:129            state_idx, state_name = 3, "VeryHigh"130    elif egfr >= 15:131        gfr_grade = "G4"132        state_idx, state_name = 3, "VeryHigh"133    else:134        gfr_grade = "G5"135        state_idx, state_name = 3, "VeryHigh"136    137    return state_idx, state_name, gfr_grade, albuminuria138 139 140st.sidebar.title("🏥 CKD Markov 風險預測")141st.sidebar.markdown("---")142 143page = st.sidebar.radio(144    "選擇功能:",145    ["🏠 首頁", "👤 單患者預測", "👥 多患者對比", "📊 患者追蹤", "💬 AI 諮詢", "📋 患者記錄"]146)147 148st.sidebar.markdown("---")149 150# API 設定部分151with st.sidebar.expander("🔑 OpenAI API 設定", expanded=False):152    st.markdown("### 輸入你的 API Key")153    154    api_key_input = st.text_input(155        "API Key",156        type="password",157        value=st.session_state.get("openai_api_key", ""),158        help="從 https://platform.openai.com/api/keys 取得"159    )160    161    if api_key_input:162        st.session_state.openai_api_key = api_key_input163        st.success("✅ API Key 已設定")164 165st.sidebar.markdown("---")166 167# ============================================================168# 頁面 1:首頁 - 改進版169# ============================================================170if page == "🏠 首頁":171    st.title("🏥 多階段CKD疾病進展機器學習預測模型")172    173    st.markdown("""174    ## 🎯 AI 驅動的腎臟病風險評估與臨床決策支持175    176    基於馬可夫連鎖模型的 CKD 進展預測,整合機器學習和 ChatGPT 智能對話177    """)178    179    st.markdown("---")180    181    # 系統功能概覽卡片182    st.subheader("✨ 核心功能")183    184    col1, col2, col3, col4 = st.columns(4)185    186    with col1:187        st.metric("📊 預測功能", "完整", "7 轉移")188    with col2:189        st.metric("🤖 AI 對話", "實時", "個性化")190    with col3:191        st.metric("📈 批量處理", "CSV/Excel", "自動")192    with col4:193        st.metric("💾 數據記憶", "自動", "智能")194    195    st.markdown("---")196    197    # KDIGO CKD 分類表198    st.subheader("🔬 KDIGO CKD 分類(eGFR × 蛋白尿)")199    200    st.markdown("""201    系統使用 KDIGO 標準將 CKD 分為 **5 個風險等級**:202    203    | eGFR 分級 | 正常 (A1) | 微量 (A2) | ≥1+ (A3) |204    |---------|---------|---------|---------|205    | G1 (≥90) | **Low** | Moderate | High |206    | G2 (60-89) | **Low** | Moderate | High |207    | G3a (45-59) | Moderate | High | **VeryHigh** |208    | G3b (30-44) | High | **VeryHigh** | VeryHigh |209    | G4 (15-29) | **VeryHigh** | VeryHigh | VeryHigh |210    | G5 (<15) | **VeryHigh** | VeryHigh | VeryHigh |211    212    > 📌 **關鍵概念:**213    > - **eGFR**:估計腎臟過濾能力(mL/min/1.73m²)214    > - **蛋白尿**:A1=正常(-),A2=微量(+/-),A3=≥1+215    > - 同一 eGFR 等級下,蛋白尿越多,風險越高216    """)217    218    st.markdown("---")219    st.subheader("📋 CKD 5個狀態分類")220    221    states_visual = pd.DataFrame({222        '狀態': ['Low', 'Moderate', 'High', 'VeryHigh', 'Dialysis'],223        '定義': ['eGFR≥90', 'G2-3a或有蛋白尿', 'G3b', 'G4-5或有蛋白尿', '已進入透析'],224        '風險': [10, 25, 50, 75, 95],225    })226    227    fig_states = go.Figure()228    229    fig_states.add_trace(go.Bar(230        x=states_visual['狀態'],231        y=states_visual['風險'],232        marker=dict(233            color=['green', 'yellow', 'orange', 'red', 'darkred'],234            line=dict(color='black', width=2)235        ),236        text=states_visual['定義'],237        textposition='outside',238        showlegend=False239    ))240    241    fig_states.update_layout(242        title="CKD 狀態與相對風險程度",243        xaxis_title="CKD 狀態",244        yaxis_title="相對風險",245        height=350,246        template='plotly_white'247    )248    249    st.plotly_chart(fig_states, use_container_width=True)250    251    # 狀態轉移圖252    st.markdown("---")253    st.subheader("🔄 CKD 狀態轉移路徑")254    255    col1, col2 = st.columns(2)256    257    with col1:258        st.markdown("""259        ### 前進轉移(惡化)260        261        🔴 **進展方向:**262        - Low → Moderate263        - Moderate → High264        - High → VeryHigh265        - VeryHigh → Dialysis266        267        危險因素:年齡、血糖、血壓、尿酸268        """)269    270    with col2:271        st.markdown("""272        ### 後退轉移(改善)273        274        🟢 **改善方向:**275        - Moderate → Low276        - High → Moderate277        - VeryHigh → High278        279        保護因素:血糖控制、血壓控制、蛋白質限制280        """)281    282    st.markdown("---")283    284    # 使用流程285    st.subheader("🚀 四步快速開始")286    287    col1, col2, col3, col4 = st.columns(4)288    289    with col1:290        st.markdown("""291        ### ① 輸入數據292        👤 **單患者預測**293        294        輸入:295        - 年齡、性別296        - 血壓、血糖297        - 腎功能、蛋白尿298        """)299    300    with col2:301        st.markdown("""302        ### ② AI 預測303        🔮 **自動計算**304        305        獲得:306        - 5年洗腎風險307        - 10年洗腎風險308        - 風險分層309        - 轉移速率310        """)311    312    with col3:313        st.markdown("""314        ### ③ 智能對話315        💬 **AI 諮詢**316        317        ChatGPT:318        - 記住患者數據319        - 個性化回答320        - 臨床建議321        """)322    323    with col4:324        st.markdown("""325        ### ④ 決策支持326        📊 **臨床應用**327        328        支持:329        - 隨訪計劃330        - 轉介決策331        - 患者教育332        """)333    334    st.markdown("---")335    336    # 風險分層表337    st.subheader("🎨 風險分層與臨床決策")338    339    risk_data = pd.DataFrame({340        '風險等級': ['🟢 低風險', '🟡 中等風險', '🟠 高風險', '🔴 極高風險'],341        '5年洗腎風險': ['< 5%', '5-15%', '15-30%', '> 30%'],342        '建議隨訪頻率': ['年 1 次', '半年 1 次', '每季 1 次', '每月 1 次'],343        '臨床行動': ['常規管理', '強化血壓血糖控制', '準備透析通路評估', '準備透析治療']344    })345    346    st.dataframe(risk_data, use_container_width=True, hide_index=True)347    348    st.markdown("---")349    350    # 模型特性351    st.subheader("🔬 模型特性與驗證")352    353    col1, col2, col3 = st.columns(3)354    355    with col1:356        st.success("""357        ### 📊 訓練數據規模358        359        ✅ **樣本數**:21,756 轉移段360        ✅ **患者數**:5,455 人361        ✅ **隨訪期**:平均 3.5 年362        ✅ **完整率**:99.2%363        """)364    365    with col2:366        st.info("""367        ### 🧮 模型結構368        369        📌 **狀態**:5 個370        📌 **轉移**:7 種(前進 4 + 後退 3)371        📌 **協變數**:10 個372        📌 **參數**:77 個373        """)374    375    with col3:376        st.warning("""377        ### ⚙️ 技術細節378        379        🔧 **方法**:最大似然估計380        🔧 **優化**:L-BFGS-B381        🔧 **驗證**:Hessian 反演382        🔧 **變數**:標準化383        """)384    385    st.markdown("---")386    387    # 10 個協變數說明388    st.subheader("📊 10 個預測協變數")389    390    covariates_info = pd.DataFrame({391        '協變數': ['age_at_screening', 'hi_UA', 'RBC', 'PDH_HP', 'GENDER', 'sbp', 'waist', 'WBC', 'GLUCOSE', 'EDU_high'],392        '中文名稱': ['年齡', '高尿酸', '紅血球', '高血壓病史', '性別', '收縮壓', '腰圍', '白血球', '血糖', '高教育'],393        '變數類型': ['連續', '二元', '連續', '二元', '二元', '連續', '連續', '連續', '連續', '二元'],394        '臨床意義': ['年紀越大風險越高', '尿酸升高增加風險', '貧血增加風險', '高血壓增加風險', '性別差異', '血壓升高增加風險', '肥胖增加風險', '感染風險', '血糖升高增加風險', '教育程度保護']395    })396    397    st.dataframe(covariates_info, use_container_width=True, hide_index=True)398    399    st.markdown("---")400    401    # 智能對話功能402    st.subheader("🤖 智能 AI 對話功能")403    404    st.markdown("""405    ### 💡 三個特點406    407    **1️⃣ 自動記憶患者數據**408    - 你在「單患者預測」輸入的所有信息,系統自動保存409    - 無需重複輸入410    411    **2️⃣ 個性化智能回答**412    - ChatGPT 知道患者的年齡、血糖、洗腎風險等具體數據413    - 提供的建議是針對這位患者,不是通用建議414    415    **3️⃣ 持續對話支持**416    - 保留完整對話記錄417    - 支持追問和深入討論418    419    ### 📝 可以問的問題範例420    421    - 「這位患者的 5 年洗腎風險為什麼是 72.5%?」422    - 「他/她應該怎樣控制血糖?」423    - 「需要準備透析通路嗎?」424    - 「下次隨訪應該檢查什麼項目?」425    - 「有飲食建議嗎?」426    - 「什麼時候應該轉介給腎臟科?」427    """)428    429    st.info(430        "**✨ 開始使用:**\n\n"431        "1. 👉 點左側「👤 單患者預測」\n"432        "2. 👉 輸入患者信息並點「🔮 預測」\n"433        "3. 👉 再點「💬 AI 諮詢」開始對話!\n\n"434        "系統會自動記住這位患者的所有信息 🎉"435    )436 437# ============================================================438# 頁面 2:單患者預測439# ============================================================440elif page == "👤 單患者預測":441    st.title("👤 單患者 CKD 風險預測")442    443    predictor = load_model()444    if predictor is None:445        st.stop()446    447    # 初始化session state用來保存預測結果448    if "prediction_results" not in st.session_state:449        st.session_state.prediction_results = None450    451    with st.form("patient_form"):452        st.subheader("📋 患者信息與協變數")453        454        col1, col2, col3 = st.columns(3)455        456        with col1:457            patient_id = st.text_input("患者ID", value="P001")458            patient_name = st.text_input("患者姓名", value="")459        460        with col2:461            age = st.slider("年齡", 20, 100, 60)462            gender = st.radio("性別", ["女 (0)", "男 (1)"], horizontal=True)463            gender_val = 0 if gender == "女 (0)" else 1464        465        with col3:466            st.markdown("### 腎功能指標")467            egfr = st.number_input("eGFR (mL/min/1.73m²)", 5, 120, 60)468            pro_coded = st.selectbox(469                "尿蛋白",470                ["陰性 (0)", "微量 (1)", "1+ (2)", "2+ (3)", "3+ (4)", "4+ (5)"],471                index=0472            )473            pro_coded_val = int(pro_coded.split("(")[1].strip(")"))474            475            # 自動判斷 CKD 狀態476            def judge_ckd_state(egfr, pro_coded):477                """478                根據 KDIGO 標準判斷 CKD 狀態479                480                蛋白尿分類:481                - A1: 正常 (-) → pro_coded < 1482                - A2: 微量 (+/-) → pro_coded == 1483                - A3: ≥1+ → pro_coded >= 2484                """485                486                # 判斷蛋白尿分類487                if pro_coded < 1:488                    albuminuria = "A1"489                elif pro_coded == 1:490                    albuminuria = "A2"491                else:492                    albuminuria = "A3"493                494                # 根據 KDIGO 表判斷狀態和等級495                if egfr >= 90:496                    gfr_grade = "G1"497                    if albuminuria == "A1":498                        state_idx, state_name = 0, "Low"499                    elif albuminuria == "A2":500                        state_idx, state_name = 1, "Moderate"501                    else:502                        state_idx, state_name = 2, "High"503                elif egfr >= 60:504                    gfr_grade = "G2"505                    if albuminuria == "A1":506                        state_idx, state_name = 0, "Low"507                    elif albuminuria == "A2":508                        state_idx, state_name = 1, "Moderate"509                    else:510                        state_idx, state_name = 2, "High"511                elif egfr >= 45:512                    gfr_grade = "G3a"513                    if albuminuria == "A1":514                        state_idx, state_name = 1, "Moderate"515                    elif albuminuria == "A2":516                        state_idx, state_name = 2, "High"517                    else:518                        state_idx, state_name = 3, "VeryHigh"519                elif egfr >= 30:520                    gfr_grade = "G3b"521                    if albuminuria == "A1":522                        state_idx, state_name = 2, "High"523                    else:524                        state_idx, state_name = 3, "VeryHigh"525                elif egfr >= 15:526                    gfr_grade = "G4"527                    state_idx, state_name = 3, "VeryHigh"528                else:529                    gfr_grade = "G5"530                    state_idx, state_name = 3, "VeryHigh"531                532                return state_idx, state_name, gfr_grade, albuminuria533            534            state_idx, state_name, gfr_grade, albuminuria = judge_ckd_state(egfr, pro_coded_val)535            st.info(f"✅ **自動判斷狀態:{state_name}**\n\n**KDIGO 分類:** {gfr_grade} {albuminuria}\n**eGFR:** {egfr} mL/min/1.73m²\n**尿蛋白:** {pro_coded.split('(')[0].strip()}")536        537        st.markdown("---")538        st.subheader("🔬 生化檢驗與測量")539        540        col1, col2, col3, col4, col5 = st.columns(5)541        542        with col1:543            hi_ua = st.selectbox("高尿酸", ["否 (0)", "是 (1)"], index=0)544            hi_ua_val = int(hi_ua.split("(")[1].strip(")"))545        546        with col2:547            rbc = st.number_input("紅血球 (RBC)", 2.0, 8.0, 4.5, 0.1)548        549        with col3:550            sbp = st.number_input("收縮壓", 80, 200, 130)551        552        with col4:553            wbc = st.number_input("白血球 (WBC)", 2.0, 15.0, 7.0, 0.1)554        555        with col5:556            glucose = st.number_input("血糖", 50, 300, 110)557        558        col1, col2, col3, col4 = st.columns(4)559        560        with col1:561            waist = st.number_input("腰圍", 60, 150, 85)562        563        with col2:564            pdh_hp = st.selectbox("高血壓病史", ["否 (0)", "是 (1)"], index=0)565            pdh_hp_val = int(pdh_hp.split("(")[1].strip(")"))566        567        with col3:568            edu_high = st.selectbox("高教育程度", ["否 (0)", "是 (1)"], index=0)569            edu_high_val = int(edu_high.split("(")[1].strip(")"))570        571        with col4:572            st.empty()573        574        st.markdown("---")575        submitted = st.form_submit_button("🔮 預測", use_container_width=True)576    577    if submitted:578        covariates = {579            'age_at_screening': age,580            'hi_UA': hi_ua_val,581            'RBC': rbc,582            'PDH_HP': pdh_hp_val,583            'GENDER': gender_val,584            'sbp': sbp,585            'waist': waist,586            'WBC': wbc,587            'GLUCOSE': glucose,588            'EDU_high': edu_high_val,589        }590        591        pred_5y = predictor.predict(covariates, start_state=state_idx, years=5)592        pred_10y = predictor.predict(covariates, start_state=state_idx, years=10)593        594        # 保存所有結果到session_state595        st.session_state.prediction_results = {596            'covariates': covariates,597            'pred_5y': pred_5y,598            'pred_10y': pred_10y,599            'state_idx': state_idx,600            'age': age,601            'gender_val': gender_val,602            'sbp': sbp,603            'glucose': glucose,604            'waist': waist,605            'rbc': rbc,606            'wbc': wbc,607            'hi_ua_val': hi_ua_val,608            'pdh_hp_val': pdh_hp_val,609            'edu_high_val': edu_high_val,610            'patient_id': patient_id,611            'patient_name': patient_name612        }613    614    # 如果有保存的預測結果,就顯示615    if st.session_state.prediction_results is not None:616        results = st.session_state.prediction_results617        covariates = results['covariates']618        pred_5y = results['pred_5y']619        pred_10y = results['pred_10y']620        state_idx = results['state_idx']621        age = results['age']622        gender_val = results['gender_val']623        sbp = results['sbp']624        glucose = results['glucose']625        waist = results['waist']626        rbc = results['rbc']627        wbc = results['wbc']628        hi_ua_val = results['hi_ua_val']629        pdh_hp_val = results['pdh_hp_val']630        edu_high_val = results['edu_high_val']631    632        dial_5y = pred_5y['final_probs']['Dialysis'] * 100633        dial_10y = pred_10y['final_probs']['Dialysis'] * 100634        635        if dial_5y < 5:636            risk_level = "🟢 低風險"637        elif dial_5y < 15:638            risk_level = "🟡 中等風險"639        elif dial_5y < 30:640            risk_level = "🟠 高風險"641        else:642            risk_level = "🔴 極高風險"643        644        # 保存患者數據到 session state645        st.session_state.current_patient_data = {646            'patient_id': results.get('patient_id', patient_id),647            'patient_name': results.get('patient_name', patient_name),648            'age': age,649            'gender': gender_val,650            'gender_text': "男" if gender_val == 1 else "女",651            # 腎功能指標652            'egfr': egfr,653            'pro_coded': pro_coded_val,654            'pro_text': pro_coded.split("(")[0].strip(),655            # CKD 狀態656            'state': state_name,657            'state_idx': state_idx,658            'gfr_grade': gfr_grade,659            'albuminuria': albuminuria,660            'kdigo_class': f"{gfr_grade} {albuminuria}",661            # 臨床指標662            'hi_ua': hi_ua_val,663            'rbc': rbc,664            'pdh_hp': pdh_hp_val,665            'pdh_hp_text': "是" if pdh_hp_val == 1 else "否",666            'sbp': sbp,667            'waist': waist,668            'wbc': wbc,669            'glucose': glucose,670            'edu_high': edu_high_val,671            'edu_high_text': "高" if edu_high_val == 1 else "一般",672            # 風險評估673            'dial_5y': dial_5y,674            'dial_10y': dial_10y,675            'risk_level': risk_level676        }677        678        st.success("✅ 預測完成!")679        680        st.subheader("📊 預測結果")681        682        col1, col2, col3, col4 = st.columns(4)683        with col1:684            st.metric("當前狀態", st.session_state.current_patient_data['state'])685        with col2:686            st.metric("洗腎風險 (5年)", f"{dial_5y:.1f}%")687        with col3:688            st.metric("洗腎風險 (10年)", f"{dial_10y:.1f}%")689        with col4:690            st.metric("風險分層", risk_level)691        692        st.markdown("---")693        694        st.subheader("📈 轉移概率預測")695        696        col1, col2 = st.columns(2)697        698        with col1:699            states = ["Low", "Moderate", "High", "VeryHigh", "Dialysis"]700            probs_5y = [pred_5y['final_probs'][s] * 100 for s in states]701            702            fig_5y = go.Figure(data=[703                go.Bar(x=states, y=probs_5y, marker_color=['green', 'yellow', 'orange', 'red', 'darkred'])704            ])705            fig_5y.update_layout(title="5年狀態預測概率", xaxis_title="狀態", yaxis_title="概率 (%)", height=400, showlegend=False)706            fig_5y.update_xaxes(tickformat="%Y-%m-%d")707            st.plotly_chart(fig_5y, use_container_width=True)708        709        with col2:710            probs_10y = [pred_10y['final_probs'][s] * 100 for s in states]711            712            fig_10y = go.Figure(data=[713                go.Bar(x=states, y=probs_10y, marker_color=['green', 'yellow', 'orange', 'red', 'darkred'])714            ])715            fig_10y.update_layout(title="10年狀態預測概率", xaxis_title="狀態", yaxis_title="概率 (%)", height=400, showlegend=False)716            fig_10y.update_xaxes(tickformat="%Y-%m-%d")717            st.plotly_chart(fig_10y, use_container_width=True)718        719        st.markdown("---")720        st.success("✅ 患者數據已保存!現在可以去「💬 AI 諮詢」頁面跟 ChatGPT 討論這位患者!")721        722        st.markdown("---")723        724        # 協變數影響 - 民眾版本725        st.subheader("💊 哪些因素會影響洗腎風險?")726        727        q_params = predictor.q_params728        h4_idx = 3729        if len(q_params) > h4_idx:730            h4_params = q_params.iloc[h4_idx]731            cov_names = ['age_at_screening', 'hi_UA', 'RBC', 'PDH_HP', 'GENDER', 'sbp', 'waist', 'WBC', 'GLUCOSE', 'EDU_high']732            cov_labels = ['年齡', '高尿酸', '紅血球', '高血壓病史', '性別', '收縮壓', '腰圍', '白血球', '血糖', '教育程度']733            734            # 詳細說明735            factor_explanations = {736                '年齡': '年紀越大,腎臟功能衰退風險越高',737                '高尿酸': '尿酸升高會加重腎臟損傷,增加風險',738                '紅血球': '貧血(紅血球低)會加重腎臟缺氧,增加風險',739                '高血壓病史': '長期高血壓直接損傷腎臟,增加風險',740                '性別': '男性患者風險相對較高',741                '收縮壓': '血壓越高,對腎臟損傷越大,增加風險',742                '腰圍': '腹部肥胖增加腎臟負擔,增加風險',743                '白血球': '白血球低可能代表免疫功能差,增加風險;但過高可能代表感染,也會增加風險',744                '血糖': '血糖升高會損傷腎小球,增加風險',745                '教育程度': '教育程度高的患者自我管理能力強,能降低風險'746            }747            748            betas = [h4_params[f'beta_{c}'] for c in cov_names]749            750            # 建立易懂的資料框751            impact_list = []752            for label, beta in zip(cov_labels, betas):753                if beta > 0:754                    impact = "🔴 增加風險"755                    direction = "升高"756                else:757                    impact = "🟢 降低風險"758                    direction = "升高"759                760                impact_list.append({761                    '健康指標': label,762                    '影響': impact,763                    '影響程度': abs(beta),764                    '詳細說明': factor_explanations.get(label, ''),765                    '方向': direction766                })767            768            impact_df = pd.DataFrame(impact_list).sort_values('影響程度', ascending=False)769            770            col1, col2 = st.columns(2)771            772            with col1:773                st.markdown("### 🔴 會增加洗腎風險的因素")774                increase = impact_df[impact_df['影響'] == "🔴 增加風險"][['健康指標', '詳細說明', '影響程度']].head(5)775                for idx, row in increase.iterrows():776                    st.markdown(f"""777**{row['健康指標']}** (影響程度:{row['影響程度']:.2f})778- {row['詳細說明']}779                    """)780            781            with col2:782                st.markdown("### 🟢 會降低洗腎風險的因素")783                decrease = impact_df[impact_df['影響'] == "🟢 降低風險"][['健康指標', '詳細說明', '影響程度']].head(5)784                for idx, row in decrease.iterrows():785                    st.markdown(f"""786**{row['健康指標']}** (保護程度:{row['影響程度']:.2f})787- {row['詳細說明']}788                    """)789            790            # 圖表791            fig_beta = px.bar(792                impact_df.head(10),793                x='影響程度',794                y='健康指標',795                color='影響',796                color_discrete_map={'🔴 增加風險': '#ff6b6b', '🟢 降低風險': '#51cf66'},797                title='各項健康指標對洗腎風險的影響程度',798                height=400,799                orientation='h',800                labels={'健康指標': '', '影響程度': '影響強度'},801                hover_data=['詳細說明']802            )803            fig_beta.update_layout(showlegend=False)804            st.plotly_chart(fig_beta, use_container_width=True)805            806            st.info(807                "💡 **怎麼理解這個圖?**\n\n"808                "• 🔴 **紅色柱子** = 這個因素會增加洗腎風險(數值越高越危險)\n"809                "• 🟢 **綠色柱子** = 這個因素會降低洗腎風險(需要維持在良好狀態)\n"810                "• **柱子越長** = 影響越大\n\n"811                "例如:\n"812                "- 白血球低 → 增加風險(需要提升白血球)\n"813                "- 紅血球低 → 增加風險(需要治療貧血)\n"814                "- 年齡增加 → 增加風險(無法改變,但可以強化其他管理)"815            )816        817        st.markdown("---")818        819        # 時間序列曲線820        st.subheader("📉 時間序列預測曲線")821        822        times_5y = pred_5y['times']823        probs_traj = pred_5y['probs']824        825        fig_traj = go.Figure()826        colors = ['green', 'yellow', 'orange', 'red', 'darkred']827        828        for i, state in enumerate(states):829            fig_traj.add_trace(go.Scatter(830                x=times_5y,831                y=probs_traj[:, i] * 100,832                mode='lines',833                name=state,834                line=dict(color=colors[i], width=2)835            ))836        837        fig_traj.update_layout(838            title="5年轉移概率時間序列",839            xaxis_title="時間 (年)",840            yaxis_title="概率 (%)",841            hovermode='x unified',842            height=450843        )844        st.plotly_chart(fig_traj, use_container_width=True)845        846        st.markdown("---")847        848        # 轉移速率表(摺疊)849        with st.expander("⚙️ 技術詳情 - 轉移速率", expanded=False):850            st.markdown("_僅供研究人員參考_")851            852            Q = pred_5y['Q']853            hazard_names = [854                'h1: Low→Moderate',855                'h2: Moderate→High',856                'h3: High→VeryHigh',857                'h4: VeryHigh→Dialysis',858                'b1: Moderate→Low',859                'b2: High→Moderate',860                'b3: VeryHigh→High'861            ]862            hazard_info = []863            864            hazards = [(0,1), (1,2), (2,3), (3,4), (1,0), (2,1), (3,2)]865            for i, (from_s, to_s) in enumerate(hazards):866                rate = Q[from_s, to_s]867                hazard_info.append({868                    'Hazard': hazard_names[i],869                    '速率 (/年)': f"{rate:.6f}",870                    '倒數 (年)': f"{1/rate:.2f}" if rate > 0 else "∞"871                })872            873            hazard_df = pd.DataFrame(hazard_info)874            st.dataframe(hazard_df, use_container_width=True, hide_index=True)875        876        st.markdown("---")877        878        # 臨床決策建議879        st.subheader("💊 臨床決策建議")880        881        col1, col2 = st.columns(2)882        883        with col1:884            if dial_5y < 5:885                st.info("✅ **低風險** - 常規隨訪\n\n每年檢查一次,注意生活方式改善")886            elif dial_5y < 15:887                st.warning("🟡 **中等風險** - 密集隨訪\n\n建議半年檢查一次,強化營養諮詢和血壓控制")888            elif dial_5y < 30:889                st.error("🟠 **高風險** - 每季隨訪\n\n需要密集監測,準備洗腎通路評估")890            else:891                st.error("🔴 **極高風險** - 每月隨訪\n\n立即準備透析,考慮先制性介入")892        893        with col2:894            st.info(895                "📋 **建議檢驗項目:**\n\n"896                "• 血清肌酐 + eGFR\n"897                "• 尿蛋白/肌酐比\n"898                "• 電解質 (Na, K, Ca, P)\n"899                "• 血紅素 + 鐵代謝\n"900                "• 血糖 + 糖化血紅素"901            )902        903        st.markdown("---")904        905        # 介入效果模擬906        st.subheader("🎯 介入效果模擬")907        908        st.markdown("""909        **自訂參數調整,模擬介入效果**910        911        調整下方參數,系統會自動重新預測洗腎風險,看看介入後會改善多少!912        """)913        914        # 創建可調整的協變數副本915        intervention_covariates = covariates.copy()916        917        # 創建調整區域918        st.markdown("#### 📊 可介入參數調整")919        920        col1, col2, col3 = st.columns(3)921        922        # 只有可以通過介入改變的參數923        with col1:924            st.markdown("**血管與代謝**")925            sbp_adjusted = st.number_input(926                "收縮壓 (mmHg)",927                min_value=80.0,928                max_value=200.0,929                value=float(sbp),930                step=1.0,931                key="sbp_int",932                help="💊 可通過藥物或生活方式改變"933            )934            intervention_covariates['sbp'] = sbp_adjusted935            936            glucose_adjusted = st.number_input(937                "血糖 (mg/dL)",938                min_value=50.0,939                max_value=300.0,940                value=float(glucose),941                step=1.0,942                key="glucose_int",943                help="💊 可通過藥物或飲食改變"944            )945            intervention_covariates['GLUCOSE'] = glucose_adjusted946        947        with col2:948            st.markdown("**身體組成**")949            waist_adjusted = st.number_input(950                "腰圍 (cm)",951                min_value=60.0,952                max_value=150.0,953                value=float(waist),954                step=0.5,955                key="waist_int",956                help="💪 可通過減重改變"957            )958            intervention_covariates['waist'] = waist_adjusted959            960            rbc_adjusted = st.number_input(961                "紅血球 (RBC)",962                min_value=2.0,963                max_value=8.0,964                value=float(rbc),965                step=0.1,966                key="rbc_int",967                help="💊 可通過治療貧血改變"968            )969            intervention_covariates['RBC'] = rbc_adjusted970        971        with col3:972            st.markdown("**血球與代謝**")973            wbc_adjusted = st.number_input(974                "白血球 (WBC)",975                min_value=2.0,976                max_value=15.0,977                value=float(wbc),978                step=0.1,979                key="wbc_int",980                help="💊 可通過感染控制改變"981            )982            intervention_covariates['WBC'] = wbc_adjusted983            984            hi_ua_adjusted = st.selectbox(985                "高尿酸",986                ["否 (0)", "是 (1)"],987                index=hi_ua_val,988                key="hi_ua_int",989                help="💊 可通過藥物改變"990            )991            intervention_covariates['hi_UA'] = int(hi_ua_adjusted.split("(")[1].strip(")"))992        993        # 腎功能指標 - 可調整994        st.markdown("---")995        st.markdown("#### 🫘 腎功能指標調整(介入目標)")996        997        col1, col2 = st.columns(2)998        999        with col1:1000            egfr_adjusted = st.number_input(1001                "eGFR (mL/min/1.73m²)",1002                min_value=5.0,1003                max_value=120.0,1004                value=float(egfr),1005                step=1.0,1006                key="egfr_int",1007                help="🎯 介入目標:改善腎功能。藥物(如 ACEi/ARB)、控制血壓血糖可能改善。"1008            )1009        1010        with col2:1011            pro_adjusted = st.selectbox(1012                "尿蛋白",1013                ["陰性 (0)", "微量 (1)", "1+ (2)", "2+ (3)", "3+ (4)", "4+ (5)"],1014                index=min(pro_coded_val, 5),1015                key="pro_int",1016                help="🎯 介入目標:減少尿蛋白。血壓控制、ACEi/ARB、減重是關鍵。"1017            )1018            pro_adjusted_val = int(pro_adjusted.split("(")[1].strip(")"))1019        1020        # 自動重算 CKD 狀態1021        new_state_idx, new_state_name, new_gfr_grade, new_albuminuria = judge_ckd_state_kdigo(egfr_adjusted, pro_adjusted_val)1022        1023        # 顯示 CKD 狀態變化1024        st.markdown("---")1025        st.markdown("#### 📊 CKD 狀態變化預測")1026        1027        col1, col2, col3, col4 = st.columns(4)1028        1029        with col1:1030            st.info(f"""1031**當前狀態**1032{state_name}1033 1034**{gfr_grade} {albuminuria}**1035            """)1036        1037        with col2:1038            st.markdown("**→**")1039            st.write("")  # 空白1040        1041        with col3:1042            if new_state_idx != state_idx:1043                st.success(f"""1044**介入後狀態**1045{new_state_name}1046 1047**{new_gfr_grade} {new_albuminuria}**1048                """)1049            else:1050                st.info(f"""1051**介入後狀態**1052{new_state_name}1053 1054**{new_gfr_grade} {new_albuminuria}**1055                """)1056        1057        with col4:1058            if new_state_idx < state_idx:1059                st.success(f"✅ 進展降級\n{state_name} → {new_state_name}")1060            elif new_state_idx > state_idx:1061                st.error(f"⚠️ 進展升級\n{state_name} → {new_state_name}")1062            else:1063                st.info(f"➡️ 狀態不變\n{state_name}")1064        1065        # 保持不變的參數(顯示但不可改)1066        st.markdown("---")1067        st.markdown("#### 📋 患者基本信息(無法改變)")1068        col1, col2, col3, col4 = st.columns(4)1069        1070        with col1:1071            st.metric("年齡", f"{age:.0f} 歲")1072            intervention_covariates['age_at_screening'] = age1073        with col2:1074            st.metric("性別", "男" if gender_val == 1 else "女")1075            intervention_covariates['GENDER'] = gender_val1076        with col3:1077            st.metric("高血壓病史", "是" if pdh_hp_val == 1 else "否")1078            intervention_covariates['PDH_HP'] = pdh_hp_val1079        with col4:1080            st.metric("教育程度", "高" if edu_high_val == 1 else "一般")1081            intervention_covariates['EDU_high'] = edu_high_val1082        # 模擬按鈕1083        if st.button("🎯 模擬介入效果", use_container_width=True, key="simulate_btn"):1084            # 使用新的 CKD 狀態進行預測(如果 eGFR/Pro 改變了)1085            pred_5y_int = predictor.predict(intervention_covariates, start_state=new_state_idx, years=5)1086            pred_10y_int = predictor.predict(intervention_covariates, start_state=new_state_idx, years=10)1087            1088            dial_5y_int = pred_5y_int['final_probs']['Dialysis'] * 1001089            dial_10y_int = pred_10y_int['final_probs']['Dialysis'] * 1001090            1091            # 計算改變1092            changes = {}1093            change_descriptions = []1094            1095            # 腎功能變化(最重要)1096            if egfr_adjusted != egfr:1097                change = egfr_adjusted - egfr1098                changes['eGFR'] = {1099                    '原始': egfr,1100                    '介入後': egfr_adjusted,1101                    '變化': change1102                }1103                direction = "改善" if change > 0 else "惡化"1104                change_descriptions.append(f"🫘 eGFR {direction} {abs(change):.0f} (從 {egfr:.0f} 到 {egfr_adjusted:.0f} mL/min/1.73m²)")1105            1106            if pro_adjusted_val != pro_coded_val:1107                changes['尿蛋白'] = {1108                    '原始': pro_coded_val,1109                    '介入後': pro_adjusted_val,1110                    '變化': pro_adjusted_val - pro_coded_val1111                }1112                direction = "減少" if pro_adjusted_val < pro_coded_val else "增加"1113                change_descriptions.append(f"🫘 尿蛋白{direction}: {pro_coded.split('(')[0].strip()} → {pro_adjusted.split('(')[0].strip()}")1114            1115            if new_state_idx != state_idx:1116                change_descriptions.append(f"🔄 CKD 狀態進展: {state_name} → {new_state_name}")1117            1118            if intervention_covariates['sbp'] != covariates['sbp']:1119                change = intervention_covariates['sbp'] - covariates['sbp']1120                changes['收縮壓'] = {1121                    '原始': covariates['sbp'],1122                    '介入後': intervention_covariates['sbp'],1123                    '變化': change1124                }1125                change_descriptions.append(f"降低收縮壓 {abs(change):.0f} mmHg (從 {covariates['sbp']:.0f} 到 {intervention_covariates['sbp']:.0f})")1126            1127            if intervention_covariates['GLUCOSE'] != covariates['GLUCOSE']:1128                change = intervention_covariates['GLUCOSE'] - covariates['GLUCOSE']1129                changes['血糖'] = {1130                    '原始': covariates['GLUCOSE'],1131                    '介入後': intervention_covariates['GLUCOSE'],1132                    '變化': change1133                }1134                change_descriptions.append(f"降低血糖 {abs(change):.0f} mg/dL (從 {covariates['GLUCOSE']:.0f} 到 {intervention_covariates['GLUCOSE']:.0f})")1135            1136            if intervention_covariates['waist'] != covariates['waist']:1137                change = intervention_covariates['waist'] - covariates['waist']1138                changes['腰圍'] = {1139                    '原始': covariates['waist'],1140                    '介入後': intervention_covariates['waist'],1141                    '變化': change1142                }1143                change_descriptions.append(f"減少腰圍 {abs(change):.1f} cm (從 {covariates['waist']:.0f} 到 {intervention_covariates['waist']:.0f})")1144            1145            if intervention_covariates['RBC'] != covariates['RBC']:1146                change = intervention_covariates['RBC'] - covariates['RBC']1147                changes['紅血球'] = {1148                    '原始': covariates['RBC'],1149                    '介入後': intervention_covariates['RBC'],1150                    '變化': change1151                }1152                change_descriptions.append(f"提升紅血球 {abs(change):.1f} (從 {covariates['RBC']:.1f} 到 {intervention_covariates['RBC']:.1f})")1153            1154            if intervention_covariates['WBC'] != covariates['WBC']:1155                change = intervention_covariates['WBC'] - covariates['WBC']1156                changes['白血球'] = {1157                    '原始': covariates['WBC'],1158                    '介入後': intervention_covariates['WBC'],1159                    '變化': change1160                }1161                change_descriptions.append(f"調整白血球 {abs(change):.1f} (從 {covariates['WBC']:.1f} 到 {intervention_covariates['WBC']:.1f})")1162            1163            if intervention_covariates['hi_UA'] != covariates['hi_UA']:1164                changes['高尿酸'] = {1165                    '原始': '是' if covariates['hi_UA'] == 1 else '否',1166                    '介入後': '是' if intervention_covariates['hi_UA'] == 1 else '否',1167                }1168                change_descriptions.append(f"高尿酸狀態變為 {'是' if intervention_covariates['hi_UA'] == 1 else '否'}")1169            1170            # 保存介入結果到session_state1171            st.session_state.intervention_results = {1172                'changes': changes,1173                'change_descriptions': change_descriptions,1174                'original_dial_5y': dial_5y,1175                'original_dial_10y': dial_10y,1176                'intervention_dial_5y': dial_5y_int,1177                'intervention_dial_10y': dial_10y_int,1178                'improvement_5y': dial_5y - dial_5y_int,1179                'improvement_10y': dial_10y - dial_10y_int,1180                'improvement_5y_percent': ((dial_5y - dial_5y_int)/dial_5y*100) if dial_5y > 0 else 0,1181                'improvement_10y_percent': ((dial_10y - dial_10y_int)/dial_10y*100) if dial_10y > 0 else 0,1182            }1183            1184            st.markdown("---")1185            st.subheader("📊 介入效果對比")1186            1187            # 建立對比表1188            comparison_data = pd.DataFrame({1189                '指標': ['5年洗腎風險', '10年洗腎風險'],1190                '原始風險': [f"{dial_5y:.1f}%", f"{dial_10y:.1f}%"],1191                '介入後': [f"{dial_5y_int:.1f}%", f"{dial_10y_int:.1f}%"],1192                '改善幅度': [f"{dial_5y - dial_5y_int:.1f}%", f"{dial_10y - dial_10y_int:.1f}%"],1193                '改善百分比': [f"{((dial_5y - dial_5y_int)/dial_5y*100):.1f}%" if dial_5y > 0 else "N/A",1194                              f"{((dial_10y - dial_10y_int)/dial_10y*100):.1f}%" if dial_10y > 0 else "N/A"]1195            })1196            1197            st.dataframe(comparison_data, use_container_width=True, hide_index=True)1198            1199            # 視覺化對比1200            col1, col2 = st.columns(2)

Showing the first 1,200 of 2311 lines. Download the file for the rest.