#!/usr/bin/env catnip
# Polars : feature engineering où la règle métier vit en Catnip pur.
# Polars tient les colonnes, le filtre et le group-by ; Catnip tient le
# jugement par élément (pure, matchable, testable, réutilisable).
pl = import('polars')
# read — Polars tient la table
df = pl.DataFrame(dict(
station=list("nord", "sud", "nord", "est", "sud", "est"),
temp=list(-2.0, 18.5, 7.1, 24.9, 12.0, 31.4),
))
# La règle métier est en Catnip : une fonction pure, isolée, qu'on teste seule.
# Polars exprimerait la même logique avec une chaîne when/then/otherwise.
@pure
zone = (t: float): str => {
match t {
x if x < 0 => { "gel" }
x if x < 15 => { "froid" }
x if x < 25 => { "doux" }
_ => { "chaud" }
}
}
# transform — broadcast Catnip sur la colonne.
# La frontière est ici : on sort du colonnaire (to_list), on diffuse la règle
# Catnip, on réinjecte une Series. Coût mémoire assumé pour un cas pédagogique.
labels = df['temp'].to_list().[zone]
df = df.with_columns(pl.Series("zone", labels))
# filter — Polars. On passe par la méthode .ne() : l'opérateur != de Catnip
# se désucre en not(a == b), ce qui force le __bool__ d'une Expr Polars (erreur).
mild = df.filter(pl.col('zone').ne("gel"))
# agrégat — Polars (group_by station)
summary = mild.group_by("station", maintain_order=True).agg(
pl.col('temp').mean().round(2).alias("temp_moy"),
pl.len().alias("n"),
)
print("⇒ Zones par relevé")
print(df)
print()
print("⇒ Résumé par station (hors gel)")
print(summary)