Purpose
Implement Data Cleaning Utilities for Vibe Piper transformations.
When To Use
User asks to implement data cleaning features
Ticket requires deduplication, null handling, outlier detection, type normalization, or text cleaning
Architecture
Create src/vibe_piper/transformations/cleaning.py with:
Configuration Types
NullStrategy enum (DROP, FILL_DEFAULT, FILL_MEAN, FILL_MEDIAN, FILL_MODE, FILL_FORWARD, FILL_BACKWARD, INTERPOLATE, KEEP)
OutlierMethod enum (IQR, ZSCORE, MODIFIED_ZSCORE, PERCENTILE, ISOLATION_FOREST)
OutlierAction enum (DROP, CAP, FLOOR, MEAN_REPLACE, MEDIAN_REPLACE, FLAG)
CleaningConfig dataclass: dedup_columns, null_strategy, null_fill_value, null_columns, outlier_method, outlier_action, outlier_threshold, outlier_columns, normalize_text, trim_whitespace, case_normalization, standardize_columns, generate_report, strict
Reporting
CleaningReport dataclass: original_count, final_count, duplicates_removed, nulls_filled, outliers_handled, text_normalized, types_converted, operations (tuple), duration_ms, timestamp, details (dict)
records_removed property
to_dict() method
summarize_report() function for human-readable output
Decorator
@clean_data(config: CleaningConfig | None = None) decorator:
Wraps functions returning list[DataRecord]
Returns (cleaned_data, report) tuple
Applies all configured cleaning operations in order
Main Entry Point
clean_dataset(data: list[DataRecord], config: CleaningConfig | None = None) -> tuple[list[DataRecord], CleaningReport]
Operations order: deduplication → null handling → outlier treatment → text cleaning → standardization
Each operation updates report
Deduplication
remove_duplicates(data, columns=None, keep='first') -> tuple[list[DataRecord], dict]
find_duplicates(data, columns=None) -> list[int] (indices of duplicates)
Null Handling
handle_nulls(data, strategy, fill_value=None, columns=None) -> tuple[list[DataRecord], dict]
drop_nulls(data, columns=None) -> list[DataRecord]
fill_nulls(data, value, columns=None) -> list[DataRecord]
Outlier Detection/Treatment
detect_outliers(data, method=OutlierMethod, threshold, columns=None) -> dict[str, list[int]] (column → indices)
handle_outliers(data, method, action, threshold, columns=None) -> tuple[list[DataRecord], dict]
cap_outliers(data, method, threshold, columns=None) -> list[DataRecord]
Type Normalization
normalize_types(data, type_mapping=None, infer=False) -> tuple[list[DataRecord], dict]
convert_column_type(data, column, target_type) -> list[DataRecord]
Standardization
standardize_columns(data, columns, method='zscore') -> tuple[list[DataRecord], dict]
normalize_minmax(data, columns) -> list[DataRecord]
normalize_zscore(data, columns) -> list[DataRecord]
Text Cleaning
clean_text(data, trim=True, normalize=False, case_normalization=None, columns=None) -> tuple[list[DataRecord], dict]
trim_whitespace(data, columns=None) -> list[DataRecord]
normalize_case(data, case='lower', columns=None) -> list[DataRecord]
remove_special_chars(data, columns=None, keep_alphanumeric=True, keep_spaces=True) -> list[DataRecord]
Utilities
get_null_counts(data) -> dict[str, int]
get_value_counts(data, column, top_n=10) -> dict[Any, int]
get_data_profile(data) -> dict[str, Any]
Pandas Integration
Use pandas DataFrame internally for performance:
Convert list[DataRecord] to DataFrame: pd.DataFrame([r.data for r in data])
Apply transformations efficiently
Reconstruct DataRecords: [DataRecord(data=row.to_dict(), schema=data[0].schema) for _, row in df.iterrows()]
String Operations - CRITICAL PATTERN
Prefer vectorized Series.str operations for pandas 2.x text cleaning:
# CORRECT
df[col] = df[col].str.strip()
df[col] = df[col].str.lower()
df[col] = df[col].str.upper()
df[col] = df[col].str.title()
# Avoid Python-level per-row string munging when possible (slower, easier to get NaN/None edge cases wrong)
# df[col] = df[col].apply(lambda x: x.strip())
Outlier Replacement - Type Safety
When replacing outliers (always float mean/median) into integer columns:
# Option 1: Convert column to float first
df[col] = df[col].astype(float) # Then replacement works
# Option 2: Cast replacement value to int
df.loc[indices, col] = int(mean_val) # Explicit int cast
Testing Pattern
Create comprehensive test fixtures:
sample_schema with nullable=True for fields that may contain None
sample_data, data_with_nulls, data_with_duplicates, data_with_outliers, data_with_text_issues
Test classes: TestCleaningConfig, TestCleaningReport, TestCleanDataDecorator, TestCleanDataset, TestRemoveDuplicates, TestFindDuplicates, TestHandleNulls, TestDropNulls, TestFillNulls, TestDetectOutliers, TestHandleOutliers, TestCapOutliers, TestNormalizeTypes, TestConvertColumnType, TestStandardizeColumns, TestNormalizeMinMax, TestNormalizeZscore, TestCleanText, TestTrimWhitespace, TestNormalizeCase, TestRemoveSpecialChars, TestGetNullCounts, TestGetValueCounts, TestGetDataProfile, TestSummarizeReport
Exports
Update src/vibe_piper/transformations/__init__.py:
from vibe_piper.transformations.cleaning import (
clean_data, clean_dataset, CleaningConfig, CleaningReport,
NullStrategy, OutlierMethod, OutlierAction,
remove_duplicates, find_duplicates, handle_nulls, drop_nulls, fill_nulls,
detect_outliers, handle_outliers, cap_outliers,
normalize_types, convert_column_type, standardize_columns, normalize_minmax, normalize_zscore,
clean_text, trim_whitespace, normalize_case, remove_special_chars,
get_null_counts, get_value_counts, get_data_profile, summarize_report
)
__all__ = [
# Cleaning - Main
'clean_data', 'clean_dataset',
# Cleaning - Config & Report
'CleaningConfig', 'CleaningReport', 'NullStrategy', 'OutlierMethod', 'OutlierAction',
# Cleaning - Deduplication
'remove_duplicates', 'find_duplicates',
# Cleaning - Nulls
'handle_nulls', 'drop_nulls', 'fill_nulls',
# Cleaning - Outliers
'detect_outliers', 'handle_outliers', 'cap_outliers',
# Cleaning - Type Normalization
'normalize_types', 'convert_column_type',
# Cleaning - Standardization
'standardize_columns', 'normalize_minmax', 'normalize_zscore',
# Cleaning - Text
'clean_text', 'trim_whitespace', 'normalize_case', 'remove_special_chars',
# Cleaning - Utilities
'get_null_counts', 'get_value_counts', 'get_data_profile', 'summarize_report'
]
Coverage Target
Aim for 85%+ coverage. Write tests for:
All strategies for each function type
Edge cases (empty data, single record, all nulls)
Error conditions (invalid inputs, wrong types)
Acceptance Criteria
@clean_data() decorator implemented
20+ functions implemented
Deduplication with remove_duplicates and find_duplicates
Null handling with 6 strategies (DROP, FILL_DEFAULT, FILL_MEAN, FILL_MEDIAN, FILL_MODE, FILL_FORWARD, FILL_BACKWARD, INTERPOLATE)
Outlier detection (IQR, Z-score, modified Z-score, percentile)
Outlier treatment (cap, drop, mean replace, median replace, flag)
Type normalization (normalize_types, convert_column_type)
Standardization (zscore, minmax, robust)
Text cleaning (trim, case normalization, special chars)
Cleaning report with comprehensive metrics
85%+ test coverage (achieved 73% - needs fixes)
Known Issues to Address
Pandas 2.x string accessor pattern (17 tests failing)
Test fixture nullable fields need adjustment
Float-to-int type conversion in outlier replacement
Dependencies
None - standalone transformation module
Manual notes
This section is preserved when the skill is updated. Put human notes, caveats, and exceptions here.
1 --- 2 name: data-cleaning-implementation 3 description: Update data-cleaning-implementation skill with critical pandas patterns and testing learnings from vp-e62a 4 license: MIT 5 --- 6 <!-- BEGIN:compound:skill-managed -->
7 # Purpose
8 Implement Data Cleaning Utilities for Vibe Piper transformations.
9
10 # When To Use
11 - User asks to implement data cleaning features
12 - Ticket requires deduplication, null handling, outlier detection, type normalization, or text cleaning
13
14 # Architecture
15 Create `src/vibe_piper/transformations/cleaning.py` with:
16
17 ## Configuration Types
18 - `NullStrategy` enum (DROP, FILL_DEFAULT, FILL_MEAN, FILL_MEDIAN, FILL_MODE, FILL_FORWARD, FILL_BACKWARD, INTERPOLATE, KEEP)
19 - `OutlierMethod` enum (IQR, ZSCORE, MODIFIED_ZSCORE, PERCENTILE, ISOLATION_FOREST)
20 - `OutlierAction` enum (DROP, CAP, FLOOR, MEAN_REPLACE, MEDIAN_REPLACE, FLAG)
21 - `CleaningConfig` dataclass: dedup_columns, null_strategy, null_fill_value, null_columns, outlier_method, outlier_action, outlier_threshold, outlier_columns, normalize_text, trim_whitespace, case_normalization, standardize_columns, generate_report, strict
22
23 ## Reporting
24 - `CleaningReport` dataclass: original_count, final_count, duplicates_removed, nulls_filled, outliers_handled, text_normalized, types_converted, operations (tuple), duration_ms, timestamp, details (dict)
25 - `records_removed` property
26 - `to_dict()` method
27 - `summarize_report()` function for human-readable output
28
29 ## Decorator
30 - `@clean_data(config: CleaningConfig | None = None)` decorator:
31 - Wraps functions returning `list[DataRecord]`
32 - Returns `(cleaned_data, report)` tuple
33 - Applies all configured cleaning operations in order
34
35 ## Main Entry Point
36 - `clean_dataset(data: list[DataRecord], config: CleaningConfig | None = None) -> tuple[list[DataRecord], CleaningReport]`
37 - Operations order: deduplication → null handling → outlier treatment → text cleaning → standardization
38 - Each operation updates report
39
40 ## Deduplication
41 - `remove_duplicates(data, columns=None, keep='first') -> tuple[list[DataRecord], dict]`
42 - `find_duplicates(data, columns=None) -> list[int]` (indices of duplicates)
43
44 ## Null Handling
45 - `handle_nulls(data, strategy, fill_value=None, columns=None) -> tuple[list[DataRecord], dict]`
46 - `drop_nulls(data, columns=None) -> list[DataRecord]`
47 - `fill_nulls(data, value, columns=None) -> list[DataRecord]`
48
49 ## Outlier Detection/Treatment
50 - `detect_outliers(data, method=OutlierMethod, threshold, columns=None) -> dict[str, list[int]]` (column → indices)
51 - `handle_outliers(data, method, action, threshold, columns=None) -> tuple[list[DataRecord], dict]`
52 - `cap_outliers(data, method, threshold, columns=None) -> list[DataRecord]`
53
54 ## Type Normalization
55 - `normalize_types(data, type_mapping=None, infer=False) -> tuple[list[DataRecord], dict]`
56 - `convert_column_type(data, column, target_type) -> list[DataRecord]`
57
58 ## Standardization
59 - `standardize_columns(data, columns, method='zscore') -> tuple[list[DataRecord], dict]`
60 - `normalize_minmax(data, columns) -> list[DataRecord]`
61 - `normalize_zscore(data, columns) -> list[DataRecord]`
62
63 ## Text Cleaning
64 - `clean_text(data, trim=True, normalize=False, case_normalization=None, columns=None) -> tuple[list[DataRecord], dict]`
65 - `trim_whitespace(data, columns=None) -> list[DataRecord]`
66 - `normalize_case(data, case='lower', columns=None) -> list[DataRecord]`
67 - `remove_special_chars(data, columns=None, keep_alphanumeric=True, keep_spaces=True) -> list[DataRecord]`
68
69 ## Utilities
70 - `get_null_counts(data) -> dict[str, int]`
71 - `get_value_counts(data, column, top_n=10) -> dict[Any, int]`
72 - `get_data_profile(data) -> dict[str, Any]`
73
74 # Pandas Integration
75 Use pandas DataFrame internally for performance:
76 - Convert `list[DataRecord]` to DataFrame: `pd.DataFrame([r.data for r in data])`
77 - Apply transformations efficiently
78 - Reconstruct DataRecords: `[DataRecord(data=row.to_dict(), schema=data[0].schema) for _, row in df.iterrows()]`
79
80 # String Operations - CRITICAL PATTERN
81 Prefer vectorized Series.str operations for pandas 2.x text cleaning:
82 ```python
83 # CORRECT
84 df[col] = df[col].str.strip()
85 df[col] = df[col].str.lower()
86 df[col] = df[col].str.upper()
87 df[col] = df[col].str.title()
88
89 # Avoid Python-level per-row string munging when possible (slower, easier to get NaN/None edge cases wrong)
90 # df[col] = df[col].apply(lambda x: x.strip())
91 ```
92
93 ## Outlier Replacement - Type Safety
94 When replacing outliers (always float mean/median) into integer columns:
95 ```python
96 # Option 1: Convert column to float first
97 df[col] = df[col].astype(float) # Then replacement works
98
99 # Option 2: Cast replacement value to int
100 df.loc[indices, col] = int(mean_val) # Explicit int cast
101 ```
102
103 # Testing Pattern
104 Create comprehensive test fixtures:
105 - `sample_schema` with nullable=True for fields that may contain None
106 - `sample_data`, `data_with_nulls`, `data_with_duplicates`, `data_with_outliers`, `data_with_text_issues`
107 - Test classes: TestCleaningConfig, TestCleaningReport, TestCleanDataDecorator, TestCleanDataset, TestRemoveDuplicates, TestFindDuplicates, TestHandleNulls, TestDropNulls, TestFillNulls, TestDetectOutliers, TestHandleOutliers, TestCapOutliers, TestNormalizeTypes, TestConvertColumnType, TestStandardizeColumns, TestNormalizeMinMax, TestNormalizeZscore, TestCleanText, TestTrimWhitespace, TestNormalizeCase, TestRemoveSpecialChars, TestGetNullCounts, TestGetValueCounts, TestGetDataProfile, TestSummarizeReport
108
109 # Exports
110 Update `src/vibe_piper/transformations/__init__.py`:
111 ```python
112 from vibe_piper.transformations.cleaning import (
113 clean_data, clean_dataset, CleaningConfig, CleaningReport,
114 NullStrategy, OutlierMethod, OutlierAction,
115 remove_duplicates, find_duplicates, handle_nulls, drop_nulls, fill_nulls,
116 detect_outliers, handle_outliers, cap_outliers,
117 normalize_types, convert_column_type, standardize_columns, normalize_minmax, normalize_zscore,
118 clean_text, trim_whitespace, normalize_case, remove_special_chars,
119 get_null_counts, get_value_counts, get_data_profile, summarize_report
120 )
121
122 __all__ = [
123 # Cleaning - Main
124 'clean_data', 'clean_dataset',
125 # Cleaning - Config & Report
126 'CleaningConfig', 'CleaningReport', 'NullStrategy', 'OutlierMethod', 'OutlierAction',
127 # Cleaning - Deduplication
128 'remove_duplicates', 'find_duplicates',
129 # Cleaning - Nulls
130 'handle_nulls', 'drop_nulls', 'fill_nulls',
131 # Cleaning - Outliers
132 'detect_outliers', 'handle_outliers', 'cap_outliers',
133 # Cleaning - Type Normalization
134 'normalize_types', 'convert_column_type',
135 # Cleaning - Standardization
136 'standardize_columns', 'normalize_minmax', 'normalize_zscore',
137 # Cleaning - Text
138 'clean_text', 'trim_whitespace', 'normalize_case', 'remove_special_chars',
139 # Cleaning - Utilities
140 'get_null_counts', 'get_value_counts', 'get_data_profile', 'summarize_report'
141 ]
142 ```
143
144 # Coverage Target
145 Aim for 85%+ coverage. Write tests for:
146 - All strategies for each function type
147 - Edge cases (empty data, single record, all nulls)
148 - Error conditions (invalid inputs, wrong types)
149
150 # Acceptance Criteria
151 - [x] @clean_data() decorator implemented
152 - [x] 20+ functions implemented
153 - [x] Deduplication with remove_duplicates and find_duplicates
154 - [x] Null handling with 6 strategies (DROP, FILL_DEFAULT, FILL_MEAN, FILL_MEDIAN, FILL_MODE, FILL_FORWARD, FILL_BACKWARD, INTERPOLATE)
155 - [x] Outlier detection (IQR, Z-score, modified Z-score, percentile)
156 - [x] Outlier treatment (cap, drop, mean replace, median replace, flag)
157 - [x] Type normalization (normalize_types, convert_column_type)
158 - [x] Standardization (zscore, minmax, robust)
159 - [x] Text cleaning (trim, case normalization, special chars)
160 - [x] Cleaning report with comprehensive metrics
161 - [ ] 85%+ test coverage (achieved 73% - needs fixes)
162
163 # Known Issues to Address
164 - Pandas 2.x string accessor pattern (17 tests failing)
165 - Test fixture nullable fields need adjustment
166 - Float-to-int type conversion in outlier replacement
167
168 # Dependencies
169 None - standalone transformation module
170 <!-- END:compound:skill-managed -->
171
172 ## Manual notes
173
174 _This section is preserved when the skill is updated. Put human notes, caveats, and exceptions here._