Handling outliers and missing values in credit scoring

The standard advice on data preprocessing has quietly become “just impute with the mean,” and it is costing people real model performance. If you want robust credit scoring models, you cannot be lazy about outliers or missing values. Most devs I meet treat data cleaning as a checkbox item, but in credit risk one unhandled extreme value will distort your probability of default (PD) estimates.

I have watched production systems crash and, worse, hand out subtly wrong financial advice because a race condition in the data pipeline let null values reach a model that expected floats. Your preprocessing logic needs the same version control and stability as the prediction code. This is the third part of the risk modeling series, so if you missed the earlier steps, start with my guide on credit scoring EDA.

Lock the test set before you clean anything

Before touching a single outlier, deal with data leakage. Any statistic you use to clean the data, a median or an IQR bound, has to come only from the training set, and you then apply those exact values to the test and Out-of-Time (OOT) sets. Calculate a global median and impute with it and you have leaked future information into training. Split the data first, every time.

# The right way to split for Robust Credit Scoring Models
from sklearn.model_selection import train_test_split

# Stratify by both default indicator and time to preserve structure
train_df, test_df = train_test_split(
    df, 
    test_size=0.2, 
    random_state=42, 
    stratify=df[["def", "year"]]
)

Handling outliers with the IQR method

In robust credit scoring models, outliers are usually valid but awkward observations: a borrower with a 30-year history, or a €1M income. The Interquartile Range (IQR) method clips those values rather than dropping the row, which cuts the variance of your estimators without losing the observation. The bounds are Q1 – 1.5 * IQR and Q3 + 1.5 * IQR.

def bbioon_apply_iqr_bounds(train, test, oot, variables):
    train = train.copy()
    test = test.copy()
    oot = oot.copy()

    for var in variables:
        Q1 = train[var].quantile(0.25)
        Q3 = train[var].quantile(0.75)
        IQR = Q3 - Q1
        
        lower = Q1 - 1.5 * IQR
        upper = Q3 + 1.5 * IQR

        # Clip values based on training bounds
        for df in [train, test, oot]:
            df[var] = df[var].clip(lower, upper)

    return train, test, oot

Missing value imputation: MAR vs MCAR

Missing data is not all the same. If a value is Missing Completely at Random (MCAR), median imputation is fine. If it is Missing at Random (MAR), meaning the missingness correlates with another variable, you need a strategy. Say people on lower incomes are less likely to report employment length: handing them the average employment length quietly costs you accuracy. The conservative option is to assign a value that correlates with higher risk instead.

For the heavier strategies, the Scikit-learn imputation guide is worth your time. It covers SimpleImputer through to IterativeImputer (MICE).

Refactoring the pipeline

When you refactor the data pipeline, keep the imputation logic encapsulated. I have seen teams hack together scripts that run fine against local CSVs and then fall over inside a production Docker container because the path to “training_median.pkl” was hardcoded. Don’t be that dev. Use a proper configuration file or a data registry.

If this credit scoring model work is eating your dev hours, hand it to me. I have been wrestling with WordPress, Python and messy data integrations since the 4.x days.

What actually makes the model hold up

Robust credit scoring models hold up because the input data is clean, the splits are honest and the preprocessing repeats the same way twice. A flashier neural network will not rescue any of that. The hours you put into IQR clipping and sensible imputation now are the hours you do not lose to a model failure later. Ship it, but ship it clean.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.