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

A new laptop on a wooden desk with a blurred code editor and spreadsheet on screen, an empty laptop box beside it

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.
01/THE CHANGE

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.

02/THE TEST

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.

The same script on the same workbook, measured on 2 October 2026
Line in the scriptpandas 2.2.3pandas 3.0.6
Region column type after read_excelobjectstr
[c for c in df if df[c].dtype == object]['Region'][] (no columns)
df["Region"].fillna("Unknown", inplace=True)Blank filledNothing changed, warning only
df["Region"][df["Amount"] > 100] = "BIG"Two rows changedNothing changed, warning only
OrderDate typedatetime64[ns]datetime64[us]
date.astype("int64") // 10**917882208001788220
df.applymap(...)Works, with a warningAttributeError, script stops
Put the number 5 into RegionAcceptedTypeError, script stops
df["Region"].astype(str) on the blank cellthe 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.

03/THE DATES

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.

ONE EXCEL DATE, ONE LINE OF CODE, TWO PANDAS VERSIONSCELL IN THE .XLSX2026-09-01astype("int64") // 10**9PANDAS 2.2.3 · DATETIME64[NS] · NANOSECONDSint64: 1,788,220,800,000,000,000// 10**9: 1,788,220,800 secondsBACK TO A DATE: 2026-09-01PANDAS 3.0.6 · DATETIME64[US] · MICROSECONDSint64: 1,788,220,800,000,000// 10**9: 1,788,220 secondsBACK TO A DATE: 1970-01-21 16:43:40MEASURED ON ONE TEST WORKBOOK, 2 OCTOBER 2026. NO ERROR AND NO WARNING ON EITHER VERSION.
fig 01: the same date and the same line of code give a 2026 date on pandas 2 and a 1970 date on pandas 3
An old yellowed paper desk calendar standing next to a closed modern laptop on a light desk
On pandas 3, one common line of date code sends a 2026 date back to January 1970.

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.

04/THE COPIES

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.

05/THE TEXT

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.

06/THE RISK

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.

AN OLD SCRIPT ON PANDAS 3: WHAT YOU NOTICE AND WHAT YOU DO NOTQUIET: THE SCRIPT FINISHES, THE FILE IS WRONGdtype == object finds no text columnsfillna(..., inplace=True) changes nothingdf["col"][mask] = value changes nothingdate to int64 is 1,000 times smallerLOUD: THE SCRIPT STOPSapplymap: AttributeErrora number into a text column: TypeErrorAN ERROR IS THE GOOD CASE.YOU SEE IT AND FIX IT.SAME SCRIPT, SAME WORKBOOK: PANDAS 2.2.3 AGAINST PANDAS 3.0.6, OPENPYXL 3.1.5, 2 OCTOBER 2026.
fig 02: two changes stop the script, four let it finish with wrong or unchanged data

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.

07/THE FIX

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.

  1. 01
    Check the version first
    Run 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.
  2. 02
    Buy time if a report is due today
    pip 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.
  3. 03
    Search the script for six things
    dtype == 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.
  4. 04
    Compare with last week
    Run 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.

> Where this leaves you

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.