Lösungen#

Im Folgenden wird ein Experiment, mit welchem der Zusammenhang von individueller Segregationspräferenz und realisierter Segregation untersucht werden kann, implementiert.

Schritt 1 - Daten sammeln#

Zunächst muss die Funktion run_model() so verändert werden, dass die gewünschten Daten eines Simulationsdurchlaufs gesammelt werden. Am Ende der Funktion run_model() werden daher u.a. die realisierte Segregation als durchschnittliche Anzahl von Nachbarn der eigenen Gruppe sowie die individuelle Segregationspräferenz (threshold) in einem Dictionary gesammelt und ausgegeben.

import random

def create_grid(n_rows, n_cols, n_group1, n_group2):
    # 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):
    # 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):
    
    # 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):
    # 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=10000, n_rows=20, n_cols=20, n_group1=180, n_group2=180):
    
    # 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
    
    
    ###############################################
    # Daten sammeln
    ###############################################
    
    # Am Ende der Simulation für jede Zelle die Zusammensetzung der Nachbarschaft aktualisieren und sammeln
    segregation_data = []
    for cell in cell_population:
        eval_neighborhood(cell)
        
        # "ungültige Werte ausschließen"
        if cell["share_of_same_group"] <= 1: 
            segregation_data.append(cell["share_of_same_group"])
    
    # Durchschnittliche Segregation für Population berechnen
    segregation = sum(segregation_data) / len(segregation_data)
    
    # Daten in Dictionary zusammentragen und ausgeben
    output_dict = {
        "segregation": segregation,
        "threshold": threshold,
        "ticks": ticks,
        "n_rows": n_rows,
        "n_cols": n_cols,
        "n_rows": n_rows,
    }
    
    return output_dict

Schritt 2 - Experiment durchführen#

Da der Parameter threshold die unabhängige Variable darstellt, wird dieser im Folgenden variiert, sodass das Simulationsmodell jeweils mit unterschiedlichen Werten für threshold und mit jeweils mehreren Replikationen durchgeführt wird. Die von jedem Simulationsdurchgang produzierten Dictionaries werden in der Liste data gespeichert und schließlich in einen Dataframe namens df überführt.

import pandas as pd

# Sammelliste für Ergebnisse/Dictionaries der einzelnen Simulationsdurchgänge
data = []

# Anzahl der verschiedenen Parameterwerte
steps = 100

# Anzahl der Wiederholungen pro Parameterwert
replications = 10

# für jede Zahl von 0 bis 99
for i in range(steps):
    
    # Wert für threshold "berechnen"
    threshold = i / 100
    
    # für jede Wiederholung für diesen Parameterwert
    for rep in range(replications):
        
        # Simulation laufen lassen & Daten in Variable speichern
        results = run_model(threshold=threshold)
        
        # Daten an Sammelliste anhängen
        data.append(results)

# Daten in Dataframe überführen
df = pd.DataFrame(data)
df.head()
segregation threshold ticks n_rows n_cols
0 0.481848 0.0 10000 20 20
1 0.470699 0.0 10000 20 20
2 0.461202 0.0 10000 20 20
3 0.451387 0.0 10000 20 20
4 0.455313 0.0 10000 20 20
df.tail()
segregation threshold ticks n_rows n_cols
995 0.475756 0.99 10000 20 20
996 0.449301 0.99 10000 20 20
997 0.486420 0.99 10000 20 20
998 0.457122 0.99 10000 20 20
999 0.495985 0.99 10000 20 20

Schritt 3 - Ergebnisse darstellen#

import seaborn as sns
import matplotlib.pyplot as plt

# Farbschema einstellen
sns.set_theme()

# Diagramme malen
sns.lineplot(data=df, x="threshold", y="segregation")

# sichtbare Wertebereiche der Achsen einstellen
plt.xlim([-0.1, 1.1])
plt.ylim([-0.1, 1.1])

# Achsen beschriften
plt.xlabel("Individuelle Segregationspräferenz")
plt.ylabel("Realisierte Segregation")

# Diagramm anzeigen
plt.show()
../_images/amp08c_7_0.png
  • Die individuelle Segregationspräferenz übersetzt sich weder “1 zu 1” in die realisierte Segregation noch gibt es einen linearen Zusammenhang zwischen diesen beiden Variablen.

  • Es scheint ein Grundlevel an realisierter Segregation von ca. 0.5 zu geben. Dies bedeutet, dass die beiden Gruppen gut durchmischt sind und eine Person durchschnittlich 50% Nachbarn der eigenen Gruppe und 50% Nachbarn der anderen Gruppe hat.

  • Bereits jedoch ab einer individuellen Segregationspräferenz von 0.2 bis 0.3 beginnt die realisierte Segregation über das Basislevel von 0.5 hinaus stark anzusteigen und bei vergleichsweise niedrigen Segregationspräferenzen nun vergleichsweise starke Segregation zu erzeugen. Bis zu einer individuellen Segregationspräferenz von ca. 0.7 wird jeweils im Vergleich zu den individuellen Präferenzen eine stark “überhöhte” Segregation realisiert.

  • Ab einer Segregationspräferenz von ca. 0.7 sinkt die realisierte Segregation wieder auf das “Basis-Niveau” ab. Dies ist damit zu erklären, dass nun die Bedürfnisse der Agenten nicht mehr erfüllt werden zu scheinen, diese daher ständig auf einen zufälligen neuen Platz umziehen und somit eine ständige Durchmischung erzeugen. Dieser Effekt ist jedoch vermutlich auf die spezielle Implementierung des Modells zurückzuführen: Würden Agenten beispielsweise bei Unzufriedenheit mit der Nachbarschaft nicht auf einen zufälligen neuen Ort umziehen, sondern nur auf Orte mit einer “besseren” Zusammensetzung der Nachbarschaft, dann würde vermutlich kein Absinken, sondern lediglich eine Stagnation der realisierten Segregation bei sehr hohen individuellen Segregationspräferenzen zu beobachten sein.