Lesson 5.1.2: Data preprocessing for machine learning models
Lesson 5.1.2: Data preprocessing for machine learning models
Learning Objectives
- Understand the importance of data preprocessing in the AI workflow for disease classification
- Identify common preprocessing techniques such as normalisation, encoding, and imputation
- Apply preprocessing steps to a real-world medical dataset
- Evaluate the impact of preprocessing choices on model performance
Key Terms
| Preprocessing | The series of data preparation steps applied before training a machine learning model to ensure data quality and compatibility |
|---|---|
| Normalisation | Scaling numerical features to a standard range, often 0–1, to prevent bias towards larger values |
| Encoding | Transforming categorical variables into numerical format, e.g., one‑hot or label encoding |
| Imputation | Replacing missing data with substituted values such as mean, median, or predictive models |
| Feature Scaling | Adjusting the range of independent variables to improve algorithm convergence |
| Data Leakage | Unintended introduction of information from the test set into the training process, leading to overly optimistic performance estimates |
flowchart TD
A[Raw Data] --> B[Missing Value Handling]
B --> C[Feature Scaling]
C --> D[Encode Categorical]
D --> E[Feature Selection]
E --> F[Model‑Ready Data]
Concept: The Role of Preprocessing in Disease Classification
In medical machine‑learning pipelines, the term preprocessing refers to a set of systematic transformations applied to raw clinical data before it is handed to a predictive model. Although it may seem trivial compared with the sophisticated algorithms that follow, preprocessing is often the single most influential stage in determining whether a model will generalise to new patients, produce clinically useful predictions, or, conversely, produce misleading or unsafe outputs. Below we analyse why preprocessing is critical for accurate and reliable disease classification, explore the common challenges it addresses, and illustrate the process with a concise Python example.
1. Harmonising Heterogeneous Data Sources
Clinical datasets are notoriously heterogeneous. Electronic health records (EHRs) aggregate data from multiple hospital departments, each using its own coding conventions, units of measurement, and data entry standards. A laboratory result recorded as 5.6 mmol/L in one system may appear as 0.8 mmol/dL in another. If a model receives these conflicting representations as separate features, it will learn spurious correlations and overfit to artefacts rather than genuine biological signals.
Preprocessing normalises units, maps local codes to standard ontologies (e.g. SNOMED CT, ICD‑10), and consolidates redundant fields. By ensuring that every sample is expressed on a common scale, we reduce the dimensionality of the feature space without losing clinically meaningful information. This improves the signal‑to‑noise ratio, which is essential for models that must detect subtle patterns distinguishing, for example, early‑stage diabetes from normal glucose tolerance.
2. Addressing Missingness and Incomplete Records
Missing data is a pervasive issue in healthcare. A patient may have a complete set of blood tests but lack a family history record, or vice versa. Models that naively drop rows with missing values (listwise deletion) can introduce bias, especially when the missingness is not random. Conversely, imputing missing values without care can inject noise or create artificial correlations.
Robust preprocessing pipelines implement principled missing‑data techniques: simple imputation (mean, median), model‑based imputation (k‑nearest neighbours, multiple imputation by chained equations), or even sophisticated deep‑learning approaches such as variational autoencoders. Importantly, the choice of method should be guided by the missingness mechanism (MCAR, MAR, MNAR) and validated through sensitivity analyses. Proper handling of missingness preserves the integrity of the training set and ensures that the model learns from a faithful representation of the underlying patient population.
3. Scaling and Normalisation for Gradient‑Based Optimisation
Many machine‑learning algorithms, particularly neural networks and support‑vector machines, are sensitive to the scale of input features. If one feature ranges from 0 to 1 while another spans thousands, the optimiser will prioritise the larger‑magnitude feature, potentially neglecting clinically relevant signals in smaller‑range variables.
Standard preprocessing steps include min‑max scaling, z‑score standardisation, or more robust techniques such as quantile transformation. Additionally, for categorical variables with high cardinality, one‑hot encoding can explode the feature space; embedding layers or target‑encoding can be employed instead. By ensuring that all inputs contribute proportionally to the loss function, we facilitate faster convergence, reduce the risk of overfitting, and improve the stability of the model during training.
4. Mitigating Class Imbalance and Bias
Disease classification tasks often involve highly imbalanced classes: for instance, the prevalence of malignant tumours may be only a few percent of all biopsies. In such cases, a naïve classifier can achieve high accuracy by simply predicting the majority class. Preprocessing can counteract this by resampling (oversampling minority, undersampling majority), synthesising new instances (SMOTE), or re‑weighting the loss function to penalise misclassifications of the minority class more heavily.
Moreover, preprocessing should be used to detect and remove demographic or socio‑economic biases that could lead to disparate impact. Techniques such as re‑weighting or adversarial debiasing rely on careful feature engineering to ensure that the model’s predictions are fair and equitable across subgroups.
5. Enhancing Interpretability and Clinical Trust
Clinicians require transparent decision support tools. Preprocessing steps that preserve clinical interpretability—such as maintaining original lab units or providing audit trails for imputed values—facilitate trust. For example, a model that flags a patient as high risk for heart failure because of an out‑of‑range troponin value is immediately understandable, whereas a model that relies on an abstract composite feature derived from a complex series of transformations may be less readily accepted.
Additionally, preprocessing can generate derived features that are directly interpretable, such as calculating a Body Mass Index (BMI) from height and weight, or computing a risk score by aggregating validated clinical criteria. These engineered features can be visualised, validated against clinical guidelines, and communicated to stakeholders, thereby bridging the gap between data science and bedside practice.
6. Facilitating Reproducibility and Regulatory Compliance
In the context of medical device regulation (e.g. NHS Digital, MHRA), reproducibility of the entire data‑processing pipeline is mandatory. Preprocessing scripts must be version‑controlled, documented, and accompanied by unit tests that verify that transformations are applied consistently across datasets. By modularising preprocessing (e.g. using Scikit‑Learn’s Pipeline objects), developers can encapsulate each step, making the pipeline auditable and maintainable.
Regulators also require traceability of how raw data was transformed into model inputs. Explicit preprocessing logs, data dictionaries, and provenance metadata ensure that any post‑market issue can be traced back to a specific data transformation, thereby enabling rapid remediation.
7. Example: Normalising Lab Values and Imputing Missing Data in Python
Below is a minimal example that demonstrates two common preprocessing tasks: standardising laboratory test results and imputing missing values with the median. The code uses pandas for data manipulation and scikit‑learn for the imputer and scaler.
import pandas as pd
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler
Sample dataset with missing values and mixed units
data = pd.DataFrame({
'PatientID': [1, 2, 3, 4],
'Glucose_mmol_L': [5.6, None, 7.8, 6.1], # mmol/L
'Glucose_mg_dL': [None, 120, None, 110], # mg/dL
'Age': [45, 52, 38, 60]
})
1. Harmonise glucose units: convert mg/dL to mmol/L (1 mg/dL ≈ 0.0555 mmol/L)
data['Glucose_mmol_L'] = data['Glucose_mmol_L'].fillna(
data['Glucose_mg_dL'] * 0.0555
)
2. Drop the original mg/dL column as it is now redundant
data = data.drop(columns=['Glucose_mg_dL'])
3. Separate features and target (the target is omitted here for brevity)
features = data.drop(columns=['PatientID'])
4. Impute remaining missing values (e.g. Age) with the median
imputer = SimpleImputer(strategy='median')
features_imputed = pd.DataFrame(
imputer.fit_transform(features),
columns=features.columns
)
5. Standardise numeric features to zero mean and unit variance
scaler = StandardScaler()
features_scaled = pd.DataFrame(
scaler.fit_transform(features_imputed),
columns=features_imputed.columns
)
Resulting preprocessed dataframe
preprocessed = pd.concat([data['PatientID'], features_scaled], axis=1)
print(preprocessed)
Line‑by‑line explanation of the code:
- Import the required libraries:
pandasfor data frames,SimpleImputerfor missing‑value handling, andStandardScalerfor feature scaling. - Define a small sample dataset with two representations of glucose and an age column.
- Convert any glucose values expressed in mg/dL to mmol/L by multiplying by 0.0555; if the mmol/L value is missing, use the converted mg/dL value.
- Drop the now‑redundant
Glucose_mg_dLcolumn to avoid duplicate information. - Separate the feature matrix from the patient identifier.
- Instantiate a median imputer; fit it to the feature matrix and transform the data, ensuring that any remaining missing values (e.g. in
Age) are replaced by the median of that column. - Instantiate a standard scaler; fit it to the imputed features and transform them, producing zero‑mean, unit‑variance variables suitable for most machine‑learning algorithms.
- Re‑attach the
PatientIDcolumn to the scaled features to obtain a fully preprocessed data frame.
Even in this toy example, each preprocessing step removes a source of bias or noise that could otherwise degrade model performance or compromise interpretability. When scaled to thousands of patients and dozens of features, such disciplined preprocessing becomes indispensable for building robust, clinically deployable disease‑classification systems.
8. Additional Example: Min‑Max Scaling with Power Transformation
The following snippet demonstrates a lightweight alternative to z‑score standardisation. It first rescales features to the interval [0, 1] using MinMaxScaler and then applies a power transform (Box‑Cox) to stabilise variance and reduce skewness. This two‑stage approach is useful when the data contain outliers but the model still benefits from bounded input ranges.
from sklearn.preprocessing import MinMaxScaler, PowerTransformer
Assume `features_scaled` from the previous example
minmax = MinMaxScaler()
scaled_0_1 = pd.DataFrame(
minmax.fit_transform(features_scaled),
columns=features_scaled.columns
)
Power transform to approximate normality
power = PowerTransformer(method='box-cox')
Box‑Cox requires all values to be strictly positive
scaled_0_1 += 1e-6
power_transformed = pd.DataFrame(
power.fit_transform(scaled_0_1),
columns=scaled_0_1.columns
)
print(power_transformed.head())
9. Table: Comparing Common Scaling Strategies
| Method | Transformation | Effect on Outliers | Range | Typical Use‑Case |
|---|---|---|---|---|
| Z‑Score Standardisation | \(x' = (x - \mu)/\sigma\) | Can be influenced by extreme values | Unbounded (≈ ±3) | Gradient‑based models (NN, SVM) |
| Min‑Max Scaling | \(x' = (x - min)/(max - min)\) | Preserves outliers, compresses them to 0 or 1 | 0 to 1 | Neural nets, models requiring bounded input |
| Robust Scaler (median, IQR) | Subtract median, divide by IQR | Resistant to outliers | Unbounded | When data contain many outliers |
| Quantile Transformation | Maps to a normal or uniform distribution | Very robust; can distort relationships | Uniform [0,1] or Normal | Kernel methods, preprocessing for tree‑based models |
| Power Transformation (Box‑Cox, Yeo‑Johnson) | Non‑linear scaling to approximate normality | Reduces skewness; sensitive to zeros | Unbounded | Regression, when normality assumptions matter |
Think about how the preprocessing steps you just studied—such as handling missing data, normalising features, and encoding categorical variables—could influence the performance of a machine learning model in a real-world disease classification scenario. Consider a specific disease diagnosis task (e.g., predicting diabetes from patient records) and describe how each preprocessing step might affect the model’s accuracy, interpretability, or fairness. Write a brief paragraph summarising your thoughts.
Implementation/Code: Applying Preprocessing Techniques in Python
In this section we walk through a realistic workflow for preparing a medical dataset for supervised learning. The example dataset contains patient records with a mix of numeric, categorical and missing values, and a highly imbalanced target variable indicating the presence of a rare disease. We will use pandas for data manipulation, scikit‑learn for scaling, encoding and splitting, and imblearn for handling class imbalance. The code is organised into logical blocks, each accompanied by a line‑by‑line explanation.
1. Loading the data
Assume the dataset is stored as a CSV file named medical_records.csv. The first task is to read it into a DataFrame and inspect its structure.
import pandas as pd
Load the raw data
df = pd.read_csv('medical_records.csv')
Quick look at the first few rows and metadata
print(df.head())
print(df.info())
Explanation:
import pandas as pdbrings the pandas library into the namespace.pd.read_csvreads the CSV file into a DataFrame calleddf.df.head()prints the first five rows, giving a glimpse of the raw values.df.info()outputs the data types and non‑null counts for every column, revealing missingness and potential type issues.
2. Cleaning the data
Medical records often contain placeholder values such as -999 or empty strings. We replace these with NaN so that pandas recognises them as missing.
Define placeholder patterns
placeholders = [-999, 'N/A', '']
Replace placeholders with NaN
df.replace(placeholders, pd.NA, inplace=True)
Line‑by‑line:
placeholders = [-999, 'N/A', '']lists values that should be treated as missing.df.replace(placeholders, pd.NA, inplace=True)scans the entire DataFrame, substituting any occurrence of the placeholder list withpd.NA. Theinplace=Trueflag ensures the operation mutatesdfdirectly.
Next, we drop rows that lack a target label and impute remaining missing values. Categorical missingness is filled with the mode, numeric with the median to avoid outlier influence.
Drop rows without a target
df.dropna(subset=['disease_present'], inplace=True)
Identify numeric and categorical columns
num_cols = df.select_dtypes(include='number').columns
cat_cols = df.select_dtypes(exclude='number').columns
Impute numeric columns with median
df[num_cols] = df[num_cols].fillna(df[num_cols].median())
Impute categorical columns with mode
df[cat_cols] = df[cat_cols].fillna(df[cat_cols].mode().iloc[0])
Explanation:
df.dropna(subset=['disease_present'], inplace=True)removes any record missing the target variable.- Selection of numeric and categorical columns uses pandas'
select_dtypesmethod. - Median and mode imputation preserve the central tendency of each feature while avoiding the introduction of extreme values.
3. Encoding categorical variables
Machine learning algorithms require numeric input. We use one‑hot encoding for nominal categories and ordinal encoding for ordered ones. Suppose sex is nominal and severity is ordinal.
from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder
One‑hot encode 'sex' and drop the first column to avoid multicollinearity
ohe = OneHotEncoder(drop='first', sparse=False)
sex_encoded = pd.DataFrame(
ohe.fit_transform(df[['sex']]),
columns=ohe.get_feature_names_out(['sex']),
index=df.index
)
Ordinal encode 'severity'
ordinal_map = {'mild': 1, 'moderate': 2, 'severe': 3}
df['severity'] = df['severity'].map(ordinal_map)
Combine encoded features back into the DataFrame
df = pd.concat([df.drop('sex', axis=1), sex_encoded], axis=1)
Line‑by‑line:
OneHotEncoder(drop='first', sparse=False)creates an encoder that removes the first dummy variable to prevent the dummy‑variable trap.- Transforming
df[['sex']]yields a 2‑D array; wrapping it inpd.DataFramerestores column names and index alignment. - The ordinal mapping converts categorical severity levels into integers that preserve inherent order.
- Finally, the original
sexcolumn is removed and the new dummy variables are concatenated.
4. Scaling numeric features
Algorithms such as Logistic Regression or k‑Nearest Neighbours are sensitive to the scale of features. Standardisation centres each feature at zero mean and unit variance.
from sklearn.preprocessing import StandardScaler
Identify numeric columns again (post‑encoding)
num_cols = df.select_dtypes(include='number').columns.drop('disease_present')
scaler = StandardScaler()
df[num_cols] = scaler.fit_transform(df[num_cols])
Explanation:
- We exclude the target column from scaling.
- The
StandardScaleris fitted on the training data to learn the mean and standard deviation, then applied to transform the features.
5. Train‑test split with stratification
Medical datasets often have imbalanced classes. Stratified splitting guarantees the same class proportion in both training and test sets.
from sklearn.model_selection import train_test_split
X = df.drop('disease_present', axis=1)
y = df['disease_present']
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.25,
random_state=42,
stratify=y
)
Line‑by‑line:
Xcontains all predictors;yholds the binary target.- Using
train_test_splitwithstratify=ypreserves the minority‑class ratio across splits. - The
random_stateensures reproducibility.
6. Handling class imbalance with SMOTE
To mitigate the bias toward the majority class, we apply Synthetic Minority Oversampling Technique (SMOTE) from the imblearn library. SMOTE generates synthetic samples of the minority class by interpolating between existing minority instances.
from imblearn.over_sampling import SMOTE
smote = SMOTE(sampling_strategy='auto', random_state=42)
X_train_res, y_train_res = smote.fit_resample(X_train, y_train)
Explanation:
sampling_strategy='auto'instructs SMOTE to balance the classes to the size of the majority class.- After resampling,
X_train_resandy_train_rescontain a roughly equal number of disease‑present and disease‑absent cases.
7. Persisting the pre‑processed data
It is good practice to save the cleaned, encoded, and balanced training set, as well as the untouched test set, so future experiments can reuse the same preprocessing pipeline.
import joblib
joblib.dump((X_train_res, y_train_res), 'preprocessed_train.pkl')
joblib.dump((X_test, y_test), 'preprocessed_test.pkl')
Line‑by‑line:
joblib.dumpserialises Python objects efficiently, preserving the exact shapes and data types.- By storing the objects as
.pklfiles, you can quickly reload them for model training and evaluation without repeating the entire preprocessing chain.
8. Summary of the preprocessing pipeline
Below is a concise table summarising each step, the libraries utilised, and the key parameters.
| Step | Library / Tool | Parameters / Notes |
|---|---|---|
| Load CSV | pandas | Default parsing, infer column types |
| Placeholder replacement | pandas replace |
List of placeholders → pd.NA |
| Missing value imputation | pandas fillna |
Median for numeric, mode for categorical |
| Categorical encoding | scikit‑learn OneHotEncoder, OrdinalEncoder |
Drop first dummy, map ordinal levels |
| Feature scaling | scikit‑learn StandardScaler |
Fit on training, transform all numeric features |
| Train‑test split | scikit‑learn train_test_split |
Stratify on target, 75/25 split |
| Imbalance handling | imblearn SMOTE |
Auto sampling to majority class size |
| Persist data | joblib | Serialise pre‑processed splits as .pkl |
By following this pipeline, you ensure that the data fed into any subsequent machine‑learning model is clean, well‑formatted, and free from biases introduced by missing values or class imbalance. The modular structure also makes it straightforward to swap out components—for instance, replacing SMOTE with ADASYN or applying a different scaler—without disrupting the overall workflow.
9. Additional example: Polynomial Feature Generation
Sometimes linear models benefit from interaction terms. We can generate polynomial features using PolynomialFeatures from scikit‑learn.
from sklearn.preprocessing import PolynomialFeatures
Fit on training numeric features only
poly = PolynomialFeatures(degree=2, include_bias=False)
X_train_poly = poly.fit_transform(X_train_res[num_cols])
X_test_poly = poly.transform(X_test[num_cols])
Combine back with non‑numeric features
X_train_res_poly = pd.DataFrame(
X_train_poly,
columns=poly.get_feature_names_out(num_cols),
index=X_train_res.index
)
X_test_poly_df = pd.DataFrame(
X_test_poly,
columns=poly.get_feature_names_out(num_cols),
index=X_test.index
)
Add back non‑numeric columns
X_train_res_final = pd.concat(
[X_train_res_poly, X_train_res.drop(columns=num_cols)],
axis=1
)
X_test_final = pd.concat(
[X_test_poly_df, X_test.drop(columns=num_cols)],
axis=1
)
Line‑by‑line:
PolynomialFeatures(degree=2)creates all pairwise interactions up to second order.- We fit only on the balanced training set to avoid leakage.
- The transformed arrays are wrapped back into
DataFrameobjects for easy concatenation with the remaining features. - Finally,
X_train_res_finalandX_test_finalcontain the expanded feature set ready for model training.
10. Comparing Imbalance Handling Methods
Below is a quick comparison of popular techniques for dealing with class imbalance.
| Technique | Library | Primary Idea | Pros | Cons |
|---|---|---|---|---|
| Random Undersampling | imblearn | Remove majority instances to balance | Simple, fast | Loss of potentially useful data |
| Random Oversampling | imblearn | Duplicate minority instances | Preserves all data | Risk of overfitting |
| SMOTE | imblearn | Synthesise new minority samples | Reduces overfitting compared to duplication | Can introduce noise if minority distribution is sparse |
| ADASYN | imblearn | Adaptive synthetic sampling focusing on hard‑to‑learn instances | Targets difficult regions | More computationally intensive |
| Class Weighting | scikit‑learn (e.g., logistic_regression(class_weight='balanced')) | Adjusts loss function based on class frequencies | No data alteration, simple to implement | May not fully compensate for severe imbalance |
-
Which scikit‑learn transformer is best for imputing missing numeric values with the mean?
-
In a pipeline, at what point should you apply StandardScaler relative to a RandomForestClassifier?
-
Which of the following is NOT a typical purpose of one‑hot encoding?
-
When should you perform dimensionality reduction (e.g., PCA) in a preprocessing pipeline?
3. Encoding categorical variables (alternative approach)
In addition to the one‑hot and ordinal strategies already shown, a third option is label encoding, which assigns an integer to each category without implying any ordinal relationship. This is useful when the target model can handle categorical integers directly, such as tree‑based algorithms that are invariant to the actual numeric values.
# Label encoding for tree‑based models
from sklearn.preprocessing import LabelEncoder
label_encoders = {}
for col in categorical_vars:
le = LabelEncoder()
df_clean[col] = le.fit_transform(df_clean[col].astype(str))
label_encoders[col] = le
Now df_clean contains integer‑encoded categorical columns
| Encoding Method | When to Use | Pros | Cons |
|---|---|---|---|
| One‑Hot Encoding | Nominal variables, linear models, models sensitive to feature scaling | Preserves independence, no implicit ordering | Can create many sparse columns if many categories |
| Ordinal Encoding | Variables with natural order (e.g. education level) | Reduces dimensionality, retains order information | May impose unintended numeric relationships on models that assume linearity |
| Label Encoding | Tree‑based models, when categories are numerous and ordering irrelevant | Saves memory, simple to apply | Can mislead algorithms that interpret numbers as ordered (e.g. linear regression) |
Using the concepts covered in the worked example, design a simple preprocessing pipeline for a new dataset. Identify the required steps (e.g. missing value handling, scaling, encoding), justify each choice, and outline the sequence of operations that would be implemented in code. Consider potential pitfalls and how you would test the pipeline’s effectiveness.
Pitfalls: Common Mistakes and How to Avoid Them
Data preprocessing is the foundation of any successful machine‑learning pipeline. Even a minor misstep at this stage can propagate through training and evaluation, leading to misleading metrics, poor generalisation, and ultimately a failed model. Below we analyse three of the most pervasive errors: data leakage, improper scaling, and over‑fitting. For each, we describe why it occurs, its consequences, and concrete mitigation strategies, complete with code examples to illustrate best practice.
1. Data Leakage
Data leakage occurs when information from the test set or future data leaks into the training process. This can happen in many subtle ways, such as:
- Using a feature that is derived from the target variable (e.g., including the actual sale price in a model predicting price).
- Applying feature engineering steps (like imputation or encoding) on the combined dataset before splitting.
- Normalising data using the mean and standard deviation of the full dataset rather than the training set alone.
Leakage inflates performance metrics during development while the model performs poorly in real deployment. It is therefore crucial to isolate training data strictly from any future information.
Below is a minimal example demonstrating how to avoid leakage when imputing missing values and performing one‑hot encoding. The key is to fit transformers only on the training data.
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder
Load dataset
df = pd.read_csv('data.csv')
Separate features and target
X = df.drop('target', axis=1)
y = df['target']
Split first – keep the split private
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y)
Impute only on training data
imputer = SimpleImputer(strategy='median')
X_train_imputed = imputer.fit_transform(X_train)
X_test_imputed = imputer.transform(X_test)
One‑hot encode only on training data
encoder = OneHotEncoder(handle_unknown='ignore', sparse=False)
X_train_encoded = encoder.fit_transform(X_train_imputed)
X_test_encoded = encoder.transform(X_test_imputed)
Now X_train_encoded and X_test_encoded are ready for modelling
Line‑by‑line explanation:
- Import necessary libraries.
- Load the CSV into a
DataFrame. - Separate the feature matrix
Xfrom the target vectory. - Use
train_test_splitto create disjoint training and test sets. Therandom_stateguarantees reproducibility, andstratify=ypreserves the class distribution. - Instantiate a
SimpleImputerthat will replace missing values with the median of each column. - Fit the imputer only on the training set and transform both training and test sets. This ensures that the test set statistics are not used during fitting.
- Create an
OneHotEncoderconfigured to ignore any new categories that appear only in the test set. - Fit the encoder on the imputed training data, then transform both sets. The resulting matrices are dense (
sparse=False) for ease of use with downstream algorithms.
2. Improper Scaling
Many algorithms, particularly distance‑based methods (k‑NN, SVM) and gradient‑based optimisers (logistic regression, neural networks), assume that features are on a comparable scale. Failure to scale can lead to:
- Dominance of high‑magnitude features during optimisation.
- Poor convergence behaviour and inflated training times.
- Misleading feature importance rankings.
Common mistakes include scaling the entire dataset before splitting, or using inappropriate scalers for the data distribution (e.g., applying StandardScaler to skewed features without considering robust alternatives).
The following table summarises typical scalers, their assumptions, and suitable use‑cases:
| Scaler | Assumptions | When to Use |
|---|---|---|
StandardScaler |
Features are normally distributed. | Linear models, SVM, k‑NN when data is approximately Gaussian. |
RobustScaler |
Presence of outliers; uses median and IQR. | Skewed data, robust to outliers. |
| MinMaxScaler | Non‑negative data bounded between 0 and 1. | Neural networks with sigmoid activations, normalising pixel intensities. |
| PowerTransformer (Box‑Cox, Yeo‑Johnson) | Transforms to approximate normality. | Data with strong skewness; when StandardScaler assumptions fail. |
To avoid scaling leakage, the scaler must be fit on the training set only and subsequently applied to the test set. A common practice is to encapsulate both imputation and scaling within a Pipeline so that the sequence of transformations is applied consistently.
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import LogisticRegression
numeric_features = ['age', 'income', 'balance']
categorical_features = ['gender', 'occupation']
numeric_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')),
('onehot', OneHotEncoder(handle_unknown='ignore'))
])
preprocessor = ColumnTransformer(
transformers=[
('num', numeric_transformer, numeric_features),
('cat', categorical_transformer, categorical_features)
])
clf = Pipeline(steps=[('preprocessor', preprocessor),
('classifier', LogisticRegression(max_iter=1000))])
clf.fit(X_train, y_train)
preds = clf.predict(X_test)
In this example, each step is confined to the training data during fitting. The ColumnTransformer ensures that numeric and categorical columns are processed separately, preventing any inadvertent leakage of test‑set statistics.
3. Over‑Fitting
Over‑fitting occurs when a model captures noise in the training data rather than the underlying pattern. It manifests as high training accuracy but poor performance on unseen data. Common causes include:
- Excessively complex models (e.g., deep neural nets with many layers).
- Insufficient regularisation.
- Inadequate cross‑validation.
- Using the same data for hyperparameter tuning and performance evaluation.
Mitigation strategies centre on regularisation, proper validation, and early stopping.
3.1 Regularisation Techniques
For linear models, L1 (lasso) and L2 (ridge) regularisation add a penalty term to the loss function:
Loss = DataLoss + λ * RegularisationTerm
Choosing the regularisation strength λ is critical; too large and the model under‑fits, too small and it over‑fits.
3.2 Cross‑Validation and Nested CV
A single train/test split is insufficient for hyperparameter optimisation. Nested cross‑validation separates the inner loop (parameter tuning) from the outer loop (performance estimation). This protects against optimistic bias.
In scikit‑learn, GridSearchCV or RandomizedSearchCV can be wrapped inside a cross_val_score call to achieve nesting.
3.3 Early Stopping
For iterative learners such as gradient boosting or neural networks, monitoring validation loss and stopping training when it no longer improves prevents the model from memorising noise. Most frameworks expose an early_stopping flag or a callback mechanism.
Below is a concise example using XGBoost, demonstrating early stopping with a validation set.
import xgboost as xgb
from sklearn.model_selection import train_test_split
X_train, X_val, y_train, y_val = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y)
dtrain = xgb.DMatrix(X_train, label=y_train)
dval = xgb.DMatrix(X_val, label=y_val)
params = {
'objective': 'binary:logistic',
'eval_metric': 'auc',
'eta': 0.05,
'max_depth': 6,
'subsample': 0.8,
'colsample_bytree': 0.8
}
evallist = [(dtrain, 'train'), (dval, 'eval')]
Train with early stopping after 50 rounds with no improvement
bst = xgb.train(params, dtrain, num_boost_round=1000,
evals=evallist, early_stopping_rounds=50, verbose_eval=10)
print(f'Best iteration: {bst.best_iteration}')
print(f'Best AUC: {bst.best_score}')
In this snippet, the training stops once the AUC on the validation set fails to improve for 50 consecutive rounds, thereby curbing over‑fitting.
3.4 Feature‑Selection Preprocessing
Another common source of leakage is performing feature‑selection on the full dataset before splitting. Selecting variables based on their correlation with the target can bias the pipeline. The following example demonstrates a safe approach using a VarianceThreshold within a pipeline, ensuring that the selection is based solely on the training data.
from sklearn.feature_selection import VarianceThreshold
from sklearn.pipeline import Pipeline
Define a simple transformer that removes low‑variance features
variance_selector = VarianceThreshold(threshold=0.01)
Construct a pipeline that includes the selector and a classifier
pipeline = Pipeline(steps=[
('variance', variance_selector),
('classifier', LogisticRegression(max_iter=1000))
])
Fit on training data only
pipeline.fit(X_train, y_train)
Predict on the test set
test_preds = pipeline.predict(X_test)
Because the VarianceThreshold is fitted only on the training set, no information from the test set influences the feature‑selection step.
Feature‑Selection Comparison Table
| Method | Leakage Risk | Typical Use‑Case | Implementation Hint |
|---|---|---|---|
| VarianceThreshold | Low – if fitted within a pipeline | Remove near‑constant features | Set threshold based on domain knowledge |
| SelectKBest (f‑score, mutual information) | High – if applied before splitting | Feature ranking for linear models | Wrap inside a Pipeline or use cross_val_score |
| Recursive Feature Elimination (RFE) | High – if performed on full data | Complex models, e.g., SVM | Use RFECV within a cross‑validation loop |
| Embedded methods (Lasso, tree‑based importance) | Low – if model is trained within a pipeline | Regularisation or tree ensembles | Extract feature importance after fitting |
Practical Checklist for Robust Preprocessing
- Always split before preprocessing. Keep training and test data completely separate at the outset.
- Fit transformers only on training data. Use
fiton training,transformon all sets. - Use pipelines. Encapsulate the entire preprocessing and modelling workflow to avoid manual mistakes.
- Validate hyperparameters with nested CV. Separate the evaluation from the tuning loop.
- Monitor for over‑fitting. Compare training and validation metrics; employ early stopping where applicable.
- Document data flow. Keep a clear record of each transformation and its parameters.
By systematically addressing data leakage, scaling errors, and over‑fitting, practitioners can build models that truly generalise. The code snippets above provide concrete patterns to integrate into a Moodle lesson, illustrating how best practice translates into reproducible, maintainable pipelines.
Reflect on the pitfalls highlighted in the “Pitfalls: Common Mistakes and How to Avoid Them” section. In a few sentences, describe a real‑world scenario where a preprocessing error (such as incorrect scaling, leakage, or mishandled missing values) led to a model’s failure. Then propose a concrete strategy to prevent that mistake in future projects.
Key Takeaways
- Understand the importance of data preprocessing in the AI workflow for disease classification
- Identify common preprocessing techniques such as normalisation, encoding, and imputation
- Apply preprocessing steps to a real-world medical dataset
- Evaluate the impact of preprocessing choices on model performance
Further Reading
- Goodfellow, I., Bengio, Y., & Courville, A. (2016) — Deep Learning. A comprehensive textbook covering data preprocessing techniques for neural networks. This source explains standardisation, normalisation and data augmentation essential for disease classification tasks.
- Raschka, S., & Mirjalili, V. (2020) — Python Machine Learning (2nd ed.). Covers practical preprocessing steps such as missing‑value imputation, encoding categorical variables and feature scaling, all demonstrated with scikit‑learn.
- IEEE (2021) — IEEE Std 1885.1–2021: Data Pre‑Processing for Machine Learning. Provides industry‑accepted guidelines for data cleaning, outlier handling and dimensionality reduction, useful for clinical datasets.
- Wang, Y., & Wang, J. (2019) — Explainable Machine Learning in Medical Imaging. Discusses domain‑specific preprocessing for radiomic feature extraction and bias mitigation in disease classification studies.
- Microsoft Azure Machine Learning Documentation (2024) — Data Preparation. Official cloud documentation detailing best practices for data ingestion, validation and transformation pipelines in medical applications.


Comments are closed.