ARTICLE DETAIL

资讯详情

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

Pandas数据处理实战:从Excel读取到数据清洗与分组聚合

Pandas数据处理实战:从Excel读取到数据清洗与分组聚合 1. 从一次双击Excel死机开始聊聊这个库先说个真实经历。去年年中我做数据复盘客户发来一张业务明细表也就十二万行。我习惯性用Excel打开想拉个透视表看看渠道分布结果Excel直接卡死转了五分钟沙漏最后弹窗未响应。我盯着屏幕想了三秒钟打开PyCharm写了三行代码import pandas as pd df pd.read_excel(业务明细_2024.xlsx) print(df[渠道].value_counts())回车一秒不到结果出来了。从那天起我在团队里再也没用Excel处理过超过五万行的数据。这个让我彻底转向的库就是Pandas——Python数据分析生态里绕不开的核心工具也是各位做数据分析、数据工程、商业分析甚至转行面试中最常被问到的技术栈。这篇内容我不打算写成官方文档的复读机而是想以一个真正拿Pandas干活的人的身份把我从环境配置、数据读取、清洗、转换到分组聚合的全过程踩坑经验梳理一遍。主要面向刚学完Python基础、准备进入数据分析领域的初学者已经在用Excel做报表、想提升效率的运营和业务人员以及准备跳槽、需要系统复习Pandas核心用法的数据分析岗位候选人。你会搞清楚这几件事Pandas为什么是三剑客里的中坚力量它和NumPy边界在哪环境到底怎么装才不会在版本上翻车读取Excel时哪些参数会直接影响你的工作效率数据清洗里的删除、去重、排序隐藏着哪些细节以及指定两列值相同则取第一条这类高频需求优雅的写法是什么。2. 环境准备阶段最容易踩的三个坑2.1 Python版本和Pandas版本的匹配关系很多初学者在网上搜pandas安装搜到的教程五花八门上来就pip install pandas大部分情况确实能装上但如果你用的Python版本比较新或者比较老就会遇到装上了但导入报错的尴尬。Pandas的版本迭代很快目前主流版本已经到2.x。对于Python 3.10及以上的环境安装pandas 2.x基本没有问题如果你的环境还是Python 3.8或者3.7建议安装pandas 1.5.x系列。直接装最新版有时候会提示当前Python版本不支持这个在pip的报错信息里会写得很清楚。我用Python 3.10跑pandas 2.1版本稳定使用了差不多一年没有遇到兼容性问题。如果你用的正好是Python 3.10在终端里执行pip install pandas2.1.4这个版本在Windows、macOS、Linux上都有预编译的wheel包不需要本地编译装起来速度很快。切记不要用pip install pandas直接装最新版除非你确认自己的Python版本在支持列表里。2.2 pip install pandas openpyxl 这行命令为什么必须存在这里有一个高频热搜词——pip install pandas openpyxl。很多新手只装了pandas没装openpyxl结果用pd.read_excel()读xlsx文件时报错提示缺少引擎。Pandas本身不直接解析Excel文件格式它是通过底层引擎来实现的。读取.xls文件需要xlrd读取.xlsx文件需要openpyxl。openpyxl不只是用来读它在Pandas的to_excel()方法中也是默认的写入引擎也就是说你想把处理好的DataFrame导出成Excel表格同样得靠它。所以安装的正确姿势是pip install pandas openpyxl把两个包一起装省得后面跑代码时才发现缺依赖再折回来补装。我用的是清华镜像下载速度比官方源快很多不在国内的朋友随意pip install pandas openpyxl -i https://pypi.tuna.tsinghua.edu.cn/simple2.3 PyCharm里安装Pandas包的两种方式在热搜词里看到pycharm怎么安装pandas包这个问题频率很高说明很多人习惯在IDE里解决环境问题而不是去敲命令行。第一种方式在PyCharm底部打开Terminal面板直接输入pip install pandas openpyxl按回车等它跑完。这种方式最直观而且能看到完整的安装日志如果有报错也方便排查。第二种方式通过PyCharm的设置界面安装File - Settings - Project - Python Interpreter点击右上角的号搜索pandas选中后点击Install Package。但这里有个坑——你必须先确认解释器选的是你当前项目正在用的那个Python环境。我见过不少同学在A项目里配了环境却跑到B项目里装包装完了回来导入还是报ModuleNotFoundError其实就是解释器选错了。一个小建议做数据分析项目时尽量在项目根目录建一个虚拟环境不要用全局Python装一堆包。虚拟环境的好处是项目之间的依赖互相隔离你在这个项目里升级pandas版本不会影响到另一个还依赖旧版本的项目。3. 先搞懂Series和DataFrame后面读代码就顺了3.1 用好Excel和SQL的类比这几个概念就不难理解Pandas里两个最核心的数据结构一个是Series一个是DataFrame这个基础概念直接决定后面能不能读得懂代码。可以把DataFrame想象成一张Excel表格有行号索引有列名表头每一列可以是不同的数据类型——数值列、字符串列、日期列都可以共存。DataFrame就是Pandas里最常用的表。Series则是这张表里单独抽出来的某一列它只有一列数据但依然保留着行索引。这个结构在数据分析里太常用了比如你想单独看某个字段的分布、算某一列的平均值操作的都是Series。如果你用过SQL也可以这样理解DataFrame是一张数据库表Series是这张表的某一个字段。Pandas里的很多操作其实就是在模拟SQL的查询逻辑——筛选、分组、聚合、关联本质上都是SQL里的概念。我们来创建一个小数据集把三个城市的销售数据塞进去import pandas as pd df pd.DataFrame({ 城市: [北京, 上海, 广州, 深圳], 销售额: [12000, 15000, 9000, 11000] }) print(df) print(type(df[销售额]))输出结果里type(df[销售额])会告诉你这是一个pandas.core.series.Series。你取单列拿到的是Series取多列或整个表拿到的是DataFrame。搞清楚这两者的区别后续写条件筛选、分组聚合时心里会非常有底。3.2 创建DataFrame的常用方式从字典、列表和文件读入除了从文件读取数据我自己最常用来快速验证想法的方式是从字典直接创建DataFrame。上面例子就是标准写法把字典的键作为列名值作为整列数据。这种方式在做小规模样例测试时非常高效。另一种常见方式是从列表套字典的格式创建每一行是一个字典data [ {城市: 北京, 销售额: 12000}, {城市: 上海, 销售额: 15000}, {城市: 广州, 销售额: 9000}, ] df2 pd.DataFrame(data) print(df2)这种格式的好处是当你从API接口拿到JSON数据时它天然就是列表套字典的结构直接丢给pd.DataFrame()就能完成转换比手动循环要快得多。当然日常工作中数据量通常不会小直接用字典构造的情况很少绝大多数是从文件、数据库或接口读入。在后面几个章节里我会逐个展开讲从Excel读取时的细节以及数据清洗时要做的处理。4. 读取Excel文件这一步操作的差距直接体现在效率上4.1 read_excel里真正要关心的几个参数pandas读取excel文件是被搜索最多的用法之一但大多数人只会用最基础的pd.read_excel(文件路径.xlsx)一旦遇到真实业务数据各种问题就冒出来了。read_excel()里值得花时间吃透的参数有这几个pd.read_excel( io文件路径.xlsx, sheet_nameSheet1, header0, skiprowsNone, usecolsA:F, dtype{工号: str}, parse_dates[日期] )sheet_name如果Excel文件里有多张表你可以传入表名或索引传入列表可以一次读取多张表。header指定哪一行作为列名。默认是第0行也就是第一行但如果你的报表前几行是标题、日期说明之类的内容就要用header2这类写法跳过。skiprows有时候表格上方有各种备注信息可以用skiprows3直接跳过前3行不读进来。usecols这个参数特别实用我强烈建议在读取大批量Excel时用它做列裁剪。如果文件有50列而你只需要其中6列用usecolsA:F能大幅减少内存占用和读取时间。要只读指定的几列也可以传入列名的列表比如usecols[城市, 销售额]。dtype强制指定列的数据类型。这个在读取工号00123这类数据时特别关键——如果不强制指定为字符串Pandas会把列推断成整数前面的0就丢了。数据一旦在读取阶段就错了后面怎么处理都白搭。parse_dates把指定的列解析为日期格式比读进来之后再用pd.to_datetime()转换更省事。我做一个实际的例子假设你手上的业务表长这样前2行是公司名称和统计日期说明第3行才是真正的表头。读取的代码应该是import pandas as pd df pd.read_excel( 销售明细.xlsx, skiprows2, usecolsA:F, dtype{门店编号: str}, parse_dates[下单日期] )4.2 字符集和引擎很多莫名其妙的报错都出在这里读Excel文件遇到报错时第一反应不是怀疑代码而是检查引擎。如果你没装openpyxl就读取xlsx文件报错信息会提示你Missing optional dependency openpyxl。这种情况不用怀疑直接补装。还有一种常见情况是读取2003年之前的旧版Excel文件后缀是.xls这时候需要的是xlrd引擎而且要注意xlrd 2.0以上的版本已经不支持xlsx格式文件了只用它来读xls格式即可。读CSV文件时也存在类似问题。很多人用pd.read_csv()处理从业务系统导出的文件遇到乱码就是编码格式没对上。国内很多系统导出的CSV是GBK或GB2312编码而Pandas默认用UTF-8读一读就乱码或者抛UnicodeDecodeError。解决办法是在read_csv()里手动指定编码df_csv pd.read_csv(数据.csv, encodinggbk)要是连gbk都报错可以试试encodinggb18030这个编码格式的覆盖范围更广基本能兼容所有中文场景。我在项目里的习惯是接到任何来源不明的CSV文件时第一步先打开文件看一眼原始格式或者用Python的chardet库检测编码而不是直接猜。虽然绕了一点但能省下后面大量排查乱码的时间。5. 数据清洗里的删除操作drop家族的正确打开方式5.1 按行删、按列删inplace到底用不用pandas drop这个热搜词说明很多人在查删除操作。Pandas里和删除相关的命令很多最常用的就是drop()方法它可以删行也可以删列关键看axis参数。axis0或默认情况删除行。axis1删除列。直接对内存里的DataFrame执行删除时注意一个核心规则默认情况下drop()返回的是一个新对象不会修改原DataFrame。如果你希望直接改掉原数据需要加inplaceTrue。我在实际工作中很少用inplaceTrue原因是链式操作时它没法复用结果而且如果操作中途报错原数据已经被改掉了排查比较麻烦。我通常会把每一步处理结果赋值给新的变量保留每一步中间状态df_clean df.drop(columns[备注, 辅助列], errorsignore)这里要特别提醒一个参数errorsignore。当你想删除的列名在数据里不存在时drop()默认会抛KeyError。加上这个参数代码不会挂掉适用于批量处理多张表结构不完全一致的场景。删除行也一样。如果你需要删除满足某些条件的行可以结合布尔索引操作。比如删掉销售额为空的记录df_clean df[df[销售额].notna()]notna()保留非空的行等价于删掉空的。这种写法比dropna()更灵活因为你可以在条件里随意组合其他逻辑。5.2 重复值处理drop_duplicates的精髓在subset数据清洗里还有一个高频动作就是去除重复记录。drop_duplicates()是Pandas专门用来做去重的方法如果不带任何参数调用它会根据整行所有列来判断是否重复。但真实业务中更常见的需求是根据某几个关键列来判断重复。热搜词里有一个非常典型的场景——pandas如果指定两列的值均相同则取第一条数据即可。例如一张订单表里同一用户在同一天下了多次订单你希望保留每个用户每天的第一条下单记录删除其余记录怎么操作我们用drop_duplicates()来实现df_result df.drop_duplicates(subset[用户ID, 下单日期], keepfirst)这里subset参数指定判断重复的列keepfirst表示当出现重复时保留最早出现的那一条。如果希望保留最后一条就把keeplast。如果想要把所有重复记录都标记出来而不直接删也可以先把keepFalse的结果取出来看看确认无误后再删。为什么这个场景值得好好理解因为取第一条在SQL里对应的写法要复杂得多比如用窗口函数ROW_NUMBER() OVER (PARTITION BY 用户ID, 下单日期 ORDER BY 下单时间) 1。而在Pandas里一行代码就搞定了这也是Pandas在数据处理阶段效率远高于SQL的原因之一。5.3 处理缺失值时的克制与分寸处理NaN缺失值是数据清洗绕不开的环节。很多新手一看到数据里有空值第一反应是全部删掉。这个做法在有大量数据且缺失值很少时问题不大但一旦缺失值比例较高直接删除会损失大量有效信息。我一般的处理策略是分三步第一先看每列缺失值的比例missing_ratio df.isna().mean().sort_values(ascendingFalse) print(missing_ratio)这样能快速定位哪些列是重灾区。如果某列缺失比例超过60%我会直接和业务方确认——这列到底还有没有用可能留着意义不大。第二根据缺失比例和数据特征决定策略删除整行、删除整列、填充常量或者用前后值填充。数值列可以用该列的中位数或均值填充分类列可以用众数填充。第三涉及时间的序列数据优先用ffill()和bfill()做填充这一个技巧在处理用户行为日志时尤其好用。# 用上一个有效值填充 df_filled df.fillna(methodffill)6. 数据处理中的类型和去重排序细节6.1 数据类型转换为什么会影响后续计算pandas 数据类型转换也是热搜词中的高频需求。Pandas读取文件后会自动推断每一列的数据类型但这种推断并不总是符合我们的预期。比较经典的场景从Excel读入的金额列如果某些格子被填写成了字符串格式那么整列数据可能被Pandas识别成object类型。这时候你对这列做sum()得到的结果不是数值加总而是字符串拼接。诊断数据类型的方法print(df.dtypes)转换类型的关键方法是astype()以及专门用来转换日期格式的pd.to_datetime()。df[销售额] df[销售额].astype(float) df[下单日期] pd.to_datetime(df[下单日期]) df[用户ID] df[用户ID].astype(str)转换是有成本的高频列尽量在读取阶段就通过read_excel()的dtype参数指定好而不要依赖读进来之后的二次转换。还有一个细节类型转换会引入NaN值尤其是把金额123元这样的字符串强行转成数值时转不动的部分会变成NaN。所以最优做法是在源头清洗数据比如先去掉单位符号再转换df[金额_干净] df[金额].str.replace(元, ).astype(float)6.2 复合去重场景两列相同保留第一条的完整实战我们把上面的去重场景扩展成完整的真实案例方便你直接照着用。假设数据是某电商平台的订单明细列包括订单号、用户ID、下单时间、商品名称、销售额。业务需求是由于系统同步异常同一个用户在同一分钟内下了多笔订单但实际应该合并为一次会话要求每个用户保留第一次下单记录其余删除。import pandas as pd df pd.read_excel(订单明细.xlsx) # 1. 先把下单时间字符串转为真正的日期时间格式方便排序 df[下单时间] pd.to_datetime(df[下单时间]) # 2. 按用户ID和下单时间两列确定重复 # 先按用户ID和下单时间组合排序确保第一次出现排在最前面 df_sorted df.sort_values(by[用户ID, 下单时间]) df_result df_sorted.drop_duplicates(subset[用户ID, 下单时间], keepfirst)这里有个容易被忽略的点drop_duplicates()判断第一条是依据数据在DataFrame里的先后顺序。如果原始数据的顺序本来就是乱的不先排序就直接去重保留的第一条不一定是时间上最早的那一条。所以需求里只要提到保留最早就一定要配合sort_values()先做排序。排序后去重再按原来的时间顺序恢复df_result df_result.sort_values(下单时间).reset_index(dropTrue)reset_index(dropTrue)的作用是把原索引重置为从0开始的连续整数。去重之后行索引会保留原始的位置编号出现类似[0, 3, 5, 9]这样的断层后续做遍历或和其他表拼接时可能出问题提前刷新索引是更稳妥的做法。7. 条件筛选和分组聚合这才是业务分析的主战场7.1 布尔索引的写法比SQL更灵活Pandas筛选数据的核心机制是布尔索引——你给它一个由True和False组成的条件序列它返回所有条件为True的行。示例# 找出销售额大于1万的记录 df_high df[df[销售额] 10000] # 多个条件组合 df_filter df[(df[销售额] 10000) (df[城市] 北京)] # 或运算 df_filter2 df[(df[城市] 北京) | (df[城市] 上海)] # 取反 df_not_bj df[~(df[城市] 北京)]注意两点条件里的每个比较要用括号包起来这是Python运算优先级决定的多个条件之间逻辑与用、逻辑或用|不要写and和or。Python的and和or只能用于单个True/False值的判断而DataFrame的条件筛选返回的是一整个布尔序列用and会直接抛ValueError。新手在这里踩坑的概率非常高我在带人的时候几乎每个人都会撞一次。7.2 groupby聚合分组后的everything如果你做过SQL的GROUP BY那Pandas的groupby()对你来说会非常亲切。它的核心是拆分-应用-合并三步按分组键把数据拆成若干小组对每个小组应用聚合函数再把结果合并成一张新表。基础用法# 按城市分组计算销售额总和、平均值、订单数 df_group df.groupby(城市).agg( 销售额总和(销售额, sum), 销售额平均(销售额, mean), 订单数(订单号, count) ).reset_index()这段代码里的agg()函数很关键。它接收一个字典字典里的键是结果列的新列名值是元组第一个元素是要被聚合的列第二个元素是聚合方式。聚合方式可以是sum、mean、count、max、min、nunique等。按多个列同时分组也完全支持df_group2 df.groupby([城市, 商品类别]).agg( 销售额(销售额, sum), 用户数(用户ID, nunique) ).reset_index()nunique计算去重后的数量统计有多少个不同用户时用它非常合适这也正好承接前面讲过的去重逻辑——如果你不主动去重直接用count()统计用户数反复下单的用户会被重复计算。7.3 金融风控场景里的实战组合在金融风控数据分析里很多用人方会考察求职者对分组聚合的熟练度。举个典型的例子有一张用户交易流水表包含用户ID、交易时间、交易金额、交易类型等字段要求你分析每位用户的首次交易金额分布。这里的核心是每位用户的首次交易。你可以这样做df[交易时间] pd.to_datetime(df[交易时间]) # 按用户排序找到每个用户最早的一笔交易 df_sorted df.sort_values(交易时间) first_trade df_sorted.drop_duplicates(subset[用户ID], keepfirst) # 看首次交易金额的分布 print(first_trade[交易金额].describe())describe()方法会输出金额列的均值、标准差、最小值、四分位数、最大值等关键统计量这是拿到任何新数据时先摸个底的好工具。在风控场景里首次交易金额如果出现非常极端的长尾分布往往意味着有一批异常账户在试探支付通道。8. 关于pandas文档和持续学习的建议8.1 官方文档的正确用法是查不是从头读python pandas docs在热搜词里出现代表大家不是不知道有官方文档而是不知道怎么高效使用它。Pandas的官方文档内容多、组织庞大要是从头到尾当一本书来读不出十页就会放弃。我自己的习惯是把文档当工具书来查。用df.groupby遇到问题去搜官方页面里对应方法的参数解释和示例用pd.merge没把握就在CSDN、Stack Overflow和官方User Guide之间来回对照。这里推荐一个学习方法在PyCharm里调出一个DataFrame对象后用print(df.head())看前几行数据用df.info()看每列的类型和空值情况用df.describe()看数值列的分布。这三个方法是你探索任何新数据集的第一步。8.2 拿真实数据集练手比刷一百个课程视频都有用数据分析案例、数据分析项目这类热搜词背后是大家普遍困惑的知识和应用之间的断层。Pandas学完之后总觉得自己会但要独立面对一个脏数据集的清洗和分析时还是不知道从哪下手。破解这个困局的唯一办法是拿真实数据练手。Kaggle上有大量开源数据集国内一些数据竞赛平台也开放了脱敏的网约车轨迹数据、电商购物数据、金融借贷数据。拿到数据后试着给自己提几个业务问题哪些城市的订单量最高环比上月销售额的变化幅度是多少用户首次购买后7日回购率怎么样不同支付渠道的客单价是否有显著差异实际操作中你会发现80%的时间做的是清洗整理工作20%的时间才是真正的分析建模。这种比例完全正常也是Pandas价值最集中的地方。8.3 pandas和NumPy的分工在实战中越来越清晰Pandas和NumPy是我最早接触Python数据分析时同步学习的两个库它们的紧密程度让numpy和pandas库的使用成了所有入门教程的必讲章节。NumPy提供了底层的多维数组对象——ndarray以及一大堆高效的数值运算函数Pandas则构建在NumPy之上把ndarray包装成了带行列标签的DataFrame和Series。说明白点凡是涉及单个数或向量级的高性能数值计算用NumPy凡是涉及表结构的数据处理、列名操作、分组聚合、时间序列处理用Pandas。两个库之间的衔接也顺畅——直接取df[销售额].values拿到的就是NumPy数组可以喂给各种算法模型和数学函数。在实际项目里Pandas是骨架NumPy是肌肉Matplotlib和Seaborn则是输出分析结果的五官。这就是人们常说的数据分析三剑客的真正含义——三个库各司其职组合起来才能完成从数据导入、清洗、分析到可视化的完整闭环。9. 我压箱底的几个Pandas操作习惯最后毫无保留地分享几个真实经验都是我在项目和带人过程中沉淀下来的习惯不一定写在哪本教材里但对效率提升非常明显。第一能不写for循环就不写。刚接触Pandas的同学因为惯性思维总喜欢用循环逐行处理数据。Pandas本身是向量化运算的对整列同时操作是它的核心优势。能用df[df[销售额] 10000]筛选就不要写for i in range(len(df))再逐行判断。一旦数据量上了几百万行循环和向量化之间的速度差距是以百倍计的。第二好钢花在处理前先备份原始数据。常常有同学跑了一堆清洗代码之后发现某一步逻辑不对想回到前面重来结果原数据早就被覆盖了。我的做法是每次用read_excel或read_csv读进原始数据后不管三七二十一先执行一次df_origin df.copy()。copy()生成的是一个真正独立的对象后续改它不会影响原数据。这点非常重要Pandas很多切片操作返回的是视图而不是副本直接赋值后修改视图原表可能会被连带改动。第三merge()关联多张表之前确认关联键的数据类型一致。这是我在实际项目中翻车最多的地方。一张表的用户ID是从Excel读的被识别成了整数另一张表的用户ID是后来从CSV读的因为某些ID带前导零被识别成了字符串。用df1.merge(df2, on用户ID)合并的结果要么全为空要么直接报错。解决办法是在merge之前统一用astype(str)处理或者干脆在读取阶段就通过dtype参数强制指定。第四随手把常用查询逻辑封装成函数。如果你发现某段数据清洗逻辑在多个项目中反复使用比如去掉超长会话标记异常金额把它封装成函数放到自己维护的utils.py里。我做了三年数据分析积累了二十多个这样的通用函数每进入一个新项目先导入这个工具包很多脏活累活能减少一半。写到这里回头看这两年用Pandas处理数据的经历最明显的感受是它把数据分析师从怎么处理数据中解放出来让人把精力真正放到数据说明了什么上面。工具本身的学习曲线没有想象中陡峭花两周时间把数据读取、清洗、转换、聚合这些基础操作练熟就已经能在实际工作中发挥十之七八的战斗力了。剩下的高阶技巧和方法都是在真实的项目里遇到问题、解决问题时一点一点沉淀下来的。希望这篇写得够实诚对正在往这条路上走的你有一点实质性的帮助。
返回列表