Files
AIclinicalresearch/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_NA处理功能开发总结.md
HaHafeng f4f1d09837 feat(dc/tool-c): Add pivot column ordering and NA handling features
Major features:
1. Pivot transformation enhancements:
   - Add option to keep unselected columns with 3 aggregation methods
   - Maintain original column order after pivot (aligned with source file)
   - Preserve pivot value order (first appearance order)

2. NA handling across 4 core functions:
   - Recode: Support keep/map/drop for NA values
   - Filter: Already supports is_null/not_null operators
   - Binning: Support keep/label/assign for NA values (fix nan display)
   - Conditional: Add is_null/not_null operators

3. UI improvements:
   - Enable column header tooltips with custom header component
   - Add closeable alert for 50-row preview
   - Fix page scrollbar issues

Modified files:
Python: pivot.py, recode.py, binning.py, conditional.py, main.py
Backend: SessionController, QuickActionController, QuickActionService
Frontend: PivotDialog, RecodeDialog, BinningDialog, ConditionalDialog, DataGrid, index

Status: Ready for testing
2025-12-09 14:40:14 +08:00

10 KiB
Raw Blame History

工具C - NA处理功能开发总结

📋 概述

目标在4个核心功能中添加对NA空值/缺失值)的显式处理,让用户能够明确看到并处理缺失值。

NA显示名称空值/NA(中英文结合)


已完成Python后端100%

1. recode.py - 数值映射

新增参数

  • na_handling: 'keep' | 'map' | 'drop'
    • keep: 保持为NA默认
    • map: 映射为指定值
    • drop: 删除包含NA的行
  • na_value: NA映射值当na_handling='map'时使用)

实现逻辑

if original_na_count > 0:
    na_mask = result[column].isna()
    
    if na_handling == 'keep':
        # 保持为NA已经是NA无需操作
        print(f'📊 NA处理保持为NA{original_na_count}个)')
    
    elif na_handling == 'map':
        # 映射为指定值
        result.loc[na_mask, target_column] = na_value
        print(f'📊 NA处理映射为 {na_value}{original_na_count}个)')
    
    elif na_handling == 'drop':
        # 删除包含NA的行
        result = result[~na_mask].copy()

2. filter.py - 高级筛选

已支持is_nullnot_null 运算符

无需修改,原有代码已经支持!

elif operator == 'is_null':
    mask = df[column].isna()
elif operator == 'not_null':
    mask = df[column].notna()

3. binning.py - 生成分类变量

新增参数

  • na_handling: 'keep' | 'label' | 'assign'
    • keep: 保持为NA默认
    • label: 标记为指定标签(如"缺失"
    • assign: 分配到指定组
  • na_label: NA标签当na_handling='label'时使用)
  • na_assign_to: NA分配到的组索引当na_handling='assign'时使用)

实现逻辑

if original_na_count > 0:
    na_mask = result[column].isna()
    
    if na_handling == 'keep':
        # 保持为NA
        print(f'📊 NA处理保持为NA{original_na_count}个)')
    
    elif na_handling == 'label':
        # 标记为指定标签
        label_to_use = na_label if na_label else '空值/NA'
        result.loc[na_mask, new_column_name] = label_to_use
        print(f'📊 NA处理标记为 "{label_to_use}"{original_na_count}个)')
    
    elif na_handling == 'assign':
        # 分配到指定组
        if labels and na_assign_to is not None:
            result.loc[na_mask, new_column_name] = labels[na_assign_to]

4. conditional.py - 条件生成列

新增支持is_nullnot_null 运算符

elif operator == 'is_null':  # ✨ 新增:为空
    mask = result[column].isna()
elif operator == 'not_null':  # ✨ 新增:不为空
    mask = result[column].notna()

5. main.py - API请求模型

RecodeRequest

na_handling: str = 'keep'
na_value: Any = None

BinningRequest

na_handling: str = 'keep'
na_label: str = None
na_assign_to: int = None

FilterRequest 和 ConditionalRequest 无需修改,已支持


🔄 待完成Node.js后端

QuickActionService.ts

需要更新的接口

  1. RecodeParams
interface RecodeParams {
  column: string;
  mapping: Record<string, any>;
  createNewColumn?: boolean;
  newColumnName?: string;
  naHandling?: 'keep' | 'map' | 'drop';  // ✨ 新增
  naValue?: any;  // ✨ 新增
}
  1. BinningParams
interface BinningParams {
  column: string;
  method: 'custom' | 'equal_width' | 'equal_freq';
  newColumnName: string;
  bins?: number[];
  labels?: string[];
  numBins?: number;
  naHandling?: 'keep' | 'label' | 'assign';  // ✨ 新增
  naLabel?: string;  // ✨ 新增
  naAssignTo?: number;  // ✨ 新增
}

API调用(自动传递所有参数,无需特殊处理)


🎨 待完成前端UI

1. RecodeDialog.tsx - 数值映射

UI设计

┌─────────────────────────────────────┐
│ 数值映射                     [X]    │
├─────────────────────────────────────┤
│ 选择列:[婚姻状况▼]                 │
│                                      │
│ 唯一值映射:                         │
│ ┌──────────────────────────────┐   │
│ │ 原始值      → 新值            │   │
│ │ 已婚        → [1        ]     │   │
│ │ 未婚        → [0        ]     │   │
│ │ 空值/NA     → [▼]            │ ⭐│
│ │   ├─ 保持为NA默认         │   │
│ │   ├─ 映射为:[____]           │   │
│ │   └─ 删除该行                 │   │
│ └──────────────────────────────┘   │
│                                      │
│  当前有125个空值15.6%        │
└─────────────────────────────────────┘

实现要点

  1. 调用/api/v1/dc/tool-c/sessions/:id/unique-values检测是否有NA
  2. 如果有NA显示"空值/NA"特殊行
  3. 提供3种选择保持NA / 映射为指定值 / 删除行

2. FilterDialog.tsx - 高级筛选

UI设计

条件:
[婚姻状况▼] [运算符▼]
  • 等于
  • 不等于
  • 为空          ← ✨ 新增
  • 不为空        ← ✨ 新增
  • ...

实现要点

  1. 在运算符下拉菜单中添加"为空"和"不为空"选项
  2. 当选择这两个运算符时,隐藏"值"输入框(不需要输入值)

3. BinningDialog.tsx - 生成分类变量

UI设计

┌─────────────────────────────────────┐
│ 生成分类变量                 [X]    │
├─────────────────────────────────────┤
│ 原始列:[年龄▼]                     │
│ ...分组规则...                       │
│                                      │
│ ⚠️ 空值处理:                       │ ⭐
│ ⚪ 保持为空(默认)                 │
│ ⚪ 标记为:[缺失___]                │
│ ⚪ 分配到组:[第1组▼]               │
│                                      │
│  当前有25个空值3.1%          │
└─────────────────────────────────────┘

实现要点

  1. 添加Radio Group for NA处理方式
  2. 根据选择显示相应的输入框
  3. 传递naHandlingnaLabelnaAssignTo参数

4. ConditionalDialog.tsx - 条件生成列

UI设计

规则1
如果 [婚姻状况▼] [运算符▼]
  • 等于
  • 不等于
  • 为空          ← ✨ 新增
  • 不为空        ← ✨ 新增
  • ...
则填充:[低风险        ]

实现要点

  1. 与FilterDialog类似在运算符下拉菜单中添加"为空"和"不为空"
  2. 这两个运算符不需要输入值

🧪 测试用例

测试数据准备

ID,婚姻状况,年龄,收缩压
1,已婚,45,120
2,未婚,35,130
3,,50,  # ← NA
4,离异,60,
5,,NA,140

测试场景

编号 功能 测试场景 预期结果
TC-1 数值映射 - 保持NA 婚姻状况:已婚=1未婚=0NA=保持 NA行的新列为NA
TC-2 数值映射 - 映射NA 婚姻状况:已婚=1未婚=0NA=映射为9 NA行的新列为9
TC-3 数值映射 - 删除NA 婚姻状况:已婚=1未婚=0NA=删除 NA行被删除总行数减少
TC-4 高级筛选 - 为空 筛选"婚姻状况"为空 只保留NA行
TC-5 高级筛选 - 不为空 筛选"婚姻状况"不为空 只保留非NA行
TC-6 生成分类变量 - 保持NA 年龄分组NA保持 NA行的新列为NA
TC-7 生成分类变量 - 标记NA 年龄分组NA标记为"缺失" NA行的新列为"缺失"
TC-8 生成分类变量 - 分配NA 年龄分组NA分配到第1组 NA行的新列为第1组标签
TC-9 条件生成列 - 为空 如果婚姻状况为空,则"未知" NA行的新列为"未知"
TC-10 条件生成列 - 不为空 如果婚姻状况不为空,则"已知" 非NA行的新列为"已知"

📊 开发进度

阶段 状态 备注
Python后端 - recode.py 100% 已完成
Python后端 - filter.py 100% 已支持(无需修改)
Python后端 - binning.py 100% 已完成
Python后端 - conditional.py 100% 已完成
Python后端 - main.py 100% 已完成
Node.js后端 100% 已完成(参数传递)
前端 - RecodeDialog 100% 已完成NA处理下拉菜单
前端 - FilterDialog 100% 已完成已支持is_null/not_null
前端 - BinningDialog 100% 已完成NA处理Radio Group
前端 - ConditionalDialog 100% 已完成添加is_null/not_null
测试 待测试 等待用户测试验证

🎯 下一步行动

  1. Node.js后端预计15分钟

    • 更新RecodeParams接口
    • 更新BinningParams接口
    • FilterParams和ConditionalParams无需修改
  2. 前端UI预计2小时

    • RecodeDialog添加NA处理下拉菜单45分钟
    • FilterDialog添加"为空"/"不为空"运算符15分钟
    • BinningDialog添加NA处理Radio Group30分钟
    • ConditionalDialog添加"为空"/"不为空"运算符30分钟
  3. 测试预计30分钟

    • 执行10个测试用例
    • 修复发现的问题

总计剩余时间约3小时


📝 技术要点

Python端

  • 使用df[column].isna()检测NA
  • 使用df.loc[mask, col] = value填充NA
  • 使用df[~mask]删除NA行
  • 统计并打印NA处理信息

前端端

  • 在获取unique values时检测NA
  • 使用<空值/NA>作为显示名称
  • 根据用户选择构造请求参数
  • 显示NA统计信息如"当前有125个空值"

验收标准

  • 用户能明确看到NA的存在
  • 用户能选择如何处理NA
  • 处理后的结果符合用户选择
  • 所有功能的NA处理方式清晰统一

文档创建时间2025-12-09
Python后端开发状态 已完成
剩余工作Node.js后端 + 前端UI + 测试