pandas 3.0 was released in January 2026, and most data teams are now planning the move from 2.x. It is the biggest change to pandas in years: strings get their own data type, Copy-on-Write becomes the only behaviour, and dates default to a different precision. This guide covers what changed, the code that is most likely to break, and a safe way to upgrade.
Before you start: upgrade in two steps
The pandas team's own advice is to go through 2.3 first. Version 2.3 warns about almost everything that changes in 3.0, so you can fix your code while it still runs.
pip install "pandas==2.3.*"
python -W error::FutureWarning -m pytest # turn warnings into failures
# fix everything that fails, then:
pip install "pandas==3.0.*" pyarrowpandas 3.0 needs Python 3.11 or newer and NumPy 1.26 or newer. PyArrow is optional but strongly recommended, because it makes the new string type much faster.
1. Strings have their own dtype
Text columns used to be stored as the generic object type. They are now inferred as a dedicated str dtype.
ser = pd.Series(["London", "Milan"])
ser.dtype
# pandas 2.x: dtype('O') (object)
# pandas 3.0: strWhat breaks: code that checks df[col].dtype == object to find text columns will silently skip them. Use the type helpers instead:
# Before
text_cols = [c for c in df.columns if df[c].dtype == object]
# After: works in pandas 2.x and 3.0
text_cols = [c for c in df.columns if pd.api.types.is_string_dtype(df[c])]A str column can only hold text or missing values, so putting a number into one now raises an error instead of quietly creating a mixed column. Missing values in it are always NaN.
2. Copy-on-Write is the only behaviour
Every result of selecting or filtering now behaves like a copy. Changing it never changes the original DataFrame by accident, and the infamous SettingWithCopyWarning is gone.
What breaks: chained assignment, where you select twice and then assign, no longer changes anything.
# Silently does nothing in pandas 3.0
df["discount"][df["units"] > 10] = 0.1
# Do it in one step with .loc
df.loc[df["units"] > 10, "discount"] = 0.1The good news: you can delete all the defensive .copy() calls you added to silence warnings, and pandas avoids making real copies until something actually changes, which often saves memory.
3. Dates default to microseconds, not nanoseconds
pandas now picks the precision from the input. Parsing ordinary date strings gives microseconds; integers in seconds stay in seconds.
pd.to_datetime(["2026-10-13 09:30"]).dtype # datetime64[us]
pd.to_datetime([0], unit="s").dtype # datetime64[s]This removes the old limit where dates before 1677 or after 2262 caused errors. What breaks: code that converts dates to integers with astype("int64") now gets numbers 1,000 times smaller. Set the unit explicitly first:
epoch_ns = df["created_at"].dt.as_unit("ns").astype("int64")4. Other changes that can catch you out
- Time zones now use Python's built-in
zoneinfoinstead ofpytz. Ambiguous or non-existent times around clock changes raise an error rather than guessing. pd.offsets.Daynow means a calendar day, keeping the same clock time across daylight-saving changes.- Nullable and Arrow types: calculations that used to produce
NaN, such as dividing by zero, can now produceNA. value_counts(sort=False)keeps the original order instead of sorting by label.- Functionality deprecated in 2.x has been removed, so any warning you ignored in 2.3 becomes an error in 3.0.
The new features worth using
pd.col: expressions without lambdas
pd.col() refers to a column by name, which makes method chains much easier to read.
# Before
df = df.assign(revenue=lambda d: d["units"] * d["unit_price"])
# pandas 3.0
df = df.assign(revenue=pd.col("units") * pd.col("unit_price"))
big_orders = df.loc[pd.col("revenue") > 1000]Anti joins in one line
Finding rows in one table with no match in another, such as customers who never ordered, used to need a merge, an indicator column and a filter. Now it is a join type:
never_ordered = customers.merge(orders, on="customer_id", how="left_anti")Arrow and Iceberg
DataFrame.from_arrow()and the Arrow PyCapsule interface move data efficiently to and from Arrow-based tools such as DuckDB and Polars.pd.read_iceberg()andDataFrame.to_iceberg()read and write Apache Iceberg tables directly.to_excel()gains anautofilteroption, so exported reports open with filters already on.
A quick upgrade checklist
- Upgrade to 2.3 and fix every
FutureWarningandDeprecationWarning. - Search your code for
== object,dtype("O")and"object"used to detect text. - Search for chained assignment such as
df["a"][mask] =and rewrite it with.loc. - Search for
astype("int64")on dates and add.dt.as_unit("ns")where you need nanoseconds. - Install
pyarrow, upgrade to 3.0 in a branch, and compare a few key outputs with the old version before switching production.
New to pandas? Start with pandas for Excel users, and keep the pandas cheat sheet open while you work. To check your cleaned data before and after an upgrade, Clean & Compare can show exactly which rows and cells changed.
Source: the official pandas 3.0.0 release notes and the pandas 3.0 release announcement. Check the full "Backwards incompatible API changes" section before upgrading production code.
