Skip to contents

Let’s try to replicate the results shown in the tweet below.

We start by identifying the events that he mentioned. (“Data using Group Stages and Playoffs for VCT Stage 1, 2 and LCQ. International LANs not used (due to likely average tougher competition.”)

events <- load_valorant("events")
events_na2022 <- events |> 
  inner_join(get_all_region_names(), by = "regionId") |> 
  filter(regionName == "North America" & grepl("2022", name)) |> 
  distinct(event_id = id, event_name = name)
events_na2022
#>   event_id                                                 event_name
#> 1     1740           VCT 2022 - North America - Last Chance Qualifier
#> 2     1490    VCT North America 2022 - Stage 2 Challengers - Playoffs
#> 3     1461 VCT North America 2022 - Stage 2 Challengers - Group Stage
#> 4     1286  VCT North America 2022 - Stage 1 Challengers - Main Event
#> 5     1199 VCT North America 2022 - Stage 1 Challengers - Group Stage

And then we grab all series played at those events.

series <- load_valorant("series")

series_na2022 <- series |> 
  rename(event_id = eventId, start_date = startDate) |> 
  inner_join(
    events_na2022,
    by = "event_id"
  ) |> 
  unnest_wider(c(team1, team2), names_sep = "_") |>
  select(
    series_id = id, 
    event_id, 
    team1_id,
    team1_name,
    team2_id,
    team2_name
  )
series_na2022
#> # A tibble: 100 × 6
#>    series_id event_id team1_id team1_name    team2_id team2_name       
#>        <int>    <int>    <int> <chr>            <int> <chr>            
#>  1     34293     1740     5201 The Guard          305 100 Thieves      
#>  2     34294     1740      617 FaZe Clan          305 100 Thieves      
#>  3     34269     1740      305 100 Thieves        141 Cloud9           
#>  4     34132     1740     5201 The Guard          617 FaZe Clan        
#>  5     34080     1740      141 Cloud9             116 NRG Esports      
#>  6     34079     1740      305 100 Thieves        388 Sentinels        
#>  7     34032     1740     1448 Evil Geniuses      116 NRG Esports      
#>  8     34030     1740      617 FaZe Clan          305 100 Thieves      
#>  9     34031     1740      388 Sentinels         7510 Shopify Rebellion
#> 10     34029     1740     5201 The Guard          141 Cloud9           
#> # ℹ 90 more rows

And all matches in those series.

set_names_from_id_element <- function(x) {
  match_ids <- x |> map_int(~pluck(.x, "id"))
  x |> set_names(match_ids)
}
matches <- load_valorant("matches") |> set_names_from_id_element()
matches_na2022 <- matches[names(matches) %in% as.character(series_na2022$series_id)]

Before we move on, we should stop and make some helpful “mappings” for IDs for series, matches, players, and teams.

series_player_mapping_na2022 <- matches_na2022 |> 
  map_dfr(
    ~pluck(.x, "playerStats"),
    .id = "series_id"
  ) |> 
  distinct(series_id, team_number = teamNumber, player_id = playerId) |> 
  mutate(across(series_id, as.integer))

map_pluck_matches <- function(matches, element) {
  matches |> map_int(~pluck(.x, element))
}

series_team_mapping_na2022 <- tibble(
  series_id = matches_na2022 |> map_pluck_matches("id"),
  `1` = matches_na2022 |> map_pluck_matches("team1Id"),
  `2` = matches_na2022 |> map_pluck_matches("team2Id")
) |> 
  pivot_longer(
    -series_id,
    names_to = "team_number",
    values_to = "team_id"
  ) |> 
  mutate(
    across(team_number, as.integer)
  )

series_team_player_mapping_na2022 <- series_player_mapping_na2022 |> 
  inner_join(
    series_team_mapping_na2022 |> 
      transmute(series_id, team_number, team_id), 
    by = c("series_id", "team_number")
  ) |> 
  select(-team_number)

series_match_mapping_na2022 <- matches_na2022 |> 
  map_dfr(
    ~tibble(match_id = pluck(.x, "matches", "id")), 
    .id = "series_id"
  ) |> 
  mutate(
    across(series_id, as.integer)
  )

Next, we grab the round details of all the matches, including round events and load-outs.

## the names for matches are series ids
## the names for match details are match ids (individual maps between two teams)
match_details <- load_valorant("match_details") |> set_names_from_id_element()
match_details_na2022 <- match_details[names(match_details) %in% as.character(series_match_mapping_na2022$match_id)]
cleanly_pluck_from_match_details <- function(match_details, element) {
  match_details |> 
    map_dfr(\(.x) pluck(.x, element), .id = "match_id") |> 
    as_tibble() |> 
    mutate(
      across(match_id, as.integer)
    )
}

match_events_na2022 <- cleanly_pluck_from_match_details(
  match_details_na2022, 
  "events"
)
econ_na2022 <- cleanly_pluck_from_match_details(
  match_details_na2022, 
  "economies"
)

Then, we create a dataframe of weapon kills, combining the match details with the various mappings that we created before.

players <- load_valorant("players")
teams <- get_all_team_names()
weapon_ids <- get_all_weapon_names() |>
  select(
    weapon_id = weaponId, 
    weapon = weaponName, 
    weapon_category = weaponCategory
  )

weapon_kills_na2022 <- match_events_na2022 |> 
  filter(!is.na(killId), damageType == "weapon") |> 
  select(
    match_id,
    round_id = roundId,
    round_number = roundNumber,
    kill_id = killId,
    killer_id = playerId, 
    victim_id = referencePlayerId,
    killer_weapon_id = weaponId
  ) |> 
  ## data doesn"t list victim"s actual weapon, so we use what they bought in their loadout
  left_join(
    econ_na2022 |> 
      select(
        match_id,
        round_id = roundId,
        victim_id = playerId, 
        victim_weapon_id = weaponId
      ),
    by = c("match_id", "round_id", "victim_id")
  ) |> 
  left_join(
    series_match_mapping_na2022,
    by = "match_id"
  ) |> 
  left_join(
    series_team_player_mapping_na2022 |> 
      select(
        series_id, 
        killer_id = player_id, 
        killer_team_id = team_id
      ),
    by = c("series_id", "killer_id")
  ) |> 
  left_join(
    series_team_player_mapping_na2022 |> 
      select(
        series_id, 
        victim_id = player_id, 
        victim_team_id = team_id
      ),
    by = c("series_id", "victim_id")
  ) |>
  left_join(
    players |> select(killer_id = id, killer_ign = ign),
    by = "killer_id"
  ) |> 
  left_join(
    players |> select(victim_id = id, victim_ign = ign),
    by = "victim_id"
  ) |> 
  left_join(
    teams |> select(killer_team_id = id, killer_team_name = name),
    by = "killer_team_id"
  ) |> 
  left_join(
    teams |> select(victim_team_id = id, victim_team_name = name),
    by = "victim_team_id"
  ) |> 
  left_join(
    weapon_ids |> rename_all(~sprintf("killer_%s", .x)),
    by = "killer_weapon_id"
  ) |> 
  left_join(
    weapon_ids |> rename_all(~sprintf("victim_%s", .x)),
    by = "victim_weapon_id"
  )
glimpse(weapon_kills_na2022)
#> Rows: 34,556
#> Columns: 19
#> $ match_id               <int> 76753, 76753, 76753, 76753, 76753, 76753, 76753…
#> $ round_id               <int> 1196159, 1196159, 1196159, 1196159, 1196159, 11…
#> $ round_number           <int> 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3,…
#> $ kill_id                <int> 9730768, 9730772, 9730771, 9730769, 9730770, 97…
#> $ killer_id              <int> 1012, 269, 763, 1012, 1012, 122, 4141, 4141, 26…
#> $ victim_id              <int> 4141, 2551, 1091, 11412, 1804, 1804, 1012, 763,…
#> $ killer_weapon_id       <dbl> 11, 12, 12, 11, 11, 18, 18, 18, 18, 19, 19, 18,…
#> $ victim_weapon_id       <dbl> 12, 11, 11, 12, 12, 18, 18, 18, 18, 18, 18, 11,…
#> $ series_id              <int> 34293, 34293, 34293, 34293, 34293, 34293, 34293…
#> $ killer_team_id         <int> 305, 305, 305, 305, 305, 305, 5201, 5201, 305, …
#> $ victim_team_id         <int> 5201, 5201, 5201, 5201, 5201, 5201, 305, 305, 5…
#> $ killer_ign             <chr> "bang", "Derrek", "Asuna", "bang", "bang", "ste…
#> $ victim_ign             <chr> "neT", "Sayaplayer", "JonahP", "trent", "valyn"…
#> $ killer_team_name       <chr> "100 Thieves", "100 Thieves", "100 Thieves", "1…
#> $ victim_team_name       <chr> "The Guard", "The Guard", "The Guard", "The Gua…
#> $ killer_weapon          <chr> "classic", "ghost", "ghost", "classic", "classi…
#> $ killer_weapon_category <chr> "sidearm", "sidearm", "sidearm", "sidearm", "si…
#> $ victim_weapon          <chr> "ghost", "classic", "classic", "ghost", "ghost"…
#> $ victim_weapon_category <chr> "sidearm", "sidearm", "sidearm", "sidearm", "si…

Next, we count up how many kills and deaths individual players had in their Phantom/Vandal vs. Phantom/Vandal scenarios.

rifle_rifle_win_rates_by_player_na2022 <- bind_rows(
  weapon_kills_na2022 |> 
    mutate(
      ign = killer_ign, 
      player_id = killer_id, 
      is_killer = TRUE
    ),
  weapon_kills_na2022 |> 
    mutate(
      ign = victim_ign, 
      player_id = victim_id,
      is_killer = FALSE
    )
) |> 
  ## could due ALL rifles for real for real
  # filter(
  #   killer_weapon_category == "rifle" &
  #   victim_weapon_category == "rifle"
  # ) |>
  filter(
    (killer_weapon %in% c("phantom", "vandal")) &
      (victim_weapon %in% c("phantom", "vandal"))
  ) |>
  count(player_id, ign, is_killer, sort = TRUE) |> 
  group_by(player_id, ign) |> 
  mutate(total = sum(n), prop = n / total) |> 
  ungroup() |> 
  filter(is_killer) |>
  select(-is_killer) |> 
  arrange(desc(prop))
rifle_rifle_win_rates_by_player_na2022
#> # A tibble: 85 × 5
#>    player_id ign           n total  prop
#>        <int> <chr>     <int> <int> <dbl>
#>  1       394 Poach        12    20 0.6  
#>  2      3016 koalanoob    70   122 0.574
#>  3      1802 Cryocells   209   367 0.569
#>  4     11412 trent       344   614 0.560
#>  5      1780 NiSMO       122   218 0.560
#>  6       789 dicey       199   361 0.551
#>  7      3798 Zander      149   272 0.548
#>  8      1804 valyn       336   616 0.545
#>  9      3352 penny        99   182 0.544
#> 10      7025 wippie      135   250 0.54 
#> # ℹ 75 more rows

Before we look at the results, let’s make one final mapping to link players to the last teams that they played with. This adds a bit of extra information for our final results.

last_player_team_mapping_na2022 <- series_team_player_mapping_na2022 |> 
  group_by(player_id) |> 
  slice_max(series_id, n = 1, with_ties = FALSE) |> 
  ungroup() |> 
  select(player_id, team_id) |> 
  left_join(
    players |> select(player_id = id, ign),
    by = "player_id"
  ) |> 
  left_join(
    teams |> 
      select(team_id = id, last_team = name),
    by = "team_id"
  )

Finally, the results. (We use a minimum of 200 fights like Weltis.) The top 10 are very close to those that were posted.

top_rifle_rifle_win_rates_na2022 <- rifle_rifle_win_rates_by_player_na2022 |> 
  filter(total >= 200) |> 
  inner_join(
    last_player_team_mapping_na2022 |> select(player_id, last_team),
    by = "player_id"
  ) |> 
  transmute(
    rank = row_number(desc(prop)),
    ign,
    last_team,
    n,
    total,
    prop
  )
head(top_rifle_rifle_win_rates_na2022, 20)
#> # A tibble: 20 × 6
#>     rank ign       last_team               n total  prop
#>    <int> <chr>     <chr>               <int> <int> <dbl>
#>  1     1 Cryocells XSET (Inactive)       209   367 0.569
#>  2     2 trent     The Guard             344   614 0.560
#>  3     3 NiSMO     Ghost Gaming          122   218 0.560
#>  4     4 dicey     FaZe Clan             199   361 0.551
#>  5     5 Zander    Version1 (Inactive)   149   272 0.548
#>  6     6 valyn     The Guard             336   616 0.545
#>  7     7 wippie    Version1 (Inactive)   135   250 0.54 
#>  8     8 Derrek    100 Thieves           287   535 0.536
#>  9     9 BcJ       XSET (Inactive)       279   522 0.534
#> 10    10 yay       OpTic Gaming          156   294 0.531
#> 11    11 supamen   FaZe Clan             360   679 0.530
#> 12    12 crashies  OpTic Gaming          266   502 0.530
#> 13    13 SicK      Sentinels             169   319 0.530
#> 14    14 TenZ      Sentinels             195   371 0.526
#> 15    15 neT       The Guard             281   538 0.522
#> 16    16 Moose     Shopify Rebellion     272   523 0.520
#> 17    17 Marved    OpTic Gaming          307   591 0.519
#> 18    18 ShahZaM   Sentinels             267   515 0.518
#> 19    19 bang      100 Thieves           298   575 0.518
#> 20    20 skuba     Knights               115   222 0.518

Here’s a direct comparison of our lists.

Player Weltis Rank Weltis % valorantr Rank valorantr %
Cryo 1 58 1 57
NiSMO 2 58 3 56
trent 3 58 2 56
Derrek 4 55 8 54
dicey 5 54 4 55
yay 6 54 10 53
valyn 7 53 6 55
TenZ 8 53 14 53
eeiu 9 53 27 51
s0ms 10 53 21 51