Skip to main content

Command Palette

Search for a command to run...

Your ML Model Might Need Better Features, Not a Better Algorithm Feature Engineering Explained: A Beginner's Guide

Updated
•16 min read•View as Markdown

Imagine you're building a machine learning model to predict whether someone will repay a loan, you give the model a dataset containing the borrower's age, monthly income, employment status, loan amount, and repayment history. You train it.

But what if the model could learn something more useful from the same information?

For example, instead of looking at monthly income and loan amount separately, you could calculate how much of the borrower's monthly income would go toward the loan payment.

That new value might tell the model something neither column communicates as directly on its own.

This is feature engineering.

And it's one of those machine learning concepts that sounds complicated until you start looking at ordinary data differently.

What is feature engineering?

Feature engineering is the process of creating, transforming, or selecting input variables that help a machine learning model learn patterns relevant to a prediction task.

Let's break that down.

Raw data is the information you collect. It might contain numbers, dates, categories, text, missing values, and a few columns that make you wonder why anyone collected them in the first place.

A feature is a piece of information the model uses to make a prediction.

Feature engineering is how we turn the raw information into a representation that is more useful for the model.

For our loan example, raw data might contain:

Raw column Value
Monthly income UGX 1,500,000
Monthly loan payment UGX 450,000
Existing monthly debt payments UGX 150,000
Employment status Self-employed
Loan purpose Business
Previous late payments 2

From these columns, we could derive a feature called debt-to-income ratio:

Formula: DTI Ratio = Monthly Debt Payments / Monthly Income

Calculation: DTI Ratio = 600,000 / 1,500,000 = 0.4 (40%)

That is a debt-to-income ratio of 40%.

The ratio gives the model a compact representation of how much of the borrower's monthly income is already committed to debt payments.

It doesn't guarantee a better prediction, and it isn't a complete measure of creditworthiness. But it gives us a meaningful candidate feature to test.

That's the key idea is that we're not collecting more information. We're making better use of the information we already have.

Why does feature engineering matter?

A machine learning algorithm learns patterns from the representation of data it receives.

If that representation hides a useful relationship, the model may struggle to learn it.

Suppose two borrowers have these profiles:

Borrower Monthly income Monthly loan payment
A UGX 500,000 UGX 200,000
B UGX 2,000,000 UGX 200,000

Both have the same monthly loan payment.

But the payment represents 40% of Borrower A's income and only 10% of Borrower B's income.

Looking at the loan payment alone doesn't communicate that difference. Combining the information into a ratio makes it easier to represent.

This is one reason a carefully designed feature set can matter as much as, or sometimes more than, switching to a more complicated algorithm.

Of course, the best features depend on the model, the dataset, the prediction task, and how the features are evaluated. Feature engineering is not a guarantee of better performance.

1. Numerical features

Numerical features represent quantities that can be measured or counted.

In a loan repayment dataset, examples include:

  • Monthly income

  • Loan amount

  • Monthly repayment amount

  • Number of previous late payments

  • Length of employment in months

  • Existing monthly debt

Some numerical features are continuous, meaning they can take many values within a range. Income and loan amounts are examples.

Others are discrete counts, such as the number of previous late payments.

Creating useful numerical features

You can combine existing numerical columns to create new ones.

For example:

  • Debt-to-income ratio

  • Loan amount divided by annual income

  • Monthly disposable income

  • Number of late payments per year, if the relevant time period is available

  • Difference between income and estimated monthly expenses

Here's a simple example:

import pandas as pd

df = pd.DataFrame({
    "monthly_income": [1_500_000, 800_000, 2_000_000],
    "monthly_debt_payments": [600_000, 200_000, 500_000],
    "loan_amount": [5_000_000, 2_000_000, 8_000_000],
})

df["debt_to_income_ratio"] = (
    df["monthly_debt_payments"] / df["monthly_income"]
)

print(df[["debt_to_income_ratio"]])

The new column represents the proportion of monthly income already committed to debt payments.

In a real dataset, we'd also check for zero or missing income values before dividing. We would need to define the time period consistently, too. Comparing monthly debt payments with annual income without converting the units would produce a misleading feature.

One more thing: a ratio is not automatically better than its original columns. Keep it if evaluation shows that it helps, or if it provides another clear benefit.

2. Categorical features

Not all useful information comes as numbers.

A loan application might include:

  • Employment status: employed, self-employed, unemployed

  • Loan purpose: education, business, housing

  • Residence type: rented, owned, family home

  • Application channel: branch, mobile app, agent

These are categorical features. They represent groups or labels rather than numerical quantities.

Most traditional machine learning algorithms need categorical values to be represented in a suitable numerical format.

Simply changing labels to numbers can be dangerous.

For example:

Employed      → 1
Self-employed → 2
Unemployed    → 3

This encoding introduces an apparent order: 3 is greater than 2, which is greater than 1. But employment categories don't necessarily have that numerical relationship. For nominal categories, one common solution is one-hot encoding.

For example:

Employment status employed self_employed unemployed
Employed 1 0 0
Self-employed 0 1 0
Unemployed 0 0 1

Each category gets its own binary feature. In pandas, you can create this representation with:

encoded = pd.get_dummies(
    df,
    columns=["employment_status"],
    dtype=int
)

This assumes your DataFrame contains an employment_status column.

For a production model, be careful about categories that appear during prediction but were not present during training. A fitted encoder in a consistent preprocessing pipeline can handle this more reliably than manually encoding columns each time.

Also, some categories have a genuine order. For example, an explicitly defined education level may be ordinal. Whether to use ordinal encoding depends on the meaning of the categories and the model.

3. Time features

Dates can contain more useful information than their raw values suggest, suppose a borrower has a repayment date of 2026-10-15. A model might benefit from features such as:

  • Day of the week

  • Month

  • Time since the loan was issued

  • Number of days since the last repayment

  • Number of missed payments within a defined past period

We can extract some calendar features with pandas:

df = pd.DataFrame({
    "application_date": [
        "2026-01-12",
        "2026-06-21",
        "2026-10-05",
    ]
})

df["application_date"] = pd.to_datetime(
    df["application_date"]
)

df["application_month"] = (
    df["application_date"].dt.month
)

df["application_day_of_week"] = (
    df["application_date"].dt.dayofweek
)

print(df)

The dayofweek values range from 0 for Monday to 6 for Sunday.

Whether these features help depends on the problem. A day-of-week feature might be useful for predicting transaction activity, for example, but irrelevant for a particular loan repayment task.

There is also a major warning with time-based data which is to use only information that would have been available at the moment the prediction was made.

If you're predicting repayment at the time a loan application is submitted, you cannot use the borrower's eventual repayment date or future missed payments as input features.

That would be data leakage. We'll get to that shortly.

4. Text features

Loan applications may also contain text, for example, an applicant might write a short explanation of why they need a loan.

Text can contain useful information, but a traditional ML model cannot automatically understand a raw sentence in the same way a human reader does. One simple approach is to convert text into numerical representations.

A common beginner-friendly technique is bag-of-words: represent documents using counts or weights associated with the words they contain. For example, these two phrases contain different terms:

"Buy equipment for my business"

"Pay school fees for my children"

A bag-of-words representation can turn the text into a vector of numerical values indicating which terms occur and how frequently. In Python, scikit-learn's CountVectorizer can do this:

from sklearn.feature_extraction.text import CountVectorizer

texts = [
    "buy equipment for my business",
    "pay school fees for my children",
    "expand my small business",
]

vectorizer = CountVectorizer()

text_features = vectorizer.fit_transform(texts)

print(vectorizer.get_feature_names_out())
print(text_features.toarray())

The vectorizer builds a vocabulary and represents each text using counts from that vocabulary.

This is only one option. TF-IDF can reduce the relative weight of terms that occur frequently across documents, while more advanced NLP systems can use learned embeddings.

For a loan model, though, we should not assume that an applicant's wording reliably predicts repayment. Text features can encode irrelevant patterns or sensitive proxies, and any such use needs careful evaluation for fairness, privacy, and regulatory compliance.

5. Feature scaling

Imagine one feature measures age in years and another measures annual income in Uganda shillings, their numerical ranges are very different. Age might range from 18 to 70, while annual income might range from hundreds of thousands to tens of millions.

Some algorithms are sensitive to these differences in scale. Models based on distances, gradient optimization, or regularization often benefit from appropriate scaling. Two common techniques are standardization and min-max scaling:

Standardization

Standardization transforms a feature using its mean and standard deviation:

z = (x - mean) / standard_deviation

Definitions:

  • x: The original value.

  • mean: The average calculated from the training data.

  • standard_deviation: The standard deviation calculated from the training data.

The transformed values are centered around zero and scaled by the standard deviation, assuming it is nonzero.

Min-max scaling

Min-max scaling maps values to a chosen range, commonly 0 to 1:

x_scaled = (x - x_min) / (x_max - x_min)

Where:

  • x_scaled: The new normalized value (typically between 0 and 1).

  • x: The original value.

  • x_min: The minimum value in the training data.

  • x_max: The maximum value in the training data.

This formula assumes the feature has a nonzero range.

Do all models need feature scaling?

No.

Linear models, support vector machines, k-nearest neighbors, and many gradient-based methods can benefit from scaling, depending on the setup.

Tree-based models such as decision trees and random forests generally don't require standardization because their splits depend on feature thresholds rather than distances between raw values.

Scaling also doesn't automatically make a feature more informative. It changes its numerical representation.

One important rule: fit the scaler on the training data, then use that fitted scaler to transform validation, test, and production data. Don't calculate the scaling statistics using the entire dataset before splitting it. That can leak information from evaluation data into the training process.

Scikit-learn pipelines help keep these steps consistent. Its documentation explains both the benefits of preprocessing pipelines and the risks of data leakage. Scikit-learn: Common pitfalls and recommended practices.

6. Feature selection

After creating and preparing features, you might end up with dozens or hundreds of columns.

Do you need all of them?

Not necessarily.

Feature selection is the process of choosing a useful subset of features for a model.

Why might you remove features?

  • Some contain very little useful information.

  • Some duplicate information already represented elsewhere.

  • Some add noise.

  • Some make the system more expensive or complicated.

  • Some create privacy, fairness, or data-availability concerns.

  • Some are only available after the prediction should already have been made.

There are several approaches to feature selection.

Filter methods

These evaluate features using statistical criteria, such as correlation or tests of association. They're relatively simple, but a feature that appears weak on its own might still be useful in combination with other features.

Wrapper methods

These evaluate subsets of features by repeatedly training and assessing a model. They can be computationally expensive because they may require many model fits.

Embedded methods

Some models perform feature selection as part of training. Regularization methods, for example, can shrink certain coefficients toward zero.

Tree-based models also provide feature-importance measures, although those measures must be interpreted carefully. Importance does not prove causation, and correlated features can complicate interpretation.

The goal isn't to keep the smallest possible dataset. It's to find a useful representation that performs well on unseen data and makes sense for the application.

Feature selection must also be performed without using the held-out test set to make decisions. Otherwise, the test set gradually becomes part of the model-development process.

7. Data leakage: the feature engineering mistake to avoid

This is one of the most important parts of the whole topic. Data leakage happens when information that wouldn't legitimately be available at prediction time influences model training or evaluation. The result can be a model that looks excellent during testing but performs much worse when deployed.

Let's return to loan repayment.

Suppose we're predicting whether a borrower will repay a loan when they apply. Our dataset contains:

Feature Available at application time?
Monthly income Yes, if collected and verified
Employment status Yes, if collected
Requested loan amount Yes
Previous repayment history Potentially, if historical records are available
Number of missed payments on the new loan No, not at application time
Final repayment status No, this is the outcome we're trying to predict

If we use the number of missed payments on the new loan to predict whether the borrower will repay it, we're allowing future information into the model.

The model isn't learning to predict the future, it's getting clues from what happened afterward and that is a serious problem.

How to avoid leakage

First, define the exact moment when the prediction will be made.

Then ask of every feature: would this information genuinely be available at that moment?

Next, split the dataset into training and evaluation sets before fitting preprocessing steps that learn statistics or select features. Fit those steps on the training data only.

For time-dependent tasks, use a time-aware evaluation strategy when appropriate. Randomly splitting historical records can create an unrealistic evaluation if it lets future information influence the past.

Finally, build the feature transformations into a repeatable pipeline so training and prediction use the same rules.

A useful question to keep asking is:

"Would I actually know this value when the model needs to make its prediction?"

If the answer is no, don't use it as an input feature for that prediction.

8. Let's build a small feature engineering example

Let's put some of these ideas together.

We'll create a tiny illustrative loan dataset and engineer a few features using pandas. The dataset is synthetic, it isn't real borrower data, and the example is for learning rather than making actual lending decisions.

import pandas as pd

df = pd.DataFrame({
    "monthly_income": [1_500_000, 800_000, 2_000_000, 1_200_000],
    "monthly_debt_payments": [600_000, 200_000, 500_000, 300_000],
    "loan_amount": [5_000_000, 2_000_000, 8_000_000, 3_000_000],
    "loan_term_months": [12, 10, 24, 12],
    "employment_status": [
        "employed",
        "self-employed",
        "employed",
        "self-employed",
    ],
    "application_date": [
        "2026-01-12",
        "2026-03-18",
        "2026-06-21",
        "2026-10-05",
    ],
    "repaid": [1, 1, 0, 1],
})

# Create a feature from existing numerical columns.
df["debt_to_income_ratio"] = (
    df["monthly_debt_payments"] / df["monthly_income"]
)

# Extract a calendar feature from the application date.
df["application_month"] = (
    pd.to_datetime(df["application_date"]).dt.month
)

# Convert the categorical feature into indicator columns.
df = pd.get_dummies(
    df,
    columns=["employment_status"],
    dtype=int,
)

print(df.head())

Let's walk through what happened.

First, we created debt_to_income_ratio from two existing numerical features.

Second, we extracted the application month from the date. This would be available at application time, assuming the date is recorded correctly.

Third, we encoded employment status into numerical indicator columns.

The repaid column is our target: the outcome the model will learn to predict. It must not be included among the input features.

Also, this small example intentionally leaves out several steps you'd need for a real training pipeline, including splitting the data, handling missing values, validating feature ranges, and checking whether the features are useful.

One final detail: the loan amount and loan term might be used to derive an estimated monthly payment, but only if the repayment schedule and interest assumptions are known. It's better to define the calculation carefully than create a feature that looks meaningful but doesn't represent the actual loan.

9. Putting it into a real ML workflow

Feature engineering doesn't end when you create a few columns in a notebook.

If you're building a model that will be used in production, you need to ensure the same feature logic is applied consistently to training data and new applications.

For example, if your model was trained using a debt-to-income ratio calculated from monthly values, production inference must calculate that ratio using the same definitions and units.

A pipeline can help organize preprocessing and model training. In scikit-learn, you can combine transformations and an estimator so that the same fitted preprocessing steps are applied when making predictions.

This is especially useful for:

  • Handling missing numerical values.

  • Encoding categorical variables.

  • Scaling features where appropriate.

  • Selecting features.

  • Avoiding inconsistent transformations between training and serving.

For a more complex dataset with numerical and categorical columns, scikit-learn's ColumnTransformer can apply different transformations to different groups of columns, while a Pipeline chains those transformations together with a model.

The broader lesson is that feature engineering is part of the model system, not a one-time notebook trick.

If the feature definitions change, you may need to retrain the model and evaluate the new version. If upstream data changes unexpectedly, feature validation and monitoring can help catch the problem before it silently affects predictions.

What I'm taking away

Feature engineering is about asking better questions of your data, Instead of looking at monthly income and loan amount as unrelated columns, I can ask whether their relationship tells me something useful.

Instead of feeding a model raw dates, I can consider whether the month or elapsed time is relevant.

Instead of assuming every column deserves a place in the dataset, I can question whether it's useful, available at prediction time, and appropriate for the task.

And I need to remember that more features don't automatically mean a better model.

The next time you open a dataset, don't rush straight to choosing an algorithm.

Look at the columns first.

Ask what each one represents, what it leaves out, what useful information you could derive from it, and whether that information would genuinely be available when the prediction is made.

Sometimes the most useful improvement to an ML model starts before the model is trained.

I'm documenting what I'm learning about machine learning and production systems here on Godfident Data. Next, I'll continue exploring the decisions that turn a working model into a reliable system.

I Finally Understand…

Part 5 of 7

A beginner-friendly, 10-day guide demystifying MLOps and production machine learning. From data drift and pipeline management to post-deployment model monitoring, learn how to bridge the gap between building a model and keeping it running successfully in the real world.

Up next

Why Machine Learning Models Fail After Deployment

A model can be mathematically correct and still fail spectacularly in production, it can achieve excellent accuracy during evaluation, pass your tests, and produce predictions that look perfectly reas