
做数据分析这些年我带过不少新人发现一个特别普遍的现象很多人学Pandas是从某个小例子开始的会读文件、会groupby、会画个折线图觉得自己已经上手了。结果真拿到一份业务数据当场就懵了——日期列有的是Excel序列号、有的是文本、还有的直接是空的金额字段里混着“12,345万”这种带单位的字符串分组后发现同一个公司重复出现三次但收入还不一样。这时候才发现所谓的“数据分析”80%的时间不是在建模画图而是在跟脏数据搏斗。这篇文章我就按一条完整的实战链路来写从拿到数据开始怎么读取、怎么快速掌握全貌、怎么做清洗和加工最后怎么用图表把结论讲清楚。整条链路都围绕一个具体场景展开——整理一份财务数据算指标画图看趋势。过程中我会把Pandas里那些文档没写透的细节、踩过的坑和排查思路一起交代清楚。如果你是刚入门想做点真事的人这篇文章能帮你把零散的API串成一条能跑通的流水线。1. 拿到数据先别急着画图整条分析链路的思路拆解1.1 为什么说数据清洗才是重头戏先纠正一个很多教程会误导人的地方网上那些漂亮的图表、炫酷的大屏看着很唬人但真正决定分析质量的是数据分析的前半段——数据清洗和数据治理。行业里有一句老话叫“先采集再清洗”采集只要保证拿到数据就算完成而清洗才是把“数据”变成“能用数据”的关键。举一个我真实踩过的例子。之前处理一份销售明细需要按季度对比营收。第一版脚本跑出来一季度营收竟然比二季度还高明显不对劲。排查到最后发现日期列里混着三种格式一部分是真正的日期类型一部分是字符串“2024/1/5”还有一小部分居然是用Excel序列号存的数字。直接按字符串排序1月、2月、10月、11月这种“字符串序”就会排在错误的位置。这就是典型的清洗不到位导致的结论错误。所以你在规划一个分析项目时心里要有数读数据、探查、清洗、加工、可视化是一条流水线清洗和加工往往占掉一半以上的工作量。这不是浪费时间而是给后续的图表和结论打地基。地基歪了房子再好看也不敢住。1.2 用一个财务数据案例串起整篇文章为了让整篇内容不空谈我选了一个贯穿全文的实操场景整理一份公司的财务利润表数据目标是计算几个核心指标营收、净利润、净利润率然后按季度画出营收和净利润的走势图。这个场景在真实工作中很常见。不管你是看上市公司财报还是分析自家公司的经营情况利润表都是绕不开的数据源。而且这类数据有个特点字段结构相对固定但数据质量参差不齐——有缺失、有异常、有空值很适合用来演示清洗的各个步骤。我做这件事的时候先用requests从一个数据接口拉取了利润表数据返回的是JSON转成DataFrame后结构大致如下每一行是一家公司在某个报告期的一组指标字段包括股票代码、报告期、营业收入、净利润、营业成本等还有几个字符串类型的列。关于requests抓接口的部分不是本文重点我们直接从“拿到了DataFrame之后”开始讲起。后面所有示例代码你都可以在Jupyter Notebook或PyCharm里直接跑。1.3 环境准备和装包避坑开始实操之前先说一个新手很容易卡住的环节Pandas到底怎么装。搜索“pycharm怎么安装pandas包”的人特别多其实根本不用在PyCharm的设置界面里折腾图形化安装直接在项目终端里执行一行命令就行。pip install pandas # 如果下载速度很慢可以用清华镜像 pip install pandas -i https://pypi.tuna.tsinghua.edu.cn/simple很多人在这一步遇到“安装失败”十有八九是Python版本太新而本地的pandas版本太旧。比如Python 3.13刚出来的时候很多第三方库的编译版还没跟上直接用pip装就会报红。这时候最简单的办法是先升级pippython -m pip install --upgrade pip然后重新执行安装命令。如果你做的是数据分析我更推荐直接装Anaconda或Miniconda用conda创建环境conda会自动帮你匹配对应Python版本下能够正常工作的pandas版本省掉一堆编译报错。跑下面的示例代码你还需要matplotlib同样用pip安装即可。装好之后在代码开头把常用的导入写了后面所有示例都基于这些导入import pandas as pd import numpy as np import matplotlib.pyplot as plt2. 数据读取与初步探查别急着清洗先看清数据长什么样2.1 read_excel和read_csv读取文件时最容易被忽视的三个参数拿到一份数据第一步永远是读取。Excel和CSV是最常见的两种格式。这里先说Excel因为很多业务数据都躺在Excel里。# 读取Excel文件注意sheet_name和dtype df pd.read_excel(财务数据.xlsx, sheet_name利润表, dtype{股票代码: str})sheet_name参数用来指定读取哪个工作表。不传的话默认读第一个sheet如果你不确定文件里有几个sheet可以传sheet_nameNone返回的是一个字典键是sheet名值是DataFramesheets pd.read_excel(财务数据.xlsx, sheet_nameNone) print(sheets.keys())dtype参数则用来强制指定某些列的读取类型。为什么一定要处理这个因为股票代码、身份证号这类字段在Excel里经常被当成数字处理读进来之后会变成int类型前导零全部丢失——比如“000001”读进来就变成了1。还有那种很长的ID如果不指定dtype会直接变成科学计数法显示精度还丢了联表的时候怎么都对不上。读取CSV文件的时候最经典的问题是编码。Windows上用Excel导出的CSV默认编码是gbk直接用pd.read_csv读会报UnicodeDecodeError。解决办法很简单df pd.read_csv(数据.csv, encodingutf-8-sig)utf-8-sig能自动去掉UTF-8 BOM头还能兼容多数中文内容。如果你的数据是GBK就换成encodinggbk。还有一列是日期建议读取时直接用parse_dates参数转好df pd.read_csv(数据.csv, parse_dates[报告期])这样报告期这一列读进来的时候就已经是datetime64[ns]类型了后面处理时间特征的效率会高很多。2.2 探查四件套head、tail、info、describe读取完成后先别急着清洗。你要先做“探查性分析”搞清楚这堆数据大概是什么情况。我每次拿到一个新DataFrame就固定用四个函数打底。# 1. 看前5行了解字段和值的大致长相 df.head() # 2. 看后5行避免文件尾部有汇总行或奇怪内容 df.tail() # 3. 看整体结构行数、列数、每列的非空数量、类型 df.info() # 4. 看数值型列的统计量均值、标准差、分位数 df.describe()df.info()的输出里最关键的是Non-Null Count列。拿一份数据我第一眼就是看这里哪列的非空数量比总行数少就说明哪列有缺失值。比如总行数1000行净利润列Non-Null Count是980那就说明有20个缺失值。df.describe()输出的是数值列的统计量重点关注min和max。如果营收的最小值是负数那大概率是异常值因为营业收入不太可能是负的。注意describe()默认只统计数值型列如果你发现某个明明是数值的列没出现在结果里那就要回头看看这一列是不是被读成了object类型。还有一个我习惯性用的组合print(df.shape) # 看行数和列数 print(df.columns.tolist()) # 把列名平铺打出来方便复制2.3 dtype陷阱object不一定是字符串这是Pandas里最容易踩的坑之一。df.dtypes会显示每列的类型很多人看到object就认为这一列是字符串真假不一定。object是Pandas的统一兜底类型它可能存的是字符串也可能是混杂类型——同一列里既有数字又有文本。举个典型例子df[报告期] pd.to_datetime(df[报告期], errorscoerce)errorscoerce的意思是遇到转不了的格式不报错而是变成NaT时间类型的缺失值。这是一个非常实用的参数后面处理缺失值的时候还会用到。转换完之后你再打印一下df[报告期].dtypes确认它已经变成了datetime64[ns]。另外很多人在处理数值列的时候会遇到“明明看起来是数字但mean()算了半天报错”的情况。那是因为这一列里混入了一个“-”或空字符串导致整列被读成object。解决办法就是先pd.to_numeric批量转df[营业收入] pd.to_numeric(df[营业收入], errorscoerce)转完原来那个“-”就变成NaN了这一列也就顺理成章变成了float类型。3. 数据清洗实战缺失值、重复值、异常值和文本标准化3.1 缺失值处理先判断缺失原因再决定填充策略数据清洗的顺序我建议固定下来先处理缺失值再处理重复值最后处理异常值。缺失值处理的核心不是“怎么填”而是“为什么缺失”。同样是缺失原因不同处理方式完全不同。先统计缺失情况df.isnull().sum()Pandas的isnull()是按单元格判断是否为缺失值sum()按列加总。这一步能快速告诉你每一列有多少缺失。缺失值处理的常用手段有这几种场景处理方法代码示例缺失行占比很小且样本足够直接删除df.dropna(subset[净利润])数值列少量缺失不希望减少样本用中位数或均值填充df[净利润].fillna(df[净利润].median())时间序列中某个点缺失用前后值衔接前向填充或后向填充df[营收].fillna(methodffill)类别列缺失填一个“未知”占位df[行业].fillna(未知)用中位数填充而不是均值是因为均值容易受异常值影响。比如一个部门10个人9个人月薪1万老板月薪100万均值会被拉高到10万以上但中位数还是1万更能代表一般水平。我自己的习惯是给缺失值处理加一个“审计”步骤处理之前打印一条缺失统计处理之后再打印一条确保处理逻辑没有把不该删的行删掉。print(处理前总行数:, len(df)) df df.dropna(subset[股票代码, 报告期]) print(处理前总行数:, len(df))这一步虽然简单但非常有用后面我会再提一次。任何时候你不知道你的清洗操作有没有弄丢数据就打印一下shape。3.2 重复值处理别无脑drop_duplicates重复值处理看起来简单但有一个关键问题哪几列重复才算真正重复# 默认所有列都一样才算重复 df.duplicated().sum() # 按关键列判断重复比如同一家公司同一个报告期 df.duplicated(subset[股票代码, 报告期]).sum()df.duplicated()返回的是一个布尔序列True代表重复False代表非重复。如果你只想看重复的那部分长什么样可以这样df[df.duplicated(subset[股票代码, 报告期], keepFalse)].sort_values(股票代码)加了keepFalse之后所有重复的行都会被标为True而不只是后面的行。删除重复行的时候keep参数决定了保留哪个# 保留第一次出现的行删除后续重复行 df df.drop_duplicates(subset[股票代码, 报告期], keepfirst)这里有一个重要的踩坑提醒duplicated()和drop_duplicates()默认keepfirst也就是说保留第一行删掉后面的。但在某些业务场景下最后一行才是最新的数据比如每天更新一次同一只股票出现多次最后一行是最新状态。这时候你需要keeplast。还有一种情况数据集里存在合法的重复——例如不同报表类型合并报表和母公司报表字段相同但口径不同。这种重复如果直接按全部列去重可能把有意义的行给删了。所以一定要想清楚“重复”的业务定义是什么。3.3 异常值识别用分位数和标准差双重校验异常值不是必须删除的但你必须能识别出来然后根据业务判断怎么处理。财务数据里最常见的异常值是营收为负、净利润比营收还大、毛利率超过100%或小于0。我常用的异常值识别方法有两种标准差法和四分位距法。标准差法适合数据近似正态分布的情况如果某个值与均值的偏差超过3个标准差就认为是异常值。mean df[净利润率].mean() std df[净利润率].std() df[净利润率].apply(lambda x: abs(x - mean) 3 * std)四分位距法IQR不要求数据是正态分布更稳健一些。它的思路是计算上四分位数Q3和下四分位数Q1超出[Q1 - 1.5×IQR, Q3 1.5×IQR]区间的值视为异常值。Q1 df[净利润率].quantile(0.25) Q3 df[净利润率].quantile(0.75) IQR Q3 - Q1 lower Q1 - 1.5 * IQR upper Q3 1.5 * IQR df[(df[净利润率] lower) | (df[净利润率] upper)]异常值识别出来之后处理方式要看业务背景如果是录入错误可以改成缺失值或删除如果是真实存在但异常的值比如某一年公司大亏损净利润率为-80%那就不能删删了反而失真。这里我强烈建议异常值不要直接覆盖原数据可以先加一列标记比如df[异常标记] True保留追溯的线索。数据清洗也要可审计这个习惯能帮你省掉很多将来“这数字哪来的”的麻烦。3.4 文本字段标准化从“12,345万”到干净数字文本清洗是很多人会忽略的部分。财务数据里常见的情况是金额字段带着单位比如“12,345.67万”“1.2亿”或者数字里混了逗号千分位符号。这种字段不能直接用pd.to_numeric转换得先做标准化。Pandas的str访问器是做文本批量操作的神器。所谓“访问器”就是字符串列后面可以跟.str然后就有一堆字符串方法可以用# 去掉字符串两侧空格 df[行业] df[行业].str.strip() # 去掉千分位逗号 df[营收] df[营收].str.replace(,, , regexFalse) # 把“万”替换成“*10000”前的准备工作 df[营收] df[营收].str.replace(万, e4, regexFalse) df[营收] pd.to_numeric(df[营收], errorscoerce)这里我用了科学计数法的技巧把“万”替换成“e4”然后pd.to_numeric会直接把它解析成数字。比如“12,345万”经过处理后变成“12.345e4”解析出来的数值是123450。这个技巧在处理“万”“亿”混杂的财务数据时非常实用。如果金额里既有“万”又有“亿”那就一次性处理def convert_amount(val): if isinstance(val, str): val val.replace(,, ) if 亿 in val: return float(val.replace(亿, )) * 1e8 elif 万 in val: return float(val.replace(万, )) * 1e4 else: return float(val) return val df[营收] df[营收].apply(convert_amount)4. 数据加工与特征构造从会用函数到会讲故事4.1 groupby分组聚合用agg一次算出多个口径数据清洗干净之后下一步就是加工。groupby是Pandas里最核心的聚合操作它的作用是把明细数据按照某个维度汇总成统计口径。比如我现在想知道每个行业、每个季度分别的营收总和、净利润均值、公司数量。df_grouped ( df.groupby([行业, 季度]) .agg( 营收合计(营业收入, sum), 净利润均值(净利润, mean), 公司数量(股票代码, count), ) .reset_index() )这里有一个容易踩的坑groupby之后分组字段会被当作索引而不是普通列。如果你不想要这种行为有两种处理方式一是在groupby的时候加as_indexFalse二是后面跟一个reset_index()。两种效果一样选一种用就行。agg函数非常灵活它可以接收一个字典键是新列名值是一个元组元组里第一个元素是原始列名第二个元素是聚合函数。这种写法比df.groupby(...).sum()灵活得多可以一次对不同的列做不同的统计。聚合函数除了sum、mean、count还常用median中位数、std标准差、min/max。有一个细节count()不会统计NaN值而size()会统计。如果你要统计有效业务记录数用count是合理的如果你要统计“这一组里总共有几行数据”得用size()。4.2 merge多表合并联表时最容易出问题的环节业务分析经常要合并多张表。比如利润表数据和公司基本信息表用“股票代码”关联。Pandas的pd.merge是这个场景的核心函数。df_merged pd.merge( df_profit, # 利润表 df_company, # 公司信息表 on股票代码, # 关联键 howleft, # 左连接保留左表全部行 suffixes(_利润表, _公司) # 重名列的后缀 )连接类型how有几种inner只保留两张表都匹配到的行left保留左表全部行right保留右表全部行outer保留所有行。日常分析中inner和left用得多选哪种取决于你要保留哪边的样本口径。用merge最容易出问题的是关联键的类型不一致。比如左表的股票代码是字符串“000001”右表的股票代码是整数1匹配的时候全部落空合并完之后发现数据行数少了一大截。排查方法很简单分别打印两边的dtype。print(df_profit[股票代码].dtype) print(df_company[股票代码].dtype)第二个容易出问题的是关联键有重复值。如果右表中一个股票代码出现多次而左表对应一行合并后会生成多行行数暴涨。这有时候是业务需要的但大多数时候是数据问题。为了避免踩坑可以在merge时加validate参数df_merged pd.merge( df_profit, df_company, on股票代码, howleft, validatemany_to_one, # 如果左表多行对右表一行校验通过右表有多行会报错 )validate支持one_to_one、one_to_many、many_to_one、many_to_many它不改变结果只会在不符合预期时抛异常。这个参数对排查脏数据特别有用建议merge时都加上。另一个隐藏在merge里的小坑是“列名冲突”。如果两张表里都有“营业收入”这个字段merge后会自动加上后缀变成“营业收入_利润表”和“营业收入_公司”。如果不指定suffixes默认是_x和_y读起来语义不够清晰。4.3 别只会apply向量化操作和性能对比Pandas里最“误导人”的写法是处理数据时第一反应写个applylambda。它确实方便但性能很差数据量一大就跑得很慢。其实大部分场景是可以用向量化操作解决的。所谓向量化就是直接对整列做运算Pandas底层会调用NumPy的C语言实现比Python循环快几十倍。举个例子我要计算净利润率# 低效的写法 df[净利润率] df.apply( lambda x: x[净利润] / x[营业收入] if x[营业收入] else None, axis1 ) # 高效的写法 df[净利润率] df[净利润] / df[营业收入]第二种写法完全不用循环Pandas会自动对每一行做除法。除数为0或缺失时结果会是inf或NaN可以用replace处理df[净利润率].replace([np.inf, -np.inf], np.nan, inplaceTrue)什么时候才真的需要apply当这一行的计算没法用向量化表达的时候比如要调一个自定义函数且函数的输入是多列。这时候apply是对的。但它不应该成为首选。还有一个常见的向量化需求是条件判断。np.where比applylambda快得多df[规模等级] np.where(df[营业收入] 100e8, 大型, 中小型)4.4 时间特征提取和季度趋势分析财务分析里时间维度是绕不开的。Pandas对时间类型的处理很强大前提是你的列真的是datetime类型。之前已经讲过用pd.to_datetime转换转换成功后就可以提取各种时间特征df[报告期] pd.to_datetime(df[报告期]) df[年份] df[报告期].dt.year df[月份] df[报告期].dt.month df[季度] df[报告期].dt.quarterdt是时间列专属的访问器类似于字符串列的.str。提取季度在财务分析里非常常见因为很多指标要按季度口径看。计算环比和上个季度比和同比和去年同期比也是财务分析的高频需求。用pct_change()可以算环比df_sorted df.sort_values(报告期) df_sorted[营收环比] df_sorted.groupby(公司名称)[营业收入].pct_change()注意顺序要先按时间排序再分组算环比否则算出来的是乱序的比值。这里的groupby是为了防止把不同公司的数据混在一起算环比——每家公司的上一个季度应该对应自己公司的上个季度。如果要把数据从季度明细汇总成半年报或年报可以用resampledf_yearly ( df.set_index(报告期) .groupby(公司名称)[营业收入] .resample(Y) .sum() )resample是按时间频率重新采样的功能Y表示按年汇总Q表示按季度汇总。它的逻辑是“先set_index为时间列再重新采样聚合”写起来稍微绕一点但功能很强大。5. 可视化一条链路快速出图再用Matplotlib精修细节5.1 Pandas内置的plot一行代码快速出图很多人不知道Pandas本身就封装了最简单的绘图功能底层调用的是Matplotlib。不需要单独学某个可视化库就可以快速看数据的形状。# 按季度汇总营收后画折线图 df_quarter ( df.groupby(季度)[营业收入] .sum() .reset_index() ) df_quarter.plot(x季度, y营业收入, kindline, figsize(10, 5))如果你的运行环境是Jupyter Notebook需要先执行一行魔法命令%matplotlib inline这行命令的作用是让图表直接显示在Notebook的输出区域而不是弹出新窗口。如果你在PyCharm里运行图表会默认弹出单独窗口。kind参数控制图表类型line折线图、bar柱状图、barh水平柱状图、hist直方图、box箱线图、scatter散点图、pie饼图。对应到Excel里最常用的十种图表Pandas的plot都能覆盖大部分基础场景。5.2 图表选型什么样的数据配什么样的图很多新手拿到数据就想画图但画出来之后别人根本看不懂他在讲什么。图表选型的本质是你要传达什么信息就用什么图形。你要表达什么推荐图表对应Pandas代码时间维度的变化趋势折线图df.plot(x报告期, y营收, kindline)类别之间的对比柱状图df.plot(x行业, y营收, kindbar)两个数值变量的关系散点图df.plot(x营收, y净利润, kindscatter)数据分布和异常值箱线图df.plot(y净利润率, kindbox)各类别占比饼图df.plot(y营收, kindpie, subplotsTrue)举一个具体例子同样是看“哪些行业赚钱”柱状图比折线图合适因为行业是离散类别没有连续的时间关系同样是看“营收和净利润有没有关系”散点图比柱状图合适因为要观察的是两个连续变量的相关性。选错图表信息的传达效率会低很多。我在实际做数据可视化时还会刻意控制图表的数量一个结论配一个图一页放不下重点就等于没有重点。宁可少画几张每张都讲清一件事。5.3 中文字体、坐标轴和图例的精修Pandas的plot函数画出来的图默认是Matplotlib风格的标题、轴标签、图例都需要手动补。这里有一个绕不开的坑中文乱码。Matplotlib默认字体不支持中文直接画图中文标签会显示成一个个小方框。解决办法是在画图之前设置中文字体plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, PingFang SC] plt.rcParams[axes.unicode_minus] False # 解决负号显示成方块的问题第一行设置中文字体第二行设置负号的显示。不同操作系统支持的字体不一样Windows上有SimHei黑体和Microsoft YaHei微软雅黑macOS上有PingFang SC。如果你用的环境都没有这些字体可以试试plt.rcParams[font.sans-serif] [Noto Sans CJK SC]。画完基础图之后建议不要停在那里把标题、轴标签、图例补全。方法很简单plot()函数会返回一个Axes对象你在它上面继续设置ax df_quarter.plot(x季度, y营业收入, kindline, figsize(10, 5)) ax.set_title(2024年各季度营业收入走势) ax.set_xlabel(季度) ax.set_ylabel(营业收入(元)) ax.grid(True, linestyle--, alpha0.6)细心的读者会发现df.plot()直接把x,y,kind,figsize这些都消化掉了剩下的标题、坐标轴标签通过ax再设置。这一层关系搞清楚了你就能在“Pandas快速出图”和“Matplotlib精细控制”之间自由切换。图例的默认位置有时会遮挡数据区域可以用ax.legend(locbest)自动选择位置。如果图例的标签不理想你可以在plot()里用label参数提前指定df_quarter.plot(x季度, y营业收入, kindline, label营业收入, figsize(10, 5))5.4 从静态图表到分析大屏轻量化落地思路现在很多公司喜欢做可视化大屏把关键指标实时展示在一张大屏上。很多人一听到大屏就想到用专门的工具或前端框架实际上数据分析和图表的基础工作用PandasMatplotlib完全可以搞定两者并不冲突。Pandas这边负责的是把数据清洗、加工、聚合做好然后生成一组高质量的图表。大屏工具那边负责的是把这些图表按照视觉方案排布到一块屏幕上。也就是说你在Pandas阶段画的每一张图都是给后续可视化方案提供“素材”和“结论”。即便不做大屏我建议每个分析项目结束时都沉淀两份东西一份是汇总表把关键指标用to_excel导出df_result.to_excel(分析结果.xlsx, indexFalse, sheet_name汇总)另一份是核心图表用fig.savefig保存成图片fig df_quarter.plot(x季度, y营业收入, kindline, figsize(10, 5)).get_figure() fig.savefig(营收走势图.png, dpi200, bbox_inchestight)bbox_inchestight的作用是裁剪掉图表周围多余的空白保存的图片更紧凑规范。这两个输出物后续无论是做汇报PPT还是给大屏做素材都能直接用。6. 常见问题与排查技巧实录6.1 SettingWithCopyWarningPandas最常见的警告到底怎么解决跑Pandas代码时经常会看到一行警告SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame。这个警告不是报错代码还能继续跑但它是一个非常危险的信号——你的赋值操作可能没有写进原DataFrame而是写进了一个临时副本。典型的触发场景是这样df_sub df[df[行业] 制造业] df_sub[新指标] df_sub[营业收入] * 0.1第二行代码会触发警告。原因是df_sub是通过布尔索引切片出来的它可能是原DataFrame的一个视图view也可能是副本copy。Pandas无法确定所以它会警告你“你这个操作可能不会生效。”正确的做法有两种。第一种使用.loc的方式在原始DataFrame上操作df.loc[df[行业] 制造业, 新指标] df[营业收入] * 0.1第二种如果你确实只想操作子集那就显式复制一份df_sub df[df[行业] 制造业].copy() df_sub[新指标] df_sub[营业收入] * 0.1.copy()的作用是明确告诉Pandas这是一份独立的副本后续怎么改都不影响原数据。加了.copy()之后警告通常会消失行为也变得可预期。6.2 inplaceTrue少用但必须知道它为什么有争议Pandas的很多方法都有inplace参数比如dropna(inplaceTrue)、fillna(inplaceTrue)。它的含义是“就地修改原DataFrame”不用重新赋值。看起来挺方便但我个人建议尽量少用。第一个原因是可读性。Pandas官方文档和社区都更推荐“链式赋值”风格df df.dropna()。这种写法逻辑清晰每一行都明确表示“我要把处理结果赋值给df”别人看代码时很容易跟上思路。inplaceTrue则需要追查每一行是否修改了原数据排查问题的时候很费劲。第二个原因是有些情况下inplaceTrue并不生效。比如对df.groupby()的结果、对某些链式表达式的中间结果使用inplaceTrue可能什么都没改。这就很容易造成隐性问题你以为数据已经清洗过了实际上原数据没有任何变化。所以我的建议是日常代码统一用df df.dropna()这种显式赋值的写法inplaceTrue只在两种情况下使用——一是在处理超大DataFrame、担心复制内存开销的时候二是你明确知道这个操作会把原数据替换成新数据且不影响别的对象时。6.3 大文件读不动chunksize和dtype优化内存读几个G的CSV文件卡死甚至内存溢出是很多数据分析新手遇到的第一道坎。处理这种大文件有两个思路。第一个思路是用nrows先抽样看一眼df_sample pd.read_csv(大文件.csv, nrows10000)先用一万行观察列结构和数据长相确认编码、列名、类型都没问题再决定要不要全量读取。第二个思路是分块读取。pd.read_csv支持chunksize参数读取时会把文件分成一块一块的迭代对象每次只读一块进内存chunk_list [] for chunk in pd.read_csv(大文件.csv, chunksize100000): # 对每一块做清洗 chunk chunk.dropna(subset[股票代码]) chunk_list.append(chunk) df pd.concat(chunk_list)这种方式适合“清洗逻辑可以逐块独立完成”的场景。如果你需要全表排序或者跨行计算分块就不好搞了这时候建议直接用数据库比如SQLite或Spark来处理而不是硬扛Pandas。还有一个减少内存的实用技巧把字符串列转成category类型对低基数列内存占用可以降一半以上df[行业] df[行业].astype(category)6.4 排查数据异常的科学顺序从类型到分布层层推进最后分享一个实战排查方法。每次遇到分析结果不对劲我都有固定的排查顺序。第一步查列类型df.dtypes排除类型混乱的问题。第二步查缺失df.isnull().sum()看是不是有该死的数据少了。第三步查唯一值分布df[营收].value_counts()看看有没有明显重复或异常值。第四步查统计量df[营收].describe()看min、max是否合理。第五步才是按业务维度分组定位是哪一层出了问题。这五步走下来90%的数据问题都能定位。大部分“分析结果很奇怪”的案例最后查出来的原因都不是算法问题而是数据问题——要么类型错了要么缺失值没处理要么重复值没去干净。结尾写了这么多最后想分享两个我个人的小习惯。第一个习惯是每做一步清洗都打印一次df.shape。清洗前多少行、去重后多少行、缺失值处理后多少行每一步的数据量变化都要心里有数。这个习惯帮我避免过无数次“数据怎么少了”的疑案。第二个习惯是把常用的清洗逻辑封装成函数形成自己的分析流水线。比如把“读Excel→规范列名→去重→填充缺失值→转时间格式→导出清洗结果”这些步骤写成一个clean_financial_data(file_path)函数下次换一批数据直接调用即可。这样做的好处不仅仅是省事更重要的是——清洗逻辑是固定的、可复现的、可审计的这比在Notebook里手动敲一遍要靠谱得多。数据清洗和可视化这条路本质上是在跟“混乱”打交道。Pandas只是一套工具真正决定你分析质量的是你面对脏数据时的思路和耐心。希望这篇实战笔记能帮你少踩几个坑也欢迎在评论区交流你遇到过的奇怪数据问题。