☰
Pandas核心函数实战:从读写、清洗到聚合的高效数据处理
2026/10/16 3:37:06 网站建设 项目流程

如果你和我一样,每天都要面对成堆的表格、CSV、数据库导出的杂乱数据,那你大概率绕不开 Pandas。最近我在 HoRain云 上整理了一批数据处理脚本,把日常项目里最常用的一批核心函数系统过了一遍,越用越觉得“核心函数”这四个字比想象中值钱:很多你以为很复杂的操作,其实都是几个基础函数组合出来的。这篇文章不搞花哨炫技,只讲实际项目里真正高频、能帮你把数据快速整理干净的 Pandas 核心函数,按“读数据、洗数据、变数据、聚数据”这条主流程来拆,适合刚从 Excel 迈向 Python 的初级选手,也适合经常写数据处理脚本但总在一些小地方卡壳的进阶用户。

1. 内容整体设计与思路拆解

1.1 为什么每个数据处理项目都离不开这批函数

我在不同环境里写过数据处理脚本,最大的感受是:Pandas 的 API 极其庞大,但真正每天都被反复调用的也就是那二三十个函数。很多人在学习时喜欢把 DataFrame 里所有方法都翻一遍,结果记得多、用得少,等到真正面对脏数据时反而不知道从哪下手。

核心函数之所以“核心”,是因为它们正好对应了数据处理的三类刚需:读取解析、清洗修正、重塑聚合。只要把这三类吃透,大部分 Excel 手工操作都能被脚本取代。比如merge对应 VLOOKUP,groupby对应数据透视表,apply对应单元格级自定义公式,fillna对应查找替换空值。用代码做数据处理的本质,就是把你在表格界面里的一次次鼠标点击,翻译成一行行可复用、可追溯、可回放的逻辑。

1.2 数据处理的标准流水线

我在处理一个模拟项目时,无论数据来自日志、业务导出还是公开数据集,主线流程基本都是固定的:

  1. 数据读取:把外部数据装入 DataFrame
  2. 初步探查:理解数据规模、类型、缺失情况
  3. 数据清洗:处理缺失值、重复值、类型问题、异常值
  4. 数据变换:筛选、排序、新增列、长宽表转换
  5. 分组聚合:按某种维度汇总、合并多张表、处理时间序列
  6. 结果输出:导出为 CSV、Excel,或进入可视化环节

这套流水线在云上跑也一样适用。比如我在 HoRain云 的 Notebook 环境里,读取的是对象存储上的数据文件,处理完再写回。核心函数本身不挑环境,但云上有个额外好处:内存、CPU 资源容易弹性调整,适合处理单机跑不动的大文件。

1.3 环境准备与版本选择

做任何数据处理前,先把环境定下来。我一般用 Python 3.9+ 搭配 pandas 1.5.x 或 2.x,外加 NumPy。安装很简单:

pip install pandas numpy openpyxl

这里必须提醒一句:openpyxl是读写 Excel 的后端引擎,很多人在read_excel时报错,就是因为没装这个包。云上的基础镜像通常预装了 pandas 和 numpy,但 Excel 相关依赖不一定有,第一次用前先跑一下import openpyxl确认。

另一个容易被忽略的是版本差异。pandas 2.x 在有些 API 上调用了新的数据类型,比如read_csv默认的字符串类型更规范,某些操作对空字符串的处理也更严格。所以团队协作时,最好把环境版本固定写进requirements.txt,避免“本地跑得好好的,部署到云上就报错”的尴尬。

2. 数据读取与初步探查

2.1 read_csv 和 read_excel 的高频参数

我碰到过最离谱的需求,是一个业务方给的 CSV 文件,分隔符不是逗号而是分号,第一行还不是表头。如果不懂参数,光这两点就能卡住。read_csv的常用参数,务必熟练:

import pandas as pd df = pd.read_csv( "data.csv", sep=";", # 分隔符,默认逗号 encoding="utf-8", # 编码,中文数据常用 utf-8 / gbk header=0, # 指定表头行号,None 表示无表头 names=["id", "name"], # 无表头时手动指定列名 dtype={"id": str}, # 指定列类型,防止ID被读成数字 parse_dates=["date"], # 自动解析日期列 nrows=10000, # 只读前1万行,快速预览 )

读 Excel 时路径层次很容易混,read_excel的sheet_name可以直接填工作表名称,也可以填索引从 0 开始。我习惯用名称,因为 Excel 里工作表顺序常常被人挪动。

注意:数据量大时,nrows参数只适合探查,真正全量读取后要先确认文件行数与预期一致。我曾经碰到过文件里有大量空行,read_csv默认会把空行当成 NaN 行读进来,后续统计时莫名多了很多条记录。

2.2 用 info、describe、shape 快速摸清数据底细

读完数据先不要急着清洗,一定要先“望闻问切”。我的固定组合是:

print(df.shape) # 行数、列数 print(df.info()) # 每列的非空计数和数据类型 print(df.describe()) # 数值列的统计描述 print(df.head(10)) # 头部样例

info()看的是数据集的“体检报告”:哪列有缺失、哪列类型不对、总共多少内存占用。describe()能暴露很多玄机,比如某列最大值离谱、某列均值明显偏离中位数,都说明可能存在异常值。至于head()和tail(),则是用肉眼确认清洗方向的前提。

2.3 索引:处理数据的第一根拐杖

很多初学者吃亏在索引上。df.loc[2]取的是“索引标签为 2”的行,而df.iloc[2]取的是“第 3 行”。如果索引经过筛选后不再连续,这两个的差别会直接造成取数错误。

df_sorted = df.sort_values("salary", ascending=False) # 排序后索引还是原来的顺序,如果想变成 0,1,2... df_sorted = df_sorted.reset_index(drop=True)

set_index则可以把业务主键设置成索引,比如订单号、用户ID,后续用df.loc["order_1001"]取数就很快,也方便与其他表做索引对齐。

3. 数据清洗核心函数

3.1 缺失值处理:先摸清缺失模式,再决定删还是填

处理缺失值最怕一刀切。我见过有人看到有 NaN 就直接dropna(),结果把整表数据删得只剩一半。正确的姿势是先搞清楚缺失分布:

print(df.isna().sum()) print(df.isna().mean() * 100) # 缺失占比

isna()返回同形状的布尔 DataFrame,sum()之后就能看到每列缺失数量;想看百分比就除以行数。接下来按列性质决定策略:

  • 关键业务字段缺失较多,且无法推断时,删除整行
  • 数值型字段,可用均值、中位数填充
  • 时间序列数据,建议用前向或后向填充
  • 分类字段,可填充“未知”或该列众数
df["age"] = df["age"].fillna(df["age"].median()) df["join_date"] = df["join_date"].fillna(method="ffill") # 向前填充 df.dropna(subset=["order_id"], inplace=True) # 删除关键字段缺失

dropna的subset参数很多人不知道,它能指定只在某些列做缺失判断,而不是整行全空才删。thresh参数则是“非空值数量阈值”,保留有效信息较多的行,比如df.dropna(thresh=5)表示一行至少要有 5 个非空值才保留。

3.2 重复值处理:先确认重复范围,再决定保留策略

重复值不只是行完全一样才算重复。业务上一个用户可能有多条记录,但其中某几个关键字段重复就足以判断为异常。duplicated和drop_duplicates都支持subset参数:

df.duplicated(subset=["user_id", "order_id"], keep=False) df.drop_duplicates(subset=["user_id"], keep="last", inplace=True)

keep参数有三个值:first保留第一条,last保留最后一条,False删除所有重复行。我最常踩的坑是:想找出重复项时用了keep=False,结果把只出现一次的也标成 True,因为keep=False会把所有重复组内成员都标记出来,而单条记录不属于重复。

3.3 类型转换与异常值过滤

SQL 里有个说法:垃圾进、垃圾出。Pandas 里类型混乱就是典型的垃圾入口。最常见的情况是:ID 列明明是字符串,却被读成了数字,前导零全没了;金额列里混入了逗号或人民币符号。

解决办法是astype和to_numeric:

df["id"] = df["id"].astype(str) df["amount"] = pd.to_numeric(df["amount"].str.replace("¥", "").str.replace(",", ""), errors="coerce")

errors="coerce"会把无法转换的值变成 NaN,这一步再配合缺失值处理就能定位脏数据。我一般会在转换后立刻看一下:

bad = df[df["amount"].isna()]

用clip限制数值范围也很实用,尤其处理明显的录入错误时:

df["age"] = df["age"].clip(lower=0, upper=120)

3.4 文本清洗:str 访问器是文本列的万能工具

Pandas 里文本列通过.str访问器暴露字符串方法,几乎把 Python 原生字符串方法都搬了过来。我做用户信息清洗时,最常见的就是空格、大小写、前后缀问题:

df["name"] = df["name"].str.strip() # 去首尾空格 df["city"] = df["city"].str.upper() # 统一大写 df["phone"] = df["phone"].str.replace("-", "", regex=False) df["category"] = df["category"].str.split("|").str[0]

regex=False这个参数是血泪教训。默认情况下replace把模式当正则表达式,如果你只想替换普通字符串,必须显式关闭正则,否则括号、点号这类字符会跑出奇怪结果。

4. 数据变换核心函数

4.1 排序、改名与排名

排序是高频刚需。sort_values支持多列、升降序组合:

df.sort_values(by=["group", "score"], ascending=[True, False], inplace=True)

rename改名时,列名映射字典的 key 是“旧列名”,value 是“新列名”,这个方向很多人第一次写反:

df.rename(columns={"buy_cnt": "purchase_count", "amt": "total_amount"}, inplace=True)

rank在竞赛式排名场景里非常好用,method参数控制并列处理:average取平均名次,min取最小名次,dense是连续排名。比如销量榜,相同销量希望名次不跳号,就选method="dense"。

4.2 条件筛选:query 让代码像 SQL

用df[df["age"] > 18]这种写法看久了眼睛累,字符串式的query可读性高很多:

adults = df.query("age >= 18 and city in ['北京', '上海']") top = df.query("0 < score < 100")

query对列名里的空格和特殊字符处理有门槛,变量引用需要加@前缀:

min_age = 18 adults = df.query("age >= @min_age")

loc和iloc是精确取数的底牌。df.loc[df["a"] > 0, ["col1", "col2"]]一次完成“条件筛选 + 列选择”。between在区间筛选时更直观:df["score"].between(60, 90)。

4.3 apply 家族:别把所有活都丢给 apply

apply是万能工具,但也是性能陷阱。很多新手遇到不规则需求就 apply,实际上map、applymap、向量化操作各有适用场景:

df["gender_label"] = df["gender"].map({"M": "男", "F": "女"}) df[["height_cm", "weight_kg"]] = df[["height_m", "weight_kg"]].apply(pd.to_numeric)

规则:

  • 单列值映射是标量字典:用map
  • 整行或整列做函数操作:用apply
  • DataFrame 每个元素逐一操作:用applymap(现在很多场景可以另用map)
  • 能直接用算术运算的绝不 apply,比如df["bmi"] = df["weight"] / (df["height"] ** 2)比 apply 快一个量级

assign是链式写法里新增列的利器,它不会修改原 DataFrame,非常适合一步步组装新表:

df = df.assign( amount_yuan=lambda x: x["amount"] / 100, category_upper=lambda x: x["category"].str.upper(), )

4.4 长宽表转换:melt 和 pivot 是透视表的灵魂

Excel 里的透视表,在 Pandas 里就是pivot_table。它的参数对应关系非常直观:index是透视表的行区域,columns是列区域,values是数值区域,aggfunc是汇总方式。

pivot = pd.pivot_table( df, index="category", columns="region", values="sales", aggfunc="sum", fill_value=0, )

反过来,宽表变长表用melt。我从业务系统导出的宽表经常是“每个月份一列”,合计 12 列,做趋势分析前必须先变形:

long_df = df.melt( id_vars=["product_id"], value_vars=["2024-01", "2024-02", "2024-03"], var_name="month", value_name="sales", )

stack和unstack处理多层索引时很好用,unstack可以把行索引转到列,stack反之。业务上不常用,但一旦遇到多层索引的聚合结果,这两个是救命级函数。

5. 分组聚合核心函数

5.1 groupby:先拆分,再应用,最后合并

groupby是 Pandas 的灵魂,也是从“会处理表”到“会分析数据”的分水岭。它的执行逻辑可以拆成三步:按指定列分组、对每组应用聚合逻辑、把结果拼接起来。我经常在脑子里把它想成“SQL 的 GROUP BY + 各类聚合函数”。

常用写法:

# 单列聚合 df.groupby("category")["sales"].sum() # 多列多聚合 result = df.groupby(["category", "region"]).agg( total_sales=("sales", "sum"), avg_price=("price", "mean"), order_count=("order_id", "count"), )

agg中传入元组列表非常灵活,左边是新列名,右边是聚合方式。比起groupby(["category"]).agg({"sales": ["sum", "mean"]})生成多层列名,这种方式更适合直接导出报表。

transform能在原 DataFrame 保持行数不变的前提下,把聚合结果广播回去。比如计算“每个人销售额占所属组总销售额的比例”:

df["group_total"] = df.groupby("category")["sales"].transform("sum") df["pct"] = df["sales"] / df["group_total"]

filter则可以对分组做整体筛选,比如只保留订单数大于 100 的分组:

df.groupby("category").filter(lambda x: x["order_id"].count() > 100)

5.2 窗口函数:rolling、expanding、shift

做时间序列或者滑动统计时,rolling是默认选择。滚动平均是消除波动的第一招:

df["sales_ma7"] = df["sales"].rolling(window=7).mean()

expanding算累计值,比如累计销售额、从首日到当日的最大值:

df["cum_sales"] = df["sales"].expanding().sum()

shift是构造“上一行 / 下一行”数据的利器,尤其在算环比、同比时:

df["previous_sales"] = df["sales"].shift(1) df["mom_growth"] = (df["sales"] - df["previous_sales"]) / df["previous_sales"] * 100

遇到“每天只取最近 N 条”这种需求,可以用分组内 rolling + 固定窗口,或者用sort_values+groupby().head(n)。很多人不知道分组里也能直接head(3),它比循环拼接高效得多。

5.3 merge、concat、join:多表组合的三种姿势

多表数据合并是核心函数里最像“正规军”的部分。merge对应 SQL 的 JOIN,核心参数是how和on:

df_orders.merge(df_users, how="left", on="user_id") df_orders.merge(df_users, how="inner", left_on="uid", right_on="id")

how的可选值我总结成一句话:inner是取交集,left、right分别是保留左表或右表全部行,outer是并集。on用在两边列名相同的情况,不同就用left_on+right_on。

concat更多用于纵向拼接,比如把几个月的数据拼到一张表:

all_df = pd.concat([df_month1, df_month2], axis=0, ignore_index=True)

axis=0是上下拼,axis=1是左右拼。业务报表中常常需要把多张结构相同的表上下拼接,ignore_index=True会在拼接后重新生成连续索引,避免索引重复造成的取数错乱。

join是基于索引的合并,适合两个表都有相同索引但列不同时。它没有merge用的多,但是写完set_index之后直接join会产生非常简洁的效果。

5.4 时间序列:to_datetime、resample、dt 访问器

时间列在业务数据里几乎无孔不入。我拿到任何表,第一件事是把时间列转成datetime64类型:

df["date"] = pd.to_datetime(df["date"], format="%Y/%m/%d")

format参数是一个大救星。读取时如果日期格式混杂,不指定 format 会非常慢,pandas 会自动推断,但在大数据量场景下推断耗时长且容易误判。指定 format 后速度能提升数倍。

resample是时间序列的 groupby。它要求索引是时间类型:

df.set_index("date", inplace=True) daily = df["sales"].resample("D").sum() monthly = df["sales"].resample("M").mean() weekly = df["sales"].resample("W-MON").sum()

频率代码是硬记的知识点:D日、W周、M月末、MS月初、Q季度、H小时。我用M和MS时经常搞混,后来记住一句话:大写的M是“月末”,带S的MS是“月初(Start)”。

dt访问器用于从时间列里提取月份、星期、小时等成分:

df["month"] = df["date"].dt.month df["weekday"] = df["date"].dt.dayofweek # 0=周一 df["hour"] = df["date"].dt.hour

6. 常见问题与排查技巧实录

6.1 内存告警:读大文件时怎么省内存

在云上处理几百 MB 甚至上 GB 的数据时,最烦的莫过于MemoryError。我实际项目里总结了几招。

第一招,只读需要的列和行:

df = pd.read_csv("big.csv", usecols=["id", "date", "sales"], nrows=500000)

usecols能极大减少内存占用,因为 pandas 不会把没选中的列解析进内存。第二招,对能确定类型但默认推断不合理的列,指定dtype,比如布尔列、短文本列。第三招,分批读取后合并结果:

chunks = pd.read_csv("big.csv", chunksize=100000) result = [] for chunk in chunks: result.append(chunk.groupby("category")["sales"].sum()) final = pd.concat(result).groupby(level=0).sum()

6.2 链式赋值的坑:为什么设置值没生效

新手最困惑的问题之一,是df[df["a"] > 0]["b"] = 1之后发现原表没变。这不是 bug,而是 pandas 的链式赋值语义。修改数据时优先统一用loc:

df.loc[df["a"] > 0, "b"] = 1

用loc能确保修改的是原 DataFrame 的视图,而不是一个副本。排查此类问题时,可以把pd.set_option("mode.chained_assignment", "raise")打开,它会在链式赋值时抛出异常,帮你快速定位位置。

6.3 聚合结果列名过长或混乱

groupby().agg({"sales": ["sum", "mean"]})之后,列名会变成多层索引,导出 Excel 时不直观。两个解决办法:直接在agg里用元组命名,或者事后展平列名:

df.columns = ["_".join(col).strip() for col in df.columns.values]

如果是元组命名方式,这一步基本可以省略。我在做报表输出时,习惯最后统一重命名一遍,确保交付的表头是中文或业务术语。

6.4 云上环境运行 Pandas 的实操建议

在云上环境(比如我常用的 HoRain云 Notebook)跑数据,有几个和本机不一样的地方。一是数据文件通常要先下载到本地工作目录,或者直接挂在对象存储路径下,我用read_csv时习惯先用!ls -lh确认文件大小。二是云上环境可能会限制当前工作目录的写权限,导出结果时先确认路径能写:

import os print(os.getcwd())

三是多云环境经常启用多个 CPU,multiprocessing配合简单任务并不直观,我一般优先用 pandas 本身的向量化能力,而不是盲目并行。四是注意运行时长和空闲超时策略,长时间任务尽量拆成小批次跑,或者把中间结果落盘,防止一次会话断掉前功尽弃。

6.5 核心函数速查场景对照表

场景核心函数一句话要点
读 CSVread_csv记得处理分隔符、编码、表头
预览数据head/info/describe先看规模、类型、缺失再动手
缺失值统计isna().sum()缺失占比决定删还是填
重复值删除drop_duplicatessubset指定判断列
类型转换astype/to_numeric转换失败用errors="coerce"
文本清洗str.strip()/str.replace()regex=False防正则误伤
条件筛选query/loc变量引用加@
新增列assign/map向量化优于逐行 apply
宽表转长表meltid_vars固定分类列
透视表pivot_tableaggfunc写清汇总方式
分组聚合groupby().agg()元组命名新列
滚动均值rolling().mean()固定窗口滑过时间轴
多表合并mergehow决定保留哪些行
纵向拼接concatignore_index=True避免索引重复
时间处理to_datetime/resampleformat指定格式提速

6.6 我的实操经验补充

最后分享几个只有自己亲手敲过代码才会注意的细节。

第一,凡是赋值操作,养成指定inplace=True或重新赋值的好习惯,避免混淆。现在很多项目越来越强调“无 inplace 写法”,我也慢慢转向返回新对象的风格,因为它在链式操作里更安全。

第二,排错时善用单行小 sample。我会把业务关键数据切成几十行的小表,所有操作先在小表上跑通,再去全量跑。小表打印出来肉眼可读,效率远高于盯着大报错信息猜。

第三,脚本里保留关键节点的形状输出。跑一步打印一次(行数, 列数),能很快发现哪一步多出了数据或者弄丢了数据。这招在长时间数据任务里尤其管用。

第四,Excel 导入的日期列经常是真正的日期格式,但 CSV 里可能是字符串。统一用pd.to_datetime处理时,遇到无法解析的格式,errors="coerce"会让它变成 NaN,然后你再回头去查原始数据,比一次次报错来得舒服。

我在实际项目里把上面这些函数串起来,通常一个数据处理脚本不会超过一百行,却能把几十个 Excel 手工操作替换成分钟级完成的任务。Pandas 的函数虽多,核心思路始终是“读、洗、变、聚”四步。把这四步里的高频函数用熟,遇到再复杂的业务表,也能拆成这几个动作的组合来解决。有一段时间我还特地把每个新需求对应到速查表上,发现绝大多数场景都能落到我列出的那些函数里。希望你也能在 HoRain云 或者自己本机环境里,把这几个组合跑一遍,踩坑之后才是自己的东西。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询