Simulants 完整教學

Repository: https://github.com/mdsol/Simulants Stars: 2 | Language: Python | License: MIT Tags: clinical-trial, baseline, Medidata 最後更新: 2024-09-20 維護者: Mandis Beigi — Medidata Solutions (Dassault Systemes)

2. 核心架構

2.1 系統架構圖


graph TD
    subgraph 輸入層 Input Layer
        A[CSV 原始資料
Raw Tabular Data] --> B[uci_config.py
設定檔 Config] end subgraph 前處理層 Preprocessing B --> C[preprocessor_lib.py] C --> C1[Label Encoding
標籤編碼] C --> C2[One-Hot Encoding
獨熱編碼] C --> C3[Imputation
缺值填補] C --> C4[k-Anonymity
K 匿名化] end subgraph 合成核心 Synthesis Core C1 --> D[synthesis_wrapper.py] C2 --> D C3 --> D C4 --> D D --> E[synthesis_lib.py] E --> E1[相關性分析
Correlation Analysis] E --> E2[降維嵌入
Dim Reduction] E --> E3[KNN 鄰域抽樣
Neighbor Sampling] E --> E4[噪音注入
Noise Injection] E --> E5[去重保護
Deduplication] end subgraph 降維引擎 dimanalysis_lib.py E2 --> F1[t-SNE + Gower] E2 --> F2[PCA] E2 --> F3[ICA] E2 --> F4[CCA] end subgraph 驗證層 Analytics E5 --> G[analytics_wrapper.py] G --> G1[analytics_lib.py] G1 --> H1[Fisher Exact Test
費雪精確檢定] G1 --> H2[KS Test
KS 檢定] G1 --> H3[Silhouette Score
輪廓係數] G1 --> H4[RF / ET Classifier
分類器鑑別] G1 --> H5[BOW Histogram
直方圖距離] G --> G2[visualization_lib.py] G2 --> I1[PCA Scatter Plot] G2 --> I2[Correlation Heatmap] G2 --> I3[PDF Report] end subgraph 輸出層 Output E5 --> J1[合成 CSV
Synthetic CSV] I3 --> J2[驗證報告 PDF
Validation Report] G1 --> J3[統計 CSV
Statistics CSVs] end style E fill:#e1f5fe,stroke:#0288d1 style G fill:#fff3e0,stroke:#ef6c00

2.2 檔案結構

 1Simulants/
 2├── uci_demo.py              # 主程式進入點 (Entry Point)
 3├── uci_config.py            # 設定檔:路徑、演算法參數、驗證開關
 4├── synthesis_wrapper.py     # 合成流程協調器 (Orchestrator)
 5├── synthesis_lib.py         # 核心合成演算法 (Core Synthesis)
 6├── analytics_wrapper.py     # 驗證流程協調器
 7├── analytics_lib.py         # 統計檢定與分類器驗證
 8├── preprocessor_lib.py      # 前處理:編碼、填補、型別判斷
 9├── dimanalysis_lib.py       # 降維方法集合 (t-SNE/PCA/ICA/CCA)
10├── k_anonymity.py           # k-Anonymity 實作
11├── bow_lib.py               # Bag-of-Words 直方圖表示
12├── visualization_lib.py     # 視覺化:散佈圖、相關熱圖
13├── utilities_lib.py         # 通用工具函式
14├── requirements.txt         # Python 依賴
15├── uci-heart-disease/       # 範例資料集 (UCI Heart Disease)
16│   ├── processed.cleveland.csv
17│   └── heart-disease.names.txt
18└── factbook.yaml            # 專案聯絡資訊

2.3 合成演算法流程


flowchart LR
    A[原始 CSV] --> B[k-Anonymity
移除低頻類別] B --> C[Label Encoding
+ Imputation] C --> D[One-Hot
Encoding] D --> E[相關性偵測
Pearson / PB / Chi2 / Cramer V] E --> F[t-SNE / PCA
降維至 2D] F --> G[KNN 找
K 近鄰] G --> H{是否為
離群值?} H -->|是| I[跳過] H -->|否| J[逐欄位從
鄰域隨機取值] J --> K[Co-Segregation
高相關欄位綁定] K --> L[Gaussian Noise
乘性雜訊] L --> M[移除與原始
重複的紀錄] M --> N[One-Hot
Decode] N --> O[合成 CSV 輸出]

3. 安裝與設定

3.1 環境需求

  • Python 3.8 以上
  • 建議使用虛擬環境隔離

3.2 安裝步驟

 1# 1. Clone 專案
 2git clone https://github.com/mdsol/Simulants.git
 3cd Simulants
 4
 5# 2. 建立虛擬環境(推薦使用 uv)
 6uv venv .venv --python 3.10
 7source .venv/bin/activate
 8
 9# 3. 安裝依賴
10# 注意:requirements.txt 鎖定了較舊的版本,建議手動安裝核心套件
11uv pip install numpy pandas scikit-learn scipy matplotlib seaborn \
12    gower lifelines umap-learn sas7bdat pynndescent

注意requirements.txt 中的版本較舊(如 numpy==1.22pandas==1.2.4scikit-learn==1.0.1),在 Python 3.11+ 可能無法直接安裝。建議去除版本鎖定或使用上述手動安裝方式。

3.3 設定檔說明

設定全部集中在 uci_config.py,分為三個區塊:

 1# ===== 一般設定 =====
 2proj_name = 'demo'                     # 專案名稱
 3data_path = './uci-heart-disease/'     # 原始資料目錄
 4data_file = 'processed.cleveland.csv'  # CSV 檔名
 5output_dir = './output_demo/'          # 輸出目錄
 6log_file = 'demo.log'                  # Log 檔名
 7report_file = 'demo_report.pdf'        # 驗證報告 PDF 檔名
 8num_cpus = 1                           # CPU 數量
 9
10# ===== 核心合成參數 =====
11anonymity_k = 1           # k-Anonymity 門檻(1=不過濾,建議 >=5)
12embedding_method = 'tsne'  # 降維方法:tsne / pca / ica / cca
13embedding_metric = 'gower' # t-SNE 距離度量:gower / euclidean
14min_cluster_size = 5       # KNN 最小鄰域大小
15max_cluster_size = 5       # KNN 最大鄰域大小
16corr_threshold = 0.7       # 相關性門檻(> 此值的欄位會被綁定共同搬遷)
17batch_size = 1             # 合成/原始紀錄比(1 = 1:1)
18include_outliers = True    # 是否保留離群值
19col_pairings = []          # 強制綁定的欄位對,如 [['age','weight']]
20holdout_cols = []          # 嵌入前排除的欄位
21imputing_method = 'simple' # 填補方法:simple / iterative
22add_noise = True           # 是否加入高斯雜訊
23
24# ===== 驗證參數 =====
25cv_flag = True             # 是否執行交叉驗證
26cv_bow_num_of_bins = 40    # BOW 直方圖 bin 數

3.4 關鍵參數調校建議

參數隱私偏好保真度偏好說明
anonymity_k5-101值越大,低頻類別紀錄被移除越多
embedding_methodpcatsne + gowert-SNE + Gower 能保留混合型態的局部結構
min/max_cluster_size10-203-5鄰域越大,合成點越「平均化」
corr_threshold0.50.8門檻越低,越多欄位被綁定,保留更多結構
add_noiseTrueFalse雜訊增加隱私保護但降低數值精確度

4. 使用方式與程式碼範例

4.1 範例一:使用內建 UCI Heart Disease 資料集

這是最快速的入門方式,直接執行內附的 demo。

 1# 直接執行(使用預設 uci_config.py 設定)
 2# $ python uci_demo.py
 3
 4# 或在 Python 中逐步執行:
 5import pandas as pd
 6import time
 7import logging
 8import warnings
 9from matplotlib.backends.backend_pdf import PdfPages
10
11import uci_config as config
12import preprocessor_lib
13import synthesis_wrapper
14import analytics_wrapper
15
16# 設定 logger
17logging.basicConfig(level=logging.INFO,
18    format='%(asctime)-15s [%(levelname)s] %(funcName)s: %(message)s')
19warnings.filterwarnings("ignore")
20
21# 載入資料
22df = pd.read_csv(config.data_path + config.data_file)
23print(f"原始資料形狀: {df.shape}")
24# 輸出: 原始資料形狀: (303, 14)
25
26# 合成
27syn_df = synthesis_wrapper.synthesize(df, config)
28print(f"合成資料形狀: {syn_df.shape}")
29# 輸出: 合成資料形狀: (~290, 14)  -- 數量視 k-Anonymity 與去重結果而異
30
31# 比對前幾筆
32print("=== 原始資料 ===")
33print(df.head())
34print("=== 合成資料 ===")
35print(syn_df.head())
36
37# 交叉驗證(產生 PDF 報告)
38pdf_page = PdfPages(config.output_dir + config.report_file)
39analytics_wrapper.analyze(df.copy(), syn_df.copy(), config, pdf_page)
40pdf_page.close()
41print(f"驗證報告已儲存至: {config.output_dir}{config.report_file}")

輸出目錄結構

 1output_demo/
 2├── demo_syn.csv                    # 合成資料
 3├── demo_report.pdf                 # 含散佈圖、相關熱圖、統計檢定的 PDF 報告
 4├── demo.log                        # 完整執行紀錄
 5├── demo_fid_mean_comparison.csv    # 平均值比較
 6├── demo_fid_median_comparison.csv  # 中位數比較
 7├── demo_fid_cov_real.csv           # 原始資料共變異矩陣
 8├── demo_fid_cov_syn.csv            # 合成資料共變異矩陣
 9├── demo_missingness_src.csv        # 原始資料遺漏值分析
10├── demo_missingness_syn.csv        # 合成資料遺漏值分析
11├── demo_dissimilar_cols.csv        # 分布不一致的欄位清單
12└── figs/                           # SVG 圖檔
13    ├── demo_real_data.svg          # 原始資料 PCA 散佈圖
14    ├── demo_syn_data.svg           # 合成資料 PCA 散佈圖
15    ├── demo_real_corr.svg          # 原始相關熱圖
16    ├── demo_syn_corr.svg           # 合成相關熱圖
17    └── demo_diff_corr.svg          # 相關差異熱圖

4.2 範例二:自訂臨床試驗基線資料

假設你有一份臨床試驗基線資料,含有混合型態欄位(連續 + 類別),且需要強制綁定某些欄位。

 1import pandas as pd
 2import numpy as np
 3import sys
 4sys.path.insert(0, '/path/to/Simulants')
 5
 6import synthesis_wrapper
 7import k_anonymity
 8import preprocessor_lib
 9import synthesis_lib
10import utilities_lib
11
12# 模擬一份臨床試驗基線資料
13np.random.seed(42)
14n = 500
15trial_data = pd.DataFrame({
16    'age': np.random.normal(55, 12, n).astype(int).clip(18, 85),
17    'weight_kg': np.random.normal(70, 15, n).round(1).clip(40, 150),
18    'height_cm': np.random.normal(168, 10, n).round(1).clip(140, 200),
19    'bmi': None,  # 衍生欄位,稍後計算
20    'sex': np.random.choice(['M', 'F'], n, p=[0.55, 0.45]),
21    'race': np.random.choice(
22        ['White', 'Black', 'Asian', 'Hispanic', 'Other'],
23        n, p=[0.5, 0.2, 0.15, 0.1, 0.05]
24    ),
25    'ecog_score': np.random.choice([0, 1, 2], n, p=[0.4, 0.45, 0.15]),
26    'prior_therapy': np.random.choice(
27        ['None', 'Chemo', 'Radiation', 'Both'],
28        n, p=[0.3, 0.35, 0.2, 0.15]
29    ),
30    'tumor_size_mm': np.random.lognormal(3.0, 0.5, n).round(1).clip(5, 200),
31    'baseline_albumin': np.random.normal(3.8, 0.6, n).round(2).clip(1.5, 5.5),
32})
33trial_data['bmi'] = (
34    trial_data['weight_kg'] / (trial_data['height_cm'] / 100) ** 2
35).round(1)
36
37# 建立自訂設定物件(模擬 config module)
38class TrialConfig:
39    proj_name = 'trial_baseline'
40    data_path = './'
41    data_file = 'trial_baseline.csv'
42    output_dir = './output_trial/'
43    log_file = 'trial.log'
44    report_file = 'trial_report.pdf'
45    num_cpus = 1
46
47    # 合成參數
48    anonymity_k = 5               # 移除出現少於 5 次的類別紀錄
49    embedding_method = 'tsne'     # 使用 t-SNE
50    embedding_metric = 'gower'    # Gower 距離(混合型態最佳)
51    min_cluster_size = 5
52    max_cluster_size = 10
53    corr_threshold = 0.6          # 相關性門檻稍低,捕捉更多結構
54    batch_size = 2                # 生成 2 倍量的合成資料
55    include_outliers = False      # 排除離群值
56    col_pairings = [
57        ['weight_kg', 'height_cm'],   # 體重身高必須綁定
58        ['ecog_score', 'prior_therapy']  # ECOG 與治療史語義相關
59    ]
60    holdout_cols = ['bmi']        # BMI 是衍生欄位,不參與嵌入
61    imputing_method = 'simple'
62    add_noise = True
63
64    # 驗證參數
65    cv_flag = True
66    cv_bow_num_of_bins = 40
67
68config = TrialConfig()
69
70# 儲存原始資料
71import os
72os.makedirs(config.output_dir, exist_ok=True)
73trial_data.to_csv(config.data_path + config.data_file, index=False)
74
75# 合成
76syn_df = synthesis_wrapper.synthesize(trial_data, config)
77print(f"原始: {trial_data.shape} -> 合成: {syn_df.shape}")
78
79# 快速品質檢查:比較平均值
80for col in ['age', 'weight_kg', 'tumor_size_mm', 'baseline_albumin']:
81    orig_mean = trial_data[col].mean()
82    syn_mean = syn_df[col].mean() if col in syn_df.columns else float('nan')
83    print(f"  {col}: 原始={orig_mean:.2f}, 合成={syn_mean:.2f}")

4.3 範例三:僅使用合成核心(不走 wrapper)

如果你需要更細緻地控制流程,可以直接呼叫 synthesis_lib.synthesize()

 1import pandas as pd
 2import numpy as np
 3import sys
 4sys.path.insert(0, '/path/to/Simulants')
 5
 6import preprocessor_lib
 7import synthesis_lib
 8import k_anonymity
 9import utilities_lib
10
11# 載入你的資料
12df = pd.read_csv('your_baseline_data.csv')
13
14# Step 1: k-Anonymity(選用)
15ignore_columns = utilities_lib.get_date_columns(df)
16df_anon = k_anonymity.perform_k_anonymity(df, anonymity_k=5, ignore_columns=ignore_columns)
17print(f"k-Anonymity 前: {df.shape[0]} rows -> 後: {df_anon.shape[0]} rows")
18
19# Step 2: 編碼
20tmp_df = df_anon.loc[:, ~df_anon.columns.isin(ignore_columns)]
21one_hot_df = preprocessor_lib.one_hot_encoding_encode(tmp_df)
22
23# Step 3: 偵測高相關欄位群組
24label_encoded_df, _ = preprocessor_lib.label_encoding_encode(tmp_df)
25label_encoded_df = preprocessor_lib.impute_label_encoded_df(label_encoded_df)
26corr_groups = synthesis_lib.generate_corr_cols_groups(label_encoded_df, threshold=0.7)
27print(f"偵測到 {len(corr_groups)} 組高相關欄位")
28
29# Step 4: 合成(直接呼叫核心函式)
30syn_encoded_df = synthesis_lib.synthesize(
31    one_hot_df,
32    method='pca',             # 用 PCA(比 t-SNE 快,適合大資料集)
33    metric='euclidean',
34    min_cluster_size=3,
35    max_cluster_size=8,
36    batch_size=1,
37    corr_thresh=0.7,
38    include_outliers=True,
39    col_pairings=corr_groups,
40    imputing_method='simple',
41    add_noise=True
42)
43
44# Step 5: 解碼回原始類別
45syn_decoded = preprocessor_lib.one_hot_encoding_decode(
46    syn_encoded_df.reset_index(drop=False)
47)
48print(f"合成資料形狀: {syn_decoded.shape}")
49
50# Step 6: 隱私檢查——確認無重複
51common = pd.merge(df_anon, syn_decoded, how='inner')
52print(f"與原始資料完全重複的紀錄數: {len(common)}")

5. 在醫療結構化資料合成生態系中的定位

5.1 Domain 1 定位圖

Simulants 位於 Sub-domain D: Clinical Trial Data(臨床試驗資料) 區塊,與 VICTRE(虛擬影像試驗)、tsynth(時間序列合成)、TrialSynth(試驗設計知識圖譜)並列。

子領域代表工具主要特色Simulants 關係
A. Patient SimulationSynthea, Synthea-FHIR完整病患生命週期模擬Synthea 產生的縱向資料可擷取 baseline 後用 Simulants 合成
B. Privacy-PreservingOpenDP, SmartNoise, dp_cgans差分隱私 (Differential Privacy; DP)Simulants 用 k-Anonymity + noise injection,非 DP
C. Medical Tabularsynthcity, DataSynthesizer, COR-GANEHR 通用表格合成替代方案——synthcity 功能更全面但門檻更高
D. Clinical TrialVICTRE, tsynth, TrialSynth, Simulants專注臨床試驗場景Simulants 專攻表格基線資料合成
E. Medical Time SeriesTemporAI時序臨床資料互補——Simulants 做橫斷面,TemporAI 做縱向

5.2 獨特價值

Simulants 在生態系中的差異化定位

  1. Medidata 血統:開發團隊來自全球最大的臨床試驗數據平台,了解 CDISC SDTM/ADaM 實務需求
  2. 非深度學習方法:不需要 GPU、不需要調 GAN 超參數,適合小樣本臨床試驗(N=50-500)
  3. 內建完整驗證管線:其他工具通常需要自行建立 fidelity 評估,Simulants 一鍵產出含統計檢定的 PDF 報告
  4. 混合型態原生支援:透過 Gower 距離處理連續 + 類別混合欄位,無需手動前處理

5.3 對 Pre-IND 藥物開發的意義

在 Pre-IND (Investigational New Drug; 新藥臨床試驗申請前) 階段,Simulants 可用於:

  • Protocol Feasibility(方案可行性評估):在不接觸真實受試者資料的情況下,模擬基線特性,評估入組標準 (Inclusion/Exclusion Criteria) 是否合理
  • CRO 資料交換:將合成基線資料提供給 CRO (Contract Research Organization; 委託研究機構) 做系統整合測試
  • 法規準備:產生統計相似但非真實的範例資料集,用於 IND 申請文件中的分析方法說明

6. 與其他工具的整合

6.1 搭配 Synthea 的工作流

 1Synthea (完整病患模擬)
 2 3    ├─ 產生 FHIR Bundle (JSON)
 4 5    ├─ 擷取 Baseline 欄位 → 轉為 CSV
 6 7    └─ 餵入 Simulants → 合成基線資料
 8 9                ├─ 驗證報告 (PDF)
10                └─ 合成 CSV → CDISC SDTM 轉換

6.2 搭配 synthcity / DataSynthesizer 做方法比較

Simulants 可作為輕量 baseline 方法,與 GAN/VAE 方法做 A/B 比較:

 1# 假設已有 Simulants 合成結果 syn_simulants
 2# 以及 synthcity 的 CTGAN 合成結果 syn_ctgan
 3
 4from analytics_lib import calculate_silhouette_coef, compare_columns
 5
 6# 比較 Silhouette Score(越接近 0 越好,代表難以區分真假)
 7s_simulants = calculate_silhouette_coef(real_encoded, syn_simulants_encoded)
 8s_ctgan = calculate_silhouette_coef(real_encoded, syn_ctgan_encoded)
 9print(f"Simulants Silhouette: {s_simulants:.3f}")
10print(f"CTGAN Silhouette: {s_ctgan:.3f}")
11
12# 逐欄位統計檢定比較
13stats_simulants = compare_columns(real_encoded, syn_simulants_encoded)
14stats_ctgan = compare_columns(real_encoded, syn_ctgan_encoded)

6.3 搭配隱私工具加強保護

Simulants 的隱私保護(k-Anonymity + 噪音注入)屬於啟發式 (Heuristic) 層級,沒有嚴格的數學隱私保證。如需更高標準,可在 Simulants 輸出後疊加差分隱私機制:

 1# Simulants 合成後,疊加 OpenDP 的 Laplace Mechanism
 2import opendp.prelude as dp
 3
 4# 對合成資料的敏感統計量加入 DP 雜訊
 5dp.enable_features("contrib")
 6input_domain = dp.vector_domain(dp.atom_domain(T=float))
 7input_metric = dp.symmetric_distance()
 8
 9# 建立 DP 平均值量測
10base_lap = dp.m.make_base_laplace(
11    input_domain=dp.atom_domain(T=float),
12    input_metric=dp.absolute_distance(T=float),
13    scale=1.0  # epsilon ≈ 1
14)

6.4 搭配 CDISC 標準格式轉換

Simulants 輸出的是通用 CSV。若要用於 IND 送審,需轉換為 CDISC SDTM/ADaM 格式:

步驟工具說明
1. 合成基線SimulantsCSV 輸出
2. 欄位映射手動 / Python將欄位名稱對應到 SDTM 變數(如 AGE, SEX, RACE, WEIGHT)
3. 格式轉換sas7bdat / pyreadstat轉為 SAS Transport (XPT) 格式
4. 驗證Pinnacle 21 / P21 CommunityCDISC 合規性驗證

7. 優缺點分析

7.1 優點

面向說明
低門檻純 Python,不需 GPU,不需理解 GAN/VAE 理論
混合型態原生支援Gower 距離天然處理連續 + 類別欄位
內建驗證管線一次執行產出 PDF 報告 + 多種統計指標
可解釋性KNN 重組邏輯比黑盒子模型易於向法規機構解釋
小樣本友善臨床試驗常見 N=50-500,此方法不需大量訓練資料
Co-Segregation 機制自動偵測並保留高相關欄位間的結構
Medidata 實務背景設計者理解真實臨床試驗資料的特殊性

7.2 缺點

面向說明嚴重度
無數學隱私保證k-Anonymity + noise injection 非差分隱私,無 epsilon/delta 量化
依賴過時requirements.txt 鎖定 2021-2022 版本,NumPy/Pandas/Scikit-learn 均已過時
無 API 設計設定以全域 module 變數(uci_config.py)傳遞,無 class / function signature
無測試沒有任何 unit test 或 integration test
無 CLI只有 python uci_demo.py,不支援命令列參數
非套件化setup.py / pyproject.toml,無法 pip install
擴展性有限t-SNE 對大資料集(>10K rows)極慢
專案活躍度極低2 stars,0 forks,最後更新 2024-09,基本上是一次性釋出
缺乏時序支援僅處理橫斷面 (Cross-Sectional) 表格,不支援縱向追蹤資料
SAS 依賴載入 sas7bdat 但範例中未使用,暗示原始設計針對 SAS 資料集

7.3 風險評估與建議

使用情境建議
內部開發測試適合直接使用,搭配 k≥5 + noise
跨組織資料交換需額外疊加 DP 機制,或改用 synthcity 的 DP-CTGAN
法規送審不建議單獨依賴——缺乏正式隱私保證,建議搭配 re-identification risk 評估
大規模資料(>10K rows)建議將 embedding_method 改為 pca,避免 t-SNE 效能瓶頸
台灣 PDPA 合規k-Anonymity 是 PDPA 認可的去識別化手段之一,但建議搭配 Quasi-Identifier 分析

7.4 與替代方案的比較

特性SimulantssynthcityDataSynthesizerCOR-GAN
方法KNN 重組多種 GAN/VAE/統計BN + DPGAN (CNN)
GPU 需求依方法
隱私保證k-Anonymity可選 DP可選 DP
小樣本表現視方法
維護活躍度極低
驗證工具內建內建部分
混合型態原生原生原生僅數值
安裝難度中(需手動修依賴)低(pip install)

總結:Simulants 是一個來自 Medidata 的小巧臨床試驗基線合成工具,核心價值在於其 KNN + 降維的非深度學習方法論與內建驗證管線。它特別適合小樣本、混合型態的臨床試驗場景,但缺乏正式隱私保證與持續維護。建議作為概念驗證 (PoC) 或方法比較的 baseline,正式生產環境建議搭配 synthcity 等更成熟的框架。