Der Umfang von generierten Daten steigt ständig durch die zunehmende Digitalisierung nahezu aller Lebensbereiche – von Industrie 4.0 über den öffentlichen Sektor bis hin zu SmartHome. Dabei verbergen sich in den Daten enorme Informationsmengen, welche zur Erfüllung vielerlei Ziele genutzt werden können, etwa zur Effizienzsteigerung in der Wirtschaft, beispielsweise in Form von kundenorientierten Kaufempfehlungen von Amazon.
Dabei ist die schiere Menge an Daten gleichermaßen Fluch und Segen. Denn während ein zunehmender Umfang an Messpunkten eine offensichtliche Informationszunahme bedeutet, droht die Flut an beobachteten Attributen die tatsächlich für die Zielerreichung relevanten Informationen zu verwässern. Um schließlich die Informationen aus den Daten zu extrahieren und nutzbar zu machen, ist zudem ein geeigneter Algorithmus zu wählen, welcher die enorme Datenmenge auch effizient verarbeiten kann.
In dieser dreiteiligen Blogbeitragserie soll anhand eines Beispiels skizziert werden, wie sich große Datenbestände nutzbar machen lassen. Dabei behandelt der erste Teil die thematische Einführung in das Beispiel sowie die Datenexploration, im zweiten Teil werden die Daten bereinigt und aufbereitet und im dritten Teil zum Training eines Machine-Learning-Modells genutzt, nachdem dieses vorgestellt wurde.
Die Problemstellung
Als Beispiel nutzen wir eine Challenge von Kaggle, welche hier verlinkt ist. Dort können die Informationen auch nachgelesen und heruntergeladen werden.
Die US-amerikanische Organisation ASHRAE (American Society of Heating, Refrigerating and Air-Conditioning Engineers) vertritt als Berufsverband die Tätigen aus den Bereichen Heizungs‑, Lüftungs‑, Klima- und Kühlanlagenbau. Es gilt eine Vorhersage über den Energieverbrauch von Gebäuden zu treffen, um unter dem Aspekt der Pay-for-Performance-Finanzierung ermitteln zu können, wie groß die Einsparung von energetischen Optimierungen eines Gebäudes ist. Bei dieser Finanzierung zahlt der Eigentümer des Gebäudes einen Teil der tatsächlich erreichten Einsparungen an denjenigen, der die Baumaßnahme zur Energieverbrauchssenkung durchgeführt hat und finanziert diese damit. Doch während der tatsächliche Energieverbrauch nach einer energetischen Optimierungsmaßnahme messbar ist, muss der theoretische Energieverbrauch ohne Maßnahme für die Zeit nach der Optimierung mit einem Modell berechnet werden.
Die Daten
Der erste Schritt einer Analyse ist stets die Betrachtung der Rohdaten. In diesem Fall liegen Daten zum historische Energieverbauch von Gebäuden für ein Jahr vor, welche mit einer Granularität von einer Stunde gemessen wurden. Gegeben ist in diesem Fall der historische Energieverbauch von Gebäuden in einem Jahr in einer Granularität von einer Stunde. Es gibt vier verschiedene Energieformen, welche betrachtet werden: Electricity, Hot Water, Chilled Water und Steam, sowie deren abgelesener Verbrauch in kWh. Zusätzlich gibt es Wetterinformationen von diversen Wetterstationen in dessen Einzugsgebieten die Gebäude liegen. Es gibt einen Trainings- und einen Testdatensatz, wobei der Testdatensatz keine Werte für den Verbrauch beinhaltet. Im Detail gliedern sich die Daten wie folgt:
train.csv
Feld | Bedeutung |
building_id | Fremdschlüssel für den Join mit den Gebäudedaten |
meter | Energieform {0:Electricity, 1:chilledwater, 2:hotwater, 3:steam} |
timestamp | Zeitstempel der Messung |
meter_reading | Verbrauch in kWh |
building_metadata.csv
Feld | Bedeutung |
site_id | Fremdschlüssel für den Join mit den Wetterdaten |
building_id | Primärschlüssel |
primary_use | Indikator der primären Nutzung des Gebäudes |
square_feet | Gebäudefläche in ft2 |
year_built | Baujahr |
floor_count | Anzahl der Stockwerke |
weather_train.csv
Feld | Bedeutung |
site_id | Primärschlüssel |
timestamp | Zeitstempel der Messung |
ait_temperature | Lufttemperatur in °C |
cloud_coverage | Anteil des durch Wolken bedeckten Himmels |
dew_temperature | Temperatur in °C |
precip_depth_1_hr | Niederschlag in mm/m2 |
sea_level_pressure | Luftdruck in mbar |
wind_direction | Windrichtung in Kompassrichtung (0°-360°) |
wind_speed | Windgeschwindigkeit in m/s |
Die Exploration
Damit sind die Metainformationen der Daten bereits vorhanden. Um sich ein besseres Bild der Daten machen zu können, bietet sich eine Visualisierung an. Diese werden wir wie alle weiteren folgenden Schritte mit Python 3 vornehmen. Dazu ist zunächst der Import der Daten (hier als pandas Dataframe) notwendig. Anschließend werden die Datensätze anhand der Fremdschlüssel zusammengeführt:
train = train.merge(building_metadata, on='building_id', how='left')
train = train.merge(weather_train, on=['site_id', 'timestamp'], how='left')
test = test.merge(building_metadata, on='building_id', how='left')
test = test.merge(weather_test, on=['site_id','timestamp'], how='left')
Mit diesen vereinten Datensätzen können nun erste einfache Analysen der Datenstrukturen vorgenommen werden. Zunächst ist es interessant zu betrachten, ob es fehlende Werte (NAs) in einzelnen Datenreihen bzw. Attributen gibt. Dazu lässt sich beispielsweise ein einfaches Diagramm erstellen:
import numpy as np
import matplotlib.pyplot as plt
train_data = (train.count() / len(train)).drop('meter_reading').sort_values().values
ind = np.arange(len(train_data))
width = 0.35
fig, axes = plt.subplots(1,1,figsize=(14, 6), dpi=200)
tr = axes.bar(ind, train_data, width, color='red')
test_data = (test.count() / len(test)).drop('row_id').sort_values().values
tt = axes.bar(ind+width, test_data, width, color='blue')
axes.set_title('Anteil von NAs an den Daten', fontsize=16)
axes.set_ylabel('Anteil verfügbarer Daten [%]');
axes.set_xticks(ind + width / 2)
axes.set_xticklabels((train.count() / len(train)).drop('meter_reading')
.sort_values().index, rotation=40)
axes.legend([tr, tt], ['Train', 'Test']);
Wir erhalten bei Ausführung obigen Codes folgende Grafik:
Es ist ersichtlich, dass die Datenverfügbarkeit der Attribute floor_count, year_built und cloud_coverage sehr gering ist. Auch die Attribute precip_depth_1_hr, wind_direction und sea_level_pressure sind nicht für jeden Datenpunkt verfügbar. Die übrigen Attribute sind für nahezu jeden Datenpunkt vorhanden.
Ein hoher Anteil fehlender Werte eines Attributs disqualifiziert dieses Attribut oftmals für weitere Analysen. Zusammenhänge zwischen diesem Attribut und der vorherzusagenden Größe, in unserem Fall der Energieverbrauch, lassen sich dann nur für einen kleinen Teil der Daten untersuchen. Dies führt dann beim späteren Training des Machine-Learning-Modells zu Problemen.
Auch eine Betrachtung des Energieverbrauchs im Laufe der Zeit erscheint sinnvoll. Dazu erstellen wir eine weitere Grafik:
fig, axes = plt.subplots(1, 1, figsize=(14, 6), dpi=200)
train[['timestamp', 'meter_reading']]
.set_index(pd.DatetimeIndex(train['timestamp'])).resample('H').mean()
['meter_reading'].plot(ax=axes, label='By hour', alpha=0.8, color='red').set_ylabel('Energieverbrauch', fontsize=14);
train[['timestamp', 'meter_reading']]
.set_index(pd.DatetimeIndex(train['timestamp'])).resample('D').mean()
['meter_reading'].plot(ax=axes, label='By day', alpha=1, color='blue').set_ylabel('Energieverbrauch', fontsize=14);
axes.set_title('Gemittelter Energieverbrauch pro Stunde und Tag', fontsize=16);
axes.legend();
Es sind gleich mehrere interessante Dinge zu beobachten. Zunächst ist der Energieverbrauch über das Jahr verteilt sehr unterschiedlich. Diese enormen Unterschiede lassen sich nicht mit den unterschiedlichen Wetterbedingungen während des Jahres erklären.
Darüber hinaus lässt sich erkennen, dass die stündlichen Werte sehr viel stärker schwanken, als die täglichen Werte. Die stündlichen Werte beinhalten demnach einzelne größere Ausreißer, welche weitere Analysen stark verfälschen können.
Was kann man nun tun, um den Datensatz genauer zu analysieren? Anhand der bekannten Attribute wirkt es plausibel, den Datensatz anhand der kategorischen Attribute aufzuteilen, also beispielsweise anhand von meter_type, primary_use oder site_id.
Im Folgenden betrachten wir den Datensatz für den meter_type chilled water. Für die übrigen Werte des Attributs meter_type kann analog vorgegangen werden.
Eingeschränkt auf den meter_type chilled water ergibt sich folgender Graph:
Der Verlauf des Energieverbrauchs über das Jahr wirkt nun schon homogener als zuvor. Eine weitere Aufteilung anhand des site_id Attributs ist denkbar. Dabei fällt auf, dass nicht zu jeder site_id Einträge mit dem meter_type chilled_water vorhanden sind.
for i in range(train_combined['site_id'].nunique()):
print(str(i) + " " +
str(len(train_combined[train_combined['site_id'] == i])))
site_id | Anzahl Einträge |
0 | 168253 |
1 | 0 |
2 | 863845 |
3 | 0 |
4 | 0 |
5 | 0 |
6 | 164107 |
7 | 130956 |
8 | 0 |
9 | 833113 |
Es gibt also nur Einträge zu den site_ids 0, 2, 6, 7 und 9. Plottet man nun die Datensätze für diese site_ids erhält man folgendes Bild:
Es ist ersichtlich, dass die Unterscheidung nach der site_id weitere Inhomogenitäten offenbart. Man könnte nun noch weitere Unterscheidungen vornehmen: nach meter_type und site_id und primary_use etwa. Und selbst dann ließe sich noch weiter differenzieren, etwa nach der building_id. Man sieht: oft ist eine beliebig feine Granularität möglich.
Correlation-Heat-Maps
Eine weitere Möglichkeit zur Visualisierung von Zusammenhängen bietet die Correlation-Heat-Map. In dieser werden die Korrelationen der ausgewählten Attribute visuell dargestellt. Beispielhaft für unseren Datensatz könnte das so aussehen:
train_values = train_combined.drop(['building_id', 'meter', 'site_id'], axis = 1)
correlation_values = train_values.corr()
axis_heatmap = sns.heatmap(correlation_values, vmin = -1, vmax=1, center=0,
cmap = sns.diverging_palette(20, 220, n = 200),
square = True)
axis_heatmap.set_xticklabels(axis_heatmap.get_xticklabels(), rotation = 45,
horizontalalignment = 'right')
Dabei wurden die Attribute building_id, meter und site_id aussortiert, da diese als kategorische Attribute keine sinnvolle Korrelation mit anderen Attributen aufweisen können.
Boxplots
Zuletzt sei die Möglichkeit von BoxPlots erwähnt. Diese ermöglichen die Visualisierung der Verteilung der einzelnen Datenpunkte und kann so dabei unterstützen Ausreißer zu erkennen. Hier am Beispiel von den Einträgen mit primary_use Education, wobei die Werte der Attribute allesamt auf den Wertebereich von 0 bis 1 normalisiert wurden.
columns_with_values = ['meter_reading', 'square_feet', 'age', 'air_temperature',
'cloud_coverage', 'dew_temperature', 'sea_level_pressure',
'wind_speed']
plt.figure(figsize=(16, 7))
sns.boxplot(data = train_combined[train_combined['primary_use'] == 'Education'], order = columns_with_values)
sns.despine(offset = 10, trim = True)
Die „Box“ der Boxplots wird durch die unteren und oberen Quartile begrenzt, die Linie im Innern einer Box markiert den Median. Die „Fühler“ an der Box reichen maximal bis zum 1,5‑fachen des Interquartilsabstands. Alle Punkte außerhalb dieser Fühler werden als „Ausreißer“ bezeichnet. Nähere Infos dazu gibt es u.A. hier.
Ausblick
Wir haben nun diverse Möglichkeiten gesehen, um die Daten zu visualisieren und sich einen besseren Überblick zu verschaffen. Dieser Überblick ist nötig, da die Daten oftmals Inhomogenitäten aufweisen, verursacht zum Beispiel durch Messfehler. Auch kategorische Unterschiede in den Daten können so erkannt und berücksichtigt werden, genauso wie saisonale Komponenten.
Wie mit Inhomogenitäten umgegangen werden kann, welche in diesem Beispiel deutlich zu erkennen waren, wird im nächsten Teil dieser Blogbeitragserie beschrieben.