Overview

ggsoccer provides a handful of functions that make it easy to plot soccer event data in R/ggplot2.

Installation

ggsoccer is available via CRAN:

install.packages("ggsoccer")

Alternatively, you can download the development version from github like so:

# install.packages("remotes")
remotes::install_github("torvaney/ggsoccer")

Usage

The following example uses ggsoccer to solve a realistic problem: plotting a set of passes onto a soccer pitch.

pass_data <- data.frame(x = c(24, 18, 64, 78, 53),
                        y = c(43, 55, 88, 18, 44),
                        x2 = c(34, 44, 81, 85, 64),
                        y2 = c(40, 62, 89, 44, 28))

ggplot(pass_data) +
  annotate_pitch() +
  geom_segment(aes(x = x, y = y, xend = x2, yend = y2),
               arrow = arrow(length = unit(0.25, "cm"),
                             type = "closed")) +
  theme_pitch() +
  direction_label() +
  ggtitle("Simple passmap", 
          "ggsoccer example")

Because ggsoccer is implemented as ggplot layers, it makes customising a plot very easy. Here is a different example, plotting shots on a green pitch.

Note that by default, ggsoccer will display the whole pitch. To display a subsection of the pitch, simply set the plot limits as you would with any other ggplot2 plot. Here, we use the xlim and ylim arguments to coord_flip.

Because of the way coordinates get flipped, we must also reverse the y-axis to ensure that the orientation remains correct.

NOTE: Ordinarily, we would just do this with scale_y_reverse. However, due to a bug in ggplot2, this results in certain elements of the pitch (centre circle and penalty box arcs) failing to render. Instead, we can flip the y coordinates manually (100 - y in this case).


shots <- data.frame(x = c(90, 85, 82, 78, 83, 74, 94, 91),
                    y = c(43, 40, 52, 56, 44, 71, 60, 54))

ggplot(shots) +
  annotate_pitch(colour = "white",
                 fill   = "springgreen4",
                 limits = FALSE) +
  geom_point(aes(x = x, y = 100 - y),
             fill = "yellow", 
             shape = 21,
             size = 4) +
  theme_pitch() +
  theme(panel.background = element_rect(fill = "springgreen4")) +
  coord_flip(xlim = c(49, 101),
             ylim = c(-12, 112)) +
  ggtitle("Simple shotmap",
          "ggsoccer example")

Data providers

ggsoccer defaults to Opta’s 100x100 coordinate system. However, different data providers may use alternative coordinates.

ggsoccer provides support for a few data providers out of the box, as well as an interface for any custom coordinate system:

  • Opta
  • Statsbomb
  • Wyscout

Statsbomb

# ggsoccer enables you to rescale coordinates from one data provider to another, too
to_statsbomb <- rescale_coordinates(from = pitch_opta, to = pitch_statsbomb)

passes_rescaled <- data.frame(x  = to_statsbomb$x(pass_data$x),
                              y  = to_statsbomb$y(pass_data$y),
                              x2 = to_statsbomb$x(pass_data$x2),
                              y2 = to_statsbomb$y(pass_data$y2))

ggplot(passes_rescaled) +
  annotate_pitch(dimensions = pitch_statsbomb) +
  geom_segment(aes(x = x, y = y, xend = x2, yend = y2),
               colour = "coral",
               arrow = arrow(length = unit(0.25, "cm"),
                             type = "closed")) +
  theme_pitch() +
  direction_label(x_label = 60) +
  ggtitle("Simple passmap", 
          "Statsbomb co-ordinates")

Custom data

To plot data for a dataset not provided, ggsoccer just requires a pitch specification. This is a list containing the required pitch dimensions like so:

pitch_custom <- list(
  length = 150,
  width = 100,
  penalty_box_length = 25,
  penalty_box_width = 50,
  six_yard_box_length = 8,
  six_yard_box_width = 26,
  penalty_spot_distance = 16,
  goal_width = 12,
  origin_x = 0,
  origin_y = 0
)

ggplot() +
  annotate_pitch(dimensions = pitch_custom) +
  theme_pitch()

Goals

The standard “box” goals may not be perfectly suited to your use-case. ggsoccer allows you to customise your goals markings by supplying a function to the goals argument of annotate_pitch:

ggplot() +
  annotate_pitch(fill = "steelblue4", colour = "white", goals = goals_line) +
  theme_pitch() +
  theme(panel.background = element_rect(fill = "steelblue4"))

ggplot() +
  annotate_pitch(goals = goals_strip, fill = "lightgray") +
  theme_pitch()

Since this argument just requires a function (or a one-sided formula), you can modify the supplied functions, or create your own goal markings function. Additionally, the goals argument supports using one-sided formulas as lambda functions (see rlang::as_function).

ggplot() +
  annotate_pitch(
    goals = ~ goals_strip(..., lineend = "square", size = 3.5), 
    fill = "lightgray"
  ) +
  theme_pitch()

See help(goals_box) for the full list of available functions.

The idea for having multiple goal markings was taken and adapted from the fc.rstats package.

Other options

There are other packages that offer alternative pitch plotting options. Depending on your use case, you may want to check these out too:

Python

There are a couple of pitch plotting options for matplotlib, too: