Transforming Features for Linear Models
A logarithmic transformation can improve a linear model when a feature is asymmetrical or has large outliers.
A Model That Scores Poorly
Imagine you have trained a Ridge regression model and checked its R 2 score. The score is relatively low, which means the model was not able to capture the relationship between the feature X and the target y. The natural reaction is to suspect the model, but a poor score is often telling you something about the shape of the data itself.
The fix introduced in this part of the chapter does not change the model at all. It changes the feature values before the model sees them. Transforming a feature so that a linear model can use it more effectively is the central idea of this chapter, and the transformation studied here is the logarithm. As you read, keep one question in mind: what does this line of code do to each value in the array, and why would a model respond differently to the result?
Why Zero Breaks the Logarithm
Feature transformation: changing the values of a feature before a model is trained on them, so that a linear model can use the feature more effectively. The model itself stays the same.
The feature in this example contains the value 0. The logarithm is not defined at 0, so the transformation cannot simply be the logarithm of each value X. If you applied the plain logarithm to a zero, the result would not be a usable number, and the transformed feature would be broken before the model ever saw it.
Why log(X + 1) Replaces log(X)
The source solves the zero problem by adding one before taking the logarithm. Each value in the feature is replaced by the logarithm of that value plus one. When X is zero or positive, the input to the logarithm is at least 1, so it is always strictly greater than zero and the logarithm is defined.
In Python this is written as np.log(X + 1). NumPy applies the expression to every element of an array at once, so one line of code transforms an entire feature column. The +1 is applied first, inside the parentheses, and the logarithm is taken of the result.
Tracing Inputs Through the Transform
The diagram below traces a few generated input values through log(X + 1). Notice that the input 0 fails under the plain logarithm but produces a valid output once one is added first. Each arrow shows the value entering the transform and the value that comes out.
| Original value X | X + 1 | log(X + 1), rounded |
|---|---|---|
| 0 | 1 | 0.00 |
| 9 | 10 | 2.30 |
| 99 | 100 | 4.61 |
| 999 | 1000 | 6.91 |
Generated values showing how log(X + 1) maps inputs to outputs (natural logarithm, as computed by np.log)
Worked Example: Compressing Large Values
How far apart the values end up
A feature takes the values 0, 9, 99, and 999. Apply log(X + 1) to each value and compare the gap between the smallest and largest values before and after the transformation.
- 1Transform the zero
0 + 1 = 1, and the logarithm of 1 is 0. The zero stays a valid, finite value.
- 2Transform the middle values
9 + 1 = 10 gives about 2.30, and 99 + 1 = 100 gives about 4.61.
- 3Transform the largest value
999 + 1 = 1000 gives about 6.91. The largest value is now close to the others on a much smaller scale.
- 4Compare the gaps
On the original scale the gap between 9 and 999 is 990. Between 9 and 99 the gap is 90, but between 99 and 999 it is 900. After the transform, the gap between 9 and 99 is about 2.31, and between 99 and 999 it is about 2.30. Each tenfold step in X adds roughly the same amount to the output.
The original feature spans 0 to 999. The transformed feature spans 0 to about 6.91. The logarithm compresses large values much more than small ones, so the extreme value 999 no longer dominates the feature.
How the Distribution Changes
The purpose of the transformation is to make the data more symmetrical and to reduce the effect of outliers. Before the transformation, the feature is asymmetrical: most of its values sit in one region, and a few very large values stand apart from the rest. This is the right-skewed shape shown on the left of the diagram below. After the transformation, the distribution is less asymmetrical and the very large outliers are no longer present in the same way, as shown on the right.
The compression is the mechanism behind the change. Because the logarithm stretches small values apart and squeezes large values together, the long tail of big numbers is pulled inward. The values that were far apart on the original scale end up much closer together, which is why the transformed feature is easier for a linear model to use.
Applying the Transform to Train and Test
The source applies the transformation to both the training split and the test split, producing two new arrays. The same expression is used for each, so the model sees values that were produced in the same way whether it is learning or being evaluated. The code below is a generated illustration of that pattern, using the array names the source uses for the transformed data.
- 1Line 1 imports NumPy under the usual alias np.
- 2Line 3 creates a new training array where every value is replaced by log(value + 1).
- 3Line 4 applies the same expression to the test array, producing a second new array.
np.logis the NumPy logarithm, applied element by element to the whole array- X_train + 1 adds one to every training value before the logarithm is taken
- The identical expression on X_test ensures the test values are transformed the same way
Checking the Result With a Histogram
A histogram groups values into bins and shows how many values fall into each bin, which makes it the natural way to see whether the distribution has become more symmetrical. The source plots the first column of the transformed training data with 25 bins in gray and labels its axes. The code below is a generated version of that plotting step.
- 1Line 3 passes the first column of X_train_log directly to plt.hist.
- 2The histogram therefore shows the values already stored in X_train_log, not any new transformation.
- The first argument is the array that is plotted, so it defines what the histogram shows
- bins=25 divides the value range into 25 groups
- The axis labels describe the values and how often each one appears
plt.hist(X_train_log[:, 0], bins=25, color='gray')The histogram plots the values stored in X_train_log, which are the feature the model was trained on.
plt.hist(np.log(X_train_log[:, 0] + 1), bins=25, color='gray')The histogram plots log applied a second time, so the picture reflects two transformations and can look different from the training feature.
Mistakes to Avoid
Common mistake Using plain np.log(X) on a feature that contains zeros
What it looks likenp.log(X_train) when X_train contains 0Why it failsThe logarithm is not defined at 0, so the zero value cannot be transformed.
Do this insteadUse np.log(X_train + 1), which keeps every input to the logarithm at 1 or above when X is zero or positive.
Common mistake Transforming the training split and leaving the test split raw
What it looks likeX_train_log = np.log(X_train + 1) with X_test used unchangedWhy it failsThe model is trained on logarithmic values and then evaluated on raw values, so its inputs are on different scales.
Do this insteadApply the same np.log(X + 1) expression to both the training and test arrays.
Common mistake Applying the transform again in the plotting line
What it looks likeplt.hist(np.log(X_train_log[:, 0] + 1), bins=25)Why it failsThe histogram shows the values passed to plt.hist, so the plot reflects two transformations and no longer matches the training feature.
Do this insteadPass X_train_log[:, 0] directly to plt.hist, and check the exact expression that produced the plotted array.
Common mistake Assuming a low R 2 score always means the model is wrong, without checking the shape of the feature
Practice: Spot the Extra Transform
A colleague transforms a feature with np.log(X_train + 1) and np.log(X_test + 1), then plots a histogram with plt.hist(np.log(X_train_log[:, 0] + 1), bins=25, color='gray'). The histogram looks more symmetrical than expected. Which line changes the data the histogram shows, and what would you change to make the plot match the training feature?
Hints
- Find the line where the histogram is drawn and look at the first argument to plt.hist.
- Ask whether the plotted array is X_train_log itself or an expression built from it.
- The fix is in the plotting line, not in the transformation of the training or test splits.
What do you think happens?
If the plotting line transforms the stored values a second time, does the histogram show the same values the model was trained on?
Reveal answer
No, because the histogram shows the values passed into plt.hist, which include a second transformation
The histogram is built from whatever expression is passed as its first argument. If that expression applies log(X + 1) again, the plot shows double-transformed values rather than the stored training feature.
Key Takeaways
- 1The logarithm is not defined at 0, so the transform uses log(X + 1), written as
np.log(X + 1)in Python. - 2The same transformation must be applied to both the training and test splits so the model sees consistent values.
- 3The logarithm compresses large values more than small ones, making an asymmetrical distribution more symmetrical and reducing the influence of large outliers.
- 4A histogram shows the values passed to plt.hist, so an extra transformation in the plotting line changes the picture.
At a glance
- The logarithm is not defined at 0, so the transform uses log(X + 1), written as
np.log(X + 1)in Python. - The same transformation must be applied to both the training and test splits so the model sees consistent values.
- The logarithm compresses large values more than small ones, making an asymmetrical distribution more symmetrical and reducing the influence of large outliers.
- A histogram shows the values passed to plt.hist, so an extra transformation in the plotting line changes the picture.