Introduction

In Test cricket, losing early wickets can place a batting team under significant pressure. However, does losing wickets in the opening 10 overs actually influence a team’s chances of winning the match?

This analysis uses ball-by-ball Test cricket data from Cricsheet to investigate the relationship between early wickets and match outcomes.

We will use logistic regression in R to estimate how the number of wickets lost in the first 10 overs of the first innings relates to the probability of winning a Test match.

The analysis will focus on three questions:

1. Loading the Data

We will use the cricketdata package to access ball-by-ball Test cricket data from Cricsheet.

First, load the required packages.

library(tidyverse)
## Warning: package 'ggplot2' was built under R version 4.5.2
## Warning: package 'tidyr' was built under R version 4.5.2
## Warning: package 'readr' was built under R version 4.5.2
## Warning: package 'purrr' was built under R version 4.5.2
## Warning: package 'dplyr' was built under R version 4.5.2
## Warning: package 'lubridate' was built under R version 4.5.2
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.2.1     ✔ readr     2.2.0
## ✔ forcats   1.0.1     ✔ stringr   1.6.0
## ✔ ggplot2   4.0.3     ✔ tibble    3.3.0
## ✔ lubridate 1.9.5     ✔ tidyr     1.3.2
## ✔ purrr     1.2.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(cricketdata)
library(jsonlite)
## 
## Attaching package: 'jsonlite'
## 
## The following object is masked from 'package:purrr':
## 
##     flatten

Rather than downloading individual match files, fetch_cricsheet() allows us to import cricket data directly into R.

We will import two datasets: ball-by-ball deliveries and match-level information.

# Import men's Test cricket ball-by-ball data
bbb <- fetch_cricsheet(type = "bbb", gender = "male",competition = "tests")
# Download Test match JSON files
download.file("https://cricsheet.org/downloads/tests_male_json.zip",
  destfile = "test_matches.zip", mode = "wb")

# Extract files
unzip("test_matches.zip", exdir = "test_matches")

# Locate JSON files
match_files <- list.files("test_matches", pattern = "\\.json$", full.names = TRUE, recursive = TRUE)

# Extract match results and remove draws
match_results <- purrr::map_dfr(match_files, function(file) {

  match <- jsonlite::fromJSON(file, simplifyVector = FALSE)

  tibble(match_id = as.integer(tools::file_path_sans_ext(basename(file))),
    winner = match$info$outcome$winner %||% NA_character_,
    result = match$info$outcome$result %||% "win")
})

# Check results
glimpse(match_results)
## Rows: 895
## Columns: 3
## $ match_id <int> 1000851, 1000853, 1000855, 1000881, 1000883, 1000885, 1019985…
## $ winner   <chr> "South Africa", "South Africa", "Australia", "Australia", "Au…
## $ result   <chr> "win", "win", "win", "win", "win", "win", "win", "win", "win"…
table(match_results$result)
## 
## draw  win 
##  169  726

2. Preparing the Data

We will focus on the first 10 overs of the first innings of each Test match.

# Filter for the first 10 overs
first_10 <- bbb |>
  filter(innings == 1, ball < 10)

# Calculate wickets and runs
early_performance <- first_10 |>
  group_by(match_id, batting_team) |>
  summarise(wickets_10 = sum(!is.na(player_dismissed) & player_dismissed != "" &
      wicket_type != "retired hurt"),
    runs_10 = sum(runs_off_bat + extras, na.rm = TRUE), .groups = "drop")

head(early_performance)
## # A tibble: 6 × 4
##   match_id batting_team wickets_10 runs_10
##      <int> <chr>             <int>   <int>
## 1    63963 England               1      33
## 2    64012 Australia             0      27
## 3    64013 England               1      21
## 4    64022 South Africa          2      39
## 5    64023 South Africa          0      47
## 6    64027 Australia             1      39

Next, we combine the early performance data with the final match results.

# Join match results
cricket_model <- early_performance |>
  inner_join(match_results, by = "match_id") |>
  filter(!is.na(winner)) |>
  mutate(win = if_else(batting_team == winner, 1, 0))

# Examine the final dataset
glimpse(cricket_model)
## Rows: 726
## Columns: 7
## $ match_id     <int> 64012, 64013, 64022, 64023, 64027, 64036, 64040, 64041, 6…
## $ batting_team <chr> "Australia", "England", "South Africa", "South Africa", "…
## $ wickets_10   <int> 0, 1, 2, 0, 1, 1, 1, 2, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, …
## $ runs_10      <int> 27, 21, 39, 47, 39, 25, 24, 13, 41, 28, 47, 34, 42, 16, 2…
## $ winner       <chr> "Australia", "England", "South Africa", "South Africa", "…
## $ result       <chr> "win", "win", "win", "win", "win", "win", "win", "win", "…
## $ win          <dbl> 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, …
table(cricket_model$win)
## 
##   0   1 
## 325 401

The win variable is binary, where 1 represents a win for the team batting first and 0 represents a loss. Drawn and tied matches are excluded.

3. Exploring Early Wickets

First, we can examine how frequently teams lose wickets in the opening 10 overs.

ggplot(cricket_model, aes(x = wickets_10)) +
  geom_bar(fill = "steelblue") +
  labs(title = "Wickets Lost in the First 10 Overs", 
       x = "Wickets Lost", y = "Number of Matches") +
  theme_minimal()

We can also calculate the percentage of matches won for each number of early wickets.

# Calculate win percentage
win_rates <- cricket_model |>
  group_by(wickets_10) |>
  summarise(matches = n(), win_percentage = mean(win) * 100, .groups = "drop")

win_rates
## # A tibble: 7 × 3
##   wickets_10 matches win_percentage
##        <int>   <int>          <dbl>
## 1          0     271          66.1 
## 2          1     249          53.4 
## 3          2     134          48.5 
## 4          3      51          41.2 
## 5          4      13           7.69
## 6          5       7          28.6 
## 7          7       1           0
ggplot(win_rates, aes(x = wickets_10, y = win_percentage)) +
  geom_col(fill = "steelblue") +
  labs(title = "Test Match Win Percentage by Early Wickets",
    x = "Wickets Lost in First 10 Overs", y = "Win Percentage (%)") +
  theme_minimal()

This allows us to examine whether teams losing more early wickets tend to have lower match-winning percentages. The results suggest that losing early wickets is associated with a lower probability of winning a Test match. Teams that lost no wickets in the first 10 overs won 66.1% of matches, compared with 53.4% after losing one wicket, 48.5% after two wickets and 41.2% after three wickets.

Teams losing four wickets won just 7.7% of matches. However, results for four or more wickets should be interpreted cautiously due to the small number of matches.

4. Logistic Regression

We will use logistic regression to estimate how wickets lost in the first 10 overs relate to the probability of winning a Test match.

model <- glm(win ~ wickets_10, data = cricket_model, family = binomial)

summary(model)
## 
## Call:
## glm(formula = win ~ wickets_10, family = binomial, data = cricket_model)
## 
## Coefficients:
##             Estimate Std. Error z value Pr(>|z|)    
## (Intercept)   0.6299     0.1082   5.820  5.9e-09 ***
## wickets_10   -0.3967     0.0732  -5.419  6.0e-08 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 998.48  on 725  degrees of freedom
## Residual deviance: 966.76  on 724  degrees of freedom
## AIC: 970.76
## 
## Number of Fisher Scoring iterations: 4

The logistic regression identifies a statistically significant negative relationship between early wickets and winning (p < 0.001).

The coefficient of -0.3967 indicates that each additional wicket reduces the log-odds of winning.

4.1 Odds Ratio

We can convert the regression coefficients into odds ratios to make them easier to interpret.

exp(coef(model))
## (Intercept)  wickets_10 
##   1.8774219   0.6725647

The odds ratio for early wickets is approximately 0.673. This means each additional wicket lost in the first 10 overs is associated with a 32.7% reduction in the odds of winning.

4.2 Predicted Win Probabilities

We can use the model to estimate the probability of winning for different numbers of early wickets.

predictions <- data.frame(wickets_10 = 0:7)

predictions$win_probability <- predict(model, newdata = predictions, type = "response")

predictions
##   wickets_10 win_probability
## 1          0       0.6524667
## 2          1       0.5580477
## 3          2       0.4592371
## 4          3       0.3635310
## 5          4       0.2775337
## 6          5       0.2053175
## 7          6       0.1480419
## 8          7       0.1046401
ggplot(predictions,
       aes(x = wickets_10, y = win_probability)) +
  geom_line(colour = "steelblue", linewidth = 1) +
  geom_point(colour = "steelblue", size = 2) +
  scale_y_continuous(labels = scales::percent) +
  labs(title = "Predicted Probability of Winning a Test Match",
    x = "Wickets Lost in First 10 Overs", y = "Probability of Winning") +
  theme_minimal()

The model predicts a 65.2% probability of winning with no early wickets lost, falling to approximately 45.9% after two wickets and 27.8% after four wickets.

These findings suggest that preserving wickets early in a Test match is associated with a greater likelihood of winning, although this relationship does not establish causation.

5. Model Evaluation

To assess the usefulness of our logistic regression model, we will compare its predictions against actual match outcomes.

# Calculate predicted probabilities and outcomes
cricket_model <- cricket_model |>
  mutate(predicted_probability = predict(model, type = "response"),
    predicted_win = if_else(predicted_probability >= 0.5, 1, 0))

# Calculate model accuracy
model_accuracy <- mean(cricket_model$predicted_win == cricket_model$win)

# Calculate baseline accuracy
baseline_accuracy <- max(mean(cricket_model$win == 1), mean(cricket_model$win == 0))

# Compare model performance
tibble(Model = c("Logistic Regression", "Baseline"),
  Accuracy = round(c(model_accuracy, baseline_accuracy) * 100, 1))
## # A tibble: 2 × 2
##   Model               Accuracy
##   <chr>                  <dbl>
## 1 Logistic Regression     59.1
## 2 Baseline                55.2

The baseline represents the accuracy achieved by always predicting the most common match outcome. Comparing the two results helps us determine whether early wickets improve our ability to predict match outcomes.The logistic regression achieved an accuracy of 59.1%, outperforming the baseline accuracy of 55.2% by 3.9 percentage points. This suggests that early wickets provide some predictive value, although the relatively modest improvement indicates that other factors are important in determining Test match outcomes.

We can also examine how accurately the model predicts wins and losses separately.

table(Actual = cricket_model$win, Predicted = cricket_model$predicted_win)
##       Predicted
## Actual   0   1
##      0 117 208
##      1  89 312

The confusion matrix shows that the model correctly predicted 117 losses and 312 wins, while incorrectly predicting 208 losses as wins and 89 wins as losses. Overall, the model correctly classified 429 of 726 matches (59.1%). It was considerably better at identifying wins (77.8%) than losses (36.0%), suggesting that the model tends to predict victories more frequently and has limited ability to distinguish between the two outcomes using early wickets alone.

6. Conclusion

This analysis investigated whether losing wickets in the first 10 overs influences the likelihood of winning a Test cricket match.

The results demonstrated a significant negative relationship between early wickets and match success. Teams losing no wickets won 66.1% of matches, compared with 48.5% after losing two wickets and 41.2% after losing three.

Logistic regression confirmed this relationship was statistically significant (p < 0.001), with each additional early wicket associated with a 32.7% reduction in the odds of winning.

However, the model achieved an accuracy of just 59.1%, only slightly outperforming the baseline of 55.2%. The confusion matrix also demonstrated that the model was considerably more effective at predicting wins than losses.

The model only considered wickets lost in the first 10 overs and excluded drawn and tied matches. Other factors such as runs scored, pitch conditions, opposition strength and home advantage may also influence match outcomes.

Future research could incorporate these variables and include drawn matches to develop a more comprehensive prediction model.

Overall, early wickets are a statistically significant indicator of Test match success, but are insufficient on their own to accurately predict match outcomes.