Klimadaten auswerten#

In diesem Jupyter Notebook werden wir die Klimadaten der NASA aus und lernen die wichtigsten Python hierfür kennen, die Sie auch für das Messtechnik-Praktikum benötigen um Ihre Versuche auszuwerten.

Zunächst werden die für dieses Jupyter Notebook benötigten Libraries geladen:

#Benötigte Libraries:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt


import plotly.offline as py
py.init_notebook_mode(connected=True)
import plotly.graph_objs as go
import plotly.tools as tls
import seaborn as sns
import time
import warnings
warnings.filterwarnings('ignore')

# MatplotLib Settings:
plt.style.use('default') # Matplotlib Style wählen
plt.rcParams['font.size'] = 10; # Schriftgröße

.csv-Datei als DataFrame einlesen#

Im Folgenden Nutzen wir globale Klimadaten, die auf der Webseite der NASA zu finden sind: https://data.giss.nasa.gov/gistemp/. Hierbei handelt es sich um Temperaturdaten, die Anomalien gegenüber dem Mittelwert in den Jahren 1951-1980 aufweisen. Es werden Daten von Dateien (online oder offline) eingelesen mit der Python Bilbiothek pandas. Die Daten werden in sogenannten DataFrames hier mit dem Namen global_mean abgespeichert.

link = "https://data.giss.nasa.gov/gistemp/graphs_v4/graph_data/Global_Mean_Estimates_based_on_Land_and_Ocean_Data/graph.csv"
#link = 'data/graph.csv'
global_mean = pd.read_csv(link, header = 1) #DataFrame erstellen

Wir geben das DataFrame aus um uns die Messdaten einmal anzusehen:

global_mean.head(10) # Ausgabe der ersten 5 Spalten
#global_mean.tail(5) # Ausgabe der letzten 5 Spalten
#global_mean # Ausgabe des DataFrames 
Year No_Smoothing Lowess(5)
0 1880 -0.17 -0.10
1 1881 -0.09 -0.13
2 1882 -0.11 -0.17
3 1883 -0.17 -0.20
4 1884 -0.28 -0.24
5 1885 -0.33 -0.26
6 1886 -0.31 -0.27
7 1887 -0.36 -0.27
8 1888 -0.18 -0.27
9 1889 -0.11 -0.26

In der ersten Spalte befinden sich lediglich die Indizes der Messungen. Die zweite Spalte beinhaltet das Jahr und die dritte Spalte zeigt den gemessenen globalen Temperaturunterschied im Vergleich zur gemittelten Temeratur der Jahre 1951-1980. Die letzte Spalte zeigt die gleichen Messwerte, jedoch gefiltert.

Einzelne Spalten kann man sich anzeigen lassen, indem den Spaltel-Namen des zugehörigen DataFrames nutzt:

global_mean['Year']
0      1880
1      1881
2      1882
3      1883
4      1884
       ... 
140    2020
141    2021
142    2022
143    2023
144    2024
Name: Year, Length: 145, dtype: int64

Statistische Größen#

Die Bibliothek pandas ist sehr umfangreich und wird viel zur Datenverarbeitung genutzt. Im folgenden dazu einige Beispiele:

Statistische Größen: Mittelwert, Standardabweichung, Min, Max#

Für jede Spalte lassen sich statistische Größen wie z.B. die Anzahl der Einträge pro Spalte, deren Mittelwert, Standardabweichung, Minimal- und Maximalwert bestimmen:

print(global_mean.describe())

#print(global_mean['No_Smoothing'].describe())
#print(global_mean['Lowess(5)'].describe())
              Year  No_Smoothing   Lowess(5)
count   145.000000    145.000000  145.000000
mean   1952.000000      0.072966    0.073931
std      42.001984      0.392561    0.384890
min    1880.000000     -0.490000   -0.420000
25%    1916.000000     -0.200000   -0.230000
50%    1952.000000     -0.030000   -0.040000
75%    1988.000000      0.310000    0.310000
max    2024.000000      1.280000    1.190000

Die mittlere Jahresmitteltemperaturabweichung im Vergleich zu 1951-1980 beträgt in diesem Fall 0,07 Grad Celsius. Der Trend ist also positiv und im Mittel müssen wir mit einer Erwärmung rechnen, auch wenn einige Jahre negative Jahresmitteltemperaturabweichungen aufzeigen.

Kälteste Jahre#

Die folgenden Jahre weisen die niedrigsten Werte der Temperatur-Anomalie auf.
Sie repräsentieren die kältesten Phasen in der betrachteten Zeitreihe. Als erstes wenden wir einen Filter auf die Jahre mit negativer Temperatur-Anomalie an:

cold_years = global_mean[global_mean["No_Smoothing"] < 0] # Filter auf Jahre mit negativer Temperatur-Anomalie

Mit .sort_values("Spaltenname") können wir die Tabelle nach dem definierten Spaltennamen sortieren (standardmäßig in ansteigender Reihenfolge):

# Sortieren nach kältesten Jahren (kleinste Werte zuerst)
cold_years_sorted = cold_years.sort_values(by="No_Smoothing")

# Ausgabe der Top 10 kältesten Jahre
print(cold_years_sorted.head(10))
    Year  No_Smoothing  Lowess(5)
29  1909         -0.49      -0.42
24  1904         -0.48      -0.32
37  1917         -0.47      -0.31
31  1911         -0.45      -0.40
30  1910         -0.44      -0.42
28  1908         -0.44      -0.40
27  1907         -0.40      -0.38
32  1912         -0.38      -0.36
23  1903         -0.38      -0.29
49  1929         -0.37      -0.20

Und wenn man nur die Jahreszahlen und Werte angezeigt bekommen möchte:

cold_years_sorted[["Year", "No_Smoothing"]].head(10)
Year No_Smoothing
29 1909 -0.49
24 1904 -0.48
37 1917 -0.47
31 1911 -0.45
30 1910 -0.44
28 1908 -0.44
27 1907 -0.40
32 1912 -0.38
23 1903 -0.38
49 1929 -0.37

Wärmste Jahre#

Die folgenden Jahre weisen die höchsten Werte der Temperatur-Anomalie auf.
Sie repräsentieren die wärmsten Phasen in der betrachteten Zeitreihe.

# Filter auf Jahre mit positiver Temperatur-Anomalie
warm_years = global_mean[global_mean["No_Smoothing"] > 0]

# Sortieren nach wärmsten Jahren (größte Werte zuerst)
warm_years_sorted = warm_years.sort_values(by="No_Smoothing", ascending=False)

# Ausgabe der Top 10 wärmsten Jahre
print(warm_years_sorted.head(10))
     Year  No_Smoothing  Lowess(5)
144  2024          1.28       1.19
143  2023          1.17       1.13
140  2020          1.01       0.97
136  2016          1.01       0.87
139  2019          0.98       0.94
137  2017          0.92       0.91
135  2015          0.90       0.83
142  2022          0.89       1.08
141  2021          0.85       1.02
138  2018          0.85       0.93

Wärmstes Jahr#

Wenn wir wissen wollen, wann diese maximale Temperaturdifferenz auftrat, kann auch der zugehörige Index dieses Events gespeichert werden:

index_max = global_mean["No_Smoothing"].idxmax()
print(index_max)
144

Diesen Index können wir nun benutzen, um mittels .loc die entsprechenden Einträge zu diesem Index auszugeben:

global_mean.loc[index_max]
Year            2024.00
No_Smoothing       1.28
Lowess(5)          1.19
Name: 144, dtype: float64

Daten plotten und Diagramm sichern mit ‘matplotlib’#

Als Beispiel für eine gelungene grafische Darstellung wollen wir die beiden Spalten, No_Smoothing and Lowess(5) gegenüber der Zeitachse Year plotten. Hierfür benützen wir die Python Library matplotlib. Einmal geplottet kann das zuletzt angezeigte Diagramm in verschiedenen Formaten mit plt.savefig('klima_plot1.png') abgespeichert werden. Wenn nicht anders angegeben, wird die Datei im gleichen Ordner angelegt.

import matplotlib.pyplot as plt
#plt.style.use('default')
plt.figure(figsize=(10,5))
plt.rcParams['font.size'] = 10; # Schriftgröße
plt.plot(global_mean["Year"],global_mean["No_Smoothing"], ls="--", lw=1, marker="s", ms=3, color="tab:gray", alpha=0.5, label="Werte");
#plt.plot(global_mean["Year"],global_mean["Lowess(5)"], lw=4,  color="tab:blue", label="Glättung (NASA)");
plt.xlabel('Jahr')
plt.ylabel("Jahresmitteltemperaturabweichung [°C]")
plt.legend();
plt.grid();
plt.savefig('klima_plot1.png')
plt.savefig('klima_plot1.pdf')
../_images/d2ab5539abb46d3d4088aa43cac382b2dc23f257c303c60a8761fc799ec4c29f.png

Daten glätten#

Die von der NASA verwendete Glättung ist die LOcally WEighted Scatter-plot Smoother (LOWESS). Dabei wird in einem lokal zu definierenden Bereich eine lineare Regression durchgeführt. Eine genauere Erklärung zur Methode findet ihr auf Youtube.

Es gibt natürlich viele Methoden und Filter, um Daten zu glätten. Wir wollen nun versuchen, die Methode der NASA zu rekonstruieren. Hierfür benutzen wir die Python Library statsmodels und erstellen eine weitere Spalte Lowess(own) in unserem DataFrame global_mean. In diese Spalte schreiben wir die geglätteten Werte von den Rohdaten global_mean["No_Smoothing"] indem wir die Funktion lowess aufrufen. Details zu Nutzung der Funktion findet ihr https://www.statsmodels.org:

  • an erster Stelle in der Funktion werden die Y-Werte eingegeben, hier global_mean["No_Smoothing"]

  • an zweiter Stelle in der Funktion werden die X-Werte eingegeben, hier global_mean["Year"]

  • die Option frac ist eine Zahl zwischen 0 und 1. Dies ist der Anteil der Daten, der bei der Schätzung der einzelnen y-Werte verwendet wird.

  • Ausgegeben wird zweidimensionalas Array. Die erste Spalte enthält die sortierten x-Werte und die zweite Spalte die zugehörigen geschätzten y-Werte. Um die zweite Spalte in den DataFrame zu speichern, wählen wir diese mit [:,1] aus.

from statsmodels.nonparametric.smoothers_lowess import lowess

global_mean["Lowess(eigene)"] = lowess(global_mean["No_Smoothing"],global_mean["Year"], frac=1/14)[:,1]
global_mean
Year No_Smoothing Lowess(5) Lowess(eigene)
0 1880 -0.17 -0.10 -0.096166
1 1881 -0.09 -0.13 -0.131758
2 1882 -0.11 -0.17 -0.167628
3 1883 -0.17 -0.20 -0.202299
4 1884 -0.28 -0.24 -0.236649
... ... ... ... ...
140 2020 1.01 0.97 0.965670
141 2021 0.85 1.02 1.023666
142 2022 0.89 1.08 1.078238
143 2023 1.17 1.13 1.133268
144 2024 1.28 1.19 1.191802

145 rows × 4 columns

plt.figure(figsize=(10,5))
plt.rcParams['font.size'] = 10;
plt.plot(global_mean["Year"],global_mean["No_Smoothing"], ls="-", lw=1, marker="s", ms=3, color="tab:gray", alpha=0.5, label="Werte");
plt.plot(global_mean["Year"],global_mean["Lowess(5)"], lw=4,  color="tab:blue", label="Glättung (NASA)");
plt.plot(global_mean["Year"],global_mean["Lowess(eigene)"], lw=2,ls='-' ,  color="tab:red", label="Glättung (eigene)");
plt.xlabel('Jahr')
plt.ylabel("Jahresmitteltemperaturabweichung [°C]")
plt.legend();
plt.grid();
plt.savefig('klima_plot2.png')
plt.savefig('klima_plot2.pdf')
../_images/051b7bcf0cfad9c2bb80696578e26c6f3c4118b78570787c394e391504ceb574.png

Messunsicherheiten als Fehlerbalken hinzufügen#

Bei diesem Datenset stehen uns leider keine Messunsicherheiten zur Verfügung. Um Sie jedoch als Fehlerbalken miteinzubeziehen, wollen wir im Folgenden annehmen, dass der Temperaturunterschied auf 0.25K genau messen werden konnte und fügen die unserem Datensatz hinzu:

global_mean["uncertainty"] = 0.25
print(global_mean)
     Year  No_Smoothing  Lowess(5)  Lowess(eigene)  uncertainty
0    1880         -0.17      -0.10       -0.096166         0.25
1    1881         -0.09      -0.13       -0.131758         0.25
2    1882         -0.11      -0.17       -0.167628         0.25
3    1883         -0.17      -0.20       -0.202299         0.25
4    1884         -0.28      -0.24       -0.236649         0.25
..    ...           ...        ...             ...          ...
140  2020          1.01       0.97        0.965670         0.25
141  2021          0.85       1.02        1.023666         0.25
142  2022          0.89       1.08        1.078238         0.25
143  2023          1.17       1.13        1.133268         0.25
144  2024          1.28       1.19        1.191802         0.25

[145 rows x 5 columns]

Grafisch darstellen tun wir Messunsicherheiten mittels Fehlerbalken und der Matplotlib-Funktion plt.errorbar.

plt.figure(figsize=(10,5))
plt.rcParams['font.size'] = 10;
plt.errorbar(global_mean["Year"],global_mean["No_Smoothing"], yerr=global_mean["uncertainty"], ls="-", lw=1, marker="s", ms=3, color="tab:gray", alpha=0.5, label="Werte");
plt.plot(global_mean["Year"],global_mean["Lowess(5)"], lw=4,  color="tab:blue", label="Glättung (NASA)");
plt.plot(global_mean["Year"],global_mean["Lowess(eigene)"], lw=2,ls='-' ,  color="tab:red", label="Glättung (eigene)");
plt.xlabel('Jahr')
plt.ylabel("Jahresmitteltemperaturabweichung [°C]")
plt.legend();
plt.grid();
plt.savefig('klima_plot3.png')
plt.savefig('klima_plot3.pdf')
../_images/6749f28ba1a468269271890d12e7a6461b8fb82ca341f1f0de75b4a9677a36cc.png

Modellfunktionen an Daten anpassen#

Lineare Regression#

Mittels linearer Regression kann der Temperaturanstieg aus den Daten berechnet werden. Hierfür wird die Python Library numpy benutzt und die Funktion polyfit aufgerufen und in als model gespeichert. Diese Funktion benutzt die Least-Square Methode für polynomische Modelle. Weitere Informationen zu der Funktion findet ihr hier. Mit der Option cov=True wird die Kovarianz-Matrix berechnet, welche die Unsicherheiten für die Fit-Parameter beinhaltet.

x=global_mean["Year"]
y=global_mean["No_Smoothing"]
y_err = global_mean["uncertainty"]
model_lin1 = np.polyfit(x, y, deg=1, w=1/y_err, cov=True) # 1. Wert = Anstieg , 2. Wert = Schnittpunkt mit y-Achse
y_model_1 = model_lin1[0][0]*x+model_lin1[0][1] # Modell einer linearen Regression

plt.figure(figsize=(10,5))
plt.rcParams['font.size'] = 10;
plt.ylabel("Jahresmitteltemperaturabweichung [°C]")
plt.xlabel("Jahr")
plt.errorbar(global_mean["Year"],global_mean["No_Smoothing"], yerr=global_mean["uncertainty"], ls="-", lw=1, marker="s", ms=3, color="tab:gray", alpha=0.5, label="Werte");
plt.plot(x,y_model_1, ls="-", lw=3, color="tab:red", label=f"Modell: y=({model_lin1[0][0]*1000:.3f}+-{np.sqrt(model_lin1[1][0][0]*1000):.3f})1e-3*x+({model_lin1[0][1]:.3f}+-{np.sqrt(model_lin1[1][1][1]):.3f})");
plt.legend();
plt.grid();
plt.savefig('klima_plot4.png')
plt.savefig('klima_plot4.pdf')
../_images/2d79bc1a2704acbb16554176939fa048522660692b7233aeb9786a0df7d4464c.png

Das Model beinhaltet zwei Matrizen:

model_lin1
(array([ 8.16509211e-03, -1.58652943e+01]),
 array([[ 1.44641636e-07, -2.82340474e-04],
        [-2.82340474e-04,  5.51382018e-01]]))

Im ersten Array stehen die Fit-Parameter der linearen Ausgleichsgeraden entsprechend der obigen Deklaration: y_model = model_lin1[0][0]*x+model_lin1[0][1]. Im zweiten Array, hier eine 2x2 Matrix, sind die Unsicherheiten in Form von der Kovarianz-Matrix dargestellt. Der Temperaturanstieg kann entsprechend ausgegeben werden:

print(f"Temperaturanstieg pro Jahr (von 1981 bis 2020): {model_lin1[0][0]:.3f}°C/Jahr")
print(f"Temperaturanstieg seit Beginn der Messung: {(y_model.iloc[-1]-y_model.iloc[0]):.3f}°C")
Temperaturanstieg pro Jahr (von 1981 bis 2020): 0.008°C/Jahr
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[20], line 2
      1 print(f"Temperaturanstieg pro Jahr (von 1981 bis 2020): {model_lin1[0][0]:.3f}°C/Jahr")
----> 2 print(f"Temperaturanstieg seit Beginn der Messung: {(y_model.iloc[-1]-y_model.iloc[0]):.3f}°C")

NameError: name 'y_model' is not defined

Warnung

Die lineare Regression bezieht hier den ganzen Zeitraum mit ein! Im folgenden betrachten wir für den Temperaturgradienten nur die Daten von 1980 bis 2020!

x_1980=global_mean.loc[global_mean["Year"] >= 1980,"Year"]
y_1980=global_mean.loc[global_mean["Year"] >= 1980,"No_Smoothing"]
y_err = global_mean.loc[global_mean["Year"] >= 1980,"uncertainty"]

model_lin2 = np.polyfit(x_1980, y_1980, deg=1, w=1/y_err, cov=True) # 1. Wert = Anstieg , 2. Wert = Schnittpunkt mit y-Achse
y_model_2 = model_lin2[0][0]*x+model_lin2[0][1] # Modell einer linearen Regression

plt.figure(figsize=(10,5))
plt.rcParams['font.size'] = 10;
plt.ylabel("Jahresmitteltemperaturabweichung [°C]")
plt.xlabel("Jahr")
plt.errorbar(global_mean["Year"],global_mean["No_Smoothing"], yerr=global_mean["uncertainty"], ls="-", lw=1, marker="s", ms=3, color="tab:gray", alpha=0.5, label="Messwerte");
plt.plot(x,y_model_2, ls="-", lw=3, color="tab:red", label=f"Modell: y=({model_lin2[0][0]*1000:.3f}+-{np.sqrt(model_lin2[1][0][0]*1000):.3f})1e-3*x+({model_lin2[0][1]:.3f}+-{np.sqrt(model_lin2[1][1][1]):.3f})");
plt.legend();
plt.grid();
plt.savefig('klima_plot5.png')
plt.savefig('klima_plot5.pdf')
../_images/d547568e02caa6aa4ce78a35f117c319515855bfc10c9514609fc3b915646146.png
# Temperaturanstieg pro Jahr:
print(f"Temperaturanstieg pro Jahr (von 1980 bis 2020): {model_lin2[0][0]:.3f}°C/Jahr")

# Vorhersage für Jahr 2050
x_val = 2050
y_pred = np.polyval(model_lin2[0], x_val)
print("Vorhersage für 2050:", y_pred)
Temperaturanstieg pro Jahr (von 1980 bis 2020): 0.020°C/Jahr
Vorhersage für 2050: 1.5430715854193977

Beliebige Funktion fitten#

Mit der Polyfit Funktion können in erster Linie Polynome gefittet werden. Für beliebige Modellierung, wie z.B. Exponential-Funktionen, werden die Funktionen in aller Regel selber definiert. Hierfür eignet sich die scipy-Funktion curve_fit.

from scipy.optimize import curve_fit

Als erstes wird die Funktion deklariert:

def quadratic_fit(x, a, b, c):
    return a*x**2 + b*x + c

Die Funktion wird anschließend an die Daten angepasst:

# Nutze alle Daten über die gesamte Zeit, nicht gefiltert:
x=global_mean["Year"]
y=global_mean["No_Smoothing"]
y_err = global_mean["uncertainty"]

model_quadr = curve_fit(quadratic_fit, x, y)

print(model_quadr)
(array([ 9.43408527e-05, -3.60141597e-01,  3.43436749e+02]), array([[ 4.12615232e-11, -1.61084987e-07,  1.57146658e-04],
       [-1.61084987e-07,  6.28933616e-04, -6.13613427e-01],
       [ 1.57146658e-04, -6.13613427e-01,  5.98721651e+02]]))

Man sieht, je komplexer die Modellfunktion ist, desto schwieriger ist es die Matrizen zu lesen. Daher können die Modelparameter und die Kovarianzmatrix auch separat voneinander gespeichert werden:

popt, pcov = curve_fit(quadratic_fit, x, y)
print('Modellparameter:', popt)
print('Mit der Kovarianz-Matrix und entsprechende Unsicherheiten:', pcov)

print(model_quadr[0][1])
Modellparameter: [ 9.43408527e-05 -3.60141597e-01  3.43436749e+02]
Mit der Kovarianz-Matrix und entsprechende Unsicherheiten: [[ 4.12615232e-11 -1.61084987e-07  1.57146658e-04]
 [-1.61084987e-07  6.28933616e-04 -6.13613427e-01]
 [ 1.57146658e-04 -6.13613427e-01  5.98721651e+02]]
-0.3601415966534972
plt.figure(figsize=(10,5))
plt.rcParams['font.size'] = 10;
plt.ylabel("Jahresmitteltemperaturabweichung [°C]")
plt.xlabel("Jahr")
plt.errorbar(global_mean["Year"],global_mean["No_Smoothing"], yerr=global_mean["uncertainty"], ls="-", lw=1, marker="s", ms=3, color="tab:gray", alpha=0.5, label="Messwerte");
plt.plot(x,quadratic_fit(x,*popt), ls="-", lw=3, color="tab:red", label=f"Modell: y=({model_quadr[0][0]*1000:.3f}+-{np.sqrt(model_quadr[1][0][0]*1000):.3f})1e-3*x^2+({model_quadr[0][1]:.3f}+-{np.sqrt(model_quadr[1][1][1]):.3f})*x + ({model_quadr[0][2]:.3f}+-{np.sqrt(model_quadr[1][2][2]):.3f})");
plt.legend();
plt.grid();
plt.savefig('klima_plot6.png')
plt.savefig('klima_plot6.pdf')
../_images/b0d635c9477943c25a5223f8f89338508f0152b2a8c7bdf2da3f19417c1e3c2c.png

Vorhersage für das Jahr 2050#

# Vorhersage für Jahr 2050
x_val = 2050
y_pred_lin1 = np.polyval(model_lin1[0], x_val)
print("Lineares Modell 1 (1880-2025):\t", y_pred_lin1)

y_pred_lin2 = np.polyval(model_lin2[0], x_val)
print("Lineares Modell 2 (1981-2025):\t", y_pred_lin2)

y_pred_quadr = np.polyval(model_quadr[0], x_val)
print("Quadratisches Modell:\t\t", y_pred_quadr)
Lineares Modell 1 (1880-2025):	 0.8731445441662782
Lineares Modell 2 (1981-2025):	 1.5430715854193977
Quadratisches Modell:		 1.6139089222256189

Wie gut passen die Modelle?#

Ein Maß dafür, wie gut ein einzelnes Modell die Daten beschreibt, liefert der sogenannte \(\chi^2\) Wert.

# Residuen berechnen:
residuals_lin1 = y - (model_lin1[0][0]*x + model_lin1[0][1])
residuals_lin2 = y_1980 - (model_lin2[0][0]*x_1980 + model_lin2[0][1])
residuals_quadr = y - quadratic_fit(x,*popt)


# Für Chi^2 quadratisch aufsummieren:
chi2_lin1 = np.sum((residuals_lin1 / y_err) ** 2)  
chi2_lin2 = np.sum((residuals_lin2 / y_err) ** 2)  
chi2_quadr = np.sum((residuals_quadr / y_err) ** 2)  

# Freiheitsgrade (Anzahl der Datenpunkte - Anzahl der Modellparameter)
dof_lin1 = len(x) - len(model_lin1[0])
dof_lin2 = len(x_1980) - len(model_lin2[0])
dof_quadr = len(x) - len(model_quadr[0])

# Reduziertes Chi^2
chi2_red_lin1 = chi2_lin1 / dof_lin1
chi2_red_lin2 = chi2_lin2 / dof_lin2
chi2_red_quadr = chi2_quadr / dof_quadr

# Ergebnisse in Tabelle
results = pd.DataFrame({
    "Modell": ["Linear (ges. Reihe)", "Linear (ab 1981)", "Quadratisch"],
    "Chi²": [chi2_lin1, chi2_lin2, chi2_quadr],
    "Freiheitsgrade": [dof_lin1, dof_lin2, dof_quadr],
    "Chi²_red": [chi2_red_lin1, chi2_red_lin2, chi2_red_quadr]
})

print(results.to_string(index=False, float_format="%.3f"))
             Modell   Chi²  Freiheitsgrade  Chi²_red
Linear (ges. Reihe) 84.072             143     0.588
   Linear (ab 1981)  7.899              43     0.184
        Quadratisch 33.375             142     0.235
plt.figure(figsize=(10,5))
plt.rcParams['font.size'] = 10;
plt.ylabel("Residuen")
plt.xlabel("Jahr")
plt.plot(x,residuals_lin1, label = 'Lineares Modell (ges. Reihe)')
plt.plot(x_1980,residuals_lin2, label = 'Lineares Modell (ab 1981)')
plt.plot(x,residuals_quadr, label = 'Quadratisches Modell')
plt.legend()
plt.grid()
plt.savefig('klima_plot7.png')
plt.savefig('klima_plot7.pdf')
../_images/2b44bd7cdd790534910c93851038f63075a6bdcecabdcb705cd2dd4ae0ee6448.png

Alle reduzierten \(\chi^2\)-Werte liegen unter 1. Das deutet darauf hin, dass die angenommenen Fehlerbalken zu groß gewählt worden sind und das Modell zu gut passt. Das 2. lineare Modell liefert den geringsten Wert und ist somit mathematisch gesehen das beste Modell. Jedoch bezieht dies nicht die komplette Zeitserie mit ein. Das quadratische Modell passt besser über die gesamte Zeitserie betrachtet. Alle Modelle sind akzeptabel, aber unsere geschätzten Unsicherheiten von 0,25 Kelvin dominieren.