Annexe G — Semaines 3–5 — Préparer les données de glissements

Cette page reproduit les deux fichiers utilisés pendant le bloc de statistiques univariées: le catalogue réel chargé en semaines 3–4, puis le jeu synthétique utilisé pour introduire l’inférence en semaine 5. La simulation est placée à la fin de la même chaîne afin de distinguer clairement les observations publiées des données produites pour l’enseignement.

G.1 Source originale

Le catalogue de Dohmen et al. (2025) est publié par PANGAEA sous le DOI 10.1594/PANGAEA.979839. Le fichier téléchargé le 28 septembre 2026 est conservé sans modification dans:

appendices/data/landslides/Dohmen_et_al_2025_PANGAEA_979839.tab

Le fichier contient 398 lignes de métadonnées avant l’en-tête de la table. Les noms fournis par PANGAEA sont parfois tronqués ou dupliqués; les dates utilisent plusieurs formats et certaines catégories sont codées par des abréviations. La préparation doit donc conserver l’information originale tout en créant des variables stables pour l’analyse.

G.2 Lire la table et stabiliser ses colonnes

Le premier bloc localise l’en-tête au lieu de supposer silencieusement qu’il restera toujours à la même ligne. pd.read_csv() lit ensuite la partie tabulée. Les 31 noms explicites décrivent les colonnes originales sans encore transformer leurs valeurs.

Listing G.1: Extraire la table PANGAEA et attribuer des noms de colonnes stables
from pathlib import Path
import re

import numpy as np
import pandas as pd

source_path = Path(
    "appendices/data/landslides/Dohmen_et_al_2025_PANGAEA_979839.tab"
)

with source_path.open(encoding="utf-8") as fichier:
    ligne_entete = next(
        i for i, ligne in enumerate(fichier)
        if ligne.startswith("Event\t")
    )

df = pd.read_csv(
    source_path,
    sep="\t",
    skiprows=ligne_entete,
)

colonnes_source = [
    "event", "event_label", "landslide_id", "tsunami_id",
    "ngdc_tsunami_id", "tsunami_latitude", "tsunami_longitude",
    "landslide_latitude", "landslide_longitude", "country", "continent",
    "event_date_raw", "water_body_code", "landslide_cause_code",
    "n_landslides_reported", "landslide_location", "landslide_type",
    "landslide_material", "landslide_height_m", "landslide_distance_m",
    "landslide_width_m", "landslide_volume_m3", "trigger_magnitude",
    "max_wave_height_m", "max_runup_height_m", "max_water_height_m",
    "max_inundation_distance_m", "fatalities", "damage_class_code",
    "references", "status",
]

assert len(df.columns) == len(colonnes_source)
df.columns = colonnes_source
1
Repère la première ligne de la table à partir de son contenu.
2
Ignore le préambule et lit les valeurs séparées par des tabulations.
3
Remplace les libellés tronqués par des noms uniques en snake_case.

G.3 Expliciter les dates et les catégories

La colonne event_date_raw est conservée telle quelle, car la précision historique varie d’un événement à l’autre. La fonction suivante extrait seulement l’information réellement présente: une année, un mois ou un jour. Elle ne fabrique donc pas un 1er janvier lorsque seule l’année est connue.

Les dictionnaires traduisent ensuite les codes de milieu, de cause et de dommage. map() crée une nouvelle colonne descriptive sans supprimer le code source.

Listing G.2: Décrire la précision des dates et traduire les catégories
def analyser_date(valeur):
    if pd.isna(valeur):
        return pd.NA, pd.NA, pd.NA, "unknown", pd.NA

    texte = str(valeur).strip()

    if re.fullmatch(r"-?\d{1,4}", texte):
        return int(texte), pd.NA, pd.NA, "year", texte

    resultat = re.fullmatch(r"(\d{4})-(\d{2})", texte)
    if resultat:
        annee, mois = map(int, resultat.groups())
        return annee, mois, pd.NA, "month", texte

    resultat = re.fullmatch(r"(\d{4})-(\d{2})-(\d{2})", texte)
    if resultat:
        annee, mois, jour = map(int, resultat.groups())
        return annee, mois, jour, "day", texte

    resultat = re.fullmatch(r"(\d{2})\.(\d{2})\.(\d{4})", texte)
    if resultat:
        jour, mois, annee = map(int, resultat.groups())
        iso = f"{annee:04d}-{mois:02d}-{jour:02d}"
        return annee, mois, jour, "day", iso

    return pd.NA, pd.NA, pd.NA, "unparsed", pd.NA


dates = pd.DataFrame(
    [analyser_date(valeur) for valeur in df["event_date_raw"]],
    columns=[
        "event_year", "event_month", "event_day",
        "event_date_precision", "event_date_iso",
    ],
    index=df.index,
)

for colonne in ["event_year", "event_month", "event_day"]:
    dates[colonne] = dates[colonne].astype("Int64")

df = pd.concat([df, dates], axis="columns")

df["water_body"] = df["water_body_code"].map({
    "OM": "open marine",
    "EM": "enclosed marine",
    "IW": "inland water",
})

df["landslide_cause"] = df["landslide_cause_code"].map({
    "EQ": "earthquake", "V": "volcanic activity",
    "PA": "paraglacial conditions", "PR": "precipitation",
    "A": "anthropogenic activity", "U": "unknown",
})

df["damage_class"] = df["damage_class_code"].map({
    1: "limited (<1 million USD)",
    2: "moderate (1–5 million USD)",
    3: "severe (5–25 million USD)",
    4: "extreme (>25 million USD)",
})
1
Analyse chaque date sans augmenter artificiellement sa précision.
2
Ajoute les cinq variables dérivées à la table originale.
3
Traite le dommage comme une classe ordinale, et non comme un montant mesuré.

G.4 Convertir les mesures et documenter les décisions

pd.to_numeric() garantit que les mesures sont numériques et transforme les textes incompatibles en valeurs manquantes. Un volume exactement égal à zéro ne représente pas ici un glissement sans volume: il signale une valeur non renseignée. Le remplacement est donc accompagné d’un indicateur qui permet de retrouver les lignes concernées.

Listing G.3: Convertir les mesures et créer les indicateurs de contrôle
colonnes_numeriques = [
    "landslide_id", "tsunami_id", "ngdc_tsunami_id",
    "tsunami_latitude", "tsunami_longitude",
    "landslide_latitude", "landslide_longitude",
    "landslide_height_m", "landslide_distance_m", "landslide_width_m",
    "landslide_volume_m3", "trigger_magnitude", "max_wave_height_m",
    "max_runup_height_m", "max_water_height_m",
    "max_inundation_distance_m", "fatalities", "damage_class_code",
]

df[colonnes_numeriques] = df[colonnes_numeriques].apply(
    pd.to_numeric,
    errors="coerce",
)

df["status_clean"] = df["status"].map({
    "okay": "valid",
    "erroneous entry": "erroneous",
    "tsunami triggered by multiple landslides": "multiple_landslides",
})

df["is_valid_entry"] = df["status_clean"] != "erroneous"
df["is_multiple_landslide_tsunami"] = (
    df["status_clean"] == "multiple_landslides"
)

volume_nul = df["landslide_volume_m3"] == 0
df["landslide_volume_zero_replaced"] = volume_nul
df.loc[volume_nul, "landslide_volume_m3"] = np.nan
1
Convertit les identifiants et les mesures sans supprimer de ligne.
2
Conserve les entrées problématiques, mais rend leur statut directement filtrable.
3
Remplace les zéros de volume tout en enregistrant cette intervention.

Cette stratégie sépare la préparation, qui rend les décisions visibles, de la sélection analytique réalisée par les étudiants. En semaine 3 3.2.5.1, ceux-ci produisent Dohmen_et-al_landslides_valid.csv en conservant les lignes dont is_valid_entry vaut True.

G.5 Écrire et contrôler le catalogue pédagogique

Listing G.4: Écrire et vérifier le catalogue utilisé en semaines 3 et 4
sortie = "course/data/Dohmen_et-al_landslides_clean.csv"
df.to_csv(sortie, index=False)

assert df.shape == (355, 43)
assert df["landslide_id"].notna().sum() == 354
assert not (df["landslide_volume_m3"] == 0).any()
assert set(df["event_date_precision"]) <= {"day", "month", "year"}
assert set(df["is_valid_entry"].unique()) <= {True, False}
1
Écrit le fichier distribué en UTF-8, sans index technique.
2
Vérifie l’effectif, la structure et les principales décisions de nettoyage.

Ces contrôles détectent une modification de la chaîne, mais ne prouvent pas que chaque valeur historique du catalogue est exacte.

G.6 Générer les données synthétiques de la semaine 5

Le catalogue réel contient peu d’observations dans certaines catégories. La semaine 5 utilise donc un jeu synthétique pour étudier l’inférence avec des effectifs contrôlés. Il ne complète pas le catalogue et ne constitue aucune preuve supplémentaire sur les glissements naturels.

Nous simulons directement le logarithme décimal des volumes:

\[ \log_{10}(V_V)\sim\mathcal{N}(7.0,1.1^2) \tag{G.1}\]

\[ \log_{10}(V_P)\sim\mathcal{N}(6.3,0.65^2) \tag{G.2}\]

Dans Équation G.1 et Équation G.2, \(V_V\) et \(V_P\) sont les volumes en mètres cubes associés respectivement à l’activité volcanique et aux précipitations. Les tailles sont fixées à 60 et 90 observations. Les dispersions différentes sont intentionnelles: elles motivent l’emploi du test de Welch plutôt qu’un test supposant une variance commune.

G.6.1 Génération reproductible

Listing G.5: Simuler et écrire les volumes utilisés en semaine 5
rng = np.random.default_rng(1234)

parametres = [
    ("volcanic activity", 60, 7.0, 1.1, "V"),
    ("precipitation", 90, 6.3, 0.65, "P"),
]

groupes = []

for cause, n, moyenne, ecart_type, prefixe in parametres:
    log_volume = rng.normal(moyenne, ecart_type, n)
    groupe = pd.DataFrame({
        "event_id": [f"SYN-{prefixe}{i:03d}" for i in range(1, n + 1)],
        "landslide_cause": cause,
        "landslide_volume_m3": np.rint(10 ** log_volume).astype("int64"),
    })
    groupes.append(groupe)

simulation = pd.concat(groupes, ignore_index=True)
simulation.to_csv(
    "course/data/landslides_volumes_synthetic.csv",
    index=False,
)

assert simulation.shape == (150, 3)
assert simulation["event_id"].is_unique
assert (simulation["landslide_volume_m3"] > 0).all()
assert simulation["landslide_cause"].value_counts().to_dict() == {
    "precipitation": 90,
    "volcanic activity": 60,
}
1
Fixe la graine afin de reproduire exactement les mêmes observations.
2
Tire les logarithmes dans les deux distributions définies ci-dessus.
3
Réunit les groupes en une table de 150 événements.
4
Écrit le CSV chargé en semaine 5.

Le catalogue nettoyé et la simulation sont ainsi reproductibles sur une même page, mais leur statut scientifique reste explicitement différent: le premier décrit des cas publiés; la seconde matérialise un modèle pédagogique.