import numpy as np import pandas as pd def build_features(df): df = df.copy() df['living_to_income'] = df['living_expenses'] / df['net_income'].replace(0, np.nan) df['discretionary_ratio'] = df['discretionary_income'] / df['net_income'].replace(0, np.nan) df['gross_to_net_ratio'] = df['net_income'] / df['gross_income'].replace(0, np.nan) df['score_norm'] = df['credit_score'] / 850 df['score_risk'] = 1 - df['score_norm'] df['combined_risk'] = df['score_risk'] + df['debt_service_ratio'] df['food_ratio'] = df['food_expense'] / df['net_income'].replace(0, np.nan) df['housing_ratio'] = df['accommodation_expense'] / df['net_income'].replace(0, np.nan) df['transport_ratio'] = df['transport_expense'] / df['net_income'].replace(0, np.nan) if 'credit_bureau_monthly_debt' in df.columns: df['bureau_debt_to_income'] = df['credit_bureau_monthly_debt'] / df['net_income'].replace(0, np.nan) if 'affordability_surplus' in df.columns: df['affordability_surplus_ratio'] = df['affordability_surplus'] / df['net_income'].replace(0, np.nan) if 'affordability_expense_basis' in df.columns: df['affordability_basis_ratio'] = df['affordability_expense_basis'] / df['net_income'].replace(0, np.nan) if {'affordability_expense_basis', 'living_expenses'}.issubset(df.columns): df['expense_gap_ratio'] = ( (df['affordability_expense_basis'] - df['living_expenses']) / df['net_income'].replace(0, np.nan) ) if {'minimum_living_expense', 'living_expenses'}.issubset(df.columns): df['norm_to_declared_ratio'] = df['minimum_living_expense'] / df['living_expenses'].replace(0, np.nan) if 'predicted_monthly_income' in df.columns: df['income_prediction_gap'] = ( (df['predicted_monthly_income'] - df['gross_income']) / df['gross_income'].replace(0, np.nan) ) if 'household_gross_income' in df.columns: df['household_to_income_ratio'] = df['household_gross_income'] / df['gross_income'].replace(0, np.nan) if {'cpa_commitments', 'net_income'}.issubset(df.columns): df['cpa_to_income_ratio'] = df['cpa_commitments'] / df['net_income'].replace(0, np.nan) if {'nlr_commitments', 'net_income'}.issubset(df.columns): df['nlr_to_income_ratio'] = df['nlr_commitments'] / df['net_income'].replace(0, np.nan) if {'contactability_index', 'bureau_utilisation'}.issubset(df.columns): df['contactability_risk_gap'] = (100 - df['contactability_index']) / 100 + df['bureau_utilisation'] if {'monthly_instalment', 'net_income'}.issubset(df.columns): df['instalment_to_income'] = df['monthly_instalment'] / df['net_income'].replace(0, np.nan) if {'total_debt_outstanding_zar', 'gross_income'}.issubset(df.columns): df['debt_to_income_multiple'] = df['total_debt_outstanding_zar'] / df['gross_income'].replace(0, np.nan) if {'asset_total_zar', 'gross_income'}.issubset(df.columns): df['assets_to_income_multiple'] = df['asset_total_zar'] / df['gross_income'].replace(0, np.nan) if {'avg_bank_balance_30d_zar', 'net_income'}.issubset(df.columns): df['avg_balance_to_income'] = df['avg_bank_balance_30d_zar'] / df['net_income'].replace(0, np.nan) if {'min_bank_balance_30d_zar', 'net_income'}.issubset(df.columns): df['min_balance_to_income'] = df['min_bank_balance_30d_zar'] / df['net_income'].replace(0, np.nan) if {'credit_history_months'}.issubset(df.columns): df['credit_history_years'] = df['credit_history_months'] / 12 df['income_band'] = pd.cut( df['gross_income'], bins=[0, 8000, 15000, 25000, 45000, 80000, np.inf], labels=['very_low', 'low', 'lower_mid', 'middle', 'upper_mid', 'high'], ) return df