pandas 3.0, released on 21 January 2026, changes what read_excel gives back. Text columns now come out as a new str type instead of object, dates come out in microseconds instead of nanoseconds, and Copy-on-Write means fillna with inplace=True on a column no longer changes the data. An old Excel script often keeps running and writes wrong output, so check the six patterns below before you trust it.
Last updated 2 October 2026 · by Inam Ul Haq, data analyst and automation engineer · about the author
Say you have a Python script that opens a sales workbook every Monday, cleans it and saves a report. You wrote it last year, or you copied it, or an AI chat wrote it for you, and it has worked for months. Then you set up a new laptop or update your packages, and pip installs pandas 3. The script still runs to the end with no red error, but the blanks are no longer filled and the dates in the output say 1970.
That is the worst kind of break, because nothing tells you it happened. The pandas release notes explain every change, but they are written for people who build libraries. I wanted to see what happens to an ordinary office script, so I ran the same script on the same workbook with pandas 2 and pandas 3 and wrote down every line that gave a different answer.
If your Excel script was written before 2026, check which pandas you have. If it is 3 or higher, test the script on a copy of last week’s file and compare the output with last week’s report before you send anything.
The lines that crash are the easy ones. The lines that keep running and quietly do nothing are the ones that reach your boss.
What changed in pandas 3 for Excel files?
Some background first. pandas is the Python library most scripts use to read and write spreadsheets, and read_excel is the function that opens an .xlsx file and turns a sheet into a DataFrame. A DataFrame is just a table in memory, with rows, columns and a type for each column, like number, date or text.
The pandas 3.0.0 release notes list three changes that touch almost every Excel script. Text columns now get a dedicated string type, written as str, instead of the general object type, and that str type can only hold text or blanks. Copy-on-Write is now the only mode, which means every step that picks out part of a table hands you a copy. And dates read from a file can now come out at a coarser resolution, microseconds instead of nanoseconds.
On top of that, some old functions that were warned about in pandas 2 are now gone. The one I hit straight away in my test was DataFrame.applymap, which many older cleaning scripts use to run a small function over every cell.
What happens when you run the same Excel script on pandas 2 and pandas 3?
I made a small workbook that looks like a real export. It has five columns: an OrderID typed as text with leading zeros like 00123, a Region with one blank cell and one value with spaces around it, an Amount, an OrderDate and a Notes column that is completely empty. Then I ran the same script on pandas 2.2.3 and pandas 3.0.6, both with openpyxl 3.1.5 on Python 3.12 on Windows.
Most things did not change. The numbers, the OrderID, the blank Notes column and saving back to Excel came out the same on both. These are the lines that did change, with the exact result each version gave.
| Line in the script | pandas 2.2.3 | pandas 3.0.6 |
|---|---|---|
| Region column type after read_excel | object | str |
| [c for c in df if df[c].dtype == object] | ['Region'] | [] (no columns) |
| df["Region"].fillna("Unknown", inplace=True) | Blank filled | Nothing changed, warning only |
| df["Region"][df["Amount"] > 100] = "BIG" | Two rows changed | Nothing changed, warning only |
| OrderDate type | datetime64[ns] | datetime64[us] |
| date.astype("int64") // 10**9 | 1788220800 | 1788220 |
| df.applymap(...) | Works, with a warning | AttributeError, script stops |
| Put the number 5 into Region | Accepted | TypeError, script stops |
| df["Region"].astype(str) on the blank cell | the text 'nan' | a real blank (NaN) |
Read the third and fourth rows again. On pandas 2 those lines worked and showed a warning that most people ignore. On pandas 3 they show a different warning and do nothing at all, so the script carries on with the blanks still in place.
Why do my dates turn into 1970 after pandas 3?
A computer often stores a date as one big number, the time that has passed since 1 January 1970. Old scripts use that number to sort, to compare or to send dates to a database, and a common line to get it in seconds is df["OrderDate"].astype("int64") // 10**9. That line assumes the number is counted in nanoseconds, which are billionths of a second, because pandas 2 always stored dates that way.
In my test, pandas 3 stored the same Excel dates in microseconds, which are millionths of a second. So the big number is 1,000 times smaller, and dividing by a billion gives 1,788,220 seconds instead of 1,788,220,800. When I turned that back into a date, 1 September 2026 became 21 January 1970 at 16:43:40, and pandas raised no error and no warning.
The release notes say this directly: when you convert dates to integers, avoid astype("int64"), or set the unit you want first with as_unit. On pandas 3, df["OrderDate"].dt.as_unit("s").astype("int64") gave me 1788220800, the right answer, and it says what it means. You can also skip the integers and subtract a start date, which gave the same 1788220800 on both versions.
Why does fillna with inplace=True do nothing now?
This one comes from Copy-on-Write. Think of it like a photocopy. When you write df["Region"], pandas 3 hands you a copy of the column, and anything you change on that copy stays on the copy. So df["Region"].fillna("Unknown", inplace=True) fills the blanks in a copy that is thrown away straight after, and your table is never touched.
The same goes for chained assignment, which is two square brackets in a row followed by an equals sign, like df["Region"][mask] = "BIG". On pandas 2 that changed the table, with a SettingWithCopyWarning. On pandas 3 it changes nothing and shows a ChainedAssignmentError warning. The Copy-on-Write guide explains the rule in full, and the short version is that one step should do the whole change.
Here is something I did not expect. The other order, df[mask]["Region"] = "BIG", did not work on pandas 2 either. It looked like it should, but it already changed a copy, so if your old script has that line, it was never doing anything. The fix is the same for both: df.loc[mask, "Region"] = "BIG", or for blanks, df["Region"] = df["Region"].fillna("Unknown"). Both gave the right result on pandas 2 and pandas 3.
Why do my text column checks find nothing?
A lot of cleaning scripts loop over the columns and only touch the text ones, for example to strip spaces or make everything upper case. The usual test is df[c].dtype == object. On pandas 3 that test said no for every column, because text is now str, so the cleaning step was skipped and the spaces around " South " stayed in the file.
The pandas string migration guide suggests pd.api.types.is_string_dtype(...) for code that has to run on both versions. I found a small catch with it. If you pass it the column itself and the column has a blank, pandas 2.2.3 said False. If you pass the column’s type, is_string_dtype(df[c].dtype), it said True on both versions. So pass the type.
Two more things change inside text columns. A str column refuses anything that is not text or a blank, so a line that writes the number 5 into the Region column stopped the script with a TypeError. That one is easy to see. The quieter one is astype(str), which used to turn a blank into the text "nan" and now keeps it as a real blank. If your script later searched for the word nan to find empty cells, it now finds none. This is the same kind of hidden blank that I wrote about in cleaning messy Excel data, just coming from a different place.
Which pandas 3 changes crash, and which ones stay quiet?
When I sorted the results, they fell into two groups. Two changes stop the script with an error, and four let it finish with the wrong output. The errors are actually the good case, because you see them on the first run.
For the applymap error, the fix is one word. DataFrame.map does the same job, cell by cell, and it has been there since pandas 2.1, so the new line works on both versions.
How do you fix an old Excel script for pandas 3?
You do not have to rewrite anything. Most scripts only need a handful of lines changed, and the hard part is finding them. This is the order I would go in.
- 01Check the version firstRun python -c "import pandas; print(pandas.__version__)". If it starts with 2, nothing on this page affects you yet. If it starts with 3, carry on.
- 02Buy time if a report is due todaypip install "pandas<3" puts pandas 2 back. That is a stopgap for a deadline, not a fix, because the next clean install brings pandas 3 again.
- 03Search the script for six thingsdtype == object, inplace=True, two square brackets followed by =, astype("int64") on a date, applymap, and the word nan in quotes. Change each one as shown in sections 03 to 06.
- 04Compare with last weekRun the fixed script on last week's input file and compare the row count, one total and one date with last week's report. If all three match, the script is safe.
That last step is the one that catches what a search misses, and it is the same check I run on any number a tool hands me. I wrote it up in how I check numbers before I trust them. If the script was written by an AI chat, ask it to fix the script for pandas 3 and paste this list into the chat, then still run the comparison yourself. I covered how to work with an AI on code you did not write in learning Python fast with AI.
If you need the old text behaviour back while you fix things, pd.set_option("future.infer_string", False) at the top of the script made read_excel give object columns again on pandas 3.0.6 in my test. It only covers the text change, not dates or Copy-on-Write, so use it as a bridge. And if you are still deciding whether a job belongs in Python at all, I compared the two routes in Power Query vs Python for automating reports.
pandas 3 did not break read_excel. It changed what read_excel hands back: text is str, dates can be microseconds, and every column you pick out is a copy. Scripts that relied on the old way mostly keep running, and that is why they are dangerous, because the output looks normal until someone reads a 1970 date or a blank that should have been filled. If the script feeds a report that runs every week, the setup in how I automate Excel reports is worth a look too.
Check your pandas version. If it is 3, search the script for the six patterns, fix them, and compare one week of output with the old report before you trust it again.
Sources: pandas documentation, What’s new in 3.0.0 (January 21, 2026); pandas user guide, Migration guide for the new string data type (pandas 3.0); pandas user guide, Copy-on-Write. Test results measured on 2 October 2026 with pandas 2.2.3 and 3.0.6.

