为什么pandas读Excel日期列全是浮点数字?

📅 2026/6/26 1:31:12
为什么pandas读Excel日期列全是浮点数字?
上个月接了个活要把客户发来的几十个Excel报表汇总到一个数据库里。数据量不大逻辑也简单我心想pandas几行代码就搞定。结果read_excel一跑出来日期列全是44562.0、44601.5这种数字。Excel里明明显示的是2025-03-15怎么到pandas嘴里就变味了这个问题我前前后后折腾了两天试了好几种方法最后才理清楚到底怎么回事。写下来给同样被这坑绊过的朋友参考。为什么日期变成了浮点数这不是pandas的bug是Excel自己存日期的方式。Excel内部用浮点数表示日期和时间。整数部分是从1900年1月1日起的天数有个著名的1900年闰年bug2月29日其实是虚拟的小数部分是当天的时间比例。所以44562就代表从1900年1月1日往后数44562天对应2025年3月15日。你在Excel里看到的2025-03-15只是单元格格式化后的显示效果底层存的还是那个浮点数。pandas默认读Excel的时候如果单元格格式信息没有被正确传递它就只拿到了底层的浮点数。尤其是用xlrd引擎读xls文件或者Excel里日期列的格式被手动改过这种情况特别容易出现。方法一read_excel的parse_dates参数最直接的办法是在read_excel的时候告诉pandas哪些列是日期importpandasaspd dfpd.read_excel(sales_report.xlsx,parse_dates[order_date,delivery_date])如果列名不确定也可以用列索引dfpd.read_excel(sales_report.xlsx,parse_dates[2,5]# 第3列和第6列是日期)这个方法大部分时候管用。但有个前提Excel文件里这些列的格式必须是日期类型。如果有人把格式改成了常规或者文本parse_dates就不起作用了读出来照样是浮点数。我碰到的第一个坑就是这个客户的报表里有人在日期列上点了清除格式结果日期格式信息全丢了。方法二浮点数手动转日期当parse_dates不生效或者数据已经读出来了可以手动把浮点数转成日期。Excel的浮点数日期有个基准从1899年12月30日开始算那个1900闰年bug导致偏移了一天。转换公式fromdatetimeimportdatetime,timedeltadefexcel_float_to_date(f):# Excel基准日期是1899-12-30因为1900闰年bugbasedatetime(1899,12,30)returnbasetimedelta(daysf)# 测试print(excel_float_to_date(44562))# 2025-03-15 00:00:00print(excel_float_to_date(44601.5))# 2025-03-15 12:00:00 39.5天批量转换整列df[order_date]df[order_date].apply(lambdax:datetime(1899,12,30)timedelta(daysx)ifpd.notna(x)andisinstance(x,(int,float))elsex)这里有两个容易忽略的点第一列里可能混着浮点数和正常字符串。有的人在Excel里既敲了2025-03-15这样的文本又敲了格式化的日期读出来就一半是float一半是str。apply里要加类型判断别把字符串也当浮点数处理了。第二空单元格读出来是NaNNaN是float类型isinstance(x, float)会匹配到它。所以必须先判断pd.notna(x)。方法三换引擎读Excelpandas读Excel支持两种引擎xlrd只支持xls和openpyxl支持xlsx。不同引擎对日期格式的识别能力不一样。openpyxl引擎在读取xlsx时会尝试根据单元格的格式信息来判断类型。如果你的文件是xlsx格式指定openpyxl引擎往往能更好地保留日期类型dfpd.read_excel(report.xlsx,engineopenpyxl)但openpyxl也有问题它读大文件比较慢而且如果单元格格式是自定义比如yyyy年mm月dd日这种中文格式有时候也识别不出来。xlrd引擎在老版本里读xlsx是默认行为但新版xlrd2.0以后只支持xls了。所以如果你用xlrd读xlsx会直接报错得用openpyxl。对于xls文件xlrd是唯一选择而且它确实更容易出现日期变浮点数的问题。一个解决办法是用xlrd单独打开文件先检测哪些列是日期类型importxlrd bookxlrd.open_workbook(old_report.xls)sheetbook.sheet_by_index(0)# 找出日期列date_cols[]forcol_idxinrange(sheet.ncols):# 检查前几行的单元格类型forrow_idxinrange(min(5,sheet.nrows)):cellsheet.cell(row_idx,col_idx)ifcell.ctypexlrd.XL_CELL_DATE:date_cols.append(col_idx)breakprint(f日期列索引:{date_cols})# 然后用pandas读取指定这些列为日期dfpd.read_excel(old_report.xls,parse_datesdate_cols)xlrd.XL_CELL_DATE的值是3代表单元格类型是日期。这个检测方法虽然多写几行但比瞎猜parse_dates靠谱得多。几个我踩过的边角坑同一列里日期格式不一致。比如前100行是日期格式后面有人手敲了3月15日这种文本。pandas读出来列类型就变成mixedparse_dates直接失效。解决办法是先在Excel里统一格式或者读出来后分两段处理。时区问题。Excel日期没有时区概念转成pandas的datetime后也是naive的。如果你要做跨时区对比记得手动加时区df[order_date]pd.to_datetime(df[order_date]).dt.tz_localize(Asia/Shanghai)时间精度丢失。44601.5这种小数代表某个时刻但如果你只取日期部分用.dt.date时间信息就丢了。如果业务需要精确到小时转的时候注意保留小数部分。pandas的to_datetime也能处理浮点数。如果你不想写手动转换函数其实pd.to_datetime对某些浮点数也能处理但它默认把浮点数当Unix时间戳来解析从1970年开始算跟Excel的基准1899年完全不同。所以千万别直接对Excel浮点数用to_datetime出来的日期会差一百多年。# 错误示范这会把44562解释成1970年以后的44562秒pd.to_datetime(44562,units)# 1970-01-01 44562秒 ≈ 1970-01-02# 正确做法先转成Excel基准的datetime再让pandas识别df[date]df[date].apply(excel_float_to_date)我的最终方案折腾一圈后我最后稳定用的流程先用openpyxl引擎读取xlsx文件配合parse_dates参数读完后检查日期列的类型如果还是float就用excel_float_to_date手动转换对于xls文件先用xlrd检测日期列索引再传给parse_dates最后统一用pd.to_datetime做一轮格式校验确保没有遗漏的str类型日期importpandasaspdfromdatetimeimportdatetime,timedeltadefexcel_float_to_date(f):basedatetime(1899,12,30)returnbasetimedelta(daysfloat(f))defclean_date_column(col):统一处理日期列中的混合类型result[]forvalincol:ifpd.isna(val):result.append(pd.NaT)elifisinstance(val,(int,float)):result.append(excel_float_to_date(val))else:result.append(pd.to_datetime(val,errorscoerce))returnpd.Series(result)# 主流程dfpd.read_excel(sales_report.xlsx,engineopenpyxl,parse_dates[order_date])# 检查如果日期列没被正确解析ifdf[order_date].dtype!datetime64[ns]:df[order_date]clean_date_column(df[order_date])这套流程跑下来几十个报表没再出日期问题。虽然多写了些代码但省了后续反复修数据的时间。碰到日期变浮点数怎么办说到底Excel把格式和值分开存内部没问题跨工具读就麻烦了。pandas只能尽力去识别格式识别不了就给你原始值。下次碰到日期列全是浮点数大概率不是代码写错了是Excel格式信息丢了。parse_dates不行就手动转手动转注意NaN和混合类型基本都能搞定。如果你自己生成Excel一开始就把日期列设成明确的日期格式别用常规或文本。这样后面不管谁读都不会再踩这个坑。