Pandas 3.0 je tady — co přináší a jak přežít migraci
Tak, stalo se. Dne 21. ledna 2026 vyšla dlouho očekávaná verze pandas 3.0.0 — první velký (major) release knihovny pandas za několik let. A věřte mi, není to jen kosmetická záležitost.
Pandas 3.0 přináší zásadní změny, které ovlivní prakticky každého, kdo s touto knihovnou pracuje. Nový výchozí datový typ pro řetězce, Copy-on-Write jako jediný režim, nová syntaxe pd.col(), změna rozlišení datetime a spousta odstraněných zastaralých funkcí. Zkrátka — dost na to, aby se vám mírně zatočila hlava.
Pokud jste dosud pracovali s pandas 2.x a přemýšlíte, jestli je čas upgradovat — odpověď zní ano, ale s přípravou. Projdeme si všechny klíčové novinky, ukážeme si praktické příklady kódu s porovnáním starého a nového chování a sestavíme kompletní migrační plán krok za krokem.
Nový výchozí datový typ pro řetězce (str dtype)
Co se vlastně změnilo
Historicky pandas ukládal řetězcové sloupce jako NumPy object dtype. A upřímně — byl to docela problém. Typ object dokáže uložit jakýkoli Python objekt, nejen řetězce, a navíc nebyl příliš efektivní z hlediska výkonu ani paměti.
Od verze 3.0 pandas automaticky rozpoznává řetězcová data a přiřazuje jim dedikovaný typ str. Na pozadí tento nový typ využívá knihovnu PyArrow (pokud je nainstalovaná), což přináší výrazné zrychlení řetězcových operací. Pokud PyArrow k dispozici není, pandas se vrátí k NumPy object dtype — takže nic se nerozbije, jen přijdete o výkonnostní bonus.
# Staré chování (pandas < 3.0)
import pandas as pd
ser = pd.Series(["Praha", "Brno", "Ostrava"])
print(ser.dtype)
# Výstup: object
# Nové chování (pandas 3.0)
ser = pd.Series(["Praha", "Brno", "Ostrava"])
print(ser.dtype)
# Výstup: str
Na co si dát pozor
Tohle je důležité. Tato změna může rozbít kód, který kontroluje datový typ sloupce porovnáním s "object". Pokud máte ve svém kódu něco takového, je potřeba to přepsat:
# Starý způsob kontroly (pandas < 3.0) — PŘESTANE FUNGOVAT
if df["jmeno"].dtype == "object":
print("Je to řetězec")
# Nový správný způsob (pandas 3.0)
if pd.api.types.is_string_dtype(df["jmeno"]):
print("Je to řetězec")
Další důležitá vlastnost — nový typ str může obsahovat pouze řetězce a chybějící hodnoty. Pokud se pokusíte do řetězcového sloupce vložit číslo nebo jiný objekt, pandas vyvolá chybu. Indikátor chybějící hodnoty je NaN (np.nan), což je konzistentní s ostatními výchozími datovými typy.
Doporučení pro migraci
Pro nejlepší výkon nainstalujte PyArrow jako backend pro řetězcový typ:
pip install pyarrow
Pokud potřebujete zpětnou kompatibilitu a chcete v určitém sloupci nadále používat object dtype (třeba proto, že tam máte smíšené typy — a ano, to se v praxi stává), explicitně to specifikujte:
# Vynucení starého object dtype
df = pd.read_csv("data.csv", dtype={"sloupec_se_smisenym_obsahem": "object"})
Copy-on-Write — konec SettingWithCopyWarning
Proč tato změna
Ruku na srdce — pokud jste s pandas pracovali déle, pravděpodobně jste se s varováním SettingWithCopyWarning setkali. Asi víckrát, než byste chtěli přiznat. Toto varování se objevovalo, když jste se pokoušeli o takzvaný řetězový přístup (chained assignment) — tedy modifikaci DataFramu přes dva po sobě jdoucí kroky.
Problém byl, že chování bylo naprosto nepředvídatelné. Někdy se změna projevila na originálu, jindy ne. A to je přesně typ bugu, který vás dokáže připravit o celý páteční večer.
Pandas 3.0 tento problém řeší radikálně: Copy-on-Write (CoW) je nyní jediný a výchozí režim. Pravidlo je jednoduché — jakýkoli výsledek indexovací operace nebo metody se vždy chová jako kopie a nikdy nemodifikuje originál.
Praktické příklady — staré vs. nové chování
Řetězový přístup (chained assignment) — už nefunguje:
# Starý kód (pandas < 3.0) — nepředvídatelné chování
df = pd.DataFrame({"mesto": ["Praha", "Brno", "Ostrava"],
"populace": [1300000, 380000, 280000]})
# Toto MOHLO modifikovat df (nepředvídatelné!)
df["populace"][df["populace"] < 300000] = 0
# Nový správný způsob (pandas 3.0) — použijte .loc
df.loc[df["populace"] < 300000, "populace"] = 0
Podmnožiny se vždy chovají jako kopie:
df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
# Staré chování — subset mohl být view a modifikovat originál
subset = df["a"]
subset.iloc[0] = 999
# df["a"] se MOHLO změnit (nepředvídatelné!)
# Nové chování (pandas 3.0)
subset = df["a"]
subset.iloc[0] = 999
# df zůstává BEZE ZMĚNY — subset je vždy kopie
A zbytečné volání .copy()? Už není potřeba:
# Staré — obranné .copy() proti SettingWithCopyWarning
filtr = df[df["a"] > 1].copy()
# Nové (pandas 3.0) — .copy() není nutné
filtr = df[df["a"] > 1]
Osobně jsem měl ve svém kódu desítky zbytečných .copy() volání. Konečně je můžu smazat.
Výkonnostní přínosy CoW
Copy-on-Write neznamená, že pandas teď kopíruje všechno pořád. Právě naopak — pod kapotou pandas používá views (pohledy) kdykoli je to možné a skutečnou kopii vytvoří až ve chvíli, kdy je to nutné pro zachování sémantiky. Výsledkem je nižší spotřeba paměti a rychlejší provádění běžných operací. Žádné zbytečné defenzivní kopie.
Nová syntaxe pd.col() — výrazy místo lambda funkcí
Jak pd.col() funguje
Tohle je novinka, na kterou jsem se těšil. Pandas 3.0 přináší funkci pd.col(), která umožňuje odkazovat na sloupce DataFramu a vytvářet výrazy bez nutnosti psát lambda funkce. Všude tam, kde jste dříve museli psát lambda df: df["sloupec"], můžete teď použít pd.col("sloupec").
Výsledek? Čitelnější kód.
import pandas as pd
df = pd.DataFrame({
"produkt": ["Notebook", "Telefon", "Tablet"],
"cena": [25000, 15000, 12000],
"dph_sazba": [0.21, 0.21, 0.21]
})
# Starý způsob s lambda
df = df.assign(cena_s_dph=lambda x: x["cena"] * (1 + x["dph_sazba"]))
# Nový způsob s pd.col() — čitelnější
df = df.assign(cena_s_dph=pd.col("cena") * (1 + pd.col("dph_sazba")))
pd.col() v praxi — filtrování a řetězcové operace
Funkce pd.col() podporuje všechny standardní operátory a také metody Series včetně přístupových atributů .str a .dt. Takže můžete psát věci jako:
# Filtrování s pd.col()
df_drahe = df.loc[pd.col("cena") > 14000]
print(df_drahe)
# produkt cena dph_sazba cena_s_dph
# 0 Notebook 25000 0.21 30250.0
# 1 Telefon 15000 0.21 18150.0
# Řetězcové operace
df_produkty = pd.DataFrame({
"nazev": ["notebook lenovo", "telefon samsung", "tablet apple"]
})
df_produkty = df_produkty.assign(
nazev_velka=pd.col("nazev").str.title()
)
print(df_produkty)
# nazev nazev_velka
# 0 notebook lenovo Notebook Lenovo
# 1 telefon samsung Telefon Samsung
# 2 tablet apple Tablet Apple
Aktuálně je pd.col() podporováno v metodách DataFrame.assign(), DataFrame.loc() a v operacích getitem/setitem. V budoucích verzích se podpora rozšíří na další metody — tak uvidíme, jak rychle to půjde.
Změna výchozího rozlišení datetime
Z nanosekund na mikrosekundy
Další významná změna se týká zpracování časových údajů. Tady je třeba dávat pozor, protože to může tiše rozbít existující kód.
Dříve pandas převáděl veškerá datetime data na nanosekundové rozlišení, což způsobovalo problémy s daty mimo rozsah let 1678–2262 (a ano, někteří z nás s takovými daty pracují). Od verze 3.0 pandas inferuje vhodné rozlišení z vstupních dat — typicky mikrosekundy.
import pandas as pd
# Parsování řetězce — výchozí mikrosekundy
ts = pd.to_datetime(["2026-03-01 10:30:00"])
print(ts.dtype)
# Výstup: datetime64[us]
# Celé číslo s explicitní jednotkou — zachovává jednotku
ts_s = pd.to_datetime([0], unit="s")
print(ts_s.dtype)
# Výstup: datetime64[s]
# NumPy datetime — zachovává rozlišení
import numpy as np
ts_ms = pd.Series([np.datetime64("2026-03-01", "ms")])
print(ts_ms.dtype)
# Výstup: datetime64[ms]
# Řetězec s nanosekundovou přesností — nanosekund. rozlišení
ts_ns = pd.to_datetime(["2026-03-01 11:43:01.123456789"])
print(ts_ns.dtype)
# Výstup: datetime64[ns]
Důležité upozornění pro konverzi na int64
Pokud ve svém kódu převádíte datetime na celá čísla pomocí .astype("int64"), dejte si pozor — výsledné hodnoty budou nyní 1000× menší (mikrosekundy místo nanosekund). To je přesně ten typ změny, která projde code review bez povšimnutí a pak způsobí záhadné chyby v produkci.
Před převodem použijte metodu .dt.as_unit():
# Bezpečný převod na int64
ts = pd.Series(pd.to_datetime(["2026-03-01"]))
# Špatně — výsledek se liší oproti pandas 2.x
print(ts.astype("int64"))
# Správně — explicitní specifikace jednotky
print(ts.dt.as_unit("ns").astype("int64"))
Arrow PyCapsule Interface a anti-join v merge()
Bezeztrátová výměna dat přes Arrow
Pandas 3.0 plně podporuje Arrow C Data Interface prostřednictvím PyCapsule Protocolu. Co to znamená v praxi? Efektivní, v některých případech bezeztrátový (zero-copy), přenos dat mezi různými knihovnami — Polars, DuckDB nebo Apache Spark.
import pandas as pd
# Import dat z libovolného Arrow-kompatibilního objektu
# df = pd.DataFrame.from_arrow(arrow_objekt)
# ser = pd.Series.from_arrow(arrow_objekt)
# Export — DataFrame a Series automaticky implementují
# rozhraní __arrow_c_stream__, takže je lze přímo předat
# do jiných knihoven podporujících Arrow PyCapsule
Anti-join — nový typ spojení
Tohle potěší každého, kdo někdy potřeboval najít „co chybí". Metoda merge() nyní podporuje dva nové typy spojení: how='left_anti' a how='right_anti'. Anti-join vrací řádky z jedné tabulky, které nemají odpovídající záznam v druhé tabulce.
import pandas as pd
zakaznici = pd.DataFrame({
"id": [1, 2, 3, 4, 5],
"jmeno": ["Anna", "Boris", "Cyril", "Dana", "Emil"]
})
objednavky = pd.DataFrame({
"zakaznik_id": [1, 3, 3, 5],
"produkt": ["Notebook", "Tablet", "Telefon", "Monitor"]
})
# Zákazníci BEZ objednávek (left anti-join)
bez_objednavek = zakaznici.merge(
objednavky,
left_on="id",
right_on="zakaznik_id",
how="left_anti"
)
print(bez_objednavek)
# id jmeno
# 1 2 Boris
# 3 4 Dana
Dříve byste tohle museli řešit přes merge s outer joinem a filtrováním NaN hodnot, nebo přes ~isin(). Teď je to jeden řádek.
Další novinky, které stojí za zmínku
Metody inplace=True nyní vrací self
Metody jako fillna(), replace(), ffill(), bfill(), interpolate(), where(), mask() a clip() nyní při použití inplace=True vrací self místo None. Drobná změna, ale umožňuje řetězení i při inplace operacích.
Nové Rolling a Expanding metody
Byly přidány metody Rolling.first(), Rolling.last(), Rolling.nunique(), Rolling.pipe() a jejich protějšky v Expanding. Metoda pipe() je obzvlášť užitečná pro vytváření čistých datových pipeline — pokud jste ji v pandas dosud postrádali, teď ji máte.
Podpora Apache Iceberg
Nové funkce read_iceberg() a DataFrame.to_iceberg() umožňují přímou práci s tabulkami Apache Iceberg. Pro ty, kdo pracují s datovými jezery, tohle je velká věc.
Oprava chování pd.offsets.Day
Offset pd.offsets.Day nyní správně přidává kalendářní den místo 24 hodin. Rozdíl je patrný při přechodech letního a zimního času — a věřte mi, tohle byl zdroj nepříjemných bugů:
import pandas as pd
ts = pd.Timestamp("2026-03-29 08:00", tz="Europe/Prague")
# Nové chování — kalendářní den (správně)
print(ts + pd.offsets.Day(1))
# 2026-03-30 08:00:00+02:00
# Pokud chcete přesně 24 hodin, použijte Timedelta
print(ts + pd.Timedelta(hours=24))
# 2026-03-30 09:00:00+02:00
Přejmenované frekvenční aliasy
Starší frekvenční aliasy byly odstraněny. Pokud je stále používáte, musíte přejít na nové. Tady je přehled těch nejběžnějších:
# Staré aliasy (ODSTRANĚNY v pandas 3.0)
# "M" → "ME" (MonthEnd)
# "Q" → "QE" (QuarterEnd)
# "Y" → "YE" (YearEnd)
# "BM" → "BME" (BusinessMonthEnd)
# Příklad — vytvoření měsíčního rozsahu
import pandas as pd
# Špatně — vyvolá chybu
# pd.date_range("2026-01", periods=6, freq="M")
# Správně
pd.date_range("2026-01", periods=6, freq="ME")
Minimální požadavky
Pandas 3.0 zvyšuje minimální verze závislostí (takže než začnete upgradovat, zkontrolujte si prostředí):
- Python 3.11+ (podpora pro Python 3.9 a 3.10 byla odstraněna)
- NumPy 1.26.0+
- PyArrow 13.0.0+ (volitelné, ale doporučené pro řetězcový backend)
- SQLAlchemy 2.0.36+
- matplotlib 3.9.3+
- scipy 1.14.1+
Důležitá změna se týká také časových pásem — pandas nyní používá standardní knihovnu zoneinfo místo pytz. Pokud potřebujete zpětnou kompatibilitu s pytz, nainstalujte s: pip install pandas[timezone].
Kompletní migrační plán — krok za krokem
Krok 1: Upgrade na pandas 2.3
Než přejdete na pandas 3.0, nejdříve upgradujte na pandas 2.3 a zapněte budoucí kompatibilní režimy. Tohle je klíčový mezikrok — přeskočit ho je lákavé, ale nevyplatí se to:
pip install pandas==2.3.3
import pandas as pd
# Zapnutí budoucích režimů pro testování
pd.options.mode.copy_on_write = True # Testuje CoW chování
pd.options.future.infer_string = True # Testuje nový string dtype
Krok 2: Opravte všechna varování
Spusťte svůj kód a opravte veškerá DeprecationWarning a FutureWarning. Nejčastější problémy:
- Řetězové přiřazení — nahraďte
df["col"][mask] = valzadf.loc[mask, "col"] = val - Kontrola dtype — nahraďte
dtype == "object"zapd.api.types.is_string_dtype() - Frekvenční aliasy — nahraďte
"M"za"ME","Q"za"QE"atd. - Zbytečné .copy() — můžete bezpečně odstranit defenzivní volání
.copy()
Krok 3: Upgrade na pandas 3.0
pip install --upgrade pandas==3.0.1
Krok 4: Zkontrolujte kompatibilitu knihoven třetích stran
Některé knihovny (například Ray, starší verze scikit-learn nebo vlastní interní nástroje) mohou interně odkazovat na SettingWithCopyWarning nebo spoléhat na staré chování. Zkontrolujte, zda jsou dostupné aktualizované verze. Tohle je krok, na který se často zapomíná — a pak se divíte, proč vám pipeline padá na místě, kde jste nic nezměnili.
Krok 5: Otestujte, otestujte, otestujte
Spusťte kompletní sadu testů. Věnujte zvláštní pozornost:
- Kódu, který modifikuje DataFrames na místě
- Pipeline, které pracují s řetězcovými sloupci
- Konverzím datetime na celá čísla
- Kódu, který porovnává datové typy
Pokud nemáte testy (buďme upřímní, ne každý je má), věnujte čas ručnímu ověření alespoň kritických datových pipeline.
Často kladené otázky (FAQ)
Musím okamžitě přejít na pandas 3.0?
Ne, nemusíte. Pandas 2.3 je stále plně podporovaný a bude dostávat opravné vydání. Nicméně pandas 3.0 přináší významné výkonnostní vylepšení díky Copy-on-Write a nativní podpoře PyArrow řetězců, takže přechod se vyplatí plánovat. Doporučuji začít s testováním migrace co nejdříve — čím déle budete čekat, tím víc kódu naberete.
Proč mi nefunguje přiřazení přes df["sloupec"][podmínka] = hodnota?
Pandas 3.0 aktivoval Copy-on-Write jako výchozí režim. To znamená, že df["sloupec"] se nyní vždy chová jako kopie, a následné přiřazení do kopie neovlivní originální DataFrame. Řešení je jednoduché — použijte df.loc[podmínka, "sloupec"] = hodnota, čímž provedete modifikaci v jednom kroku přímo na originálu.
Kam zmizelo SettingWithCopyWarning?
Bylo odstraněno, protože už není potřeba. Copy-on-Write zajišťuje konzistentní a předvídatelné chování — jakýkoli výsledek indexovací operace je vždy kopie. Neexistuje tedy nejednoznačnost, před kterou by varování mělo chránit. Jeden z mála případů, kdy odstranění varování skutečně znamená vyřešení problému.
Je PyArrow povinná závislost pro pandas 3.0?
Ne, PyArrow není povinná závislost. Nicméně je silně doporučená, protože nový řetězcový typ ji využívá jako backend pro lepší výkon a nižší spotřebu paměti. Bez PyArrow se pandas vrátí k NumPy object dtype pro řetězce. Instalace je jednoduchá: pip install pyarrow.
Jak pd.col() souvisí s Polars a DuckDB?
Syntaxe pd.col() je inspirovaná podobným přístupem v knihovnách jako Polars (pl.col()) a DuckDB. Pandas tím reaguje na trend deklarativního přístupu k transformacím dat. Aktuálně je pd.col() ve fázi počáteční podpory a v budoucích verzích se rozšíří na více metod. Jestli se z toho stane plnohodnotná alternativa k lambda funkcím — to teprve uvidíme, ale směr je slibný.