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

340 lines
10 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 工具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<string, any>;
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未婚=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 + 测试