Du betrachtest gerade 13) Datenvisualisierung Teil 1

13) Datenvisualisierung Teil 1

  • Beitrags-Autor:Peter Kühn
  • Beitrags-Kategorie:chess_analyser
  • Beitrags-Kommentare:0 Kommentare

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 😉

Ein Bild sagt mehr…

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;
die Auswertung nach Ergebnissen

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()
Anzahl Partien nach ihren Ergebnissen

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

Spielergebnisse nach Quoten

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()
das Score-Rating

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()
durchschnittliche Anzahl Halbzüge nach Spielausgang

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()
Scoring Raten im direkten Vergleich

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.

Schreibe einen Kommentar