8.2 Mit Schelling experimentieren#

Nun ist es deine Aufgabe ein kleines Simulationsexperiment auf Basis des Schelling-Modells durchzuführen. In den Übungsaufgaben des letzten Kapitels solltest du dir ein mögliches Simulationsexperiment überlegen. Greife diese Überlegungen nun auf und setze sie um. Verwende als Ausgangspunkt entweder den (von dir modifizierten) Code aus dem letzten Kapitel oder den untigen Code.

Damit du ein Simulationsexperiment umsetzen kannst, musst du den Code so verändern, dass dieser

  1. eine interessierende abhängige Variable misst

  2. eine interessierende unabhängige Variable als Parameterwert in die Funktion run_model() eingegeben werden kann

  3. relevante Modellinformationen durch die Funktion run_model() in einem Dictionary ausgegeben werden

  4. in einem oder mehreren For-Loops die Funktion run_model() mit unterschiedlichen Werten für die unabhängige Variable deiner Wahl läuft und alle Dictionaries mit den Modellinformationen gespeichert werden

  5. die Modellinformationen/Daten zu einem Pandas-Dataframe zusammengeführt werden

  6. einfache graphische Auswertungen vollzogen werden.

Falls du dir in den letzten Übungsaufgaben kein Simulationsexperiment überlegt haben solltest, überprüfe den Zusammenhang zwischen indivdueller Segregations-Präferenz (threshold) und der tatsächlich auf der Makroebene entstehenden Segregation. Als Maß für Segregation auf der Makroebene kannst du den durchschnittlichen Anteil von Mitgliedern der eigenen Gruppe innerhalb der Nachbarschaft verwenden. Dieser liegt für jede Zelle auf dem Zellen-Attribut "share_of_same_group" vor. Aber Vorsicht: Zum einen musst du diesen Wert einmal für alle Zellen aktualisieren, bevor du den Durchschnitt berechnen kannst. Zum anderen musst du darauf achten, dass du nur gültige Werte bei der Mittelwertsberechnung beachtest, denn Agenten ohne direkte Nachbarn bekommen einen “ungültigen” Wert von 999 eingetragen.

Code zum Schelling-Modell (ohne grafische Darstellung)#

import random


def create_grid(n_rows, n_cols, n_group1, n_group2):
    """
    Erstellt eine Matrix als geschachtelte Liste der Größe n_rows*n_cols.
    Auf jede Zelle der Matrix wird ein Dictionary mit relevanten Zellen-Informationen gesetzt.
    In die Zellen ziehen die Mitglieder der Gruppe 1 und Gruppe 2 entsprechend der durch n_group1 und n_group2
    angegebenen Häufigkeiten ein.
    """
    
    # Grid als Liste von Listen erstellen und auf jede Position eine unbewohnte Zelle setzen
    grid = []
    for i in range(n_rows):
        row = []
        for j in range(n_cols):
            cell = {
                "row": i,
                "col": j,
                "resident": 0,
                "share_of_same_group": None,
                "neighbor_cells": [],
            }
            row.append(cell)
            
        grid.append(row)
    
    # Alle Zellen auch in einer normalen, flachen Liste speichern, 
    # damit man einfacher per For-Loop alle Zellen durchgehen kann
    cell_population = []
    for row in grid:
        for cell in row:
            cell_population.append(cell)
    
    # für jede Zelle die benachbarten Zellen finden
    for cell in cell_population:
        find_neighbor_cells(cell, grid)
    
    # Agenten der Gruppe 1 auf Grid setzen
    for i in range(n_group1):
        empty_cells, occupied_cells = get_empty_and_occupied_cells(cell_population)
        random_empty_cell = random.choice(empty_cells)
        random_empty_cell["resident"] = 1

    # Agenten der Gruppe 2 auf Grid setzen
    for i in range(n_group2):
        empty_cells, occupied_cells = get_empty_and_occupied_cells(cell_population)
        random_empty_cell = random.choice(empty_cells)
        random_empty_cell["resident"] = 2
    
    return grid, cell_population


def find_neighbor_cells(cell, grid):
    """
    Sucht für jede Zelle die 8 umgebenden Nachbarzellen auf dem Grid und 
    speichert diese im Zellen-Attribut "neighbor-cells".
    Das Grid wird als Torus angesehen.
    """
    
    # Anzahl der Zeilen des Grids ermitteln
    n_rows = len(grid)
    
    # Anzahl der Spalten bzw. Zellen in einer Zeile des Grids anhand der ersten Zeile ermitteln
    n_cols = len(grid[0])
    
    # für jede Zweilenpositionsabweichung
    for row_deviation in [-1, 0, 1]:
         
        # für jede Spaltenpositionsabweichung
        for col_deviation in [-1, 0, 1]:
            
            # wenn nicht Zeilen- und Spaltenabweichung beide 0 sind
            if not (row_deviation == 0 and col_deviation == 0):
            
                # Zeilen- und Spaltenposition der Nachbarzelle berechnen
                neighbor_row = (cell["row"] - row_deviation) % n_rows
                neighbor_col = (cell["col"] - col_deviation) % n_cols

                # Nachbarzelle im grid finden und der betrachteten Zelle in die Liste von Nachbarzellen einfügen
                neighbor_cell = grid[neighbor_row][neighbor_col]
                cell["neighbor_cells"].append(neighbor_cell)
                

def get_empty_and_occupied_cells(cell_population):
    """
    Gibt eine Liste mit Verweisen auf alle bewohnten und eine Liste mit Verweisen auf alle unbewohnten Zellen aus. 
    Erwartet eine flache Liste mit allen Zellen.
    """
    
    # Liste für alle unbewohnten Zellen
    empty_cells = []
    
    # Liste für alle bewohnten Zellen
    occupied_cells = []
    
    # Alle Zellen durchgehen und prüfen, ob Zelle unbewohnt. Dann entsprechender Liste anhängen.
    for cell in cell_population:
        if cell["resident"] == 0:
            empty_cells.append(cell)
        else:
            occupied_cells.append(cell)
    
    return empty_cells, occupied_cells


def eval_neighborhood(cell):
    """
    Berechnet für eine eingegebene Zelle den Anteil an bewohnten Nachbarzellen, 
    die einen Agenten aus derselben Gruppe beherbergen und speichert diesen im Zellen-Attribut "share_of_same_group".
    """
    
    # Variable zur Zählung der aktuell bewohnten Nachbarzellen
    n_neighbors = 0
    
    # Variable zur Zählung der durch Mitglieder derselben Gruppe bewohnte Nachbarzellen
    n_neighbors_of_same_group = 0
    
    # für jede Nachbarzelle
    for neighbor_cell in cell["neighbor_cells"]:
        
        # wenn Zelle bewohnt, n_neighbors um 1 erhöhen
        if neighbor_cell["resident"] != 0:
            n_neighbors += 1
        
        # wenn Zelle mit Mitglied derselben Gruppe bewohnt, dann n_neighbors_of_same_group um 1 erhöhen
        if neighbor_cell["resident"] == cell["resident"]:
            n_neighbors_of_same_group += 1
    
    # Wenn es Nachbarn gibt
    if n_neighbors > 0:
        # Anteil derselben Gruppe unter Nachbarn berechnen und einspeichern
        cell["share_of_same_group"] = n_neighbors_of_same_group / n_neighbors
    
    # ansonten
    else:
        # "Missing" eintragen
        cell["share_of_same_group"] = 999
        

def run_model(threshold, ticks, ticks_per_snapshot, n_rows=20, n_cols=20, n_group1=190, n_group2=190):
    """
    Führt das gesamte Simulationsmodell aus. Hat aktuell noch keinen Output, aber durch Modifikation
    könnte ein Dictionary mit relevanten Modell-Informationen ausgegeben werden.
    """
    
    # Grid erstellen
    grid, cell_population = create_grid(n_rows, n_cols, n_group1, n_group2)
    
    ###############################################
    # Simulationsloop
    ###############################################
    
    # für jeden Zeitschritt
    for tick in range(ticks):
        
        # zwei Listen mit allen leeren und bewohnten Zellen erstellen
        empty_cells, occupied_cells = get_empty_and_occupied_cells(cell_population)
        
        # eine zufällige Zelle aussuchen
        random_cell = random.choice(occupied_cells)
        
        # ausgesuchte Zelle schaut, wie hoch der Anteil der eigenen Gruppe in Nachbarschaft ist
        eval_neighborhood(random_cell)
        
        # Wenn in Nachbarschaft zu wenige aus derselben Gruppe wie Agent sind
        if random_cell["share_of_same_group"] < threshold:
            
            # zufällige neue, unbewohnte Zelle aussuchen
            new_cell = random.choice(empty_cells)
            
            # der neuen Zelle den Agenten zuweisen
            new_cell["resident"] = random_cell["resident"]
            
            # den Agenten bei alter Zelle "löschen"
            random_cell["resident"] = 0