cassionAnalyse de données

Retour à la leçonLeçon 7 sur 8Produire et transmettre

Dates, âges et périodes de rapportage

Le même diaporama que les téléchargements, rendu sous forme de page. Lancez le diaporama pour le présenter en plein écran — les flèches ou un clic avancent d'une diapositive, Échap quitte.

Diapositives · PDFDiapositives · PowerPoint

  1. Diapositive 1 / 24

    Ce que couvre cette leçon

    • Pourquoi cela mérite une leçon entière
    • Analyser la date, une fois, explicitement
    • L'âge en mois révolus
    • Les périodes de rapportage
    • Rééchantillonner une série temporelle
    • Les durées
    • Les fuseaux horaires, brièvement
    • Ce qui vient ensuite
    Notes du présentateur
    L'âge en mois révolus calculé correctement plutôt qu'en divisant des jours, des périodes de rapportage qui survivent à un mois partiel, et l'arithmétique de frontière dont dépend un standard de croissance.
  2. Diapositive 2 / 24

    Pourquoi cela mérite une leçon entière

    • L'arithmétique des dates paraît être la partie facile et produit certaines des erreurs les plus coûteuses du secteur.
    Notes du présentateur
    L'arithmétique des dates paraît être la partie facile et produit certaines des erreurs les plus coûteuses du secteur. Un âge en mois décide du standard de croissance auquel un enfant est comparé. Une période de rapportage décide si une formation sanitaire compte comme ayant rapporté. Une frontière de mois décide si une distribution tombe dans les chiffres de ce trimestre ou du suivant. Aucun de ces cas n'échoue bruyamment.
  3. Diapositive 3 / 24

    Analyser la date, une fois, explicitement — En Python

    import pandas as pd
    
    muac = pd.read_csv(path)
    muac["screening_date"] = pd.to_datetime(
        muac["screening_date"], format="%Y-%m-%d", errors="raise"
    )
  4. Diapositive 4 / 24

    Analyser la date, une fois, explicitement

    • Énoncez le format. Un format inféré peut changer d'un fichier à l'autre selon la composition des valeurs — la même…
    • Utilisez errors="raise". L'alternative, errors="coerce", transforme toute date illisible en valeur manquante :…
    Notes du présentateur
    Deux habitudes à prendre : 01/02/2024 est soit le 1er février, soit le 2 janvier, et le fichier ne vous dira pas lequel. Si un export vous livre des dates ambiguës, dayfirst=True est une décision que vous documentez, non un réglage que vous acceptez.
  5. Diapositive 5 / 24

    L'âge en mois révolus — En Python

    # Faux assez souvent pour que cela compte.
    age_months = ((visit_date - date_of_birth).dt.days / 30.4375).astype(int)
    Notes du présentateur
    L'approche évidente divise des jours par un mois moyen :
  6. Diapositive 6 / 24

    L'âge en mois révolus — En Python

    def age_in_months(dob: pd.Series, visit: pd.Series) -> pd.Series:
        months = (visit.dt.year - dob.dt.year) * 12 + (visit.dt.month - dob.dt.month)
        # Le quantieme n'est pas atteint : le dernier mois n'est pas revolu.
        return months - (visit.dt.day < dob.dt.day).astype(int)
    Notes du présentateur
    Sur les deux mille premiers jours de vie, ce calcul diverge de la bonne réponse sur 54 d'entre eux — toujours d'un mois, toujours à une frontière. Un enfant de 60 jours est rapporté à 1 mois alors qu'il en a 2. Calculez directement les mois révolus :
  7. Diapositive 7 / 24

    L'âge en mois révolus — En Python

    dob   = pd.to_datetime(["2022-03-15", "2022-03-16", "2022-02-28"])
    visit = pd.to_datetime(["2024-03-15", "2024-03-15", "2024-03-01"])
    
    age_in_months(pd.Series(dob), pd.Series(visit)).tolist()   # [24, 23, 24]
  8. Diapositive 8 / 24

    L'âge en mois révolus

    • Pourquoi un mois compte ici
    • Les standards de croissance de l'OMS changent de mesure exactement à 24 mois — En dessous de 24 mois, l'enfant est…
    • Les tranches d'âge sont l'autre endroit — 0-5, 6-23, 24-59 mois sont les tranches nutritionnelles standard, et un…
    Notes du présentateur
    Le deuxième enfant est à un jour de ses deux ans : il a 23 mois, non 24. Ce seul jour est toute la raison d'être de cette fonction. Les standards de croissance de l'OMS changent de mesure exactement à 24 mois. En dessous de 24 mois, l'enfant est mesuré couché et comparé à la courbe poids-pour-longueur ; à partir de 24 mois, debout, contre le poids-pour-taille. Les deux mesures diffèrent d'environ 0,7 cm et les deux courbes sont des tables différentes. Un enfant poussé de 23 à 24 mois par une règle d'arrondi est comparé au mauvais standard, et son score Z se déplace. Faites cela à quelques centaines d'enfants près de la frontière et l'estimation de prévalence se déplace avec eux — c'est pourquoi le projet Analyse d'enquête SMART rapporte l'attraction des âges vers les années entières comme un constat de plausibilité et non comme une curiosité. Les tranches d'âge sont l'autre endroit. 0-5, 6-23, 24-59 mois sont les tranches nutritionnelles standard, et un enfant à exactement 24 mois appartient à la troisième :
  9. Diapositive 9 / 24

    L'âge en mois révolus — En Python

    BANDS = [0, 6, 24, 60]
    LABELS = ["0-5m", "6-23m", "24-59m"]
    
    muac["age_band"] = pd.cut(
        muac["age_months"], bins=BANDS, labels=LABELS, right=False, include_lowest=True
    )
  10. Diapositive 10 / 24

    L'âge en mois révolus — En Python

    check = muac.loc[muac["age_months"].isin([5, 6, 23, 24, 59, 60]),
                     ["age_months", "age_band"]].drop_duplicates().sort_values("age_months")
    print(check)
    Notes du présentateur
    right=False rend chaque intervalle [bas, haut) — fermé à gauche, ouvert à droite — de sorte que 24 mois tombe dans 24-59m et non dans 6-23m. Le comportement par défaut est l'inverse, et il est faux pour toutes les tranches d'âge employées dans ce secteur. Vérifiez les frontières plutôt que de leur faire confiance :
  11. Diapositive 11 / 24

    Les périodes de rapportage — En Python

    muac["month"] = muac["screening_date"].dt.to_period("M")
    
    by_month = muac.groupby("month").size()
    Notes du présentateur
    Une période est un mois, un trimestre ou une année pour lequel on rapporte, et ce n'est pas la même chose qu'une date.
  12. Diapositive 12 / 24

    Les périodes de rapportage — Exemple

    2024-01    207
    2024-02    391
    ...
    2024-11    375
    2024-12    156
  13. Diapositive 13 / 24

    Les périodes de rapportage — En Python

    current = pd.Period("2024-06", freq="M")
    print(current - 1)                 # 2024-05
    print(current.start_time, current.end_time)
    Notes du présentateur
    Les périodes se trient correctement, s'affichent lisiblement et — contrairement à une chaîne comme "2024-01" — acceptent l'arithmétique :
  14. Diapositive 14 / 24

    Les périodes de rapportage

    • Les périodes partielles sont le piège
    Notes du présentateur
    Les chaînes se trient aussi correctement si vous employez l'ordre ISO, bonne raison d'utiliser %Y-%m et jamais %m/%Y. Mais une chaîne ne sait pas répondre à « le mois précédent », et cette question surgit dans tout calcul de tendance. Ce registre court du 15 janvier au 13 décembre. Janvier porte 207 dépistages et décembre 156, contre une moyenne mensuelle proche de 375. Aucun de ces deux mois n'est une baisse ; les deux sont partiels.
  15. Diapositive 15 / 24

    Les périodes de rapportage — En Python

    first, last = muac["screening_date"].min(), muac["screening_date"].max()
    print(first.date(), last.date())
    
    muac["partial_period"] = muac["month"].isin(
        [first.to_period("M"), last.to_period("M")]
    )
  16. Diapositive 16 / 24

    Les périodes de rapportage

    • Étiquetez-les plutôt que de les supprimer — Un lecteur qui voit décembre s'effondrer sur un graphique conclut que le…
    • Aligner deux sources sur des périodes
    Notes du présentateur
    Étiquetez-les plutôt que de les supprimer. Un lecteur qui voit décembre s'effondrer sur un graphique conclut que le programme s'est écroulé ; un lecteur qui ne voit aucun décembre se demande où sont passées les données. Nommer le mois partiel est la seule option qui répond aux deux. La même logique vaut pour une période inachevée. Un cumul en cours de mois comparé à des mois révolus est la façon la plus courante dont un tableau de bord affiche une baisse fictive tous les mois, et cela arrive parce que les deux s'appellent « le total mensuel ». L'extraction de vaccination enregistre une date de début de période plutôt qu'une date de prestation :
  17. Diapositive 17 / 24

    Les périodes de rapportage — En Python

    epi = pd.read_csv(path, parse_dates=["period"])
    epi["month"] = epi["period"].dt.to_period("M")
    print(epi["month"].nunique())      # 12
    Notes du présentateur
    Convertir les deux sources en Period avant la jointure est ce qui les fait coïncider. Joindre une date à un début de période ne produit silencieusement aucune correspondance pour chaque mois où une source a utilisé le dernier jour et l'autre le premier.
  18. Diapositive 18 / 24

    Rééchantillonner une série temporelle — En Python

    daily = muac.set_index("screening_date")
    weekly = daily.resample("W")["child_id"].size()
    monthly = daily.resample("MS")["child_id"].size()      # MS = debut de mois
    Notes du présentateur
    Quand l'index est un datetime, resample agrège par période sans colonne de regroupement manuelle : resample comble les trous : une semaine sans dépistage apparaît à zéro plutôt que d'être absente. C'est juste pour une série temporelle et faux pour un dénominateur — une école fermée quinze jours n'a pas de jours de présence, et non une présence nulle ; le projet Assiduité scolaire montre ce que coûte le remplissage.
  19. Diapositive 19 / 24

    Les durées — En Python

    referrals["days_to_service"] = (
        referrals["service_date"] - referrals["referral_date"]
    ).dt.days
    Notes du présentateur
    Soustraire deux datetimes donne un Timedelta :
  20. Diapositive 20 / 24

    Les durées — En Python

    hours = (referrals["service_date"] - referrals["referral_date"]) / pd.Timedelta(hours=1)
    referrals["same_day"] = hours < 24
    Notes du présentateur
    .dt.days tronque vers zéro : un écart de 23 heures vaut 0 jour. Si le même jour compte — et pour un référencement en protection, c'est le cas — dites-le explicitement plutôt que de laisser la troncature décider :
  21. Diapositive 21 / 24

    Les durées — En Python

    impossible = referrals["days_to_service"] < 0
    print(f"service saisi avant le referencement : {impossible.sum()}")
    Notes du présentateur
    Une durée négative est un constat de qualité des données, non une valeur dont on prend la valeur absolue :
  22. Diapositive 22 / 24

    Les fuseaux horaires, brièvement — En Python

    submissions["submitted_at"] = pd.to_datetime(
        submissions["submitted_at"], utc=True
    ).dt.tz_convert("America/Port-au-Prince")
    
    submissions["submission_date"] = submissions["submitted_at"].dt.date
    Notes du présentateur
    Les données de programme sont généralement des dates locales naïves et doivent le rester. Attacher un fuseau à une date de dépistage invite une conversion qui fera franchir minuit à un enregistrement et le fera basculer dans le mauvais mois de rapportage. Là où des horodatages portent réellement un fuseau — heures de soumission des formulaires CommCare ou Kobo côté serveur — convertissez une fois, à la frontière, et stockez la date locale : L'horodatage de soumission et la date de la visite qu'il enregistre sont deux choses différentes, et rapporter la première quand on veut dire la seconde décale toute soumission du soir vers le lendemain.
  23. Diapositive 23 / 24

    Ce qui vient ensuite

    • Chaque colonne dit désormais ce qu'elle signifie, y compris celles faites de dates.
    Notes du présentateur
    Chaque colonne dit désormais ce qu'elle signifie, y compris celles faites de dates. La dernière leçon transforme l'analyse en un script qu'un autre chargé pourra exécuter au trimestre prochain sur un autre export, sans rien modifier d'autre que ses arguments.
  24. Diapositive 24 / 24

    La suite

    Lire la leçon complète, avec le code exécutable Retour à la leçon