cassionAnalyse de données

Retour à la leçonLeçon 4 sur 8Ce que Sphère demande et qu'une échelle ne dit pas

Testé sur un tiers, censuré sur un cinquième

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 / 26

    Ce que couvre cette leçon

    • Le dénominateur change, et le rapport généralement non
    • Les classes de risque du JMP
    • Amélioré n'est pas sûr, et c'est ici que cela se démontre
    • La valeur censurée, et pourquoi elle n'est pas manquante
    • Que faire d'une valeur censurée
    • L'incohérence entre champs qu'il faut nommer
    • Rapportez-le comme un bloc à part
    • La suite
    Notes du présentateur
    E. coli a été testé sur 33,4 % des ménages, le chlore sur 40,0 %, et les deux sur 13,3 %. Dans le registre des points d'eau, 271 lectures de chlore sont la chaîne "<0.1" — et les supprimer relève la conformité apparente de seize points.
  2. Diapositive 2 / 26

    Le dénominateur change, et le rapport généralement non — En Python

    import pandas as pd
    
    households = pd.read_csv("wash-household-survey-2024.v1.csv")
    
    ecoli = households["ecoli_cfu_100ml"].notna()
    chlorine = households["free_residual_chlorine_mgl"].notna()
    
    print(f"E. coli tested:   {ecoli.sum():>5} = {ecoli.mean():.1%}")
    print(f"chlorine tested:  {chlorine.sum():>5} = {chlorine.mean():.1%}")
    print(f"both:             {(ecoli & chlorine).sum():>5} = {(ecoli & chlorine).mean():.1%}")
    Notes du présentateur
    La qualité de l'eau coûte cher à mesurer. Une enquête qui interroge 2 403 ménages en teste une fraction, et tout indicateur de qualité tourne donc sur un dénominateur différent de tout indicateur d'accès.
  3. Diapositive 3 / 26

    Le dénominateur change, et le rapport généralement non — En R

    library(dplyr)
    
    households |> summarise(
      ecoli = mean(!is.na(ecoli_cfu_100ml)),
      chlorine = mean(!is.na(free_residual_chlorine_mgl)),
      both = mean(!is.na(ecoli_cfu_100ml) & !is.na(free_residual_chlorine_mgl))
    )
  4. Diapositive 4 / 26

    Le dénominateur change, et le rapport généralement non

    • 802 ménages pour E. coli, 961 pour le chlore, et 320 pour les deux — Le dernier nombre est celui qui compte : toute…
    • Imprimez le n analysable pour chaque chiffre de qualité — dans le tableau et non en note de bas de page
    Notes du présentateur
    802 ménages pour E. coli, 961 pour le chlore, et 320 pour les deux. Le dernier nombre est celui qui compte : toute question liant les deux porte sur 13,3 % de l'enquête, et son intervalle sera large. Imprimez le n analysable pour chaque chiffre de qualité, dans le tableau et non en note de bas de page. Un pourcentage de qualité voisin d'un pourcentage d'accès sur la même ligne se lit comme le même dénominateur, et ici ce n'est jamais le cas.
  5. Diapositive 5 / 26

    Les classes de risque du JMP — En Python

    tested = households[ecoli]
    classes = pd.cut(
        tested["ecoli_cfu_100ml"],
        [-1, 0, 10, 100, 10**9],
        labels=["safe <1", "low 1-10", "moderate 11-100", "high >100"],
    )
    print(classes.value_counts(normalize=True).round(3))
  6. Diapositive 6 / 26

    Les classes de risque du JMP — En R

    households |>
      filter(!is.na(ecoli_cfu_100ml)) |>
      mutate(risk = cut(ecoli_cfu_100ml, c(-1, 0, 10, 100, Inf),
                        labels = c("safe", "low", "moderate", "high"))) |>
      count(risk) |> mutate(share = n / sum(n))
  7. Diapositive 7 / 26

    Les classes de risque du JMP

    Classe de risqueMénagesPart des testés
    Sûre (<1 UFC/100 mL)43053,6 %
    Faible (1–10)17321,6 %
    Modérée (11–100)13917,3 %
    Élevée (>100)607,5 %
  8. Diapositive 8 / 26

    Les classes de risque du JMP

    • Rapportez les classes, non une moyenne — Les dénombrements d'E
    Notes du présentateur
    Rapportez les classes, non une moyenne. Les dénombrements d'E. coli sont fortement asymétriques et une moyenne de 14 UFC/100 mL ne décrit rien — les classes sont ce dans quoi la directive est écrite et ce sur quoi une décision se prend.
  9. Diapositive 9 / 26

    Amélioré n'est pas sûr, et c'est ici que cela se démontre — En Python

    IMPROVED = {"piped-into-dwelling", "piped-into-yard", "public-tap",
                "borehole", "protected-well", "protected-spring"}
    
    tested = tested.assign(improved=tested["water_source"].isin(IMPROVED))
    print(tested.groupby("improved")["ecoli_cfu_100ml"].agg(
        n="size", safe=lambda s: (s < 1).mean(), high=lambda s: (s > 100).mean()
    ).round(3))
  10. Diapositive 10 / 26

    Amélioré n'est pas sûr, et c'est ici que cela se démontre — En R

    households |>
      filter(!is.na(ecoli_cfu_100ml)) |>
      mutate(improved = water_source %in% improved) |>
      summarise(n = n(), safe = mean(ecoli_cfu_100ml < 1),
                high = mean(ecoli_cfu_100ml > 100), .by = improved)
  11. Diapositive 11 / 26

    Amélioré n'est pas sûr, et c'est ici que cela se démontre

    SourceTestésSûreRisque élevé
    Améliorée62561,1 %3,8 %
    Non améliorée ou de surface17727,1 %20,3 %
    Notes du présentateur
    Le type de source prédit fortement la qualité, et 38,9 % des ménages sur une source améliorée ont malgré tout de l'E. coli détectable. La leçon 1 disait que l'échelon supérieur exige l'absence de contamination en plus d'une source améliorée ; voici le nombre qui montre pourquoi c'est une condition distincte et non une formalité.
  12. Diapositive 12 / 26

    La valeur censurée, et pourquoi elle n'est pas manquante — En Python

    points = pd.read_csv("water-point-monitoring-2024.v1.csv")
    readings = points["free_residual_chlorine_mgl"].dropna()
    
    censored = readings.astype(str).str.startswith("<")
    print(f"tested {len(readings)}, censored {censored.sum()}")
    print(readings[censored].unique())
    Notes du présentateur
    Le registre des points d'eau mesure le chlore sur le terrain, et le kit a une limite de détection.
  13. Diapositive 13 / 26

    La valeur censurée, et pourquoi elle n'est pas manquante — En R

    points |>
      filter(!is.na(free_residual_chlorine_mgl)) |>
      count(censored = startsWith(free_residual_chlorine_mgl, "<"))
  14. Diapositive 14 / 26

    La valeur censurée, et pourquoi elle n'est pas manquante — En Python

    numeric = pd.to_numeric(readings, errors="coerce")   # <-- the silent one
    print(f"after coercion: {numeric.isna().sum()} became NaN")
    
    in_range = readings[~censored].astype(float).between(0.2, 0.5)
    print(f"in target range, over all tested:   {in_range.sum() / len(readings):.1%}")
    print(f"in target range, dropping censored: {in_range.mean():.1%}")
    Notes du présentateur
    811 visites portent une lecture et 271 d'entre elles sont la chaîne <0.1. C'est une mesure : la vraie valeur se situe entre zéro et la limite de détection. Ce n'est pas une valeur manquante, et les deux choses en lesquelles on la transforme habituellement sont fausses toutes les deux.
  15. Diapositive 15 / 26

    La valeur censurée, et pourquoi elle n'est pas manquante — En R

    points |>
      filter(!is.na(free_residual_chlorine_mgl),
             !startsWith(free_residual_chlorine_mgl, "<")) |>
      summarise(in_range = mean(between(as.numeric(free_residual_chlorine_mgl), 0.2, 0.5)))
  16. Diapositive 16 / 26

    La valeur censurée, et pourquoi elle n'est pas manquante

    • 30,9 % contre 46,5 % — Supprimer les lectures censurées relève la conformité apparente à la cible de chloration de près…
    Notes du présentateur
    30,9 % contre 46,5 %. Supprimer les lectures censurées relève la conformité apparente à la cible de chloration de près de seize points, et le fait dans une seule direction — toute valeur censurée est un échec, si bien que les retirer ne retire que des échecs. pd.to_numeric(..., errors="coerce") le fait en silence et ressemble à une correction de type.
  17. Diapositive 17 / 26

    Que faire d'une valeur censurée

    • Classer plutôt que moyenner — La cible est une plage, si bien qu'une lecture censurée répond parfaitement à la question…
    • Substituer la limite, ou sa moitié — s'il vous faut vraiment une moyenne
    • Rapporter la part sous la limite comme un nombre à part entière — 33,4 % de ces lectures sont sous le seuil de…
    Notes du présentateur
    Trois options, par ordre de préférence pour cet indicateur. Classer plutôt que moyenner. La cible est une plage, si bien qu'une lecture censurée répond parfaitement à la question : <0.1 est en dessous de 0,2 et donc non conforme. La conformité se calcule sur les 811 lectures sans aucune substitution. Substituer la limite, ou sa moitié, s'il vous faut vraiment une moyenne. Dites laquelle vous avez employée — LD/2 est la convention courante — et rapportez à combien de valeurs elle s'est appliquée. Rapporter la part sous la limite comme un nombre à part entière. 33,4 % de ces lectures sont sous le seuil de détection, ce qui est un constat sur la pratique de chloration et non une gêne dans les données.
  18. Diapositive 18 / 26

    Que faire d'une valeur censurée — En Python

    compliant = pd.to_numeric(readings.where(~censored), errors="coerce").between(0.2, 0.5)
    result = pd.DataFrame({
        "readings": [len(readings)],
        "below detection": [censored.sum()],
        "in 0.2-0.5 range": [compliant.sum()],
        "compliance": [f"{compliant.sum() / len(readings):.1%}"],
    })
    print(result)
  19. Diapositive 19 / 26

    Que faire d'une valeur censurée — En R

    # The denominator is every reading, including the ones below the limit.
  20. Diapositive 20 / 26

    Que faire d'une valeur censurée

    • Ne laissez jamais une limite de détection rétrécir un dénominateur — C'est la façon la plus courante dont un rapport de…
    Notes du présentateur
    Ne laissez jamais une limite de détection rétrécir un dénominateur. C'est la façon la plus courante dont un rapport de qualité de l'eau devient optimiste, et elle survit à la relecture parce que l'arithmétique sur les valeurs restantes est juste.
  21. Diapositive 21 / 26

    L'incohérence entre champs qu'il faut nommer — En Python

    treats = households["water_treated_at_home"]
    print(f"treat at home: {treats.sum()}")
    print(f"  of which no chlorine reading: {(treats & ~chlorine).sum()}")
  22. Diapositive 22 / 26

    L'incohérence entre champs qu'il faut nommer — En R

    households |> filter(water_treated_at_home) |>
      count(no_reading = is.na(free_residual_chlorine_mgl))
    Notes du présentateur
    533 ménages déclarent traiter leur eau et n'ont aucune mesure de chlore. Le cours sur le nettoyage a établi de quoi il s'agit — une absence corrélée à la chose mesurée — et la conséquence ici est précise : supprimer ces lignes retire des ménages qui traitent leur eau plus souvent que la moyenne, si bien que le sous-échantillon testé n'est pas représentatif de l'enquête dont il vient.
  23. Diapositive 23 / 26

    Rapportez-le comme un bloc à part — Exemple (suite)

    Water quality
    
      E. coli, point of collection          802 households tested (33.4% of survey)
        Safe <1 CFU/100 mL                  53.6%
        Low 1-10                            21.6%
        Moderate 11-100                     17.3%
        High >100                            7.5%
        Improved sources: 61.1% safe (n=625)
        Unimproved and surface: 27.1% safe (n=177)
    
      Free residual chlorine, water points  811 visits tested (30.8% of visits)
        Below detection limit (<0.1 mg/L)   33.4%   counted as non-compliant
        Within 0.2-0.5 mg/L target          30.9%
    
      Quality was tested on a subsample and does not share the denominator of the
      access indicators above. 533 households reporting home treatment have no
  24. Diapositive 24 / 26

    Rapportez-le comme un bloc à part — Exemple (suite)

      chlorine reading, so the tested subsample over-represents untreated water.
    Notes du présentateur
    Son propre bloc, ses propres dénominateurs, sa propre limite. Un chiffre de qualité placé dans un tableau de chiffres d'accès sera lu contre leur dénominateur, et rien dans la mise en page ne l'en empêchera.
  25. Diapositive 25 / 26

    La suite

    • Tout ce qui précède est une coupe transversale : ce qui était vrai le jour où quelqu'un est passé.
    Notes du présentateur
    Tout ce qui précède est une coupe transversale : ce qui était vrai le jour où quelqu'un est passé. L'unité suivante porte sur le registre qui visite les mêmes points d'eau douze fois, et sa première conséquence est de transformer un taux de fonctionnalité en trois.
  26. Diapositive 26 / 26

    La suite

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