Die Datenbasis steht nun, so dass wir in diesem Kapitel die Visualisierung der Kennzahlen gemäß unserem Fahrplan in Angriff nehmen können.
Wir nutzen dazu wieder Jupyter-Notbooks. Allerdings benötigen wir jetzt statt der Programm-Bibliothek chess Pakete aus dem Statistik- und Grafik-Bereich wie pandas und mathplotlib. Außerdem verwenden wir wieder unsere mariadb-Bibliothek für den Zugriff auf unsere Datenbank mit den Kennzahlen.
Zur Auflockerung starten wir mit einer Auswertung zur Anzahl der insgesamt gespielten Partien aller bisherigen Schachweltmeister, indem wir folgenden Code in unser Jupyter-Notebook eingeben.
Jupyter Notebook Code
import sys
import getpass
import mariadb
import pandas as pd
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore')
def connect(p_password):
global conn
global cursor
# Database connection details
db_config = {
"user": "chess_user",
"password": p_password,
"host": "localhost",
"database": "chess",
"port": 3306, # Standard port for MariaDB
}
# Establishing the connection
conn = mariadb.connect(**db_config)
# Disable autocommit
conn.autocommit = False
# Create a cursor to execute queries
cursor = conn.cursor()
def disconnect():
cursor.close()
conn.close()
def main():
passwd = None
# If password not provided, prompt securely
if not passwd:
try:
passwd = getpass.getpass(prompt="Enter database password: ")
except (KeyboardInterrupt, EOFError):
print("\nPassword input cancelled.")
sys.exit(1)
# Validate password input
if not passwd.strip():
print("Error: Database password cannot be empty.")
sys.exit(1)
# connect to database
try:
connect(passwd)
df = pd.read_sql_query("""select
case when p.name in ('Kasparov, Garry', 'Kasparov, G.') then 'Kasparov, Garry'
when p.name in ('Smyslov, Vasily', 'Smyslov, V.') then 'Smyslov, Vasily'
else p.name
end name,
count(g.id) anzahl_spiele
from
player p
join game g on
(p.id = g.black_player_id
or p.id = g.white_player_id )
where
p.name in ('Staunton, Howard', 'Morphy, Paul', 'Steinitz, Wilhelm', 'Lasker, Emanuel', 'Capablanca, Jose', 'Alekhine, Alexander', 'Euwe, Max',
'Botvinnik, Mikhail URS', 'Smyslov, Vasily', 'Smyslov, V.', 'Tal, Mikhail', 'Petrosian, Tigran V', 'Spassky, Boris V.', 'Fischer, Robert J', 'Karpov, Anatoly',
'Kasparov, Garry', 'Kasparov, G.', 'Kramnik, Vladimir', 'Anand, Viswanathan', 'Carlsen, Magnus', 'Ding, Liren', 'Gukesh D')
group by
case when p.name in ('Kasparov, Garry', 'Kasparov, G.') then 'Kasparov, Garry'
when p.name in ('Smyslov, Vasily', 'Smyslov, V.') then 'Smyslov, Vasily'
else p.name
end
order by
p.name""", conn)
print(df)
disconnect()
except Exception as e:
print(e)
try:
disconnect()
except:
pass
sys.exit(1)
if __name__ == "__main__":
main()


Das geht auch noch etwas hübscher 😉

Wir benötigen dazu lediglich ein paar zusätzliche Codezeilen.
Jupyter Notebook Code
import sys
import getpass
import mariadb
import pandas as pd
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore')
def connect(p_password):
global conn
global cursor
# Database connection details
db_config = {
"user": "chess_user",
"password": p_password,
"host": "localhost",
"database": "chess",
"port": 3306, # Standard port for MariaDB
}
# Establishing the connection
conn = mariadb.connect(**db_config)
# Disable autocommit
conn.autocommit = False
# Create a cursor to execute queries
cursor = conn.cursor()
def disconnect():
cursor.close()
conn.close()
def get_data():
df = pd.read_sql_query("""select
case when p.name in ('Kasparov, Garry', 'Kasparov, G.') then 'Kasparov, Garry'
when p.name in ('Smyslov, Vasily', 'Smyslov, V.') then 'Smyslov, Vasily'
else p.name
end 'Name',
count(g.id) 'Anzahl Spiele'
from
player p
join game g on
(p.id = g.black_player_id
or p.id = g.white_player_id )
where
p.name in ('Staunton, Howard', 'Morphy, Paul', 'Steinitz, Wilhelm', 'Lasker, Emanuel', 'Capablanca, Jose', 'Alekhine, Alexander', 'Euwe, Max',
'Botvinnik, Mikhail URS', 'Smyslov, Vasily', 'Smyslov, V.', 'Tal, Mikhail', 'Petrosian, Tigran V', 'Spassky, Boris V.', 'Fischer, Robert J', 'Karpov, Anatoly',
'Kasparov, Garry', 'Kasparov, G.', 'Kramnik, Vladimir', 'Anand, Viswanathan', 'Carlsen, Magnus', 'Ding, Liren', 'Gukesh D')
group by
case when p.name in ('Kasparov, Garry', 'Kasparov, G.') then 'Kasparov, Garry'
when p.name in ('Smyslov, Vasily', 'Smyslov, V.') then 'Smyslov, Vasily'
else p.name
end
order by
p.name""", conn)
return(df)
def visualize_data(p_df):
barplot = plt.bar(p_df['Name'], p_df['Anzahl Spiele'], color='skyblue')
plt.xlabel("Spieler")
plt.ylabel("Anzahl Spiele")
plt.ylim([0,6000])
plt.title("Anzahl Partien der Schachweltmeister")
plt.bar_label(container=barplot, rotation = 90, label_type = "edge", padding=5)
plt.xticks(rotation=90)
plt.show()
def main():
passwd = None
# If password not provided, prompt securely
if not passwd:
try:
passwd = getpass.getpass(prompt="Enter database password: ")
except (KeyboardInterrupt, EOFError):
print("\nPassword input cancelled.")
sys.exit(1)
# Validate password input
if not passwd.strip():
print("Error: Database password cannot be empty.")
sys.exit(1)
# connect to database
try:
connect(passwd)
df = get_data()
visualize_data(df)
disconnect()
except Exception as e:
print(e)
try:
disconnect()
except:
pass
sys.exit(1)
if __name__ == "__main__":
main()
Die Grafik kann sich schon sehen lassen, doch wir wollen mehr wissen. Vor allem sind wir an Ergebnissen interessiert und daher wollen wir auch die Anteile von Gewinn-, Verlust- und Remispartien sehen – hold my beer!
Das Schöne an unserer Konfiguration mit einer separaten Datenbank ist, dass wir uns die benötigten Daten immer leicht mit SQL-Abfragen beschaffen können, ohne eine Zeile Python-Code programmieren zu müssen🤗
So auch hier – wir müssen im Vergleich zur vorigen Auswertung lediglich noch die entsprechend aufgeschlüsselten Summen dazu ermitteln.
SQL-Code
select
case when p.name in ('Kasparov, Garry', 'Kasparov, G.') then 'Kasparov, Garry'
when p.name in ('Smyslov, Vasily', 'Smyslov, V.') then 'Smyslov, Vasily'
else p.name
end Name,
count(p.name) "Anzahl Spiele",
sum(case when (p.id = g.white_player_id and g.result = 1) or (p.id = g.black_player_id and g.result = 2) then 1 else 0 end) "Anzahl Gewinn",
sum(case when g.result = 3 then 1 else 0 end) "Anzahl Remis",
sum(case when (p.id = g.white_player_id and g.result = 2) or (p.id = g.black_player_id and g.result = 1) then 1 else 0 end) "Anzahl Verlust",
sum(case when g.result = 4 then 1 else 0 end) "Anzahl mit unbekanntem Ergebnis"
from
player p
join game g on
(p.id = g.black_player_id
or p.id = g.white_player_id )
where
p.name in ('Staunton, Howard', 'Morphy, Paul', 'Steinitz, Wilhelm', 'Lasker, Emanuel', 'Capablanca, Jose', 'Alekhine, Alexander', 'Euwe, Max',
'Botvinnik, Mikhail URS', 'Smyslov, Vasily', 'Smyslov, V.', 'Tal, Mikhail', 'Petrosian, Tigran V', 'Spassky, Boris V.', 'Fischer, Robert J', 'Karpov, Anatoly',
'Kasparov, Garry', 'Kasparov, G.', 'Kramnik, Vladimir', 'Anand, Viswanathan', 'Carlsen, Magnus', 'Ding, Liren', 'Gukesh D')
group by
case when p.name in ('Kasparov, Garry', 'Kasparov, G.') then 'Kasparov, Garry'
when p.name in ('Smyslov, Vasily', 'Smyslov, V.') then 'Smyslov, Vasily'
else p.name
end
order by
p.name;

Wir sehen, dass alle Partien ein reguläres Ende genommen haben, so dass wir für unsere grafische Präsentation die letzte Spalte mit lauter Nullen bei der Verwendung in unserem Jupyter Notebook wieder entfernen können.
Wir ersetzen daher das SQL in der Funktion get_data() unseres Notebooks und speichern dieses unter einem neuen Namen. Außerdem legen wir noch eine Konfigurationsdatei an, um dort unser Datenbank-Passwort zu speichern. Damit sparen wir uns die Tipparbeit bei jedem Programmstart und lesen es stattdessen unter Benutzung der load_dotenv-Bibliothek aus der Datei .env ein.
Etwas mehr Aufwand bereitet lediglich die Anpassung der Funktion visualize_data(), da wir die relativ simple Balkengrafik oben nun durch eine gestalpelte Balkengrafik ersetzen werden, die nicht nur mit der Anzahl Spiele, sondern auch mit den Werten für die Ergebnis-Anteile für jeden gestapelten Balken beschriftet sein soll. Außerdem wollen wir sinnvolle Farben für diese Anteile nutzen sowie eine Legende zur Übersicht.
Jupyter Notebook Code
import sys
import os
import getpass
import mariadb
import pandas as pd
import matplotlib.pyplot as plt
import warnings
from dotenv import load_dotenv
warnings.filterwarnings('ignore')
def connect(p_password):
global conn
global cursor
# Database connection details
db_config = {
"user": "chess_user",
"password": p_password,
"host": "localhost",
"database": "chess",
"port": 3306, # Standard port for MariaDB
}
# Establishing the connection
conn = mariadb.connect(**db_config)
# Disable autocommit
conn.autocommit = False
# Create a cursor to execute queries
cursor = conn.cursor()
def disconnect():
cursor.close()
conn.close()
def get_data():
df = pd.read_sql_query("""select
case when p.name in ('Kasparov, Garry', 'Kasparov, G.') then 'Kasparov, Garry'
when p.name in ('Smyslov, Vasily', 'Smyslov, V.') then 'Smyslov, Vasily'
else p.name
end Spieler,
cast(sum(case when (p.id = g.white_player_id and g.result = 1) or (p.id = g.black_player_id and g.result = 2) then 1 else 0 end) as unsigned) "Anzahl Gewinn",
cast(sum(case when g.result = 3 then 1 else 0 end) as unsigned) "Anzahl Remis",
cast(sum(case when (p.id = g.white_player_id and g.result = 2) or (p.id = g.black_player_id and g.result = 1) then 1 else 0 end) as unsigned) "Anzahl Verlust"
from
player p
join game g on
(p.id = g.black_player_id
or p.id = g.white_player_id )
where
p.name in ('Staunton, Howard', 'Morphy, Paul', 'Steinitz, Wilhelm', 'Lasker, Emanuel', 'Capablanca, Jose', 'Alekhine, Alexander', 'Euwe, Max',
'Botvinnik, Mikhail URS', 'Smyslov, Vasily', 'Smyslov, V.', 'Tal, Mikhail', 'Petrosian, Tigran V', 'Spassky, Boris V.', 'Fischer, Robert J', 'Karpov, Anatoly',
'Kasparov, Garry', 'Kasparov, G.', 'Kramnik, Vladimir', 'Anand, Viswanathan', 'Carlsen, Magnus', 'Ding, Liren', 'Gukesh D')
group by
case when p.name in ('Kasparov, Garry', 'Kasparov, G.') then 'Kasparov, Garry'
when p.name in ('Smyslov, Vasily', 'Smyslov, V.') then 'Smyslov, Vasily'
else p.name
end
order by
p.name,
g.result""", conn)
return(df)
def visualize_data(p_df):
color=['green', 'orange', 'red']
ax=p_df.plot(x='Spieler', kind='bar', width=.8, stacked=True, figsize=(20,18), color=color, ylabel="Anzahl Partien", title="Anzahl Partien der Schachweltmeister nach Ergebnis")
plt.bar_label(ax.containers[-1], rotation = 0, label_type = "edge", padding=6)
plt.bar_label(ax.containers[0], labels=p_df['Anzahl Gewinn'], rotation = 0, label_type = "center")
plt.bar_label(ax.containers[1], labels=p_df['Anzahl Remis'], rotation = 0, label_type = "center")
plt.bar_label(ax.containers[2], labels=p_df['Anzahl Verlust'], rotation = 0, label_type = "center")
plt.ylim([0,4300])
plt.show()
def main():
# try to get passwd from .env file
load_dotenv()
passwd = os.getenv("passwd")
# If password not provided, prompt securely
if not passwd:
try:
passwd = getpass.getpass(prompt="Enter database password: ")
except (KeyboardInterrupt, EOFError):
print("\nPassword input cancelled.")
sys.exit(1)
# Validate password input
if not passwd.strip():
print("Error: Database password cannot be empty.")
sys.exit(1)
# connect to database
try:
connect(passwd)
df = get_data()
visualize_data(df)
disconnect()
except Exception as e:
print(e)
try:
disconnect()
except:
pass
sys.exit(1)
if __name__ == "__main__":
main()

So farbenfroh sich die obige Grafik auch darstellt, so wenig hilft sie im direkten Vergleich der Spieler. Dazu müssen wir die unterschiedliche Partien-Anzahl der einzelnen Spieler in die Rechnung einbeziehen. Daher betrachten wir statt der Absolutwerte die prozentualen Anteile der Spielresultate. Außerdem sortieren wir die erhaltenen Daten nicht mehr nach Namen der Spieler, sondern absteigend nach den Ergebnissen.
Wir ermitteln die neuen prozentualen Werte sowie die Umsortierung durch moderate Anpassungen unserer SQL-Abfrage. Die Notebook-Anpassungen sind ebenfalls marginal.
Jupyter Notebook Code
import sys
import os
import getpass
import mariadb
import pandas as pd
import matplotlib.pyplot as plt
import warnings
from dotenv import load_dotenv
warnings.filterwarnings('ignore')
def connect(p_password):
global conn
global cursor
# Database connection details
db_config = {
"user": "chess_user",
"password": p_password,
"host": "localhost",
"database": "chess",
"port": 3306, # Standard port for MariaDB
}
# Establishing the connection
conn = mariadb.connect(**db_config)
# Disable autocommit
conn.autocommit = False
# Create a cursor to execute queries
cursor = conn.cursor()
def disconnect():
cursor.close()
conn.close()
def get_data():
df = pd.read_sql_query("""select
case when p.name in ('Kasparov, Garry', 'Kasparov, G.') then 'Kasparov, Garry'
when p.name in ('Smyslov, Vasily', 'Smyslov, V.') then 'Smyslov, Vasily'
else p.name
end Spieler,
round(sum(case when (p.id = g.white_player_id and g.result = 1) or (p.id = g.black_player_id and g.result = 2) then 1 else 0 end) / count(p.name) * 100, 2) "Anteil Gewinn",
round(sum(case when g.result = 3 then 1 else 0 end) / count(p.name) * 100, 2) "Anteil Remis",
round(sum(case when (p.id = g.white_player_id and g.result = 2) or (p.id = g.black_player_id and g.result = 1) then 1 else 0 end) / count(p.name) * 100, 2) "Anteil Verlust"
from
player p
join game g on
(p.id = g.black_player_id
or p.id = g.white_player_id )
where
p.name in ('Staunton, Howard', 'Morphy, Paul', 'Steinitz, Wilhelm', 'Lasker, Emanuel', 'Capablanca, Jose', 'Alekhine, Alexander', 'Euwe, Max',
'Botvinnik, Mikhail URS', 'Smyslov, Vasily', 'Smyslov, V.', 'Tal, Mikhail', 'Petrosian, Tigran V', 'Spassky, Boris V.', 'Fischer, Robert J', 'Karpov, Anatoly',
'Kasparov, Garry', 'Kasparov, G.', 'Kramnik, Vladimir', 'Anand, Viswanathan', 'Carlsen, Magnus', 'Ding, Liren', 'Gukesh D')
group by
case when p.name in ('Kasparov, Garry', 'Kasparov, G.') then 'Kasparov, Garry'
when p.name in ('Smyslov, Vasily', 'Smyslov, V.') then 'Smyslov, Vasily'
else p.name
end
order by
2 desc, 3 desc""", conn)
return(df)
def visualize_data(p_df):
color=['green', 'orange', 'red']
ax=p_df.plot(x='Spieler', kind='bar', width=.8, stacked=True, figsize=(20,18), color=color, ylabel="Anteile Resultat", title="Partie-Anteile der Schachweltmeister nach Ergebnis")
plt.bar_label(ax.containers[-1], rotation = 0, label_type = "edge", padding=6)
plt.bar_label(ax.containers[0], labels=p_df['Anteil Gewinn'], rotation = 0, label_type = "center")
plt.bar_label(ax.containers[1], labels=p_df['Anteil Remis'], rotation = 0, label_type = "center")
plt.bar_label(ax.containers[2], labels=p_df['Anteil Verlust'], rotation = 0, label_type = "center")
plt.ylim([0, 115])
plt.show()
def main():
# try to get passwd from .env file
load_dotenv()
passwd = os.getenv("passwd")
# If password not provided, prompt securely
if not passwd:
try:
passwd = getpass.getpass(prompt="Enter database password: ")
except (KeyboardInterrupt, EOFError):
print("\nPassword input cancelled.")
sys.exit(1)
# Validate password input
if not passwd.strip():
print("Error: Database password cannot be empty.")
sys.exit(1)
# connect to database
try:
connect(passwd)
df = get_data()
visualize_data(df)
disconnect()
except Exception as e:
print(e)
try:
disconnect()
except:
pass
sys.exit(1)
if __name__ == "__main__":
main()

Die Grafik ist eindrucksvoll und überraschend zugleich. Zunächst sticht die einzigartig hohe Gewinnquote von Paul Morphy ins Auge. Dass aber ein Spieler wie Howard Staunton bereits auf Platz 2 liegt, hätten wir so nicht erwartet. Im direkten Vergleich zum Drittplatzierten Wilhelm Steinitz fällt dann aber schon auf, dass der Verlustanteil von Staunton ebenfalls auf Platz 1 liegt!
Darum ist es ratsam, nicht nur die Gewinnquote ins Kalkül zu ziehen, sondern die (relative) Score-Rate, welche die Punktgewinne misst (1=Gewinn, 0,5=Remis, 0=Verlust).
Die Code-Anpassungen sind wieder rasch erledigt.
Jupyter Notebook Code
import sys
import os
import getpass
import mariadb
import pandas as pd
import matplotlib.pyplot as plt
import warnings
from dotenv import load_dotenv
warnings.filterwarnings('ignore')
def connect(p_password):
global conn
global cursor
# Database connection details
db_config = {
"user": "chess_user",
"password": p_password,
"host": "localhost",
"database": "chess",
"port": 3306, # Standard port for MariaDB
}
# Establishing the connection
conn = mariadb.connect(**db_config)
# Disable autocommit
conn.autocommit = False
# Create a cursor to execute queries
cursor = conn.cursor()
def disconnect():
cursor.close()
conn.close()
def get_data():
df = pd.read_sql_query("""select Spieler, round(score / CountGamesPlayed * 100) "Score Rate"
from (
select
case when p.name in ('Kasparov, Garry', 'Kasparov, G.') then 'Kasparov, Garry'
when p.name in ('Smyslov, Vasily', 'Smyslov, V.') then 'Smyslov, Vasily'
else p.name
end Spieler,
count(p.name) CountGamesPlayed,
sum(case when (p.id = g.white_player_id and g.result = 1) or (p.id = g.black_player_id and g.result = 2) then 1 else 0 end) + sum(case when g.result = 3 then 0.5 else 0 end) score
from
player p
join game g on
(p.id = g.black_player_id
or p.id = g.white_player_id )
where
p.name in ('Staunton, Howard', 'Morphy, Paul', 'Steinitz, Wilhelm', 'Lasker, Emanuel', 'Capablanca, Jose', 'Alekhine, Alexander', 'Euwe, Max',
'Botvinnik, Mikhail URS', 'Smyslov, Vasily', 'Smyslov, V.', 'Tal, Mikhail', 'Petrosian, Tigran V', 'Spassky, Boris V.', 'Fischer, Robert J', 'Karpov, Anatoly',
'Kasparov, Garry', 'Kasparov, G.', 'Kramnik, Vladimir', 'Anand, Viswanathan', 'Carlsen, Magnus', 'Ding, Liren', 'Gukesh D')
group by
case when p.name in ('Kasparov, Garry', 'Kasparov, G.') then 'Kasparov, Garry'
when p.name in ('Smyslov, Vasily', 'Smyslov, V.') then 'Smyslov, Vasily'
else p.name
end) t
order by
2 desc""", conn)
return(df)
def visualize_data(p_df):
color=['green', 'orange', 'red']
ax=p_df.plot(x='Spieler', kind='bar', width=.8, stacked=True, figsize=(20,18), color=color, ylabel="Score Rate", title="Score Raten der Schachweltmeister")
plt.bar_label(ax.containers[0], labels=p_df['Score Rate'], rotation = 0, label_type = "center")
plt.ylim([0, 90])
plt.show()
def main():
# try to get passwd from .env file
load_dotenv()
passwd = os.getenv("passwd")
# If password not provided, prompt securely
if not passwd:
try:
passwd = getpass.getpass(prompt="Enter database password: ")
except (KeyboardInterrupt, EOFError):
print("\nPassword input cancelled.")
sys.exit(1)
# Validate password input
if not passwd.strip():
print("Error: Database password cannot be empty.")
sys.exit(1)
# connect to database
try:
connect(passwd)
df = get_data()
visualize_data(df)
disconnect()
except Exception as e:
print(e)
try:
disconnect()
except:
pass
sys.exit(1)
if __name__ == "__main__":
main()

Was Paul Morphy betrifft, so hebt er sich mit einer eindrucksvollen Score-Rate von 83% von seinen Mitbewerbern deutlich ab. Auf den weiteren Plätzen folgen Garry Kasparov und Bobby Fischer, was wir ebenfalls so erwartet hätten. Howard Staunton hingegen fällt ins Mittelfeld zurück, liegt dabei aber immer noch knapp vor Schachgiganten wie Capablanca und Carlsen – erstaunlich genug!
Vehemenz und Hartnäckigkeit
Es kann angenommen werden, dass ein Spieler seinem Gegner um so überlegener ist, je schneller er diesen besiegt. Wir wollen diese gerade erfundene Kennzahl Vehemenz nennen. Umgekehrt soll die Kennzahl Hartnäckigkeit messen, wie lange sich ein Spieler in seinen Verlustpartien wehren kann – je länger, desto besser. Wollen wir diese Kennzahlen für unsere Spieler vergleichbar machen, müssen wir dazu die gruppierten Durchschnittswerte ermitteln und entsprechend sortieren.
Wir greifen dazu wieder zum Jupyter Notebook.
Jupyter Notebook Code
import sys
import os
import getpass
import mariadb
import pandas as pd
import matplotlib.pyplot as plt
import warnings
from dotenv import load_dotenv
warnings.filterwarnings('ignore')
def connect(p_password):
global conn
global cursor
# Database connection details
db_config = {
"user": "chess_user",
"password": p_password,
"host": "localhost",
"database": "chess",
"port": 3306, # Standard port for MariaDB
}
# Establishing the connection
conn = mariadb.connect(**db_config)
# Disable autocommit
conn.autocommit = False
# Create a cursor to execute queries
cursor = conn.cursor()
def disconnect():
cursor.close()
conn.close()
def get_data():
df = pd.read_sql_query("""select
t.spieler "Spieler",
round(avg(case when t.result = 1 then t.anzahl_halbzuege else null end)) "durchschnittliche Anzahl Halbzuege Gewinn",
round(avg(case when t.result = 2 then t.anzahl_halbzuege else null end)) "durchschnittliche Anzahl Halbzuege Verlust",
round(avg(case when t.result = 3 then t.anzahl_halbzuege else null end)) "durchschnittliche Anzahl Halbzuege Remis"
from
(
select
case
when p.name in ('Kasparov, Garry', 'Kasparov, G.') then 'Kasparov, Garry'
when p.name in ('Smyslov, Vasily', 'Smyslov, V.') then 'Smyslov, Vasily'
else p.name
end Spieler,
case
when (p.id = g.white_player_id
and g.result = 1)
or (p.id = g.black_player_id
and g.result = 2) then 1
when (p.id = g.white_player_id
and g.result = 2)
or (p.id = g.black_player_id
and g.result = 1) then 2
else 3
end result,
(
select
count(*)
from
position
where
game_id = g.id) anzahl_halbzuege
#(select avg(count(*)) from position po where po.game_id=g.id) countHalfMoves
from
player p
join game g on
(p.id = g.black_player_id
or p.id = g.white_player_id )
join position po on
po.game_id = g.id
where
p.name in ('Staunton, Howard', 'Morphy, Paul', 'Steinitz, Wilhelm', 'Lasker, Emanuel', 'Capablanca, Jose', 'Alekhine, Alexander', 'Euwe, Max',
'Botvinnik, Mikhail URS', 'Smyslov, Vasily', 'Smyslov, V.', 'Tal, Mikhail', 'Petrosian, Tigran V', 'Spassky, Boris V.', 'Fischer, Robert J', 'Karpov, Anatoly',
'Kasparov, Garry', 'Kasparov, G.', 'Kramnik, Vladimir', 'Anand, Viswanathan', 'Carlsen, Magnus', 'Ding, Liren', 'Gukesh D')
group by
case
when p.name in ('Kasparov, Garry', 'Kasparov, G.') then 'Kasparov, Garry'
when p.name in ('Smyslov, Vasily', 'Smyslov, V.') then 'Smyslov, Vasily'
else p.name
end,
g.id) t
group by
t.spieler
order by
2 ,
3 desc""", conn)
return(df)
def visualize_data(p_df):
color=['green', 'orange', 'red']
ax=p_df.plot(x='Spieler', kind='bar', width=.8, stacked=True, figsize=(20,18), color=color, ylabel="durchschnittliche Anzahl Halbzüge", title="durchschnittliche Anzahl Halbzüge der Schachweltmeister nach Ergebnis")
plt.bar_label(ax.containers[0], labels=p_df['durchschnittliche Anzahl Halbzuege Gewinn'], rotation = 0, label_type = "center")
plt.bar_label(ax.containers[1], labels=p_df['durchschnittliche Anzahl Halbzuege Remis'], rotation = 0, label_type = "center")
plt.bar_label(ax.containers[2], labels=p_df['durchschnittliche Anzahl Halbzuege Verlust'], rotation = 0, label_type = "center")
plt.ylim([0,300])
plt.show()
def main():
# try to get passwd from .env file
load_dotenv()
passwd = os.getenv("passwd")
# If password not provided, prompt securely
if not passwd:
try:
passwd = getpass.getpass(prompt="Enter database password: ")
except (KeyboardInterrupt, EOFError):
print("\nPassword input cancelled.")
sys.exit(1)
# Validate password input
if not passwd.strip():
print("Error: Database password cannot be empty.")
sys.exit(1)
# connect to database
try:
connect(passwd)
df = get_data()
visualize_data(df)
disconnect()
except Exception as e:
print(e)
try:
disconnect()
except:
pass
sys.exit(1)
if __name__ == "__main__":
main()

Wie man aus der Grafik ablesen kann, hält Paul Morphy gleich zwei Rekorde. Niemand hat jemals seine Gegner derart schnell an die Wand gespielt – durchschnittlich in 30 Zügen! Gleichzeitig ist er der hartnäckigste Spieler – im Durchschnitt wehrte er sich ca. 48 Züge lang gegen den Verlust. Am wenigsten wehrhaft war Emanuel Lasker, der im Schnitt bereits nach 36 Zügen seinen König umlegen musste. Ebenfalls klar abzulesen ist der Trend, dass im Laufe der Zeit die Anzahl Züge bis zum Spielende ungeachtet des Spielausgangs immer weiter angestiegen ist. Dies deutet auf ein allgemein angestiegenes Leistungsniveau in der Weltspitze hin.
Die bislang gewonnenen Erkenntnisse sind höchst interessant, doch wie wir bereits in unserer Einleitung dargelegt hatten, geben uns diese Resultate wenig Aufschluss über die Spielstärken im direkten Vergleich untereinander. Dazu müssen wir andere Metriken heranziehen, womit wir im nächsten Kapitel beginnen.
Stichproben-Relevanz
Doch zunächst wollen wir prüfen, ob unsere Stichproben, mit denen wir die tiefgehenden Spielanalysen durchgeführt haben, überhaupt repräsentativ sind. Dazu sollten die Scoring Raten einigermaßen mit denen der Gesamtheit aller Partien übereinstimmen.
Wir bauen obiges SQL daher so um, dass wir sowohl die Scoring Raten für jeweils alle Partien eines Spielers wie auch der analysierten Spiele ermitteln und gemeinsam darstellen.
Jupyter Notebook Code
import sys
import os
import getpass
import mariadb
import pandas as pd
import matplotlib.pyplot as plt
import warnings
from dotenv import load_dotenv
warnings.filterwarnings('ignore')
def connect(p_password):
global conn
global cursor
# Database connection details
db_config = {
"user": "chess_user",
"password": p_password,
"host": "localhost",
"database": "chess",
"port": 3306, # Standard port for MariaDB
}
# Establishing the connection
conn = mariadb.connect(**db_config)
# Disable autocommit
conn.autocommit = False
# Create a cursor to execute queries
cursor = conn.cursor()
def disconnect():
cursor.close()
conn.close()
def get_data():
df = pd.read_sql_query("""select
Spieler,
round(score / CountGamesPlayed * 100) "Score Rate",
round(score_analysed / CountGamesAnalysed * 100) "Score Rate Analysed"
from
(
with pg as
(
select
p.game_id
from
position p
join position_analysis pa on
pa.position_id = p.id
group by
p.game_id
)
select
case
when p.name in ('Kasparov, Garry', 'Kasparov, G.') then 'Kasparov, Garry'
when p.name in ('Smyslov, Vasily', 'Smyslov, V.') then 'Smyslov, Vasily'
else p.name
end Spieler,
count(p.name) CountGamesPlayed,
sum(case when pg.game_id is not null then 1 else 0 end) CountGamesAnalysed,
sum(case when (p.id = g.white_player_id and g.result = 1) or (p.id = g.black_player_id and g.result = 2) then 1 else 0 end) + sum(case when g.result = 3 then 0.5 else 0 end) score,
sum(case when pg.game_id is null then 0 when (p.id = g.white_player_id and g.result = 1) or (p.id = g.black_player_id and g.result = 2) then 1 else 0 end) + sum(case when pg.game_id is null then 0 when g.result = 3 then 0.5 else 0 end) score_analysed
from
player p
join game g on
(p.id = g.black_player_id
or p.id = g.white_player_id )
left outer join pg on
pg.game_id = g.id
where
p.name in ('Staunton, Howard', 'Morphy, Paul', 'Steinitz, Wilhelm', 'Lasker, Emanuel', 'Capablanca, Jose', 'Alekhine, Alexander', 'Euwe, Max',
'Botvinnik, Mikhail URS', 'Smyslov, Vasily', 'Smyslov, V.', 'Tal, Mikhail', 'Petrosian, Tigran V', 'Spassky, Boris V.', 'Fischer, Robert J', 'Karpov, Anatoly',
'Kasparov, Garry', 'Kasparov, G.', 'Kramnik, Vladimir', 'Anand, Viswanathan', 'Carlsen, Magnus', 'Ding, Liren', 'Gukesh D')
group by
case
when p.name in ('Kasparov, Garry', 'Kasparov, G.') then 'Kasparov, Garry'
when p.name in ('Smyslov, Vasily', 'Smyslov, V.') then 'Smyslov, Vasily'
else p.name
end) t
order by
2 desc""", conn)
return(df)
def visualize_data(p_df):
color=['blue', 'red']
ax=p_df.plot(x='Spieler', kind='bar', width=.8, stacked=False, figsize=(20,18), color=color, ylabel="Score Rate", title="Score Raten der Schachweltmeister")
plt.bar_label(ax.containers[0], labels=p_df['Score Rate'], rotation = 0, label_type = "center")
plt.bar_label(ax.containers[1], labels=p_df['Score Rate Analysed'], rotation = 0, label_type = "center")
plt.ylim([0, 90])
plt.show()
def main():
# try to get passwd from .env file
load_dotenv()
passwd = os.getenv("passwd")
# If password not provided, prompt securely
if not passwd:
try:
passwd = getpass.getpass(prompt="Enter database password: ")
except (KeyboardInterrupt, EOFError):
print("\nPassword input cancelled.")
sys.exit(1)
# Validate password input
if not passwd.strip():
print("Error: Database password cannot be empty.")
sys.exit(1)
# connect to database
try:
connect(passwd)
df = get_data()
visualize_data(df)
disconnect()
except Exception as e:
print(e)
try:
disconnect()
except:
pass
sys.exit(1)
if __name__ == "__main__":
main()

Wie im Schaubild oben gut zu sehen ist, weichen die beiden Scoring Raten für die einzelnen Spieler so wenig voneinander ab, dass wir guten Mutes sind, mit einer repräsentativen (Zufalls-)Auswahl in die weiteren Untersuchungen starten zu können.