2.1. Grundwasservorhersage mittels eines Random Forests, Hessen#

author: Noura Barakat, Melvin Hildebrand

area: Hessen

date: 26.08.2024

## Erstmal importieren wir alle benötigten libraries

import os, sys
# import netCDF4
import pandas as pd
#import datetime
#from datetime import date, datetime, timedelta
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import xarray as xr
import geopandas as gpd
import rioxarray
import xarray as xr
import glob

import warnings
warnings.filterwarnings("ignore", "use_inf_as_na")
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[1], line 11
      9 import matplotlib.pyplot as plt
     10 import seaborn as sns
---> 11 import xarray as xr
     12 import geopandas as gpd
     13 import rioxarray

ModuleNotFoundError: No module named 'xarray'
from io import StringIO
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
import math
from math import sqrt
pwd = os.getcwd()
print(pwd)
C:\Users\nunub\OneDrive\Documents\KI_2024

2.1.1. Einlesen unserer vorverarbeiteten Daten#

Wir verwenden hier bereits vorverarbeitete Daten. Der Datensatz umfasst Daten von einer Grundwassermesstelle (ID: 10319, Name: Lettgenbrunn) in Hessen. Zur Vorverarbeitung haben wir die .nc-Datei auf das AOI (Gebiet von Interesse) zugeschnitten und die .nc-Dateien für bestimmte Jahre zu einer Datei zusammengeführt. Dann haben wir Klimadaten (Temperatur- und Niederschlagswerte) an bestimmten Standorten extrahiert, sie mit GW-Pegel-Daten (Grundwasserpegel-Daten) zusammengeführt und die Daten für die Messtelle als einzelne CSV-Datei gespeichert. Diese Datei ist nun die Grundlage für den Aufbau unseres Modells.

Unser nächster Schritt ist der Modellaufbau.

data = pd.read_csv("./data/HYRAS_GWData_csv/HE_10319_weeklyData_HYRAS_GWData.csv", skipinitialspace=True).drop(['Unnamed: 0'], axis=1)

data.shape[0]
835
col2use = ['pr', 'tas', 'GWL']
#data = data.replace(np.inf, np.nan)
data.isnull().sum()
Date      0
pr        0
tas       0
GWL     148
dtype: int64
## hier werfen wir die Werte ohne Angaaben raus

data.dropna(axis=0, inplace=True)
data
Date pr tas GWL
148 1992-11-02 10.2 7.900000 371.92
149 1992-11-09 35.0 6.800000 372.46
150 1992-11-16 22.8 6.914286 373.40
151 1992-11-23 21.6 8.500000 376.69
152 1992-11-30 9.5 7.385714 376.83
... ... ... ... ...
830 2005-11-28 5.9 2.714286 360.67
831 2005-12-05 7.1 3.157143 360.85
832 2005-12-12 10.2 3.200000 361.95
833 2005-12-19 12.8 2.514286 363.28
834 2005-12-26 15.3 -0.950000 364.93

687 rows × 4 columns

sns.pairplot(data[col2use], corner=True)
plt.show()
../../../_images/5bbc996913914cb4864246b0cd563a4f4b92f9503cf33997880fec35b44d70d9.png

Nun integrieren wir in unseren Datensatz pro Grundwasserpegelwert die Niederschlags- (pr) und Temperaturwerte (tas) von jeweils 2, 5 und 10 Wochen im Vorhinein. Dieser Schritt ist wichtig, um die Präzision der Vorhersage zu verbessern. Der aktuelle Grundwasserpegel ist grundsätzlich auch abhängig von den Niederschlagswerten der vergangenen Wochen. Zudem kann die Temperatur der vergangenen Wochen auch ein Indikator für die Bodenfeuchte sein.

data["pr_sum_rolling_2w"] = data["pr"].rolling(2).sum()
data["pr_sum_rolling_5w"] = data["pr"].rolling(5).sum()
data["pr_sum_rolling_10w"] = data["pr"].rolling(10).sum()
data
Date pr tas GWL pr_sum_rolling_2w pr_sum_rolling_5w pr_sum_rolling_10w
148 1992-11-02 10.2 7.900000 371.92 NaN NaN NaN
149 1992-11-09 35.0 6.800000 372.46 45.2 NaN NaN
150 1992-11-16 22.8 6.914286 373.40 57.8 NaN NaN
151 1992-11-23 21.6 8.500000 376.69 44.4 NaN NaN
152 1992-11-30 9.5 7.385714 376.83 31.1 99.1 NaN
... ... ... ... ... ... ... ...
830 2005-11-28 5.9 2.714286 360.67 12.6 38.6 91.7
831 2005-12-05 7.1 3.157143 360.85 13.0 29.8 52.8
832 2005-12-12 10.2 3.200000 361.95 17.3 39.9 62.0
833 2005-12-19 12.8 2.514286 363.28 23.0 42.7 74.8
834 2005-12-26 15.3 -0.950000 364.93 28.1 51.3 84.5

687 rows × 7 columns

data["tas_sum_rolling_2w"] = data["tas"].rolling(2).sum()
data["tas_sum_rolling_5w"] = data["tas"].rolling(5).sum()
data["tas_sum_rolling_10w"] = data["tas"].rolling(10).sum()
data
Date pr tas GWL pr_sum_rolling_2w pr_sum_rolling_5w pr_sum_rolling_10w tas_sum_rolling_2w tas_sum_rolling_5w tas_sum_rolling_10w
148 1992-11-02 10.2 7.900000 371.92 NaN NaN NaN NaN NaN NaN
149 1992-11-09 35.0 6.800000 372.46 45.2 NaN NaN 14.700000 NaN NaN
150 1992-11-16 22.8 6.914286 373.40 57.8 NaN NaN 13.714286 NaN NaN
151 1992-11-23 21.6 8.500000 376.69 44.4 NaN NaN 15.414286 NaN NaN
152 1992-11-30 9.5 7.385714 376.83 31.1 99.1 NaN 15.885714 37.500000 NaN
... ... ... ... ... ... ... ... ... ... ...
830 2005-11-28 5.9 2.714286 360.67 12.6 38.6 91.7 4.185714 26.485715 91.228571
831 2005-12-05 7.1 3.157143 360.85 13.0 29.8 52.8 5.871428 18.157143 80.500000
832 2005-12-12 10.2 3.200000 361.95 17.3 39.9 62.0 6.357143 13.842857 69.157143
833 2005-12-19 12.8 2.514286 363.28 23.0 42.7 74.8 5.714286 13.057143 59.800000
834 2005-12-26 15.3 -0.950000 364.93 28.1 51.3 84.5 1.564286 10.635714 47.421428

687 rows × 10 columns

2.1.2. Was ist ein Random Forest?#

Ein Random Forest ist ein Ensemble-Lernverfahren, das aus vielen Entscheidungsbäumen besteht, die während des Trainings erstellt werden. Jeder Baum wird aus einem zufälligen Teil der Trainingsdaten und zufällig ausgewählten Features gebildet. Die Vorhersagen der einzelnen Bäume werden dann zu einer Gesamtvorhersage aggregiert, was die Genauigkeit erhöht und Überanpassung (Overfitting) reduziert. Random Forests sind robust, flexibel und können sowohl für Klassifikations- als auch für Regressionsaufgaben verwendet werden.

2.1.2.1. Schritt 1: Datensatz in Trainings- and Testdaten aufteilen#

data = data.set_index(pd.DatetimeIndex(data['Date']))
del data['Date']
data
pr tas GWL pr_sum_rolling_2w pr_sum_rolling_5w pr_sum_rolling_10w tas_sum_rolling_2w tas_sum_rolling_5w tas_sum_rolling_10w
Date
1992-11-02 10.2 7.900000 371.92 NaN NaN NaN NaN NaN NaN
1992-11-09 35.0 6.800000 372.46 45.2 NaN NaN 14.700000 NaN NaN
1992-11-16 22.8 6.914286 373.40 57.8 NaN NaN 13.714286 NaN NaN
1992-11-23 21.6 8.500000 376.69 44.4 NaN NaN 15.414286 NaN NaN
1992-11-30 9.5 7.385714 376.83 31.1 99.1 NaN 15.885714 37.500000 NaN
... ... ... ... ... ... ... ... ... ...
2005-11-28 5.9 2.714286 360.67 12.6 38.6 91.7 4.185714 26.485715 91.228571
2005-12-05 7.1 3.157143 360.85 13.0 29.8 52.8 5.871428 18.157143 80.500000
2005-12-12 10.2 3.200000 361.95 17.3 39.9 62.0 6.357143 13.842857 69.157143
2005-12-19 12.8 2.514286 363.28 23.0 42.7 74.8 5.714286 13.057143 59.800000
2005-12-26 15.3 -0.950000 364.93 28.1 51.3 84.5 1.564286 10.635714 47.421428

687 rows × 9 columns

# Features und target trennen
X = data.drop(columns=['GWL'])
y = data['GWL']
# Daten in Trainings- und Testmengen aufteilen
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

2.1.2.2. Schritt 2: Initiales Modell trainieren#

# Initialwert und Anzahl der Bäume für den Random Forest festlegen
seed = 196
n_estimators = 100

# RandomForest erstellen, X und y definieren und Modell trainieren
model = RandomForestRegressor(n_estimators = n_estimators, random_state = seed, max_features = 1.0,
                               min_samples_split = 2, min_samples_leaf = 1, max_depth = None)
model.fit(X_train, y_train)
RandomForestRegressor(random_state=196)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.

Die Zeile model.fit(X_train, y_train) startet den Trainingsprozess eines Random Forest Regressors, nachdem der Initialwert (Seed) und die Anzahl der Bäume festgelegt wurden. Die Parameter n_estimators und random_state legen die Anzahl der Entscheidungsbäume und den Zufallsstartwert fest. Weitere Parameter wie max_features, min_samples_split, min_samples_leaf und max_depth steuern die Struktur und die Komplexität der einzelnen Bäume. Während des Trainingsprozesses wird jeder Baum auf einem zufälligen Teil der Trainingsdaten (X_train, y_train) aufgebaut, wobei eine zufällige Auswahl von Merkmalen für die Splits verwendet wird. Durch die Aggregation der Vorhersagen aller Bäume wird ein robustes Modell erzeugt, das eine hohe Genauigkeit bei der Vorhersage ermöglicht und gleichzeitig Überanpassung (Overfitting) reduziert.

2.1.2.3. Schritt 3: Evaluieren#

# Bestimmheitsmaß und erklärte Varianz ausgeben lassen
print(f"Coefficient of determination: {model.score(X_train, y_train)}")
print(f"% Var explained: {model.score(X_train, y_train) * 100}")
Coefficient of determination: 0.9530484749262945
% Var explained: 95.30484749262945

In diesem Codeabschnitt werden 2 wichtige Leistungskennzahlen für das Modell ausgegeben: das Bestimmtheitsmaß und die erklärte Varianz.

Zusammengefasst gibt das Bestimmtheitsmaß bzw. die erklärte Varianz Auskunft darüber, wie gut ein Modell die Streuung der Daten erklärt. Ein höheres Bestimmtheitsmaß deutet auf ein besseres Modell hin, das die Daten gut beschreibt. Unser Ergebnis von ca. 0.95 suggeriert eine hohe Vorhersagegenauigkeit.

y_predictions = model.predict(X_test)
# Import library for metrics
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
# Mean absolute error (MAE)
mae = mean_absolute_error(y_test, y_predictions)
# Mean squared error (MSE)
mse = mean_squared_error(y_test, y_predictions)
# R-squared scores
r2 = r2_score(y_test, y_predictions)
mae, mse, r2
(2.0546712560386524, 8.391051875257594, 0.6901583928593296)

In diesem Codeabschnitt werden Vorhersagen für die Testdaten erstellt. Diese Vorhersagen werden dann mit den tatsächlichen Werten verglichen und im Anschluss der durchschnittliche absolute Fehler, der durchschnittliche quadrierte Fehler und das Bestmmtheistmaß berechnet. Im Durchschnitt weichen die Vorhersagen des Modells um etwa 2,05 Einheiten (hier Meter ü. NN) von den tatsächlichen Werten ab. Ein R²-Wert von 0,69 bedeutet, dass etwa 69 % der Varianz in den tatsächlichen Werten durch das Modell erklärt werden.

data
pr tas GWL pr_sum_rolling_2w pr_sum_rolling_5w pr_sum_rolling_10w tas_sum_rolling_2w tas_sum_rolling_5w tas_sum_rolling_10w
Date
1992-11-02 10.2 7.900000 371.92 NaN NaN NaN NaN NaN NaN
1992-11-09 35.0 6.800000 372.46 45.2 NaN NaN 14.700000 NaN NaN
1992-11-16 22.8 6.914286 373.40 57.8 NaN NaN 13.714286 NaN NaN
1992-11-23 21.6 8.500000 376.69 44.4 NaN NaN 15.414286 NaN NaN
1992-11-30 9.5 7.385714 376.83 31.1 99.1 NaN 15.885714 37.500000 NaN
... ... ... ... ... ... ... ... ... ...
2005-11-28 5.9 2.714286 360.67 12.6 38.6 91.7 4.185714 26.485715 91.228571
2005-12-05 7.1 3.157143 360.85 13.0 29.8 52.8 5.871428 18.157143 80.500000
2005-12-12 10.2 3.200000 361.95 17.3 39.9 62.0 6.357143 13.842857 69.157143
2005-12-19 12.8 2.514286 363.28 23.0 42.7 74.8 5.714286 13.057143 59.800000
2005-12-26 15.3 -0.950000 364.93 28.1 51.3 84.5 1.564286 10.635714 47.421428

687 rows × 9 columns

Allerdings könnten unsere Ergebnisse auch an der Auswahl der Trainings- und Testdaten liegen. Um den Einfluss dieser Aufteilung zu reduzieren, führen wir eine sogenannte k-fache Kreuzvalidierung (k-fold cross validation) durch. Das bedeutet, dass wir die Trainingsdaten in 10 gleich große Teile aufteilen, also \(k=10\) in diesem Fall. Dann trainieren wir das Modell auf neun der 10 Teile und verwenden den 10. Teil zur Validierung des Modells wie oben beschrieben. Dieser Prozess wird 10 Mal wiederholt, wobei jedes Mal ein anderer Teil zur Validierung verwendet wird. Am Ende erhalten wir 10 RMSE-Werte, die uns einen Einblick in die Validität des oben genannten Wertes geben.

from sklearn.model_selection import cross_val_score
scores = - cross_val_score(estimator=model, X=X_train, y=y_train, cv=10, scoring='neg_mean_squared_error', n_jobs=1)
print(scores)
print(np.mean(scores), np.std(scores))
[ 7.99462509  7.6769795   8.86374395  7.83255243  9.20695896 10.75050609
  6.92620051  9.73670632  8.32539769 10.4834025 ]
8.779707304687633 1.1942959488195237

2.1.2.4. Schritt 4: Hyperparameter Tuning#

Hier verwenden wir Grid Search, um die besten Hyperparameter für unser Modell zu finden. Hyperparameter sind Parameter, die nicht durch das Modelltraining selbst optimiert werden, sondern im Vorhinein festgelegt werden müssen. Sie steuern die Lernprozesse und die Architektur des Modells. Im Gegensatz zu Modellparametern, die während des Trainings durch Optimierungsalgorithmen angepasst werden, werden Hyperparameter manuell oder automatisiert (z. B. durch Grid Search) bestimmt. Grid Search durchsucht systematisch einen vordefinierten Bereich von Hyperparametern und evaluiert die Modellleistung für jede Kombination. Dies ermöglicht es, die optimalen Hyperparameter auszuwählen, die die Modellleistung maximieren und somit zu besseren Vorhersagen führen.

2.1.2.4.1. gridSearchCV#

from sklearn.model_selection import GridSearchCV
rfc = RandomForestRegressor(random_state=42)
param_grid = { 
    'n_estimators': [50, 100, 150, 200, 250, 300],
   # 'max_features': ['auto', 'sqrt', 'log2'],
    'max_depth' : [4,5,6,7,8],
#    'criterion' :['gini', 'entropy']
}
CV_rfc = GridSearchCV(estimator=rfc, param_grid=param_grid, cv= 5)
CV_rfc.fit(X_train, y_train)
GridSearchCV(cv=5, estimator=RandomForestRegressor(random_state=42),
             param_grid={'max_depth': [4, 5, 6, 7, 8],
                         'n_estimators': [50, 100, 150, 200, 250, 300]})
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
best_params = CV_rfc.best_params_

best_params
{'max_depth': 8, 'n_estimators': 150}

Der Hyperparameter max_depth bestimmt die maximale Tiefe der Entscheidungsbäume im Random Forest-Modell. Eine Tiefe von 8 bedeutet, dass jeder Baum im Random Forest maximal 8 Ebenen tief ist. Der Hyperparameter n_estimators gibt die Anzahl der Entscheidungsbäume im Random Forest-Modell an. In diesem Fall bedeutet n_estimators: 150, dass der Random Forest aus 150 Entscheidungsbäumen besteht.

# Initialwert für den Random Forest festlegen
seed = 196


# RandomForest erstellen, X und y definieren und Modell trainieren
model = RandomForestRegressor(n_estimators = best_params["n_estimators"], 
                              random_state = seed, 
                              max_features = 1.0,
                              min_samples_split = 2, 
                              min_samples_leaf = 1, 
                              max_depth = best_params["max_depth"])
model.fit(X_train, y_train)
RandomForestRegressor(max_depth=8, n_estimators=150, random_state=196)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
X_train
pr tas pr_sum_rolling_2w pr_sum_rolling_5w pr_sum_rolling_10w tas_sum_rolling_2w tas_sum_rolling_5w tas_sum_rolling_10w
Date
1998-03-02 24.1 9.985715 26.400000 42.100000 95.700000 17.271429 30.771429 48.042858
1994-12-12 2.8 5.185714 8.900000 50.900000 79.000000 13.685714 38.842857 87.999999
1994-05-30 12.2 15.957143 33.100000 120.300000 196.299999 30.742857 76.385714 128.299999
1993-10-25 0.0 4.757142 20.100000 74.999999 155.799999 11.185713 47.914285 123.228570
1997-01-06 0.0 -3.971428 8.700000 55.899998 169.199998 -11.628571 -14.571429 11.257143
... ... ... ... ... ... ... ... ...
1994-03-14 18.7 7.628571 32.300000 55.200000 98.300000 17.528572 28.728572 48.328572
1994-11-14 16.4 10.528571 25.800000 44.500000 110.500000 19.814285 49.799999 111.528569
1998-01-05 7.0 6.714286 23.400000 76.100000 145.900000 13.614286 28.142858 52.828572
2001-03-05 10.6 8.071428 21.899999 49.299999 122.699997 11.242857 28.728571 44.385714
1994-10-17 3.2 8.685714 3.200000 35.800000 152.700000 18.571428 56.799998 140.028566

549 rows × 8 columns

2.1.2.5. Schritt 5: Plotten#

stats = model.get_params()
feature_importance = model.feature_importances_
features = X.columns
var_imp_df = pd.DataFrame({'Feature': features, 'Importance': feature_importance})
var_imp_df = var_imp_df.sort_values(by = 'Importance', ascending = False).head(54)
var_imp_10 = var_imp_df.head(10)
# Plotten der wichtigsten Variablen
plt.figure(figsize = (8, 3))
sns.barplot(x = 'Importance', y = 'Feature', data = var_imp_10, color = 'pink')
plt.title("δ¹⁸O")
plt.xlabel("Importance score")
plt.ylabel("Feature")
plt.text(0, -1.5, f"n_estimators = {n_estimators}, max_features = {max_features}\nRMSE = {rmse:.2f}, R² = {r2:.2f}", 
         fontsize = 10, ha = 'left', va = 'center')
plt.show()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[373], line 7
      5 plt.xlabel("Importance score")
      6 plt.ylabel("Feature")
----> 7 plt.text(0, -1.5, f"n_estimators = {n_estimators}, max_features = {max_features}\nRMSE = {rmse:.2f}, R² = {r2:.2f}", 
      8          fontsize = 10, ha = 'left', va = 'center')
      9 plt.show()

NameError: name 'max_features' is not defined
../../../_images/3574d8c85ca72c4cb3d49be8d83434d94fb6a7021f41373809b7db100ad3ebc9.png

Hierbei ist klar geworden, dass die Variable der Temperatur 10 Wochen im Vorhinein die Wichtigste ist.

data_1992_2004 = data[(data.index.year >= 1992) & (data.index.year <= 2004)]
data_2005 = data[(data.index.year >= 2005)]
data.index.year
Index([1992, 1992, 1992, 1992, 1992, 1992, 1992, 1992, 1992, 1993,
       ...
       2005, 2005, 2005, 2005, 2005, 2005, 2005, 2005, 2005, 2005],
      dtype='int32', name='Date', length=687)
data_1992_2004
pr tas GWL pr_sum_rolling_2w pr_sum_rolling_5w pr_sum_rolling_10w tas_sum_rolling_2w tas_sum_rolling_5w tas_sum_rolling_10w
Date
1992-11-02 10.2 7.900000 371.92 NaN NaN NaN NaN NaN NaN
1992-11-09 35.0 6.800000 372.46 45.2 NaN NaN 14.700000 NaN NaN
1992-11-16 22.8 6.914286 373.40 57.8 NaN NaN 13.714286 NaN NaN
1992-11-23 21.6 8.500000 376.69 44.4 NaN NaN 15.414286 NaN NaN
1992-11-30 9.5 7.385714 376.83 31.1 99.1 NaN 15.885714 37.500000 NaN
... ... ... ... ... ... ... ... ... ...
2004-11-29 3.1 4.457143 374.78 7.5 41.0 120.200002 9.771429 27.157143 87.857143
2004-12-06 0.0 0.528571 372.96 3.1 36.2 107.200002 4.985714 17.628572 74.271429
2004-12-13 12.5 1.028571 371.89 12.5 27.7 99.500002 1.557143 15.457143 61.714286
2004-12-20 4.6 2.157143 371.48 17.1 24.6 90.500002 3.185714 13.485715 54.757144
2004-12-27 12.6 3.357143 373.36 17.2 32.8 98.600002 5.514286 11.528572 45.842858

635 rows × 9 columns

# Daten zwischen 1992 und 2004 plotten

plt.figure(figsize=(10, 5))
plt.plot(data_1992_2004.index, data_1992_2004['GWL'], marker='o', linestyle='-', color='b', label='1992-2004')
plt.xlabel('Jahr')
plt.ylabel('Grundwasserpegel')
plt.title('Grundwasserpegel von 1992 bis 2004')
plt.legend()
plt.grid(True)
plt.ylim(340, 390) 
plt.show()

# Daten von 2005 plotten

plt.figure(figsize=(10, 5))
plt.plot(data_2005.index, data_2005['GWL'], marker='o', linestyle='-', color='r', label='2005')
plt.xlabel('Jahr')
plt.ylabel('Grundwasserpegel')
plt.title('Grundwasserpegel im Jahr 2005')
plt.legend()
plt.grid(True)
plt.ylim(340, 390)  
plt.show()
../../../_images/8cdee09434be318c41efd9f585720b2be8de645c4c7eb1a69f6f272602837822.png ../../../_images/0789e476f196c10f74b562f65c5d6606b75cb3a52b53b5509ed37a82cd860a55.png
X_val, y_val=data_2005.drop(columns=['GWL']), data_2005['GWL']
y_predictions_2005 = model.predict(X_val) 
# Die Vorhersagewerte und die tatsächlichen Werte aus 2005 gleichzeitig plotte, um Modelleistung zu sehen. 
plt.plot(X_val.index, y_predictions_2005, marker='o', linestyle='-', color = 'red', label = 'Vorhersagewerte')
plt.plot(X_val.index, y_val, marker='o', linestyle='-', color = 'green', label = 'tatsächliche Werte')
plt.xlabel('Jahr')
plt.ylabel('Grundwasserpegel')
plt.title('Grundwasserpegel im Jahr 2005')
plt.legend()
plt.show()
../../../_images/5493c8c5e8edc741f93e7e1fac653c965d87b118ed8a4400c7d48ae6251ffd53.png

Hierbei stellt der rote Graph die vom Modell vorhergesagten Were an, wobei der grüne Graph die tatsächlichen Daten von 2005 darstellt. Wir sehen, dass die Diskrepanzen zwischen den 2 Graphen relativ klein sind.

2.1.3. Diskussion#

Zusammenfassend haben wir hier ein Random-Forest-Modell entwickelt, um die Grundwasserstände an der Messstelle Lettgenbrunn in Hessen vorherzusagen. Dafür wurden die Grundwasserdaten der Messstelle, sowie die Temperatur- und Niederschlagsdaten für den Zeitraum von 1992 bis 2005 gesammelt und in das Modell eingespeist. Die Entscheidung, die Vorhersage auf eine einzelne Messstelle zu beschränken, wurde getroffen, um Overfitting zu vermeiden. Anfangs hatten wir versucht, die Daten mehrerer Messstellen zu aggregieren, jedoch führte dies zu einer geringeren Modellleistung. Dies lag wahrscheinlich an der hohen Heterogenität der Daten, insbesondere der Niederschlags- und Temperaturwerte, die stark zwischen den verschiedenen Messstellen variierten.

Ein wesentlicher Schritt zur Verbesserung der Modellgenauigkeit war die Erweiterung der Features um historische Daten. Neben den aktuellen Temperatur- und Niederschlagswerten wurden auch die Werte von 2, 5 und 10 Wochen zuvor einbezogen. Diese Erweiterung der Features hat sich als vorteilhaft erwiesen, da Temperatur- und Niederschlagswerte wichtige Indikatoren für die Bodenfeuchte sind und historische Werte einen bedeutenden Einfluss auf den aktuellen Grundwasserstand haben. Die Integration dieser zusätzlichen Datenpunkte hat die Vorhersagegenauigkeit deutlich verbessert.

Das Modell erzielte bei den Trainingswerten ein Bestimmtheitsmaß (R²) bzw. eine erklärte Varianz von ca. 95%, was auf eine gute Modellleistung hinweist. Bei den Testdaten erreichte das Modell ein Bestimmheitsmaß von ca. 69%. Zur Validierung des Modells wurden die vorhergesagten Grundwasserstände für das Jahr 2005 mit den tatsächlichen Werten verglichen. Die simultane Darstellung beider Datensätze in einem Plot zeigte, dass die vorhergesagten Werte den tatsächlichen Werten ziemlich nahe kamen.

Trotzdem beobachten wir, dass die Vorhersage fehlerhaft ist. Besonders im Umgang mit extremen Werten, scheint das Modell Fehler zu machen. Ein möglicher Grund hierfür könnte die Komplexität von Grundwasserdaten generell und die limitierte Datenverfügbarkeit sein. Grundwasserstände werden von zahlreichen Faktoren beeinflusst, die in komplexen, nichtlinearen Beziehungen zueinander stehen. Einflussfaktoren wie die Bodenbeschaffenheit, anthropogene Einflüsse, die Landnutzung und die Vegetation wurden bei unserem Modell außer Acht gelassen. Die Intergration der genannten Einflussfaktoren in den Datensatz hätte vermutlich ein genaueres Vorhersagergebnis erzielt. Ferner neigen Random Forest-Modelle dazu, extremere Werte zu glätten, da sie auf dem Durchschnitt vieler Entscheidungsbäume basieren und extreme Ausprägungen in den Entscheidungsbäumen in den Mittelwert miteinfließen. Dies kann dazu führen, dass Extremwerte flacher vorausgesagt werden, als sie tatsächlich sind, wie das in unserem Modell bis ca. August 2005 der Fall ist. Unser Modell ist etwas überangepasst (overfittet), was durch die Diskrepanz zwischen den erklärten Varianzen der Trainings- und Testdaten gezeigt wird. Dies spiegelt sich in der teilweise Ungenauigkeit unserer Vorhersage wider. Außerdem sagt das Modell die Fluktuationen zum Teil extremer voraus, als sie eigentlich sind, z.B. zwischen September und November 2005. Dies könnte daran liegen, dass in den Trainingsdaten die Fluktuationen zum Ende des Jahres tendenziell extremer waren, also Extremwerte überrepräsentiert waren und das Modell diese Extremwerte in der Vorhersage überbetont.

Insgesamt zeigt die Modellleistung jedoch, dass der Random Forest eine leistungsfähige Methode zur Vorhersage von Grundwasserständen ist, insbesondere wenn relevante historische Daten einbezogen werden. Weitere Verfeinerungen des Modells und eine erweiterte Datengrundlage um weitere Eiflussfaktoren für Gundwasserstände könnten die Vorhersagegenauigkeit weiter verbessern.