NetCDF Einführung

Lernziele


Alle Schritte aus diesem Kurs sind auch in einem gebrauchsfertigen Notizbuch verfügbar, das Sie unter den folgenden Links herunterladen können:

Öffnen Sie das Notizbuch in Jupyter Lab und folgen Sie den Anweisungen. Alternativ können Sie den Python-Code-Schnipsel in Ihr Jupyter Notebook kopieren und die Zelle ausführen.


Umgang mit NetCDF-Dateien

NetCDF (Network Common Data Form) ist ein Datenformat, das speziell für wissenschaftliche Daten entwickelt wurde. Es wurde in den späten 1980er-Jahren vom Unidata-Programm des University Corporation for Atmospheric Research (UCAR) entwickelt. Ziel war es, ein standardisiertes Format für die Speicherung und den Austausch von multidimensionalen Daten bereitzustellen, das besonders für Anwendungen in der Klimaforschung, Meteorologie und Ozeanographie geeignet ist. NetCDF hat sich seitdem zu einem zentralen Werkzeug für die Arbeit mit raum- und zeitbezogenen Daten entwickelt.

Für weitere Informationen besuchen Sie die UCAR-Websites.

Um mit den NetCDF Dateien arbeiten zu können installieren Sie zunächst die notwendigen Python Bibliotheken

pip install netCDF4 xarray numpy matplotlib

1. NetCDF-Zip-Datei entpacken

Öffnen Sie Ihr Jupyter lab über die Eingabeaufforderung (cmd):

jupyter lab

Öffnen Sie ein Notebook in einem Ordner Ihrer Wahl. Sie können den „CDSdata“ Ordner aus dem ersten Kursmodul (CDS API Installation und Download) nehmen, in diesem befinden sich die Daten, mit denen wir arbeiten wollen.

Die Datei die Sie im ersten Kursmodul (CDS API Installation und Download) heruntergeladen haben ist im NetCDF-Format, jedoch noch in einer Zip Datei komprimiert. Alternativ können Sie den Datensatz von CDS API Installation und Download hier herunterladen und in den Ordner „CDSdata“ innerhalb des aktuellen Arbeitsordners in Jupyter Lab verschieben:

Zunächst entpacken Sie die Daten, kopieren Sie sich folgenden Block in Ihr Notebook und modifizieren Sie die Zeilen wie benötigt.

import zipfile
import os

# Pfad zur ZIP-Datei (im selben Verzeichnis oder absoluter Pfad)
zip_datei = './CDSdata/reanalysis-era5-land.zip'  # Passen Sie den Pfad bei Bedarf an

# Zielverzeichnis zum Entpacken
zielverzeichnis = 'Era5Data'
os.makedirs(zielverzeichnis, exist_ok=True)  # Verzeichnis erstellen, falls nicht vorhanden

# ZIP-Datei öffnen und extrahieren
        with zipfile.ZipFile(zip_datei, 'r') as zip_ref:
                zip_ref.extractall(zielverzeichnis)
        print(f"ZIP-Datei erfolgreich in '{zielverzeichnis}' entpackt.")

Nun schauen Sie sich die Datei genauer an.

2. NetCDF-Datei untersuchen

NetCDF Dateien sind in Dimensionen, Variablen und Attributen aufgebaut. Dabei sind Dimensionen die Achsen, an denen sich die Variablen bewegen. Achsen können beispielsweise geographische Länge und Breite sein, oder verschiedene Höhenlevel. Variablen sind die meteorologischen Parameter, die in der Datei beinhaltet sind. Beispielsweise Temperatur, Luftdruck oder Windgeschwindigkeiten. Attribute einer NetCDF-Datei sind die Metadaten und beinhalten Einheiten der Variablen, Beschreibungen und die Quellen/Autoren der Daten.

Zunächst verschaffen Sie sich einen Überblick über den Datensatz.

import netCDF4 as nc

# Datei öffnen
dataset = nc.Dataset('./ERA5Data/data_0.nc', 'r')

# Metadaten anzeigen
print(dataset)

Der print-Command gibt Ihnen die wichtigsten Informationen über die vorliegende Datei als lesbaren Text wieder. Sie können auf einen Blick sehen, welche Variablen enthalten sind, welche Formate diese Variablen haben oder auch wie viele Zeitschritte verfügbar sind.

Es gibt viele weitere Möglichkeiten schnell mehr Informationen über eine NetCDF-Datei zu erhalten. Sie können sich die keys, also die Kurznamen der Variablen anzeigen lassen und dadurch den print-Command auf eine einzelne anwenden. Mit dem nächsten Code-Block können Sie auf einen Bloick sehen, ob die 2m-Temperatur-Werte unseres Test-Datensatzes valide erscheinen.

# Variablen auflisten
print(dataset.variables.keys())

# Zugriff auf eine Variable
temperatur = dataset.variables['t2m'][:]
print(temperatur)

Wenn Sie die verschiedenen Commands für den schnellen Überblick ausprobiert haben, können Sie mit den verschiedenen Visualisierungsmöglichkeiten weiter machen.


Um für die Visualisierung mehr Möglichkeiten zu haben benötigen Sie einen weiteren Datensatz. Diesen haben wir Ihnen bereits zum Download zur Verfügung gestellt. Es handelt sich genau wie im vorangegangenen Abschnitt um einen Datensatz aus der ERA-5 Reanalyse, die Monatsmittel der 2m-Temperatur für eine vordefinierte Region in Süddeutschland.

Zunächst sollten Sie die Pfade für Ihren Output definieren. Damit sollen Sie jedes Notebook beginnen, um sicherzugehen, dass Sie Ihre erzeugten Daten und Plots wiederfinden. Es sorgt auch dafür, dass Ihr Code flexibler wird. Durch die Aliase (Bsp. „output_folder“) für die Speicherpfade ersparen Sie sich mühsames durchsuchen Ihres Notebooks, falls sich diese einmal ändern sollten. Sie müssen nur die Pfade im ersten Codeblock anpassen, der Rest erledigt sich durch die Aliase von alleine.

import os

# ---- Verzeichnisse unten angeben ----
download_folder = r"./era5-land-monthly/download"  # Ordner für heruntergeladene Daten
output_folder = r"./era5-land-monthly/output"  # Ordner für die endgültigen Ausgaben
# ---- Ende der Benutzereingaben ----

# Verzeichnis erstellen, falls nicht vorhanden
os.makedirs(download_folder, exist_ok=True)
os.makedirs(output_folder, exist_ok=True)

1. Einlesen und Kennenlernen der Daten

Im Folgenden nutzen wir einige nützliche Python Bibliotheken, wie zum Beispiel die Datenanalyse-Bibliothek pandas. Weiterführende Informationen zu den einzelnen Bibliotheken finden Sie online, diese sind nicht in den Ressourcen von CoKLIMAx II inkludiert. Die Code-Böcke enthalten Kommentare, die die einzelnen Arbeitschritte in Textform dokumentieren.

Definieren Sie zusätzlich zu den Speicherpfaden auch die Pfade zu den Dateien, mit denen Sie arbeiten möchten.

# Geben Sie den Dateinamen des Datensatzes an und erstellen Sie seinen vollständigen Pfad.
filename = "reanalysis-era5-land-monthly-means_2m_temperature_1950_2024.nc"
filepath = os.path.join(download_folder, filename)

# Anzeige des konstruierten Dateipfads zur Überprüfung
print(f"Dataset file path: {filepath}")

Nun verschaffen Sie sich einen Überblick über die Datei, die räumliche und zeitliche Ausdehnung, sowie die verfügbaren Variablen und Zeitschritte.

import netCDF4 as nc

# Öffne die NetCDF-Datei im Lesemodus
dataset = nc.Dataset(filepath, mode='r')

# Liste alle Variablen im Datensatz auf
variables_list = dataset.variables.keys()
print(f"Verfügbare Variablen: {list(variables_list)}")

# Extrahiere Koordinatendaten und die Daten der Hauptvariablen
lon_list = dataset['longitude'][:]  # Längengrad extrahieren
lat_list = dataset['latitude'][:]  # Breitengrad extrahieren
import pandas as pd

test_variable = 't2m'
variable_data = dataset[test_variable]

# Erstelle eine Zusammenfassung der Hauptvariablen
summary = {
    "Variablenname": test_variable,
    "Datentyp": variable_data.dtype,
    "Form": variable_data.shape,
    "Variableninfo": f"{test_variable}({', '.join(variable_data.dimensions)})",
    "Einheiten": getattr(variable_data, "units", "N/A"),
    "Long Name": getattr(variable_data, "long_name", "N/A"),
}

# Zeige die Datensatz-Zusammenfassung als DataFrame für bessere Visualisierung
nc_summary = pd.DataFrame(list(summary.items()), columns=['Beschreibung', 'Bemerkungen'])

# Zeige das Zusammenfassungs-DataFrame an
nc_summary
import numpy as np
import pandas as pd

# Konfiguriere die Anzeigeeinstellungen von pandas für bessere Lesbarkeit
pd.set_option('display.max_colwidth', None)

# Erstelle eine Zusammenfassung des Datensatzes
ds_summary = {
    "Institution": dataset.institution if hasattr(dataset, 'institution') else "N/A",
    "Dimensionen": list(dataset.dimensions.keys()),
    "Variablen": list(dataset.variables.keys()),
    "Variablen-Dimensionen": [
        np.shape(dataset[variable]) for variable in dataset.variables.keys()
    ],
}

# Konvertiere das Zusammenfassungs-Dictionary in ein DataFrame für bessere Visualisierung
dataset_summary = pd.DataFrame(list(ds_summary.items()), columns=['Beschreibung', 'Bemerkungen'])

# Zeige das Zusammenfassungs-DataFrame an
dataset_summary

Im erstellten Summary sehen Sie, dass die Datei 898 valide Zeitschritte enthält. Da es sich um monatliche Mittelwerte handelt un die Datei im Januar 1950 ihren ersten Zeitschritt hat, wissen wir nun, dass im Oktober 2024 der letzte Zeitschritt ist: (2024 - 1950) x 12 + 10 = 898.

2. Erstellen eines Plots für August 1980

Mit dem folgenden Code-Blöcken können Sie sich ein flexibles Plotskript erstellen, mit dem Sie ind er Lage sind, schnell zwischen Visualisierungen verschiedener Monate zu switchen.

Um dies zu erreichen definieren Sie Aliase für Jahr und Monat direkt zu Beginn, genau wie für die Speicherpfade und Dateipfade.

# Definieren Sie das Zieljahr und den Zielmonat für die Visualisierung
selected_year = 1980
selected_month = 8

# Berechnen Sie den Bandindex für das ausgewählte Jahr und den ausgewählten Monat
# Der Index wird durch die Position in der Zeitdimension bestimmt
band_index = (selected_year - 1950) * 12 + (selected_month - 1)

# Extrahieren Sie den Datenausschnitt, der dem ausgewählten Jahr und Monat entspricht
# Dies ergibt die räumlichen Daten (Breitengrad x Längengrad) für die angegebene Zeit
band_data = variable_data[band_index,:,:]

Im folgenden Block legen Sie noch einige Visualisierungsoptionen fest, bevor Sie sich das Ergebnis anschauen können.

import matplotlib.pyplot as plt

# Daten mit matplotlib darstellen
fig, ax = plt.subplots(figsize=(8, 8))

# Vordefinierte Farbkarte laden
cmap = plt.get_cmap("turbo")

# Pseudofarbdiagramm für die Daten erstellen
pcm = ax.pcolormesh(
    lon_list,
    lat_list,
    band_data,
    cmap=cmap,
    shading="auto",
)

# Farbbalken mit Einheiten hinzufügen
cbar = plt.colorbar(pcm, ax=ax, label=f'{summary["Long Name"]} ({summary["Einheiten"]})')

# Diagrammtitel und Beschriftungen festlegen
ax.set_title(
    f'{summary["Long Name"]} ({summary["Einheiten"]}) - {selected_year}-{selected_month:02d}',
    fontsize=14,
)
ax.set_ylabel('Breitengrad', fontsize=12)
ax.set_xlabel('Längengrad', fontsize=12)

# Diagramm anzeigen
plt.tight_layout()
plt.show()

Sie können dem Plot weitere Informationen hinzufügen, wie zum Beispiel administrative Grenzen oder Gitterlinien. Zur besseren Lesbarkeit können Sie die Temperaturewerte von °Kelvin zu °Celsius konvertieren oder die Farbgebung anpassen. Einige Möglichkeiten haben wir Ihnen in den nächsten Code-Blöcken vorbereitet.

Das benötigte Shapefile von Konstanz können Sie sich hier herunterladen:

Laden Sie die Datei kn_boundary.zip herunter und entpacken Sie sie in Ihren Arbeitsordner. Denken Sie daran, im folgenden den Dateipfad zum Shapefile anzupassen, damit das Skript darauf zugreifen kann.

import numpy as np
import math as ma
import geopandas as gpd
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter
from mpl_toolkits.axes_grid1 import make_axes_locatable

# Temperaturdaten von Kelvin in °C umwandeln
band_data_C = variable_data[band_index, :, :] - 273.15

# Minimal- und Maximalwerte innerhalb der Banddaten berechnen
vmin = np.nanmin(band_data_C)
vmax = np.nanmax(band_data_C)

vmin_floor = ma.floor(vmin * 10) / 10
vmax_ceil = ma.ceil(vmax * 10) / 10

# Intervall für die Farbleiste berechnen
interval = 0.1
bins = int((vmax_ceil - vmin_floor) / interval)
# Funktion zum Formatieren von Breitengrad-Markierungen
def format_latitude(x, pos):
    return f"{x:.2f}°N"

# Funktion zum Formatieren von Längengrad-Markierungen
def format_longitude(x, pos):
    return f"{x:.2f}°E"

# Plotten mit matplotlib
fig, ax = plt.subplots(figsize=(8, 8))

# Vordefinierte Farbkarte mit 10 diskreten Farben laden
cmap = plt.get_cmap('turbo', bins)

pcm = ax.pcolormesh(
    lon_list,
    lat_list,
    band_data_C,
    cmap=cmap,
    vmin=vmin_floor,
    vmax=vmax_ceil
)

# Verwaltungsgrenze von Konstanz hinzufügen (Shapefile)
konstanz_shp = r"./shapefiles/kn_boundary.shp"
konstanz_boundary = gpd.read_file(konstanz_shp)
konstanz_boundary.boundary.plot(ax=ax, edgecolor='red')

# Farbbalken plotten
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=-0.95)
plt.colorbar(pcm, cax=cax, label=f'{summary["Long Name"]} (°C)')

# Gitternetzlinien hinzufügen
ax.grid(visible=True, which='major', color='#f0f0f0', linestyle='--', alpha=0.5)

# Benutzerdefinierte Tick-Formatierer für Breiten- und Längengrad festlegen
ax.xaxis.set_major_formatter(FuncFormatter(format_longitude))
ax.yaxis.set_major_formatter(FuncFormatter(format_latitude))

ax.set_title(f'{summary["Long Name"]} (°C)')
ax.set_ylabel('Breitengrad', fontsize=12)
ax.set_xlabel('Längengrad', fontsize=12)

# Diagramm anzeigen
plt.show()

Mit dem Plot können Sie sich einfach einen Überblick über die räumliche Ausprägung und Verteilung eines Paramenters verschaffen. Probieren Sie verschiedene Konfigurationen aus um herauszufinden, welche Farbgebung und Skala für Ihren Zweck am besten funktioniert.

3. Erstellen eines Plots für eine Zeitserie

Um den Verlauf der aggregierten monatlichen Temperatur-Mittelwerte für August aller Jahre einer Region zu betrachten eignet sich ein Linienplot. Diesen erstellen Sie mit dem foilgenden Code-Block. Dabei arbeiten Sie mit der dataframe-Struktur, einem Format, das (ähnlich einer Tabelle mit Zeilen und Spalten) sortierte Variablengruppen erstellt, welche sehr nützlich im Umgang mit großen Datenpaketen sein können.

Mehr Informationen zum richtigen Einsatz von Listen, Arrays und Dataframes in Python finden Sie online.

# Listen zum Speichern von Statistiken initialisieren
mean_values_list = []
median_values_list = []
std_values_list = []

# Gesamtanzahl der Zeitbänder berechnen
total_bands = range(variable_data.shape[0])

# Listen für Jahr und Monat basierend auf dem Zeitindex ableiten
year_list = [(band_index // 12) + 1950 for band_index in total_bands]
month_list = [(band_index % 12) + 1 for band_index in total_bands]

# Über alle Bänder iterieren, um Statistiken zu berechnen
for band_index in total_bands:
        # Kelvin in Celsius umrechnen
        band_data = variable_data[band_index, :, :] - 273.15

        # Statistiken berechnen und hinzufügen
        mean_values_list.append(np.nanmean(band_data))  # Mittelwert ohne NaNs
        median_values_list.append(np.ma.median(band_data))  # Median für maskierte Arrays
        std_values_list.append(np.nanstd(band_data))  # Standardabweichung ohne NaNs

# Ein Dictionary zur Speicherung der Ergebnisse erstellen
df_data = {
        'Jahr': year_list,
        'Monat': month_list,
        'Mittelwerd': mean_values_list,
        'Median': median_values_list,
        'Standardabweichung': std_values_list
}

# Dictionary in ein DataFrame umwandeln
df_statistics = pd.DataFrame(df_data)

# Die ersten Zeilen des DataFrames anzeigen
df_statistics.head()
import matplotlib.ticker as ticker

# Das Statistik-DataFrame nach dem ausgewählten Monat (August) filtern
selected_month = 8  # August
df_statistics_filtered = df_statistics[df_statistics['Month'] == selected_month]

# Diagramm initialisieren
fig, ax = plt.subplots(figsize=(14, 8), facecolor='#f1f1f1')

# Titel und Achsenbeschriftungen
ax.set_title(
        f'Durchschnittliche  {summary["Long Name"]} für August (°C)',
        fontsize=20,
        fontweight='bold',
        color='#333333',
        pad=20
)
ax.set_xlabel("Jahr", fontsize=16, color='#555555')
ax.set_ylabel(f'{summary["Long Name"]} (°C)', fontsize=16, color='#555555')

# Diagrammparameter für Konsistenz aktualisieren
params = {
        'axes.labelsize': 16,
        'axes.titlesize': 18,
        'xtick.labelsize': 12,
        'ytick.labelsize': 12,
}
plt.rcParams.update(params)

# Raster und Tick-Formatierung hinzufügen
ax.grid(visible=True, color='#b0b0b0', linestyle='--', linewidth=0.8, alpha=0.6)
ax.yaxis.set_major_formatter(ticker.FormatStrFormatter('%0.2f'))
ax.tick_params(axis='y', which='both', color='#b0b0b0')

# Begrenzung der y-Achse definieren
ax.set_ylim(15,24)

# Durchschnittliche Temperatur-Trendlinie zeichnen
line1, = ax.plot(
        df_statistics_filtered['Jahr'],
        df_statistics_filtered['Mittelwert'].astype(float),
        label='Durchschnittstemperatur',
        color='#ff6f61',
        linestyle='-.',
        marker='o',
        linewidth=2.5
)

# Quadratische Anpassung (Grad 2) für die Trendlinie berechnen
degree = 2  # Quadratische Anpassung
coefficients = np.polyfit(
        df_statistics_filtered['Jahr'],
        df_statistics_filtered['Mittelwert'].astype(float),
        degree
)
curve_fit = np.poly1d(coefficients)

# Angepasste Trendlinie zeichnen
ax.plot(
        df_statistics_filtered['Jahr'],
        curve_fit(df_statistics_filtered['Jahr']),
        label=f'Kurvenanpassung (Grad {degree})',
        color='blue',
        linestyle='--',
        linewidth=1.5
)

# Legende hinzufügen
ax.legend(loc='upper left', fontsize=12, frameon=True, facecolor='#ffffff', edgecolor='#b0b0b0')

# Diagramm anzeigen
plt.tight_layout()
plt.show()