Categories Uncategorized

Comment créer un modèle d’objectifs attendus (xG) en R avec worldfootballR

Objectifs attendus est devenu l’un des concepts les plus importants de l’analyse du football moderne. Au lieu de juger une équipe uniquement sur les buts marqués, xG nous aide à estimer la qualité des occasions créées. Dans ce didacticiel, nous allons créer un modèle pratique d’objectifs attendus dans R en utilisant des données de football, l’ingénierie des fonctionnalités, la régression logistique, l’évaluation du modèle et la visualisation.

Il s’agit d’un guide pratique destiné aux analystes qui souhaitent aller au-delà des simples statistiques de football et commencer à créer des flux de travail d’analyse de football reproductibles dans R.

Quels sont les objectifs attendus ?

Les buts attendus, généralement écrits xG, mesurent la probabilité qu’un tir devienne un but. Un tir à deux mètres devant le but aura généralement une valeur xG élevée, tandis qu’un tir à longue distance depuis l’extérieur de la surface aura généralement une valeur xG faible.

Un modèle xG peut utiliser des variables telles que :

  • Distance de tir
  • Angle de prise de vue
  • Partie du corps utilisée
  • État du jeu
  • Minute du match
  • Type de tir
  • Situation décisive
  • Contexte à domicile ou à l’extérieur

Dans cet article, nous allons créer un modèle de démarrage propre à l’aide de R. Vous pourrez ensuite l’étendre avec des données d’événements plus riches, des données de suivi ou des modèles d’apprentissage automatique plus avancés.

Installer et charger les packages R

# Core data science packages
install.packages(c(
  "tidyverse",
  "ggplot2",
  "dplyr",
  "readr",
  "janitor",
  "broom",
  "yardstick",
  "rsample",
  "pROC",
  "patchwork"
))

# Football data package
install.packages("worldfootballR")

library(tidyverse)
library(ggplot2)
library(dplyr)
library(readr)
library(janitor)
library(broom)
library(yardstick)
library(rsample)
library(pROC)
library(patchwork)
library(worldfootballR)

Créer un ensemble de données de prise de vue simple

Différentes sources de données publiques sur le football structurent les données de tir différemment. Pour rendre ce didacticiel reproductible, nous allons d’abord créer un ensemble de données de tir synthétiques qui se comporte comme de véritables données d’événements de football. Plus tard, vous pourrez les remplacer par vos propres données provenant de FBref, de données ouvertes StatsBomb, d’exportations de style Wyscout ou de flux d’événements personnalisés.

set.seed(123)

n_shots <- 5000

shots <- tibble(
  shot_id = 1:n_shots,
  player = sample(
    c("Player A", "Player B", "Player C", "Player D", "Player E"),
    n_shots,
    replace = TRUE
  ),
  team = sample(
    c("Team Red", "Team Blue", "Team Green", "Team White"),
    n_shots,
    replace = TRUE
  ),
  minute = sample(1:95, n_shots, replace = TRUE),
  x_location = runif(n_shots, min = 70, max = 120),
  y_location = runif(n_shots, min = 0, max = 80),
  body_part = sample(
    c("Right Foot", "Left Foot", "Header", "Other"),
    n_shots,
    replace = TRUE,
    prob = c(0.43, 0.32, 0.20, 0.05)
  ),
  situation = sample(
    c("Open Play", "Corner", "Free Kick", "Penalty", "Counter Attack"),
    n_shots,
    replace = TRUE,
    prob = c(0.68, 0.12, 0.08, 0.03, 0.09)
  ),
  home_away = sample(c("Home", "Away"), n_shots, replace = TRUE)
)

glimpse(shots)

Distance et angle de tir de l’ingénieur

La distance et l’angle sont deux des caractéristiques les plus importantes d’un modèle xG de base. Nous supposerons que l’objectif est centré sur x = 120 et y = 40.

goal_x <- 120
goal_y <- 40

shots <- shots %>%
  mutate(
    distance_to_goal = sqrt(
      (goal_x - x_location)^2 + (goal_y - y_location)^2
    ),
    angle_to_goal = atan2(
      abs(goal_y - y_location),
      goal_x - x_location
    ),
    angle_degrees = angle_to_goal * 180 / pi
  )

shots %>%
  select(shot_id, x_location, y_location, distance_to_goal, angle_degrees) %>%
  head()

Créer un résultat d’objectif

Pour la démonstration, nous simulerons des buts en utilisant une logique de football réaliste. Les tirs plus proches du but devraient avoir plus de chances de devenir des buts. Les pénalités devraient avoir une probabilité plus élevée. Les frappes de tête et les tentatives à longue distance devraient généralement être plus difficiles.

shots <- shots %>%
  mutate(
    linear_probability =
      -2.8 -
      0.08 * distance_to_goal +
      0.025 * angle_degrees +
      if_else(body_part == "Header", -0.35, 0) +
      if_else(body_part == "Other", -0.60, 0) +
      if_else(situation == "Penalty", 3.00, 0) +
      if_else(situation == "Counter Attack", 0.35, 0) +
      if_else(situation == "Free Kick", -0.45, 0),
    
    goal_probability = plogis(linear_probability),
    goal = rbinom(n(), size = 1, prob = goal_probability)
  )

shots %>%
  summarise(
    total_shots = n(),
    total_goals = sum(goal),
    conversion_rate = mean(goal)
  )

Explorez les données de tir

shots %>%
  count(body_part, goal) %>%
  group_by(body_part) %>%
  mutate(rate = n / sum(n))
shots %>%
  group_by(situation) %>%
  summarise(
    shots = n(),
    goals = sum(goal),
    conversion_rate = mean(goal),
    avg_distance = mean(distance_to_goal),
    .groups = "drop"
  ) %>%
  arrange(desc(conversion_rate))

Visualisez les emplacements des prises de vue

ggplot(shots, aes(x = x_location, y = y_location, color = factor(goal))) +
  geom_point(alpha = 0.35) +
  coord_fixed() +
  labs(
    title = "Shot Map",
    x = "Pitch Length",
    y = "Pitch Width",
    color = "Goal"
  ) +
  theme_minimal()

Diviser les données en ensembles de formation et de test

set.seed(123)

shot_split <- initial_split(shots, prop = 0.80, strata = goal)

train_data <- training(shot_split)
test_data  <- testing(shot_split)

nrow(train_data)
nrow(test_data)

Créer un modèle xG de régression logistique

Les objectifs attendus se prêtent naturellement à la régression logistique car le résultat est binaire : objectif ou pas d’objectif.

xg_model <- glm(
  goal ~ distance_to_goal +
    angle_degrees +
    body_part +
    situation +
    home_away +
    minute,
  data = train_data,
  family = binomial()
)

summary(xg_model)

Convertir la sortie du modèle en valeurs xG

test_predictions <- test_data %>%
  mutate(
    xg = predict(xg_model, newdata = test_data, type = "response")
  )

test_predictions %>%
  select(player, team, goal, xg, distance_to_goal, angle_degrees) %>%
  head(10)

Évaluer le modèle xG

Un bon modèle xG doit non seulement prédire les objectifs, mais également produire des probabilités bien calibrées. Si 100 tirs ont chacun un xG de 0,10, nous nous attendons à environ 10 buts sur un échantillon suffisamment grand.

test_predictions %>%
  summarise(
    actual_goals = sum(goal),
    expected_goals = sum(xg),
    avg_xg = mean(xg),
    actual_conversion = mean(goal)
  )

ROC ASC

roc_obj <- roc(
  response = test_predictions$goal,
  predictor = test_predictions$xg
)

auc(roc_obj)
plot(
  roc_obj,
  main = "ROC Curve for xG Model"
)

Score du Brier

brier_score <- mean((test_predictions$xg - test_predictions$goal)^2)

brier_score

Créer des compartiments xG pour l’étalonnage

calibration_table <- test_predictions %>%
  mutate(
    xg_bucket = cut(
      xg,
      breaks = seq(0, 1, by = 0.05),
      include.lowest = TRUE
    )
  ) %>%
  group_by(xg_bucket) %>%
  summarise(
    shots = n(),
    avg_xg = mean(xg),
    actual_goal_rate = mean(goal),
    goals = sum(goal),
    .groups = "drop"
  ) %>%
  filter(shots >= 10)

calibration_table
ggplot(calibration_table, aes(x = avg_xg, y = actual_goal_rate)) +
  geom_point(size = 3) +
  geom_abline(intercept = 0, slope = 1, linetype = "dashed") +
  labs(
    title = "xG Model Calibration",
    x = "Average Predicted xG",
    y = "Actual Goal Rate"
  ) +
  theme_minimal()

Analyse xG au niveau du joueur

Une fois que chaque tir a une valeur xG, nous pouvons les regrouper par joueur. Cela nous permet de comparer les buts, les buts attendus, la surperformance et le volume des tirs.

player_xg <- test_predictions %>%
  group_by(player) %>%
  summarise(
    shots = n(),
    goals = sum(goal),
    xg = sum(xg),
    goals_minus_xg = goals - xg,
    xg_per_shot = mean(xg),
    conversion_rate = mean(goal),
    .groups = "drop"
  ) %>%
  arrange(desc(xg))

player_xg
ggplot(player_xg, aes(x = reorder(player, xg), y = xg)) +
  geom_col() +
  coord_flip() +
  labs(
    title = "Expected Goals by Player",
    x = "Player",
    y = "Total xG"
  ) +
  theme_minimal()

Analyse xG au niveau de l’équipe

team_xg <- test_predictions %>%
  group_by(team) %>%
  summarise(
    shots = n(),
    goals = sum(goal),
    xg = sum(xg),
    goals_minus_xg = goals - xg,
    avg_xg_per_shot = mean(xg),
    .groups = "drop"
  ) %>%
  arrange(desc(xg))

team_xg
ggplot(team_xg, aes(x = reorder(team, goals_minus_xg), y = goals_minus_xg)) +
  geom_col() +
  coord_flip() +
  labs(
    title = "Goals Minus xG by Team",
    x = "Team",
    y = "Goals - Expected Goals"
  ) +
  theme_minimal()

Distribution de la qualité des tirs

ggplot(test_predictions, aes(x = xg)) +
  geom_histogram(bins = 40) +
  labs(
    title = "Distribution of Shot Quality",
    x = "Expected Goals",
    y = "Number of Shots"
  ) +
  theme_minimal()

Comparez les objectifs et les xG par situation

situation_xg <- test_predictions %>%
  group_by(situation) %>%
  summarise(
    shots = n(),
    goals = sum(goal),
    xg = sum(xg),
    avg_xg = mean(xg),
    .groups = "drop"
  ) %>%
  arrange(desc(avg_xg))

situation_xg
situation_long <- situation_xg %>%
  select(situation, goals, xg) %>%
  pivot_longer(
    cols = c(goals, xg),
    names_to = "metric",
    values_to = "value"
  )

ggplot(situation_long, aes(x = reorder(situation, value), y = value, fill = metric)) +
  geom_col(position = "dodge") +
  coord_flip() +
  labs(
    title = "Goals vs Expected Goals by Situation",
    x = "Situation",
    y = "Value",
    fill = "Metric"
  ) +
  theme_minimal()

Créez un modèle xG plus avancé avec des interactions

Un modèle simple est utile, mais le football regorge d’interactions. Par exemple, la distance peut affecter les coups de tête différemment des tirs du pied. Nous pouvons inclure des termes d’interaction dans le modèle.

xg_model_interaction <- glm(
  goal ~ distance_to_goal * body_part +
    angle_degrees +
    situation +
    home_away +
    minute,
  data = train_data,
  family = binomial()
)

summary(xg_model_interaction)
test_predictions_interaction <- test_data %>%
  mutate(
    xg_interaction = predict(
      xg_model_interaction,
      newdata = test_data,
      type = "response"
    )
  )

mean((test_predictions_interaction$xg_interaction - test_predictions_interaction$goal)^2)

Comparez deux modèles xG

model_comparison <- tibble(
  model = c("Basic Logistic Regression", "Interaction Logistic Regression"),
  brier_score = c(
    mean((test_predictions$xg - test_predictions$goal)^2),
    mean((test_predictions_interaction$xg_interaction - test_predictions_interaction$goal)^2)
  ),
  total_predicted_goals = c(
    sum(test_predictions$xg),
    sum(test_predictions_interaction$xg_interaction)
  ),
  actual_goals = c(
    sum(test_predictions$goal),
    sum(test_predictions_interaction$goal)
  )
)

model_comparison

Créer une fonction de prédiction xG réutilisable

predict_xg <- function(model, new_shots) {
  new_shots %>%
    mutate(
      predicted_xg = predict(
        model,
        newdata = new_shots,
        type = "response"
      )
    )
}

new_predictions <- predict_xg(xg_model, test_data)

head(new_predictions)

Créer un exemple de prise de vue personnalisée

custom_shot <- tibble(
  distance_to_goal = 12,
  angle_degrees = 28,
  body_part = "Right Foot",
  situation = "Open Play",
  home_away = "Home",
  minute = 62
)

predict(
  xg_model,
  newdata = custom_shot,
  type = "response"
)

Utilisez worldfootballR pour de vrais flux de travail de football

Pour de vrais projets, vous pouvez utiliser des packages tels que worldfootballR pour collecter des données sur le football à partir de sources publiques et créer des pipelines d’analyse reproductibles. Les colonnes exactes disponibles dépendent de la source et du point de terminaison, donc inspectez toujours vos données avant la modélisation.

library(worldfootballR)
library(tidyverse)

# Example: get FBref match results
# Adjust country, gender, season_end_year, and tier depending on your project

premier_league_results <- fb_match_results(
  country = "ENG",
  gender = "M",
  season_end_year = 2025,
  tier = "1st"
)

glimpse(premier_league_results)
premier_league_results %>%
  clean_names() %>%
  head()

Si vous créez un pipeline complet d’analyse du football avec des flux de travail de type FBref, Transfermarkt et Understat, un modèle de projet plus structuré peut vous faire gagner beaucoup de temps. Je couvre ce type de flux de travail de bout en bout dans Maîtriser les données de football avec worldfootballR, en particulier pour les lecteurs qui souhaitent des scripts R réutilisables, des dossiers propres et des exemples pratiques de données de football.

Exemple : Nettoyer les données des résultats de correspondance

clean_results <- premier_league_results %>%
  clean_names()

clean_results %>%
  glimpse()
# Example structure will depend on the returned data
# Always check column names first

names(clean_results)

Créer un résumé d’équipe au niveau du match

# This is an example pattern.
# You may need to adjust column names depending on your data source.

team_summary_example <- clean_results %>%
  summarise(
    matches = n()
  )

team_summary_example

Enregistrez votre modèle xG

Une fois que vous avez entraîné un modèle, enregistrez-le afin de pouvoir le réutiliser ultérieurement dans des rapports, des tableaux de bord, des API ou des pipelines automatisés.

saveRDS(xg_model, "xg_model_logistic_regression.rds")

loaded_xg_model <- readRDS("xg_model_logistic_regression.rds")

predict(
  loaded_xg_model,
  newdata = custom_shot,
  type = "response"
)

Créer un tableau de rapport xG

xg_report <- test_predictions %>%
  group_by(team, player) %>%
  summarise(
    shots = n(),
    goals = sum(goal),
    xg = round(sum(xg), 2),
    goals_minus_xg = round(goals - sum(xg), 2),
    xg_per_shot = round(mean(xg), 3),
    .groups = "drop"
  ) %>%
  arrange(desc(xg))

xg_report
write_csv(xg_report, "xg_player_report.csv")

Créer une carte de prise de vue xG

ggplot(test_predictions, aes(x = x_location, y = y_location)) +
  geom_point(aes(size = xg, alpha = xg)) +
  coord_fixed() +
  labs(
    title = "xG Shot Map",
    x = "Pitch Length",
    y = "Pitch Width",
    size = "xG",
    alpha = "xG"
  ) +
  theme_minimal()

Créer une table de chances de grande valeur

big_chances <- test_predictions %>%
  filter(xg >= 0.30) %>%
  arrange(desc(xg)) %>%
  select(
    player,
    team,
    minute,
    body_part,
    situation,
    distance_to_goal,
    angle_degrees,
    xg,
    goal
  )

big_chances %>%
  head(20)

Idées d’amélioration du modèle

Ce modèle xG de démarrage peut être amélioré de plusieurs manières. Un flux de travail d’analyse du football professionnel peut inclure :

  • Coordonnées de tir plus précises
  • Poste de gardien de but
  • Pression du défenseur
  • Type de passe avant le tir
  • À travers les balles et les réductions
  • Vitesse de tir
  • Des clichés pour la première fois
  • État du jeu
  • Force de l’équipe
  • Historique de finition du joueur

Former un modèle de style XGBoost plus tard

La régression logistique est interprétable et constitue un bon point de départ. Pour des performances prédictives plus élevées, vous pouvez ensuite le comparer avec des forêts aléatoires, une amplification de gradient ou des modèles bayésiens.

# Example packages for future model upgrades
# install.packages(c("xgboost", "ranger", "tidymodels"))

library(tidymodels)

# A future tidymodels workflow could look like this:

xg_recipe <- recipe(
  goal ~ distance_to_goal + angle_degrees + body_part + situation + home_away + minute,
  data = train_data
) %>%
  step_dummy(all_nominal_predictors()) %>%
  step_normalize(all_numeric_predictors())

xg_recipe

Créer un flux de travail de régression logistique Tidymodels

logistic_spec <- logistic_reg() %>%
  set_engine("glm") %>%
  set_mode("classification")

xg_workflow <- workflow() %>%
  add_recipe(xg_recipe) %>%
  add_model(logistic_spec)

xg_fit <- fit(
  xg_workflow,
  data = train_data %>%
    mutate(goal = factor(goal, levels = c(0, 1)))
)

xg_fit
tidy(xg_fit)

Prédire les probabilités avec Tidymodels

tidy_predictions <- predict(
  xg_fit,
  new_data = test_data,
  type = "prob"
) %>%
  bind_cols(test_data %>% mutate(goal = factor(goal, levels = c(0, 1))))

head(tidy_predictions)
tidy_predictions %>%
  roc_auc(
    truth = goal,
    .pred_1
  )

Transformez xG en Match Insights

La vraie valeur des buts attendus ne consiste pas seulement à prédire si un tir deviendra un but. La valeur provient de l’agrégation. Une fois que chaque tir a une probabilité, vous pouvez créer des informations au niveau du match et de la saison.

match_shots <- test_predictions %>%
  mutate(
    match_id = sample(1:100, n(), replace = TRUE)
  )

match_xg <- match_shots %>%
  group_by(match_id, team) %>%
  summarise(
    shots = n(),
    goals = sum(goal),
    xg = sum(xg),
    .groups = "drop"
  )

match_xg %>%
  arrange(match_id, desc(xg)) %>%
  head(20)

Trouvez des équipes créant de meilleures chances

team_chance_quality <- test_predictions %>%
  group_by(team) %>%
  summarise(
    shots = n(),
    total_xg = sum(xg),
    avg_xg_per_shot = mean(xg),
    big_chances = sum(xg >= 0.30),
    low_quality_shots = sum(xg <= 0.05),
    .groups = "drop"
  ) %>%
  arrange(desc(avg_xg_per_shot))

team_chance_quality

Pensées finales

Construire un modèle d’objectifs attendus dans R est l’un des meilleurs moyens d’apprendre l’analyse du football, car il combine le nettoyage des données, l’ingénierie des fonctionnalités, la modélisation statistique, la visualisation et l’interprétation. Un simple modèle de régression logistique peut déjà vous en apprendre beaucoup sur la qualité des tirs, les performances des joueurs et le style d’attaque de l’équipe.

À partir de là, les prochaines étapes sont claires : utiliser des données de football plus riches, améliorer vos fonctionnalités, comparer différents modèles, évaluer l’étalonnage et créer des flux de travail reproductibles qui peuvent être mis à jour chaque semaine pendant la saison.

Les buts attendus ne constituent pas la réponse définitive à l’analyse du football, mais ils constituent l’un des meilleurs points de départ pour une science sérieuse des données sur le football dans R.

L’article Comment créer un modèle d’objectifs attendus (xG) dans R avec worldfootballR apparaît en premier sur R Programming Books.


PakarPBN

A Private Blog Network (PBN) is a collection of websites that are controlled by a single individual or organization and used primarily to build backlinks to a “money site” in order to influence its ranking in search engines such as Google. The core idea behind a PBN is based on the importance of backlinks in Google’s ranking algorithm. Since Google views backlinks as signals of authority and trust, some website owners attempt to artificially create these signals through a controlled network of sites.

In a typical PBN setup, the owner acquires expired or aged domains that already have existing authority, backlinks, and history. These domains are rebuilt with new content and hosted separately, often using different IP addresses, hosting providers, themes, and ownership details to make them appear unrelated. Within the content published on these sites, links are strategically placed that point to the main website the owner wants to rank higher. By doing this, the owner attempts to pass link equity (also known as “link juice”) from the PBN sites to the target website.

The purpose of a PBN is to give the impression that the target website is naturally earning links from multiple independent sources. If done effectively, this can temporarily improve keyword rankings, increase organic visibility, and drive more traffic from search results.

Jasa Backlink

Download Anime Batch

Leave a Reply

Your email address will not be published. Required fields are marked *