Laliga Soccer Dataset from Season 2018/2019

Author

Asim Maharjan

Image Source

Introduction

This is the dataset of soccer’s Laliga season 2018/2019 where the teams compete for a title. I got this dataset from “kaggle” where Prabhat had created it whose official source is “fbref” website. This dataset has 556 observations and 11 variables. This dataset consists of goals, assists, name, shots, cards, penalties and more which was interesting to work. It contains a lot of variables where I choosed the following:

For Visualization and exploratory plots

  1. goals_scored…17: quantitative
  2. team: categorical
  3. name: categorical
  4. position: categorical

For Correlation and Regression

  1. shots_on_target: quantitative
  2. assists: quantitative
  3. shots: quantitative
  4. successful_dribbles: quantitative
  5. passes: quantitative
  6. minutes_played: quantitative
  7. games_played: quantitative

Cleaning

First, I started with loading the libraries and data, finding out the data was not properly managed. I used “tolower” and “gsub” functions to lowercase and remove space from the variables. Then, I used “select” function to only include those variable I need for the project and stored in “soccer1” variable. There were multiple variables of goal scored, where the true values was stored in “goals_scored…17”, which I renamed to goals.

Why this dataset?

I chose this dataset because I was interested in finding patterns in something different like sports and what I can find new compared to other data. Also, I understand soccer and the stats, so it would not be difficult for me to understand the data.

Loading Libraries and Dataset

library(tidyverse)
Warning: package 'tidyr' was built under R version 4.6.1
Warning: package 'readr' was built under R version 4.6.1
Warning: package 'dplyr' was built under R version 4.6.1
Warning: package 'forcats' was built under R version 4.6.1
Warning: package 'lubridate' was built under R version 4.6.1
── 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.1
✔ 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(dplyr)
library(readr)
library(ggplot2)
soccer <- read_csv("laliga_player_stats_english.csv")
New names:
Rows: 556 Columns: 62
── Column specification
──────────────────────────────────────────────────────── Delimiter: "," chr
(7): Team, Position, Name, Percentage of games played, Percentage of fu... dbl
(55): Shirt number, Minutes played, Games played, Full games played, Gam...
ℹ Use `spec()` to retrieve the full column specification for this data. ℹ
Specify the column types or set `show_col_types = FALSE` to quiet this message.
• `Goals scored` -> `Goals scored...17`
• `Penalties scored` -> `Penalties scored...18`
• `Tackles` -> `Tackles...21`
• `Goals scored` -> `Goals scored...44`
• `Penalties scored` -> `Penalties scored...49`
• `Tackles` -> `Tackles...54`

Cleaning of the Data-set

# Variables lower-cased and removed space, included "_"
names(soccer) <- tolower(names(soccer))
names(soccer) <- gsub(" ","_", names(soccer))

# selecting the variables required for the project and storing in new data-set
soccer1 <- soccer |>
  rename(goals = goals_scored...17) |>
  select(team, goals, name, position, shots_on_target, assists, shots, successful_dribbles, passes, minutes_played, games_played)

Exploratory Plots

# Bar chart of players by their position
ggplot(soccer1, aes(x = position, fill = position)) +
  geom_bar() +
  scale_fill_brewer(palette = "Set2") +
  theme_minimal() +
  labs(
    title = "Players by Position",
    x = "Position",
    y = "Number of players", 
    fill = "Position",
    caption = "Source: FBref, 2018-2019 La Liga Stats")

# Box-plot of goals by the position
ggplot(soccer1, aes(x = position, y = goals, fill = position)) +
  geom_boxplot() +
  theme_minimal() +
  labs(
    title = "Goals Scored by Position",
    x = "Position",
    y = "Goals", 
    fill = "Position",
    caption = "Source: FBref, 2018-2019 La Liga Stats")

Visualizations

In the first visualization, I tried to find which club in La Liga had scored the most goals which resulted in FC Barcelona having 87 goals, followed by Real Madrid and Seville FC. I grouped it by team and used summarize to sum the goals. Then, I used ggplot to make the scatter-plot where I included my understandings and changed the color, I also flipped the plot because the club would be mixed together which results in bad visualization.

1

g1 <- soccer1 |>
  # grouping by team 
  group_by(team) |>
  # summing clubs with most goals 
  summarize(top_goals = sum(goals)) |>
  # arranging top to low
  arrange(desc(top_goals))

  # creating scatter plot and flipping the variables
library(RColorBrewer)
Warning: package 'RColorBrewer' was built under R version 4.6.1
ggplot(g1, aes(x = reorder(team, top_goals), y = top_goals, color = top_goals)) +
  geom_point(size = 2) +
  # using different color
  scale_color_distiller(palette = "Set1") +
  coord_flip() +
  theme_minimal() +
  annotate("text", x = 4, y = 70, label = "FC Barcelona scored\nthe most: 87 goals") +
  # adding labels
  labs(
    title = "Total Goals Scored by Each La Liga Team",
    x = "Team",
    y = "Total Goals",
    color = "Total goals",
    caption = "Source: FBref, 2018-2019 La Liga Stats")

In the second visualization, I tried to find the players with most goals which also mentions clubs they played for. The result shows that Lionel Messi is the top goal scorer of the season with 36 goals. Also, I only selected Midfielder and Forward as only those two position could have more goals rather than Defender and Goalkeeper. I again played with the code so I can present in a proper way. In this visualization, I used “plotly” to make the plot interactive.

2

top_scorers <- soccer1 |>
  # Filtering only Midfielder and Forward players
  filter(position %in% c("Midfielder", "Forward")) |>
  group_by(team) |>
  # keeping one player from one club who has highest goal
  slice_max(goals, n = 1, with_ties = FALSE) |>
  ungroup()

top <- ggplot(top_scorers, aes(x = reorder(name, goals), y = goals, fill = goals)) +
  geom_col(size = 3) +
  # arranging the labels
  geom_text(aes(label = team), hjust = -0.2, size = 2, color = "black") +
  # keeping the y-axis limit to 40
  expand_limits(y = 40) +
  # flipping the graph as it is mixed in normal 
  coord_flip() +
  annotate("text", x = 4, y = 30, label = "Messi is the top goal scorer\nwith 36 goals") +
  # using different color
  scale_fill_distiller(palette = "Set2") +
  theme_minimal() +
  # labels
  labs(
    title = "Top Goal Scorer in La Liga Club, 2018-2019 Season",
    x = "Player",
    y = "Goals scored",
    fill = "Goals",
    caption = "Source: FBref, 2018-2019 La Liga Stats")
Warning in geom_col(size = 3): Ignoring unknown parameters: `size`
# Using interactive charts
library(plotly)
Warning: package 'plotly' was built under R version 4.6.1

Attaching package: 'plotly'
The following object is masked from 'package:ggplot2':

    last_plot
The following object is masked from 'package:stats':

    filter
The following object is masked from 'package:graphics':

    layout
ggplotly(top, tooltip = "y")

Correlation Plot

I tried to find the relation between my selected variables, which is connected with each other.

library(DataExplorer)
Warning: package 'DataExplorer' was built under R version 4.6.1
cors <- soccer1 |>
  # selecting only required data for correlation
  select(goals, shots_on_target, assists, successful_dribbles, games_played, passes)
plot_correlation(cors)

Multiple Regression

Lastly, I did multiple regression to end my project where I tried wanted to find what influences goals, is it passes, shots, duration or something else also has some contributions.

I started with a full model containing shots on target, assists, successful dribbles, games played, passes, and minutes played. Using backward elimination, I removed the variables with the highest p-values (games played, passes, and minutes played) and re-run the model. Minutes played also had problems in the original file.

All three predictors are significant at the 0.05 level. Shots on target and successful dribbles have p-values below 0.001, and assists has a p-value of 0.049, which is only just under the cutoff. Overall, F-test is also significant (p < 0.001), so the model predicts goals better than chance.

The adjusted R² is about 0.863, so the model explains roughly 86% of the variation in goals, after adjusting for the number of predictors.

# Starting regression models
reg <- lm(goals ~ shots_on_target + assists +
                   successful_dribbles + games_played + passes + minutes_played,
                 data = soccer1)
summary(reg)

Call:
lm(formula = goals ~ shots_on_target + assists + successful_dribbles + 
    games_played + passes + minutes_played, data = soccer1)

Residuals:
    Min      1Q  Median      3Q     Max 
-5.5038 -0.3153  0.0101  0.4364  7.6482 

Coefficients:
                      Estimate Std. Error t value Pr(>|t|)    
(Intercept)          0.0087875  0.1140597   0.077  0.93862    
shots_on_target      0.3756456  0.0084948  44.221  < 2e-16 ***
assists              0.0951289  0.0402412   2.364  0.01843 *  
successful_dribbles -0.0220391  0.0046135  -4.777 2.29e-06 ***
games_played        -0.0174020  0.0062242  -2.796  0.00536 ** 
passes               0.0003310  0.0001873   1.767  0.07775 .  
minutes_played      -0.0003240  0.0002386  -1.358  0.17504    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 1.289 on 549 degrees of freedom
Multiple R-squared:  0.8662,    Adjusted R-squared:  0.8647 
F-statistic: 592.4 on 6 and 549 DF,  p-value: < 2.2e-16
reg1 <- lm(goals ~ assists +
                   successful_dribbles + shots_on_target, 
                 data = soccer1)
summary(reg1)

Call:
lm(formula = goals ~ assists + successful_dribbles + shots_on_target, 
    data = soccer1)

Residuals:
    Min      1Q  Median      3Q     Max 
-5.6183 -0.3359  0.1835  0.3168  7.9288 

Coefficients:
                     Estimate Std. Error t value Pr(>|t|)    
(Intercept)         -0.183544   0.069391  -2.645   0.0084 ** 
assists              0.078056   0.039605   1.971   0.0492 *  
successful_dribbles -0.024790   0.004445  -5.577 3.84e-08 ***
shots_on_target      0.372188   0.008352  44.561  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 1.296 on 552 degrees of freedom
Multiple R-squared:  0.8639,    Adjusted R-squared:  0.8632 
F-statistic:  1168 on 3 and 552 DF,  p-value: < 2.2e-16

Equation

goals = -0.184 + 0.078(assists) - 0.025(successful_dribbles) + 0.372(shots_on_target)

Limitations and Difficulty

I did not spend a lot of time in finding the duration of games played by each player which can affect the result. In future, I would spend some time on finding player’s game time and their progress. The difficulties I came across were that, I had to go through official source to find out the real data of goals scored and ignore other variable. Also, the dataset is named season 2019/2020, when I researched about the season it was actually 2018/2019.