Anomaly Detection: How to Identify Outliers in Your Data

Throughout this journey, we’ve examined tools to describe data, test hypotheses, and build models. But there’s a question that comes before all others—one that’s too often ignored: are these data reliable?

In any dataset—daily sessions, organic clicks, conversion rates—values that don’t behave like the others can hide. Values that deviate abnormally from the rest of the distribution. In statistics, we call them outliers, or anomalous values.

Let’s make one point clear immediately: an anomalous value isn’t necessarily an error. It can be a measurement error, certainly (a broken tracking tag, a bot inflating sessions). But it can also be the most important signal in the entire dataset: a Google algorithm update, content going viral, a technical issue crushing traffic. The issue isn’t eliminating anomalies—it’s recognizing them and then deciding what to do about them.

In this article, we’ll examine three statistical methods for identifying outliers, from the most intuitive to the most formal. For each, we’ll look at the logic, the limitations, and practical application with R.

Continue reading “Anomaly Detection: How to Identify Outliers in Your Data”

Bayesian Statistics: How to Learn from Data, One Step at a Time

In previous articles, we’ve examined statistical inference from a precise and coherent perspective: formulate a hypothesis, collect data, calculate a p-value, construct a confidence interval. We’ve conducted hypothesis tests, compared variants with A/B testing, and seen with the Central Limit Theorem why all of this works even when data isn’t normal.

This approach—called frequentist—has a clear logic: the parameter we want to estimate is a fixed value (even if unknown), and we “chase” it with data. But there’s another way to think about uncertainty, one that allows us to update our beliefs as new data arrives. It’s called the Bayesian approach, and in this article we’ll build its foundations.

Let’s start with a concrete example. Imagine we’ve just launched an advertising campaign and we don’t know the true click rate. We have an initial opinion based on experience (“click rates usually fall between 0% and 20%”), and then data starts coming in. The Bayesian approach lets us combine our initial opinion with the observed data to get an updated estimate—and repeat this process every time new information arrives.

Continue reading “Bayesian Statistics: How to Learn from Data, One Step at a Time”

The Central Limit Theorem: Why Statistics Works (Even When Data Isn’t Normal)

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.

Continue reading “The Central Limit Theorem: Why Statistics Works (Even When Data Isn’t Normal)”

Which Statistical Test Should You Use for A/B Testing? Practical Guide with R

Statistical tests are fundamental tools for data analysis and informed decision-making. Choosing the appropriate test depends on the characteristics of the data, the hypotheses to be tested, and the underlying assumptions.

Continue reading “Which Statistical Test Should You Use for A/B Testing? Practical Guide with R”

How to Use Decision Trees to Classify Data

When we analyse a dataset with many variables and want to predict a category — whether a user will convert or not, whether a page will reach the top 10, whether a keyword is high or low competition — the first problem is figuring out which variables actually matter and how to combine them.
Decision trees tackle this problem in the most natural way possible: a sequence of yes/no questions that, step by step, separate the data into increasingly homogeneous groups.

The general idea

A decision tree is a structure that makes decisions sequentially. Each internal node asks a question (for instance: “is petal length greater than 2.45 cm?”), and depending on the answer it follows a branch to the next node. Eventually we reach a leaf, which assigns the predicted class.

The power of the idea is that the result is intrinsically interpretable: you can understand why the tree made a particular decision by simply following the path from root to leaf. No black box.

Impurity and split criteria

How does the tree decide which question to ask first? The goal is to find the split that separates the data as “cleanly” as possible. The core concept is impurity: a perfect split is one where all elements in a group belong to the same class.

The most common measure is the Gini index. For a node with \( K \) classes, where \( p_k \) is the proportion of elements in class \( k \):

\( G = \sum_{k=1}^{K} p_k (1 – p_k) \\ \)

The index is 0 when the node is pure (all elements belong to the same class), and reaches its maximum when classes are uniformly distributed.
The tree tries all possible splits on all variables and chooses the one that most reduces the weighted average impurity of the two child nodes.

Other measures exist, such as entropy (used in C4.5 trees), but the idea is the same: minimise uncertainty.

A decision tree in R with the iris data

We build a decision tree using the iris dataset, which contains measurements of sepals and petals for 150 flowers of three species. Our goal is to classify the species based on the measurements.

Let’s prepare the data:

data(iris)
library(rpart)

set.seed(123)
train_idx <- sample(1:nrow(iris), 0.8 * nrow(iris))
train <- iris[train_idx, ]
test  <- iris[-train_idx, ]

tree <- rpart(Species ~ ., data = train, method = "class")

Visualise the tree:

library(rpart.plot)
rpart.plot(tree, type = 2, extra = 104)
Decision tree: classification of iris species
Decision tree: classification of iris species

The tree is very simple. It starts at the root with a question about petal length. If it is less than 2.45 cm, we can be certain the species is setosa (all 41 training cases go there). If it is greater, we move to the next branch, which separates versicolor from virginica again based on petal length, this time with a threshold of 4.75 cm.

Notice that the tree did not use sepal measurements at all: it found them irrelevant for classification and ignored them automatically. This is an implicit form of variable selection: the tree chooses which metrics matter, and in which order.

An SEO example: predicting top 10 pages

Iris is fine for understanding the mechanism, but let’s try something closer to our daily work. We generate synthetic data for 200 web pages with real SEO metrics — word count, readability, speed, backlinks, images, keyword in title, meta description length — and build a tree that predicts whether a page will reach the top 10.

set.seed(2026)

n <- 200
word_count      <- round(runif(n, 300, 3000))
readability     <- round(runif(n, 20, 80), 1)
page_speed_ms   <- round(runif(n, 500, 8000))
backlinks       <- round(runif(n, 0, 150))
images          <- round(runif(n, 0, 20))
keyword_in_title <- rbinom(n, 1, 0.35)
meta_desc_len   <- round(runif(n, 0, 165))

# simulated target: top_10 based on a combination of factors
score <- (
  (backlinks > 30) * 0.30 +
  (keyword_in_title == 1) * 0.20 +
  (readability > 45 & readability < 70) * 0.15 +
  (word_count > 800 & word_count < 2200) * 0.15 +
  (page_speed_ms < 3000) * 0.10 +
  (meta_desc_len > 110 & meta_desc_len < 155) * 0.10
)
score <- score + rnorm(n, 0, 0.12)
top_10 <- factor(ifelse(score > 0.50, "yes", "no"))

seo_data <- data.frame(word_count, readability, page_speed_ms,
                       backlinks, images, keyword_in_title,
                       meta_desc_len, top_10)

The target variable top_10 is constructed so that it depends on all these factors, each with a different weight — backlinks are the most influential, followed by the presence of the keyword in the title, readability in the right range, and so on. We also add a random component to simulate what we cannot measure.

Train the tree:

library(rpart)
train_idx <- sample(1:n, 0.7 * n)
tree_seo <- rpart(top_10 ~ ., data = seo_data[train_idx, ],
                  method = "class", control = rpart.control(cp = 0.01))

Visualise the resulting tree:

library(rpart.plot)
rpart.plot(tree_seo, type = 2, extra = 104)
Decision tree: predicting top 10 pages
Decision tree: predicting top 10 pages

The tree tells us something very interesting. The first question — the most discriminant variable — is about backlinks: if they are few, the page can hardly reach the top 10, regardless of other factors. Only pages with enough backlinks move to the next branch, where the tree evaluates readability and keyword presence in the title. This is exactly the kind of hierarchy we would expect from a serious SEO analysis: first authority (backlinks), then on-page quality.

Feature importance confirms this hierarchy:

tree_seo$variable.importance
SEO variable importance
SEO variable importance

Backlinks dominate, as expected. Readability and keyword in title come next, while speed and meta description length contribute less. The tree did not select the number of images as relevant — in our synthetic data it was not, and the tree ignored it.

n.b. These data are synthetic: in a real project with real data, the tree would reveal the actual hierarchy of variables for your specific context. The example is meant to show the kind of answer you get, not to establish universal truths.

An important caveat: the tree identifies recurring patterns in observed data, not cause-effect relationships. A variable can be very useful for predicting a page’s behaviour without being its direct cause. For instance, if the tree selects CTR as a discriminant variable, it does not mean that increasing CTR will push the page into the top 10 — it could be that pages already in the top 10 tend to have higher CTR precisely because they are in the top 10. The tree tells us what is correlated, not what is causal.

Evaluating accuracy

Let’s check how it performs on the test data:

pred <- predict(tree, newdata = test, type = "class")
table(pred, test$Species)

The contingency table (confusion matrix) shows how many predictions are correct and how many are not. Accuracy is calculated as:

\( \text{accuracy} = \frac{\text{correct predictions}}{\text{total predictions}} \\ \)

Our tree makes very few mistakes: it is a simple model and the iris data are well separated. In the real world, things are almost always more complex.

Overfitting and pruning

Here is the delicate part. A decision tree can keep splitting until every leaf contains a single element. In that case, accuracy on the training set will be 100%. But the tree will have simply memorised the training dataset instead of learning its regularities.

This phenomenon is called overfitting: the model adapts too closely to the training data, capturing noise and specific details that will not appear in new data.

Overfitting is the main problem with decision trees. A tree that is too deep memorises the training set but fails on new data; one that is too shallow cannot capture the real structure of the data. The optimal balance is found through cross-validation and pruning: building an intentionally large tree and then cutting the branches that do not improve prediction error.

In R, you can prune with rpart using the cp parameter (complexity parameter), which prevents the tree from making splits that do not reduce error by at least a certain threshold. The printcp(tree) function shows cross-validated error for different tree sizes, helping you choose the cut-off point.

Variable importance

One of the most useful byproducts of a decision tree is feature importance: a measure of how much each variable contributes to the overall reduction in impurity. In rpart:

tree$variable.importance

This tells us, for example, that petal length is by far the most discriminant variable for iris — and sepal measurements count for little or nothing.
In web marketing, a similar analysis can reveal which metrics truly separate high-performing pages from low-performing ones.

From single trees to forests: Random Forest

A single decision tree is unstable: a small change in the input data can produce a completely different tree. To reduce this problem, Random Forests build hundreds of trees on slightly different versions of the data (bootstrap sampling) and average their predictions. The result is much more stable and accurate than a single tree, while retaining much of its interpretability.

If a single tree is like asking one expert for their opinion, a Random Forest is like consulting hundreds of independent experts and going with the majority.

We will cover this in a future article.

FAQ

When should I use a decision tree instead of logistic regression?
When the data have complex interactions between variables (for example, the effect of age on conversion changes depending on the acquisition channel), and when interpretability is the priority.

Can a tree handle numerical and categorical variables together?
Yes, rpart handles both types natively. Categorical variables are internally converted to binary splits.

How deep should a tree be?
It depends on the data. The rule of thumb is to use cross-validation (the plotcp() function in rpart) to find the depth that minimises prediction error on unseen data.

Does the decision tree work for regression too?
Yes. Using method = "anova" instead of "class", the tree predicts numerical values (for example, expected time on page).

Why did the tree only use petal length?
Because in the iris data it is the most discriminant variable: it alone suffices to separate one of the three species and almost separates the other two. The tree, being a greedy algorithm, chooses the variable that gives the best immediate split.


From decision trees to gradient descent and dimensionality reduction, there is a common thread: all solve the problem of giving structure to complex data, each with its own approach. Our next step in this journey is gradient descent, the engine that powers much of modern machine learning.

Further reading

If you want to dive deeper into decision trees, An Introduction to Statistical Learning by James, Witten, Hastie and Tibshirani is the reference: it covers trees, Random Forests and split criteria with the right balance of intuition and formalism. The R labs are freely available online.