Naive Bayes › Example: A Two-Feature Gaussian Calculation
Machine Learning from ScratchAll chapters Naive Bayes › Example: A Two-Feature Gaussian CalculationSection view · Sections in this chapter6 / 10 ↓
Reading tools Definitions, links & notes
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
The formulas are compact, but compact formulas hide their arithmetic. Before writing any code, we work one small Gaussian naive Bayes problem by hand, in full, so that every symbol in the score has a concrete number attached to it. For the Gaussian parameter estimates in this worked example, subscripts give the class first and the feature second; the general Gaussian formulas above use feature first and class second. The training data have two features and two classes, with five observations per class:
| Class | Feature 1 | Feature 2 |
|---|---|---|
| 0 | 1 | 2 |
| 0 | 2 | 3 |
| 0 | 3 | 2 |
| 0 | 1 | 1 |
| 0 | 2 | 2 |
| 1 | 6 | 5 |
| 1 | 7 | 6 |
| 1 | 8 | 7 |
| 1 | 7 | 6 |
| 1 | 8 | 5 |
The first thing to check is whether anything needs smoothing. Each class has five observations, no feature is constant within a class, and no variance is zero, so the unsmoothed maximum likelihood estimates are safe here and we can use them directly.
The priors are equal because each class has five observations: . We want to classify the new point , which sits in the gap between the two clusters but is closer to class 1.
We begin with the sufficient statistics. For class 0, the feature means are
The variances use division by :
For class 1, the means are
The variances use division by in the same way:
where:
- : the class 1 values of feature 1 used in the mean
- : the class 1 values of feature 2 used in the mean
- : the sum of the class 1 values of feature 1
- : the sum of the class 1 values of feature 2
- : the class 1 mean for feature 1,
- : the class 1 mean for feature 2,
- : the sum of squared deviations for feature 1 and, separately, for feature 2
- : the number of class 1 observations,
With the statistics in hand, we evaluate one log density at a time. Each density has the same two-part structure: a normalizing constant that depends only on the variance, and a negative penalty that grows with the squared distance from the mean. For class 0 and feature 1, with :
where:
- : the value of feature 1 for the new point
- : the class 0 mean for feature 1,
- : the class 0 variance for feature 1,
- : the log-density normalizing contribution from the variance
- : the squared distance from the mean measured in units of the variance
The first term is . The squared distance is , and the denominator is , so the second term is . The squared-distance term dominates the normalizing constant here. The new point is far from the class 0 mean, and in this calculation the distance penalty () is much larger than the variance normalizing constant (). Thus
For class 0 and feature 2, with :
where:
- : the value of feature 2 for the new point
- : the class 0 mean for feature 2,
- : the class 0 variance for feature 2,
The first term is . The squared distance is , and the denominator is , so the second term is . Thus
The total log score for class 0 is the log prior plus the two log densities, evaluated before rounding:
Now we repeat the calculation for class 1. For feature 1 the arithmetic is symmetric, because the distance from to is also and the variance is the same:
For class 1 and feature 2, with and mean :
where:
- : the class 1 mean for feature 2,
- : the class 1 variance for feature 2,
The squared distance is , so the second term is . The first term is . Thus
This is the decisive term. Feature 1 contributes identically to both classes, so the entire difference between the two scores comes from feature 2, where the new point is much closer to the class 1 mean. The total log score for class 1, again evaluated before rounding, is
Class 1 has the larger score by . To convert the scores into a posterior probability, we use the logistic form for two classes:
where:
- : the unnormalized log score for class 0, computed above as
- : the unnormalized log score for class 1, computed above as
- : the score difference, which is the only quantity that matters after normalization
Substituting gives
So we classify the point as class 1 with a predicted probability of about . Notice how the log-sum-exp normalization works here in a two-class setting: the difference of scores is the only quantity that matters, and the sigmoid turns that difference into a probability without ever computing the raw likelihoods.
Within class 0, the sample correlation between the two features is about . Within class 1, it is about . These sample correlations are a diagnostic to investigate; five observations per class cannot establish population conditional dependence. The fitted model nevertheless multiplies the two marginal likelihoods. It favors class 1, while the calibration of its probability remains a separate question.
We can verify the arithmetic with a short computation:
PythonIn [10]Expand
X_ex = np.array(
[
[1, 2],
[2, 3],
[3, 2],
[1, 1],
[2, 2],
[6, 5],
[7, 6],
[8, 7],
[7, 6],
[8, 5],
],
dtype=float,
)
y_ex = np.array([0] * 5 + [1] * 5)
x_new = np.array([4.5, 4.0])
def normal_log_pdf(x, mu, var):
return -0.5 * np.log(2 * np.pi * var) - 0.5 * (x - mu) ** 2 / var
example_rows = []
for k in [0, 1]:
Xk = X_ex[y_ex == k]
mu = Xk.mean(axis=0)
var = Xk.var(axis=0)
log_score = np.log(0.5)
for j in range(2):
log_score += normal_log_pdf(x_new[j], mu[j], var[j])
example_rows.append(
{
"class": k,
"mean feature 1": mu[0],
"mean feature 2": mu[1],
"variance feature 1": var[0],
"variance feature 2": var[1],
"log score": log_score,
}
)
example_table = pd.DataFrame(example_rows)ConsoleOut [11]Expand
class mean feature 1 mean feature 2 variance feature 1 variance feature 2 log score
0 1.8 2.0 0.56 0.40 -13.2919
1 7.2 5.8 0.56 0.56 -11.3530
Posterior probability of class 1: 0.8742
Within-class correlation for class 0: 0.4226
Within-class correlation for class 1: 0.4286The printed means, variances, and scores match the hand calculation at the displayed precision. The posterior from the printed scores comes out at approximately , matching the earlier sigmoid calculation. The arithmetic agrees with the formulas. The sample correlations motivate checking the class-specific dependence and calibration on more data; they do not establish population dependence from five observations per class.
The printed scores come from a product of one-dimensional densities. The next figure shows those densities directly for feature 2, with the new observation marked.
PythonIn [12]Expand
# Compute per-feature Gaussian densities for feature 2 from the worked example.
feature_index = 1
x_grid_density = np.linspace(
X_ex[:, feature_index].min() - 1.0,
X_ex[:, feature_index].max() + 1.0,
300,
)
density_by_class = {}
for k in [0, 1]:
Xk = X_ex[y_ex == k]
mu = Xk[:, feature_index].mean()
var = Xk[:, feature_index].var()
density_by_class[k] = np.exp(normal_log_pdf(x_grid_density, mu, var))VisualizationOut [13]Expand

Comments
Add to the conversation
A question, a correction, or an example from your own work.
No comments yet. Be the first to share your thoughts!