Abstract - REVISE

The 2018 FIFA World Cup saw Croatia’s Luka Modric awarded the prestigious Golden Ball, recognizing him as the tournament’s most valuable player. However, traditional statistical metrics such as goals and assists have led some to question the legitimacy of his selection. Soccer analytics has long relied on basic statistics like possession percentages, pass completions, and shots on goal, often overlooking spatial analytics that provide deeper insights into player performance. This paper employs advanced spatial analytics and data visualization techniques using open-source, location-based datasets to assess Modric ’s contributions compared to other key midfielders, including Kevin De Bruyne, Paul Pogba, Antoine Griezmann, and Ivan Rakitic . Through methodologies such as heatmaps, passing networks, spatial scatter plots, and spatial outputs of expected threat (xT) models, this study analyzes Modric ’s positional movements, passing distribution, offensive and defensive contributions, and overall impact. Utilizing R Studio and spatial analytics, this paper uncovers hidden trends in player performance. Anticipated results suggest that spatial statistics will substantiate Modric ’s influence on Croatia’s deep tournament run, reinforcing his case for the Golden Ball. By integrating spatial data analytics into soccer evaluation, this study provides a framework for assessing player contributions beyond conventional metrics through a spatial lens.

Introduction - REVISE

In the 2018 World Cup, the “Golden Ball” MVP award was given to Croatia’s Luka Modric. He was awarded this award due to his exceptional performances that led Croatia to their firstever World Cup final. While his performances were widely known to be crucial in Croatia’s success, several soccer fanatics use generic statistics to argue that Luka Modric was not deserving of the award. For example, France’s Antoine Griezmann had more goals and assists to goals in the tournament. The sport of soccer primarily utilizes basic statistical metrics through charts and numbers when discussing or comparing soccer players. Metrics such as the percentage of possession of time controlling the ball, total number of passes, total number of shots on goal are used widely. However, spatial statistics are hardly used outside of player positional heat maps. The usage of spatial analytics in relation to performances in soccer matches is growing, however, mainly due to the accessibility of open-source, location-based data that can be plotted in statistical computing software programs such as R Studio. By utilizing spatial analytics using this software, hidden trends can be found to validate or disprove several statements. This paper will utilize unconventional spatial analysis and data visualization techniques to compare Luka Modric to other top players of the 2018 World Cup to analyze whether he was deserving of the Golden Ball Award. In this report, Luka Modric’s seven games will be analyzed and compared to other key players, notably Belgium’s Kevin De Bruyne, France’s Paul Pogba and Antoine Griezmann, and his fellow Croatian teammate Ivan Rakitic . Unconventional metrics that utilize location-based data, including the distribution of passes, tackles, position, and more will be analyzed in chart and map-based visualizations, highlighting key discoveries. By consolidating this unique view of soccer data, fans will be able to make a more informed decision on if Luka Modric was deserving of his 2018 Golden Ball award.

Data

Soccer analytics and location-based datasets are gaining interest in both the proprietary and open-source worlds. As mentioned in the paper titled, “A public data set of spatiotemporal match events in soccer competitions”, several large open collections of soccer-logs are available to the public. These datasets contain spatio-temporal events that occurred during each match of prominent soccer competitions, including the world cup (Pappalardo, Cinita, Rossi, et al.). Soccer-logs describe match events, each containing information about its type (pass, shot, foul, tackle, etc.), a time-stamp, the player(s), the position on the field and additional information (e.g., pass accuracy) (Pappalardo, Cinita, Rossi, et al.). This data can be plotted in software such as R Studio for visualization.

Open-source data can be found here: https://figshare.com/collections/Soccer_match_event_dataset/4415000/2

Original data source, including open source and proprietary data, can be found here: https://wyscout.com/

Methodology

Challenges/Limitations

The spatial data accessible is presented in a csv format, with each action in a soccer match noted as a record in the dataset, containing fields such as a time-stamp, start x, start y, end x, end y, and the players involved. This data needed processed in R using tidyverse to filter by player, team, or specific match. Furthermore, the data can be processed to create specific columns as shown here:

Figure 1 Data cleanup format necessary to plot x/y data for analytical purposes in R Studio.
Figure 1 Data cleanup format necessary to plot x/y data for analytical purposes in R Studio.

Analysis & Methods

After processing of the data, the ggplot packages, including ggsoccer and ggforce, can be used to map the actions on a visual soccer pitch, using the following coordinates to ensure the data will be consistent throughout all soccer games.

The spatial distribution of coordinates that makeup the soccer pitch outline that allow the x/y data to be plotted on a soccer pitch. The event’s coordinates depends on the subject. The subject’s goal to be defended is always x=0% and the attack is always x=100%. All values are % expressed as (x,y). https://dataglossary.wyscout.com/pitch_coordinates/
The spatial distribution of coordinates that makeup the soccer pitch outline that allow the x/y data to be plotted on a soccer pitch. The event’s coordinates depends on the subject. The subject’s goal to be defended is always x=0% and the attack is always x=100%. All values are % expressed as (x,y). https://dataglossary.wyscout.com/pitch_coordinates/

METRICS USED - REFINE

Libraries Used

library(BasketballAnalyzeR)
library(dplyr)
library(gridExtra)
library(cowplot)
library(ggplot2)
library(gridExtra)
library(kableExtra)
library(magick)
library(tidyverse)
library(here)
library(janitor)
library(ggplot2)
library(ggforce)
library(ggsoccer)
library(scales)
library(knitr)
library(sf)
library(spdep)

Results

Top Midfielders Player Comparison

Insert Text Here

# Load the data
data <- read.csv("importantplayers_n.csv", fileEncoding = "latin1")

#Select the players you want to see. Choose 6 players for better visual results.
selected_players <- subset(data, 
                            Player=="Luka Modric" |
                            Player=="Antoine Griezmann" |
                            Player=="Kevin De Bruyne" |
                            Player=="Ivan Rakitic" |
                             Player=="N'Golo Kante" |
                             Player=="Axel Witsel" )

# Select relevant columns
Sel <- selected_players %>%
  select(Player, GCA, SCA, Cmp, PrgP, Succ, PrgC, Tkl_Int) %>%
  mutate(across(where(is.numeric), as.numeric))  # Ensure numeric columns remain numeric
# Convert radar chart to a grob
g2_grob <- ggplotGrob(g2)
g_base <- ggplot_build(g2)$plot  # Extract base layers of g2
# Create a blank plot to use as a legend-like panel
#annotation_plot <- ggplot() + 
  #theme_classic() +  # Remove gridlines and axis
  #annotation_custom(descr)  # Add the description
#g_final <- plot_grid(g2, annotation_plot, ncol = 2, rel_widths = c(3, 1))  # Adjust widths
g_base

#g_final

Code Description Table:

#create a table with descriptions for the stats we chose
descriptions <- data.frame(
  "Category"=colnames(Sel),
  "Description"=c("Player",
                  "Goal Creating Actions",
                  "Shot Creating Actions",
                  "Passes Completed",
                  "Progressive Passes",
                  "Successful Take-On Dribbles",
                  "Progressive Carries",
                  "Tackles & Interceptions"))
# Remove "Player" row
descriptions <- descriptions[-1, ]  # Removes the first row
descr <- tableGrob(print(descriptions))
##   Category                 Description
## 2      GCA       Goal Creating Actions
## 3      SCA       Shot Creating Actions
## 4      Cmp            Passes Completed
## 5     PrgP          Progressive Passes
## 6     Succ Successful Take-On Dribbles
## 7     PrgC         Progressive Carries
## 8  Tkl_Int     Tackles & Interceptions

Part 1: Passing

Insert Text Here

Progressive Pass (Explained)

A progressive pass is a pass that moves the ball forward towards the oppositions goal. In the example below, Luka Modric passes the ball to his teammate in the opponent’s 18 yard box.

Luka Modric Progressive Pass.
Luka Modric Progressive Pass.

Spatial Autocorrelation: Performing Moran’s I test on Modric Passing Locations

Explain what Moran I test is.

I tested whether the number of starting passes per location by Luka Modric is spatially clustered.

# Read the dataset
croatia_data <- read.csv("CroatiaTeamData.csv")

# Filter data to only player_id 8287 (Luka Modric)
croatia_data_filter <- croatia_data %>%
  filter(player_id == 8287 & event_name == "Pass")

# Count number of passes at each location
croatia_data_aggregated <- croatia_data_filter %>%
  group_by(start_x, start_y) %>%
  summarise(pass_count = n())

# Convert to sf object
croatia_sf_pass <- st_as_sf(croatia_data_aggregated, coords = c("start_x", "start_y"), crs = 4326)

# Create neighbors
nb <- dnearneigh(st_coordinates(croatia_sf_pass), 0, 30)  
lw <- nb2listw(nb, style = "W", zero.policy = TRUE)

# Perform Moran's I test on pass count
moran_test_count <- moran.test(croatia_data_aggregated$pass_count, lw, zero.policy = TRUE)

The key results show that:

## 
##  Moran I test under randomisation
## 
## data:  croatia_data_aggregated$pass_count  
## weights: lw    
## 
## Moran I statistic standard deviate = 2.5981, p-value = 0.004687
## alternative hypothesis: greater
## sample estimates:
## Moran I statistic       Expectation          Variance 
##      9.465372e-03     -2.309469e-03      2.053928e-05

Interpretation of the data

Since p < 0.05, we reject the null hypothesis (which assumes passes are randomly distributed). This means that certain areas of the field see more passing activity.

This result makes sense when we look at the position that Luka Modric typically plays. The Moran I test shows us that Modric tens to make plasses from certain zones. When we visualize this data on a soccer pitch, it becomes clear that Modric does pass more in certain locations.

# Define pitch dimensions
pitch_length <- 100
pitch_width <- 100
grid_size <- 10
penalty_box_length <- 16
penalty_box_width <- 62
six_yard_box_length <- 6
six_yard_box_width <- 26
goal_width <- 12

# Create grid bins
df <- croatia_data_filter %>%
  mutate(grid_x = floor(start_x / grid_size),
         grid_y = floor(start_y / grid_size))

# Aggregate counts per grid cell
heatmap_data <- df %>%
  group_by(grid_x, grid_y) %>%
  summarise(event_count = n())

# Calculate the average start_x and start_y
average_start_x <- mean(croatia_data_filter$start_x, na.rm = TRUE)
average_start_y <- mean(croatia_data_filter$start_y, na.rm = TRUE)

# Create a dataframe for the average start position point
average_point <- data.frame(
  x = average_start_x,
  y = average_start_y,
  label = "Avg Start Position"
)

# Start with the heatmap in the background
heatmap_plot <- ggplot() +
  geom_tile(data = heatmap_data, aes(x = grid_x * grid_size + grid_size / 2, 
                                     y = grid_y * grid_size + grid_size / 2, 
                                     fill = event_count), color = "white") +
  scale_fill_gradient(low = "yellow", high = "red", labels = scales::label_number(accuracy = 1)) +
  labs(title = "Heatmap Luka Modric Starting Passes",
       subtitle = "Darker regions represent more passes performed per grid",
       fill = "Pass Count") +
  theme_classic() +
  theme(axis.text = element_blank(), 
        axis.ticks = element_blank(), 
        axis.title = element_blank(),
        panel.grid = element_blank()) +
  
  # Full pitch outline
  geom_rect(aes(xmin = 0, xmax = pitch_length, ymin = 0, ymax = pitch_width), 
            color = "grey", fill = NA) +
  
  # Grid lines (10x10)
  geom_segment(data = data.frame(x = seq(0, pitch_length, by = grid_size)),
               aes(x = x, xend = x, y = 0, yend = pitch_width),
               color = "lightgrey", linetype = "dotted") +
  geom_segment(data = data.frame(y = seq(0, pitch_width, by = grid_size)),
               aes(x = 0, xend = pitch_length, y = y, yend = y),
               color = "lightgrey", linetype = "dotted") +
  
  # Penalty box (left side)
  geom_rect(aes(xmin = 0, xmax = penalty_box_length,
                ymin = (pitch_width - penalty_box_width) / 2,
                ymax = (pitch_width + penalty_box_width) / 2),
            color = "grey35", fill = NA) +
  
  # Penalty box (right side)
  geom_rect(aes(xmin = pitch_length - penalty_box_length, xmax = pitch_length,
                ymin = (pitch_width - penalty_box_width) / 2,
                ymax = (pitch_width + penalty_box_width) / 2),
            color = "grey35", fill = NA) +
  
  # Six-yard box (left side)
  geom_rect(aes(xmin = 0, xmax = six_yard_box_length,
                ymin = (pitch_width - six_yard_box_width) / 2,
                ymax = (pitch_width + six_yard_box_width) / 2),
            color = "grey35", fill = NA) +
  
  # Six-yard box (right side)
  geom_rect(aes(xmin = pitch_length - six_yard_box_length, xmax = pitch_length,
                ymin = (pitch_width - six_yard_box_width) / 2,
                ymax = (pitch_width + six_yard_box_width) / 2),
            color = "grey35", fill = NA) +
  
  # Goals (left side)
  geom_segment(aes(x = 0, xend = 0, 
                   y = (pitch_width - goal_width) / 2, 
                   yend = (pitch_width + goal_width) / 2),
               color = "black", size = 3) +
  
  # Goals (right side)
  geom_segment(aes(x = pitch_length, xend = pitch_length, 
                   y = (pitch_width - goal_width) / 2, 
                   yend = (pitch_width + goal_width) / 2),
               color = "black", size = 3) +
  
  # Center circle
  geom_circle(aes(x0 = pitch_length / 2, y0 = pitch_width / 2, r = 9.15),
              color = "grey35", inherit.aes = FALSE) +
  
  # Center line
  geom_segment(aes(x = pitch_length / 2, xend = pitch_length / 2,
                   y = 0, yend = pitch_width),
               color = "grey35") +
  
  # Attack direction annotation
  annotate("segment", x = 45, xend = 80, y = 110, yend = 110, 
           arrow = arrow(length = unit(0.3, "inches")), color = "black", size = 1.2) +
  annotate("text", x = 60, y = 120, label = "Attacking Direction", size = 5, fontface = "bold") +
  
  # Add the average start_x and start_y as a black point
  geom_point(aes(x = average_start_x, y = average_start_y), color = "white",, fill = "black", size = 7, shape = 21, stroke =1.5) +
  
  # Add text annotation above the average point
  geom_text(data = average_point, aes(x = x, y = y + 6, label = "Average Pass Start Location"), 
            color = "black", size = 3, hjust = 0.5) +
  
  coord_fixed()

# Show the final plot
print(heatmap_plot)

When comparing the heatmap and average starting pass location to the starting positional lineup, we can see that Modric tends to pass around the location he is assigned. This highlights his exceptional positional awareness and ability to follow tactical plans throughout a soccer match.

Luka Modric Starting Position in Lineup.
Luka Modric Starting Position in Lineup.

Part 2: Dribbling

Insert Text Here

Progressive Carry (Explained)

A progressive carry is a action where a player dribbles (retains possession while moving the ball) forward towards the oppositions goal. In the example below, Luka Modric dribbles past an opponent towards the opposition goal.

LukaModric Progressive Pass.
LukaModric Progressive Pass.

Part 3: Defending

Insert Text Here

Tackle/Interception (Explained)

A tackle/interception are defensive actions in soccer where the player stops the opposition from dribbling, passing, or shooting the ball. In the example below, Luka Modric intercepts the ball as he stopped the pass of his opponent.

Tackle_Interception Explination.
Tackle_Interception Explination.

Luka Modric vs N’Golo Kanté Defensive Contributions

# Load the dataset
croatiadata <- read_csv(here("CroatiaTeamData.csv"))

# Filter Luka Modric's events (Duel + Others on the ball)
modric <- croatiadata %>%
  filter(player_id == 8287 & (event_name == "Duel" | event_name == "Others on the ball"))
# Define pitch dimensions
pitch_length <- 100
pitch_width <- 100
penalty_box_length <- 16
penalty_box_width <- 62
six_yard_box_length <- 6
six_yard_box_width <- 26
goal_width <- 12

# Define 18-zone grid (3 vertical x 6 horizontal)
num_vertical <- 3
num_horizontal <- 6
grid_x_size <- pitch_length / num_horizontal
grid_y_size <- pitch_width / num_vertical

# Assign events to the 18-zone grid
df <- modric %>%
  mutate(grid_x = floor(start_x / grid_x_size),
         grid_y = floor(start_y / grid_y_size))

# Aggregate total contributions per grid cell
heatmap_data <- df %>%
  group_by(grid_x, grid_y) %>%
  summarise(event_count = n(), .groups = "drop")

# Generate the heatmap with contribution counts in each zone
modric_heatmap_plot <- ggplot() +
  geom_tile(data = heatmap_data, aes(x = grid_x * grid_x_size + grid_x_size / 2, 
                                     y = grid_y * grid_y_size + grid_y_size / 2, 
                                     fill = event_count), color = "white") +
  scale_fill_gradient(low = "yellow", high = "red", labels = scales::label_number(accuracy = 1)) +
  labs(title = "Luka Modric Defensive Contributions",
       subtitle = "Darker regions indicate more contributions",
       fill = "Contributions") +
  theme_classic() +
  theme(axis.text = element_blank(), 
        axis.ticks = element_blank(), 
        axis.title = element_blank(),
        panel.grid = element_blank()) +
  
  # Full pitch outline
  geom_rect(aes(xmin = 0, xmax = pitch_length, ymin = 0, ymax = pitch_width), 
            color = "grey", fill = NA) +
  
  # Add 18 equal-area grid lines (3x6)
  geom_segment(data = data.frame(x = seq(0, pitch_length, by = grid_x_size)),
               aes(x = x, xend = x, y = 0, yend = pitch_width),
               color = "grey", linetype = "dashed") +
  geom_segment(data = data.frame(y = seq(0, pitch_width, by = grid_y_size)),
               aes(x = 0, xend = pitch_length, y = y, yend = y),
               color = "grey", linetype = "dashed") +
  
  # Add event count labels in each zone
  geom_text(data = heatmap_data, aes(x = grid_x * grid_x_size + grid_x_size / 2, 
                                     y = grid_y * grid_y_size + grid_y_size / 2, 
                                     label = event_count), size = 5, fontface = "bold") +
  
  # Add penalty boxes
  geom_rect(aes(xmin = 0, xmax = penalty_box_length,
                ymin = (pitch_width - penalty_box_width) / 2,
                ymax = (pitch_width + penalty_box_width) / 2),
            color = "grey35", fill = NA) +
  geom_rect(aes(xmin = pitch_length - penalty_box_length, xmax = pitch_length,
                ymin = (pitch_width - penalty_box_width) / 2,
                ymax = (pitch_width + penalty_box_width) / 2),
            color = "grey35", fill = NA) +
  
  # Add six-yard boxes
  geom_rect(aes(xmin = 0, xmax = six_yard_box_length,
                ymin = (pitch_width - six_yard_box_width) / 2,
                ymax = (pitch_width + six_yard_box_width) / 2),
            color = "grey35", fill = NA) +
  geom_rect(aes(xmin = pitch_length - six_yard_box_length, xmax = pitch_length,
                ymin = (pitch_width - six_yard_box_width) / 2,
                ymax = (pitch_width + six_yard_box_width) / 2),
            color = "grey35", fill = NA) +
  
  # Add goals
  geom_segment(aes(x = 0, xend = 0, 
                   y = (pitch_width - goal_width) / 2, 
                   yend = (pitch_width + goal_width) / 2),
               color = "black", size = 3) +
  geom_segment(aes(x = pitch_length, xend = pitch_length, 
                   y = (pitch_width - goal_width) / 2, 
                   yend = (pitch_width + goal_width) / 2),
               color = "black", size = 3) +
  
  # Add center circle and line
  geom_circle(aes(x0 = pitch_length / 2, y0 = pitch_width / 2, r = 9.15),
              color = "grey35", inherit.aes = FALSE) +
  geom_segment(aes(x = pitch_length / 2, xend = pitch_length / 2,
                   y = 0, yend = pitch_width),
               color = "grey35") +
  
  # Attack direction annotation
  annotate("segment", x = 45, xend = 80, y = 110, yend = 110, 
           arrow = arrow(length = unit(0.3, "inches")), color = "black", size = 1.2) +
  annotate("text", x = 60, y = 120, label = "Attacking Direction", size = 5, fontface = "bold") +
  
  coord_fixed()
# Load the dataset
francedata <- read_csv(here("francedata.csv"))

kante <- read_csv(here("kante.csv"))
# Filter N'Golo Kante's events (Duel + Others on the ball)
#kante <- francedata %>%
  #filter(player_id == 31528 & (event_name == "Duel" | event_name == "Others on the ball"))

#write.csv(kante, "kante.csv")

# Define pitch dimensions
pitch_length <- 100
pitch_width <- 100
penalty_box_length <- 16
penalty_box_width <- 62
six_yard_box_length <- 6
six_yard_box_width <- 26
goal_width <- 12

# Define 18-zone grid (3 vertical x 6 horizontal)
num_vertical <- 3
num_horizontal <- 6
grid_x_size <- pitch_length / num_horizontal
grid_y_size <- pitch_width / num_vertical

# Assign events to the 18-zone grid
df <- kante %>%
  mutate(grid_x = floor(start_x / grid_x_size),
         grid_y = floor(start_y / grid_y_size))

# Aggregate total contributions per grid cell
heatmap_data <- df %>%
  group_by(grid_x, grid_y) %>%
  summarise(event_count = n(), .groups = "drop")

# Generate the heatmap with contribution counts in each zone
kante_heatmap_plot <- ggplot() +
  geom_tile(data = heatmap_data, aes(x = grid_x * grid_x_size + grid_x_size / 2, 
                                     y = grid_y * grid_y_size + grid_y_size / 2, 
                                     fill = event_count), color = "white") +
  scale_fill_gradient(low = "yellow", high = "red", labels = scales::label_number(accuracy = 1)) +
  labs(title = "N'Golo Kante Defensive Contributions",
       subtitle = "Darker regions indicate more contributions",
       fill = "Contributions") +
  theme_classic() +
  theme(axis.text = element_blank(), 
        axis.ticks = element_blank(), 
        axis.title = element_blank(),
        panel.grid = element_blank()) +
  
  # Full pitch outline
  geom_rect(aes(xmin = 0, xmax = pitch_length, ymin = 0, ymax = pitch_width), 
            color = "grey", fill = NA) +
  
  # Add 18 equal-area grid lines (3x6)
  geom_segment(data = data.frame(x = seq(0, pitch_length, by = grid_x_size)),
               aes(x = x, xend = x, y = 0, yend = pitch_width),
               color = "grey", linetype = "dashed") +
  geom_segment(data = data.frame(y = seq(0, pitch_width, by = grid_y_size)),
               aes(x = 0, xend = pitch_length, y = y, yend = y),
               color = "grey", linetype = "dashed") +
  
  # Add event count labels in each zone
  geom_text(data = heatmap_data, aes(x = grid_x * grid_x_size + grid_x_size / 2, 
                                     y = grid_y * grid_y_size + grid_y_size / 2, 
                                     label = event_count), size = 5, fontface = "bold") +
  
  # Add penalty boxes
  geom_rect(aes(xmin = 0, xmax = penalty_box_length,
                ymin = (pitch_width - penalty_box_width) / 2,
                ymax = (pitch_width + penalty_box_width) / 2),
            color = "grey35", fill = NA) +
  geom_rect(aes(xmin = pitch_length - penalty_box_length, xmax = pitch_length,
                ymin = (pitch_width - penalty_box_width) / 2,
                ymax = (pitch_width + penalty_box_width) / 2),
            color = "grey35", fill = NA) +
  
  # Add six-yard boxes
  geom_rect(aes(xmin = 0, xmax = six_yard_box_length,
                ymin = (pitch_width - six_yard_box_width) / 2,
                ymax = (pitch_width + six_yard_box_width) / 2),
            color = "grey35", fill = NA) +
  geom_rect(aes(xmin = pitch_length - six_yard_box_length, xmax = pitch_length,
                ymin = (pitch_width - six_yard_box_width) / 2,
                ymax = (pitch_width + six_yard_box_width) / 2),
            color = "grey35", fill = NA) +
  
  # Add goals
  geom_segment(aes(x = 0, xend = 0, 
                   y = (pitch_width - goal_width) / 2, 
                   yend = (pitch_width + goal_width) / 2),
               color = "black", size = 3) +
  geom_segment(aes(x = pitch_length, xend = pitch_length, 
                   y = (pitch_width - goal_width) / 2, 
                   yend = (pitch_width + goal_width) / 2),
               color = "black", size = 3) +
  
  # Add center circle and line
  geom_circle(aes(x0 = pitch_length / 2, y0 = pitch_width / 2, r = 9.15),
              color = "grey35", inherit.aes = FALSE) +
  geom_segment(aes(x = pitch_length / 2, xend = pitch_length / 2,
                   y = 0, yend = pitch_width),
               color = "grey35") +
  
  # Attack direction annotation
  annotate("segment", x = 45, xend = 80, y = 110, yend = 110, 
           arrow = arrow(length = unit(0.3, "inches")), color = "black", size = 1.2) +
  annotate("text", x = 60, y = 120, label = "Attacking Direction", size = 5, fontface = "bold") +
  
  coord_fixed()
pl_list <- list()

pl1 <- modric_heatmap_plot
pl2 <- kante_heatmap_plot

pl_list[[1]] <- list(pl1,  "Modric Defensive Actions")
pl_list[[2]] <- list(pl2,  "Kante Defensive Actions")

Modric vs Kante Defensive Actions Plotted

Modric Defensive Actions

Kante Defensive Actions

Part 4: Goal Creating Actions

Insert Text Here

Type 1: Goal (Explained)

A goal is scored when a player kicks the ball into the opponent’s goal. In the example below, Luka Modric shoots and scores against Argentina in the World Cup.

LukaModric Goal.
LukaModric Goal.

Type 2: Assist (Explained)

An assist is when a player passes the ball that leads directly to the goal. In the example below, Kevin De Bruyne passes the ball to Romelu Lukaku who scored, leading to an assist.

Kevin De Bruyne Assist.
Kevin De Bruyne Assist.

Type 3: Secondary Assist (Explained)

A secondary assist is the pass that directly leads to the assist, which then sets up the goal.

Kevin De Bruyne Assist.
Kevin De Bruyne Assist.