# 工具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'时使用) **实现逻辑**: ```python 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_null` 和 `not_null` 运算符 无需修改,原有代码已经支持! ```python 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'时使用) **实现逻辑**: ```python 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_null` 和 `not_null` 运算符 ```python elif operator == 'is_null': # ✨ 新增:为空 mask = result[column].isna() elif operator == 'not_null': # ✨ 新增:不为空 mask = result[column].notna() ``` ### 5. main.py - API请求模型 ✅ **RecodeRequest**: ```python na_handling: str = 'keep' na_value: Any = None ``` **BinningRequest**: ```python na_handling: str = 'keep' na_label: str = None na_assign_to: int = None ``` **FilterRequest 和 ConditionalRequest**: 无需修改,已支持 --- ## 🔄 待完成:Node.js后端 ### QuickActionService.ts **需要更新的接口**: 1. **RecodeParams**: ```typescript interface RecodeParams { column: string; mapping: Record; createNewColumn?: boolean; newColumnName?: string; naHandling?: 'keep' | 'map' | 'drop'; // ✨ 新增 naValue?: any; // ✨ 新增 } ``` 2. **BinningParams**: ```typescript 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. 传递`naHandling`、`naLabel`、`naAssignTo`参数 ### 4. ConditionalDialog.tsx - 条件生成列 **UI设计**: ``` 规则1: 如果 [婚姻状况▼] [运算符▼] • 等于 • 不等于 • 为空 ← ✨ 新增 • 不为空 ← ✨ 新增 • ... 则填充:[低风险 ] ``` **实现要点**: 1. 与FilterDialog类似,在运算符下拉菜单中添加"为空"和"不为空" 2. 这两个运算符不需要输入值 --- ## 🧪 测试用例 ### 测试数据准备 ```csv ID,婚姻状况,年龄,收缩压 1,已婚,45,120 2,未婚,35,130 3,,50, # ← NA 4,离异,60, 5,,NA,140 ``` ### 测试场景 | 编号 | 功能 | 测试场景 | 预期结果 | |------|------|----------|----------| | TC-1 | 数值映射 - 保持NA | 婚姻状况:已婚=1,未婚=0,NA=保持 | NA行的新列为NA ✅ | | TC-2 | 数值映射 - 映射NA | 婚姻状况:已婚=1,未婚=0,NA=映射为9 | NA行的新列为9 ✅ | | TC-3 | 数值映射 - 删除NA | 婚姻状况:已婚=1,未婚=0,NA=删除 | 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 Group(30分钟) - 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 + 测试