Introduction.
This research examines the question: Are grenade-weapon users and
winning teams related in CS:GO competitive matches? Understanding
whether they are related is very important to competitive teams and
players who must maximize tactical preparation and resource allocation
in a match. In CS:GO, grenades are used for different strategic purposes
like area denial, damage, and tactic deployment, so their proper use may
be due to round win. The data-set utilized in the analysis is CS:GO
Competitive Matchmaking Data on Kaggle (https://www.kaggle.com/datasets/skihikingkevin/csgo-matchmaking-damage).
The whole data-set is comprised of complete data related to competitive
CS:GO matches, including actions by players, damage incidents, and round
outcome. For analysis, we employ some important variables: round_num
(round number 1-30+) to search for timing pattern, grenade_type (type of
grenade utilized e.g., HE grenade, flash bang, smoke), damage (damage
caused by grenades), side (side of team - Terrorist or
Counter-Terrorist), round_winner (team that won the round), map (map
name to search for map-specific strategy), match_id (unique match ID),
and player_id (player ID). These variables enable us to examine how the
employment of grenades is related to winning rounds based on situation
and team composition.
Dataset Import Summary
| Total Rows |
5246458 |
| Total Columns |
23 |
| File Status |
Successfully Loaded |
Sample of Raw Data
| esea_match_13770997.dem |
1 |
153.1602 |
Animal Style |
NA |
7.65612e+16 |
NA |
CounterTerrorist |
NA |
0 |
0 |
TRUE |
B |
NA |
Smoke |
0 |
NA |
-1618.1460 |
-66.00259 |
-949.8569 |
-340.3019 |
NA |
NA |
| esea_match_13770997.dem |
2 |
184.7945 |
Hentai Hooligans |
Animal Style |
7.65612e+16 |
7.65612e+16 |
Terrorist |
CounterTerrorist |
70 |
0 |
FALSE |
NA |
Generic |
HE |
0 |
0 |
-1719.9040 |
-2357.64700 |
-2774.6650 |
-1603.9430 |
-2741.25 |
-1523.163 |
| esea_match_13770997.dem |
2 |
186.8617 |
Animal Style |
NA |
7.65612e+16 |
NA |
CounterTerrorist |
NA |
0 |
0 |
FALSE |
NA |
NA |
HE |
0 |
NA |
-1036.3520 |
492.16760 |
-466.8676 |
-356.9641 |
NA |
NA |
| esea_match_13770997.dem |
2 |
187.1122 |
Animal Style |
NA |
7.65612e+16 |
NA |
CounterTerrorist |
NA |
0 |
0 |
FALSE |
NA |
NA |
HE |
0 |
NA |
-855.0770 |
438.69090 |
-459.0147 |
-543.8581 |
NA |
NA |
| esea_match_13770997.dem |
2 |
191.0587 |
Hentai Hooligans |
NA |
7.65612e+16 |
NA |
Terrorist |
NA |
0 |
0 |
FALSE |
NA |
NA |
Molotov |
0 |
NA |
-2617.4900 |
-1832.40700 |
-2743.5610 |
-927.2995 |
NA |
NA |
| esea_match_13770997.dem |
2 |
191.4502 |
Animal Style |
NA |
7.65612e+16 |
NA |
CounterTerrorist |
NA |
0 |
0 |
FALSE |
NA |
NA |
Smoke |
0 |
NA |
-854.7708 |
427.48650 |
-490.5282 |
-652.8984 |
NA |
NA |
# Comprehensive data cleaning process
csgo_cleaned <- csgo_data %>%
# Remove rows with missing critical variables
filter(!is.na(hp_dmg), !is.na(att_side), !is.na(vic_side),
!is.na(round), !is.na(file)) %>%
# filter for realistic damage values (CS:GO damage typically 0-100)
filter(hp_dmg >= 0 & hp_dmg <= 100) %>%
# remove rounds with very few events (likely incomplete data)
group_by(file, round) %>%
filter(n() >= 3) %>%
ungroup() %>%
# team side names
mutate(
att_side_clean = case_when(
str_detect(tolower(att_side), "terror") ~ "Terrorist",
str_detect(tolower(att_side), "ct|counter") ~ "Counter-Terrorist",
TRUE ~ as.character(att_side)
),
vic_side_clean = case_when(
str_detect(tolower(vic_side), "terror") ~ "Terrorist",
str_detect(tolower(vic_side), "ct|counter") ~ "Counter-Terrorist",
TRUE ~ as.character(vic_side)
)
) %>%
# keep only standard team sides
filter(att_side_clean %in% c("Terrorist", "Counter-Terrorist"),
vic_side_clean %in% c("Terrorist", "Counter-Terrorist"))
# create cleaning results summary
cleaning_results <- data.frame(
Dataset = c("Original", "After Cleaning", "Rows Removed"),
Count = c(
nrow(csgo_data),
nrow(csgo_cleaned),
nrow(csgo_data) - nrow(csgo_cleaned)
),
Percentage = c(
100.0,
round(nrow(csgo_cleaned) / nrow(csgo_data) * 100, 1),
round((nrow(csgo_data) - nrow(csgo_cleaned)) / nrow(csgo_data) * 100, 1)
)
)
kable(cleaning_results,
caption = "Data Cleaning Summary",
col.names = c("Dataset", "Row Count", "Percentage (%)"))
Data Cleaning Summary
| Original |
5246458 |
100.0 |
| After Cleaning |
714847 |
13.6 |
| Rows Removed |
4531611 |
86.4 |
# show sample of cleaned data
head(csgo_cleaned %>% select(file, round, att_side_clean, vic_side_clean, hp_dmg)) %>%
kable(caption = "Sample of Cleaned Data")
Sample of Cleaned Data
| esea_match_13770997.dem |
4 |
Terrorist |
Terrorist |
1 |
| esea_match_13770997.dem |
4 |
Terrorist |
Terrorist |
1 |
| esea_match_13770997.dem |
4 |
Terrorist |
Terrorist |
2 |
| esea_match_13770997.dem |
4 |
Terrorist |
Terrorist |
2 |
| esea_match_13770997.dem |
4 |
Terrorist |
Terrorist |
3 |
| esea_match_13770997.dem |
4 |
Terrorist |
Terrorist |
3 |
Dataset Overview Statistics
| 714847 |
14851 |
63 |
10.84 |
98 |
1 |
Sample Data Structure
| esea_match_13770997.dem |
4 |
363.9355 |
Animal Style |
Hentai Hooligans |
7.65612e+16 |
7.65612e+16 |
CounterTerrorist |
Terrorist |
1 |
0 |
FALSE |
NA |
Generic |
Incendiary |
0 |
0 |
-2339.552 |
-358.022 |
-1823.104 |
-2271.759 |
-1872.836 |
-2442.808 |
Terrorist |
Terrorist |
| esea_match_13770997.dem |
4 |
363.9355 |
Animal Style |
Hentai Hooligans |
7.65612e+16 |
7.65612e+16 |
CounterTerrorist |
Terrorist |
1 |
0 |
FALSE |
NA |
Generic |
Incendiary |
0 |
0 |
-2339.552 |
-358.022 |
-1823.104 |
-2271.759 |
-1713.419 |
-2356.086 |
Terrorist |
Terrorist |
| esea_match_13770997.dem |
4 |
363.9355 |
Animal Style |
Hentai Hooligans |
7.65612e+16 |
7.65612e+16 |
CounterTerrorist |
Terrorist |
2 |
0 |
FALSE |
NA |
Generic |
Incendiary |
0 |
0 |
-2339.552 |
-358.022 |
-1823.104 |
-2271.759 |
-1748.881 |
-2336.291 |
Terrorist |
Terrorist |
| esea_match_13770997.dem |
4 |
363.9355 |
Animal Style |
Hentai Hooligans |
7.65612e+16 |
7.65612e+16 |
CounterTerrorist |
Terrorist |
2 |
0 |
FALSE |
NA |
Generic |
Incendiary |
0 |
0 |
-2339.552 |
-358.022 |
-1823.104 |
-2271.759 |
-1677.455 |
-2380.632 |
Terrorist |
Terrorist |
| esea_match_13770997.dem |
4 |
363.9355 |
Animal Style |
Hentai Hooligans |
7.65612e+16 |
7.65612e+16 |
CounterTerrorist |
Terrorist |
3 |
0 |
FALSE |
NA |
Generic |
Incendiary |
0 |
0 |
-2339.552 |
-358.022 |
-1823.104 |
-2271.759 |
-1773.385 |
-2305.948 |
Terrorist |
Terrorist |
| esea_match_13770997.dem |
4 |
363.9355 |
Animal Style |
Hentai Hooligans |
7.65612e+16 |
7.65612e+16 |
CounterTerrorist |
Terrorist |
3 |
0 |
FALSE |
NA |
Generic |
Incendiary |
0 |
0 |
-2339.552 |
-358.022 |
-1823.104 |
-2271.759 |
-1674.068 |
-2380.285 |
Terrorist |
Terrorist |
Data Analysis
This data analysis utilizes, exploratory data analysis (EDA) methods
to explore the association of grenade utilization and a team’s success
in ranked CS:Go matches. We filter as well as clean the data so as to
narrow down events that are relevant when it comes to grenade
utilization, develop summary statistics so as to gain insight with
respect to grenade utilization patterns within various settings, as well
as develop visualizations so as to showcase the association of grenade
utilization frequency, functionality, as well as round outcomes. The
examination entails reviewing grenade utilization both from both sides
of a team, exploring damage patterns within various categories of
grenades, as well as reviewing correlation with respect to grenade
functionality so as to be successful within rounds.
Dataset Overview Statistics
| 714847 |
14851 |
107550 |
10.84 |
98 |
1 |
Sample Data Structure (Cleaned)
| esea_match_13770997.dem |
4 |
Terrorist |
Terrorist |
1 |
363.9355 |
| esea_match_13770997.dem |
4 |
Terrorist |
Terrorist |
1 |
363.9355 |
| esea_match_13770997.dem |
4 |
Terrorist |
Terrorist |
2 |
363.9355 |
| esea_match_13770997.dem |
4 |
Terrorist |
Terrorist |
2 |
363.9355 |
| esea_match_13770997.dem |
4 |
Terrorist |
Terrorist |
3 |
363.9355 |
| esea_match_13770997.dem |
4 |
Terrorist |
Terrorist |
3 |
363.9355 |
team_performance <- csgo_cleaned %>%
filter(hp_dmg > 0) %>%
group_by(att_side_clean, file, round) %>%
summarise(
total_damage = sum(hp_dmg, na.rm = TRUE),
damage_events = n(),
avg_damage_per_event = mean(hp_dmg, na.rm = TRUE),
max_single_damage = max(hp_dmg, na.rm = TRUE),
.groups = 'drop'
)
# summmary statistics by team side
side_summary <- team_performance %>%
group_by(att_side_clean) %>%
summarise(
rounds_analyzed = n(),
avg_total_damage = mean(total_damage),
avg_events_per_round = mean(damage_events),
avg_damage_efficiency = mean(avg_damage_per_event),
max_round_damage = max(total_damage),
.groups = 'drop'
)
kable(side_summary,
caption = "Team Performance Analysis by Side",
col.names = c("Team Side", "Rounds", "Avg Total Damage", "Avg Events/Round", "Avg Efficiency", "Max Round Damage"),
digits = 2)
Team Performance Analysis by Side
| Terrorist |
107550 |
72.06 |
6.65 |
12.79 |
629 |
damage_analysis <- team_performance %>%
mutate(
damage_category = case_when(
total_damage <= 100 ~ "Low (≤100)",
total_damage <= 300 ~ "Medium (101-300)",
total_damage <= 500 ~ "High (301-500)",
TRUE ~ "Very High (500+)"
),
efficiency_level = case_when(
avg_damage_per_event <= 25 ~ "Low Efficiency",
avg_damage_per_event <= 50 ~ "Medium Efficiency",
TRUE ~ "High Efficiency"
)
)
# Analyze distribution across categories
category_distribution <- damage_analysis %>%
group_by(damage_category) %>%
summarise(
round_count = n(),
percentage = round(n() / nrow(damage_analysis) * 100, 1),
avg_events = mean(damage_events),
avg_efficiency = mean(avg_damage_per_event),
.groups = 'drop'
)
kable(category_distribution,
caption = "Round Distribution by Damage Output Category",
col.names = c("Damage Category", "Round Count", "Percentage (%)", "Avg Events", "Avg Efficiency"),
digits = 2)
Round Distribution by Damage Output Category
| High (301-500) |
333 |
0.3 |
23.27 |
20.24 |
| Low (≤100) |
82951 |
77.1 |
5.51 |
10.73 |
| Medium (101-300) |
24259 |
22.6 |
10.29 |
19.74 |
| Very High (500+) |
7 |
0.0 |
25.29 |
25.01 |
ggplot(team_performance, aes(x = att_side_clean, y = total_damage, fill = att_side_clean)) +
geom_boxplot(alpha = 0.7, outlier.alpha = 0.3) +
stat_summary(fun = mean, geom = "point", shape = 23, size = 3, fill = "yellow") +
labs(
title = "Total Damage Distribution by Team Side",
subtitle = "Yellow diamonds indicate mean values",
x = "Team Side",
y = "Total Damage per Round",
caption = "Source: CS:GO ESEA Competitive Data"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 12),
panel.background = element_rect(fill = "#34495e"),
plot.background = element_rect(fill = "#2c3e50"),
text = element_text(color = "white"),
axis.text = element_text(color = "white")
) +
scale_fill_manual(values = c("#e74c3c", "#3498db"))

# Visualization 2: Performance efficiency analysis
efficiency_summary <- damage_analysis %>%
group_by(efficiency_level, att_side_clean) %>%
summarise(
round_count = n(),
.groups = 'drop'
)
ggplot(efficiency_summary, aes(x = efficiency_level, y = round_count, fill = att_side_clean)) +
geom_col(position = "dodge", alpha = 0.8) +
geom_text(aes(label = round_count),
position = position_dodge(width = 0.9),
vjust = -0.5, size = 3, color = "white") +
labs(
title = "Round Distribution by Damage Efficiency",
subtitle = "Comparison between Terrorist and Counter-Terrorist performance",
x = "Damage Efficiency Level",
y = "Number of Rounds",
fill = "Team Side",
caption = "Source: CS:GO ESEA Competitive Data"
) +
theme_minimal() +
theme(
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 12),
axis.text.x = element_text(angle = 45, hjust = 1),
panel.background = element_rect(fill = "#34495e"),
plot.background = element_rect(fill = "#2c3e50"),
text = element_text(color = "white"),
axis.text = element_text(color = "white"),
legend.background = element_rect(fill = "#34495e")
) +
scale_fill_manual(values = c("#e74c3c", "#3498db"))

# tge correlation analysis
correlation_matrix <- team_performance %>%
select(total_damage, damage_events, avg_damage_per_event, max_single_damage) %>%
cor(use = "complete.obs") %>%
round(3)
kable(correlation_matrix,
caption = "Correlation Matrix: Key Performance Metrics")
Correlation Matrix: Key Performance Metrics
| total_damage |
1.000 |
0.542 |
0.544 |
0.673 |
| damage_events |
0.542 |
1.000 |
-0.290 |
-0.040 |
| avg_damage_per_event |
0.544 |
-0.290 |
1.000 |
0.777 |
| max_single_damage |
0.673 |
-0.040 |
0.777 |
1.000 |
# performance round analysis
performance_threshold <- quantile(team_performance$total_damage, 0.75, na.rm = TRUE)
high_performance <- team_performance %>%
filter(total_damage >= performance_threshold) %>%
group_by(att_side_clean) %>%
summarise(
elite_rounds = n(),
avg_damage = mean(total_damage),
avg_efficiency = mean(avg_damage_per_event),
avg_events = mean(damage_events),
.groups = 'drop'
)
kable(high_performance,
caption = "Elite Performance Analysis (Top 25% Damage Output)",
col.names = c("Team Side", "Elite Rounds", "Avg Damage", "Avg Efficiency", "Avg Events"),
digits = 2)
Elite Performance Analysis (Top 25% Damage Output)
| Terrorist |
27225 |
141.5 |
19.61 |
10.19 |
# the key insights
total_rounds <- nrow(team_performance)
terrorist_rounds <- sum(team_performance$att_side_clean == "Terrorist")
ct_rounds <- sum(team_performance$att_side_clean == "Counter-Terrorist")
cat("\nKey Performance Insights:")
##
## Key Performance Insights:
cat("\nTotal rounds analyzed:", total_rounds)
##
## Total rounds analyzed: 107550
cat("\nTerrorist rounds:", terrorist_rounds, "(", round(terrorist_rounds/total_rounds*100, 1), "%)")
##
## Terrorist rounds: 107550 ( 100 %)
cat("\nCounter-Terrorist rounds:", ct_rounds, "(", round(ct_rounds/total_rounds*100, 1), "%)")
##
## Counter-Terrorist rounds: 0 ( 0 %)