Pandas GroupBy v Pythonu: Průvodce agregací, transformací a optimalizací

Naučte se efektivně používat Pandas GroupBy v Pythonu — od základní agregace přes transform a apply až po optimalizaci výkonu a novinky v Pandas 3.0.

Co je Pandas GroupBy a proč ho budete používat pořád dokola

Pokud pracujete s daty v Pythonu, dřív nebo později narazíte na groupby() v Pandas. A upřímně? Je to jeden z těch nástrojů, bez kterého si analýzu dat nedokážu představit. Celý princip vychází ze strategie split-apply-combine (rozděl-aplikuj-zkombinuj) — data rozdělíte do skupin podle jednoho nebo více sloupců, na každou skupinu aplikujete výpočet a výsledky spojíte zpět dohromady.

Zní to jednoduše, a ono to jednoduché opravdu je. Ať už počítáte průměrné tržby podle regionů, analyzujete chování zákazníků podle segmentů, nebo připravujete agregované metriky pro strojové učení — groupby() bude váš denní chleba. Tak pojďme na to — od úplných základů až po pokročilé techniky včetně novinek v Pandas 3.0.

Základy GroupBy: Jak začít

Vytvoření ukázkového datasetu

Pro všechny příklady v článku budeme pracovat s tímhle realistickým datasetem prodejních dat. Nic složitého, ale dostatečně bohaté na to, abychom si ukázali vše podstatné:

import pandas as pd
import numpy as np

# Ukázkový dataset prodejních dat
data = {
    "region": ["Praha", "Brno", "Praha", "Ostrava", "Brno", "Praha", "Ostrava", "Brno"],
    "produkt": ["Notebook", "Notebook", "Telefon", "Notebook", "Telefon", "Tablet", "Telefon", "Tablet"],
    "trzby": [45000, 38000, 12000, 41000, 11500, 8500, 12500, 9000],
    "pocet_kusu": [3, 2, 5, 3, 4, 6, 5, 7],
    "mesic": ["leden", "leden", "leden", "unor", "unor", "unor", "brezen", "brezen"]
}

df = pd.DataFrame(data)
print(df)

První GroupBy operace

Nejjednodušší použití groupby() je seskupení podle jednoho sloupce a aplikace agregační funkce. Takhle to vypadá v praxi:

# Celkové tržby podle regionu
trzby_region = df.groupby("region")["trzby"].sum()
print(trzby_region)
# region
# Brno       57500
# Ostrava    53500
# Praha      65500

Co se tu vlastně stalo? Pandas vzal DataFrame, rozdělil ho na skupiny podle sloupce region, na každou skupinu aplikoval sum() a výsledky spojil do nové Series. Jednoduché a elegantní.

GroupBy podle více sloupců

Seskupení podle více sloupců vám dá mnohem detailnější pohled na data. Stačí předat seznam sloupců:

# Tržby podle regionu a produktu
trzby_detail = df.groupby(["region", "produkt"])["trzby"].sum()
print(trzby_detail)
# region   produkt
# Brno     Notebook    38000
#          Tablet       9000
#          Telefon     11500
# Ostrava  Notebook    41000
#          Telefon     12500
# Praha    Notebook    45000
#          Tablet       8500
#          Telefon     12000

Agregace s .agg(): Plná kontrola nad výpočty

Více funkcí najednou

Tady to začíná být zajímavé. Metoda .agg() vám umožní aplikovat více agregačních funkcí naráz:

# Více statistik pro tržby podle regionu
statistiky = df.groupby("region")["trzby"].agg(["sum", "mean", "std", "min", "max"])
print(statistiky)

Různé funkce pro různé sloupce

Slovníkový zápis je šikovný — můžete aplikovat úplně odlišné funkce na různé sloupce:

# Různé agregace pro různé sloupce
vysledek = df.groupby("region").agg({
    "trzby": ["sum", "mean"],
    "pocet_kusu": ["sum", "count"]
})
print(vysledek)

Výsledkem je DataFrame s víceúrovňovým (MultiIndex) záhlavím sloupců. A tady je háček — takové záhlaví může dost komplikovat další práci s daty. Naštěstí existuje lepší způsob.

Pojmenovaná agregace — moderní přístup v Pandas 3.0

Tohle je podle mě nejlepší způsob, jak psát agregace. Pojmenovaná agregace (named aggregation) je doporučený přístup od Pandas 2.0+ a v Pandas 3.0 je považována za zlatý standard. Proč? Výstupní sloupce mají hezké, čitelné názvy a žádný otravný MultiIndex.

# Pojmenovaná agregace — čistý a přehledný výstup
vysledek = df.groupby("region").agg(
    celkove_trzby=("trzby", "sum"),
    prumerne_trzby=("trzby", "mean"),
    celkem_kusu=("pocet_kusu", "sum"),
    pocet_transakci=("pocet_kusu", "count")
)
print(vysledek)
#          celkove_trzby  prumerne_trzby  celkem_kusu  pocet_transakci
# region
# Brno            57500    19166.666667           13                3
# Ostrava         53500    26750.000000            8                2
# Praha           65500    21833.333333           14                3

Jakmile si na tento zápis zvyknete, nebudete chtít používat nic jiného. Seriózně.

Transform: Skupinové výpočty se zachováním tvaru dat

Metoda .transform() je trochu jiná bestie. Potřebujete výsledek skupinového výpočtu přiřadit zpět ke každému řádku původního DataFrame? Přesně na to je .transform(). Na rozdíl od .agg(), která data redukuje, tady dostanete Series se stejným indexem jako vstup.

Typické použití: podíl na celku skupiny

# Přidáme sloupec s celkovými tržbami regionu
df["trzby_region"] = df.groupby("region")["trzby"].transform("sum")

# Procentuální podíl každé transakce na celkových tržbách regionu
df["podil_na_regionu"] = (df["trzby"] / df["trzby_region"] * 100).round(1)
print(df[["region", "produkt", "trzby", "trzby_region", "podil_na_regionu"]])

Normalizace (z-skóre) uvnitř skupin

Jedno z nejčastějších použití .transform() je normalizace hodnot uvnitř skupin. Pokud připravujete data pro strojové učení, tohle je něco, co budete dělat běžně:

# Z-skóre tržeb uvnitř každého regionu
df["trzby_zscore"] = df.groupby("region")["trzby"].transform(
    lambda x: (x - x.mean()) / x.std()
)
print(df[["region", "produkt", "trzby", "trzby_zscore"]])

Kumulativní součty a pořadí

Další skvělé využití — kumulativní výpočty a řazení uvnitř skupin:

# Kumulativní součet tržeb uvnitř každého regionu
df["kumulativni_trzby"] = df.groupby("region")["trzby"].cumsum()

# Pořadí podle tržeb uvnitř regionu (sestupně)
df["poradi_v_regionu"] = df.groupby("region")["trzby"].rank(
    ascending=False, method="dense"
).astype(int)
print(df[["region", "produkt", "trzby", "poradi_v_regionu"]])

Apply: Flexibilní skupinové operace

Metoda .apply() je ten švýcarský nůž skupinových operací. Je nejflexibilnější, ale (a to je důležité „ale") taky nejpomalejší. Použijte ji, když potřebujete pracovat s více sloupci najednou nebo provádět výpočty, které prostě nejdou vyjádřit přes .agg() nebo .transform().

Vážený průměr

# Vážený průměr ceny za kus (váženo počtem kusů)
def vazeny_prumer(skupina):
    cena_za_kus = skupina["trzby"] / skupina["pocet_kusu"]
    vahy = skupina["pocet_kusu"]
    return (cena_za_kus * vahy).sum() / vahy.sum()

vysledek = df.groupby("region").apply(vazeny_prumer, include_groups=False)
print(vysledek)

Top N záznamů v každé skupině

Klasický use case — chcete najít top záznamy v každé skupině:

# Top 2 transakce s nejvyššími tržbami v každém regionu
top2 = df.groupby("region").apply(
    lambda x: x.nlargest(2, "trzby"), include_groups=False
)
print(top2)

Apply vs Transform vs Agg — kdy co použít

Tohle je otázka, na kterou narazí snad každý. Správná volba metody výrazně ovlivňuje jak čitelnost, tak výkon kódu:

  • .agg() — souhrnné statistiky (sum, mean, count). Redukuje data na jednu hodnotu za skupinu.
  • .transform() — výsledek skupinového výpočtu zpět na úrovni jednotlivých řádků. Zachovává tvar dat.
  • .apply() — poslední možnost pro složité operace vyžadující přístup k více sloupcům. Nejpomalejší, ale nejflexibilnější.
# Srovnání: tři způsoby, jak získat průměr za skupinu
# 1. agg — vrací zredukovaný DataFrame
prumer_agg = df.groupby("region")["trzby"].agg("mean")

# 2. transform — vrací Series se stejným indexem jako vstup
prumer_transform = df.groupby("region")["trzby"].transform("mean")

# 3. apply — flexibilní, ale pomalejší
prumer_apply = df.groupby("region")["trzby"].apply(lambda x: x.mean())

print(f"agg výsledek (3 řádky):\n{prumer_agg}\n")
print(f"transform výsledek (8 řádků):\n{prumer_transform}\n")

Filtrování skupin s .filter()

Občas nepotřebujete agregovat, ale chcete vyfiltrovat celé skupiny na základě nějaké podmínky. Na to je tu .filter(). Vrátí podmnožinu původního DataFrame — jen řádky ze skupin, které prošly podmínkou.

# Pouze regiony s celkovými tržbami nad 55 000
silne_regiony = df.groupby("region").filter(lambda x: x["trzby"].sum() > 55000)
print(silne_regiony)
# Vrátí pouze řádky pro Brno (57 500) a Praha (65 500)

Novinky GroupBy v Pandas 3.0

Pandas 3.0, vydaný v lednu 2026, přinesl několik změn, o kterých byste měli vědět. Některé z nich vám můžou rozbít existující kód, takže pozor.

observed=True je nyní výchozí

Tohle je asi nejdůležitější změna. Při seskupování podle kategorických sloupců se dříve ve výstupu objevovaly i kombinace kategorií, které v datech vůbec neexistovaly. Od Pandas 3.0 je parametr observed nastaven na True, takže uvidíte jen to, co v datech skutečně je:

# Pandas 3.0 — observed=True je výchozí
df["region"] = df["region"].astype("category")

# Zobrazí pouze regiony, které existují v datech
vysledek = df.groupby("region")["trzby"].sum()
print(vysledek)
# V Pandas 2.x by se zobrazily i prázdné kategorie

Copy-on-Write ovlivňuje výsledky GroupBy

Pandas 3.0 aktivoval Copy-on-Write jako výchozí chování. V praxi to znamená, že výsledky groupby() operací se vždy chovají jako kopie — úpravy výsledku neovlivní původní DataFrame:

# Pandas 3.0 — Copy-on-Write je výchozí
vysledek = df.groupby("region")["trzby"].sum()
# Modifikace vysledku neovlivní původní df
vysledek.iloc[0] = 0  # Bezpečné — pracujete s kopií

Nový string dtype

Pandas 3.0 taky zavádí nový nativní datový typ str místo dřívějšího object pro textové sloupce. Funkčně se nic nemění, ale pokud seskupujete podle textových sloupců, dtype výsledků může vypadat jinak než dřív. Nic dramatického, jen je dobré o tom vědět.

Optimalizace výkonu GroupBy

U malých datasetů si výkonem nemusíte lámat hlavu. Ale jakmile pracujete s miliony řádků (a věřte mi, v praxi to přijde dřív, než čekáte), groupby() se může stát úzkým hrdlem. Tady jsou osvědčené triky, jak ho zrychlit.

Používejte vestavěné funkce místo lambda

import timeit

# Pomalé — lambda funkce
%timeit df.groupby("region")["trzby"].agg(lambda x: x.sum())

# Rychlé — vestavěná funkce
%timeit df.groupby("region")["trzby"].sum()

# Vestavěné funkce jsou typicky 5-10× rychlejší

Proč takový rozdíl? Vestavěné agregační funkce jako sum(), mean() nebo count() jsou implementovány v C/Cythonu a obcházejí Python overhead. Lambda funkce musí projít Python interpretem pro každou skupinu zvlášť.

Převeďte klíče na kategorický typ

# Převod na kategorický typ před groupby
df["region"] = df["region"].astype("category")
df["produkt"] = df["produkt"].astype("category")

# GroupBy na kategorických sloupcích je rychlejší a paměťově efektivnější
vysledek = df.groupby(["region", "produkt"])["trzby"].sum()

Vypněte řazení s sort=False

Výchozí chování Pandas je řadit výsledné skupiny. Pokud vám na pořadí nezáleží, můžete si ušetřit čas:

# Výchozí: skupiny jsou seřazeny (pomalejší)
vysledek_sorted = df.groupby("region")["trzby"].sum()

# Bez řazení: rychlejší pro velké datasety
vysledek_unsorted = df.groupby("region", sort=False)["trzby"].sum()

Validujte vztahy s validate (při merge po GroupBy)

Když výsledky GroupBy spojujete zpět s původním DataFrame přes merge(), vždycky použijte parametr validate. Ušetří vám to spoustu debugování:

# Agregace + merge zpět do původního DataFrame
souhrn = df.groupby("region").agg(
    celkove_trzby=("trzby", "sum")
).reset_index()

# Bezpečný merge s validací
df_enriched = df.merge(souhrn, on="region", validate="many_to_one")
print(df_enriched[["region", "produkt", "trzby", "celkove_trzby"]])

Praktický příklad: Měsíční obchodní report

Dost teorie — pojďme dát všechno dohromady a vytvořit něco, co byste mohli skutečně použít v praxi. Tenhle příklad simuluje realistický obchodní report:

import pandas as pd
import numpy as np

# Generování realistických dat
np.random.seed(42)
n = 1000
regiony = ["Praha", "Brno", "Ostrava", "Plzeň", "Liberec"]
produkty = ["Notebook", "Telefon", "Tablet", "Monitor", "Sluchátka"]
mesice = pd.date_range("2026-01-01", periods=3, freq="MS")

df_report = pd.DataFrame({
    "datum": np.random.choice(mesice, n),
    "region": np.random.choice(regiony, n),
    "produkt": np.random.choice(produkty, n),
    "trzby": np.random.randint(1000, 50000, n),
    "pocet_kusu": np.random.randint(1, 20, n)
})

# 1. Měsíční souhrn podle regionů
mesicni_souhrn = df_report.groupby(
    [df_report["datum"].dt.month_name(), "region"]
).agg(
    celkove_trzby=("trzby", "sum"),
    prumerna_objednavka=("trzby", "mean"),
    pocet_objednavek=("trzby", "count"),
    celkem_kusu=("pocet_kusu", "sum")
).round(0)
print("=== Měsíční souhrn ===")
print(mesicni_souhrn)

# 2. Top produkt v každém regionu
top_produkt = df_report.groupby(["region", "produkt"]).agg(
    trzby=("trzby", "sum")
).reset_index()

top_produkt = top_produkt.loc[
    top_produkt.groupby("region")["trzby"].idxmax()
]
print("\n=== Top produkt podle regionu ===")
print(top_produkt)

# 3. Procentuální podíl regionu na celkových tržbách
df_report["podil_regionu"] = (
    df_report.groupby("region")["trzby"].transform("sum")
    / df_report["trzby"].sum() * 100
).round(1)

podily = df_report.groupby("region")["podil_regionu"].first()
print("\n=== Podíl regionů na celkových tržbách ===")
print(podily)

Často kladené otázky (FAQ)

Jaký je rozdíl mezi .agg(), .transform() a .apply() v Pandas GroupBy?

Ve zkratce: .agg() redukuje data na jednu hodnotu za skupinu (součet, průměr...) a vrací menší DataFrame. .transform() vrací výsledek se stejným počtem řádků jako vstup — skvělé pro přiřazení skupinových statistik zpět k řádkům. A .apply()? Ta je nejflexibilnější a umí pracovat s celou skupinou najednou, ale je taky nejpomalejší. Pravidlo palce: pokud to jde přes .agg() nebo .transform(), nepoužívejte .apply().

Jak resetovat index po GroupBy operaci?

Po groupby() se seskupovací sloupce stanou indexem. Chcete zpět normální číselný index? Použijte .reset_index(), nebo ještě lepší — nastavte rovnou as_index=False přímo v groupby: df.groupby("region", as_index=False)["trzby"].sum().

Jak GroupBy zachází s chybějícími hodnotami (NaN)?

Ve výchozím nastavení (parametr dropna=True) Pandas řádky s NaN ve seskupovacím sloupci prostě ignoruje. Pokud chcete NaN zachovat jako vlastní skupinu, nastavte dropna=False: df.groupby("region", dropna=False)["trzby"].sum(). Mimochodem, v Pandas 3.0.1 byla opravena nepříjemná regrese při kombinaci kategorických dat s NaN a observed=False.

Proč je GroupBy pomalý a jak ho zrychlit?

Nejčastější viník? Lambda funkce a .apply() místo vestavěných metod. Vestavěné funkce (sum, mean, count) jsou napsané v C a bývají 5–10× rychlejší. Další tipy: převeďte seskupovací sloupce na kategorický typ, vypněte řazení s sort=False a filtrujte data ještě před GroupBy operací.

Co se změnilo v GroupBy v Pandas 3.0?

Pandas 3.0 (leden 2026) přinesl tři hlavní změny. Parametr observed je teď výchozně True pro kategorická data, Copy-on-Write je aktivní (výsledky GroupBy se chovají jako kopie) a nový datový typ str nahrazuje object pro textové sloupce. Tip na hladký přechod: nejprve upgradujte na Pandas 2.3, ověřte, že kód nehlásí žádná varování, a teprve pak přejděte na 3.0.

O Autorovi Editorial Team

Our team of expert writers and editors.