资讯动态

pandas读取Excel与字段转换实战:从脏数据到干净数据框

发布时间:2026/10/24 3:27:33 来源:尧图企业网站定制
做数据处理的人十有八九都遇到过这种场景对方发来一个Excel表格打开一看订单金额那一列既有“12,345.67”又有“12,345”还混着几个单元格是“N/A”日期列有“2024/1/5”也有“2024-01-05 00:00:00”还有几个单元格显示出来是“44998”这样的数字。你问原始数据是谁维护的对方也说不清楚。这种表没法直接进分析流程必须先洗一遍。pandas的pd.read_excel加一套字段转换组合拳就是解决这类问题最顺手的工具。这篇文章不讲花哨的自动化就围绕“读取Excel 字段数据转换”这条主线把我实际处理报表时踩过的坑、用顺手的参数、还有碰到过的小概率问题从头到尾梳理一遍。适合刚接触pandas的新手也适合那些已经能在小数据集上跑通、但面对真实脏数据仍然头疼的从业者。1. 为什么用pandas处理Excel几个核心场景先聊透1.1 不先看数据直接读表约等于给自己埋坑很多人第一次用pd.read_excel就是简单传一个文件名然后打印前几行觉得“一切正常”。但真实的Excel表永远不会这么老实。早期我在处理某业务部门发来的月报时遇到过这些情况同一列金额Excel里有些单元格是文本格式左上角还带着绿色小三角有些是数字格式但小数位数不一致。“产品ID”一列明明都是数字读取之后却变成了“末尾是.0”的浮点数因为Excel把ID当数值处理了。日期列有真有假看着是“2024/5/1”其实底层是Excel的日期序列号pandas读进来之后成了整数或浮点数。字段有很多历史遗留的空值后续做透视表的时候空值参与计算直接让整行数据变成错误结果。遇到这些情况如果只是盲目地读进DataFrame后续的所有统计、透视、可视化都会建立在错误地基上。合理做法是先想清楚这列数据需要变成什么类型哪些单元格属于异常值哪些空值是允许的然后再去配置pd.read_excel的参数或者读取之后用pandas的转换函数逐列处理。1.2 pandas的数据类型体系正好是Excel数据的“矫正器”Excel本身虽然也有数据格式设置但它对类型的管理非常自由同一个单元格可以输入任意内容。而pandas用DataFrame.dtypes把每一列的类型明确界定int64、float64、object、datetime64[ns]、category等。类型一旦明确后续的计算、排序、合并才不会被Excel那种“看起来是数字实际是文本”的问题反复折磨。用生活类比说Excel里一个单元格就像一个大纸箱你可以往里面塞任何东西读取的时候pandas只是帮我们把纸箱搬出来如果没有事先约定好每个纸箱里该装什么后面每次打开都会看到“本箱内混合物品”的尴尬状态。pandas的dtype就像给每个纸箱贴了标签让数据处理有规矩可循。1.3 掌握字段转换是解锁pandas分析能力的前置条件字段转换不是孤立的技巧。做数据透视需要数值列是数值类型做时间序列需要日期列是datetime64做分组统计需要分类列是category或至少是统一的object字符串做横向拼接时保证关键列类型一致也很重要。换句话说字段转换是后续一切分析操作的前置条件。这篇文章里的核心逻辑就是“读表时尽量减少脏数据读入后用一套转换补齐剩余问题”。2. 读取Excel前的准备工作与基础用法2.1 环境依赖别只装pandas就乱跑pandas读取Excel文件并不是完全靠自身实现的需要后端引擎。不同扩展名涉及不同依赖.xlsx文件默认使用openpyxl引擎建议安装openpyxl。.xls文件老版本Excel需要使用xlrd引擎但要注意xlrd 2.0以上版本只支持.xls不支持.xlsx很多人在这里翻车。.xlsm带宏的Excel同样依赖openpyxl。如果做批量读写还要确认目标机器上这些库都已安装。可以用一条命令装齐pip install pandas openpyxl xlrd在写代码之前先确认版本。我用过不同环境pandas版本在1.x和2.x之间对read_excel的默认行为差异不大但有一些参数比如dtype_backend在2.0之后更完善。2.2pd.read_excel核心参数逐个拆解pd.read_excel看起来简单实际可配置的参数很多。我常用的几个参数如下参数作用常踩的坑io文件路径或文件类对象路径带中文名时建议用原始字符串或Path对象sheet_name表名或索引默认读取第一个sheet不指定时如果文件打开的是非目标sheet读错数据header指定表头所在行默认0表格前面有说明文字时要用skiprows配合usecols指定读取哪些列可以传列号、列名字符串或函数dtype指定列的读取类型dtype{订单号: str}能防止ID变成科学计数法parse_dates指定哪些列解析为日期对混合格式的日期列不一定全部生效skiprows跳过开头若干行需要根据实际表头的行号调整na_values指定把哪些值当作空值常见如na_values[N/A, NULL, ]keep_default_na是否保留pandas默认的空值集合有时默认会把null、NaN都当空值反而误伤数据chunksize分块读取大文件可配合pd.concat做流式处理举一个典型配置import pandas as pd df pd.read_excel( 销售明细_2024.xlsx, sheet_name数据源, usecols[订单号, 地区, 产品, 数量, 金额, 日期, 备注], dtype{ 订单号: str, 地区: category, 产品: str, }, parse_dates[日期], na_values[N/A, NULL, NA, ], skiprows2, )这里skiprows2是因为这个Excel前两行是标题和单位说明。usecols限定列可以减少内存占用dtype明确指定订单号是字符串避免原始ID读成浮点数。parse_dates尝试将“日期”列解析为时间类型之后可以直接用.dt方法操作。2.3dtype参数和astype的关系与区别dtype是读取时指定astype是读取后强制转换。什么时候用哪个如果明确知道某列一定是什么类型优先在dtype里指定。比如订单号、身份证号这种“长得像数字但本质是文本”的字段读取时直接指定str最安全。如果类型不能一锤定音比如一个字段包含数字和文本混合内容读取时最好保留为object然后在转换阶段用自定义函数清洗。astype更像事后矫正但有些转换会失败。比如把包含N/A的列直接astype(float)会抛异常需要先用pd.to_numeric(errorscoerce)。我在项目中常把dtype看作“预分拣”把astype、to_numeric、to_datetime等看作“精加工”两步配合能把大部分类型问题解决掉。3. 字段数据转换的典型场景与实操方法3.1 文本型数字转数值处理千分位、货币符号、百分比Excel里的金额常常带有逗号、货币符号或单位。pandas在读取时并不能智能剥掉这些符号往往会把整列读成object。对于这种情况我的标准操作是使用pd.to_numeric配合字符串清洗。举个例子原始表格里“金额”列有这些值12,345.67 9,876 (1,234) # 括号表示负数 1,000.-处理思路分三步先统一为字符串去空格再手动去除逗号、、括号等符号最后用pd.to_numeric转换。import pandas as pd import numpy as np def clean_money(s): if pd.isna(s): return np.nan s str(s).strip() if s.startswith(() and s.endswith()): s - s[1:-1] s s.replace(,, ).replace(, ).replace($, ).replace( , ) return s df[金额_清洗] pd.to_numeric(df[金额].apply(clean_money), errorscoerce)如果列里存在百分数比如23.5%同样思路df[折扣] pd.to_numeric(df[折扣].astype(str).str.replace(%, ), errorscoerce) / 100注意这里一定要先把原列转成字符串再做替换否则当原列是浮点数时str.replace会直接报错。我一开始就是因为跳过这一步浪费了不少调试时间。3.2 日期字符串统一为日期类型pd.to_datetime是一把全能军刀Excel里的日期混乱程度可以单独写一篇文章。常见的有2024/1/5、2024-01-05、20240105、2024.1.5、Excel序列号44998、带时间后缀的2024-01-05 08:30:00等。pd.to_datetime默认能解析大多数常见格式但遇到Excel原生序列号时会失效因为序列号本质是数字。我的处理方案有两种如果列中的日期是纯数字比如44998需要先判断它到底是“日期序列号”还是“文本日期转成的数字”。Excel序列号是以1900-01-00为起点的天数。pandas没有内置函数直接转换但可以用一个固定偏移df[日期] pd.to_datetime(df[日期].astype(int), unitD, origin1899-12-30)origin1899-12-30是Excel日期序列号的标准起点。这里有个细节Excel把1900年2月29日当作有效日期它的bug所以真实世界的起点略有偏差但对绝大多数现代数据没有影响。如果列中是混合格式的字符串且格式不统一建议先强制转换为字符串再用errorscoerce让无法解析的内容变成NaTdf[日期解析] pd.to_datetime(df[日期].astype(str), errorscoerce)解析完成后再用df[df[日期解析].isna()]查看解析失败的行逐条处理。很多看起来无解的数据去掉字符串里的空格、调整分隔符后都能解析。3.3 字段拆分与合并从“姓名电话”里拆出结构化信息有一类需求是拆分字段。比如Excel里“客户信息”列存的是“张三 13800000000”需要拆成“客户姓名”和“联系电话”。如果数据是用空格或其他分隔符拼接起来的用str.split就行df[[客户姓名, 联系电话]] df[客户信息].str.split( , n1, expandTrue)n1表示最多拆成两段避免姓名里本来就带空格的情况。expandTrue把拆分结果直接赋给两列。合并字段则用str.cat比如把年份和月份合成一个日期字符串df[年月] df[年份].astype(str).str.cat(df[月份].astype(str), sep-)我常用astype(str)来避免数值被直接拼接成20241而不是2024-1。更好的做法是先把月份补零df[月份补零] df[月份].astype(str).str.zfill(2) df[年月] df[年份].astype(str) - df[月份补零]3.4 分类数据映射与编码让文字字段变成分析可用的数据业务表里经常有冗长的分类文本比如“华东大区”“华南大区”“华北大区”。为了分析便利可将其映射为简写或编码。常规做法是用map配合字典area_map { 华东大区: HD, 华南大区: HN, 华北大区: HB, } df[区域编码] df[地区].map(area_map)如果字典中缺少某个值map会返回NaN这可以帮我们发现没见过的分类。如果希望未知分类保持原值可以使用replace的default参数pandas 2.0或写函数配合apply。对于性别、状态这类只有少量取值的字段我更倾向于转成category类型df[订单状态] df[订单状态].astype(category)转成category之后排序会按照分类顺序而不是字母顺序数据透视也会更直观内存占用也能降低尤其是当原始object列重复值很高时。3.5 空值与异常值处理数据清洗绕不开的一步字段转换过程中空值处理是最容易出问题的。pd.to_numeric的errorscoerce会把非法值变为NaN但有些业务里NaN和0并不能混为一谈。比如退款金额为空可能表示没有退款而不是退款0元。所以在处理空值前务必想清楚业务含义。常用处理方案# 数值列空值填0 df[金额] df[金额].fillna(0) # 文本列空值填“未知” df[备注] df[备注].fillna(未知) # 只填充前向值比如日期序列 df[上一次维护日期] df[上一次维护日期].fillna(methodffill) # 删除空值过多的行比如只要金额为空就删除 df df.dropna(subset[金额])异常值通常表现为超出合理范围比如数量为-5或金额超过10亿。简单做法是用条件替换df.loc[df[数量] 0, 数量] np.nan df[数量] df[数量].fillna(0)也可以用between筛选出可疑数据后单独检查bad df[~df[数量].between(1, 1000)]如果数量为0或负数直接置空不合适可以先保留一份原始列备份再在清洗列上处理方便排查。4. 实战案例从读取到清洗的完整过程4.1 模拟一份“不太干净”的Excel数据为了便于说明我设计一份模拟销售明细表文件名为模拟销售表.xlsxExcel内容大致如下订单号地区产品数量金额下单日期备注10001华东大区商品A101,234.002024/1/5加急10002华南大区商品B52,345.502024-01-06缺货后补10003华东大区商品A8#N/A1/7NULL10004华北大区商品C1267844998正常看到没有真实数据就是这么“丰富”。其中#N/A和NULL表示空值日期既有2024/1/5也有44998还有1/7这种不规范的日期字符串。4.2 读取时先做一轮“预分拣”import pandas as pd import numpy as np file_path 模拟销售表.xlsx df pd.read_excel( file_path, dtype{ 订单号: str, 地区: str, 产品: str, }, na_values[#N/A, NULL, N/A, ], keep_default_naTrue, )为什么没有在这里用parse_dates因为“下单日期”列里有Excel序列号有普通日期parse_dates对纯序列号的识别不太可靠所以我更倾向于读取后再处理。读取后先初步查看print(df.dtypes) print(df.head())此时“金额”列应该是object因为包含#N/A和“”符号。“数量”列应该是int64“下单日期”也应该是object因为存在不同格式。4.3 逐字段转换按业务需求做“矫正”首先处理“金额”列用前面写好的clean_money函数并在清洗后把结果转为floatdef clean_money(s): if pd.isna(s): return np.nan s str(s).strip() if s.startswith(() and s.endswith()): s - s[1:-1] s s.replace(,, ).replace(, ).replace($, ).replace(#N/A, ) s s.replace( , ) if s : return np.nan return s df[金额_清洗] pd.to_numeric(df[金额].apply(clean_money), errorscoerce)这里我将#N/A先转成空字符串再在to_numeric中变成NaN。也可以直接在apply前用df[金额].replace(#N/A, np.nan)处理效果类似。然后处理“下单日期”def parse_excel_date(s): if pd.isna(s): return pd.NaT s str(s).strip() # 处理纯数字日期判断为Excel序列号 if s.isdigit(): return pd.to_datetime(int(s), unitD, origin1899-12-30) # 处理格式如“1/7”的日期假设年份为当前年份 if len(s.split(/)) 2: s f2024/{s} return pd.to_datetime(s, errorscoerce) df[下单日期_清洗] df[下单日期].apply(parse_excel_date)这个函数逻辑并不完美比如“1/7”到底代表1月7日还是7月1日需要结合业务判断。这里我假设Excel中默认是月/日如果是日/月格式则需调整。实际场景务必先和数据提供方确认格式。再处理“数量”列df[数量] pd.to_numeric(df[数量], errorscoerce) df[数量] df[数量].fillna(0).astype(int)如果原数据里有“10件”这种带单位的文本需要先去掉单位再转换。地区编码area_map { 华东大区: HD, 华南大区: HN, 华北大区: HB, } df[地区编码] df[地区].map(area_map)订单状态用categorydf[产品] df[产品].astype(category)4.4 转换后的校验与导出清洗完成后不建议急着做分析先做几项基础校验print(df.dtypes) print(df[金额_清洗].describe()) print(df[下单日期_清洗].head())重点检查金额_清洗中NaN的比例是否合理。日期解析后有没有出现NaT用df[df[下单日期_清洗].isna()]查看具体行。订单号是否全部保留为文本没有变成浮点数。地区编码是否有空值。如果清洗后的表里还有少量无法解析的异常数据可以选择单独输出到一张“问题数据表”留待人工确认problem df[df[金额_清洗].isna() | df[下单日期_清洗].isna()] problem.to_excel(问题数据.xlsx, indexFalse)最后通过df_clean df.dropna(subset[金额_清洗, 下单日期_清洗])得到一张可用于分析的表再导出df_clean df[[订单号, 地区编码, 产品, 数量, 金额_清洗, 下单日期_清洗]].copy() df_clean.columns [订单号, 地区编码, 产品, 数量, 金额, 下单日期] df_clean.to_excel(模拟销售表_清洗.xlsx, indexFalse)这段流程走完之后你会发现后续做透视、画图、求和都非常痛快不再被Excel的文本数字坑得死去活来。5. 常见问题与排查技巧实录5.1 日期解析失败看看是不是Excel序列号在作祟遇到pd.to_datetime解析失败时先打印该列的unique()。如果看到44998这种数字很可能就是Excel序列号。用pd.to_datetime(44998, unitD, origin1899-12-30)来验证返回2023-03-16左右如果业务日期吻合基本可以确认。还有一种方法是直接用openpyxl读取单元格的原始格式但那样太麻烦不如这个转换来得快。5.2 一列中既有数字又有文本astype总是报错astype报错的原因往往是对象列里有无法转换的字符串。正确处理顺序是先用pd.to_numeric(errorscoerce)将非法值变成NaN再要么删掉要么填充最后再考虑astype。如果业务需要保留非法内容应该拆分出“是否可解析”标记列df[金额_数值] pd.to_numeric(df[金额], errorscoerce) df[金额_解析异常] df[金额_数值].isna()5.3 读取大Excel文件内存占用高如果Excel文件单个sheet有几十万行读取时尽量用usecols只取需要的列再用dtype指定低精度类型。比如数量列如果范围不大可以指定dtypenp.int16。如果文件实在太大Excel本身就不是好的大数据载体建议转成CSV或数据库。chunksize参数虽然存在但有实测发现read_excel的chunksize在部分版本下只对sheet分块并非对单sheet流式读取使用时需要确认。5.4 列名前后有不可见空格透视和引用时折腾人Excel里有些表头是“订单号 ”带空格肉眼看不出来。读取后可以用df.columns df.columns.str.strip()装一个通用清理列名的步骤一劳永逸。5.5 中文文件名或路径问题pandas本身读中文路径没有大问题但如果你把DataFrame写入Excel时用了带中文的文件名建议用Path对象显式传参from pathlib import Path out_path Path(数据/清洗结果.xlsx) df_clean.to_excel(out_path, indexFalse)这样可以避免Windows下某些特殊字符引发的编码问题。5.6 为什么我强烈建议保留原始列备份在清洗数据时原则是“不改原列新增清洗列”。比如不要直接覆盖“金额”列而是新建“金额_清洗”。不出问题的时候可能觉得多此一举但一旦分析后期发现清洗逻辑写错了原始列就是你唯一能追溯真相的锚点。我在一个项目里因为直接覆盖原始列后来想对比清洗前后差异只能重新读文件白白多花时间。从那天起我的清洗流程里一定会有一个df_raw df.copy()的先手备份操作。6. 一些值得长期保存的实操心得最后分享几条我在反复踩坑之后沉淀下来的习惯。首先就是每清洗一个字段就马上用df[字段名].apply(type).value_counts()看类型分布。这个方法能快速识别哪些单元格是字符串、哪些是浮点数避免转换时因为个别异常单元格而中断。其次日期解析函数的健壮性比完美性重要。业务数据里会出现“2024年1月5日”“2024-01-05 00:00:00”“2024-01-05T00:00:00Z”这些格式。与其穷举所有格式写一堆正则不如先用errorscoerce把能解析的解析掉剩下的单独打印出来人工确认。通常手工规则只针对少数特例即可。再有字段转换后一定要跑一遍describe()和isna()的汇总。很多可疑问题在描述统计里能一眼看出来比如金额最大值比业务上限高出几个数量级日期最小值跑到20世纪数量出现负数等。做好这一步后续的分析工作才不会被隐藏的脏数据带偏。如果你处理的不只是一个Excel文件而是几十个结构相同的文件建议把刚才写的清洗函数统一封装成一个模块通过循环批量读取和清洗。注意每个文件的日期格式、空值标记可能略有差异封装时把这些差异提取成参数避免为了省事而忽视细节。这篇文章从Excel读取前的准备讲到字段转换的各个典型场景再到实战案例和问题排查覆盖了我日常处理Excel表格时的绝大多数操作。你在实际项目里遇到的问题可能不会和这里一模一样但处理思路是通用的先用dtype做预分拣再用to_numeric、to_datetime和自定义函数做精加工最后用校验和备份兜底。这套流程跑顺之后再面对那些“怎么都读不进模型”的Excel你心里会有底得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑