Retour à la leçon·Leçon 4 sur 8·Une épidémie, un cas à la fois
Taux d'attaque et le 1 % que personne n'atteint
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.
Ce que couvre cette leçon
- Le taux d'attaque
- Taux d'attaque par âge
- La létalité
- Le seuil
- L'explication, et pourquoi elle est indisponible
- Le bloc de rapportage
- La suite
Notes du présentateur
Le taux d'attaque exige la population ; la létalité exige un dénominateur qui exclut le cas encore hospitalisé. 3,90 % contre une cible de 1 %, 6,30 % dans un district — et l'explication repose sur une statistique de délai que la leçon précédente a montrée cassée.Le taux d'attaque — En Python
import pandas as pd cases = pd.read_csv("cholera-line-list-2024.v1.csv") population = pd.read_csv("district-population-2024.v1.csv") by_district = ( cases.groupby("district").size().rename("cases") .to_frame() .join(population.groupby("district")["population"].sum()) ) by_district["per_1000"] = 1000 * by_district["cases"] / by_district["population"] print(by_district.round(2))Notes du présentateur
Le taux d'attaque, ce sont les cas sur la population à risque, sur la période de l'épidémie. Malgré son nom c'est une proportion, et il exige le fichier de population que la liste linéaire livre avec elle.Le taux d'attaque — En R
library(dplyr) cases |> count(district, name = "cases") |> left_join(summarise(population, population = sum(population), .by = district), by = "district") |> mutate(per_1000 = 1000 * cases / population)Le taux d'attaque
District Cas Population Pour 1 000 Nord 381 48 000 7,94 Centre 401 62 000 6,47 Sud 193 35 000 5,51 Notes du présentateur
Nord est le plus touché à 7,94 et Sud le moins à 5,51, soit un écart de 2,43 pour 1 000. Retenez ce nombre : la leçon 6 montre que la moitié n'est pas ce qu'elle paraît. Remarquez aussi que les effectifs de cas ne se classent pas comme les taux — Centre a le plus de cas et n'est pas le plus touché. Un effectif répond à « où envoyer les intrants » ; un taux répond à « où le risque est le plus élevé ». Rapportez les deux, et ne laissez jamais un diagramme en barres d'effectifs se lire comme une carte du risque.Taux d'attaque par âge — En Python
by_band = ( cases.groupby(["district", "age_band"]).size().rename("cases") .to_frame() .join(population.set_index(["district", "age_band"])["population"]) ) by_band["per_1000"] = 1000 * by_band["cases"] / by_band["population"] print(by_band["per_1000"].unstack().round(2))Taux d'attaque par âge — En R
cases |> count(district, age_band, name = "cases") |> left_join(population, by = c("district", "age_band")) |> mutate(per_1000 = 1000 * cases / population) |> tidyr::pivot_wider(id_cols = age_band, names_from = district, values_from = per_1000)Taux d'attaque par âge
Tranche d'âge Nord Centre Sud 0-4 14,77 12,69 11,69 5-14 8,26 8,06 6,29 15-44 3,93 4,03 3,51 45+ 6,41 4,75 5,71 Taux d'attaque par âge
- Les moins de cinq ans sont trois à quatre fois plus touchés que les adultes en âge de travailler — dans chaque district
Notes du présentateur
Les moins de cinq ans sont trois à quatre fois plus touchés que les adultes en âge de travailler, dans chaque district. Ce gradient est l'épidémiologie de fond, et c'est aussi la raison pour laquelle la comparaison brute ci-dessus induit en erreur — Nord compte deux fois plus de moins de cinq ans que Sud.La létalité — En Python
with_outcome = cases[cases["outcome"].notna()] print(f"cases: {len(cases)}, with an outcome: {len(with_outcome)}") print(f"CFR: {(with_outcome['outcome'] == 'died').mean():.2%}")Notes du présentateur
La létalité, ce sont les décès parmi les cas. Deux décisions au dénominateur, et les deux ont déjà été prises dans ce cours.La létalité — En R
cases |> filter(!is.na(outcome)) |> summarise(n = n(), cfr = mean(outcome == "died"))La létalité
- 3,90 % sur 974 cas à issue enregistrée
Notes du présentateur
3,90 % sur 974 cas à issue enregistrée. Un cas était encore hospitalisé à la date d'arrêt et n'a pas d'issue. Ce n'est ni un décès ni une guérison, et le raisonnement est le même que pour les soixante et onze enfants encore en traitement PCIMA dans le cours nutrition — une issue en attente n'est pas une issue, et le dénominateur doit dire lequel il a employé. Ici cela ne déplace rien ; dans une épidémie encore en cours cela déplace beaucoup.Le seuil — En Python
TARGET = 0.01 cfr = (with_outcome["outcome"] == "died").mean() print(f"CFR {cfr:.2%} against a target of {TARGET:.0%}: " f"{'meets' if cfr < TARGET else 'does not meet'} the standard") by_district_cfr = with_outcome.groupby("district")["outcome"].apply( lambda s: (s == "died").mean() ) print((by_district_cfr * 100).round(2))Notes du présentateur
Sphere et l'OMS retiennent une létalité sous 1 % comme marque d'une réponse choléra bien conduite. Le choléra non traité tue une grande part des cas sévères ; traité rapidement par réhydratation orale il ne tue presque personne. Le seuil est donc un énoncé sur l'accès au traitement plutôt que sur l'agent pathogène.Le seuil — En R
cases |> filter(!is.na(outcome)) |> summarise(cfr = mean(outcome == "died"), n = n(), .by = district)Le seuil
District Létalité n Nord 6,30 % 381 Sud 3,11 % 193 Centre 2,00 % 400 Notes du présentateur
Tous les districts dépassent 1 %, et Nord de plus de six fois. C'est le constat de l'épidémie — non le taux d'attaque, qui est un fait sur l'exposition, mais la létalité, qui est un fait sur la réponse.L'explication, et pourquoi elle est indisponible — En Python
delay = ( pd.to_datetime(cases["admission_date"], errors="coerce") - pd.to_datetime(cases["onset_date"], errors="coerce") ).dt.days testable = cases.assign(delay=delay).dropna(subset=["delay", "outcome"]) banded = testable.assign( band=pd.cut(testable["delay"], [-1, 1, 3, 99], labels=["0-1", "2-3", "4+"]) ) print(banded.groupby("band")["outcome"].apply(lambda s: (s == "died").mean()).round(3))Notes du présentateur
L'explication classique d'une létalité élevée du choléra est le délai d'accès au traitement, et la liste linéaire a les champs pour la tester.L'explication, et pourquoi elle est indisponible — En R
cases |> filter(!is.na(onset_date), !is.na(admission_date), !is.na(outcome)) |> mutate(delay = as.integer(admission_date - onset_date), band = cut(delay, c(-1, 1, 3, 99), labels = c("0-1", "2-3", "4+"))) |> summarise(cfr = mean(outcome == "died"), n = n(), .by = band)L'explication, et pourquoi elle est indisponible
- La raison de fond — l'admission elle-même est la protection — Les cas jamais admis portent l'essentiel de la mortalité,…
- Celle qui n'en est pas — le délai de Nord est fabriqué par son registre — La leçon précédente a trouvé 80 % de ses cas…
Notes du présentateur
Parmi les cas admis, le gradient existe mais reste modeste — environ 1,9 % de zéro à trois jours et 3,3 % à quatre jours ou plus. Il est plus faible que l'écart entre districts, et il y a deux raisons à cela, l'une de fond et l'autre non. La raison de fond — l'admission elle-même est la protection. Les cas jamais admis portent l'essentiel de la mortalité, et la variable de délai n'existe que pour ceux qui ont été admis. Comparer des bandes de délai à l'intérieur des cas admis conditionne sur ce qui compte le plus. Celle qui n'en est pas — le délai de Nord est fabriqué par son registre. La leçon précédente a trouvé 80 % de ses cas avec début égal à admission. Le district ayant la plus forte létalité rapporte donc le délai le plus court, et la comparaison qui expliquerait sa mortalité est précisément celle que ses données ne peuvent pas soutenir.L'explication, et pourquoi elle est indisponible — En Python
print("Nord CFR 6.30% with a median recorded delay of 0 days.") print("The delay is a register artefact; the CFR is not.")L'explication, et pourquoi elle est indisponible — En R
# One of these two numbers is real. Say which, in the report.L'explication, et pourquoi elle est indisponible
- Consignez cela comme une limite, non comme un résultat — « La létalité est la plus élevée à Nord ; le délai entre début…
Notes du présentateur
Consignez cela comme une limite, non comme un résultat. « La létalité est la plus élevée à Nord ; le délai entre début et admission qui permettrait de tester l'explication habituelle n'y est pas fiable, car 80 % de ses cas enregistrent début et admission le même jour » est la phrase honnête, et elle engendre aussi l'action corrective — réparer le registre — qu'une explication fabriquée ne produirait pas.Le bloc de rapportage — Exemple
Cholera outbreak, weeks 1-16 Cases 975 attack rate 6.7 per 1,000 (145,000 population) Attack rate by district 7.94 / 6.47 / 5.51 per 1,000 (Nord / Centre / Sud) crude; see standardised rates before comparing Deaths 38 case fatality 3.90% Denominator 974 one case still admitted at cut-off, excluded Against Sphere target 1% not met in any district Highest CFR: Nord at 6.30% (n=381). Onset-to-admission delay is not usable in Nord (80% of cases record onset = admission), so the usual explanation cannot be tested there from this register.Notes du présentateur
Dix lignes, et ce sont les trois dernières qui en font un constat plutôt qu'un tableau.La suite
- Les taux d'attaque par district ressemblent à une comparaison et n'en sont pas encore une, car les districts ne contiennent pas les mêmes gens.
Notes du présentateur
Les taux d'attaque par district ressemblent à une comparaison et n'en sont pas encore une, car les districts ne contiennent pas les mêmes gens. L'unité suivante y remédie, après avoir d'abord réglé une question de couverture ouverte depuis le module 2.