Découper, appliquer, combiner
Bien des questions demandent un nombre par groupe : l'exposition de chaque secteur, le rendement moyen de chaque stratégie, le nombre de transactions par jour. Le schéma est toujours le même, découper-appliquer-combiner (split-apply-combine). df.groupby("sector") découpe les lignes en groupes selon les valeurs d'une colonne, une agrégation comme .sum() est appliquée à chaque groupe, et pandas combine les résultats en un seul tableau indexé par les clés de groupe. Sans cela, on écrirait une boucle avec un dictionnaire de totaux ; avec cela, toute la question tient en une ligne : df.groupby("sector")["value"].sum().
import pandas as pd
df = pd.DataFrame({
"symbol": ["AAPL", "MSFT", "GOOG", "TSLA", "F"],
"sector": ["tech", "tech", "tech", "auto", "auto"],
"value": [36850.0, 20510.0, 51300.0, 30120.0, 9000.0],
"ret": [0.012, -0.004, 0.021, 0.03, -0.01],
})
print(df.groupby("sector")["value"].sum())
# sector
# auto 39120.0
# tech 108660.0
# Name: value, dtype: float64
print(df.groupby("sector").size().to_dict()) # {'auto': 2, 'tech': 3} rows per group
print(df.groupby("sector")["ret"].mean().round(4).to_dict()) # {'auto': 0.01, 'tech': 0.0097}Plusieurs résumés à la fois avec agg
.agg() calcule plusieurs résumés en une passe et permet de nommer les colonnes du résultat : df.groupby("sector").agg(count=("value", "size"), mean_return=("ret", "mean"), max_value=("value", "max")). Chaque mot-clé est le nom de la nouvelle colonne, et le tuple dit quelle colonne lire et quelle fonction appliquer ("sum", "mean", "min", "max", "std", "size", "first"...). On peut aussi grouper par plusieurs colonnes à la fois, groupby(["sector", "day"]), ce qui donne un résultat à index à deux niveaux. Les clés de groupe sont triées par défaut, et les lignes dont la clé est manquante (NaN) sont écartées.
import pandas as pd
df = pd.DataFrame({
"symbol": ["AAPL", "MSFT", "GOOG", "TSLA", "F"],
"sector": ["tech", "tech", "tech", "auto", "auto"],
"value": [36850.0, 20510.0, 51300.0, 30120.0, 9000.0],
"ret": [0.012, -0.004, 0.021, 0.03, -0.01],
})
summary = df.groupby("sector").agg(
count=("value", "size"),
mean_return=("ret", "mean"),
max_value=("value", "max"),
)
print(summary)
# count mean_return max_value
# sector
# auto 2 0.010000 30120.0
# tech 3 0.009667 51300.0
print(df.groupby("sector")["ret"].agg(["min", "max"])) # a list of function names
# min max
# sector
# auto -0.010 0.030
# tech -0.004 0.021transform : une statistique de groupe sur chaque ligne
Une agrégation réduit chaque groupe à une ligne. transform conserve la forme d'origine : il calcule la statistique du groupe et la répète sur chaque ligne du groupe, alignée sur l'index d'origine. C'est l'outil pour « la valeur d'une ligne comparée à son groupe » : la part d'une position dans son secteur est df["value"] / df.groupby("sector")["value"].transform("sum"), et centrer au sein d'un groupe s'écrit x - x.groupby(g).transform("mean"). Pour prendre des lignes entières par groupe, comme le meilleur élément de chaque secteur, utilisez idxmax() pour obtenir l'étiquette d'index du maximum de chaque groupe, puis sélectionnez ces lignes avec .loc.
import pandas as pd
df = pd.DataFrame({
"symbol": ["AAPL", "MSFT", "GOOG", "TSLA", "F"],
"sector": ["tech", "tech", "tech", "auto", "auto"],
"value": [36850.0, 20510.0, 51300.0, 30120.0, 9000.0],
"ret": [0.012, -0.004, 0.021, 0.03, -0.01],
})
totals = df.groupby("sector")["value"].transform("sum") # one value per original row
print(totals.tolist()) # [108660.0, 108660.0, 108660.0, 39120.0, 39120.0]
print((df["value"] / totals).round(3).tolist()) # [0.339, 0.189, 0.472, 0.77, 0.23]
best = df.loc[df.groupby("sector")["ret"].idxmax()].set_index("sector")[["symbol", "ret"]]
print(best)
# symbol ret
# sector
# auto TSLA 0.030
# tech GOOG 0.021import pandas as pd
t = pd.DataFrame({
"day": ["mon", "mon", "tue", "tue"],
"sector": ["tech", "auto", "tech", "tech"],
"value": [1.0, 2.0, 3.0, 4.0],
})
# Two keys give a two-level index
print(t.groupby(["day", "sector"])["value"].sum())
# day sector
# mon auto 2.0
# tech 1.0
# tue tech 7.0
# Name: value, dtype: float64
# reset_index() turns the keys back into ordinary columns
print(t.groupby(["day", "sector"])["value"].sum().reset_index())
# day sector value
# 0 mon auto 2.0
# 1 mon tech 1.0
# 2 tue tech 7.0for name, group in df.groupby("sector"):, ce qui convient pour afficher ou tracer quelques groupes, mais pour des nombres, les agrégations intégrées et transform sont plus courtes et bien plus rapides qu'une boucle Python ou qu'un apply avec une fonction. Vérifiez l'index du résultat (clés de groupe, en général triées) et ses dtypes, et rappelez-vous que size compte les lignes tandis que count compte les valeurs non manquantes.df.groupby(clé)[col].sum()(oumean,max,size...) : une valeur par groupe, indexée par les clés..agg(nom=(colonne, fonction), ...)produit plusieurs résumés nommés d'un coup.transformgarde le nombre de lignes (parts, centrage) ;idxmax+.locprend des lignes entières par groupe.
À suivre : fusion et jointure de tableaux, pour combiner des tableaux qui partagent une clé, comme des prix et des données de référence.
Lab : ce qui est noté
- Totaliser l'exposition de chaque secteur avec
total_by_sector25 pts - Résumer chaque secteur en un seul tableau avec
summary_by_sector25 pts - Mesurer chaque position par rapport à son secteur avec
share_of_sector25 pts - Choisir le meilleur élément de chaque secteur avec
best_per_sector25 pts