Feature engineering — creating new, more informative input columns from existing raw data — is frequently the single highest-leverage activity in a machine learning project, often mattering more than the choice of algorithm itself.
import pandas as pd
import numpy as np
df["price_per_sqft"] = df["price"] / df["sqft"] # a ratio often more predictive than either raw number
df["log_income"] = np.log1p(df["income"]) # log transform reduces the impact of extreme values
df["purchase_date"] = pd.to_datetime(df["purchase_date"])
df["day_of_week"] = df["purchase_date"].dt.dayofweek # extracting a component as a new feature
df["is_weekend"] = df["day_of_week"].isin([5, 6]) # a derived boolean feature
df["age_group"] = pd.cut(df["age"], bins=[0, 18, 35, 60, 100],
labels=["minor", "young_adult", "adult", "senior"]) # binning a continuous value
Raw data rarely arrives in the ideal shape for a model — a ratio (price_per_sqft) or a difference between two columns is often far more directly predictive than either original column on its own, since it captures the relationship the model would otherwise have to learn indirectly. Domain knowledge about the actual problem being solved is what makes this step effective — knowing that "price per square foot" is a meaningful real estate concept isn't something an algorithm can invent on its own.
A log transform (np.log1p, which handles zero values safely unlike plain np.log) compresses a right-skewed distribution (income, house prices — many small values, a few very large ones) toward something closer to normal, which several algorithms and statistical assumptions work better with. Binning a continuous variable into discrete categories (pd.cut) trades away some precision for a form that can capture non-linear relationships (a linear model assumes age's effect is a straight line; age groups let it assign a genuinely different effect to each bucket) or that's simply more interpretable for a human reader.