Best Time of Day for Rank Progression in Marvel Rivals

Overview

Marvel Rivals is an objective-based competitive hero shooter, where one team of 6 battles to win an objective over the opposing team. Rivals features three roles that each have different characters to choose from: Strategist, Duelist, and Vanguard. The game offers casual game play as well as competitive, with new season and half-season updates every couple of months.

My dataset includes over 500 of my own matches and tracks a wide variety of variables through each, including: competitive or casual match status, win/loss, points gained/lost (competitive), total points (competitive), current rank (competitive), rank up games (competitive), KDA (kills/deaths/assists) ratio, mvp or svp earned, hero/heroes played, main role played, type of match, map, duration in minutes, bots, and platform.

In this project, I will utilize this data to analyze how time of day affects general win rate and rank progression in competitive gameplay. Split into 4 sections of the day (morning, afternoon, evening, night), this analysis will help me to understand if there is a certain region of the day where I can play to maximize my win rate and rank progression. Additionally, this could be applied to other players’ data to see whether these time-of-day patterns can be generalized.

Data Wrangling

The dataset was manually created by tracking my own gameplay over multiple seasons using match data from tracker.gg/marvel-rivals. While the site provided raw statistics, I selected which variables to record, structured the dataset, and compiled it for analysis. Each row represents a single completed match, distinguished by their specific date and time stamp.

To analyze time-based performance, I created a new variable called time_of_day using the hour from the datetimevariable. Match data were grouped into four categories:

Morning (6:00 - 12:00)

Afternoon (12:00 - 18:00)

Evening (18:00 - 24:00)

Night (00:00 - 6:00)

Additionally, missing values in the rank_up variable (which occur for non-competitive matches) were handled using na.rm = TRUEduring summary calculations to prevent errors and ensure rank-up percentages were calculated using only competitive games.

Variables

datetime - Date and time the match was played

season - Current game season during the match

competitive - Indicates whether the match was competitive (TRUE/FALSE)

won - Indicates whether the match was won (TRUE/FALSE)

points_change - Change in rank points after the match

total_points - Total rank points after the match

current_rank - Player’s rank at the end of the match

rank_up - Indicates whether the match resulted in a rank increase (TRUE/FALSE)

kda_ratio - Kill/Death/Assist ratio recorded for the match

mvp_or_svp - Indicates whether the player received MVP or SVP recognition (TRUE/FALSE)

heroes_played - Hero or heroes used during the match

main_role - Role played during the match (DPS = Duelist, Tank = Vanguard, Support = Strategist)

match_type - Type of match played

map - Map where the match took place

match_duration - Length of the match in minutes

bots - Indicates whether bots were present in the match (TRUE/FALSE)

xbox_or_pc - Platform used to play the match

time_of_day - The time of day in which the match started (Morning, Afternoon, Evening, Night)

# Setup data
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.2.0     ✔ readr     2.1.6
✔ forcats   1.0.1     ✔ stringr   1.6.0
✔ ggplot2   4.0.2     ✔ tibble    3.3.1
✔ lubridate 1.9.5     ✔ tidyr     1.3.2
✔ purrr     1.2.1     
── 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(ggplot2)
library(gt)
library(dplyr)
library(lubridate)
rivals_data <- read_csv("rivals.csv", na = c("", "null"))
Rows: 516 Columns: 17
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (8): datetime, current_rank, heroes_played, main_role, match_type, map, ...
dbl (4): season, points_change, total_points, kda_ratio
lgl (5): competitive, won, rank_up, mvp_or_svp, bots

ℹ 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.
# Add time of day column
data_w_tod <- rivals_data |> 
  mutate(
    datetime = mdy_hm(datetime),
    hour = hour(datetime),
    time_of_day = case_when(
      hour >= 6 & hour < 12 ~ "Morning",
      hour >= 12 & hour < 18 ~ "Afternoon",
      hour >= 18 & hour < 24 ~ "Evening",
      hour >= 0 & hour < 6 ~ "Night",
    )
  )

Visualization

I have created two visualizations to compare performance across time-of-day groups.

# Create win percentage bar chart
data_w_tod |> 
  group_by(time_of_day) |> 
  summarize(win_rate = mean(won)*100) |>
  ggplot(aes(x = time_of_day, y = win_rate, fill = time_of_day)) +
  geom_col() +
  labs(
    title = "Win Percentage by Time of Day",
    x = "Time of Day",
    y = "Win %"
  )

This visualization shows the average win percentage by time of day. It was chosen to identify patterns in overall performance across different time periods.

# Create rank-up percentage bar chart
data_w_tod |> 
  filter(competitive == TRUE) |> 
  group_by(time_of_day) |> 
  summarize(rankup_rate = mean(rank_up, na.rm = TRUE)*100) |> 
  ggplot(aes(x = time_of_day, y = rankup_rate, fill = time_of_day)) +
  geom_col() +
  labs(
    title = "Competitive Rank-Up Percentage by Time of Day",
    x = "Time of Day",
    y = "Rank-Up %"
  )

This visualization shows the rank-up percentage by time of day for competitive matches. It highlights when rank progression is most likely to occur.

Tables

# Create overall win table
win_tbl <-
  data_w_tod |> 
  select(won, rank_up, time_of_day, competitive) |> 
  group_by(time_of_day) |> 
  summarise(
    win_percent = round((mean(won)*100),2),
    n_games = n()
  ) |> 
  gt() |> 
    cols_label(
    win_percent = "Win %",
    time_of_day = "Time of Day",
    n_games = "All Games",
  )
win_tbl
Time of Day Win % All Games
Afternoon 52.94 170
Evening 54.44 248
Morning 45.45 11
Night 44.83 87

This table summarizes win percentage, rank-up percentage, and total number of games for each time of day.

# Create rank-up table
rankup_tbl <-
  data_w_tod |> 
  select(won, rank_up, time_of_day, competitive) |> 
  group_by(time_of_day) |> 
  summarise(
    rankup_percent = round((mean(rank_up, na.rm = TRUE)*100),2),
    n_comp_games = sum(competitive),
  ) |> 
  gt() |> 
    cols_label(
    rankup_percent = "Rank-Up %",
    n_comp_games = "Competitive Games",
    time_of_day = "Time of Day",
  )
  rankup_tbl
Time of Day Rank-Up % Competitive Games
Afternoon 7.81 65
Evening 11.54 104
Morning NaN 0
Night 8.70 46

This table focuses on competitive matches, showing rank-up percentage and the number of competitive games for each time period.

Conclusion

This analysis shows that Evening is the most effective time of day for both overall win percentage and competitive rank-up percentage. It has the highest win percentage at 54.44% (n = 248) and the highest rank-up percentage at 11.54% (n = 104), so on the surface, it looks like the most effective time to play for both performance and rank progression.

However, it is important to note that Evening also has significantly more data than the other time periods, which could be acting as a confounding factor. With a larger number of games, the results are likely more stable, but it also makes it harder to directly compare to time periods with much smaller sample sizes. Morning especially stands out, with zero competitive games (n = 0), meaning there is no rank-up data at all for that time. Afternoon (n = 65) and Night (n = 46) have some competitive data, but still not enough to confidently compare against Evening.

Overall, while Evening appears to be the best time to play for win percentage, when it comes to rank-up percentage, the uneven distribution of games across time periods limits how strong that conclusion can be. More competitive data, particularly in the Morning and Afternoon, would be needed to make a more confident and balanced analysis.