Introduction And Lesson Overview

Welcome back! In the last lesson, you learned how to validate and evaluate predictive models using PredictHealth’s insurance data. You practiced splitting your data into training, validation, and test sets, building preprocessing pipelines, and using metrics and visualizations to assess your models. These are essential skills for building reliable models that generalize well to new data.

In this lesson, we will take your modeling skills to the next level by focusing on feature engineering — the process of creating custom predictors from raw data. While you have already worked with basic features like age, BMI, and categorical variables, real-world data often contains hidden patterns that can be revealed by transforming or combining existing features. Feature engineering helps you capture these patterns, leading to more accurate and insightful models.

By the end of this lesson, you will know how to create new, meaningful features from raw data, visualize their impact, and compare models built with engineered features to those using only the original variables. This will help you understand the true power of custom predictors in predictive modeling.

Creating Custom Predictors From Raw Data

You have already seen how to use raw features such as age, BMI, and smoker status in your models. However, these raw features do not always capture the full story. Feature engineering allows you to transform these basic variables into new predictors that can better reflect real-world relationships.

For example, instead of using age as a simple number, you might group ages into categories that make sense for insurance risk, such as "Young Adult," "Adult," "Middle-aged," and "Senior." Similarly, BMI can be grouped into standard health categories like "Underweight," "Normal," "Overweight," and "Obese." You can also create new features, such as family size, by combining the number of children with the insured person, or convert categorical variables like smoker status into numeric values for easier modeling.

Let's look at how you can create these custom predictors in code:

import pandas as pd

# Make a copy so we don't change the original data
feature_data = insurance_data.copy()

# Convert age into age groups
bins = [0, 25, 40, 55, 100]
labels = ['Young Adult', 'Adult', 'Middle-aged', 'Senior']
feature_data['age_group'] = pd.cut(feature_data['age'], bins=bins, labels=labels)

# Convert BMI into health categories
bmi_bins = [0, 18.5, 25, 30, 100]
bmi_labels = ['Underweight', 'Normal', 'Overweight', 'Obese']
feature_data['bmi_category'] = pd.cut(feature_data['bmi'], bins=bmi_bins, labels=bmi_labels)

# Create a family size feature
feature_data['family_size'] = feature_data['children'] + 1  # +1 for the insured person

# Convert smoker to a numeric value
feature_data['smoker_numeric'] = feature_data['smoker'].map({'yes': 1, 'no': 0})

Here's how a sample row looks before and after transformation:

OriginalEngineered
age: 19age_group: Young Adult
bmi: 27.9bmi_category: Overweight
children: 0family_size: 1
smoker: yessmoker_numeric: 1

Notice how age becomes age_group, bmi becomes bmi_category, children becomes family_size, and smoker becomes smoker_numeric. These transformations make the data more meaningful for modeling.

Visualizing The Engineered Features

Once you have created new features, it is important to understand how they relate to your target variable — in this case, insurance charges. Visualization helps you see patterns and relationships that may not be obvious from the raw data alone. This step can also guide you in selecting the most useful features for your model.

For example, you can use bar plots to compare average insurance charges across different age groups, BMI categories, and family sizes. You can also look at how smoker status interacts with age groups to affect costs.

Here is how you might visualize these relationships:

import matplotlib.pyplot as plt
import seaborn as sns

plt.figure(figsize=(15, 10))

# Average charges by age group
plt.subplot(2, 2, 1)
sns.barplot(x='age_group', y='charges', data=feature_data, palette='viridis')
plt.title('Average Insurance Cost by Age Group')
plt.xlabel('Age Group')
plt.ylabel('Average Charges ($)')

# Average charges by BMI category
plt.subplot(2, 2, 2)
sns.barplot(x='bmi_category', y='charges', data=feature_data, palette='viridis')
plt.title('Average Insurance Cost by BMI Category')
plt.xlabel('BMI Category')
plt.ylabel('Average Charges ($)')

# Average charges by family size
plt.subplot(2, 2, 3)
sns.barplot(x='family_size', y='charges', data=feature_data, palette='viridis')
plt.title('Average Insurance Cost by Family Size')
plt.xlabel('Family Size')
plt.ylabel('Average Charges ($)')

# Charges by age group and smoker status
plt.subplot(2, 2, 4)
sns.barplot(x='age_group', y='charges', hue='smoker', data=feature_data, palette='Set1')
plt.title('Insurance Cost by Age Group and Smoker Status')
plt.xlabel('Age Group')
plt.ylabel('Average Charges ($)')
plt.legend(title='Smoker')

plt.tight_layout()
plt.show()

These plots will help you see, for example, that insurance charges tend to increase with age group and BMI category, and that smokers in every age group pay much higher charges. Visual inspection like this is a powerful way to spot which features are most important and how they interact.

Building A Regression Model With Engineered Features

Now that you have created and visualized your custom predictors, you are ready to use them in a regression model. This involves preparing your data by encoding categorical variables, splitting the data into training and test sets, and then training and evaluating the model.

First, you need to convert categorical features into a format that the model can use. One-hot encoding is a common approach, where each category becomes a separate column. You then combine these with your numeric features.

Here is how you can prepare the data and build the model:

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np

# One-hot encode categorical variables
cat_features = pd.get_dummies(feature_data[['age_group', 'bmi_category', 'sex', 'region']], drop_first=True)

# Combine with numeric features
X_engineered = pd.concat([feature_data[['family_size', 'smoker_numeric']], cat_features], axis=1)
y = feature_data['charges']

# Split the data
X_train, X_test, y_train, y_test = train_test_split(X_engineered, y, test_size=0.2, random_state=42)

# Train the model
model = LinearRegression()
model.fit(X_train, y_train)

# Make predictions
y_pred = model.predict(X_test)

# Evaluate the model
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2 = r2_score(y_test, y_pred)

print("\nModel Performance with Engineered Features:")
print(f"Root Mean Squared Error (RMSE): {rmse:.2f}")
print(f"R-squared (R²): {r2:.4f}")

A typical output might look like this:

Model Performance with Engineered Features:
Root Mean Squared Error (RMSE): 5000.12
R-squared (R²): 0.82

This shows how well your model predicts insurance charges using the new, engineered features.

Comparing Engineered Features With Original Features

To understand the value of feature engineering, it is helpful to compare your new model to one built with only the original features. This means using the raw age, BMI, and children columns, along with simple encodings for categorical variables.

Here is how you can build and evaluate the original model:

# Prepare original data with simple encoding
cat_features_orig = pd.get_dummies(insurance_data[['sex', 'smoker', 'region']], drop_first=True)
X_original = pd.concat([insurance_data[['age', 'bmi', 'children']], cat_features_orig], axis=1)

# Split the data
X_train_orig, X_test_orig, y_train_orig, y_test_orig = train_test_split(X_original, y, test_size=0.2, random_state=42)

# Train the model
model_orig = LinearRegression()
model_orig.fit(X_train_orig, y_train_orig)

# Make predictions
y_pred_orig = model_orig.predict(X_test_orig)

# Evaluate the model
rmse_orig = np.sqrt(mean_squared_error(y_test_orig, y_pred_orig))
r2_orig = r2_score(y_test_orig, y_pred_orig)

print("\nOriginal Model Performance:")
print(f"Root Mean Squared Error (RMSE): {rmse_orig:.2f}")
print(f"R-squared (R²): {r2_orig:.4f}")

Suppose the output is:

Original Model Performance:
Root Mean Squared Error (RMSE): 5400.45
R-squared (R²): 0.78

You can also visualize the predictions from both models to see the improvement. For example, plotting predicted vs. actual charges for each model side by side can make the difference clear.

plt.figure(figsize=(12, 5))

# Original model
plt.subplot(1, 2, 1)
plt.scatter(y_test_orig, y_pred_orig, alpha=0.5)
plt.plot([0, 60000], [0, 60000], 'r--')
plt.title('Original Model: Predicted vs Actual')
plt.xlabel('Actual Charges ($)')
plt.ylabel('Predicted Charges ($)')

# Engineered features model
plt.subplot(1, 2, 2)
plt.scatter(y_test, y_pred, alpha=0.5)
plt.plot([0, 60000], [0, 60000], 'r--')
plt.title('Engineered Features Model: Predicted vs Actual')
plt.xlabel('Actual Charges ($)')
plt.ylabel('Predicted Charges ($)')

plt.tight_layout()
plt.show()

You can see that the model with engineered features produces predictions that are closer to the actual values, especially for certain groups.

Finally, you can summarize the improvement:

rmse_improve = ((rmse_orig - rmse) / rmse_orig) * 100
r2_improve = ((r2 - r2_orig) / r2_orig) * 100
print(f"RMSE improved by {rmse_improve:.1f}%")
print(f"R² improved by {r2_improve:.1f}%")

If the output is:

RMSE improved by 7.4%
R² improved by 5.1%

This quantifies the benefit of your feature engineering efforts.

Summary And Preparation For Practice Exercises

In this lesson, you learned how to create custom predictors from raw insurance data — a process known as feature engineering. You saw how to group age and BMI into meaningful categories, create new features like family size, and convert categorical variables into numeric form. You also learned how to visualize these new features to better understand their relationship with insurance charges.

After building and evaluating a regression model with your engineered features, you compared its performance to a model using only the original variables. The results showed that thoughtful feature engineering can lead to more accurate and insightful models.

You are now ready to apply these techniques in hands-on practice. In the next exercises, you will create your own custom predictors, visualize their impact, and see how they improve your models. Keep up the great work — feature engineering is one of the most powerful tools in your data science toolkit!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal