Chess Game Analysis by R

Author: Justin Zhang, Central European University

Dataset Description

This dataset contains records of chess games, including information about:

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)

Summary of Analysis Goals

In this project, we analyze a chess game dataset to answer key questions:

1. Game Outcomes: How do most games end?

  • Count the number of games for each victory type (checkmate, resignation, timeout).
  • Visualization: Bar chart of victory type distribution.

2️. Player Ratings & Game Length: Do higher-rated players play longer games?

  • Compare white player ratings vs. number of moves per game.
  • Visualization: Scatter plot of rating vs. game length.

3️. Winning Advantage: Does White or Black win more often?

  • Count the number of wins for White vs. Black.
  • Visualization: Bar chart comparing number of wins by player color.

4. Short vs. Long Games: How do they differ?

  • Filter out short games (<30 moves) and analyze differences in player ratings.
  • Visualization: Boxplot comparing player ratings in short vs. long games.

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.

Clean Script

# 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"

Filter rows

# Keep games that lasted more than 30 moves
chess_filtered <- chess_data[turns > 30]

Aggregate rows

# 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]

1.Bar Chart: Distribution of Victory Status

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")

Checkmate and resignation are the most common endings, while timeouts and draws occur less frequently.

2.Histogram: Number of Moves per Game

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")

Most games last between 20-50 moves, suggesting that early blunders or quick victories are common.

A long tail exists, meaning some games are much longer, likely due to endgame play or evenly matched players.

3.Boxplot: Player Ratings (White vs. Black)

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")

There is no extreme rating difference between winning and losing players, suggesting that games are fairly balanced.

4.Scatter Plot: Player Ratings vs. Number of Moves

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")

Higher-rated players tend to have slightly longer games.

5.Histogram;White Player ratings

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")

Most players have ratings around 1400-1800, which aligns with intermediate to advanced skill levels.

6.Ratings of White and Black Players

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")

The rating of black and white are similar, which means it is chosen randomly.

7.Winners by color

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.

2.1 Merge datasets: Filter Data & Create a “Removed” Group

# 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)

2.2 Apply a Theme

# 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"
  )

2.3 Define the axis and plot titles

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"
  )

2.4 Use a color palette from <colorbrewer2.org>

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"
  )

2.5 Use multiple geom layers on the same plot

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 a nutshell

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.