Stéphane Caron

15 minutes

On entend souvent dire que le repêchage de la LNH, tout comme c’est le cas dans d’autres sports professionnels, est une science inexacte. J’imagine que cette expression fait référence au fait que si l’on doit repêcher un joueur $x$ à un moment $t$ donné, il n’y a aucune certitude que ce joueur est le bon choix. Mais disons qu’on prend le temps de regarder plusieurs choix, sur plusieurs années, est-ce toujours le cas? Est-ce que le repêchage devient en quelque sorte une science plus “exacte” lorsque l’échantillon de joueurs repêchés devient plus grand?

Dans cet article, je tenterai d’éclairer cette question en analysant les récents repêchages de la LNH. Pour se faire, j’utiliserai différentes fonctionnalités du package tidynhl, un projet réalisé par Jean-Philippe Le Cavalier (un bon ami à moi).

Mise en contexte

Pour commencer, je dois vous faire un aveu: je préfère regarder le repêchage de la LNH que la finale de la coupe Stanley elle-même. En tant qu’amateur de hockey, j’ai presque honte de l’avouer. J’adore analyser les futurs joueurs ainsi que les choix des équipes pendant cette séance de repêchage. Certes, il y aura toujours beaucoup d’incertitude autour de la sélection d’un joueur. Par contre, en tant que scientifique de données, je crois que cette incertitude peut être réduite par les experts et les dépisteurs les plus compétents, ceux qui savent bien évaluer les jeunes joueurs. La question à laquelle nous tenterons de répondre peut se formuler comme

Est-ce que certaines équipes sont plus performantes que d’autres pour repêcher de futurs joueurs?

Préparation des données

Pour tenter de répondre à cette question, j’ai décidé d’analyser les sélections des repêchages allant de 2005 à 2015. Ce choix est arbitraire et se base sur le fait que 10 ans me semble assez crédible comme échantillon de joueurs (près de 2500 joueurs repêchés). Aussi, pour éviter que les bons joueurs repêchés dans de mauvaises équipes soient trop pénalisés, nous allons nous restreindre aux statistiques en saison régulière. Cela permettra aux joueurs d’être comparés sur une base plus équitable, où toutes les équipes jouent le même nombre de matchs. La première étape consiste à importer les données associées à ces repêchages avec la fonction tidy_drafts().

# Charger les packages
library(data.table)
library(tidynhl)

# Définir les annees de repechages analysees
DRAFT_YEARS <- c(2005:2015)

# Obtenir les données de repêchage
dt_draft <- tidy_drafts(
  drafts_year = DRAFT_YEARS,
  keep_id = TRUE
)

# Enlever les choix sans joueurs (exceptionnels)
# Par exemple: NJ Devils perdu leur choix en 2011 (https://www.cbssports.com/nhl/news/devils-kovalchuk-penalty-reduced-get-first-round-pick-back/)
dt_draft <- dt_draft[!is.na(player_id)]

# On se crée une fonction pour fusionner les équipes déménagées (ou renommées)
# Nettoyage de données
merge_moved_teams <- function(dt) {

  dt[team_abbreviation %in% c("WPG", "ATL"), team_abbreviation := "WPG/ATL"]
  dt[team_abbreviation %in% c("PHX", "ARI"), team_abbreviation := "ARI/PHX"]

  dt

}

# Fusionner les équipes déménagés
merge_moved_teams(dt_draft)

# Afficher un extrait des données
dt_draft[]
##       draft_year draft_round draft_pick draft_overall team_id team_abbreviation player_id      player_name amateur_league_name amateur_team_name
##    1:       2005           1          1             1       5               PIT   8471675    Sidney Crosby               QMJHL          Rimouski
##    2:       2005           1          2             2      24               ANA   8471676       Bobby Ryan                 OHL        Owen Sound
##    3:       2005           1          3             3      12               CAR   8471677     Jack Johnson                 USA          USA U-18
##    4:       2005           1          4             4      30               MIN   8471678   Benoit Pouliot                 OHL           Sudbury
##    5:       2005           1          5             5       8               MTL   8471679      Carey Price                 WHL          Tri-City
##   ---
## 2334:       2015           7         26           207       8               MTL   8478921 Jeremiah Addison                 OHL            Ottawa
## 2335:       2015           7         27           208      22               EDM   8478922 Miroslav Svoboda           CZREP-JR.        Trinec Jr.
## 2336:       2015           7         28           209      22               EDM   8478923     Ziyat Paigin              RUSSIA             Kazan
## 2337:       2015           7         29           210      23               VAN   8478924       Tate Olson                 WHL     Prince George
## 2338:       2015           7         30           211      16               CHI   8478925   John Dahlstrom          SWEDEN-JR.      Frolunda Jr.

Afin d’avoir plus d’informations sur les joueurs, nous allons également importer des métadonnées (date de naissance, position, nationalité, etc.) sur ceux-ci grâce à la fonction tidy_players_meta().

# Obtenir les metadonnées sur les joueurs
dt_meta_player <- tidy_players_meta(
  players_id = dt_draft$player_id,
  keep_id = TRUE
)

# Fusionner les équipes déménagées
merge_moved_teams(dt_meta_player)

cols <- c("player_id", "player_position_type")
dt_draft[dt_meta_player,
         (cols) := mget(cols),
         on = .(player_id)]


# Afficher un extrait des métadonnées
dt_meta_player[]
##       player_id      player_name player_active player_roster_status player_number player_position player_position_type player_nationality player_birth_country player_birth_stateprovince player_birth_city player_birth_date player_dead player_death_date player_inches player_pounds player_hand player_rookie player_hof player_hof_year player_nhl_100 team_id team_abbreviation
##    1:   8470659   Masi Marjamaki         FALSE                    N            58               L                    F                FIN                  FIN                       <NA>              Pori        1985-01-16       FALSE              <NA>            74           184           L         FALSE      FALSE              NA          FALSE      NA              <NA>
##    2:   8470812     Dany Roussin         FALSE                    N            NA               L                    F                CAN                  CAN                         QC       Quebec City        1985-01-09       FALSE              <NA>            74           190           L         FALSE      FALSE              NA          FALSE      NA              <NA>
##    3:   8470817  William Colbert         FALSE                    N            52               D                    D                CAN                  CAN                         ON          Arnprior        1985-02-06       FALSE              <NA>            74           210           L         FALSE      FALSE              NA          FALSE      NA              <NA>
##    4:   8470872  Trevor Hendrikx         FALSE                    N            NA               D                    D                CAN                  CAN                         ON        Winchester        1985-03-29       FALSE              <NA>            74           200           R         FALSE      FALSE              NA          FALSE      NA              <NA>
##    5:   8470996     Danny Syvret         FALSE                    Y            26               D                    D                CAN                  CAN                         ON         Millgrove        1985-06-13       FALSE              <NA>            72           205           L         FALSE      FALSE              NA          FALSE      NA              <NA>
##   ---
## 2328:   8478921 Jeremiah Addison         FALSE                    N            64               L                    F                CAN                  CAN                         ON          Brampton        1996-10-21       FALSE              <NA>            72           188           L          TRUE      FALSE              NA          FALSE      NA              <NA>
## 2329:   8478922 Miroslav Svoboda         FALSE                    N            39               G                    G                CZE                  CZE                       <NA>            Vsetin        1995-03-07       FALSE              <NA>            75           191           L          TRUE      FALSE              NA          FALSE      NA              <NA>
## 2330:   8478923     Ziyat Paigin         FALSE                    N            92               D                    D                RUS                  RUS                       <NA>             Penza        1995-02-08       FALSE              <NA>            78           210           L          TRUE      FALSE              NA          FALSE      NA              <NA>
## 2331:   8478924       Tate Olson         FALSE                    N            NA               D                    D                CAN                  CAN                         SK         Saskatoon        1997-03-21       FALSE              <NA>            74           174           L          TRUE      FALSE              NA          FALSE      NA              <NA>
## 2332:   8478925   John Dahlstrom         FALSE                    N            44               R                    F                SWE                  SWE                       <NA>        Kungsbacka        1997-01-22       FALSE              <NA>            72           189           L          TRUE      FALSE              NA          FALSE      NA              <NA>

Nous allons ensuite importer les statstiques individuelles de ces joueurs repêchés grâce aux fonctions tidy_skaters_stats() et tidy_goalies_stats().

# Obtenir les données de statistiques individuelles des joueurs
dt_skaters_stats <- tidy_skaters_stats(
  players_id = dt_draft[player_position_type %in% c("F", "D"), player_id],
  playoffs = FALSE,
  keep_id = TRUE
)

# Obtenir les données de statistiques individuelles des joueurs
dt_goalies_stats <- tidy_goalies_stats(
  players_id = dt_draft[player_position_type == "G", player_id],
  playoffs = FALSE,
  keep_id = TRUE
)

dt_stats <- rbindlist(list(dt_skaters_stats, dt_goalies_stats),
                      use.names = TRUE,
                      fill = TRUE)

# Afficher un extrait des données
dt_stats[]
##       player_id    player_name season_id season_years season_type team_id team_abbreviation skater_games skater_goals skater_assists skater_points skater_plusminus skater_pim skater_toi skater_shifts skater_gwg skater_otg skater_shots skater_blocked skater_hits skater_ev_goals skater_ev_assists skater_ev_points skater_ev_toi skater_pp_goals skater_pp_assists skater_pp_points skater_pp_toi skater_pk_goals skater_pk_assists skater_pk_points skater_pk_toi goalie_games goalie_started goalie_wins goalie_losses goalie_ties goalie_ot goalie_shutouts goalie_shotagainst goalie_goalagainst goalie_savepct goalie_gaa goalie_toi goalie_ev_shotagainst goalie_ev_goalagainst goalie_ev_savepct goalie_pp_shotagainst goalie_pp_goalagainst goalie_pp_savepct goalie_pk_shotagainst goalie_pk_goalagainst goalie_pk_savepct
##    1:   8470659 Masi Marjamaki  20052006      2005-06     regular       2               NYI            1            0              0             0                0          0   5.283333             7          0          0            0              0           1               0                 0                0      5.283333               0                 0                0      0.000000               0                 0                0      0.000000           NA             NA          NA            NA          NA        NA              NA                 NA                 NA             NA         NA         NA                    NA                    NA                NA                    NA                    NA                NA                    NA                    NA                NA
##    2:   8470996   Danny Syvret  20052006      2005-06     regular      22               EDM           10            0              0             0               -1          6 123.133333           166          0          0            8              8           1               0                 0                0     96.916667               0                 0                0     25.050000               0                 0                0      1.166667           NA             NA          NA            NA          NA        NA              NA                 NA                 NA             NA         NA         NA                    NA                    NA                NA                    NA                    NA                NA                    NA                    NA                NA
##    3:   8470996   Danny Syvret  20062007      2006-07     regular      22               EDM           16            0              1             1              -10          6 295.500000           373          0          0           15             25           1               0                 1                1    247.700000               0                 0                0     28.300000               0                 0                0     19.500000           NA             NA          NA            NA          NA        NA              NA                 NA                 NA             NA         NA         NA                    NA                    NA                NA                    NA                    NA                NA                    NA                    NA                NA
##    4:   8470996   Danny Syvret  20082009      2008-09     regular       4               PHI            2            0              0             0               -1          0  18.850000            27          0          0            0              1           1               0                 0                0     18.850000               0                 0                0      0.000000               0                 0                0      0.000000           NA             NA          NA            NA          NA        NA              NA                 NA                 NA             NA         NA         NA                    NA                    NA                NA                    NA                    NA                NA                    NA                    NA                NA
##    5:   8470996   Danny Syvret  20092010      2009-10     regular       4               PHI           21            2              2             4                1         12 262.050000           357          0          0           14             20           6               2                 2                4    257.516667               0                 0                0      2.583333               0                 0                0      1.950000           NA             NA          NA            NA          NA        NA              NA                 NA                 NA             NA         NA         NA                    NA                    NA                NA                    NA                    NA                NA                    NA                    NA                NA
##   ---
## 6717:   8478492  Ilya Samsonov  20202021      2020-21     regular      15               WSH           NA           NA             NA            NA               NA         NA         NA            NA         NA         NA           NA             NA          NA              NA                NA               NA            NA              NA                NA               NA            NA              NA                NA               NA            NA            2              2           1             0           0         1               0                 53                  7      0.8679245   3.360000   125.0000                    43                     6         0.8604651                     8                     1         0.8750000                     2                     0                 1
## 6718:   8478499      Adin Hill  20172018      2017-18     regular      53               ARI           NA           NA             NA            NA               NA         NA         NA            NA         NA         NA           NA             NA          NA              NA                NA               NA            NA              NA                NA               NA            NA              NA                NA               NA            NA            4              4           1             3           0         0               0                129                 14      0.8914729   3.489097   240.7500                   118                    12         0.8983051                     9                     2         0.7777778                     2                     0                 1
## 6719:   8478499      Adin Hill  20182019      2018-19     regular      53               ARI           NA           NA             NA            NA               NA         NA         NA            NA         NA         NA           NA             NA          NA              NA                NA               NA            NA              NA                NA               NA            NA              NA                NA               NA            NA           13             11           7             5           0         0               1                322                 32      0.9006211   2.757234   696.3500                   271                    27         0.9003690                    41                     5         0.8780488                    10                     0                 1
## 6720:   8478499      Adin Hill  20192020      2019-20     regular      53               ARI           NA           NA             NA            NA               NA         NA         NA            NA         NA         NA           NA             NA          NA              NA                NA               NA            NA              NA                NA               NA            NA              NA                NA               NA            NA           13              9           2             4           0         3               0                343                 28      0.9183673   2.621859   640.7667                   284                    19         0.9330986                    45                     9         0.8000000                    14                     0                 1
## 6721:   8478916   Joey Daccord  20182019      2018-19     regular       9               OTT           NA           NA             NA            NA               NA         NA         NA            NA         NA         NA           NA             NA          NA              NA                NA               NA            NA              NA                NA               NA            NA              NA                NA               NA            NA            1              1           0             1           0         0               0                 40                  5      0.8750000   5.000000    60.0000                    35                     3         0.9142857                     3                     2         0.3333333                     2                     0                 1

Finalement, nous allons appliquer quelques transformations et manipulations à ces trois jeux de données afin de régler quelques détails techniques importants pour l’analyse.

# On se crée une fonction pour aggréger les données essentielles
# On va devoir aggréger plusieurs fois les memes stats (pts, games, wins) dans l'analyse
aggregate_stats <- function(dt, old_by_names, new_by_names) {

  dt_aggregated <- dt[, .(
    skater_points = sum(skater_points, na.rm = TRUE),
    skater_games = sum(skater_games, na.rm = TRUE),
    goalie_games = sum(goalie_games, na.rm = TRUE),
    goalie_wins = sum(goalie_wins, na.rm = TRUE)
  ), old_by_names]

  # On additionne les games des goalies et des players car stockés dans 2 variables différentes
  dt_aggregated[, player_games := ifelse(is.na(skater_games), 0, skater_games) + ifelse(is.na(goalie_games), 0, goalie_games)]
  setnames(dt_aggregated, old_by_names, new_by_names)

  dt_aggregated[]

}

# Fusionner les équipes déménagés
merge_moved_teams(dt_stats)

# Aggrégé les données par équipe jouée et joindre les données
dt_stats_aggregated <- aggregate_stats(
  dt = dt_stats,
  old_by_names = c("player_id", "team_abbreviation"),
  new_by_names = c("player_id", "team_played")
)

dt_all <- merge(dt_draft,
                dt_stats_aggregated,
                by = "player_id",
                all.x = TRUE)

setnames(dt_all, old = "team_abbreviation", new = "team_drafted")

# Afficher un extrait des données pour un joueur
dt_all[player_name == "P.K. Subban"]
##    player_id draft_year draft_round draft_pick draft_overall team_id team_drafted player_name amateur_league_name amateur_team_name player_position_type team_played skater_points skater_games goalie_games goalie_wins player_games
## 1:   8474056       2007           2         13            43       8          MTL P.K. Subban                 OHL        Belleville                    D         MTL           278          434            0           0          434
## 2:   8474056       2007           2         13            43       8          MTL P.K. Subban                 OHL        Belleville                    D         NSH           130          211            0           0          211
## 3:   8474056       2007           2         13            43       8          MTL P.K. Subban                 OHL        Belleville                    D         NJD            22           78            0           0           78

Maintenant que nous avons structuré les données dans un format étant plus facile à manipuler, nous allons tenter d’aggréger celles-ci de différentes manières afin d’avoir un portrait par équipe. Allons-y !

Les équipes repêchent-elles autant?

Dans un premier temps, on est en droit de se poser la question: est-ce que toutes les équipes de la LNH repêchent autant de joueurs? Sachant que chaque équipe possède un choix dans chacune des rondes du repêchage, on pourrait penser que oui. Par contre, on sait très bien que les équipes peuvent échanger leurs choix afin d’obtenir des joueurs ou d’autres choix. Ainsi, j’étais curieux de voir comment était distribué ce nombre de choix “réels” parmi les équipes. Pour commencer, nous allons aggréger certaines informations par équipe.

# Aggréger les données des jouers par équipes repêchés
dt_per_team <- dt_all[, .(
  nb_picks = uniqueN(.SD[]$player_id),
  nb_1st_round_picks = uniqueN(.SD[draft_round == 1]$player_id),
  nb_games_played = sum(player_games, na.rm = TRUE),
  nb_points = sum(skater_points, na.rm = TRUE),
  nb_wins = sum(goalie_wins, na.rm = TRUE)
  ), .(team_drafted)]

# Afficher un extrait
head(dt_per_team[])
##    team_drafted nb_picks nb_1st_round_picks nb_games_played nb_points nb_wins
## 1:          NYI       87                 14           10864      4808     129
## 2:          LAK       86                 10           12913      4856     513
## 3:          SJS       78                  9            9708      3770      65
## 4:          CBJ       84                 13           12294      4564     303
## 5:          EDM       80                 13           10545      5150       0
## 6:          NYR       71                  8            8112      3204      15

Maintenant que nous avons les données aggrégées par équipe, nous pouvons visualiser le nombre de joueurs repêchés grâce au package ggplot2. En bonus, on peut également voir le nombre de joueurs repêchés en première ronde (points rouges).

library(ggplot2)

ggplot(
  data = dt_per_team,
  mapping = aes(
    x = nb_picks,
    y = reorder(as.factor(team_drafted), nb_picks),
  )
) +
  geom_col() +
  geom_point(
    mapping = aes(
      x = nb_1st_round_picks,
      color = " "
    )
  ) +
  scale_color_manual(
    name = "Nombre de joueurs choisi en 1er ronde",
    values = c(" " = "red")
  ) +
  labs(
    title = "Nombre de joueurs repêchés par équipe",
    subtitle = "Saisons 2005 à 2015",
    x = "Nombre de joueurs "
  )

On peut conclure que le nombre de joueurs repêchés par équipe n’est clairement pas uniforme (écart de 30 choix entre la première et la dernière équipe). On pourrait également dire la même chose pour les choix de premières rondes (écart de 9 choix).

Qui repêche “bien”?

Tentons maintenant d’analyser des mesures qui nous permettront de conclure qu’une équipe semble repêcher de bons joueurs, et ce, sur une assez longue période de temps (en occurence 10 ans ici). Gardons bien en tête l’aspect relatif entre les équipes, car l’objectif demeure de voir si certaines équipes sont plus performantes que les autres.

Les matchs joués dans la Grande Ligue

Comment savoir si une équipe repêche de bons joueurs? La première idée qui me vient en tête est évidemment de regarder le nombre de matchs joué dans la LNH. Cette mesure est en quelque sorte indépendante de la position ou du style de joueur, ce qui rend son interprétation plus simple. Par contre, n’oublions pas que certaines équipes ont repêchés plus de joueurs que d’autres. Dans le graphique ci-dessous, si la logique du nombre de choix était respectée, les barres seraient ordonnées de la plus pâle vers la plus foncée (en partant d’en haut).

ggplot(
  data = dt_per_team,
  mapping = aes(
    y = reorder(as.factor(team_drafted), nb_games_played),
    x = nb_games_played,
    fill = nb_picks
  )
) +
  geom_col() +
  scale_fill_continuous("Nombre de choix au repêchage total") +
  guides(fill = guide_colourbar(barwidth = 10, barheight = 0.5)) +
  labs(
    title = "Matchs joués par les joueurs repêchés selon équipe",
    subtitle = "Saisons 2005 à 2015 (saison régulière seulement)",
    x = "Nombre de matchs joués"
  )

Je vous laisse tirer vos propres conclusions, mais de mon côté je remarque que les Blackhawks de Chicago semblent avoir peu de matchs joués pour le nombre de choix “réels”.

Les buts 🚨 et les passes 🍎 récoltés

Une autre mesure évidente à analyser est le nombre de points ($buts + passes$) obtenus par les joueurs repêchés par une équipe. Contrairement aux matchs joués, il faut tenir compte de la position du joueur dans ce cas-ci. Les attaquants font en général plus de points que les défenseurs, et peut-être que certaines équipes repêchent plus de défenseurs, ou même de gardiens (attendez ça s’en vient) …

library(dplyr)

dt_stats_position <- aggregate_stats(
  dt = dt_all,
  old_by_names = c("team_drafted", "player_position_type"),
  new_by_names = c("team_drafted", "player_position_type")
)

cols <- c("team_drafted", "nb_points")
dt_stats_position[dt_per_team, (cols) := mget(cols), on = .(team_drafted)]

dt_stats_position[player_position_type %in% c("F", "D"),] %>%
  ggplot(
    mapping = aes(
      x = skater_points,
      y = reorder(as.factor(team_drafted), nb_points),
      group = player_position_type
    )
  ) +
    geom_col() +
    facet_grid(. ~ player_position_type) +
    theme_classic() +
    theme(axis.title.y = element_blank(),
          legend.position = "bottom") +
    labs(
      title = "Nombre de points obtenus par les joueurs repêchés par équipe",
      subtitle = "Saisons 2005 à 2015 (saison régulière seulement)",
      x = "Nombre de points"
    )

Dans le graphique ci-dessus, les équipes sont ordonnées selon le nombre de points combiné entre les attaquants (F) et les défenseurs (D). On peut donc conclure que les Oilers d’Edmonton est l’équipe qui a repêché, entre 2005 et 2015, les joueurs ayant récoltés le plus de points dans la LNH (merci à la lotterie). Dans ce graphique, on peut également voir que les Ducks d’Anaheim et les Predators de Nashville semblent avoir repêchés de bons défenseurs, probablement au détriment de repêcher de bons attaquants. Voici les 5 défenseurs les plus productifs repêchés par ces 2 équipes:

unique(dt_all[team_drafted == "ANA" & player_position_type == "D"][order(-skater_points), player_name])[1:5]
## [1] "Cam Fowler"      "Jake Gardiner"   "Hampus Lindholm" "Sami Vatanen"    "Shea Theodore"
unique(dt_all[team_drafted == "NSH" & player_position_type == "D"][order(-skater_points), player_name])[1:5]
## [1] "Roman Josi"     "Ryan Ellis"     "Seth Jones"     "Mattias Ekholm" "Cody Franson"

Pas mal 😮!

Nos amis les gardiens

Maintenant que nous avons regardé les attaquants et les défenseurs, jetons un coup d’oeil aux gardiens de but. Comme mesure alternative au nombre de points, j’ai utilisé le nombre de victoires.

aggregate_stats(
  dt = dt_all,
  old_by_names = c("team_drafted", "player_position_type"),
  new_by_names = c("team_drafted", "player_position_type")
)[player_position_type == "G"] %>%
  ggplot(
    mapping = aes(
      x = goalie_wins,
      y = reorder(as.factor(team_drafted), goalie_wins),
      group = player_position_type
    )
  ) +
    geom_col() +
    facet_grid(. ~ player_position_type) +
    theme_classic() +
    theme(axis.title.y = element_blank(),
          legend.position = "bottom") +
    labs(
      title = "Nombre de victoires obtenues par les gardiens repêchés par équipe",
      subtitle = "Saisons 2005 à 2015 (saison régulière seulement)",
      x = "Nombre de victoires"
    )

Pour ceux qui pensaient que Carey Price permettrait au Canadiens d’être au premier rang, vous étiez trop ambitieux. Par contre, peut-être qu’ils auraient eu plus de chances si j’avais inclus le repêchage de 2003, séance où fut repeché un certain Jaroslav Halak. On peut remarquer que les Capitals de Washington semblent avoir eu du flair pour repêcher de bons gardiens de buts. Seriez-vous capable de nommer quelques uns de ces gardiens?

unique(dt_all[team_drafted == "WSH" & player_position_type == "G"][order(-goalie_wins), player_name])[1:5]
## [1] "Braden Holtby"    "Semyon Varlamov"  "Michal Neuvirth"  "Philipp Grubauer" "Ilya Samsonov"

Connaissant les problèmes de gardiens de but qu’on connu les Maple Leafs de Toronto, il est suprenant de les voir au 3ème rang. Toutefois, ils auraient peut-être mieux fait de conserver Tukka Rask dans leurs rangs … Encore une fois, on ne peut pas dire que les Blackhawks ont eu beaucoup de succès avec leurs gardiens repêchés sur cette période.

Trouver le bon joueur

Maintenant que nous avons un premier portrait de la performance des équipes au repêchage, je veux valider une dernière chose. Je veux voir si certaines équipes ont tendance à faire plus souvent le “bon choix” que d’autres. Pour évaluer si une équipe fait le “bon choix”, j’ai mis en place un petit algorithme. Cet algorithme peut s’expliquer comme suit: je regarde pour un choix donné, les choix subséquents et je valide qu’aucun joueur repêché après ce choix n’a fait plus de points. Pour les gardiens, je regarde le nombre de victoires. Pour paufiner cette approche, j’ai fais quelques hypothèses additionnelles:

JOUEURS_FENETRE <- 20
JOUEURS_MIN_PTS <- 100
JOUEURS_GARDIENS_INTERVAL <- 0.95
GARDIENS_MIN_WINS <- 50
  • Je regarde uniquement les 20 choix subséquents à un choix donné. Cela évite qu’une perle rare repêchée en fin de repêchage, disons Pavel Datsyuk, fasse passer tous les autres choix pour de “mauvais choix”.
  • Je vérifie que le joueur a fait un minimum de points, j’ai fixé ce minimum à 100 points.
  • Je donne le bénéfice du doute à un joueur ayant presque fait le même nombre de points qu’un autre dans la fenêtre de 20 choix. Pour rendre cela possible, je considère toujours comme un “bon choix” un joueur qui a fait au moins 95% du nombre de points qu’un autre joueur dans la fenêtre.
  • Je compare les attaquants avec les attaquants et les défenseurs avec les défenseurs.
  • En raison du plus petit nombre de gardiens repêchés, je ne considère pas de fenêtre pour cette position. Ainsi, pour qu’un gardien soit considéré comme un “bon choix”, il doit avoir récolté plus de victoires que tous les gardiens repêchés après.
  • Je donne également le bénéfice du doute aux gardiens, j’applique encore une fois un ratio de 95% sur le nombre de victoires.
  • Les gardiens doivent avoir récoltés au moins 50 victoires pour être considérés comme un “bon choix”.

Ces décisions un peu arbitraires sont basées sur mon jugement personnel. Je vous laisse le soin de changer certains de ces paramètres comme bon vous semble. Maintenant, voici la fonction qui nous permettra de tester notre approche.

define_good_choice <- function(dt, player_window, player_min_pts, goalie_min_wins, interval) {

  dt[, good_pick := NA]

  for (row in seq_len(nrow(dt))) {

    # On stock les informations sur le choix à valider
    draft_year_temp <- dt[row,]$draft_year
    draft_pick_temp <- dt[row,]$draft_overall
    draft_position_temp <- dt[row,]$player_position_type
    draft_nb_points_temp <- dt[row,]$skater_points
    draft_wins_temp <- dt[row,]$goalie_wins

    if (draft_position_temp %in% c("F", "D")) {

      # On filtre les joueurs repêchés dans la fenêtre
      dt_temp <- dt[draft_year == draft_year_temp & player_position_type == draft_position_temp & draft_overall <= (draft_pick_temp + player_window) & draft_overall > draft_pick_temp,]

      # On calcule le nombre de points maximal dans la fenetre
      max_pts_windows <- max(dt_temp$skater_points, na.rm = TRUE)

      # Si manquant, on remplace par 0
      max_pts_windows <- ifelse(is.na(max_pts_windows), 0, max_pts_windows)
      draft_nb_points_temp <- ifelse(is.na(draft_nb_points_temp), 0, draft_nb_points_temp)

      if (draft_nb_points_temp > (max_pts_windows * interval) & draft_nb_points_temp >= player_min_pts) {
        dt[row,]$good_pick <- TRUE
      } else {
        dt[row,]$good_pick <- FALSE
      }

    } else if (draft_position_temp == "G") {

      # Aucune fenetre pour les gardiens
      dt_temp <- dt[draft_year == draft_year_temp & player_position_type == draft_position_temp & draft_overall > draft_pick_temp,]

      # On calcule le nombre de victoires maximal après le choix
      max_win_windows <- max(dt_temp$goalie_wins, na.rm = TRUE)

      # Si manquant, on remplace par 0
      max_win_windows <- ifelse(is.na(max_win_windows), 0, max_win_windows)
      draft_wins_temp <- ifelse(is.na(draft_wins_temp), 0, draft_wins_temp)

      if (draft_wins_temp > (max_win_windows * interval) & draft_wins_temp >= goalie_min_wins) {
        dt[row,]$good_pick <- TRUE
      } else {
        dt[row,]$good_pick <- FALSE
      }

    }

  }

  dt[]

}

Afin de valider que notre approche se comporte bel et bien comme souhaité, on peut jeter un apperçu aux 30 premiers choix du repêchage du 2005 et interpréter la colonne good_pick.

new_draft_aggregation = aggregate_stats(
  dt = dt_all,
  old_by_names = c("player_id", "draft_year", "draft_overall", "player_name", "team_drafted", "player_position_type"),
  new_by_names = c("player_id", "draft_year", "draft_overall", "player_name", "team_drafted", "player_position_type")
)

dt_good_picks <- define_good_choice(
  dt = new_draft_aggregation,
  player_window = JOUEURS_FENETRE,
  player_min_pts = JOUEURS_MIN_PTS,
  goalie_min_wins = GARDIENS_MIN_WINS,
  interval = JOUEURS_GARDIENS_INTERVAL
)

# Apercu des 30 premiers choix du repechage de 2005
dt_good_picks[draft_year == 2005][order(draft_overall)][1:30, .(draft_year, draft_overall, team_drafted, player_name, player_position_type, skater_points, goalie_wins, good_pick)]
##     draft_year draft_overall team_drafted      player_name player_position_type skater_points goalie_wins good_pick
##  1:       2005             1          PIT    Sidney Crosby                    F          1275           0      TRUE
##  2:       2005             2          ANA       Bobby Ryan                    F           563           0     FALSE
##  3:       2005             3          CAR     Jack Johnson                    D           302           0      TRUE
##  4:       2005             4          MIN   Benoit Pouliot                    F           263           0     FALSE
##  5:       2005             5          MTL      Carey Price                    G             0         353      TRUE
##  6:       2005             6          CBJ    Gilbert Brule                    F            95           0     FALSE
##  7:       2005             7          CHI      Jack Skille                    F            96           0     FALSE
##  8:       2005             8          SJS  Devin Setoguchi                    F           261           0     FALSE
##  9:       2005             9          OTT        Brian Lee                    D            36           0     FALSE
## 10:       2005            10          VAN      Luc Bourdon                    D             2           0     FALSE
## 11:       2005            11          LAK     Anze Kopitar                    F           968           0      TRUE
## 12:       2005            12          NYR       Marc Staal                    D           192           0     FALSE
## 13:       2005            13          BUF   Marek Zagrapan                    F             0           0     FALSE
## 14:       2005            14          WSH    Sasha Pokulok                    D             0           0     FALSE
## 15:       2005            15          NYI     Ryan O'Marra                    F             7           0     FALSE
## 16:       2005            16      WPG/ATL     Alex Bourret                    F             0           0     FALSE
## 17:       2005            17      ARI/PHX    Martin Hanzal                    F           338           0     FALSE
## 18:       2005            18          NSH      Ryan Parent                    D             7           0     FALSE
## 19:       2005            19          DET      Jakub Kindl                    D            87           0     FALSE
## 20:       2005            20          FLA  Kenndal McArdle                    F             3           0     FALSE
## 21:       2005            21          TOR      Tuukka Rask                    G             0         297     FALSE
## 22:       2005            22          BOS     Matt Lashoff                    D            16           0     FALSE
## 23:       2005            23          NJD  Niclas Bergfors                    F            83           0     FALSE
## 24:       2005            24          STL       T.J. Oshie                    F           575           0     FALSE
## 25:       2005            25          EDM  Andrew Cogliano                    F           426           0     FALSE
## 26:       2005            26          CGY      Matt Pelech                    F             4           0     FALSE
## 27:       2005            27          WSH       Joe Finley                    D             1           0     FALSE
## 28:       2005            28          DAL    Matt Niskanen                    D           356           0      TRUE
## 29:       2005            29          PHI     Steve Downie                    F           196           0     FALSE
## 30:       2005            30          TBL Vladimir Mihalik                    D             3           0     FALSE
##     draft_year draft_overall team_drafted      player_name player_position_type skater_points goalie_wins good_pick

À partir de cet apperçu, on peut voir que notre approche n’est pas parfaite, mais nous donne quand même une bonne idée de quels joueurs ont été de “bons choix”. Sidney Crosby apparait comme un “bon choix” (fiouuu). On pourrait débattre que Bobby Ryan est un “bon choix”, mais Anze Kopitar ne serait pas d’accord 😉. Si vous vous demandez pour TJ Oshie, il a été doublé par Paul Stastny en 2ème ronde. Est-ce que je considère Paul Stastny meilleur que TJ Oshie, pas nécéssairement, mais force est d’admettre que le premier a fait près de 200 points de plus que le second.

Maintenant, voyons voir quelles équipes ont réalisé le plus grand nombre de “bons choix” selon l’approche que nous proposons. Puisque certains “bons choix” pourraient être considérés meilleurs que d’autres “bons choix”, j’ai pris le soin d’ajouter le nombre de points réalisés par ces joueurs dans le graphique ci-dessous.

ggplot(
  data = dt_good_picks[!is.na(good_pick), .(count = .N, nb_points = sum(skater_points, na.rm = T)), .(team_drafted, good_pick)][good_pick == TRUE],
  mapping = aes(
    x = count,
    y = reorder(as.factor(team_drafted), count),
    fill = nb_points
  )
) +
  geom_col() +
  scale_fill_continuous("Nombre de pts réalisés par les bons choix") +
  labs(
    title = "Nombre de 'bons choix' réalisés par équipe (et leurs points)",
    subtitle = "Saisons 2005 à 2015 (saison régulière seulement)",
    x = "Nombre de 'bon choix'"
  )

On peut voir que certaines équipes ont eu plus de flair que d’autres. Les Blue Jackets de Columbus semblent se démarquer, regardons leurs “bons choix”:

columns_show <- c("draft_year", "draft_overall", "player_name", "skater_points", "goalie_wins")

# Bons choix des Blue Jackets
dt_good_picks[good_pick == TRUE & team_drafted == "CBJ", .SD, .SDcols = columns_show]
##     draft_year draft_overall        player_name skater_points goalie_wins
##  1:       2005            67       Kris Russell           238           0
##  2:       2006           189      Derek Dorsett           127           0
##  3:       2006            69        Steve Mason             0         205
##  4:       2007             7      Jakub Voracek           753           0
##  5:       2008           127       Matt Calvert           201           0
##  6:       2008           157       Cam Atkinson           381           0
##  7:       2009            21         John Moore           115           0
##  8:       2010             4      Ryan Johansen           446           0
##  9:       2013            89 Oliver Bjorkstrand           146           0
## 10:       2013            14      Alex Wennberg           208           0
## 11:       2015             8      Zach Werenski           174           0

Il y a quand même quelques bons choix. Par contre, certains de ces joueurs ont peut-être profiter des failles de notre approche (Derek Dorsett ou John Moore par exemple). Je suis curieux de jeter un coup d’oeil à l’Avalanche du Colorado, qui ont realisé un peu moins de “bons choix”.

# Bons choix de l'Avalanche
dt_good_picks[good_pick == TRUE & team_drafted == "COL", .SD, .SDcols = columns_show]
##    draft_year draft_overall       player_name skater_points goalie_wins
## 1:       2005            44      Paul Stastny           759           0
## 2:       2007            14 Kevin Shattenkirk           387           0
## 3:       2009            33     Ryan O'Reilly           574           0
## 4:       2009             3      Matt Duchene           640           0
## 5:       2009            64      Tyson Barrie           360           0
## 6:       2011             2 Gabriel Landeskog           485           0
## 7:       2013             1  Nathan MacKinnon           510           0
## 8:       2015            10    Mikko Rantanen           261           0

Un peu moins de “bons choix” que les Blue Jackets, mais ceux-ci semblent avoir eu un impact bien plus grand. On peut dire que l’Avalanche a su profiter de leurs hauts choix au repêchage …

Finalement, aviez-vous réussi à deviner quels étaient les 5 “bons choix” de notre Sainte-Flanelle?

# Bons choix des Canadiens
dt_good_picks[good_pick == TRUE & team_drafted == "MTL", .SD, .SDcols = columns_show]
##    draft_year draft_overall       player_name skater_points goalie_wins
## 1:       2005             5       Carey Price             0         353
## 2:       2005           200  Sergei Kostitsyn           176           0
## 3:       2007            43       P.K. Subban           430           0
## 4:       2007            22    Max Pacioretty           567           0
## 5:       2010           147 Brendan Gallagher           343           0

Pas trop mal, mais sur 10 années de repêchages, on aurait bien aimé avoir quelques “bons choix” de plus (surtout en première ronde) …

Conclusion

Pour conclure cet article, je dois vous avouer que je continue de croire que le repêchage est une science inexacte. Il est difficile de tirer des conclusions évidentes étant donné les nombreuses composantes à prendre en compte. Sans entrer trop dans les détails, il y a le nombre de choix et les positions des joueurs repêchés. Certains joueurs font moins de points, mais apportent une composante de plus à une équipe, comme le leadership ou même l’aspect défensif. Toutefois, selon mes analyses, voici les équipes que je considère comme les grands “gagnants” et “perdants” ainsi que pourquoi.

Les grands “gagnants” 👍

  • Avalanche du Colorodo: On peut voir leurs “bons choix” plus haut. Ils ont bien saisi leurs chances avec leurs choix de 1ère ronde, considérant qu’ils n’en ont eu que 9 alors que la moyenne de la ligue se situe à 11.
  • Bruins de Boston: Arrivant au 26ème rang pour le nombre de choix, ils sont pourtant au 6ème rang pour les points récoltés par les joueurs repêchés. Ils sont également dans le premier tiers pour le nombre de “bons choix”.
  • Penguins de Pittsburg: Ils arrivent au dernier rang dans la ligue pour le nombre de choix au total (avec 66 choix), mais ils ont fait une bonne utilisation de leurs choix. On peut les voir assez haut pour les points récoltés (12ème rang) et pour les victores des gardiens (9ème rang).
  • Kings de Los Angeles: Certes, ils ont eu beaucoup de choix au total (86), mais ils ont su répondre à l’appel dans la majorité des facettes: matchs (1er rang), points (2ème rang) et victoires des gardiens (2ème rang).

Les grands “perdants” 👎

  • Sabres de Buffalo: Ils sont au 5ème rang pour le nombre de choix au total, (dont 13 en 1ère ronde), mais ils arrivent relativement loin dans le classement pour le nombre de points récoltés (21ème rang) ou pour les victoires des gardiens (19ème rang). Ils sont également en bas de peloton pour le nombre de “bons choix”.
  • Jets de Winnipeg (et Atlanta): Ils sont au-dessus de la moyenne pour le nombre de choix total (83) et nombre de choix de première ronde (12). Ils se retrouvent en bas de classement pour la majorité des métriques: matchs (26ème rang), points (24ème rang). Seul point positif, les gardiens.
  • Canucks de Vancouver: Même s’ils ont eu peu de choix au total (68), ils n’ont pas su tirer leur épingle du jeu, et ce, dans aucune catégorie. Avec 12 choix de 1ère ronde, versus une moyenne de 11 dans la ligue, on aurait pu s’attendre à de meilleures performances. Le nombre de matchs joués par leurs joueurs repêchés est catastrophique …
  • Coyotes de l’Arizona (et Phoenix): On ne peut pas dire que leur performance est “désastreuse”, mais étant l’équipe avec le plus de choix de premières rondes (16), je me serais attendu à mieux.

Mention honorable pour les “mal-aimés” Oilers d’Edmonton. Ils ont certainement eu beaucoup de choix “faciles”, mais ils arrivent quand même au premier rang pour le nombre de points.