1.Game details: Unique game ID, number of moves, victory type
2.Players: White and Black player IDs, their ratings
3.Outcome: Who won the game and how (e.g., checkmate, resignation,
timeout)
Each row represents a single chess game, with statistics about how it
was played and the ratings of the players involved.(e.g.,start and end
time,moves)
In this project, we analyze a chess game dataset to answer key questions:
These analyses provide insights into player performance, game length, and winning patterns in chess matches.
# Read directly from GitHub
chess <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2024/2024-10-01/chess.csv')
## Rows: 20058 Columns: 16
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (9): game_id, victory_status, winner, time_increment, white_id, black_id...
## dbl (6): start_time, end_time, turns, white_rating, black_rating, opening_ply
## lgl (1): rated
##
## ℹ 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.
# Source: Clean data provided by Kaggle Mitchell J.
# https://www.kaggle.com/datasets/datasnaek/chess/data
library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.4.2
## Warning: package 'ggplot2' was built under R version 4.4.2
## Warning: package 'tibble' was built under R version 4.4.2
## Warning: package 'tidyr' was built under R version 4.4.2
## Warning: package 'readr' was built under R version 4.4.2
## Warning: package 'purrr' was built under R version 4.4.2
## Warning: package 'dplyr' was built under R version 4.4.2
## Warning: package 'forcats' was built under R version 4.4.2
## Warning: package 'lubridate' was built under R version 4.4.2
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.1 ✔ tibble 3.2.1
## ✔ lubridate 1.9.4 ✔ tidyr 1.3.1
## ✔ purrr 1.0.2
## ── 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
chess <- readr::read_csv("C:/Users/Administrator/Downloads/chess.csv")
## Rows: 20058 Columns: 16
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (9): game_id, victory_status, winner, time_increment, white_id, black_id...
## dbl (6): start_time, end_time, turns, white_rating, black_rating, opening_ply
## lgl (1): rated
##
## ℹ 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.
# Data saved from Kaggle as "chess_game_dataset/chess_games.csv"
chess <- readr::read_csv("C:/Users/Administrator/Downloads/chess.csv") %>%
rename("id" = "game_id",
"created_at" = "start_time",
"last_move_at" = "end_time",
"increment_code" = "time_increment")
## Rows: 20058 Columns: 16
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (9): game_id, victory_status, winner, time_increment, white_id, black_id...
## dbl (6): start_time, end_time, turns, white_rating, black_rating, opening_ply
## lgl (1): rated
##
## ℹ 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.
library(data.table)
## Warning: package 'data.table' was built under R version 4.4.2
##
## Attaching package: 'data.table'
## The following objects are masked from 'package:lubridate':
##
## hour, isoweek, mday, minute, month, quarter, second, wday, week,
## yday, year
## The following objects are masked from 'package:dplyr':
##
## between, first, last
## The following object is masked from 'package:purrr':
##
## transpose
library(ggplot2)
# Read the dataset
chess_data <- fread("chess.csv")
# View the first few rows to understand the structure
head(chess_data)
## game_id rated start_time end_time turns victory_status winner
## <char> <lgcl> <i64> <i64> <int> <char> <char>
## 1: TZJHLljE FALSE 1504210000000 1504210000000 13 outoftime white
## 2: l1NXvwaE TRUE 1504130000000 1504130000000 16 resign black
## 3: mIICvQHh TRUE 1504130000000 1504130000000 61 mate white
## 4: kWKvrqYL TRUE 1504110000000 1504110000000 61 mate white
## 5: 9tXo1AUZ TRUE 1504030000000 1504030000000 95 mate white
## 6: MsoDV9wj FALSE 1504240000000 1504240000000 5 draw draw
## time_increment white_id white_rating black_id black_rating
## <char> <char> <int> <char> <int>
## 1: 15+2 bourgris 1500 a-00 1191
## 2: 5+10 a-00 1322 skinnerua 1261
## 3: 5+10 ischia 1496 a-00 1500
## 4: 20+0 daniamurashov 1439 adivanov2009 1454
## 5: 30+3 nik221107 1523 adivanov2009 1469
## 6: 10+0 trelynn17 1250 franklin14532 1002
## moves
## <char>
## 1: d4 d5 c4 c6 cxd5 e6 dxe6 fxe6 Nf3 Bb4+ Nc3 Ba5 Bf4
## 2: d4 Nc6 e4 e5 f4 f6 dxe5 fxe5 fxe5 Nxe5 Qd4 Nc6 Qe5+ Nxe5 c4 Bb4+
## 3: e4 e5 d3 d6 Be3 c6 Be2 b5 Nd2 a5 a4 c5 axb5 Nc6 bxc6 Ra6 Nc4 a4 c3 a3 Nxa3 Rxa3 Rxa3 c4 dxc4 d5 cxd5 Qxd5 exd5 Be6 Ra8+ Ke7 Bc5+ Kf6 Bxf8 Kg6 Bxg7 Kxg7 dxe6 Kh6 exf7 Nf6 Rxh8 Nh5 Bxh5 Kg5 Rxh7 Kf5 Qf3+ Ke6 Bg4+ Kd6 Rh6+ Kc5 Qe3+ Kb5 c4+ Kb4 Qc3+ Ka4 Bd1#
## 4: d4 d5 Nf3 Bf5 Nc3 Nf6 Bf4 Ng4 e3 Nc6 Be2 Qd7 O-O O-O-O Nb5 Nb4 Rc1 Nxa2 Ra1 Nb4 Nxa7+ Kb8 Nb5 Bxc2 Bxc7+ Kc8 Qd2 Qc6 Na7+ Kd7 Nxc6 bxc6 Bxd8 Kxd8 Qxb4 e5 Qb8+ Ke7 dxe5 Be4 Ra7+ Ke6 Qe8+ Kf5 Qxf7+ Nf6 Nh4+ Kg5 g3 Ng4 Qf4+ Kh5 Qxg4+ Kh6 Qf4+ g5 Qf6+ Bg6 Nxg6 Bg7 Qxg7#
## 5: e4 e5 Nf3 d6 d4 Nc6 d5 Nb4 a3 Na6 Nc3 Be7 b4 Nf6 Bg5 O-O b5 Nc5 Bxf6 Bxf6 Bd3 Qd7 O-O Nxd3 Qxd3 c6 a4 cxd5 Nxd5 Qe6 Nc7 Qg4 Nxa8 Bd7 Nc7 Rc8 Nd5 Qg6 Nxf6+ Qxf6 Rfd1 Re8 Qxd6 Bg4 Qxf6 gxf6 Rd3 Bxf3 Rxf3 Rd8 Rxf6 Kg7 Rf3 Rd2 Rg3+ Kf8 c3 Re2 f3 Rc2 Rg5 f6 Rh5 Kg7 Rd1 Kg6 Rh3 Rxc3 Rd7 Rc1+ Kf2 Rc2+ Kg3 h5 Rxb7 Kg5 Rxa7 h4+ Rxh4 Rxg2+ Kxg2 Kxh4 b6 Kg5 b7 f5 exf5 Kxf5 b8=Q e4 Rf7+ Kg5 Qg8+ Kh6 Rh7#
## 6: e4 c5 Nf3 Qa5 a3
## opening_eco opening_name opening_ply
## <char> <char> <int>
## 1: D10 Slav Defense: Exchange Variation 5
## 2: B00 Nimzowitsch Defense: Kennedy Variation 4
## 3: C20 King's Pawn Game: Leonardis Variation 3
## 4: D02 Queen's Pawn Game: Zukertort Variation 3
## 5: C41 Philidor Defense 5
## 6: B27 Sicilian Defense: Mongoose Variation 4
# Check column names
colnames(chess_data)
## [1] "game_id" "rated" "start_time" "end_time"
## [5] "turns" "victory_status" "winner" "time_increment"
## [9] "white_id" "white_rating" "black_id" "black_rating"
## [13] "moves" "opening_eco" "opening_name" "opening_ply"
# Keep games that lasted more than 30 moves
chess_filtered <- chess_data[turns > 30]
# Count the number of games for each victory type
chess_summary <- chess_data[, .N, by = victory_status]
# Calculate average player ratings grouped by winner
rating_summary <- chess_data[, .(avg_white = mean(white_rating),
avg_black = mean(black_rating)),
by = winner]
ggplot(chess_data, aes(x = victory_status, fill = victory_status)) +
geom_bar() +
theme_minimal() +
labs(title = "Distribution of Victory Status in Chess Games",
x = "Victory Status",
y = "Count") +
scale_fill_brewer(palette = "Set2")
ggplot(chess_data, aes(x = turns)) +
geom_histogram(binwidth = 10, fill = "steelblue", color = "black") +
theme_minimal() +
labs(title = "Distribution of Number of Moves per Game",
x = "Number of Moves",
y = "Frequency")
A long tail exists, meaning some games are much longer, likely due to endgame play or evenly matched players.
ggplot(chess_data, aes(x = winner, y = white_rating, fill = winner)) +
geom_boxplot() +
theme_minimal() +
labs(title = "White Player Rating by Game Outcome",
x = "Winner",
y = "White Rating") +
scale_fill_brewer(palette = "Pastel1")
ggplot(chess_data, aes(x = white_rating, y = turns, color = winner)) +
geom_point(alpha = 0.5) +
theme_minimal() +
labs(title = "Player Rating vs. Game Length",
x = "White Player Rating",
y = "Number of Moves") +
scale_color_brewer(palette = "Dark2")
ggplot(chess_data, aes(x = white_rating)) +
geom_histogram(binwidth = 50, fill = "purple", color = "black") +
theme_minimal() +
labs(title = "Distribution of White Player Ratings",
x = "White Player Rating",
y = "Frequency")
ggplot(chess_data, aes(x = white_rating, y = black_rating, color = winner)) +
geom_point(alpha = 0.5) +
theme_minimal() +
labs(title = "White Rating vs. Black Rating",
x = "White Player Rating",
y = "Black Player Rating") +
scale_color_brewer(palette = "Set1")
ggplot(chess_data, aes(x = winner, fill = winner)) +
geom_bar() +
theme_minimal() +
labs(title = "Number of Wins by Player Color",
x = "Winner",
y = "Count") +
scale_fill_brewer(palette = "Paired")
###The win rate of white is slightly higher than black.
# Convert to data.table
chess_data <- as.data.table(chess_data)
# Define filtering condition (removing short games with <30 moves)
filtered_data <- chess_data[turns > 30] # Games with more than 30 moves
removed_data <- chess_data[turns <= 30] # Games that were removed
# Add a new column indicating whether data is kept or removed
filtered_data[, dataset := "Filtered (Kept)"]
removed_data[, dataset := "Removed"]
# Combine both datasets
chess_combined <- rbind(filtered_data, removed_data)
# Custom theme for all plots
custom_theme <- theme_minimal() +
theme(
plot.title = element_text(size = 16, face = "bold", hjust = 0.5),
axis.title = element_text(size = 14),
legend.position = "top"
)
ggplot(chess_data, aes(x = victory_status, fill = victory_status)) +
geom_bar() +
scale_fill_brewer(palette = "Set2") +
custom_theme +
labs(
title = "Victory Status Distribution in Chess Games",
x = "Victory Type",
y = "Number of Games",
fill = "Victory Type"
)
ggplot(chess_data, aes(x = winner, fill = winner)) +
geom_bar() +
scale_fill_brewer(palette = "Paired") +
custom_theme +
labs(
title = "Number of Wins by Player Color",
x = "Winner",
y = "Count",
fill = "Winning Player"
)
ggplot(chess_data, aes(x = white_rating, y = turns, color = winner)) +
geom_point(alpha = 0.5) + # Scatter points
geom_smooth(method = "lm", se = FALSE, linetype = "dashed") + # Trend line
scale_color_brewer(palette = "Set1") +
custom_theme +
labs(
title = "White Rating vs. Game Length",
x = "White Player Rating",
y = "Number of Moves",
color = "Winner"
)
## `geom_smooth()` using formula = 'y ~ x'
In this project, I analyzed a chess game dataset to understand game outcomes, player ratings, and game length patterns. Our key findings show that most games end in checkmate or resignation, with White having a slight advantage. I observed that higher-rated players tend to play longer games, but game length alone is not a strong predictor of skill level.
Additionally, I filtered out short games (<30 moves) and found that these were more common among lower-rated players, suggesting that beginners often make quick blunders or early resignations. By using data visualization and statistical summaries, I gained valuable insights into how chess games unfold.