Люди, интересующиеся шахматами и разбирающиеся в этом виде спорта, с высокой вероятностью задумывались о начальных комбинациях ходов, которым стоит научиться в самом начале своего пути. В этой практической части мы проанализируем датасет из 20058 наблюдений с сайта lichess.org, который домтупен на платформе kaggle.com

Структура датасета

id - идентификатор игры

rated - оцениваемая игра или нет

created_at - время начала игры

last_move_at - время последнего хода

turns - количество ходов

victory_status - статус игры

winner - победитель (Ч/Б)

white_id - идентификатор игрока, играющего белыми

white_rating - рейтинг игрока (Б)

black_id - идентификатор игрока, играющего черными

black_rating - рейтинг игрока (Ч)

moves - ходы в стандартных шахматных обозначениях

opening_eco - стандартный код для каждого типа дебюта

opening_name - название дебюта

opening_ply - количество ходов на дебютных этапах

Исследовательский вопрос: С чего стоит начинать игру типичному игроку в равной (c сопоставимыми рейтингами) игре?

Разобьем ответ на этот вопрос на две части, где нулевой пункт про распределения рейтингов черных и белых:

  1. Очистка данных (выравнивание уровней игроков в интервале 100 пунктов) и изучение распределений

  2. Определение стратегии по названиям из столбца opening_name. То есть предстоит извлечь базовое название стратегии, исходя из паттерна написания этих стратегий.

Подсказка: В Excel можно воспользоваться авто-заполнением, которое здорово упростит вам работу

Можно посмотреть небольшое видео по этой ссылке

Давайте загрузим датасет games.csv

library(dplyr)
library(ggplot2)
library(ggpubr)
library(tidyr)
chess = read.csv('games.csv')

Исходя из структуры датасета, можно с уверенностью утверждать о чистоте данных, так как все переменные представлены в тех типах, которые соответствуют их природе. То есть числовые - это числовые, а строковые - это строковые.

chess %>% str()
## 'data.frame':    20058 obs. of  16 variables:
##  $ id            : chr  "TZJHLljE" "l1NXvwaE" "mIICvQHh" "kWKvrqYL" ...
##  $ rated         : chr  "FALSE" "TRUE" "TRUE" "TRUE" ...
##  $ created_at    : num  1.5e+12 1.5e+12 1.5e+12 1.5e+12 1.5e+12 ...
##  $ last_move_at  : num  1.5e+12 1.5e+12 1.5e+12 1.5e+12 1.5e+12 ...
##  $ turns         : int  13 16 61 61 95 5 33 9 66 119 ...
##  $ victory_status: chr  "outoftime" "resign" "mate" "mate" ...
##  $ winner        : chr  "white" "black" "white" "white" ...
##  $ increment_code: chr  "15+2" "5+10" "5+10" "20+0" ...
##  $ white_id      : chr  "bourgris" "a-00" "ischia" "daniamurashov" ...
##  $ white_rating  : int  1500 1322 1496 1439 1523 1250 1520 1413 1439 1381 ...
##  $ black_id      : chr  "a-00" "skinnerua" "a-00" "adivanov2009" ...
##  $ black_rating  : int  1191 1261 1500 1454 1469 1002 1423 2108 1392 1209 ...
##  $ moves         : chr  "d4 d5 c4 c6 cxd5 e6 dxe6 fxe6 Nf3 Bb4+ Nc3 Ba5 Bf4" "d4 Nc6 e4 e5 f4 f6 dxe5 fxe5 fxe5 Nxe5 Qd4 Nc6 Qe5+ Nxe5 c4 Bb4+" "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 "| __truncated__ "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 Qd"| __truncated__ ...
##  $ opening_eco   : chr  "D10" "B00" "C20" "D02" ...
##  $ opening_name  : chr  "Slav Defense: Exchange Variation" "Nimzowitsch Defense: Kennedy Variation" "King's Pawn Game: Leonardis Variation" "Queen's Pawn Game: Zukertort Variation" ...
##  $ opening_ply   : int  5 4 3 3 5 4 10 5 6 4 ...

Предупреждение: мы не ожидали от вас построения графиков и дополнительного статистического анализа - это just for fun и демонстрации интересных статистических трюков в R.

Часть 1

В силу того, что нам предстоить строить рекомендации для игроков с более-менее равными уровнями, первым делом, нужно изучить распределения и связь между рейтингами черных и белых игроков.

Распределение рейтингов черных игроков

Построив нашу первую гистограмму, можем заметить, что наблюдения распределены в похожем на “колокол” виде - это можно увидеть на светло-голубом графике ниже.

chess %>% ggplot(aes(x = black_rating)) + geom_histogram()

Красный колокол - это так называемое нормальное распределение, которое вы сможете изучить на курсах статистики, которые были упомянуты в первых двух подборках полезных бесплатных курсов.

Исходя из этого графика, распределение рейтингов черных близко к нормальному

ggdensity(data=chess, x = "black_rating", fill = "light blue")  +
scale_x_continuous(limits = c(0, 3000)) +
stat_overlay_normal_density(color = "red", linetype = "dashed") + theme_bw() + theme(text = element_text(size=12), axis.text.x = element_text(angle=90, hjust=1), plot.background = element_rect())

Распределение рейтингов белых игроков

Проделав аналогичную процедуру сможем сделать аналогичные выводы и относительно белых

chess %>% ggplot(aes(x = white_rating )) + geom_histogram()

ggdensity(data=chess, x = "white_rating", fill = "light blue")  +
scale_x_continuous(limits = c(0, 3000)) +
stat_overlay_normal_density(color = "red", linetype = "dashed") + theme_bw() + theme(text = element_text(size=12), axis.text.x = element_text(angle=90, hjust=1), plot.background = element_rect())

Часть 2

Давайте посмотрим на совместное распределение рейтингов черных и белых - то есть построим диаграмму рассеяния со значениями рейтингов черных на оси У, а белых на оси Х. Заметим, что в данных присутствует некоторый шум, а точнее - есть игры, где игрок с рейтингом 1000 противостоял игроку с рейтингом в 2500, но мы хотим несколько упростить задачу, сравняв уровни игроков для получения несмещенных результатов.

ggplot(data = chess,
       aes(x = white_rating,
           y = black_rating)) +
  geom_point() +
  geom_smooth() +
  labs(
       x = "White Player Rating",
       y = "Black Player Rating")

Очистим данные, оставив их в интервале 100 пунктов и заметим, что гарфик заметно изменился в лучшую сторону, а в новом датасете similar_chess осталось 9162 наблюдений - это меньше половины от исходного числа.

similar_chess <- chess %>%
  filter(white_rating >= black_rating - 100, white_rating <= black_rating + 100)
ggplot(data = similar_chess,
       aes(x = white_rating,
           y = black_rating)) +
  geom_point() +
  geom_smooth() +
  labs(
       x = "White Player Rating",
       y = "Black Player Rating")

Часть 3 - Последняя часть

Давайте начнем с распределения выигрышных игр среди игроков двух сторон. На первый взляд, белые и черные играют почти одинаково - это хорошо, так как мы можем убедиться в справедливости (в равности уровней) встреч. Но на этом наше исследование не заканчивается!

ggplot(data = similar_chess,
       aes(x = winner,
           fill = winner)) +
  scale_fill_manual(values = c("#000000", "#D3D2C7", "#EEEED2")) +
  geom_bar() +
  labs(title = "Fair Game Outcomes",
       x = "Winner",
       y = "Wins") +
  scale_x_discrete(limits = c("white","black","draw")) +
  theme(legend.position = "none") +
  geom_text(aes(label = ..count..), stat = "count", vjust = 1.5, color = c("#EEEED2", "#000000", "#000000"))

Перейдём к самой сложной части, которая не ожидалась от вас именно в таком углубленном формате - вам можно было пределить лучшую дебютную стратегию с помощью изучения структуры датасета

Но давайте мы покажем вам более интересный поход к анализу

Стратегии, как оказалось, представлены в данном датасете в огромном множестве вариаций, но нам нужны основные названия этих стратегий - то, что до “:”. Например, Sicilian Defense, Pawn Game и другие.

Кроме этого, стоит ограничить множество рассматриваемых стратегий до пяти самых лучших, так как нас не интересуют те, что не в топе.

similar_chess_clean <- similar_chess %>%
  separate(opening_name, c("basic_opening", "variation_detail"), sep = ":") %>%
  separate(basic_opening, c("basic_opening_no_numerical", "numerical_characters"), sep = "#") %>% 
  separate(basic_opening_no_numerical, c("basic_opening_clean", "basic_variation"), sep = "\\|") %>% 
  group_by(winner, basic_opening_clean) %>%
  summarize(number_of_wins=n()) %>% 
  arrange(desc(number_of_wins))
popular_openings <- similar_chess_clean %>%
  group_by(basic_opening_clean) %>%
  summarize(number_of_wins = sum(number_of_wins)) %>%
  arrange(desc(number_of_wins))

most_popular_openings <- top_n(popular_openings, 5, number_of_wins) %>% 
  arrange(desc(number_of_wins))

Построим рядстолбчатую диаграмму с пятью лучшими стратегиями по количеству выигранных игр

ggplot(data = most_popular_openings,
       aes(x = basic_opening_clean, 
           y = number_of_wins,
           fill = basic_opening_clean)) +
  geom_bar(stat = "identity") +
  labs(title = "Самые популярные стратегии",
       x = 'Основная стратегия',
       y = "Количество игр") +
  scale_x_discrete(limits = c("Sicilian Defense","French Defense", "Queen's Pawn Game", "Italian Game", "King's Pawn Game")) +
  theme(legend.position = "none") +
  geom_text(aes(label = number_of_wins), stat = "identity", vjust = 1.5)

Теперь построим диаграмму с показателями белых

most_popular_openings = top_n(similar_chess_clean, 5, number_of_wins)
ggplot(data = most_popular_openings %>%
         filter(winner == "white"),
       aes(x = basic_opening_clean,
           y = number_of_wins,
           fill = basic_opening_clean)) +
  geom_bar(stat = "identity") +
  scale_x_discrete(limits = c("Sicilian Defense","French Defense", "Queen's Pawn Game", "Italian Game", "King's Pawn Game")) +
  labs(title = "Самые популярные стратегии белых",
       x = "Основная стратегия",
       y = "Количество выигрышей") +
  theme(legend.position = "none") +
  geom_text(aes(label = number_of_wins), stat = "identity", vjust = 1.5)

А теперь построим диаграмму с показателями черных

ggplot(data = most_popular_openings %>%
          filter(winner == "black"),
       aes(x = basic_opening_clean, 
           y = number_of_wins, 
           fill = basic_opening_clean)) +
  geom_bar(stat = "identity") +
  scale_x_discrete(limits = c("Sicilian Defense","French Defense", "Queen's Pawn Game", "King's Pawn Game", "Italian Game")) +
  labs(title = "Самые популярные стратегии черных",
       x = "Основная стратегия",
       y = "Количество выигрышей") +
  theme(legend.position = "none") +
  geom_text(aes(label = number_of_wins), stat = "identity", vjust = 1.5)

Первый вывод Независимо от выбранного цвета, наблюдается абсолютная доминация со стороны игроков, придерживающихся Sicilian Defense

Абсолютные величины - это хорошо, но нас, аналитиков, интересуют также относительные величины (пропорции).

most_popular_openings_winrate_stage <- most_popular_openings %>% 
  group_by(basic_opening_clean) %>%
  summarize(total_games = sum(number_of_wins))

most_popular_openings_winrate <- merge(most_popular_openings,
                                       most_popular_openings_winrate_stage,
                                  by = "basic_opening_clean") %>% 
  mutate(win_rate = 100 * (number_of_wins / total_games)) %>%
  arrange(desc(win_rate)) %>% 
  filter(basic_opening_clean != "Ruy Lopez")

Второй вывод Можем заметить, что Sicilian Defense в большей пропорции доминируется черными игроками, но эта разница нелика (всего 1,27%)

ggplot(data = most_popular_openings_winrate,
       aes(x = basic_opening_clean,
           y = win_rate,
           fill = winner)) +
  geom_bar(stat = "identity") +
  scale_fill_manual(values = c("#000000", "#D3D2C7", "#EEEED2")) +
  scale_x_discrete(limits = c("Sicilian Defense","French Defense", "Queen's Pawn Game", "King's Pawn Game", "Italian Game")) +
  
  geom_text(aes(label = paste0(round(win_rate, 2), "%")), stat = "identity", position = position_stack(vjust = 0.5),
            color = c("#000000", "#EEEED2", "#EEEED2",
                       "#EEEED2", "#EEEED2", "#000000", 
                       "#000000", "#000000", "#EEEED2", 
                       "#000000", "#000000", "#000000",
                       "#000000", "#000000", "#000000"))

Ответ на исследовательский вопрос

Если бы ко мне обратились бы за советом о дебютной стратегии в честной игре, то я смело смог бы рекомендовать Sicilian Defense, как минимум, на платформе lichess.org в силу наблюдаемой нами абсолютной доминации этой стратегии над всеми другими.