ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

pandas数据清洗与类型转换实战:从read_csv到高效分析

pandas数据清洗与类型转换实战:从read_csv到高效分析 如果你把pandas系列的前两篇都翻过应该对Series和DataFrame怎么创建、怎么取数、怎么做基础筛选已经不陌生了。这篇“基础三”想换个角度专门聊一聊真正的业务数据到手之后最容易卡住新手的几个地方文件读完怎么全是NaN、明明是一列数字却没办法直接sum、一个几万行的DataFrame到手到底应该先看哪里。顺便把评论区里隔三差五就会出现的问题——pandas装不上、PyCharm里说找不到模块、手机Python能不能跑pandas——一起拆一拆。这一篇的定位是“承上启下”前两篇教你怎么把数据装进DataFrame这一篇教你怎么把数据稳稳地读进来、转成能用的类型然后快速摸底。读、转、看这三板斧练熟了后面学清洗、聚合、透视才会顺手。1. 读写文本文件先把数据稳定弄进来再谈分析1.1 read_csv 不是只写个文件名就完事关键参数逐个说清楚很多新手学pandas是从pd.read_csv(数据.csv)这一行开始的但真实文件很少这么听话。我第一次处理销售明细的时候一个CSV里既有中文列名又有千分位数字一读出来直接给了我一屏幕NaN当时整个人是懵的。后来才明白read_csv的参数表看起来很长但真正天天用的就那几个。import pandas as pd df pd.read_csv( sales_2024.csv, sep,, # 分隔符默认逗号遇到分号/制表符要改 encodingutf-8, # 编码后面单独说 header0, # 表头在第0行没有表头就设 None dtype{订单号: str, 金额: float}, # 指定列类型 parse_dates[下单时间] # 让这一列直接变成 datetime64 )先说dtype这个参数。日常数据处理中订单号、身份证号这种列看起来是数字但实际上没有任何数学意义而且如果位数超过15位读进来会被当成浮点数精度直接丢失。所以只要遇到“编码类”的列建议顺手写成dtype{订单号: str}省得后面“1.230000e18”这种经典翻车场面出现。再说parse_dates。把时间列传给它是非常划算的因为pandas会把这一列解析成datetime64[ns]类型。一旦变成这个类型后面按年、按月、按季度聚合画时间序列折线图都非常顺。如果一开始读进来的是object字符串等你做聚合的时候才想起来转又会多一顿操作。headerNone也值得多说一句。很多机器导出的数据文件是纯数据没有表头。你直接读第一行就会被误当成列名。这种情况的推荐做法是df pd.read_csv(data.txt, headerNone, names[col1, col2, col3])自己把列名写清楚数据就不会错位。1.2 乱码和NaN新手最容易撞上的两堵墙读文本文件乱码和NaN是两座大山而且它们俩经常手拉手出现。乱码的问题本质是编码不匹配。文件里写的是GBK编码的汉字你用utf-8去解码自然就是一堆“锟斤拷”。我的通用排查口诀是先试utf-8报错就换gbk还不行就用gb18030。gb18030是GB系列里字库最全的编码Windows下的老旧Excel文件基本都能覆盖。df pd.read_csv(老文件.csv, encodinggbk)还有一种不那么明显的情况文件明明能读进来但第一列列名变成了\ufeff订单号。这个\ufeff是BOM字节顺序标记很多Windows下的编辑器都会在文件开头塞一个。解决办法不是手动去删列名而是把编码写成utf-8-sigpandas会自动把BOM吃掉df pd.read_csv(带BOM的文件.csv, encodingutf-8-sig)这个细节我建议直接记在脑子里因为你从Excel另存出来的CSV、用记事本改过的CSV都有概率带BOM。我也是吃了一次亏才记住的。再说NaN。读进来全是NaN十有八九不是文件真的空而是分隔符搞错了。比如一个文件看起来是CSV实际上里面每一行是用分号;隔开的你用默认逗号去读整行会变成一列其他列全是NaN。先打开文件看一眼分隔符再动手写代码这个习惯比任何技巧都值钱。还有一种很经典的情况就是数字列带千分位逗号1,234.56。如果文件分隔符也是逗号这列就会被劈开。读出来的结果不是列数错乱就是产生一堆NaN。这种数据如果只是少量可以直接清洗如果整个文件都这样我通常会先让业务方重新导出或者自己写一小段脚本先做预处理不要硬在read_csv里死磕。1.3 导出 .csv 时的三个保存细节数据处理好之后导出同样有三个坑值得提前避一避。这三个坑我全踩过而且每一个都消耗过一下午。第一个是index。df.to_csv(结果.csv)这行代码看着没问题但打开文件后第一列会多出Unnamed: 0这一列里面是原来的行号。原因就是DataFrame的索引被默认写进去了。解决方法是加indexFalse。第二个是编码。如果你导出之后要发给别人用Excel打开尤其是Windows环境建议用utf-8-sig而不是utf-8。utf-8导出的文件在Excel里打开经常是乱码utf-8-sig则能正常显示。这个经验处理工作时很有用。df.to_csv(结果.csv, indexFalse, encodingutf-8-sig)第三个是浮点数的位数。如果不指定pandas会把float64的完整精度写出去文件里可能出现一长串0.30000000000000004这种数字。用float_format%.2f能把小数位锁住。df_summary.to_csv(汇总.csv, indexFalse, float_format%.2f)书写起来不比前面的第8章多但能把文件体积和可读性都优化不少。2. 数据类型转换astype 用得好清洗工作少一半2.1 先用 dtypes 给数据做一次“体检”我拿到任何一个DataFrame第一件事永远是看df.dtypes。这一行代码比任何文档都诚实哪些列是数值哪些列是字符串哪些是时间一眼就看完了。print(df.dtypes)pandas里的常见类型有int64、float64、object、bool、datetime64[ns]、category。其中object是最值得警惕的类型它本质上是一个“装什么都行的盒子”字符串、混合类型、甚至乱七八糟的Python对象都可能被塞进object列。你看到的“数字”如果显示成object基本可以断定它被读成字符串了。为什么说这个很关键因为一堆字符串你没法直接求和、算平均、画图。而groupby聚合时如果分组后需要把数字列相加这列却是objectpandas要么报警告要么直接甩给你一个字符串拼接结果“100200300”看着就让人血压升高。所以拿到数据先看dtypes就像体检先量血压属于必做动作。2.2 astype 的边界什么时候转得动什么时候一定会报错astype是pandas里最直观的类型转换方式用法也很简单。df[价格] df[价格].astype(float) df[订单号] df[订单号].astype(str) df[年龄分组] df[年龄分组].astype(category)但astype有一个非常耿直的脾气转不了就报错而且不会告诉你哪一行出了问题。比如一列里有90%是数字剩下10%是“未报价”这种文本你直接astype(float)只会得到一个ValueError然后满屏红字。面对这种带脏数据的情况正确姿势是先清洗再转换。“3,000元”这种字符串要先把逗号和“元”字去掉再转数值df[价格] ( df[价格] .astype(str) .str.replace(,, ) .str.replace(元, ) .str.strip() ) df[价格] pd.to_numeric(df[价格], errorscoerce)这一步就引出了to_numeric它比astype宽容得多是处理脏数据的利器下面单独说。这里再补充一个astype的冷门但实用的小点pandas里有一个可空整数类型叫Int64注意大写I。普通int64列一遇到NaN就会自动变成float64而Int64允许整数列带着NaN存在。如果你的数据里既有整数又有缺失值又不想让小数出现转成Int64是个不错的主意。2.3 脏数据克星to_numeric 和 to_datetime 的正确打开方式pd.to_numeric最核心的参数是errors它有三个取值raise、coerce、ignore。默认是raise也就是遇到非数字直接报错coerce会把所有转不了的变成NaNignore则干脆把那列原样返回。实战中我最常用的是errorscoerce因为它能让我先看清到底有多少脏数据df[金额] pd.to_numeric(df[金额], errorscoerce) print(df[金额].isna().sum())这一下就能统计出多少行金额是非数值。注意coerce不会告诉你哪些行被变成NaN了所以转完一定要自己检查一下缺失数量的变化免得把脏数据悄悄带进下一环节。这是我在实训平台的作业里踩过的坑——当时想当然以为转完就干净了结果后面聚合出来的合计数字明显偏小回头排查才发现是coerce把几行异常值变成了NaN。pd.to_datetime的用法同理但更强大。它能把“2024/5/6”“20240506”“2024-05-06 15:30:00”这种乱七八糟的格式一锅端地解析成标准的datetime64[ns]。唯一的问题是如果数据量特别大让pandas自己猜格式会慢。这时可以直接告诉它格式df[日期] pd.to_datetime(df[日期], format%Y%m%d)%Y%m%d对应“20240506”%Y-%m-%d对应“2024-05-06”%Y/%m/%d对应“2024/5/6”。指定格式之后解析速度能快好几倍而且还能避免pandas把某些暧昧日期比如“20240506”到底是5月6日还是2024年5月6日解析成完全不同的结果。类型转换这块核心思路就一句话object只是表象你要搞清楚它背后到底是什么再决定用astype硬转还是用to_numeric兜底清洗。3. 数据初体验拿到 DataFrame 后别急着写代码3.1 五个函数快速认识你的数据“数据初体验”这个词我在很多在线实训平台就是大家常说的头歌那类作业里见过其实它讲的就是拿到数据后的快速摸底。新手最容易犯的毛病是拿到DataFrame就急着跑分析结果分析到一半发现数据量、缺失情况、字段含义都没搞清楚只能返工。我自己的固定流程是一套“五连击”df.head() # 看前几行感觉一下长什么样 df.shape # 看行列数心里有数 df.info() # 看类型缺失情况一举两得 df.describe() # 看数值列分布 df[关键列].value_counts() # 看分类字段的构成info()是我最推荐新手先跑的命令。它会把每一列的非空数量、数据类型一次性列出来相当于告诉你“这张表有哪些列每列空没空”信息量很大但输出很紧凑。describe()默认只关心数值列它会给出平均值、标准差、四分位数这些统计量。看min和max能帮你快速发现离群值——比如金额列出现负数、年龄列出现200岁这种明显不合理的值一眼就能逮住。如果想把所有列都看一遍可以加includeall。value_counts()则是分类变量的神器。比如一个订单表里有地区这一列跑一下df[地区].value_counts(normalizeTrue)直接看到各地区的占比不用写任何复杂的groupby就能先掌握全局。这也是我觉得pandas特别好上手的原因——很多“初体验”类的需求根本不需要高深语法几个基础函数就能搞定大半。3.2 缺失值和重复值先看清楚再动手删缺失值处理有一句我最想强调的话先看清楚再动手删。很多教程会让新手直接df.dropna()但真实业务数据里缺失值往往是有含义的。比如“未报价”可能是这个客户还没确定预算“售后备注”为空可能是这笔订单没有售后记录。你一行dropna()下去可能把一大批有效数据也带走了。我建议的顺序是# 先看每列有多少缺失 df.isna().sum() # 再看缺失比例 df.isna().mean().sort_values(ascendingFalse) # 再决定怎么处理缺失比例很低的列比如1%以内直接dropna()或按前后值填充都无所谓。缺失比例很高的列比如60%以上就得衡量一下这个字段还有没有保留价值。如果它是“收款日期”而大部分订单没回款这往往不是数据缺失而是业务状态问题——这时候填0、填“未回款”才符合真实情况删行反而是错的。fillna()是比dropna()温和得多的选择。数值列可以用中位数填充因为中位数比均值更抗离群值影响时间序列数据常见的方法是前向填充methodffill也就是用上一行的值补下一行。但要注意前向填充在数据按时间排序之后才有意义顺序乱的表直接ffill会拿错误的“上一行”来填。我见过不止一次这种低级错误补出来的数据比原来更不可用。重复值处理相对简单但也有个细节值得说。df.duplicated().sum()能统计重复行数量df.drop_duplicates()能去重。有唯一业务标识的比如订单号一定要用subset参数指定判断列df.drop_duplicates(subset[订单号], keepfirst)keep参数很好用first保留第一条last保留最后一条False则全部删除。同一张订单如果被下游系统重复同步了好几遍这个参数就是你决定“以哪份为准”的开关。3.3 顺手画几张图pandas 与 matplotlib 的组合拳pandas和matplotlib的组合是我个人觉得Python数据分析里“性价比”最高的技能——不是因为它多高深而是因为代码量极少却能立刻看到数据形状。import matplotlib.pyplot as plt # 直方图看数值分布 df[金额].hist(bins50) plt.show() # 箱线图看离群值 df.boxplot(column金额) plt.show() # 柱状图看分类统计 df[地区].value_counts().plot(kindbar) plt.show()原理上Series.plot()和DataFrame.plot()默认使用的后端就是matplotlib所以不需要先创建figure对象再plot直接一行画图、一行show就能出结果。很多人在实训平台上做“pandas与matplotlib练习”时卡住其实卡的不是绘图函数本身而是忘了调plt.show()或者没注意绘图结果要在IDE的绘图窗口里看。画中文标签变成方框也是高频问题。这是因为matplotlib的默认字体不支持中文需要手动切换字体plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] FalseSimHei是黑体Windows下一般可用macOS可以换成Arial Unicode MS或者PingFang SC。第二行unicode_minus是防止坐标轴上的负号变成方框建议每次都一起设置。如果你想一口气对比好几列数据的分布df[[金额, 数量, 折扣]].hist(figsize(10, 8))就能自动生成一个子图矩阵非常方便。我经常用这个方式对一份陌生数据做快速“画像”一两分钟就能判断哪些列适合做数值分析、哪些列需要清洗、哪些列基本是废的。这个习惯一旦养成后面所有分析的效率都会上一个台阶。4. 数据结构再往前一步MultiIndex 与数据拼接4.1 不常见的 MultiIndex分组结果自带的结构pandas基础系列写到第三篇如果不提MultiIndex总觉得少了一块。它听起来像个高级概念但其实你在做groupby的时候早就见过它了。df.groupby([地区, 月份])[金额].sum()运行这行代码得到的结果索引就是两层结构第一层是地区第二层是月份。这个“多级索引”就是MultiIndex。用type(grouped.index)看一眼你可能会看到pandas.core.indexes.multi.MultiIndex。很多新手在这里会愣住因为取数的方式突然变了。普通DataFrame用df.loc[华东]取行MultiIndex可以用元组取数grouped.loc[(华东, 202401)]MultiIndex本身不复杂它就是把原来的多个普通列“升格”成索引层次。用set_index([地区, 月份])可以主动创建用reset_index()可以再降回普通列。这两个函数的组合是处理groupby结果后继续往其他模块传数据时的惯用招数grouped.reset_index()如果你发现分组聚合后的结果没法直接和原表合并或者想继续按地区做透视reset_index()通常是最快的解法。需要说明的是MultiIndex并不是一个需要天天手动创建的东西但理解它非常重要。因为groupby、透视表、pivot_table的输出都会用到它不认识这个结构你会在很多“结果数据处理”环节卡住只觉得pandas突然变复杂了其实只是索引层级变了。4.2 concat 与 merge纵向、横向怎么拼不出错数据来自多个文件的时候拼接是个绕不开的需求。pandas有两大拼接函数concat负责“贴”merge负责“串”。concat最常用的场景是把多个结构相同的DataFrame上下堆起来比如你每个月的数据是一个CSV年底要合成一张全年表df_all pd.concat([df_1月, df_2月, df_3月], axis0, ignore_indexTrue)axis0是上下拼接axis1是左右拼接。上下拼的时候如果各表的列不完全一致pandas会在缺失位置填NaN这句话听起来抽象实际遇到的情况就是两张表一个有“备注”列一个没有拼完后没有的那部分备注全是NaN。ignore_indexTrue则会把拼接后的索引重新从0排一遍避免行号重复。用一句话类比concat就像把几张名单按顺序叠在一起名字能不能对上它不管只管叠。merge的用法完全是另一路逻辑它像查字典拿一个共同的key键把两边的信息串起来。df_merged pd.merge( df_订单, df_客户, on客户ID, howleft )on指定连接键how决定保留哪边的数据。howleft意思是保留左表全部行右表有匹配才填进来没有就填NaN。这是我日常用最多的一种因为业务统计里经常需要“以订单表为主表补上客户信息”left join正好满足。有些实训平台的作业里会让比较inner、left、right、outer四种连接方式的区别用一句话总结就行inner只保留两边都匹配得上的left保留左表全部right保留右表全部outer则是两边全部保留、能匹配就匹配。理解这四种方式的关键不是记名字而是想清楚“哪边是主角”。5. 环境问题排查实录pandas 装不上、用不了怎么办5.1 清华镜像源也报错先检查 Python 版本和网络写完上面这些内容肯定有一批读者正卡在第一步——pandas根本装不上。评论区天天能看到“清华源也报错”的求助报错长这样ERROR: Could not find a version that satisfies the requirement pandas (from versions: none) ERROR: No matching distribution found for pandas这个报错的出现排在第一的原因永远是Python版本太新pandas还没发布对应版本。尤其是Python 3.13刚出来那阵子很多第三方库都没跟上pandas的自然也在“from versions: none”里。遇到这种情况最快的解法是装回Python 3.10或3.11这是目前pandas生态最稳的版本段。第二个原因才是网络。如果你用的是自带Python环境pip install pandas默认走的是PyPI官方源国内访问波动大的时候就可能在下载阶段卡死或者直接找不到版本。这时候切换清华镜像源是标准操作pip install pandas -i https://pypi.tuna.tsinghua.edu.cn/simple注意-i参数后面跟的是https的地址。清华源是正规的开源软件镜像站大多数时候速度都很好。如果清华源也不行还可以试试阿里云、中科大、豆瓣的镜像本质都一样——把pip的下载源换成一个网络路径更友好的站点。最后一个隐藏原因你的命令行里装的Python和你的编辑器/IDE里用的Python可能不是一个东西。这是最多人栽跟头的地方下面单独展开。5.2 PyCharm、ArcGIS Pro、手机环境到底装哪了PyCharm装包的问题核心是“解释器环境”要搞清楚。PyCharm新建项目时默认会创建一个venv虚拟环境你在右下角看到的Python版本就是虚拟环境里的Python。你如果另开一个终端、敲pip install pandas装的却是系统全局Python两边都不互通。所以一个非常管用的排查习惯是在PyCharm里装包就直接在PyCharm里装。打开File - Settings - Project - Python Interpreter选中当前解释器点右侧的搜索pandas让IDE自己调pip安装。这样装出来的包一定在当前项目环境里不会有“明明装过却找不到”的诡异情况。ArcGIS Pro的pandas问题我在后台也经常收到。这里明确说结论ArcGIS Pro自带一个Python环境底层是conda管理的环境名叫arcgispro-py3这个环境里预装了pandas不需要自己再装。如果你的ArcGIS Pro里跑Python脚本却提示找不到pandas大概率不是环境里没有而是你用的Python路径搞错了——比如在系统Python里运行脚本而系统Python当然没装pandas。解决办法是确保你打开的是ArcGIS Pro自带的Python窗口或者在IDE里把解释器指向arcgispro-py3环境。手机Python装pandas我只能说可以但有条件。安卓上的Termux环境可以装Python和pandas但pandas这种带C扩展的库在手机上编译或安装的过程容易出问题而且手机的性能跑大DataFrame也不现实。如果你只是想学语法、跑小数据我更推荐用在线notebook或者云开发环境因为它们已经把pandas装好了打开浏览器就能写。手头没有电脑的时候用在线环境应急是个真香选择。5.3 排查步骤速查表从报错到跑通的完整路径最后把环境问题的排查路径整理成一张速查表遇到问题对着查一遍大多数坑都能填上。症状可能原因排查与解决ModuleNotFoundError: No module named pandas当前环境的库列表里确实没有pandas用pip freeze | grep pandas确认用当前环境的pip安装清华源报from versions: nonePython版本过新/过老没有匹配的pandas版本python --version查看换成Python 3.10或3.11清华源报下载超时/连接失败网络链路问题换阿里云、中科大等其他镜像源再试PyCharm里装完却运行报错项目解释器不是你以为的那个Settings - Python Interpreter查看实际路径在IDE里直接装代码行能跑但import pandas很慢可能是版本较旧加载慢升级到较新版本同时清理启动时的import链ArcGIS Pro里导入不了pandas用的不是arcgispro-py3环境切换解释器到ArcGIS自带Python环境踩过几次坑之后我现在的习惯是每次在陌生环境跑pandas先执行python --version和pip list | grep pandas把版本和安装状态确认完毕再干活。看起来多花十几秒实际上省掉了大量的未知变量。再分享一个我个人的体会pandas基础系列走到这一篇核心其实就三个字——读、转、看。把文本文件稳稳读进来把列类型转到能用用一小组函数对数据快速摸底。这三件事做扎实了后面学任何进阶操作都会觉得地基特别稳。我到现在处理数据日报依然是先read_csv再df.dtypes然后df.isna().sum()循环往复乐此不疲。工具在变但这些基本功从来没变过。
返回列表