import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import textwrap
from plotnine import *TidyTuesday dataset of September 23, 2025
fide_ratings_august = pd.read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2025/2025-09-23/fide_ratings_august.csv')
fide_ratings_september = pd.read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2025/2025-09-23/fide_ratings_september.csv')fide_ratings_august| id | name | fed | sex | title | wtitle | otitle | foa | rating | games | k | bday | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 53707043 | A Darshil | IND | M | NaN | NaN | NaN | NaN | 1412 | 4 | 40 | 2013 |
| 1 | 53200465 | A F M Ehteshamul, Hoque (tuhin | BAN | M | NaN | NaN | NaN | NaN | 1797 | 0 | 40 | 1977 |
| 2 | 5716365 | A Hamid, Harman | MAS | M | NaN | NaN | NaN | NaN | 1552 | 0 | 20 | 1970 |
| 3 | 53200553 | A I Sabbir | BAN | M | NaN | NaN | NaN | NaN | 1607 | 0 | 40 | 1995 |
| 4 | 5045886 | A K, Kalshyan | IND | M | NaN | NaN | NaN | NaN | 1747 | 0 | 20 | 1964 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 201010 | 4600410 | Zysk, Robert | GER | M | IM | NaN | NaN | NaN | 2373 | 0 | 10 | 1966 |
| 201011 | 1141589 | Zysko, Jan | POL | M | NaN | NaN | NaN | NaN | 2129 | 6 | 20 | 1990 |
| 201012 | 80411533 | Zyskowski, Jean-Luc | FRA | M | NaN | NaN | NaN | NaN | 1517 | 6 | 20 | 1972 |
| 201013 | 21836060 | Zyto, Karol | POL | M | NaN | NaN | NaN | NaN | 1748 | 0 | 20 | 2006 |
| 201014 | 55812104 | Zyuryunina, Tatiana | RUS | F | NaN | NaN | NaN | NaN | 1416 | 0 | 40 | 2013 |
201015 rows × 12 columns
fide_Aug_fed_titles = (
fide_ratings_august[fide_ratings_august['title'].notna()]
.groupby(['fed','sex'])
.count()
.loc[lambda x: x['id']>100]
.sort_values(by='id', ascending=False)
.reset_index()
)
fide_Aug_fed_titles| fed | sex | id | name | title | wtitle | otitle | foa | rating | games | k | bday | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | GER | M | 902 | 902 | 902 | 0 | 29 | 0 | 902 | 902 | 902 | 902 |
| 1 | ESP | M | 646 | 646 | 646 | 0 | 45 | 0 | 646 | 646 | 646 | 646 |
| 2 | FRA | M | 404 | 404 | 404 | 0 | 8 | 0 | 404 | 404 | 404 | 404 |
| 3 | RUS | M | 375 | 375 | 375 | 0 | 3 | 0 | 375 | 375 | 375 | 375 |
| 4 | SRB | M | 360 | 360 | 360 | 0 | 24 | 0 | 360 | 360 | 360 | 360 |
| 5 | IND | M | 349 | 349 | 349 | 0 | 24 | 0 | 349 | 349 | 349 | 349 |
| 6 | USA | M | 344 | 344 | 344 | 0 | 14 | 0 | 344 | 344 | 344 | 344 |
| 7 | HUN | M | 338 | 338 | 338 | 0 | 29 | 0 | 338 | 338 | 338 | 338 |
| 8 | POL | M | 304 | 304 | 304 | 0 | 5 | 0 | 304 | 304 | 304 | 304 |
| 9 | NED | M | 289 | 289 | 289 | 0 | 6 | 0 | 289 | 289 | 289 | 289 |
| 10 | CZE | M | 282 | 282 | 282 | 0 | 12 | 0 | 282 | 282 | 282 | 282 |
| 11 | CRO | M | 196 | 196 | 196 | 0 | 17 | 0 | 196 | 196 | 196 | 196 |
| 12 | AUT | M | 186 | 186 | 186 | 0 | 6 | 0 | 186 | 186 | 186 | 186 |
| 13 | ISR | M | 175 | 175 | 175 | 0 | 8 | 0 | 175 | 175 | 175 | 175 |
| 14 | ITA | M | 175 | 175 | 175 | 0 | 7 | 0 | 175 | 175 | 175 | 175 |
| 15 | UKR | M | 169 | 169 | 169 | 0 | 3 | 0 | 169 | 169 | 169 | 169 |
| 16 | TUR | M | 159 | 159 | 159 | 0 | 5 | 0 | 159 | 159 | 159 | 159 |
| 17 | SWE | M | 155 | 155 | 155 | 0 | 7 | 0 | 155 | 155 | 155 | 155 |
| 18 | ENG | M | 152 | 152 | 152 | 0 | 6 | 0 | 152 | 152 | 152 | 152 |
| 19 | ROU | M | 150 | 150 | 150 | 0 | 8 | 0 | 150 | 150 | 150 | 150 |
| 20 | NOR | M | 133 | 133 | 133 | 0 | 5 | 0 | 133 | 133 | 133 | 133 |
| 21 | ARG | M | 133 | 133 | 133 | 0 | 10 | 0 | 133 | 133 | 133 | 133 |
| 22 | AUS | M | 123 | 123 | 123 | 0 | 7 | 0 | 123 | 123 | 123 | 123 |
| 23 | IND | F | 123 | 123 | 123 | 122 | 7 | 0 | 123 | 123 | 123 | 123 |
| 24 | FID | M | 122 | 122 | 122 | 0 | 6 | 0 | 122 | 122 | 122 | 122 |
| 25 | GRE | M | 115 | 115 | 115 | 0 | 9 | 0 | 115 | 115 | 115 | 115 |
| 26 | DEN | M | 115 | 115 | 115 | 0 | 3 | 0 | 115 | 115 | 115 | 115 |
| 27 | COL | M | 114 | 114 | 114 | 0 | 9 | 0 | 114 | 114 | 114 | 114 |
| 28 | MEX | M | 112 | 112 | 112 | 0 | 3 | 0 | 112 | 112 | 112 | 112 |
| 29 | CUB | M | 111 | 111 | 111 | 0 | 11 | 0 | 111 | 111 | 111 | 111 |
| 30 | SUI | M | 108 | 108 | 108 | 0 | 7 | 0 | 108 | 108 | 108 | 108 |
| 31 | BRA | M | 107 | 107 | 107 | 0 | 8 | 0 | 107 | 107 | 107 | 107 |
| 32 | SVK | M | 104 | 104 | 104 | 0 | 7 | 0 | 104 | 104 | 104 | 104 |
| 33 | GER | F | 103 | 103 | 103 | 102 | 6 | 0 | 103 | 103 | 103 | 103 |
fig,ax = plt.subplots(figsize=(6,8))
sns.scatterplot(data=fide_Aug_fed_titles, x='id', y='fed', hue='sex')
# add fed label to points
for i, row in fide_Aug_fed_titles.iterrows():
plt.text(row['id']+10, row['fed'], row['fed'], fontsize=12, ha='left', va='center')
sns.despine()
plt.show()
fide_Aug_fed_titles['symbol'] = fide_Aug_fed_titles['sex'].apply(
lambda s: '♔' if s == 'M' else '♕'
)
fide_Aug_fed_titles| fed | sex | id | name | title | wtitle | otitle | foa | rating | games | k | bday | symbol | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | GER | M | 902 | 902 | 902 | 0 | 29 | 0 | 902 | 902 | 902 | 902 | ♔ |
| 1 | ESP | M | 646 | 646 | 646 | 0 | 45 | 0 | 646 | 646 | 646 | 646 | ♔ |
| 2 | FRA | M | 404 | 404 | 404 | 0 | 8 | 0 | 404 | 404 | 404 | 404 | ♔ |
| 3 | RUS | M | 375 | 375 | 375 | 0 | 3 | 0 | 375 | 375 | 375 | 375 | ♔ |
| 4 | SRB | M | 360 | 360 | 360 | 0 | 24 | 0 | 360 | 360 | 360 | 360 | ♔ |
| 5 | IND | M | 349 | 349 | 349 | 0 | 24 | 0 | 349 | 349 | 349 | 349 | ♔ |
| 6 | USA | M | 344 | 344 | 344 | 0 | 14 | 0 | 344 | 344 | 344 | 344 | ♔ |
| 7 | HUN | M | 338 | 338 | 338 | 0 | 29 | 0 | 338 | 338 | 338 | 338 | ♔ |
| 8 | POL | M | 304 | 304 | 304 | 0 | 5 | 0 | 304 | 304 | 304 | 304 | ♔ |
| 9 | NED | M | 289 | 289 | 289 | 0 | 6 | 0 | 289 | 289 | 289 | 289 | ♔ |
| 10 | CZE | M | 282 | 282 | 282 | 0 | 12 | 0 | 282 | 282 | 282 | 282 | ♔ |
| 11 | CRO | M | 196 | 196 | 196 | 0 | 17 | 0 | 196 | 196 | 196 | 196 | ♔ |
| 12 | AUT | M | 186 | 186 | 186 | 0 | 6 | 0 | 186 | 186 | 186 | 186 | ♔ |
| 13 | ISR | M | 175 | 175 | 175 | 0 | 8 | 0 | 175 | 175 | 175 | 175 | ♔ |
| 14 | ITA | M | 175 | 175 | 175 | 0 | 7 | 0 | 175 | 175 | 175 | 175 | ♔ |
| 15 | UKR | M | 169 | 169 | 169 | 0 | 3 | 0 | 169 | 169 | 169 | 169 | ♔ |
| 16 | TUR | M | 159 | 159 | 159 | 0 | 5 | 0 | 159 | 159 | 159 | 159 | ♔ |
| 17 | SWE | M | 155 | 155 | 155 | 0 | 7 | 0 | 155 | 155 | 155 | 155 | ♔ |
| 18 | ENG | M | 152 | 152 | 152 | 0 | 6 | 0 | 152 | 152 | 152 | 152 | ♔ |
| 19 | ROU | M | 150 | 150 | 150 | 0 | 8 | 0 | 150 | 150 | 150 | 150 | ♔ |
| 20 | NOR | M | 133 | 133 | 133 | 0 | 5 | 0 | 133 | 133 | 133 | 133 | ♔ |
| 21 | ARG | M | 133 | 133 | 133 | 0 | 10 | 0 | 133 | 133 | 133 | 133 | ♔ |
| 22 | AUS | M | 123 | 123 | 123 | 0 | 7 | 0 | 123 | 123 | 123 | 123 | ♔ |
| 23 | IND | F | 123 | 123 | 123 | 122 | 7 | 0 | 123 | 123 | 123 | 123 | ♕ |
| 24 | FID | M | 122 | 122 | 122 | 0 | 6 | 0 | 122 | 122 | 122 | 122 | ♔ |
| 25 | GRE | M | 115 | 115 | 115 | 0 | 9 | 0 | 115 | 115 | 115 | 115 | ♔ |
| 26 | DEN | M | 115 | 115 | 115 | 0 | 3 | 0 | 115 | 115 | 115 | 115 | ♔ |
| 27 | COL | M | 114 | 114 | 114 | 0 | 9 | 0 | 114 | 114 | 114 | 114 | ♔ |
| 28 | MEX | M | 112 | 112 | 112 | 0 | 3 | 0 | 112 | 112 | 112 | 112 | ♔ |
| 29 | CUB | M | 111 | 111 | 111 | 0 | 11 | 0 | 111 | 111 | 111 | 111 | ♔ |
| 30 | SUI | M | 108 | 108 | 108 | 0 | 7 | 0 | 108 | 108 | 108 | 108 | ♔ |
| 31 | BRA | M | 107 | 107 | 107 | 0 | 8 | 0 | 107 | 107 | 107 | 107 | ♔ |
| 32 | SVK | M | 104 | 104 | 104 | 0 | 7 | 0 | 104 | 104 | 104 | 104 | ♔ |
| 33 | GER | F | 103 | 103 | 103 | 102 | 6 | 0 | 103 | 103 | 103 | 103 | ♕ |
unique_fed_order = fide_Aug_fed_titles['fed'].drop_duplicates()[::-1].tolist()
fide_Aug_fed_titles['fed'] = pd.Categorical(
fide_Aug_fed_titles['fed'],
categories=unique_fed_order,
ordered=True
)
fide_Aug_fed_titles['fed_label'] = fide_Aug_fed_titles.apply(
lambda row: f"{row['fed']} ({row['id']})", axis=1
)
fed_list = ['IND', 'GER', 'ESP', 'FRA', 'CRO', 'SVK']
#label_df = fide_Aug_fed_titles[fide_Aug_fed_titles['fed'].isin(fed_list)].copy()
label_df = fide_Aug_fed_titles.copy()
label_df['label'] = label_df.apply(lambda row: f'({row.id})', axis=1)
wrapped_title = textwrap.fill("Number of male (♔) and female (♕) chess players with titles in August 2025. Countries with more than 100 titled players are shown.", \
width=35)
(
ggplot(fide_Aug_fed_titles, aes(x='id', y='fed')) +
# geom_point(size=2) +
geom_text(aes(label='symbol'), size=15, va='center', color='#FFFFFF', show_legend=False) +
geom_text(aes(label='fed', color='sex'), nudge_x=20, size=10, ha='left', va='center',\
show_legend=False, family='monospace') +
geom_text(data=label_df, mapping=aes(label='label', color='sex'), nudge_x=65, \
size=9, va='center', ha='left', family='monospace', show_legend=False) +
annotate('text', x=325, y=10, label=wrapped_title, color='#EEEEEE', \
size=14, ha='left', va='center') +
theme_minimal() +
scale_color_manual(values={'M':'skyblue', 'F':'pink'}) +
# labs(x='Number of players with titles') +
xlim(0, 985) +
theme(figure_size=(6, 6),
axis_title_y=element_blank(),
axis_text_y=element_blank(),
axis_ticks_major_y=element_blank(),
axis_ticks_minor_y=element_blank(),
panel_grid_major_y=element_blank(),
axis_title_x=element_blank(),
axis_text_x=element_blank(),
axis_ticks_major_x=element_blank(),
axis_ticks_minor_x=element_blank(),
panel_grid_major_x=element_blank(),
panel_grid_minor_x=element_blank(),
plot_background=element_rect(fill='#2E2E2E'),
panel_background=element_rect(fill='#2E2E2E')
)
)#.save('chess_ratings.png', width=6, height=6, dpi=300)df_sept_mod = (
fide_ratings_september[(fide_ratings_september['rating']>2000) &
(fide_ratings_september['games']>0)]
.groupby("fed")
.obj
# .loc[lambda x: x['id']>100]
# .loc[lambda x: x['fed']=="IND"]
# .sort_values(by='id', ascending=False)
# change bday to datetime
.assign(bday=lambda x: pd.to_datetime(x['bday'], format='%Y'))
.assign(age=lambda x: 2025 - x['bday'].dt.year)
)
df_sept_mod| id | name | fed | sex | title | wtitle | otitle | foa | rating | games | k | bday | age | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 57 | 33362041 | Aadik Theophane Lenin | IND | M | NaN | NaN | NaN | AIM | 2108 | 9 | 40 | 2012-01-01 | 13 |
| 77 | 25678191 | Aaditya Dhingra | IND | M | IM | NaN | NaN | NaN | 2391 | 17 | 10 | 2006-01-01 | 19 |
| 127 | 25644394 | Aakash G | IND | M | FM | NaN | NaN | NaN | 2349 | 17 | 20 | 2010-01-01 | 15 |
| 131 | 25033220 | Aakash Sharadchandra, Dalvi | IND | M | IM | NaN | NaN | NaN | 2420 | 18 | 10 | 2002-01-01 | 23 |
| 137 | 510726 | Aalto, Patrik | FIN | M | FM | NaN | NaN | NaN | 2296 | 9 | 20 | 2004-01-01 | 21 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 203110 | 393770 | Zvolensky, David | CZE | M | NaN | NaN | NaN | NaN | 2157 | 18 | 20 | 2002-01-01 | 23 |
| 203124 | 325511 | Zwardon, Vojtech | CZE | M | IM | NaN | NaN | NaN | 2431 | 10 | 10 | 1990-01-01 | 35 |
| 203127 | 1332252 | Zweifel, Richard | SUI | M | CM | NaN | NaN | NaN | 2196 | 1 | 20 | 1973-01-01 | 52 |
| 203145 | 1036432 | Zwirs, Nico | NED | M | IM | NaN | NaN | NaN | 2432 | 9 | 10 | 1994-01-01 | 31 |
| 203163 | 4200292 | Zygouris, Hristos | GRE | M | NaN | NaN | NaN | NaN | 2194 | 9 | 20 | 1974-01-01 | 51 |
7581 rows × 13 columns
sns.histplot(data=df_sept_mod, x='age', bins=20, hue='sex')