ETC5523: Communicating with Data

Workshop 6: Rebuilding cricket broadcast graphics

Author

Michael Lydeamore

Published

1 June 2001

library(tidyverse)
library(cricketdata)

options(timeout = 300)

# Download the men's ODI ball-by-ball archive, then retain one match.
balls <- cricketdata::fetch_cricsheet(
  type = "bbb",
  gender = "male",
  competition = "odis"
) |>
  filter(match_id == 1384439) |>
  arrange(innings, actual_delivery) |>
  mutate(
    total_runs = runs_off_bat + extras,
    over = floor(ball) + 1L,
    # cricketdata imports unused character fields as "", not NA.
    is_wicket = !is.na(player_dismissed) &
      str_trim(player_dismissed) != ""
  ) |>
  group_by(innings) |>
  mutate(
    score_after_ball = cumsum(total_runs),
    over_position = floor(ball) +
      round((ball - floor(ball)) * 10) / 6
  ) |>
  ungroup()

overs <- balls |>
  group_by(innings, batting_team, over) |>
  summarise(
    over_runs = sum(total_runs),
    over_wickets = sum(is_wicket),
    .groups = "drop"
  ) |>
  group_by(innings, batting_team) |>
  mutate(
    score = cumsum(over_runs),
    wickets = cumsum(over_wickets)
  ) |>
  ungroup()

dismissals <- balls |>
  filter(is_wicket) |>
  select(innings, batting_team, over_position,
         score_after_ball, player_dismissed)

worm <- bind_rows(
  overs |>
    select(innings, batting_team, over, score, wickets),
  tibble(
    innings = c(1, 2),
    batting_team = c("India", "Australia"),
    over = 0L,
    score = 0,
    wickets = 0L
  )
) |>
  arrange(innings, over)

team_colours <- c(
  "Australia" = "#C58A00",
  "India" = "#0072B2"
)

theme_broadcast <- function(base_size = 13) {
  theme_minimal(base_size = base_size) +
    theme(
      plot.background = element_rect(fill = "white", colour = NA),
      panel.background = element_rect(fill = "white", colour = NA),
      panel.grid.major = element_line(colour = "#D9E2E8", linewidth = 0.3),
      panel.grid.minor = element_blank(),
      text = element_text(colour = "#142B3A"),
      axis.text = element_text(colour = "#344955"),
      axis.title = element_text(colour = "#344955"),
      strip.text = element_text(colour = "#142B3A", face = "bold"),
      legend.position = "none",
      plot.title.position = "plot",
      plot.caption = element_text(colour = "#526773", hjust = 0)
    )
}

🎯 Objectives

By the end of this workshop, you will be able to:

  • choose comparisons that answer a viewer’s question during a match;
  • reconstruct Manhattan and worm charts from ball-by-ball data; and
  • show wickets without obscuring the pattern of runs.

Install the packages before class if needed:

install.packages(c("tidyverse", "cricketdata"))

The broadcast brief

It is the 2023 Men’s Cricket World Cup final in Ahmedabad. India made 240 all out. Australia is chasing 241.

You are on the broadcast graphics team. Your audience wants to know:

How does Australia’s chase compare with India’s innings at the same stage?

We will work from Cricsheet ball-by-ball data, accessed through the cricketdata package. The match ID is 1384439.

What are we recreating?

These graphics have long appeared during cricket broadcasts. Look at how they encode the match, rather than at the particular colours, logos or decoration.

Manhattan chart

A cricket broadcast Manhattan chart with a pair of narrow bars for each over and small circles marking wickets.

Each bar shows the runs scored in one over, producing a skyline-like view of changes in scoring intensity. The two teams can be shown in separate panels or as adjacent bars. Circles mark overs in which wickets fell.

Reference image: NV Play Streaming Overlay Control Panel.

Worm chart

A cricket television worm chart comparing two teams with cumulative-run lines and circular wicket markers.

Each line shows cumulative runs as the innings progresses. A steeper section means faster scoring; the relative height of the lines compares the teams at the same stage. Circles show where wickets interrupted each innings.

Reference image: 2018 Caribbean Premier League broadcast, via CricAmerica.

1. Read the moment before drawing (5 minutes)

After ten overs:

Team Score Run rate Match situation
India 80/2 8.00 setting the target
Australia 60/3 6.00 needs 181 from 240 balls (4.53 per over)

Discuss with a partner:

  1. Which comparison would a viewer care about: score, wickets, run rate, or required run rate?
  2. Is Australia “behind” after ten overs? Write a one-sentence answer.
  3. Which quantities should be encoded visually, and which belong in text?

Discussion notes

Australia is 20 runs behind India’s score at the same stage and has lost one more wicket. However, it is scoring faster than the required rate. “Behind” is therefore ambiguous: the answer depends on whether we mean comparative score, resources remaining, or progress towards the target.

This is a useful reason not to force score, wickets and rates onto one set of axes. Show the dominant comparison visually and use a direct label or short annotation for the other context.

2. Design a live checkpoint graphic (8 minutes)

Choose checkpoint <- 10, 20 or 30. The code below creates the two rows available to the graphics producer at that moment.

checkpoint <- 10

snapshot <- overs |>
  filter(over == checkpoint) |>
  mutate(label = paste(score, "/", wickets))

snapshot
# A tibble: 2 × 8
  innings batting_team  over over_runs over_wickets score wickets label 
    <int> <chr>        <dbl>     <int>        <int> <int>   <int> <chr> 
1       1 India           10        14            1    80       2 80 / 2
2       2 Australia       10         9            0    60       3 60 / 3

On paper, or in ggplot2, design a compact comparison that could appear between overs. It must:

  • make the runs easy to compare;
  • retain the wicket context;
  • say that both teams are being compared after the same number of overs; and
  • use direct labels rather than making the viewer search a legend.

Swap checkpoint values. Does the headline remain true?

One possible checkpoint graphic

At 20 overs India still leads, 115/3 to 104/3. At 30 overs the story changes: Australia leads 167/3 to 152/4. A graphic designed for a live broadcast needs to remain truthful as the match state changes; a fixed takeaway title may not.

3. Rebuild the Manhattan chart (12 minutes)

A Manhattan chart uses bar height to show runs scored in each over. Start with the skeleton below.

Before coding, decide:

  1. Should the two innings be overlaid, dodged or placed in separate panels?
  2. Where can a wicket marker go without being mistaken for more runs?
  3. What does this chart reveal that the ten-over checkpoint cannot?

Manhattan solution

Faceted version

Faceting preserves a common horizontal and vertical scale while avoiding occlusion. Each wicket dot sits just above its bar, so it is associated with an over but cannot be read as part of the bar’s magnitude.

Dodged version

The same position_dodge2() specification is applied to both geoms. The point layer retains every team–over row while assigning NA to non-wicket heights; this lets ggplot2 calculate the same team offset before it removes the unneeded points. preserve = "single" prevents India’s bars from becoming wider after Australia’s innings ends. Splitting the match into two 25-over panels makes the paired bars easier to distinguish, while the innings-faceted version gives each team more room.

4. Rebuild the worm chart (12 minutes)

A worm chart shows cumulative runs against overs. Use worm for the lines and dismissals for the wicket events.

Your wicket layer will need its own mappings:

aes(
  x = over,
  y = score,
  fill = batting_team,
  colour = batting_team
)

Ask another pair to read your chart. Can they identify the early danger, the crossover and the winning moment without you explaining them?

Worm solution

The line makes the changing run-rate visible through its slope. Wickets are events rather than magnitudes, so points are a better encoding than a second axis. The worm reveals the early collapse and later crossover more clearly than the Manhattan chart; the Manhattan chart better reveals volatile individual overs.

5. Broadcast review (8 minutes)

Choose either your Manhattan or worm chart. Give it a final editorial pass:

  1. Write a takeaway title of no more than 14 words.
  2. Add a subtitle that supplies essential match context rather than repeating the title.
  3. Check that colour is not the only way to identify wickets or teams.
  4. Remove one element that does not help a viewer answer the broadcast question.
  5. Write one sentence describing what your graphic still cannot tell us.
Fast finisher: freeze the broadcast

Create the worm as it would have appeared after 10, 20 or 30 overs of the chase. Filter both innings to that checkpoint and rewrite the title for what was known at that moment. Do not use knowledge of the final result.

Instructor notes

Suggested running time is 5 + 8 + 12 + 12 + 8 minutes, with five minutes for transitions or a whole-class comparison of designs.

The 2023 World Cup final is useful because the encodings tell complementary stories. India led by 20 runs after ten overs, but Australia was already above the required rate. Australia moved ahead between overs 20 and 30 and won at 241/4 after 43 overs. Their first three wickets make the chase look precarious; the long wicket-free section then becomes the dominant feature of the worm.

A defensible limitation is that neither chart attributes the change to batting, bowling, pitch conditions or tactics. Both show what happened, not why it happened.

Supplied data-preparation code

Keep this code for reference. You do not need to recreate it during the workshop.

Data: Cricsheet via the cricketdata R package. Match: India v Australia, ICC Men’s Cricket World Cup final, 19 November 2023.