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
= pd.read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2025/2025-09-23/fide_ratings_august.csv')
fide_ratings_august = pd.read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2025/2025-09-23/fide_ratings_september.csv') fide_ratings_september
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 'title'].notna()]
fide_ratings_august[fide_ratings_august['fed','sex'])
.groupby([
.count()lambda x: x['id']>100]
.loc[='id', ascending=False)
.sort_values(by
.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 |
= plt.subplots(figsize=(6,8))
fig,ax =fide_Aug_fed_titles, x='id', y='fed', hue='sex')
sns.scatterplot(data# add fed label to points
for i, row in fide_Aug_fed_titles.iterrows():
'id']+10, row['fed'], row['fed'], fontsize=12, ha='left', va='center')
plt.text(row[
sns.despine() plt.show()
'symbol'] = fide_Aug_fed_titles['sex'].apply(
fide_Aug_fed_titles[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 | ♕ |
= fide_Aug_fed_titles['fed'].drop_duplicates()[::-1].tolist()
unique_fed_order 'fed'] = pd.Categorical(
fide_Aug_fed_titles['fed'],
fide_Aug_fed_titles[=unique_fed_order,
categories=True
ordered
)'fed_label'] = fide_Aug_fed_titles.apply(
fide_Aug_fed_titles[lambda row: f"{row['fed']} ({row['id']})", axis=1
)= ['IND', 'GER', 'ESP', 'FRA', 'CRO', 'SVK']
fed_list #label_df = fide_Aug_fed_titles[fide_Aug_fed_titles['fed'].isin(fed_list)].copy()
= fide_Aug_fed_titles.copy()
label_df 'label'] = label_df.apply(lambda row: f'({row.id})', axis=1)
label_df[= textwrap.fill("Number of male (♔) and female (♕) chess players with titles in August 2025. Countries with more than 100 titled players are shown.", \
wrapped_title =35)
width
(='id', y='fed')) +
ggplot(fide_Aug_fed_titles, aes(x# geom_point(size=2) +
='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',\
geom_text(aes(label=False, family='monospace') +
show_legend=label_df, mapping=aes(label='label', color='sex'), nudge_x=65, \
geom_text(data=9, va='center', ha='left', family='monospace', show_legend=False) +
size'text', x=325, y=10, label=wrapped_title, color='#EEEEEE', \
annotate(=14, ha='left', va='center') +
size+
theme_minimal() ={'M':'skyblue', 'F':'pink'}) +
scale_color_manual(values# labs(x='Number of players with titles') +
0, 985) +
xlim(=(6, 6),
theme(figure_size=element_blank(),
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_rect(fill='#2E2E2E'),
plot_background=element_rect(fill='#2E2E2E')
panel_background
)#.save('chess_ratings.png', width=6, height=6, dpi=300) )
= (
df_sept_mod 'rating']>2000) &
fide_ratings_september[(fide_ratings_september['games']>0)]
(fide_ratings_september["fed")
.groupby(
.obj# .loc[lambda x: x['id']>100]
# .loc[lambda x: x['fed']=="IND"]
# .sort_values(by='id', ascending=False)
# change bday to datetime
=lambda x: pd.to_datetime(x['bday'], format='%Y'))
.assign(bday=lambda x: 2025 - x['bday'].dt.year)
.assign(age
)
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
=df_sept_mod, x='age', bins=20, hue='sex') sns.histplot(data