1.1. Grundwasservorhersage mittels eines Decision Trees, Brandenburg#

author: Anne Schwind

area: Brandenburg, Stolpe BB 30400591

date: 26.08.2024

# First, let's import all the needed libraries. 

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

import warnings
warnings.filterwarnings("ignore", "use_inf_as_na")

from sklearn.tree import DecisionTreeRegressor 
from sklearn.tree import plot_tree
from sklearn.model_selection import train_test_split  
from sklearn import metrics                            
from sklearn import tree
from sklearn.metrics import mean_squared_error, r2_score

1.1.1. Datenverarbeitung#

Für dieses Beispiel verwenden wir eine wöchentliche Daten verschiedener hydrometeorlogischer Größen aus dem Projekt “HYRAS - Hydrometeorologische Rasterdaten” aus Brandenburg.

Die Daten wurden heruntergeladen vom LfU - Landesamt für Umwelt Brandenburg über diesen Link.

Stand der Daten: 2018

Nun müssen wir die Daten der Grundwasservorhersage einlesen und organisieren. Das bedeutet, dass wir die Daten einlesen, ungenutzte Spalten entfernen und die Spalten für besser Übersichtlichkeit unbenennen. Anschließend setzen wir das Datum als Indexspalte.

import pandas as pd

url = "https://tubcloud.tu-berlin.de/s/6cBLzd7fGDasDFz/download?path=%2F&files=BB_30400591_weeklyData_HYRAS_GWData.csv&downloadStartSecret=jscl9uomzpq"

data_raw = pd.read_csv(url)
   
data_raw
---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
Cell In[2], line 5
      1 import pandas as pd
      3 url = "https://tubcloud.tu-berlin.de/s/6cBLzd7fGDasDFz/download?path=%2F&files=BB_30400591_weeklyData_HYRAS_GWData.csv&downloadStartSecret=jscl9uomzpq"
----> 5 data_raw = pd.read_csv(url)
      7 data_raw

File /opt/anaconda3/envs/jupyter_book_ml/lib/python3.9/site-packages/pandas/io/parsers/readers.py:1026, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)
   1013 kwds_defaults = _refine_defaults_read(
   1014     dialect,
   1015     delimiter,
   (...)
   1022     dtype_backend=dtype_backend,
   1023 )
   1024 kwds.update(kwds_defaults)
-> 1026 return _read(filepath_or_buffer, kwds)

File /opt/anaconda3/envs/jupyter_book_ml/lib/python3.9/site-packages/pandas/io/parsers/readers.py:620, in _read(filepath_or_buffer, kwds)
    617 _validate_names(kwds.get("names", None))
    619 # Create the parser.
--> 620 parser = TextFileReader(filepath_or_buffer, **kwds)
    622 if chunksize or iterator:
    623     return parser

File /opt/anaconda3/envs/jupyter_book_ml/lib/python3.9/site-packages/pandas/io/parsers/readers.py:1620, in TextFileReader.__init__(self, f, engine, **kwds)
   1617     self.options["has_index_names"] = kwds["has_index_names"]
   1619 self.handles: IOHandles | None = None
-> 1620 self._engine = self._make_engine(f, self.engine)

File /opt/anaconda3/envs/jupyter_book_ml/lib/python3.9/site-packages/pandas/io/parsers/readers.py:1880, in TextFileReader._make_engine(self, f, engine)
   1878     if "b" not in mode:
   1879         mode += "b"
-> 1880 self.handles = get_handle(
   1881     f,
   1882     mode,
   1883     encoding=self.options.get("encoding", None),
   1884     compression=self.options.get("compression", None),
   1885     memory_map=self.options.get("memory_map", False),
   1886     is_text=is_text,
   1887     errors=self.options.get("encoding_errors", "strict"),
   1888     storage_options=self.options.get("storage_options", None),
   1889 )
   1890 assert self.handles is not None
   1891 f = self.handles.handle

File /opt/anaconda3/envs/jupyter_book_ml/lib/python3.9/site-packages/pandas/io/common.py:728, in get_handle(path_or_buf, mode, encoding, compression, memory_map, is_text, errors, storage_options)
    725     codecs.lookup_error(errors)
    727 # open URLs
--> 728 ioargs = _get_filepath_or_buffer(
    729     path_or_buf,
    730     encoding=encoding,
    731     compression=compression,
    732     mode=mode,
    733     storage_options=storage_options,
    734 )
    736 handle = ioargs.filepath_or_buffer
    737 handles: list[BaseBuffer]

File /opt/anaconda3/envs/jupyter_book_ml/lib/python3.9/site-packages/pandas/io/common.py:384, in _get_filepath_or_buffer(filepath_or_buffer, encoding, compression, mode, storage_options)
    382 # assuming storage_options is to be interpreted as headers
    383 req_info = urllib.request.Request(filepath_or_buffer, headers=storage_options)
--> 384 with urlopen(req_info) as req:
    385     content_encoding = req.headers.get("Content-Encoding", None)
    386     if content_encoding == "gzip":
    387         # Override compression based on Content-Encoding header

File /opt/anaconda3/envs/jupyter_book_ml/lib/python3.9/site-packages/pandas/io/common.py:289, in urlopen(*args, **kwargs)
    283 """
    284 Lazy-import wrapper for stdlib urlopen, as that imports a big chunk of
    285 the stdlib.
    286 """
    287 import urllib.request
--> 289 return urllib.request.urlopen(*args, **kwargs)

File /opt/anaconda3/envs/jupyter_book_ml/lib/python3.9/urllib/request.py:214, in urlopen(url, data, timeout, cafile, capath, cadefault, context)
    212 else:
    213     opener = _opener
--> 214 return opener.open(url, data, timeout)

File /opt/anaconda3/envs/jupyter_book_ml/lib/python3.9/urllib/request.py:523, in OpenerDirector.open(self, fullurl, data, timeout)
    521 for processor in self.process_response.get(protocol, []):
    522     meth = getattr(processor, meth_name)
--> 523     response = meth(req, response)
    525 return response

File /opt/anaconda3/envs/jupyter_book_ml/lib/python3.9/urllib/request.py:632, in HTTPErrorProcessor.http_response(self, request, response)
    629 # According to RFC 2616, "2xx" code indicates that the client's
    630 # request was successfully received, understood, and accepted.
    631 if not (200 <= code < 300):
--> 632     response = self.parent.error(
    633         'http', request, response, code, msg, hdrs)
    635 return response

File /opt/anaconda3/envs/jupyter_book_ml/lib/python3.9/urllib/request.py:561, in OpenerDirector.error(self, proto, *args)
    559 if http_err:
    560     args = (dict, 'default', 'http_error_default') + orig_args
--> 561     return self._call_chain(*args)

File /opt/anaconda3/envs/jupyter_book_ml/lib/python3.9/urllib/request.py:494, in OpenerDirector._call_chain(self, chain, kind, meth_name, *args)
    492 for handler in handlers:
    493     func = getattr(handler, meth_name)
--> 494     result = func(*args)
    495     if result is not None:
    496         return result

File /opt/anaconda3/envs/jupyter_book_ml/lib/python3.9/urllib/request.py:641, in HTTPDefaultErrorHandler.http_error_default(self, req, fp, code, msg, hdrs)
    640 def http_error_default(self, req, fp, code, msg, hdrs):
--> 641     raise HTTPError(req.full_url, code, msg, hdrs, fp)

HTTPError: HTTP Error 404: Not Found
df = data_raw.drop(columns=["Unnamed: 0"])
df
Date pr tas GWL
0 1990-01-01 0.6 -1.771429 39.75
1 1990-01-08 0.4 1.871429 39.79
2 1990-01-15 2.5 5.700000 39.82
3 1990-01-22 16.0 6.242858 39.85
4 1990-01-29 2.3 6.457143 39.90
... ... ... ... ...
830 2005-11-28 6.0 1.242857 40.06
831 2005-12-05 0.7 2.128571 40.07
832 2005-12-12 11.9 2.442857 40.06
833 2005-12-19 5.0 3.657143 40.07
834 2005-12-26 3.6 -2.333333 40.07

835 rows × 4 columns

df = df.rename(columns={"Date": "Datum",
                        "pr": "Niederschlag",
                        "tas": "Temperatur",
                        "GWL": "Grundwasserhöhe"})
df
Datum Niederschlag Temperatur Grundwasserhöhe
0 1990-01-01 0.6 -1.771429 39.75
1 1990-01-08 0.4 1.871429 39.79
2 1990-01-15 2.5 5.700000 39.82
3 1990-01-22 16.0 6.242858 39.85
4 1990-01-29 2.3 6.457143 39.90
... ... ... ... ...
830 2005-11-28 6.0 1.242857 40.06
831 2005-12-05 0.7 2.128571 40.07
832 2005-12-12 11.9 2.442857 40.06
833 2005-12-19 5.0 3.657143 40.07
834 2005-12-26 3.6 -2.333333 40.07

835 rows × 4 columns

df = df.set_index('Datum')

df
Niederschlag Temperatur Grundwasserhöhe
Datum
1990-01-01 0.6 -1.771429 39.75
1990-01-08 0.4 1.871429 39.79
1990-01-15 2.5 5.700000 39.82
1990-01-22 16.0 6.242858 39.85
1990-01-29 2.3 6.457143 39.90
... ... ... ...
2005-11-28 6.0 1.242857 40.06
2005-12-05 0.7 2.128571 40.07
2005-12-12 11.9 2.442857 40.06
2005-12-19 5.0 3.657143 40.07
2005-12-26 3.6 -2.333333 40.07

835 rows × 3 columns

1.1.2. Daten prozessieren#

Durch den Pairplot (oder auch Scatterplot) können wir die Struktur den Datenbasis und der Verteilung der Daten besser verstehen. So kann zum Beispiel festgestell werden, ob die Daten miteinander korrelieren.

sns.pairplot(df, corner=True)
plt.show()
../../../_images/3e605035e1256d6fb1b00eb8fc90caab44cb2792d7df39072538c8a4fc09ea69.png

Es ist erkennbar, dass in den vorliegenden Daten keine starken linearen Korrelationen zwischen den Variablen zu erkennen sind.

Damit wir keine ungenauen Werte erhalten, wollen wir alle fehlenden Daten löschen. Allerdings zeigt der folgende Befehl, dass es keine Spalte mit fehlenden Werten gibt.

df.isnull().sum()
Niederschlag       0
Temperatur         0
Grundwasserhöhe    0
dtype: int64

Nun können wir uns die Daten einmal beschreiben lassen und alle drei Variablen plotten lassen, um ein besseres Bild von den Daten zu erhalten.

df.describe()
Niederschlag Temperatur Grundwasserhöhe
count 835.000000 835.000000 835.000000
mean 9.368503 9.425144 40.245162
std 9.986305 7.085398 0.412379
min 0.000000 -11.342857 39.560000
25% 2.200000 4.007143 39.950000
50% 6.100000 9.500000 40.170000
75% 13.300000 15.292858 40.475000
max 73.700000 25.199999 41.660000
df['Niederschlag'].plot()
<Axes: xlabel='Datum'>
../../../_images/bd0ba1853335e6dbd80a7136497cb4cec2dbe2c26760f35a98e9276907438696.png
df['Temperatur'].plot()
<Axes: xlabel='Datum'>
../../../_images/6d974b49071215e768bc88bdb16dae578ac5e2393d46a7dc7a7056f3ee69cecb.png
df['Grundwasserhöhe'].plot()
<Axes: xlabel='Datum'>
../../../_images/79a1d1f0889368952f8ee3c1ecf39531a9fda735ecce8de91d8604952dce05ba.png

1.1.3. Zusätzliche Features#

Im nächsten Schritt sollen die Datern erweitert werden und die neue Variable “Niederschlagtimelag” hinzugefügt werden.

Die Variable “Niederschlagtimelag” fasst jeweils den den Niederschlag der Woche davor und der aktuellen Woche zusammen. Sie stellt also die Summe des Niederschlags über einen gleitenden Zeitraum von zwei Wochen dar.

df['Niederschlag']['1990-01-01':'2005-12-26']
Datum
1990-01-01     0.6
1990-01-08     0.4
1990-01-15     2.5
1990-01-22    16.0
1990-01-29     2.3
              ... 
2005-11-28     6.0
2005-12-05     0.7
2005-12-12    11.9
2005-12-19     5.0
2005-12-26     3.6
Name: Niederschlag, Length: 835, dtype: float64
df_Niederschlag_3WochenSumme = df['Niederschlag'].rolling(window=2, center=True).sum()

df_Niederschlag_3WochenSumme
Datum
1990-01-01     NaN
1990-01-08     1.0
1990-01-15     2.9
1990-01-22    18.5
1990-01-29    18.3
              ... 
2005-11-28     9.3
2005-12-05     6.7
2005-12-12    12.6
2005-12-19    16.9
2005-12-26     8.6
Name: Niederschlag, Length: 835, dtype: float64

Die Summe des Niederschlags der letzen und der aktuellen Woche wird nun als neue Spalte “Niederschlagtimelag” in den Datensatz aufgenommen.

df['Niederschlagtimelag'] = df_Niederschlag_3WochenSumme 

df
Niederschlag Temperatur Grundwasserhöhe Niederschlagtimelag
Datum
1990-01-01 0.6 -1.771429 39.75 NaN
1990-01-08 0.4 1.871429 39.79 1.0
1990-01-15 2.5 5.700000 39.82 2.9
1990-01-22 16.0 6.242858 39.85 18.5
1990-01-29 2.3 6.457143 39.90 18.3
... ... ... ... ...
2005-11-28 6.0 1.242857 40.06 9.3
2005-12-05 0.7 2.128571 40.07 6.7
2005-12-12 11.9 2.442857 40.06 12.6
2005-12-19 5.0 3.657143 40.07 16.9
2005-12-26 3.6 -2.333333 40.07 8.6

835 rows × 4 columns

Allerdings hat nun die erste Zeile fehlende Daten, da es für das erste Datum keine Messung davor gibt, die im “Niederschlagtimelag” mit der aktuellen Messung zusammengefasst werden kann.

Die erste Zeile kann jedoch gelöscht werden, da wir eine ausreichend große Datenbasis haben.

df.isnull().sum()
Niederschlag           0
Temperatur             0
Grundwasserhöhe        0
Niederschlagtimelag    1
dtype: int64
df.dropna(axis=0, inplace=True)
df
Niederschlag Temperatur Grundwasserhöhe Niederschlagtimelag
Datum
1990-01-08 0.4 1.871429 39.79 1.0
1990-01-15 2.5 5.700000 39.82 2.9
1990-01-22 16.0 6.242858 39.85 18.5
1990-01-29 2.3 6.457143 39.90 18.3
1990-02-05 6.7 6.000000 39.95 9.0
... ... ... ... ...
2005-11-28 6.0 1.242857 40.06 9.3
2005-12-05 0.7 2.128571 40.07 6.7
2005-12-12 11.9 2.442857 40.06 12.6
2005-12-19 5.0 3.657143 40.07 16.9
2005-12-26 3.6 -2.333333 40.07 8.6

834 rows × 4 columns


1.1.4. Model: Decision Tree#

Nun wollen wir einen Decision Tree (bzw. Entscheidungsbaum) erstellen. Dieser kann mit unterschiedliechen Tiefen, das heißt Anzahl an vertikal aufeinaner folgenden Verzweigungen erstellt werden. Die Tiefe des Baums spielt eine entscheidende Rolle für die Leistung und die Eigenschaften des Modells. So kann ein Decision Tree mit größerer Anzahl an Tiefen komplexere Daten darstellen.

Im Allgemeinen gilt:

Je mehr Tiefen der Decision Tree hat, desto höher ist die Gefahr von Overfitting. Je weniger Tiefen der Decision Tree hat, desto höher ist die Gefahr von Underfitting.

Wir wollen den Decision Tree mit unterschiedlichen Tiefen erstellen, um ein optimales Ergebnis zu erzielen.

Zunächst werden dafür die Daten in Features und Target Variablen unterschieden.

feature_cols = ['Niederschlag', 'Temperatur']
X = df[feature_cols] # Features
y = df.Grundwasserhöhe # Target variable

Anschließend unterteilen wir die Datenbasis in einen Trainingsdatensatz und einen Testdatensatz. Dabei nutzen wird das Verhältnis 70 zu 30.

Das bedeutet, dass der Trainingsdatensatz 70% der Datenbasis enthalten soll und der Testdatensatz 30% der Daten enthalten soll.

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1)

Zur Auswertung der Decision Trees mit unterschiedlichen Tiefen nutzen wir den Mean Squared Error (MSE).

Der MSE misst den durchschnittlichen quadratischen Fehler zwischen den durch das Regressionsmodell vorhergesagten Werten und den tatsächlichen Werten im Testdatensatz. Er test also wie gut das Regressionsmodell die Daten beschreibt.

Im Allgmeinen gilt: Je kleiner der MSE, desto besser passt das erstellte Modell zu den Daten.

1.1.4.1. Decision Tree mit der Tiefe 2#

Unsere Zielvariable der Grundwasserhöhe ist eine kontinuierliche Variable, deswegen können wir im Folgenden den folgenden Decision Tree Regressor verwenden.

model = DecisionTreeRegressor(max_depth=2)
model.fit(X_train, y_train)
DecisionTreeRegressor(max_depth=2)
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.
y_pred = model.predict(X_test)
plt.figure(figsize=(20,10))
plot_tree(model, feature_names=X.columns, filled=True, rounded=True, fontsize=10)
plt.show()
../../../_images/e90a588e1bc3fd3478d853ca9502bbab244a583c448c85e99d8ba4f8917cc71b.png
mse = mean_squared_error(y_test, y_pred)
print(f'Mean Squared Error: {mse}')
Mean Squared Error: 0.16105546585567312

Der Decision Tree mit der Tiefe 2 sagt aus:

  • Beispiel: Wenn der Niederschlag niedriger oder gleich 50.75 und die Temperatur niedriger oder gleich 12.871 Grad ist, dann liegt der Grundwasserspiegel bei rund 40.188.

  • Die Unterteilung ist hier noch sehr ungenau, da von 583 Daten für ganze 580 Daten es zutrifft, dass der Niederschlag niedriger als 50.75 ist.

  • Ein insgesamter MSE von 0.161 ist bereits ein ziemlich gutes Ergebnis.

  • Bei den unteren beiden linken Abzweiungen ist der MSE höher, da hier die sample-Anzahl deutlich höher ist und somit ein größerer Datensatz berücksichtigt wird.

Insgesamt könnte die Unterteilung und der Decision Tree besser sein, weshalb wir im nächsten Schritt eine größere Tiefe verwenden.

1.1.4.2. Decision Tree mit der Tiefe 3#

model = DecisionTreeRegressor(max_depth=3)
model.fit(X_train, y_train)
DecisionTreeRegressor(max_depth=3)
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.
y_pred = model.predict(X_test)
plt.figure(figsize=(20,10))
plot_tree(model, feature_names=X.columns, filled=True, rounded=True, fontsize=10)
plt.show()
../../../_images/39cab5f6d8ed6c46beb3a4f4a169ef63dca80624c3b496d4e5993c5fe9c63505.png
mse = mean_squared_error(y_test, y_pred)
print(f'Mean Squared Error: {mse}')
Mean Squared Error: 0.1635600972018598

Der Decision Tree mit der Tiefe 3 sagt aus:

  • Dadurch, dass eine Ebene hinzukommt, kann eine genauere Unterteilung vorgenommen werden.

  • In der ersten Unterteilung erfüllen noch immer 580 von 583 Daten das Kriterium, jedoch werden die im nächsten Schritt nochmal genauer in 4 verschiedene Temperaturabschnitte unterteilt.

  • Der insgesamte MSE von 0.163 ist höher als in dem letzten Decision Tree.

  • Die einzelnen MSE sind teilweise auch höher als in dem Decision Tree mit der Tiefe 2, dennoch ist der Decision Tree mit der Tiefe 3 aussagekräftiger.

1.1.4.3. Decision Tree mit der Tiefe 4#

model = DecisionTreeRegressor(max_depth=4)
model.fit(X_train, y_train)
DecisionTreeRegressor(max_depth=4)
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.
y_pred = model.predict(X_test)
plt.figure(figsize=(20,10))
plot_tree(model, feature_names=X.columns, filled=True, rounded=True, fontsize=10)
plt.show()
../../../_images/97f217323c0ec2fb93486c643ab2c18d1dd571097d4b30d7d90645738c8f2383.png
mse = mean_squared_error(y_test, y_pred)
print(f'Mean Squared Error: {mse}')
Mean Squared Error: 0.16447223760774984

Der Decision Tree mit der Tiefe 4 sagt aus:

  • Durch die weitere Unterteilung werden noch genauere Ergebnisse mit gerineren sample-Anzahlen deutlich.

  • Die Grundwasserhöhen liegen alle relativ nah beieinander, weshalb auch eine geringe Unterscheidung wichtig sein kann.

  • Der insgesamte MSE von 0.164 bleibt nahezu unverändert.

1.1.5. Decision Tree mit ‘Niederschlagtimelag’ - Variable#

Wir wollen herausfinden, wie sich der Niederschlag auf das Grundwasser auswirkt. Dafür ist es durchaus sinnvoll in die Berechnungen den Niederschlag von dieser Woche als auch der vorherigen Woche zu berücksichtigen. Der Niederschlag braucht je nach Beschaffenheit Zeit, um in das Grundwasser zu versickern und kann nicht bereits nach einer Woche im Grundwasser nachgewiesen werden.

Wir wollen daher nun einen Decision Tree mit der zuvor erstellten Variable ‘Niederschlagtimelag’ erstellen.

Dafür müssen wir zunächst neue Feature und Target Variablen bestimmen und das Modell darauf trainieren.

feature_cols = ['Niederschlagtimelag', 'Temperatur']
X = df[feature_cols] # Features
y = df.Grundwasserhöhe # Target variable
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1)

Nun erstellen wir einen Decision Tree mit der Tiefe 4 mit diesen neu deffinierten Variablen.

1.1.6. 6.1 Wir erstellen einen Decision Tree mit der ‘Niederschlagtimelag’-Variable und einer Tiefe von 4#

Unsere Zielvariable der Grundwasserhöhe ist nach wie vor eine kontinuierliche Variable, weshal wir erneut den Decision Tree Regressor verwenden können.

model = DecisionTreeRegressor(max_depth=4)
model.fit(X_train, y_train)
DecisionTreeRegressor(max_depth=4)
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.
y_pred = model.predict(X_test)
plt.figure(figsize=(20,10))
plot_tree(model, feature_names=X.columns, filled=True, rounded=True, fontsize=10)
plt.show()
../../../_images/311696ec3a0c68d41777447238b62596a30d9ab3880f997614e84dd3ccaa77cf.png
mse = mean_squared_error(y_test, y_pred)
print(f'Mean Squared Error: {mse}')
Mean Squared Error: 0.16136716573211654

Gegenüberstellung des Decision Trees mit der ‘Niederschlag’-Varialbe und einer Tiefe von 4 und des Decision Tree mit der ‘Niederschlagtimelag’-Variable und einer Tiefe von 4:

  • Der insgesamte MSE von dem Decision Tree mit der ‘Niederschlagtimelag’-Variable ist mit 0.161 besser als 0.164.

  • Der Decision Tree mit der ‘Niederschlagtimelag’-Variable nutzt darüber hinaus als erste Unterteilung “kleiner gleich 12.871 Grad” während der andere Decision Tree immer den Niederschlag als erste Unterteilungsvariable genutzt hat. Allerdings erhält der Decision Tree mit der ‘Niederschlagtimelag’-Variable eine breitere Unterteilung und damit ein deutlich detaillierteres Ergebnis.

  • Das differenziertere Ergebnis des Decision Trees mit der ‘Niederschlagtimelag’-Variable ist auch darauf zurückzuführen, dass die ‘Niederschlagtimelag’-Variable eine breitere Spannweite an Werten hat.

Insgesamt lässt sich der Grundwasserspiegel mit dem Decision Tree mit der ‘Niederschlagtimelag’-Variable am differenziertesten vorhersagen.