Files
AIclinicalresearch/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Pivot列顺序优化总结.md
HaHafeng 74cf346453 feat(dc/tool-c): Add missing value imputation feature with 6 methods and MICE
Major features:
1. Missing value imputation (6 simple methods + MICE):
   - Mean/Median/Mode/Constant imputation
   - Forward fill (ffill) and Backward fill (bfill) for time series
   - MICE multivariate imputation (in progress, shape issue to fix)

2. Auto precision detection:
   - Automatically match decimal places of original data
   - Prevent false precision (e.g. 13.57 instead of 13.566716417910449)

3. Categorical variable detection:
   - Auto-detect and skip categorical columns in MICE
   - Show warnings for unsuitable columns
   - Suggest mode imputation for categorical data

4. UI improvements:
   - Rename button: "Delete Missing" to "Missing Value Handling"
   - Remove standalone "Dedup" and "MICE" buttons
   - 3-tab dialog: Delete / Fill / Advanced Fill
   - Display column statistics and recommended methods
   - Extended warning messages (8 seconds for skipped columns)

5. Bug fixes:
   - Fix sessionService.updateSessionData -> saveProcessedData
   - Fix OperationResult interface (add message and stats)
   - Fix Toolbar button labels and removal

Modified files:
Python: operations/fillna.py (new, 556 lines), main.py (3 new endpoints)
Backend: QuickActionService.ts, QuickActionController.ts, routes/index.ts
Frontend: MissingValueDialog.tsx (new, 437 lines), Toolbar.tsx, index.tsx
Tests: test_fillna_operations.py (774 lines), test scripts and docs
Docs: 5 documentation files updated

Known issues:
- MICE imputation has DataFrame shape mismatch issue (under debugging)
- Workaround: Use 6 simple imputation methods first

Status: Development complete, MICE debugging in progress
Lines added: ~2000 lines across 3 tiers
2025-12-10 13:06:00 +08:00

5.6 KiB
Raw Blame History

工具C - Pivot列顺序优化总结

📋 问题描述

用户需求:长宽转换后,列的排序应该与上传文件时的列顺序保持一致。

当前问题:系统按字母顺序排列转换后的列,导致顺序与原文件不一致。


🎯 解决方案方案A - Python端排序

核心思路

  1. Node.js后端从session获取原始列顺序
  2. Node.js后端从数据中提取透视列值的原始顺序(按首次出现顺序)
  3. 传递给Python
  4. Python在pivot后按原始顺序重排列

🛠️ 实现细节

1. Python端pivot.py

新增参数

  • original_column_order: List[str]:原始列顺序(如['Record ID', 'Event Name', 'FMA', '体重', '收缩压', ...]
  • pivot_value_order: List[str]:透视列值的原始顺序(如['基线', '1个月', '2个月', ...]

排序逻辑

if original_column_order:
    # 1. 索引列始终在最前面
    final_cols = [index_column]
    
    # 2. 按原始列顺序添加转换后的列
    for orig_col in original_column_order:
        if orig_col in value_columns:
            # 找出所有属于这个原列的新列
            related_cols = [c for c in df_pivot.columns if c.startswith(f'{orig_col}___')]
            
            # ✨ 按透视列的原始顺序排序
            if pivot_value_order:
                pivot_order_map = {val: idx for idx, val in enumerate(pivot_value_order)}
                related_cols_sorted = sorted(
                    related_cols,
                    key=lambda c: pivot_order_map.get(c.split('___')[1], 999)
                )
            else:
                related_cols_sorted = sorted(related_cols)
            
            final_cols.extend(related_cols_sorted)
    
    # 3. 添加未选择的列(保持原始顺序)
    if keep_unused_columns:
        for orig_col in original_column_order:
            if orig_col in df_pivot.columns and orig_col not in final_cols:
                final_cols.append(orig_col)
    
    # 4. 重排列
    df_pivot = df_pivot[final_cols]

2. Python端main.py

PivotRequest模型

class PivotRequest(BaseModel):
    # ... 原有字段 ...
    original_column_order: List[str] = []  # ✨ 新增
    pivot_value_order: List[str] = []  # ✨ 新增

调用pivot_long_to_wide

result_df = pivot_long_to_wide(
    df,
    request.index_column,
    request.pivot_column,
    request.value_columns,
    request.aggfunc,
    request.column_mapping,
    request.keep_unused_columns,
    request.unused_agg_method,
    request.original_column_order,  # ✨ 新增
    request.pivot_value_order  # ✨ 新增
)

3. Node.js后端QuickActionController.ts

获取原始列顺序

const originalColumnOrder = session.columns || [];

获取透视列值的原始顺序

const pivotColumn = params.pivotColumn;
const seenPivotValues = new Set();
const pivotValueOrder: string[] = [];

for (const row of fullData) {
  const pivotValue = row[pivotColumn];
  if (pivotValue !== null && pivotValue !== undefined && !seenPivotValues.has(pivotValue)) {
    seenPivotValues.add(pivotValue);
    pivotValueOrder.push(String(pivotValue));
  }
}

传递给QuickActionService

executeResult = await quickActionService.executePivot(
  fullData, 
  params, 
  session.columnMapping,
  originalColumnOrder,  // ✨ 新增
  pivotValueOrder  // ✨ 新增
);

4. Node.js后端QuickActionService.ts

方法签名

async executePivot(
  data: any[], 
  params: PivotParams, 
  columnMapping?: any[], 
  originalColumnOrder?: string[],  // ✨ 新增
  pivotValueOrder?: string[]  // ✨ 新增
): Promise<OperationResult>

传递给Python

const response = await axios.post(`${PYTHON_SERVICE_URL}/api/operations/pivot`, {
  // ... 原有参数 ...
  original_column_order: originalColumnOrder || [],  // ✨ 新增
  pivot_value_order: pivotValueOrder || [],  // ✨ 新增
});

📊 效果对比

修改前(按字母顺序)

Record ID | FMA___基线 | FMA___1个月 | 收缩压___基线 | 收缩压___1个月 | 体重___基线 | 体重___1个月
    ↑          ↑             ↑              ↑               ↑            ↑           ↑
  索引列      F开头        F开头           S开头(拼音)     S开头        T开头       T开头

修改后(按原始顺序)

Record ID | FMA___基线 | FMA___1个月 | 体重___基线 | 体重___1个月 | 收缩压___基线 | 收缩压___1个月
    ↑          ↑             ↑            ↑             ↑             ↑              ↑
  索引列    原文件第3列   原文件第3列   原文件第4列   原文件第4列   原文件第5列    原文件第5列

透视值内部顺序(按原始出现顺序)

FMA___基线 | FMA___1个月 | FMA___2个月
    ↑           ↑             ↑
  首次出现    第二次出现    第三次出现
(而不是按"1个月"、"2个月"、"基线"的字母顺序)

开发完成

修改文件清单

  1. extraction_service/operations/pivot.py
  2. extraction_service/main.py
  3. backend/src/modules/dc/tool-c/controllers/QuickActionController.ts
  4. backend/src/modules/dc/tool-c/services/QuickActionService.ts

优势

  • 列顺序与原文件一致(用户熟悉)
  • 透视值顺序按时间顺序基线→1个月→2个月
  • 未选择的列也保持原始顺序
  • 导出Excel时顺序正确

开发时间2025-12-09
状态 已完成,等待测试