Leçon 5 sur 8
Unité · Le marché est aussi un résultat
Le calendrier pèse plus que le programme
Le maïs médian vaut 75 gourdes à la récolte de décembre et 130 au pic de juillet. Comparez deux passages d'enquête pris à des points différents de ce cycle et vous avez mesuré le calendrier — et le panel d'un district a changé de composition exactement quand cela comptait.
Une ligne de preuve indépendante
Tout ce qui figure dans les deux premières unités est ce que les ménages ont déclaré sur eux-mêmes. Une série de prix ne l’est pas : personne ne se souvient de rien, et un marché dont le prix du maïs a doublé l’a fait qu’une enquête ait été sur le terrain ou non.
import pandas as pd
prices = pd.read_csv("market-prices-2024.v1.csv")
print(f"{len(prices):,} observations")
print(f"{prices['market_id'].nunique()} markets, "
f"{prices['commodity'].nunique()} series, "
f"{prices['period'].nunique()} months")
library(dplyr)
prices |> summarise(n = n(), markets = n_distinct(market_id),
series = n_distinct(commodity), months = n_distinct(period))
2 273 observations, douze marchés, huit séries, vingt-quatre mois. Deux ans est la longueur utile minimale, car une seule année ne permet pas de distinguer un pic saisonnier d’une dégradation.
Nettoyez trois choses avant tout tracé
prices["price_htg"] = pd.to_numeric(prices["price_htg"])
dollars = prices["price_htg"] < 10
marmite = (prices["commodity"] == "maize") & (prices["market_id"] == "MK005")
thin = prices["trader_quotes"] == 1
print(f"dollar entries: {dollars.sum()}")
print(f"marmite market maize rows: {marmite.sum()}")
print(f"single-quote observations: {thin.sum()}")
prices |> summarise(
dollars = sum(price_htg < 10),
thin = sum(trader_quotes == 1)
)
Huit prix sont en dollars avec la colonne de devise indiquant toujours HTG. Ils se lisent comme des valeurs inférieures à 10 et un filtre de valeurs aberrantes les supprimerait, alors que les multiplier par environ 132 les récupère.
Un marché relève le maïs à la marmite, mesure de volume d’environ 2,7 kg, et le classe dans une colonne libellée au kilogramme. Son prix médian du maïs vaut 263 gourdes contre environ 100 partout ailleurs.
by_market = prices[prices["commodity"] == "maize"].groupby("market_id")["price_htg"].median()
print(by_market.round(1).sort_values())
prices |> filter(commodity == "maize") |>
summarise(median = median(price_htg), .by = market_id) |> arrange(median)
L’erreur d’unité est invisible dans un marché et évidente sur douze. C’est la règle générale du décalage d’unité : il n’a jamais l’air faux sur sa propre ligne, et il a toujours l’air faux à côté de ses pairs.
Treize observations reposent sur une seule cotation. Ce ne sont pas des erreurs. Une médiane d’une valeur est une cotation et non un prix, et la décision est de les exclure ou de leur donner moins de poids — l’un ou l’autre est défendable, le silence ne l’est pas.
Le cycle saisonnier
clean = prices[~dollars & ~marmite]
maize = clean[clean["commodity"] == "maize"]
series = maize.groupby("period")["price_htg"].median()
print(series.round(0))
prices |>
filter(commodity == "maize", price_htg >= 10, market_id != "MK005") |>
summarise(median = median(price_htg), .by = period) |> arrange(period)
| Mois | 2023 | 2024 |
|---|---|---|
| Janvier | 66 | 70 |
| Avril | 97 | 110 |
| Juillet (pic de soudure) | 130 | 159 |
| Octobre | 92 | 104 |
| Décembre (récolte) | 75 | 81 |
Le maïs coûte 74 % de plus au pic de juillet qu’à la récolte de décembre en 2023, et 96 % de plus en 2024. C’est l’ampleur de l’effet saisonnier, et elle dépasse presque tout effet de programme qu’on vous demandera de détecter.
La conséquence pour un plan d’enquête est directe. Une situation de référence en décembre et une évaluation finale en juillet montreraient une dégradation catastrophique entièrement produite par le calendrier. Comparez ce qui est comparable : juillet contre juillet.
peaks = series.loc[["2023-07", "2024-07"]]
print(f"lean peak 2023: {peaks['2023-07']:.1f}")
print(f"lean peak 2024: {peaks['2024-07']:.1f}")
print(f"year on year: {peaks['2024-07'] / peaks['2023-07'] - 1:+.1%}")
# Same point in the season, one year apart. Everything else is the calendar.
130,1 contre 159,3 — une hausse de 22 % au même point de la saison. Voilà la dégradation, et elle vaut le quart de l’oscillation saisonnière qu’on aurait pu prendre pour elle.
Le panel qui a changé de composition
coverage = maize.pivot_table(index="period", columns="market_id",
values="price_htg", aggfunc="size")
print(coverage.isna().sum(axis=1)[lambda s: s > 0])
prices |> filter(commodity == "maize") |> count(period, market_id) |>
count(period) |> filter(n < 11)
MK001 cesse de rapporter de juin à septembre 2024, quand sa route est coupée. Les lignes sont absentes plutôt que nulles, si bien que rien dans le fichier ne l’annonce.
C’est aussi le marché le plus cher de Nord-Ouest, et Nord-Ouest ne compte que trois marchés — en perdre un déplace donc la moyenne du district d’un tiers de son écart et non d’un douzième.
nord = maize[maize["district"] == "Nord-Ouest"]
reported = nord.groupby("period")["price_htg"].mean()
balanced = nord[nord["market_id"] != "MK001"].groupby("period")["price_htg"].mean()
print(pd.DataFrame({"reported": reported, "balanced": balanced}).round(1)
.loc["2024-04":"2024-10"])
prices |>
filter(commodity == "maize", district == "Nord-Ouest") |>
summarise(reported = mean(price_htg), .by = period)
| Mois | Ceux qui ont rapporté | Les deux ayant rapporté chaque mois |
|---|---|---|
| Mai 2024 | 147,0 | 123,2 |
| Juin 2024 | 172,1 | 172,1 |
| Mai à juin | +17 % | +40 % |
La série rapportée sous-estime de plus de moitié la hausse de soudure, et chacun de ses nombres est une moyenne correcte des marchés qui ont rapporté. La composition a changé sous la série et la série ne l’a pas dit.
Employez un panel équilibré — seulement les marchés présents à chacun des mois
comparés — ou chaînez des variations d’un mois à l’autre calculées à l’intérieur de
chaque marché. Les deux demandent plus de travail qu’un groupby et les deux font
la différence entre une hausse de 17 % et une de 40 %.
Rapportez la série avec son panel
Maize price, gourdes per kg, median across markets
2023 harvest (Dec) 75 2024 harvest (Dec) 81
2023 lean peak (Jul) 130 2024 lean peak (Jul) 159
Seasonal swing +74% Seasonal swing +96%
Year on year at the lean peak: +22%
Balanced panel of 10 markets throughout. MK001 (Nord-Ouest) did not
report June to September 2024 and is excluded from all periods; MK005
records maize by the marmite and is excluded from maize.
8 dollar-denominated entries converted at 132 HTG; 13 single-quote
observations retained and flagged.
Le panel énoncé, les exclusions comptées, et la comparaison d’une année sur l’autre faite au même point de la saison. Un tableau de prix sans son panel est le même défaut qu’un chiffre de couverture sans son taux de rapportage, et ce cours vient de le rencontrer pour la troisième fois.
La suite
Un prix dit ce que la nourriture coûtait. Il ne dit pas si quelqu’un pouvait se la payer, et la leçon suivante porte sur le rapport qui le dit — où une chèvre dont le prix a baissé de 18 % a perdu 62 % de son pouvoir d’achat.