Visualizing Feature Distributions with Histograms
Count features are often skewed and contain large outliers, which can make a linear model fit poorly.
Why Count Features Pile Up on One Side
Count data appears whenever you record how many times something happened: visits to a page, items bought in a store, or appearances of a word. Features built this way share a recognizable pattern. Most values are small, a few values are very large, and the bulk of the data sits on one side of the range. When you draw a histogram of such a feature, the tall bars cluster near the low end and a long, thin tail stretches out to the right.
This shape matters for modeling. A linear model fitted directly to a feature like this can struggle, because a handful of extreme values pulls the fitted line toward themselves, and the model then captures the main relationship poorly. The source example that this article follows makes the point concretely: a Ridge regression model reached only a relatively low R squared score on its data, meaning it did not capture the relationship between the feature and the target well.
One way to address a skewed count feature is to transform it with a logarithm before fitting the model. This article shows exactly how that transformation is written, what it changes in the data, and how to check the change with a histogram. The central skill is tracing what each step does to the values, so the sections below move through the operations one at a time.
Tracing the Shift Before the Logarithm
The transformation is a single line of NumPy applied to the training and test feature matrices. Before you read the explanation, try to predict what the expression does to a zero. The question is posed directly to you below, so write down your answer first.
What do you think happens?
If one of the feature values in X_train is 0, what does the expression X_train + 1 produce for that entry, and why might that matter for the logarithm that follows?
Reveal answer
It produces 1, because 0 + 1 is 1.
The shift turns a count of 0 into the input 1 before the logarithm is applied. The logarithm is undefined at 0, so the shift is what gives the zero entry a valid input.
[0. 0.69314718 1.38629436 2.30258509 4.60517019]- X_train is a NumPy array of raw count values, including a 0 and one very large count
- X_train + 1 adds 1 to every element at once, which is the shift
np.logapplies the natural logarithm to each element of the shifted array- The output has the same length as the input: one transformed value per original count
Walk through the single line that does the work: X_train + 1 is evaluated first, producing a new array in which every count has been increased by 1. np.log is then called on that new array, so it never sees the raw 0 at all.- 1Line 3 creates the raw count array, containing 0, 1, 3, 9, and 99.
- 2Line 4 evaluates X_train + 1 first, giving 1, 2, 4, 10, and 100.
- 3
np.logis then applied element by element to that shifted array. - 4The first output entry is 0 because the logarithm of 1 is 0; the raw 0 was mapped to a valid input.
Transforming three count values by hand
Apply log(X + 1) to the counts 0, 3, and 99.
- 1Shift the first count
0 + 1 = 1, so the logarithm is taken of 1 rather than of 0.
- 2Shift the second count
3 + 1 = 4, so the logarithm is taken of 4.
- 3Shift the third count
99 + 1 = 100, so the logarithm is taken of 100.
- 4Take the logarithm
The logarithm of 1 is 0, of 4 is about 1.3863, and of 100 is about 4.6052.
The counts 0, 3, and 99 become approximately 0, 1.3863, and 4.6052. The very large count is pulled much closer to the smaller ones.
Why Zero Breaks a Plain Logarithm
The source gives the reason for the shift directly. The value 0 appears in the data, and the logarithm is not defined at 0. Because of this, applying the logarithm to the raw values is not an option. The transformation is therefore written as $\log(X + 1)$, which first adds 1 to each value and then takes the logarithm.
The shift guarantees that the argument of the logarithm is never zero for a count of zero, since 0 + 1 is 1. The original count of 0 is therefore mapped to a valid input rather than an undefined one. The shift also keeps every non-negative count within the domain of the logarithm, which is why the same expression works across the whole column without special cases.
[ -inf 0. 1.09861229 2.19722458 4.59511985]- Taking the logarithm of the raw array gives -inf for the entry that was 0
- NumPy also reports a divide-by-zero warning for this entry, which signals the undefined case
- Compare this with the previous example, where the same zero produced a finite output of 0
How the Shape of the Distribution Changes
A histogram shows how many values fall into each range, so it is a natural way to confirm that the transformation did what it was meant to do. The source plots the feature after the transformation, with a label on each axis and 25 bins. Plotting the transformed column in the same way lets you compare its shape with a histogram of the raw column.
The source describes the effect in two parts. First, the distribution becomes less asymmetrical: the lopsided pile of small values and the long tail of large values become more balanced. Second, the very large outliers that stretched the range are reduced. A logarithm compresses large values much more than small ones, so the long tail of big counts ends up closer to the bulk of the data. In the example above, the raw count of 99 sits nearly a hundred units away from the count of 1, while the transformed values for 1 and 99 are separated by about 3.9 units on the log scale.
- plt.hist draws the histogram of the transformed column with 25 bins, as the source does
- The axis labels make the plot readable, which the source also includes
- Plot the raw column the same way and compare the two shapes side by side
Finding Where an Unexpected Result Appears
A transformation usually sits inside a longer pipeline: the data is loaded, the transformation is applied, and the result is plotted or fed to a model. When a histogram looks wrong, the useful question is which stage first produced the unexpected result. Tracing the stages in order lets you find that point, rather than guessing from the final plot.
Suppose the histogram looks more compressed than the first histogram did. Trace back from the plot. The plot reads the array that was passed to it, so check that array first. If the column has already been transformed and the transformation is applied again, the values are changed twice. The unexpected shape first appears at the second transform, not at the plot, and the plot only reveals it. The fix is to plot the array as it leaves the first transformation, or to keep track of which columns have already been transformed.
Mistakes Beginners Make With This Transformation
Common mistake Taking the logarithm of the raw column without shifting it
What it looks likenp.log(X_train) on a column containing 0Why it failsThe logarithm is undefined at 0, so the zero entry becomes -inf instead of a usable value.
Do this insteadWrite np.log(X_train + 1) so that every count of 0 is mapped to the valid input 1.
Common mistake Reversing the order of the shift and the logarithm
What it looks likenp.log(X_train) + 1Why it failsThe logarithm is still taken of the raw 0 before the 1 is added, so the undefined case returns.
Do this insteadAdd 1 first, then take the logarithm of the shifted values.
Common mistake Transforming only the training data
Why it failsThe model is then trained on one scale and asked to predict on another, so the test features do not match what the model learned.
Do this insteadApply the same transformation to both the training and the test feature matrices.
Common mistake Applying the transformation again to a column that is already transformed
Why it failsEach application changes the values again, so the plot and the model no longer match the intended scale.
Do this insteadKeep track of which columns have been transformed, and compare any plot against the array that was actually intended.
Practice Tracing the Transformation
Suppose X_test is np.array([0, 2, 5, 40]). Write the NumPy line that applies the same transformation used for X_train, then predict the transformed value for the entry 40 before you check it.
Hints
- The transformation is written as log(X + 1), so the shift comes before the logarithm.
- Use
np.log(X_test + 1), applied to the whole array at once. - The value for 40 is the logarithm of 41, which is a little over 3.7.
A colleague says the histogram of the transformed feature still has a long right tail. List two things you would check, in order, to locate where the unexpected result first appears in the pipeline.
Hints
- Start from the array that is handed to the plot and work backward through each stage.
- Ask whether the transformation was applied more than once, and whether the test data was transformed in the same way as the training data.
Key Takeaways
- Count features are often skewed, with most values small, a few very large, and a long tail on one side, which can make a linear model fit poorly.
- The logarithm is undefined at 0, so the transformation is written as log(X + 1), with the shift applied before the logarithm.
- After the transformation, the distribution is less asymmetrical and the very large outliers are reduced, because a logarithm compresses large values more than small ones.
- Apply the same transformation to training and test data, and be careful not to transform a column that has already been transformed.
- When a result looks wrong, trace the pipeline stage by stage to find the step where the unexpected value first appears.