Linear and Quadratic Discriminant Analysis › Example
Machine Learning from ScratchAll chapters Linear and Quadratic Discriminant Analysis › ExampleSection 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
We work through a small one-dimensional example with concrete numbers so that every step is visible. The point of the example is not to show a realistic data analysis, but to make the discriminant scores themselves tangible: we can compute them by hand, compare the LDA and QDA decisions, and see exactly where the quadratic boundary comes from. Consider a two-class problem with the following samples:
Both classes have four observations, so . Equal priors keep the comparison focused on the densities: any difference in the decision must come from the class means and variances, not from one class being more common than the other.
Step 1: compute means and variances.
For class 0, the mean is , and the unbiased variance is
For class 1, the mean is , and the unbiased variance is
The empirical variance of class 1 is four times that of class 0, by construction of this small example. These sample spreads make the two fitted covariance conventions differ visibly. Four observations per class do not establish the covariances of an underlying population, so we treat this as a worked calculation rather than evidence that a real population violates LDA's assumption.
Step 2: pooled variance for LDA.
The pooled variance divides the total within-class squared deviations by the degrees of freedom . The numerator is the sum of the within-class squared deviations: class 0 contributes and class 1 contributes , for a total of . Thus
This pooled value is a compromise between the tight class 0 variance and the broad class 1 variance. It is the single shape that LDA will use for both classes.
Step 3: LDA discriminant scores.
Using the exact pooled estimate , the one-dimensional linear discriminant is
where:
- : the estimated mean of class
- : the pooled variance shared by both classes
- : the estimated prior probability of class
- : the (scalar) feature value being classified
Working through the two classes:
At a test point :
The larger score is , so LDA predicts class 0. At a test point :
Here LDA predicts class 1. The boundary between the two classes is where , i.e., . Solving: subtract from both sides to get ; add to both sides to get ; divide by to get . As expected, this is the midpoint of the two class means, . The equal-prior assumption removes any prior-driven shift, and the shared variance makes the boundary exactly halfway between the centers.
Step 4: QDA discriminant scores.
QDA keeps the class-specific variances. The discriminant is
For class 0:
where:
- : the estimated mean of class 0
- : the unbiased estimated variance of class 0
- : the estimated prior probability of class 0
- : the test point at which the class-0 QDA score is evaluated
For class 1:
At :
The quadratic contributions are exactly for class 0 and for class 1. The combined log-variance and log-prior constants are approximately and , respectively; retaining them until the final rounding gives the scores above.
In each score, the quadratic contribution is minus one-half of the squared standardized distance. The remaining terms are the log-variance penalty and log prior. Both classes are evaluated at the same test point .
QDA also predicts class 0 at because . At , where the exact LDA scores tie:
QDA favors class 1 at , while LDA is indifferent there; a claim about different hard labels at that exact point would require an LDA tie-breaking convention. This point is 2.75 raw units from both means. Relative to the class standard deviations, that is about 2.13 standard deviations for class 0 and 1.07 for class 1. The quadratic contributions are and . Class 1 pays the larger log-variance penalty, approximately rather than , but its less negative quadratic contribution more than offsets that penalty. Thus the broader class has the higher QDA score at the midpoint.
Step 5: QDA decision boundary.
Solving gives a quadratic equation. We keep the exact variances and logarithms until the numerical evaluation:
The equal log-prior terms cancel. Multiplying by and collecting the log-variance difference gives
The left-hand side expands to . Define the boundary polynomial
Here is the exact quadratic whose zeros are the QDA ties, and is the natural logarithm. Since , the constant term is approximately . The quadratic formula gives
To determine the winning regions, subtract the exact class-0 score from the class-1 score:
The multiplier is positive, so the score difference has the same sign as . Its positive leading coefficient makes negative between the roots and positive outside them. Class 0 therefore wins between the two roots, class 1 wins outside, and the endpoints are ties. The wider class wins in both far tails because its quadratic Mahalanobis contribution grows more slowly, while the class-0 penalty along its own metric dominates far from both centers. This two-tail allocation cannot be expressed by a single nondegenerate LDA threshold.
The worked example produces more than isolated labels: it produces two score curves whose crossings define the decision regions. The figure below evaluates the fitted LDA and QDA scores on a grid whose interval spans both QDA roots and the LDA midpoint. Compare the two classes within one method: each method may discard a different term common to its classes, so score heights across methods are not on a shared probability scale.
PythonIn [10]Expand
qda_poly = np.array([3.0, 2.0, -40.0 - (40.0 / 3.0) * np.log(2.0)])
qda_roots_example = np.sort(np.roots(qda_poly))
x_example = np.linspace(
min(-1.0, qda_roots_example[0] - 1.0),
max(11.0, qda_roots_example[-1] + 1.0),
500,
)
mu0_example, mu1_example = 1.5, 7.0
var0_example, var1_example = 5.0 / 3.0, 20.0 / 3.0
pooled_example = 25.0 / 6.0
log_prior_example = np.log(0.5)
lda0_example = (
(mu0_example / pooled_example) * x_example
- mu0_example**2 / (2.0 * pooled_example)
+ log_prior_example
)
lda1_example = (
(mu1_example / pooled_example) * x_example
- mu1_example**2 / (2.0 * pooled_example)
+ log_prior_example
)
qda0_example = (
-0.5 * (x_example - mu0_example) ** 2 / var0_example
- 0.5 * np.log(var0_example)
+ log_prior_example
)
qda1_example = (
-0.5 * (x_example - mu1_example) ** 2 / var1_example
- 0.5 * np.log(var1_example)
+ log_prior_example
)
lda_boundary_example = 0.5 * (mu0_example + mu1_example)VisualizationOut [11]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!