Your website data isn’t normal. Almost never is. Organic traffic, conversion rates, session durations: in most cases their distribution doesn’t look anything like a bell curve. Yet confidence intervals work, hypothesis tests are reliable, statistical inference holds up. How is that possible? The answer lies in one of the most elegant and powerful results in all of mathematics: the Central Limit Theorem (often abbreviated as CLT). It’s the theorem that, in a sense, justifies the entire edifice of inferential statistics. We’ve already met it — implicitly — when we built confidence intervals and conducted hypothesis tests: through all those steps, the normal distribution was there, always present, like a quiet thread running through everything. But the question we may have asked ourselves without a satisfying answer is: why does it work so well even when our data aren’t normal at all? Who said organic traffic, conversion rates, or session durations follow a bell curve? In most cases, they don’t.
Let’s start with the formal statement, and then we’ll translate it into plain language.
The Central Limit Theorem states that: if we draw sufficiently large samples from any population with finite mean \(\mu\) and finite standard deviation \(\sigma\), the distribution of the sample means will be approximately normal, regardless of the shape of the original distribution.
More precisely, the distribution of sample means \(\bar{X}\) tends to:
\( \bar{X} \sim N\left(\mu, \, \frac{\sigma}{\sqrt{n}}\right) \\ \)where:
In clearer, more direct terms: it doesn’t matter how strange, skewed, or bizarre the distribution of our original data is. If we take many samples and calculate the mean of each one, those means will arrange themselves into a bell curve. Always.
This point must always be kept firmly in mind, because it’s the keystone of everything we’ve done so far.
When we calculate a confidence interval or conduct a hypothesis test, we don’t work with individual data points: we work with sample means. And the CLT guarantees that those means, as long as the sample is large enough, follow a normal distribution (or approximately so).
That’s why we can use the normal distribution and the t-distribution even when the original data aren’t normal. We’re not making a risky assumption: we’re leveraging a solid mathematical result.
In practice, the CLT is the reason why:
The most common CLT mistake
Many people think the Central Limit Theorem says that the population becomes normal if the sample is large enough. It doesn’t. The CLT doesn’t transform your raw data: no matter how large the sample, your daily traffic distribution will remain skewed. What becomes normal is the sampling distribution of the mean — a different thing, operating at a higher level of abstraction. Individual days stay as skewed as they ever were; it’s the average of many samples that forms a bell curve. Confusing the two leads to wrong conclusions: for example, demanding that daily sales data follow a normal distribution before running a hypothesis test, when in fact the test works with means and it’s the CLT that guarantees its validity.
Theory is beautiful, but seeing the CLT in action is something else entirely. Let’s build a simulation in R that shows the theorem at work.
We’ll start from a decidedly non-normal distribution: an exponential distribution, which is strongly right-skewed (think of the distribution of time spent on a website: many very short visits, few very long ones).
Let’s simulate the repeated sampling process in R:
set.seed(42)
# Popolazione: distribuzione esponenziale (media = 1/lambda)
lambda <- 0.5
pop_mean <- 1 / lambda # media vera = 2
# Simuliamo 10000 campioni di dimensione n
n_campioni <- 10000
# Funzione per calcolare le medie campionarie
simula_medie <- function(n) {
replicate(n_campioni, mean(rexp(n, rate = lambda)))
}
# Proviamo con tre dimensioni campionarie diverse
medie_n5 <- simula_medie(5)
medie_n30 <- simula_medie(30)
medie_n100 <- simula_medie(100)
# Visualizziamo
par(mfrow = c(2, 2))
# La distribuzione originale (esponenziale)
hist(rexp(10000, rate = lambda), breaks = 50, probability = TRUE,
main = "Popolazione originale\n(esponenziale)",
col = "lightcoral", xlab = "Valore", ylab = "Densità")
# Medie con n = 5
hist(medie_n5, breaks = 50, probability = TRUE,
main = "Medie campionarie (n = 5)",
col = "lightyellow", xlab = "Media", ylab = "Densità")
curve(dnorm(x, mean = pop_mean, sd = pop_mean / sqrt(5)),
add = TRUE, col = "red", lwd = 2)
# Medie con n = 30
hist(medie_n30, breaks = 50, probability = TRUE,
main = "Medie campionarie (n = 30)",
col = "lightgreen", xlab = "Media", ylab = "Densità")
curve(dnorm(x, mean = pop_mean, sd = pop_mean / sqrt(30)),
add = TRUE, col = "red", lwd = 2)
# Medie con n = 100
hist(medie_n100, breaks = 50, probability = TRUE,
main = "Medie campionarie (n = 100)",
col = "lightblue", xlab = "Media", ylab = "Densità")
curve(dnorm(x, mean = pop_mean, sd = pop_mean / sqrt(100)),
add = TRUE, col = "red", lwd = 2) As you can see, the result is spectacular. The starting population is completely asymmetric (the exponential doesn’t remotely resemble a bell curve), and yet:
Truly child’s play: just increase the sample size and normality emerges on its own.
A legitimate question: “sufficiently large” is a rather vague term. In practice, how large does the sample need to be for the CLT to do its job?
The most common rule of thumb is n ≥ 30. With 30 or more observations, the distribution of sample means is generally well approximated by the normal, even if the original distribution is moderately skewed.
But be careful: this rule has exceptions.
In everyday SEO and digital marketing practice, we usually work with samples well above 30 (hundreds or thousands of sessions, clicks, conversions), so the CLT is almost always on our side.
The theorem also tells us something valuable about the spread of sample means. The standard deviation of the distribution of means (i.e., the standard error) is:
\( SE = \frac{\sigma}{\sqrt{n}} \\ \)This has two important practical consequences:
Let’s verify in R that the observed standard error matches the theoretical formula:
# Errore standard teorico per n = 30
se_teorico <- pop_mean / sqrt(30)
# Errore standard osservato dalla simulazione
se_osservato <- sd(medie_n30)
cat("SE teorico:", round(se_teorico, 4), "\n")
cat("SE osservato:", round(se_osservato, 4), "\n")
cat("Differenza:", round(abs(se_teorico - se_osservato), 4), "\n") The agreement is remarkable: the two values practically coincide. The CLT works exactly as promised.
Let’s apply the CLT to a concrete case. Suppose we monitor a website’s daily organic traffic for a year (365 days). Traffic data are never normal: they’re right-skewed (weekdays vs. weekends, seasonal peaks, anomalies).
Let’s simulate a realistic scenario in R:
set.seed(123)
# Simuliamo 365 giorni di traffico (distribuzione log-normale, tipica del web)
traffico <- round(rlnorm(365, meanlog = 6, sdlog = 0.5))
cat("Media traffico giornaliero:", round(mean(traffico)), "visite\n")
cat("Mediana:", round(median(traffico)), "visite\n")
cat("Dev. standard:", round(sd(traffico)), "visite\n")
# Prendiamo campioni di 30 giorni e calcoliamo la media di ciascuno
medie_mensili <- replicate(5000, mean(sample(traffico, 30, replace = TRUE)))
par(mfrow = c(1, 2))
hist(traffico, breaks = 30, probability = TRUE,
main = "Traffico giornaliero\n(365 giorni)",
col = "lightcoral", xlab = "Visite", ylab = "Densità")
hist(medie_mensili, breaks = 50, probability = TRUE,
main = "Medie di campioni\nda 30 giorni",
col = "lightblue", xlab = "Media visite", ylab = "Densità")
curve(dnorm(x, mean = mean(traffico), sd = sd(traffico) / sqrt(30)),
add = TRUE, col = "red", lwd = 2)
# Test di normalità sulle medie
shapiro.test(sample(medie_mensili, 5000)) Daily traffic is clearly asymmetric (the mean differs from the median, the distribution has a long right tail). But the means of 30-day samples? Perfectly normal, just as the CLT guarantees.
This is exactly why we can build reliable confidence intervals for mean traffic, even though individual days have a distribution that’s anything but normal.
It would be dishonest not to mention the cases where the CLT has its limits. The theorem requires that the population have finite mean and variance. There are distributions (such as the Cauchy distribution) that don’t have a finite variance, and for these the CLT doesn’t hold.
In SEO and marketing practice, this is rarely a problem: our data always have finite mean and variance. However, it’s worth remembering that:
An e-commerce site records the following order amounts (in euros) over a month:
ordini <- c(12, 8, 45, 15, 22, 150, 9, 18, 35, 11,
14, 200, 7, 19, 28, 13, 55, 10, 16, 95,
8, 21, 42, 12, 17, 310, 9, 14, 25, 11) replicate() and sample() to generate 5000 sample means with n = 10 (sampling with replacement)Hint: replicate(5000, mean(sample(ordini, 10, replace = TRUE))) does almost all the work.
We’ve seen how the Central Limit Theorem is the hidden foundation of all inferential statistics: it’s the reason we can build confidence intervals, conduct hypothesis tests, and make reliable predictions, even when our data aren’t normal. But the CLT has also taught us that sample size is crucial. This opens the door to a very practical question: how much data do we need? That’s the problem of sample size and sampling, topics we’ll tackle in an upcoming article.
If you want to deepen your understanding of the role of the normal distribution and the Central Limit Theorem in statistical practice, The Art of Statistics by David Spiegelhalter is an excellent companion. Spiegelhalter manages to explain why the bell curve appears everywhere — from physical measurements to election polls — with a clarity that never sacrifices rigor.
It happens with every reasonably serious project: you export the keyword list from Search Console…
Anyone who spends their days inside Search Console knows that little nagging feeling: a page…
In the article on the multi-armed bandit we used Bayes to decide between variants: shifting…
In the article on Bayesian A/B testing we compared two variants at a fixed sample…
In the article on classic A/B testing we saw how to compare two variants with…
In the article on the foundations of Bayesian statistics, we saw how Bayesian updating works…