查看: 229|回复: 3

Python pandas读Excel日期列变浮点数的解决方法与转换代

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
在用pandas处理Excel报表时,日期列读出来变成44562.0、44601.5这种浮点数,是不少开发都遇到过的坑。Excel单元格明明显示的是2025-03-15,read_excel之后却成了数字,这通常不是pandas的bug,而是Excel底层存储日期的方式导致的。

Excel内部用浮点数表示日期时间:整数部分是从1900年1月1日起累计的天数,小数部分是当天的时间比例。例如44562就代表从基准日往后数44562天,对应2025年3月15日。由于Excel存在著名的1900年闰年bug,实际基准日应为1899年12月30日。单元格里看到的日期只是格式化后的显示效果,底层值始终是浮点数。当pandas读取时没有拿到格式信息,就会直接暴露这些原始数字。

一、用parse_dates参数指定日期列

最直接的方法是在read_excel时告诉pandas哪些列是日期:
  1. import pandas as pd
  2. df = pd.read_excel(
  3.     'sales_report.xlsx',
  4.     parse_dates=['order_date', 'delivery_date']
  5. )
复制代码

如果列名不确定,也可以用列索引:
  1. df = pd.read_excel(
  2.     'sales_report.xlsx',
  3.     parse_dates=[2, 5]  # 第3列和第6列是日期
  4. )
复制代码

这个方法要求Excel里这些列本身是日期格式。如果用户把日期列格式改成“常规”或“文本”,parse_dates就不会生效,读出来仍然是浮点数。比如有人对日期列执行了“清除格式”,格式信息丢失后,pandas只能得到底层数字。

二、浮点数手动转日期

当parse_dates失效,或数据已经读成浮点数时,可以基于Excel日期基准手动转换:
  1. from datetime import datetime, timedelta
  2. def excel_float_to_date(f):
  3.     # Excel基准日期是1899-12-30(因为1900闰年bug)
  4.     base = datetime(1899, 12, 30)
  5.     return base + timedelta(days=f)
  6. print(excel_float_to_date(44562))      # 2025-03-15 00:00:00
  7. print(excel_float_to_date(44601.5))    # 2025-03-15 12:00:00(实际是39.5天后)
复制代码

批量转换整列时,要注意列中可能混有浮点数和正常字符串,还可能存在空单元格(NaN)。NaN本身是float类型,所以要先排除空值再做类型判断:
  1. df['order_date'] = df['order_date'].apply(
  2.     lambda x: datetime(1899, 12, 30) + timedelta(days=x)
  3.     if pd.notna(x) and isinstance(x, (int, float))
  4.     else x
  5. )
复制代码

三、更换Excel读取引擎

pandas读Excel支持xlrd和openpyxl两种引擎。xlrd 2.0之后只支持xls文件,openpyxl只支持xlsx文件。两者对日期格式的识别能力不同。

对于xlsx文件,指定openpyxl引擎往往能更好地保留日期类型:
  1. df = pd.read_excel('report.xlsx', engine='openpyxl')
复制代码

但openpyxl读大文件较慢,碰到“yyyy年mm月dd日”这类自定义日期格式时,也可能识别失败。对于xls文件,xlrd是唯一选择,但它也更容易出现日期变浮点数的问题。可以先借助xlrd检测哪些列是日期类型,再把列索引传给parse_dates:
  1. import xlrd
  2. book = xlrd.open_workbook('old_report.xls')
  3. sheet = book.sheet_by_index(0)
  4. date_cols = []
  5. for col_idx in range(sheet.ncols):
  6.     for row_idx in range(min(5, sheet.nrows)):
  7.         cell = sheet.cell(row_idx, col_idx)
  8.         if cell.ctype == xlrd.XL_CELL_DATE:
  9.             date_cols.append(col_idx)
  10.             break
  11. print('日期列索引:', date_cols)
  12. df = pd.read_excel('old_report.xls', parse_dates=date_cols)
复制代码

xlrd.XL_CELL_DATE的值是3,表示单元格类型为日期。这种方法比盲猜parse_dates列名更靠谱。

四、容易踩的边角坑

1. 同一列日期格式不一致。比如前100行是日期格式,后面有人手敲了“3月15日”文本,pandas读出的列类型会变成mixed,parse_dates直接失效。处理方式是在Excel里统一格式,或者读出来后按类型分段处理。

2. 时区问题。Excel日期没有时区概念,转换后的datetime也是naive。跨时区比较时,需要手动设置时区:
  1. df['order_date'] = pd.to_datetime(df['order_date']).dt.tz_localize('Asia/Shanghai')
复制代码

3. 时间精度丢失。像44601.5这种小数带半天时间,如果只用.dt.date取日期,时间信息就没了。需要精确到小时时,必须保留小数部分。

4. 不要直接用pd.to_datetime处理Excel浮点数。pd.to_datetime默认把浮点数当Unix时间戳(从1970年开始),与Excel基准完全不同,会差出一百多年:
  1. # 错误做法:44562被解释为从1970年起的44562秒
  2. pd.to_datetime(44562, unit='s')  # 约等于1970-01-02
  3. # 正确做法:先转成Excel基准datetime
  4. from datetime import datetime, timedelta
  5. base = datetime(1899, 12, 30)
  6. df['date'] = df['date'].apply(lambda x: base + timedelta(days=x))
复制代码

五、一套稳妥的最终方案

综合下来,可以设计一个容错性较强的处理流程:xlsx文件先用openpyxl配合parse_dates读取,检查日期列是否解析成功;失败时手动转换;xls文件先用xlrd检测日期列索引;最后统一用pd.to_datetime做一轮格式清洗,把残留的字符串日期都转掉。
  1. import pandas as pd
  2. from datetime import datetime, timedelta
  3. def excel_float_to_date(f):
  4.     base = datetime(1899, 12, 30)
  5.     return base + timedelta(days=float(f))
  6. def clean_date_column(col):
  7.     """统一处理日期列中的混合类型"""
  8.     result = []
  9.     for val in col:
  10.         if pd.isna(val):
  11.             result.append(pd.NaT)
  12.         elif isinstance(val, (int, float)):
  13.             result.append(excel_float_to_date(val))
  14.         else:
  15.             result.append(pd.to_datetime(val, errors='coerce'))
  16.     return pd.Series(result)
  17. df = pd.read_excel('sales_report.xlsx', engine='openpyxl', parse_dates=['order_date'])
  18. if df['order_date'].dtype != 'datetime64[ns]':
  19.     df['order_date'] = clean_date_column(df['order_date'])
复制代码

这套流程在实际处理几十个报表时没有再出现日期问题。多写几行代码,但能省下后续反复清洗数据的时间。

六、总结

Excel把格式和值分开存储,跨工具读取时格式信息一旦丢失,pandas就只能返回原始浮点数。这不是代码逻辑错误,而是源文件格式不可靠。遇到日期列全是浮点数,parse_dates不行就手动转换,转换时注意NaN和混合类型,基本都能解决。如果自己生成Excel,从一开始就把日期列设为明确的日期格式,避免使用“常规”或“文本”,这样后续任何人读取都不会再踩坑。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python pandas读Excel日期列变浮点数的解决方法与转换代

干货很足,感谢楼主总结。我也遇到过这个坑,尤其是别人发来的Excel里日期格式五花八门,parse_dates有时候确实不灵,最后还是手动按基准日+timedelta转换最稳。楼主提到xlrd 2.0之后不支持xlsx这点很关键,之前就吃过这个亏。另外同一列混文本和数字的情况确实头疼,我现在一般读进来先看dtype,再决定处理路线。
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python pandas读Excel日期列变浮点数的解决方法与转换代

楼主总结得很全面,尤其是Excel底层日期是浮点数这个点,很多人第一次遇到都会懵。我之前也踩过类似的坑,比如同一列里既有日期又有文本,parse_dates直接失效,后来改成在read_excel里用converters参数指定列转换函数,反而更可控。另外关于时间精度丢失,如果只想要日期,可以用.dt.floor('D')或者保留原样,关键还是搞清楚数据里到底有没有带时间部分。感谢分享,手动转基准日期那个函数我直接收藏了。
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python pandas读Excel日期列变浮点数的解决方法与转换代

感谢分享!这确实是pandas读Excel很经典的坑,尤其是“单元格显示日期,读出来变成浮点数”那段,很多人第一次遇到都会懵。你前面讲基准日期和1900闰年bug的部分很清晰,手动转换的逻辑也验证过没问题。 我也补充一点小经验:如果是在公司里用,经常有人会把日期列里混进“3月15日”这类文本,确实像你说的,parse_dates直接失效,最后还得靠分段处理。另外openpyxl读大文件慢的问题感同身受,我一般会先转成csv再读,虽然少部分格式信息会丢,但速度提升明显。 话说你最后那套“稳妥的最终方案”好像没贴完?期待更新,想看看容错流程具体是怎么设计的。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-8-21 12:36 , Processed in 0.022835 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部