SECTION 1: LEARNING OBJECTIVES
By the end of this lesson, you will be able to:
-
Handle missing data in banking datasets using appropriate imputation methods.
-
Scale and normalize financial data for machine learning models.
-
Encode categorical variables (customer segments, transaction types) for modeling.
-
Create meaningful features from raw banking data (financial ratios, risk indicators).
-
Apply feature engineering techniques for time series and transactional data.
-
Select features using statistical methods and domain knowledge.
-
Understand the impact of preprocessing on model performance in banking.
SECTION 2: HANDLING MISSING DATA
2.1 Understanding Missing Data Mechanisms
Before handling missing data, understand WHY data is missing:
| Mechanism | Description | Banking Example |
|---|---|---|
| MCAR | Missing Completely at Random | A server glitch caused random records to be dropped |
| MAR | Missing at Random (depends on other variables) | High-income customers less likely to report income |
| MNAR | Missing Not at Random (depends on the missing value) | Customers with bad credit scores hide their scores |
2.2 Methods for Handling Missing Data
# ============= MISSING DATA HANDLING ============= import pandas as pd import numpy as np from sklearn.impute import SimpleImputer, KNNImputer from sklearn.experimental import enable_iterative_imputer from sklearn.impute import IterativeImputer # Create a sample dataset with missing values np.random.seed(42) sample_data = pd.DataFrame({ 'customer_id': range(1, 101), 'age': np.random.randint(18, 80, 100), 'annual_income': np.random.normal(80000, 30000, 100).clip(20000, 200000), 'credit_score': np.random.normal(700, 50, 100).clip(500, 850), 'dti_ratio': np.random.uniform(0.1, 0.5, 100), 'tenure_months': np.random.randint(1, 240, 100) }) # Introduce missing values sample_data.loc[np.random.choice(sample_data.index, 15, replace=False), 'credit_score'] = np.nan sample_data.loc[np.random.choice(sample_data.index, 10, replace=False), 'annual_income'] = np.nan sample_data.loc[np.random.choice(sample_data.index, 8, replace=False), 'dti_ratio'] = np.nan print("Original Data with Missing Values:") print(sample_data.isnull().sum()) class MissingDataHandler: """Handle missing data using various methods.""" def __init__(self, data): self.data = data.copy() self.missing_columns = data.columns[data.isnull().any()].tolist() def drop_missing(self, threshold=0.5): """Drop columns with > threshold missing values.""" missing_pct = self.data.isnull().mean() cols_to_drop = missing_pct[missing_pct > threshold].index.tolist() if cols_to_drop: print(f"Dropping columns: {cols_to_drop}") return self.data.drop(columns=cols_to_drop) return self.data def impute_constant(self, value=0): """Impute with a constant value.""" for col in self.missing_columns: self.data[col] = self.data[col].fillna(value) return self.data def impute_mean(self): """Impute with column mean.""" for col in self.missing_columns: if pd.api.types.is_numeric_dtype(self.data[col]): self.data[col] = self.data[col].fillna(self.data[col].mean()) return self.data def impute_median(self): """Impute with column median.""" for col in self.missing_columns: if pd.api.types.is_numeric_dtype(self.data[col]): self.data[col] = self.data[col].fillna(self.data[col].median()) return self.data def impute_forward_fill(self): """Forward fill (for time series data).""" self.data = self.data.fillna(method='ffill') return self.data def impute_interpolation(self): """Interpolation (for time series data).""" self.data = self.data.interpolate(method='linear') return self.data def impute_knn(self, n_neighbors=5): """KNN imputation.""" numeric_cols = self.data.select_dtypes(include=[np.number]).columns imputer = KNNImputer(n_neighbors=n_neighbors) self.data[numeric_cols] = imputer.fit_transform(self.data[numeric_cols]) return self.data def impute_iterative(self): """Iterative imputation (MICE).""" numeric_cols = self.data.select_dtypes(include=[np.number]).columns imputer = IterativeImputer(random_state=42) self.data[numeric_cols] = imputer.fit_transform(self.data[numeric_cols]) return self.data def compare_methods(self): """Compare different imputation methods.""" results = {} # Original missing counts original_missing = self.data.isnull().sum().sum() # Test methods methods = { 'Mean': self.impute_mean, 'Median': self.impute_median, 'KNN (k=3)': lambda: self.impute_knn(3), 'KNN (k=5)': lambda: self.impute_knn(5), 'Iterative': self.impute_iterative } for name, method in methods.items(): # Reset data test_data = self.data.copy() handler = MissingDataHandler(test_data) result = handler.__getattribute__(method.__name__ if hasattr(method, '__name__') else 'impute_knn')() results[name] = result.isnull().sum().sum() print("\n" + "="*60) print("COMPARISON OF IMPUTATION METHODS") print("="*60) print(f"Original missing values: {original_missing}") for name, missing_after in results.items(): print(f"{name}: {missing_after} missing values remaining") return results # Demonstrate missing data handling handler = MissingDataHandler(sample_data) print("\nMissing Data Summary:") print(handler.data.isnull().sum()) # Try different methods handler.compare_methods() # Use a specific method cleaned_data = handler.impute_median() print("\nCleaned Data (Median Imputation):") print(cleaned_data.isnull().sum())
2.3 Choosing the Right Imputation Method
# ============= GUIDELINES FOR IMPUTATION ============= def guide_imputation_choice(data): """Guide the choice of imputation method.""" missing_pct = data.isnull().mean() numeric_cols = data.select_dtypes(include=[np.number]).columns guidance = [] # Check missing percentage if missing_pct.max() > 0.5: guidance.append("⚠️ Columns with >50% missing may be better dropped") # Check data type for col in data.columns: if data[col].isnull().any(): if pd.api.types.is_numeric_dtype(data[col]): guidance.append(f"📊 {col}: Numeric - Consider mean/median imputation") elif pd.api.types.is_categorical_dtype(data[col]): guidance.append(f"📋 {col}: Categorical - Consider mode imputation") else: guidance.append(f"📝 {col}: Text/Object - Consider flagging missing values") # For time series data if 'date' in data.columns or 'timestamp' in data.columns: guidance.append("⏰ Time series data detected - Consider forward fill or interpolation") print("\n" + "="*60) print("IMPUTATION GUIDANCE") print("="*60) for g in guidance: print(g) return guidance # Get guidance guide_imputation_choice(sample_data)
SECTION 3: FEATURE SCALING & NORMALIZATION
3.1 Why Scale Financial Data?
Many machine learning algorithms require features to be on the same scale:
| Algorithm | Scaling Required? | Why |
|---|---|---|
| Logistic Regression | Yes | Gradient descent converges faster |
| SVM | Yes | Distance-based algorithm |
| K-Nearest Neighbors | Yes | Distance-based |
| Decision Trees | No | Splits are scale-invariant |
| Random Forest | No | Splits are scale-invariant |
| Neural Networks | Yes | Gradient descent |
3.2 Scaling Methods
# ============= FEATURE SCALING ============= from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler # Create sample data np.random.seed(42) scaling_data = pd.DataFrame({ 'customer_age': np.random.normal(45, 15, 1000).clip(18, 80), 'annual_income': np.random.lognormal(10.5, 0.6, 1000).clip(20000, 250000), 'credit_score': np.random.normal(700, 50, 1000).clip(500, 850), 'transaction_count': np.random.poisson(20, 1000).clip(0, 80) }) print("Original Data Statistics:") print(scaling_data.describe()) class FeatureScaler: """Scale features using different methods.""" def __init__(self, data): self.data = data.copy() self.scalers = {} def standard_scale(self, columns=None): """Standardization (z-score): mean=0, std=1.""" if columns is None: columns = self.data.select_dtypes(include=[np.number]).columns scaler = StandardScaler() self.data[columns] = scaler.fit_transform(self.data[columns]) self.scalers['standard'] = scaler print(f"Standardized {len(columns)} columns") return self.data def minmax_scale(self, columns=None, feature_range=(0, 1)): """Min-Max scaling: scale to [0, 1] range.""" if columns is None: columns = self.data.select_dtypes(include=[np.number]).columns scaler = MinMaxScaler(feature_range=feature_range) self.data[columns] = scaler.fit_transform(self.data[columns]) self.scalers['minmax'] = scaler print(f"Min-Max scaled {len(columns)} columns") return self.data def robust_scale(self, columns=None): """Robust scaling: uses median and IQR (robust to outliers).""" if columns is None: columns = self.data.select_dtypes(include=[np.number]).columns scaler = RobustScaler() self.data[columns] = scaler.fit_transform(self.data[columns]) self.scalers['robust'] = scaler print(f"Robust scaled {len(columns)} columns") return self.data def compare_scaling(self): """Compare scaling methods.""" columns = self.data.select_dtypes(include=[np.number]).columns results = {} for col in columns: results[col] = { 'original': self.data[col].describe() } # Test different scalers for method in ['standard', 'minmax', 'robust']: test_data = self.data.copy() scaler_obj = FeatureScaler(test_data) getattr(scaler_obj, f'{method}_scale')([col]) results[col][method] = scaler_obj.data[col].describe() # Print comparison for first column first_col = columns[0] print("\n" + "="*60) print(f"SCALING COMPARISON: {first_col}") print("="*60) for method, stats in results[first_col].items(): print(f"\n{method.upper()}:") print(f" Mean: {stats['mean']:.4f}") print(f" Std: {stats['std']:.4f}") print(f" Min: {stats['min']:.4f}") print(f" Max: {stats['max']:.4f}") return results # Demonstrate scaling scaler = FeatureScaler(scaling_data) # Try different scaling methods print("\n" + "="*60) print("FEATURE SCALING DEMONSTRATION") print("="*60) # Original print("\nOriginal Statistics (Annual Income):") print(scaling_data['annual_income'].describe()) # Standard scale standard_scaled = scaler.standard_scale(['annual_income']) print("\nStandardized (Annual Income):") print(standard_scaled['annual_income'].describe()) # Min-Max scale scaler = FeatureScaler(scaling_data) minmax_scaled = scaler.minmax_scale(['annual_income']) print("\nMin-Max Scaled (Annual Income):") print(minmax_scaled['annual_income'].describe()) # Compare scaling methods scaler.compare_scaling()
SECTION 4: ENCODING CATEGORICAL VARIABLES
4.1 Types of Categorical Data
| Type | Description | Banking Example |
|---|---|---|
| Nominal | No inherent order | State (CA, NY, TX), Transaction type |
| Ordinal | Has inherent order | Customer segment (Premium > Standard > Basic) |
| Binary | Two categories | Is_active (True/False), Has_loan (Yes/No) |
4.2 Encoding Methods
# ============= ENCODING CATEGORICAL VARIABLES ============= from sklearn.preprocessing import LabelEncoder, OneHotEncoder import pandas as pd # Create sample categorical data categorical_data = pd.DataFrame({ 'customer_id': range(1, 101), 'segment': np.random.choice(['Premium', 'Standard', 'Basic'], 100, p=[0.2, 0.5, 0.3]), 'state': np.random.choice(['CA', 'NY', 'TX', 'FL', 'IL'], 100), 'has_credit_card': np.random.choice([0, 1], 100, p=[0.3, 0.7]), 'risk_tier': np.random.choice(['Low', 'Medium', 'High'], 100, p=[0.4, 0.4, 0.2]) }) print("Sample Categorical Data:") print(categorical_data.head()) class CategoricalEncoder: """Encode categorical variables.""" def __init__(self, data): self.data = data.copy() self.encoders = {} def label_encode(self, column): """Label encoding (integer encoding).""" encoder = LabelEncoder() self.data[f'{column}_encoded'] = encoder.fit_transform(self.data[column]) self.encoders[column] = encoder print(f"Label encoded: {column}") print(f" Mapping: {dict(zip(encoder.classes_, encoder.transform(encoder.classes_)))}") return self.data def one_hot_encode(self, column): """One-hot encoding (create binary columns).""" one_hot = pd.get_dummies(self.data[column], prefix=column) self.data = pd.concat([self.data, one_hot], axis=1) self.data = self.data.drop(column, axis=1) print(f"One-hot encoded: {column}") print(f" Created {len(one_hot.columns)} columns: {list(one_hot.columns)}") return self.data def ordinal_encode(self, column, ordering): """Ordinal encoding (for ordered categories).""" mapping = {category: rank for rank, category in enumerate(ordering)} self.data[f'{column}_ordinal'] = self.data[column].map(mapping) self.encoders[column] = mapping print(f"Ordinal encoded: {column}") print(f" Mapping: {mapping}") return self.data def binary_encode(self, column, positive_value=1, negative_value=0): """Binary encoding (for binary categories).""" self.data[f'{column}_binary'] = self.data[column].map( {self.data[column].unique()[0]: positive_value, self.data[column].unique()[1]: negative_value} ) print(f"Binary encoded: {column}") return self.data # Demonstrate encoding encoder = CategoricalEncoder(categorical_data) # Label encode segment encoder.label_encode('segment') # One-hot encode state encoder.one_hot_encode('state') # Ordinal encode risk tier encoder.ordinal_encode('risk_tier', ['Low', 'Medium', 'High']) # Binary encode has_credit_card encoder.binary_encode('has_credit_card') print("\nEncoded Data:") print(encoder.data.head()) # Summary of encoding print("\n" + "="*60) print("ENCODING SUMMARY") print("="*60) encoded_cols = [col for col in encoder.data.columns if col not in categorical_data.columns] print(f"New columns created: {len(encoded_cols)}") print(f"Columns: {encoded_cols}")
SECTION 5: FEATURE ENGINEERING FOR BANKING
5.1 Creating Financial Features
# ============= FEATURE ENGINEERING ============= # Create sample banking data np.random.seed(42) n_customers = 500 banking_data = pd.DataFrame({ 'customer_id': range(1, n_customers + 1), 'monthly_income': np.random.normal(6000, 2000, n_customers).clip(1500, 20000), 'monthly_expenses': np.random.normal(4000, 1500, n_customers).clip(500, 15000), 'credit_card_balance': np.random.normal(5000, 3000, n_customers).clip(0, 20000), 'credit_card_limit': np.random.normal(15000, 5000, n_customers).clip(5000, 40000), 'loan_balance': np.random.normal(100000, 50000, n_customers).clip(0, 300000), 'savings_balance': np.random.normal(30000, 20000, n_customers).clip(0, 100000), 'tenure_months': np.random.randint(1, 240, n_customers), 'num_transactions': np.random.poisson(20, n_customers).clip(0, 80), 'num_late_payments': np.random.poisson(0.5, n_customers).clip(0, 10) }) class FinancialFeatureEngineer: """Create financial features from raw banking data.""" def __init__(self, data): self.data = data.copy() def create_ratio_features(self): """Create financial ratios.""" self.data['savings_rate'] = self.data['savings_balance'] / self.data['monthly_income'] self.data['debt_to_income_ratio'] = (self.data['credit_card_balance'] + self.data['loan_balance']) / self.data['monthly_income'] self.data['credit_utilization'] = self.data['credit_card_balance'] / self.data['credit_card_limit'] self.data['expense_to_income'] = self.data['monthly_expenses'] / self.data['monthly_income'] self.data['savings_to_debt'] = self.data['savings_balance'] / (self.data['credit_card_balance'] + self.data['loan_balance'] + 1) print("Created ratio features:") print(f" • savings_rate: {self.data['savings_rate'].describe()}") print(f" • debt_to_income_ratio: {self.data['debt_to_income_ratio'].describe()}") print(f" • credit_utilization: {self.data['credit_utilization'].describe()}") return self.data def create_risk_features(self): """Create risk indicators.""" # Risk flags self.data['high_utilization'] = (self.data['credit_utilization'] > 0.8).astype(int) self.data['high_debt'] = (self.data['debt_to_income_ratio'] > 0.43).astype(int) self.data['low_savings'] = (self.data['savings_rate'] < 0.1).astype(int) self.data['has_late_payments'] = (self.data['num_late_payments'] > 0).astype(int) # Composite risk score self.data['risk_score'] = ( self.data['high_utilization'] * 3 + self.data['high_debt'] * 4 + self.data['low_savings'] * 2 + self.data['has_late_payments'] * 3 ) print("Created risk features:") print(f" • high_utilization: {self.data['high_utilization'].sum()} customers") print(f" • high_debt: {self.data['high_debt'].sum()} customers") print(f" • risk_score: {self.data['risk_score'].describe()}") return self.data def create_behavioral_features(self): """Create behavioral features.""" # Tenure categories self.data['tenure_category'] = pd.cut( self.data['tenure_months'], bins=[0, 12, 36, 120, float('inf')], labels=['New', 'Medium', 'Long', 'Veteran'] ) # Transaction intensity self.data['transaction_intensity'] = self.data['num_transactions'] / (self.data['tenure_months'] / 12) self.data['transaction_intensity_category'] = pd.cut( self.data['transaction_intensity'], bins=[0, 10, 30, float('inf')], labels=['Low', 'Medium', 'High'] ) # Total balance self.data['total_balance'] = ( self.data['savings_balance'] - self.data['credit_card_balance'] - self.data['loan_balance'] ) self.data['positive_balance'] = (self.data['total_balance'] > 0).astype(int) print("Created behavioral features:") print(f" • tenure_category: {self.data['tenure_category'].value_counts().to_dict()}") print(f" • transaction_intensity_category: {self.data['transaction_intensity_category'].value_counts().to_dict()}") print(f" • positive_balance: {self.data['positive_balance'].sum()} customers") return self.data def create_interaction_features(self): """Create interaction features.""" # Income × Tenure (loyalty signal) self.data['income_x_tenure'] = self.data['monthly_income'] * self.data['tenure_months'] / 1000 # Balance × Utilization (risk signal) self.data['balance_x_utilization'] = self.data['credit_card_balance'] * self.data['credit_utilization'] # Savings × Income (wealth building signal) self.data['savings_x_income'] = self.data['savings_balance'] * self.data['monthly_income'] / 10000 print("Created interaction features:") print(f" • income_x_tenure: {self.data['income_x_tenure'].describe()}") print(f" • balance_x_utilization: {self.data['balance_x_utilization'].describe()}") return self.data def engineer_features(self): """Run all feature engineering steps.""" self.create_ratio_features() self.create_risk_features() self.create_behavioral_features() self.create_interaction_features() print("\n" + "="*60) print("FEATURE ENGINEERING COMPLETE") print("="*60) print(f"Original features: 9") print(f"New features: {len(self.data.columns) - 9}") print(f"Total features: {len(self.data.columns)}") return self.data # Demonstrate feature engineering engineer = FinancialFeatureEngineer(banking_data) engineered_data = engineer.engineer_features() print("\nFeature Engineering Results:") print(f"\nSample Data with New Features:") print(engineered_data.head()) print(f"\nAll Features ({len(engineered_data.columns)} total):") for col in engineered_data.columns: print(f" • {col}")
5.2 Feature Engineering for Time Series Data
# ============= TIME SERIES FEATURE ENGINEERING ============= def create_time_series_features(transactions): """Create features from transaction time series.""" # Ensure date is datetime transactions['date'] = pd.to_datetime(transactions['date']) # Time-based features transactions['hour'] = transactions['date'].dt.hour transactions['day_of_week'] = transactions['date'].dt.dayofweek transactions['is_weekend'] = transactions['day_of_week'].isin([5, 6]).astype(int) transactions['month'] = transactions['date'].dt.month transactions['quarter'] = transactions['date'].dt.quarter transactions['year'] = transactions['date'].dt.year # Lag features transactions = transactions.sort_values(['customer_id', 'date']) # Create lag features (previous transactions) transactions['prev_amount'] = transactions.groupby('customer_id')['amount'].shift(1) transactions['amount_change'] = transactions['amount'] - transactions['prev_amount'] transactions['pct_change'] = transactions['amount'] / transactions['prev_amount'] - 1 # Rolling statistics transactions['rolling_avg_3'] = transactions.groupby('customer_id')['amount'].rolling(3).mean().reset_index(0, drop=True) transactions['rolling_std_3'] = transactions.groupby('customer_id')['amount'].rolling(3).std().reset_index(0, drop=True) transactions['rolling_max_3'] = transactions.groupby('customer_id')['amount'].rolling(3).max().reset_index(0, drop=True) # Count features transactions['transaction_count_cum'] = transactions.groupby('customer_id').cumcount() + 1 print("Created time series features:") print(f" • Hour: {transactions['hour'].nunique()} unique values") print(f" • Day of week: {transactions['day_of_week'].nunique()} unique values") print(f" • Lag features: prev_amount, amount_change, pct_change") print(f" • Rolling stats: rolling_avg_3, rolling_std_3, rolling_max_3") return transactions # Create sample transaction data n_transactions = 1000 sample_transactions = pd.DataFrame({ 'transaction_id': range(1, n_transactions + 1), 'customer_id': np.random.randint(1, 51, n_transactions), 'date': [datetime.now() - timedelta(days=np.random.randint(0, 365)) for _ in range(n_transactions)], 'amount': np.random.lognormal(4, 1.5, n_transactions) }) # Create time series features enhanced_transactions = create_time_series_features(sample_transactions) print("\nSample Time Series Features:") print(enhanced_transactions.head())
SECTION 6: FEATURE SELECTION
6.1 Feature Selection Methods
# ============= FEATURE SELECTION ============= from sklearn.feature_selection import SelectKBest, f_regression, mutual_info_regression from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split # Prepare data for feature selection # Create a target variable (e.g., total spending) np.random.seed(42) target_data = engineered_data.copy() target_data['total_spending'] = ( target_data['monthly_expenses'] * 12 + target_data['credit_card_balance'] * 0.1 + target_data['num_transactions'] * 50 + np.random.normal(0, 1000, len(target_data)) ) # Separate features and target feature_cols = [col for col in target_data.columns if col not in ['customer_id', 'total_spending']] X = target_data[feature_cols] y = target_data['total_spending'] # For categorical features, we need to encode them for selection # Handle categorical columns categorical_cols = X.select_dtypes(include=['object', 'category']).columns X_encoded = pd.get_dummies(X, columns=categorical_cols, drop_first=True) class FeatureSelector: """Select important features using different methods.""" def __init__(self, X, y): self.X = X self.y = y self.results = {} def filter_method(self, k=10): """Filter method: Select top k features based on statistical tests.""" selector = SelectKBest(score_func=f_regression, k=k) selector.fit(self.X, self.y) # Get scores scores = pd.DataFrame({ 'feature': self.X.columns, 'score': selector.scores_ }).sort_values('score', ascending=False) selected = scores.head(k)['feature'].tolist() self.results['filter'] = { 'selected': selected, 'scores': scores } print(f"Filter Method: Selected {len(selected)} features") print(f" Top features: {selected[:5]}") return selected def wrapper_method(self, n_features=10): """Wrapper method: Forward selection using model performance.""" from sklearn.feature_selection import RFE from sklearn.linear_model import LinearRegression estimator = LinearRegression() selector = RFE(estimator, n_features_to_select=n_features) selector.fit(self.X, self.y) selected = self.X.columns[selector.support_].tolist() rankings = pd.DataFrame({ 'feature': self.X.columns, 'ranking': selector.ranking_ }).sort_values('ranking') self.results['wrapper'] = { 'selected': selected, 'rankings': rankings } print(f"Wrapper Method (RFE): Selected {len(selected)} features") print(f" Selected features: {selected[:5]}") return selected def embedded_method(self, n_features=10): """Embedded method: Feature importance from Random Forest.""" rf = RandomForestRegressor(n_estimators=100, random_state=42) rf.fit(self.X, self.y) importances = pd.DataFrame({ 'feature': self.X.columns, 'importance': rf.feature_importances_ }).sort_values('importance', ascending=False) selected = importances.head(n_features)['feature'].tolist() self.results['embedded'] = { 'selected': selected, 'importances': importances } print(f"Embedded Method (Random Forest): Selected {len(selected)} features") print(f" Top features: {selected[:5]}") print(f" Feature importances:") print(importances.head(10)) return selected def compare_methods(self): """Compare feature selection methods.""" print("\n" + "="*60) print("FEATURE SELECTION COMPARISON") print("="*60) # Run all methods filter_selected = self.filter_method(k=10) wrapper_selected = self.wrapper_method(n_features=10) embedded_selected = self.embedded_method(n_features=10) # Find common features common = set(filter_selected) & set(wrapper_selected) & set(embedded_selected) print(f"\nCommon features across all methods: {len(common)}") print(f" {common}") # Union of all selected union = set(filter_selected) | set(wrapper_selected) | set(embedded_selected) print(f"Unique features across all methods: {len(union)}") return common, union # Run feature selection selector = FeatureSelector(X_encoded, y) common, union = selector.compare_methods()
SECTION 7: BUSINESS RISK & FINANCIAL IMPACT
7.1 Impact of Poor Preprocessing
| Issue | Impact | Example |
|---|---|---|
| Missing Data | Biased models | Credit scoring model underestimates risk for missing data |
| Outliers | Skewed predictions | Fraud model flags too many false positives |
| Incorrect Scaling | Poor model convergence | Neural network fails to learn |
| Poor Encoding | Lost information | Ordinal categories encoded as nominal |
| Missing Features | Lost predictive power | Customer churn model misses key signals |
7.2 Regulatory Considerations
| Regulation | Preprocessing Requirement |
|---|---|
| SR 11-7 | Must document feature engineering decisions |
| Fair Lending | Features must not discriminate |
| GDPR | Must explain how features are created |
SECTION 8: SUMMARY FOR THE DATA PRACTITIONER
8.1 The 1-Minute Elevator Pitch
“Data preprocessing transforms raw banking data into features that machine learning models can use effectively. We handle missing data using imputation, scale features for consistent ranges, encode categorical variables, and engineer meaningful financial features like debt-to-income ratios and credit utilization. Feature selection identifies the most predictive variables. Proper preprocessing is essential for building accurate, compliant models for credit scoring, fraud detection, and customer analytics.”
8.2 Key Takeaways
-
Missing data requires careful handling based on why it’s missing.
-
Imputation methods include mean, median, KNN, and iterative approaches.
-
Scaling is required for distance-based algorithms.
-
Encoding transforms categorical data into numeric form.
-
Feature engineering creates domain-specific features from raw data.
-
Financial ratios (DTI, utilization, savings rate) are powerful predictors.
-
Risk indicators help identify potentially problematic customers.
-
Time series features capture patterns in transaction data.
-
Feature selection identifies the most predictive variables.
-
Documentation is essential for regulatory compliance.
8.3 Recommended Next Steps
-
Practice preprocessing on a real banking dataset
-
Implement your own feature engineering pipeline
-
Learn about automated feature engineering (FeatureTools)
-
Document your preprocessing decisions for regulators