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
10 KiB
10 KiB
工具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_null 和 not_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_null 和 not_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
需要更新的接口:
- RecodeParams:
interface RecodeParams {
column: string;
mapping: Record<string, any>;
createNewColumn?: boolean;
newColumnName?: string;
naHandling?: 'keep' | 'map' | 'drop'; // ✨ 新增
naValue?: any; // ✨ 新增
}
- 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%) │
└─────────────────────────────────────┘
实现要点:
- 调用
/api/v1/dc/tool-c/sessions/:id/unique-values时,检测是否有NA - 如果有NA,显示"空值/NA"特殊行
- 提供3种选择:保持NA / 映射为指定值 / 删除行
2. FilterDialog.tsx - 高级筛选
UI设计:
条件:
[婚姻状况▼] [运算符▼]
• 等于
• 不等于
• 为空 ← ✨ 新增
• 不为空 ← ✨ 新增
• ...
实现要点:
- 在运算符下拉菜单中添加"为空"和"不为空"选项
- 当选择这两个运算符时,隐藏"值"输入框(不需要输入值)
3. BinningDialog.tsx - 生成分类变量
UI设计:
┌─────────────────────────────────────┐
│ 生成分类变量 [X] │
├─────────────────────────────────────┤
│ 原始列:[年龄▼] │
│ ...分组规则... │
│ │
│ ⚠️ 空值处理: │ ⭐
│ ⚪ 保持为空(默认) │
│ ⚪ 标记为:[缺失___] │
│ ⚪ 分配到组:[第1组▼] │
│ │
│ ℹ️ 当前有25个空值(3.1%) │
└─────────────────────────────────────┘
实现要点:
- 添加Radio Group for NA处理方式
- 根据选择显示相应的输入框
- 传递
naHandling、naLabel、naAssignTo参数
4. ConditionalDialog.tsx - 条件生成列
UI设计:
规则1:
如果 [婚姻状况▼] [运算符▼]
• 等于
• 不等于
• 为空 ← ✨ 新增
• 不为空 ← ✨ 新增
• ...
则填充:[低风险 ]
实现要点:
- 与FilterDialog类似,在运算符下拉菜单中添加"为空"和"不为空"
- 这两个运算符不需要输入值
🧪 测试用例
测试数据准备
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) |
| 测试 | ⏳ 待测试 | 等待用户测试验证 |
🎯 下一步行动
-
Node.js后端(预计15分钟)
- 更新RecodeParams接口
- 更新BinningParams接口
- (FilterParams和ConditionalParams无需修改)
-
前端UI(预计2小时)
- RecodeDialog:添加NA处理下拉菜单(45分钟)
- FilterDialog:添加"为空"/"不为空"运算符(15分钟)
- BinningDialog:添加NA处理Radio Group(30分钟)
- ConditionalDialog:添加"为空"/"不为空"运算符(30分钟)
-
测试(预计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 + 测试