
拿到一批电商订单数据时我第一反应不是急着写代码而是先想清楚一个问题这份数据到底讲了一个什么故事这不是矫情。做过几个数据分析项目之后你就会发现 Pandas 最核心的战斗力不在于“能跑多少行代码”而在于你能否用它把一团乱麻的数据捋成一条清晰的业务主线。从数据清洗到可视化每一步都是在回答“数据背后发生了什么”而不是“代码怎么写得漂亮”。这篇文章我就拿一份模拟的电商销售数据当例子完整走一遍 Pandas 数据分析全流程。不绕弯子直接讲实操怎么清洗脏数据、怎么处理缺失值和异常值、怎么做分组聚合找业务规律、最后怎么把结论变成图表。不管你是刚学 Python 数据分析的小白还是已经会点 Pandas 但没系统做过项目的读者这份流程都可以直接当模板抄作业。1. 整体设计先想清楚链路再动手指1.1 数据分析项目的标准五步法我习惯把任何一个 Pandas 数据分析项目拆成五步环境准备、数据读取、数据清洗、探索分析、可视化输出。这五步不是拍脑袋定的而是对应着“拿到数据之后你脑子里必须依次解决的五个问题”这份数据长什么样结构、字段、样本量、类型分别是啥数据是不是干净的有没有缺失、重复、异常、格式不一致数据里隐藏着哪些规律不同维度的对比、时间的走势、Top 排名是怎样的这些规律能不能量化比如“华东区域贡献了多少营收”“哪个品类的复购率最高”。结论怎么呈现给业务方图表选型、配色、标注都要为“讲清楚事实”服务。1.2 为什么选 Pandas 而不是 Excel 或 Spark我知道有人会问现在不是有大模型、有 Spark、有各种可视化大屏工具吗为什么还要折腾 Pandas我的回答是Pandas 是“数据分析手感”的起点也是绝大多数场景下效率最高的工具。Excel 适合小数据量的轻量探查但一碰上几万行、几十万行或者需要写逻辑分支清洗数据的时候Excel 公式会让你怀疑人生。Spark 确实能处理海量数据但它的分布式架构、资源调度、集群配置对大多数中小规模项目来说完全是杀鸡用牛刀。Pandas 正好卡在中间单机内存能装下的数据量几百万行以内它的处理速度、表达能力和生态完整性都是最优解。更关键的是Pandas 的 DataFrame 结构非常符合人的直觉。行是样本列是字段你用df[df[区域] 华东]这种接近自然语言的表达去筛选数据业务人员看了也能懂个七八分。这比写一段 Spark SQL 再解释一堆执行计划要友好太多。1.3 环境准备与数据模拟我用的是 Python 3.10 Pandas 2.0 Matplotlib 3.7 Seaborn 0.12。安装没啥好说的pip install pandas matplotlib seaborn一条命令搞定。如果下载慢国内源用清华的镜像pip install pandas matplotlib seaborn -i https://pypi.tuna.tsinghua.edu.cn/simple下面是我用来生成本文示例数据的代码。注意这个数据是我故意“埋了雷”的包含缺失值、重复记录、异常订单、前后不一致的日期格式、带空格和大小写不统一的类目名。这样后面每一步清洗操作都有用武之地import pandas as pd import numpy as np # 模拟一份2024年1-6月的电商订单数据 np.random.seed(42) rows 5000 dates pd.date_range(2024-01-01, 2024-06-30, freqh) sample_dates np.random.choice(dates, rows) data { 订单号: [fORD{i:05d} for i in range(1, rows 1)], 下单时间: sample_dates, 区域: np.random.choice([华东, 华南, 华北, 西南, 东北], rows), 渠道: np.random.choice([自有App, 天猫, 京东, 抖音], rows), 类目: np.random.choice([ 数码 , 服饰, 食品, 家居, 美妆, 图书, 生鲜, 运动], rows), 销量: np.random.randint(1, 10, rows), 单价: np.round(np.random.uniform(19.9, 2999, rows), 2), 客户ID: np.random.randint(10000, 99999, rows), } df pd.DataFrame(data) # 开始埋雷混入缺失值、重复值、异常值、脏格式 df.loc[100:103, 区域] None df.loc[250:252, 单价] [np.nan, np.nan, np.nan] df.loc[300, 订单号] ORD00001 # 重复单号 df.loc[400, 销量] 999 # 明显异常值 df.loc[500, 单价] 0.01 # 另一个异常值 df.loc[600, 客户ID] abc123 # 本应是数字 df.loc[700, 下单时间] 2024/01/05 10:30 # 日期格式混入 df.loc[701, 下单时间] 2024-01-05 # 日期格式混入 df.loc[702:704, 渠道] # 追加两条完全重复的行 df pd.concat([df, df.iloc[[1, 2]]], ignore_indexTrue)数据造好了保存在本地 CSV 里然后开始正式干活。整个过程我会带着你一步步看结果、做判断而不是只丢一段“标准答案”。2. 数据读取与第一眼探查别急着清洗2.1 读取文件与基础信息检查清洗数据的第一步其实是“看”不是“洗”。我拿到 CSV 文件后第一件事永远是用info()和head()建立整体感知df pd.read_csv(sales_data.csv, encodingutf-8) print(df.shape) # 查看行数和列数 print(df.info()) # 每列类型和非空计数 print(df.head(10)) # 前10行长啥样这一步看到的核心信息包括shape告诉你有多少行多少列初步判断数据规模是否在 Pandas 处理范围内。info()会显示每一列的数据类型dtype和非空值数量。非空值数量差异就是缺失值的第一信号。head()能让你直接“肉眼”扫一遍字段格式比如日期里有没有混着斜杠、文本里有没有多余空格。以我的模拟数据为例这时候就能发现区域列的非空数少于总行数单价列也有缺失客户ID列里有个abc123一看就不是纯数字。这些就是后续清洗的“待办清单”。2.2 描述统计用 describe 快速识别可疑值看完结构之后我会对数值列跑一次describe()print(df.describe(percentiles[.25, .5, .75]))describe()会输出每个数值字段的均值、标准差、最小值、四分位数和最大值。看这张表的核心技巧是对比 min/max 与四分位数的差距如果销量的最大值是 999而 75% 分位数只有 6那 999 大概率是录入错误。如果单价的最小值是 0.01而 25% 分位数是 19.9那 0.01 也基本可以判定为异常。这一步的意义在于让数据告诉你哪里“不对劲”而不是你猜哪里不对劲。很多新手会跳过 describe 直接写清洗规则结果漏掉那些分布边缘的脏数据。2.3 重复值检查的常规操作重复值要分两种场景看。一种是整行完全重复这多半是数据合并时造成的冗余另一种是业务主键重复比如文档里我故意造的ORD00001重复单号# 全行重复 print(df.duplicated().sum()) df df.drop_duplicates().reset_index(dropTrue) # 指定关键列重复 print(df[df.duplicated(订单号, keepFalse)].sort_values(订单号))在真实项目中主键是否允许重复要由业务规则决定。比如一笔订单拆成多个包裹那订单号重复但包裹号不同是正常情况。做 drop 之前先问一句这个字段在这个业务里真的应该唯一吗想清楚了再删别让数据清洗把真相也洗掉了。提示我在处理订单号重复时保留了第一次出现的行删掉了后面出现的重复记录。但实际操作中如果重复行的其他字段有差异建议把差异列出来人工判断而不是无脑keepfirst。3. 数据清洗每一步都要有业务理由3.1 数据类型转换从“看起来对”到“真正对”数据清洗里最高频的操作就是类型转换。Pandas 里最常见的三类转换是字符串转数值、字符串转日期、分类文本转 category 类型。我一个个说。第一类字符串转数值。客户ID 里混入了abc123直接用astype(int)会直接报错所以要用to_numeric加errorscoercedf[客户ID] pd.to_numeric(df[客户ID], errorscoerce)errorscoerce的意思是把无法转换的值变成 NaN而不是让程序崩溃。转完之后abc123就会变成 NaN后面统一处理缺失值。这里有一个非常重要的原则要记住转换失败并不可怕可怕的是转换失败后不知道哪些行出了问题。所以转换完最好立刻看一眼被转成 NaN 的行到底有多少、是谁bad_ids df[df[客户ID].isna()] print(bad_ids[[订单号, 客户ID]])第二类字符串转日期。这份数据里的日期格式有2024-01-05 10:30:00、2024/01/05 10:30、还有只有日期的2024-01-05必须统一df[下单时间] pd.to_datetime(df[下单时间])Pandas 的to_datetime相当聪明大部分常见格式都能自动识别。识别不了的会变成 NaN你可以手动指定格式df[下单时间] pd.to_datetime(df[下单时间], format%Y/%m/%d %H:%M)实际项目里我遇到过不少奇葩格式比如日期和时间用T连接ISO 8601 格式或者年份两位数的老数据。这时候format参数就是你最后的武器。第三类文本列转类型。区域、渠道这类取值有限的字段转成category类型可以显著减少内存占用逻辑上也是一种优化df[区域] df[区域].astype(category) df[渠道] df[渠道].astype(category)3.2 缺失值处理先分清类型再决定方法缺失值处理没有放之四海而皆准的规则核心是判断“数据为什么会缺失”。我通常分三类来处理。第一类因为数据合并或导入产生的缺失比如某个字段在整个时间段都没记录到。这类缺失如果比例很高比如超过 30%我干脆就删掉这个字段如果比例很低就直接删掉对应行对整体分析影响可以忽略。第二类字段本身有意义只是个别值漏填。比如单价缺失如果不影响整行其他字段的价值可以考虑用同类订单的中位数填充。为什么要用中位数而不是均值因为销售数据里大额订单会把均值拉高导致填充值失真。中位数更稳健median_price df[单价].median() df[单价] df[单价].fillna(median_price)第三类关键标识字段缺失。比如客户ID 是号码缺失了就等于失去了客户身份的线索硬填一个数字反而会制造假数据。这种情况我倾向于直接删除对应列或行而不是“编”一个值出来。df df.dropna(subset[客户ID])另外区域列的缺失可以用众数填充因为区域是一个分类变量用最高频的值补上是最合理的猜测mode_region df[区域].mode()[0] df[区域] df[区域].fillna(mode_region)注意fillna(methodffill)这种“前向填充”在时间序列里经常用但在普通订单数据里要慎用。它会把上一个订单的区域填到当前订单里如果不同订单的下单时间并不按区域归组那填出来的区域就是完全错误的业务含义。3.3 文本清洗空格、大小写与统一格式文本清洗是数据清洗里最容易被忽略、但实际脏数据最多的地方。我的模拟数据里类目列混入了 数码 这样的左右空格渠道列甚至有全空格的脏值这类问题在真实业务数据里太常见了。清洗套路基本是三步# 1. 去除字符串首尾空格 df[类目] df[类目].str.strip() # 2. 规整大小写比如统一为小写或大写 df[类目] df[类目].str.lower() # 3. 对于只有空格的脏值直接转为 NaN 再统一处理 df[渠道] df[渠道].str.strip().replace(, np.nan)顺便说一个真实项目的经验很多企业数据系统的字段值会带着不可见字符比如\t、\r、不间断空格你在 Excel 里根本看不出来但df[某个字段].unique()一打印就露馅。所以遇到文本字段unique()和value_counts()是你的好朋友print(df[类目].unique()) print(df[渠道].value_counts(dropnaFalse))3.4 异常值识别与处理策略异常值处理要格外小心因为你以为的异常值可能恰恰是最重要的业务信号。比如一个用户一次性买 999 件商品也许是团购订单也许是刷单也许是录入错误。我的策略永远是先查证、再处理。数值字段我用 IQR四分位距法则来判断异常值把低于Q1 - 1.5 * IQR或高于Q3 1.5 * IQR的数值视为可疑点。代码如下Q1 df[销量].quantile(0.25) Q3 df[销量].quantile(0.75) IQR Q3 - Q1 lower_bound Q1 - 1.5 * IQR upper_bound Q3 1.5 * IQR outliers df[(df[销量] lower_bound) | (df[销量] upper_bound)] print(outliers)我的模拟数据里销量 999 会妥妥落在异常范围里。但这个例子里我是刻意模造的所以直接删掉没问题df df[(df[销量] lower_bound) (df[销量] upper_bound)]单价同样是异常值高发区0.01 的单价在电商订单里常见于秒杀活动或补差价链接不一定是脏数据。真实项目里遇到这种情况我会先和业务方确认有没有“补差价”“秒杀”这类特殊场景确认之后保留或者打上标记而不是直接删。清洗数据的最高原则是宁可多问一句不要冤杀一个。到这里我的模拟数据基本洗干净了。我建议每完成一步清洗就df.shape看一眼把“清洗前后行数变化”记下来。这个变化量到时候写报告、复盘、交接给同事的时候都是重要参考。3.5 新增辅助列与特征构建清洗完之后为了让分析更顺手我通常会顺手构建几个辅助字段。日期字段拆分出月份、季度、星期几这在做业务周期分析时非常有用df[月份] df[下单时间].dt.to_period(M).astype(str) df[季度] df[下单时间].dt.quarter df[星期几] df[下单时间].dt.dayofweek # 0周一, 6周日 df[销售额] df[销量] * df[单价]我还习惯加一个“是否周末”的标志df[是否周末] df[星期几].isin([5, 6])这些字段看似简单但在后续分组聚合时能省一堆事。比如分析“周末 vs 工作日哪类商品卖得更好”直接按是否周末分组就行不需要再去处理时间序列。4. 探索性数据分析让规律从数据里“长出来”4.1 分组聚合从整体到维度的拆解数据洗干净后终于到了最激动人心的环节。我先按“区域”看销售大盘region_summary df.groupby(区域, observedTrue).agg( 总销售额(销售额, sum), 订单数(订单号, count), 平均单价(单价, mean) ).sort_values(总销售额, ascendingFalse) print(region_summary)这个聚合表能回答的问题非常多哪个区域贡献了最多的销售额哪个区域的客单价更高订单数和销售额是否匹配——比如某个区域订单很多但销售额不高那就说明这个区域的低价订单占比偏大可以考虑该区域消费力或者推广策略的问题。再按“渠道”看各个平台的贡献差异channel_summary df.groupby(渠道, observedTrue).agg( 总销售额(销售额, sum), 总销量(销量, sum), 订单数(订单号, count) ).sort_values(总销售额, ascendingFalse) print(channel_summary)做聚合的时候有几点要注意。多指标聚合用agg()加字典比多次groupby高效得多因为只遍历一遍数据。另外如果不想保留分组列可以加as_indexFalse或事后reset_index()否则分组字段会变成索引后续操作容易绕晕。4.2 时间维度分析发现趋势和周期规律月度趋势几乎是我在每个项目里必做的分析因为它能揭示业务的中期趋势——是增长、衰退还是围绕某个水平波动。代码如下monthly_trend df.groupby(月份).agg( 总销售额(销售额, sum), 订单数(订单号, count), 总销量(销量, sum) ) print(monthly_trend)只看数字你可能觉得不够直观但把这个表画成折线图“1 月冲高、2 月回落、3 月回升”的走势瞬间就清晰了。可视化不是花架子它是帮人“看见”数字的辅助工具。我还会用周几来分析用户的下单习惯weekday_summary df.groupby(星期几).agg( 总销售额(销售额, sum), 订单数(订单号, count) ) print(weekday_summary)这一步往往能揭示有意思的结论比如周一到周五的订单集中在晚间周末的订单分散全天。如果再叠加“渠道”维度你甚至能发现不同平台的用户活跃时段完全不同——这种洞察在广告投放和运营排期里价值极大。4.3 最受欢迎的品类与高频客户商品维度的分析我的标准动作是拉 Top 10 类目category_summary df.groupby(类目).agg( 总销售额(销售额, sum), 总销量(销量, sum), 订单数(订单号, count) ).sort_values(总销售额, ascendingFalse) print(category_summary.head(10))Top10 条形图是后续可视化的重头戏。这里我先手动扫一眼数字心里有个谱数码、美妆这类高客单品类销售额靠前而生鲜、图书这类低客单价品类订单量可能更高。这是经典的“销售额 vs 订单量”分化现象也是做商品策略时很有价值的参考。客户维度我来做一个简化版 RFM 分析。RFM 是 Recency最近一次购买间隔、Frequency购买频次、Monetary消费金额三个指标的缩写用于客户价值分层# 以2024-06-30作为观测日 obs_date pd.Timestamp(2024-06-30) rfm df.groupby(客户ID).agg( Recency(下单时间, lambda x: (obs_date - x.max()).days), Frequency(订单号, count), Monetary(销售额, sum) ) print(rfm.describe())这不是完整版 RFM 打分但已经足够我看出客户分布的大致结构。比如大部分客户只买过一次高频占比低少数客户贡献了很高金额典型的“长尾分布”。这种数据在业务上直接支撑“会员分层运营”的决策。4.4 相关性分析哪些字段在联动当数据里有多个数值字段时我会跑一个相关系数矩阵看在分析框架内哪些指标在联动变化corr df[[销量, 单价, 销售额, 客户ID]].corr() print(corr)正常情况下销量和单价是弱相关甚至负相关因为低价商品往往买得多销售额和销量强正相关这是数学恒等关系带来的必然结果。所以相关性分析的意义不在于发现那些显然成立的结论而在于找出意外的关联——比如某些品类的高单价反而带来高销量那说明该品类存在高品质溢价空间。5. 可视化把结论画成一眼能懂的图5.1 环境配置与中文字体问题用 Matplotlib 做中文可视化第一步永远是解决中文字体问题。不加处理的话图上的中文全变方块那是非常劝退的体验。import matplotlib.pyplot as plt import seaborn as sns plt.rcParams[font.sans-serif] [SimHei] # 使用黑体显示中文 plt.rcParams[axes.unicode_minus] False # 解决负号显示异常 sns.set_theme(stylewhitegrid)这里有个小坑如果你的系统里没有 SimHei 这个字体rcParams设置了也不生效。解决办法有两个一是换成系统已有的字体比如 macOS 的Arial Unicode MSLinux 的WenQuanYi Micro Hei二是用matplotlib.font_manager.FontProperties指定字体文件路径。判断当前可用字体的代码如下import matplotlib.font_manager as fm fonts [f.name for f in fm.fontManager.ttflist] print([f for f in fonts if Hei in f or Song in f or YaHei in f])5.2 折线图月度销售趋势清楚显示了业务走势是入门到进阶都要用的核心图表fig, ax plt.subplots(figsize(10, 5)) ax.plot(monthly_trend.index.astype(str), monthly_trend[总销售额], markero, linewidth2) ax.set_title(2024年上半年月度销售额走势) ax.set_xlabel(月份) ax.set_ylabel(总销售额) plt.xticks(rotation45) plt.tight_layout() plt.savefig(monthly_trend.png, dpi150, bbox_inchestight) plt.show()折线图的要点就一个字少。一条主线最多加一条对比线不要一个图上堆五个系列。彩色配色留给重点标注其他元素尽量用灰色。5.3 条形图Top 10 类目销售额条形图是我做“排行榜”类结论的首选。对于类目这种分类变量横向条形图比纵向条形图更好读因为类目名称本身就长横向摆放不会挤成一团top10 category_summary.head(10).sort_values(总销售额, ascendingTrue) fig, ax plt.subplots(figsize(10, 6)) ax.barh(top10.index, top10[总销售额], color#4C72B0) ax.set_title(Top 10 类目销售额排名) ax.set_xlabel(总销售额) plt.tight_layout() plt.savefig(category_top10.png, dpi150, bbox_inchestight) plt.show()注意我先sort_values(总销售额, ascendingTrue)做了升序这样画出来的条形图是从小到大排列最大的类目在最上面视觉上最舒服。如果你直接画Top 排名会倒过来看着很别扭。5.4 饼图与环形图渠道占比饼图曾被数据可视化社区批评过因为人眼对角度的判断不如对长度敏感。但我觉得渠道占比这种“部分与整体”的关系环形图Donut chart仍然很直观前提是分类别太多、差异足够大channel_share df.groupby(渠道, observedTrue)[销售额].sum().sort_values(ascendingFalse) colors [#55A868, #4C72B0, #C44E52, #8172B3] fig, ax plt.subplots(figsize(7, 7)) wedges, texts, autotexts ax.pie( channel_share.values, labelschannel_share.index, autopct%.1f%%, startangle90, counterclockFalse, colorscolors, wedgeprops{width: 0.4} ) ax.set_title(各渠道销售额占比) plt.tight_layout() plt.savefig(channel_share.png, dpi150, bbox_inchestight) plt.show()一个小技巧wedgeprops{width: 0.4}可以把饼图变成环形图。环形的中间空白区域还能额外放汇总指标比如总销售额信息密度更高。我在企业汇报时经常这么干。5.5 箱线图销量的分布形态箱线图是数据做“体检”的好帮手它能一眼看出分布形态、离散程度和异常值fig, ax plt.subplots(figsize(8, 5)) sns.boxplot(datadf, x类目, y销量, axax) ax.set_title(不同类目的销量分布) plt.xticks(rotation45) plt.tight_layout() plt.savefig(sales_boxplot.png, dpi150, bbox_inchestight) plt.show()如果某个类目的箱体特别长说明该类目的销量波动很大如果某些点远远超出 “胡须”范围就是前面 IQR 法则识别出来的异常值。箱线图的价值在于它不作任何假设地展示真实分布和describe()的百分位数正好互相印证。5.6 热力图相关性矩阵的可视化相关性矩阵光看数字不够直观用热力图把数字映射成颜色深浅就友好多了fig, ax plt.subplots(figsize(7, 6)) sns.heatmap(corr, annotTrue, fmt.2f, cmapRdBu_r, center0, squareTrue, linewidths0.5, cbar_kws{label: 相关系数}) ax.set_title(数值字段相关性热力图) plt.tight_layout() plt.savefig(corr_heatmap.png, dpi150, bbox_inchestight) plt.show()热力图的读法非常简单颜色越接近红色正相关越强越接近蓝色负相关越强。加上annotTrue后数字直接标注在格子里做汇报材料时观众不需要猜图例。5.7 可视化的三个经验教训我个人在可视化上踩过的坑值得拿出来说。第一图是用来讲故事的不是用来堆数据的。一张图上超过三个信息维度阅读者基本就懵了。与其在一张图里放大堆信息不如拆成两张简洁的图分开展示。第二保存图片务必带bbox_inchestight和dpi150。前者能把超出边界的标题和图例纳入保存范围后者保证放大投影时不糊。我见过太多人在 Jupyter 里看没问题一保存就缺胳膊少腿多半是忘了这两个参数。第三颜色别乱用。红配绿对色弱人群不友好彩虹色更是灾难。一套图里尽量用统一的配色方案比如 Seaborn 自带的配色就能达到不错的默认效果。你画图不是放飞艺术灵感而是要让信息第一眼清晰。6. 常见问题与排查技巧实录6.1 读 CSV 乱码问题真实业务里从数据库导出的 CSV 最常见的是两种编码UTF-8 和 GBK。如果你的pd.read_csv报错或出现乱码试试加encodinggbk或encodinggb18030。还有一个技巧只读前几行就能判断编码不用反复整表加载try: df pd.read_csv(sales_data.csv, encodingutf-8) except UnicodeDecodeError: df pd.read_csv(sales_data.csv, encodinggbk)6.2 SettingWithCopyWarning 警告这是我见过最讓新手困惑的 Pandas 警告之一。它的根源是“链式赋值”比如df[df[区域] 华东][单价] 99 # 这样会触发警告正确的做法是先用loc定位再赋值df.loc[df[区域] 华东, 单价] 99道理很简单链式赋值是先筛选出一个视图再对这个视图赋值Pandas 无法保证这个视图会同步回原 DataFrame。直接用loc能在原 DataFrame 上精确修改既安全又不产生警告。我工作时的习惯是只要看到这个警告就回到上一行代码改用loc绝不含糊。6.3 to_datetime 解析失败的排查思路pd.to_datetime一旦遇到无法解析的字符串要么抛异常要么在errorscoerce时生成 NaN。我排查的路径是先找出哪些行解析失败df[日期解析] pd.to_datetime(df[下单时间], errorscoerce) bad_dates df[df[日期解析].isna()] print(bad_dates[下单时间].unique())看到失败样本后手动看一眼它们的格式再决定是补齐格式规则还是单独处理。千万别直接dropna一删了之万一这些行是重要样本呢。6.4 grouping 分组后索引混乱问题很多新手 groupby 之后发现reset_index()忘记加了导致后续df[月份]直接报 KeyError。这是因为分组字段变成了索引。我提供两个习惯使用groupby([月份], as_indexFalse)从源头避免索引问题。使用.reset_index()显式把分组列拉回普通列。两个都行习惯哪个用哪个。但千万别混着来同一个项目里两种方式换来换去代码的可读性会迅速下降。6.5 大文件读不动怎么办有人问数据文件有几个 GPandas 直接读会内存爆炸怎么办我的经验是按需读取chunk_iter pd.read_csv(big_file.csv, chunksize500000) result pd.concat( (chunk.groupby(区域)[销售额].sum() for chunk in chunk_iter), ) print(result.groupby(level0).sum())代码里chunksize500000将文件分成一块一块读入内存每块单独做聚合最后把结果合并。这是一种“分治”思想能有效规避单机内存不足的问题。如果数据实在大到单机装不下那才考虑上 Spark 那一套重武器。7. 从数据清洗到可视化的完整项目复盘到这里一个完整的 Pandas 数据分析闭环就走完了。我再把这套流程压缩成一份可复用的执行清单方便你以后做项目时对照着来。第一阶段读与看。确认环境读取数据用head、info、describe、shape建立整体感知列出“待清洗问题清单”。第二阶段洗与转。按顺序处理数据类型、缺失值、重复值、异常值、文本格式。每一步都用代码明确记录处理理由和处理前后行数变化。第三阶段探与析。用groupbyagg做维度拆解用时间序列找趋势用 RFM 思路做客户分层用相关系数矩阵找关联。第四阶段画与讲。根据问题选图表类型走势用折线、排名用横向条形、占比用环形图、分布用箱线图、相关性用热力图。统一中文字体规范配色保存高分辨率图片。第五阶段查与补。用value_counts()核验分类字段用isna().sum()复查清洗效果必要时抽样人工核对。在实际做项目的过程中我对“清洗”这个环节的体会尤其深。很多新手觉得清洗就是跑几个fillna和dropna实际上不是的。数据清洗的最高境界是你清楚每一行数据为什么被删、为什么被填、为什么被改并且能给业务方讲出理由。比如“删除单价为 0.01 的订单因为这是补差价链接不代表真实商品交易”这一句话比一百行清洗代码都有说服力。最后再分享一个我自己的习惯每次做完数据分析项目我会把整个清洗流程整理成一页 Markdown 文档标题就叫“数据清洗记录”。里面写着原始数据多少行、清洗后多少行、删了什么、填了什么、为什么这么干。这样做的两个好处一是下次做类似项目直接套用二是业务方或同事质疑分析结果时我能一页纸讲清楚数据的来龙去脉。数据分析这份工作真正值钱的地方从来不是你会敲几行 Pandas 代码而是你能不能让数据在别人面前“说人话”。希望这篇文章能帮你少走几步弯路把这份手感尽快建立起来。