Pandas 3.0 made Copy-on-Write default. Chained assignment can fail silently. Patch pandas tonight; keep Polars for big ETL, not a stack rewrite.
If you open Jupyter after dinner and pin pandas to whatever pip last installed, January 2026 may already have changed your evening scripts. pandas 3.0.0 shipped on 21 January 2026. Copy-on-Write is now the only mode. Chained assignment no longer updates the original frame. SettingWithCopyWarning is gone, so the failure is silent. String columns infer as str, not NumPy object. Python older than 3.11 is out.
This is not a career-switch pitch and not a get-rich stack rewrite. SoloQuant is short after-work lessons for people who already have a job and want to stay competitive — closer to QuantInsti or CFI pacing, with a Le Wagon evening-class tone. You do not need to throw pandas away. You do need to know what broke, patch the one-liners that used to "work," and decide when Polars is worth a second tool.
The pandas 3.0 announcement and the v3.0.0 what's new list four highlights that matter at a kitchen table:
str instead of object).pd.col() expressions for assign and friends.The GitHub v3.0.0 release is explicit: pandas 3.0.0 supports Python 3.11 and higher. If your office laptop is still on 3.10 because IT froze it, the upgrade will not install until Python moves. Pair this with [Set up Python after work in 30 minutes] — a 3.11+ interpreter in a real .py file is the floor.
pandas recommends upgrading to 2.3 first and clearing warnings, then jumping to 3.0. That is a weekend project if you maintain a shared notebook. For a personal evening script, check pd.__version__ tonight and look at the assignment patterns below.
Before 3.0, this pattern sometimes wrote back into df and sometimes did not:
# Old pandas — unpredictable; might warn, might mutate df
df["qty"][df["region"] == "HK"] = 0
In pandas 3.0, every indexing result behaves as a copy at the user API. The assignment above updates a throwaway object. df stays unchanged. There is no SettingWithCopyWarning because there is no ambiguity left.
Do the write in one step:
# pandas 3.0 Copy-on-Write — one loc, original frame updates
df.loc[df["region"] == "HK", "qty"] = 0
Defensive .copy() calls that only existed to silence the warning can go. mode.copy_on_write no longer does anything; the option is deprecated and will be removed in pandas 4.0.
This is the silent-break class of bug. An old notebook that "worked last quarter" can still run, print a table, and export a CSV that never received the edit. After work you have maybe 45 minutes. Spend the first 10 grepping for ][ assignment and rewriting those lines with .loc.
The dedicated str dtype is the other common surprise. pd.Series(["a", "b"]).dtype is now str, not object. Code that checks dtype == object for "this is text" will miss string columns. Install PyArrow alongside pandas if you can — the new string type uses it when present, otherwise it falls back to NumPy object under the hood. The migration notes in what's new also mention missing-value sentinel changes: the new string dtype uses NaN. If your evening script compared against a different NA sentinel, that comparison can fail quietly too.
An independent 2026 comparison by danilchenko (Polars vs Pandas, last updated July 2026, re-verified August 2026) measured a 240-million-row Parquet workload. The headline is not "switch everything." It is: Polars is the better engine above about 1 GB; pandas still wins on small interactive frames, scikit-learn / matplotlib round-trips, and a lot of last-mile work. After porting two production pipelines, the author runs both: Polars for bulk transforms, pandas for the last mile. The "should I rewrite everything?" answer is still no.
That matches an after-work constraint. You are not rewriting a 4,000-line ETL package tonight. If you already filter a CSV in pandas and plot it, stay there. If a work extract is multi-gigabyte and your laptop fans spin for half an hour, a lazy Polars scan is the tool — not a new identity.
Optional five-line sketch when the file is large:
import polars as pl
out = (
pl.scan_csv("events.csv")
.filter(pl.col("region") == "HK")
.collect()
)
scan_csv is lazy. collect() runs the plan. Hand the result to pandas only when you need a plotting or ML library that still expects a DataFrame: out.to_pandas().
danilchenko's table (Polars 1.18 vs pandas 2.2 on that 240M-row run; the author states the engine-level gap still holds on later versions) is the source for the order-of-magnitude story: group-bys and joins landed roughly 10× faster, Parquet reads about 5×. pandas 3.0 closed part of the small-data gap — PyArrow-backed strings and CoW by default, plus pd.col(). It did not make pandas multi-core or lazy. Do not invent a personal benchmark; use that published one if you need a number, then measure your own file if the job actually hurts.
pandas 3.0 also added pd.col() so assign can look a little more like Polars expressions. That is useful sugar for evening code. It is not a query optimizer. If you liked Polars mostly for readable expressions and your data still fits in memory, you can stay on pandas 3.0 and use pd.col("a") + pd.col("b") without a rewrite.
pd.__version__. If you are still on 2.x, upgrade via 2.3 if you can, then 3.0..loc.object dtype.If you are still at "install a library and get a real result," start with [Your first Python program that uses a library] before you touch CoW. Pacing for employed people is in [Learn Python after work: 19 short lessons].
SoloQuant Academy is short, hands-on, after-work Python for employed people who want to stay competitive in the job they already have. It is not a career-switch bootcamp and not a get-rich trading pitch.
The live path is Intro to Python Programming: 19 published lessons. Check the course page for the current enrollment window. A data-analysis course is an outline being built; it is not a live 39-lesson product.
pandas 3.0 is a real break for old Jupyter habits. Patch the assignment style, keep pandas for last mile, and add Polars only when the table is actually large. That is an evening's work, not a stack rewrite.