做数据分析这行,pandas大概是绕不开的第一座山。有人形容它是Python数据处理里的“瑞士军刀”,有人干脆说它就是内存里的Excel,这两种说法都有点道理,但都不够准确。pandas真正厉害的地方,不是某个函数多好用,而是它把“表格数据处理”这一整套流程——读取、清洗、转换、聚合、合并、输出——全部统一到了一个库里,让你能用同一套语法处理各种乱七八糟的真实数据。
这篇内容适合刚入门Python数据分析、或者已经在用Excel但想转向自动化处理的同学。我会从核心数据结构讲起,把读写、清洗、分组聚合、合并、时间序列、性能优化这些日常最高频的模块都过一遍,最后再给一个完整的实战案例和一份避坑清单。很多细节是我在实际项目里踩过坑才总结出来的,希望能帮你少走几个月的弯路。
1. pandas到底解决了什么问题——核心设计思路拆解
1.1 Series与DataFrame:一张表和一列数的差别
pandas最核心的概念就是两种数据结构。Series是一维带标签的数组,你可以把它理解成Excel里的一列数据,只不过它额外多了一个“索引”;DataFrame是二维表格,由多个Series组成,看起来就是一张完整的Excel工作表。
import pandas as pd # Series:一列数据,索引是行标签 s = pd.Series([10, 20, 30], index=["01-01", "01-02", "01-03"]) # DataFrame:二维表 df = pd.DataFrame({ "日期": ["01-01", "01-02", "01-03"], "销售额": [1200, 980, 1560], "区域": ["北区", "南区", "东区"] })很多人学pandas几个月还是觉得别扭,核心原因就是没理解“索引”这个概念。索引是行标签,它不一定是0、1、2这样的数字,也可以是日期、城市名、ID等等。pandas在做匹配、对齐、分组操作时,背后大量依赖索引。你可以把索引理解成表格第一列的“行名”,但它又不完全等价于普通数据列——它承担的是“定位”功能。实战里最常见的误区,是有人把某一列普通数据用set_index设成索引后,后用reset_index把它救回来,来回折腾,其实是没有想清楚这列到底该放在索引里还是当作普通字段。
1.2 pandas与Excel、SQL相比,优势在哪里
很多人一开始接触数据分析,会纠结一个问题:Excel那么好用,SQL又那么成熟,为什么还要学pandas?这个问题的答案,等你处理过几次“脏数据”之后就会很清晰。
| 工具 | 擅长场景 | 明显短板 |
|---|---|---|
| Excel | 交互操作、可视化、快速看数 | 行数稍大就卡、手动操作难复用、逻辑不可追溯 |
| SQL | 大规模存储与查询、并发、分布式 | 复杂清洗与科学计算能力弱,写出来很长 |
| pandas | 清洗、转换、探索性分析、算法预处理 | 单机内存有限,不适合几十亿行超大数据 |
我自己的经验是,三者不是替代关系,而是不同阶段用不同的工具。数据在数据库里做第一层过滤,常见的做法是先写SQL把需要的大范围数据拉出来;拉出来之后,各种清洗和判断逻辑用pandas处理是最舒服的,因为它的表达能力强,几行代码就能完成很多Excel里需要录宏或者手拖几步才能实现的操作;最终结果再导回Excel给业务同事看。这套流程一旦跑通,你想换参数重跑的时候,只需要重新执行一遍脚本,比在Excel里手动调整省太多事。
1.3 为什么pandas“慢”却依然是很多团队的默认选择
网上经常有人吐槽pandas慢,这个评价要分两层看。如果你拿它和Spark、ClickHouse这些分布式计算引擎比,那确实不是一个量级;但现实是,大多数业务场景的数据量也就是百万行以内,这个体量用pandas处理,耗时通常只在几秒到几十秒之间,完全在可接受范围内。更重要的是,pandas的开发效率极高——你写一个for循环可能要十几行,用pandas往往一行就搞定,省下来的开发时间比那几秒的运行时间值钱得多。
真正让pandas变慢的,往往是使用方式的问题。比如有人习惯在几千行的DataFrame里一层层嵌套for循环,那当然慢;换成向量化操作后,速度提升几十倍都不奇怪。关于性能优化的具体手段,我会在第6部分详细展开。总之,单机内存能装下的数据规模内,pandas就是那个“用飞机去买菜”的例子:你明明楼下就是超市,没必要非得开战斗机出门。
2. 数据读取与数据清洗的实操要点
2.1 read_csv里那些容易踩坑的参数
读取数据是每个pandas项目的起点,但read_csv这个函数真不是填一个路径那么简单。我见过太多人在这一步就翻车,尤其是从业务系统导出的CSV文件,格式千奇百怪。
df = pd.read_csv( "订单数据.csv", encoding="utf-8-sig", dtype={"订单金额": "float64", "数量": "int32"}, parse_dates=["下单时间"], usecols=["订单ID", "下单时间", "区域", "订单金额", "数量"], nrows=1000, )这里每个参数都有它的用途。encoding指定编码,中文CSV最常见的是“utf-8”和“gbk”,如果你的文件读出来全是乱码,大概率就是编码选错了;用“utf-8-sig”而不是“utf-8”,可以顺手处理掉表头前面的BOM标记,很多从Windows环境导出的文件都有这个问题。dtype参数容易被人忽略,它能在读取阶段就确定列类型,避免pandas把本来应该是数值的列读成object类型,后面还得再费劲转换。parse_dates可以直接把时间列解析成datetime类型,省得之后用pd.to_datetime再转一步。
usecols只读需要的列,既能省内存又能减少后续干扰;nrows=1000先试读前1000行,用来快速确认表头和数据格式是否正常,尤其是面对陌生文件时,这个习惯能帮你少踩很多坑。你可以在正式读全量之前,先用nrows读一小部分,打印出dtypes看一眼,确认每个字段的类型符合预期,再放开读全量。这比一次读完发现错了再返工要高效得多。
2.2 缺失值处理不该一删了之
拿到数据之后第一步是看缺失值,很多新手一看到NaN就直接dropna()删掉,这种做法很危险。删除永远是最简单的处理方式,但不一定是对业务理解最合理的处理方式。
# 统计缺失 df.isnull().sum() # 删除某些关键列缺失的行 df.dropna(subset=["订单ID", "销售额"], inplace=True) # 用各组中位数填充缺失销售额 df["销售额"] = df.groupby("区域")["销售额"].transform( lambda x: x.fillna(x.median()) )这里有个重要的经验判断:如果销售额这一列存在缺失,直接删行会损失该条订单的其他信息;用整列均值填充又容易被极端值带偏。更稳妥的方式是按区域分组,用各组的中位数来填充——比如北区和南区的客单价差距很大,用全局平均值就会让两组数据的特征都被“平均化”稀释。时间序列数据则更适合用前向填充,也就是fillna(method="ffill"),因为连续日期的价格往往更接近前一天的数值,而不是更接近全局平均值。
还有一点要提醒,dropna有一个subset参数,只在指定列存在缺失时才丢弃行。这个参数很好用,因为一张订单表可能有几十个字段,有些字段缺失完全不影响你的分析目标,不需要因为某个不重要的列有NaN就删掉整行。
2.3 字符串脏值和类型统一的处理
真实数据里最磨人的不是大计算量,而是各种不规整的字符串。金额前面带着人民币符号、数字中间夹着千分位逗号、日期格式五花八门,这些才是日常工作的常态。
df["金额文本"] = df["金额文本"].str.strip() df["金额文本"] = df["金额文本"].str.replace("¥", "") df["金额文本"] = df["金额文本"].str.replace(",", "") df["金额"] = pd.to_numeric(df["金额文本"], errors="coerce")这段代码做的事情很朴素:先把字符串两端的空格清掉,再把人民币符号和千分位逗号替换掉,最后统一转成数值。errors="coerce"这个参数非常关键——它能在转换失败时把值变成NaN而不是直接抛异常中断程序。比如某条记录里金额写了个“待定”,你用默认参数转换,脚本当场崩溃;用coerce之后,它会变成NaN,你后面再用缺失值处理逻辑统一兜底,程序能继续往下跑,最后在结果排查阶段再决定怎么处理这些脏数据。
另外注意区分df["金额文本"].str.replace和Python内置的str.replace方法,前者是pandas对Series的向量化字符串操作,后者是单个字符串对象的方法。很多新手在Series上直接调用原生方法,结果报AttributeError,其实就是这个区别。
3. 筛选、分组、聚合——日常分析最常用的三把刀
3.1 loc与iloc:一个按标签,一个按位置
筛选数据是pandas使用频率最高的操作,而loc和iloc的区分又是新手最容易懵的点。我用一句话总结:loc按索引标签筛,iloc按整数位置筛。
| 方法 | 依据 | 典型示例 | 说明 |
|---|---|---|---|
| loc | 行/列标签 | df.loc[df["区域"]=="北区", ["销售额"]] | 布尔条件筛选必须用loc |
| iloc | 整数位置 | df.iloc[0:3, 0:2] | 切片含头不含尾 |
# 按条件筛选行 + 按列名取列 north = df.loc[df["区域"] == "北区", ["订单ID", "销售额"]] # 按位置取前三行前两列 top_left = df.iloc[0:3, 0:2]为什么布尔条件筛选要用loc?因为df[df["销售额"] > 1000]这种写法虽然也可以用,但它在pandas内部走的是另一套“便捷索引”逻辑,一旦遇到链式赋值就可能触发SettingWithCopyWarning。更重要的是,loc能同时过滤行和筛选列,一个表达式把逻辑写完,读起来也清晰。还有一个细节:df[["销售额"]]取出来的是DataFrame,而df["销售额"]取出来的是Series,这两个在后续处理中行为有差异,很多bug就是因为没搞清楚二选一的区别。
3.2 groupby + agg:多维度统计的核心模式
如果说loc是pandas的“眼睛”,那groupby就是pandas的“拳头”。按组统计这种需求,在Excel里要用透视表,在SQL里要写GROUP BY,在pandas里就是一行groupby加一行agg。
result = df.groupby("区域").agg( 订单笔数=("订单ID", "nunique"), 销售总额=("销售额", "sum"), 客单价=("销售额", "mean") ).reset_index()这段代码用的是pandas新版本支持的“命名聚合”写法,返回的列名就是你在等号左边起的名字,非常直观。agg里可以给不同列分配不同的聚合方式:订单笔数用nunique去重统计,因为同一张订单可能有多条明细,直接count会把重复订单算进去;销售总额用sum;客单价本质上是平均单笔销售额,所以用mean。
这里要特别注意nunique和count的区别。count是非空值的个数,不关心重复;nunique是去重后非空值的个数。统计订单ID时,如果你想要“真实订单数”,几乎肯定要用nunique而不是count。还有一个高频操作是groupby之后要不要reset_index。默认groupby会把分组字段变成索引,如果你打算后续把这个结果和其他表merge,或者写入数据库,最好reset_index把它变回普通列;如果只是看一眼结果,保持索引形式反而更紧凑。
3.3 apply与transform:什么时候用哪个
apply和transform是pandas里最容易混淆的两个函数,但它们的设计目的完全不同。apply是“逐组计算,返回任意形状的结果”;transform是“逐组计算,但保持原始DataFrame的行数不变”。简单说,apply像是汇总,transform像是广播。
# 按区域标准化销售额 df["销售标准化"] = df.groupby("区域")["销售额"].transform( lambda x: (x - x.mean()) / x.std() )这里的逻辑是:对每个区域分别计算销售额的均值和标准差,然后把这一组内每个原始值都减去组均值、除以组标准差,最后回填到对应行。这个操作如果用apply做,你会得到每组只有一行计算结果,还得想方设法merge回原表;用transform一步就搞定,也不改变行数,写起来非常清爽。
这类操作的典型应用场景是特征工程:在做机器学习模型之前,要对不同分组的数据分别做标准化或者缺失值填充,transform就是为此设计的。在2.2节里我们用它填充组内中位数,本质上和标准化是同一个套路。所以我的建议是,凡是需要“按组计算一个值,然后填回每一行”的场景,先想transform,而不是apply。
4. 数据合并与时间序列——进阶必过的两道坎
4.1 merge与concat:两张表怎么拼
数据分析很少只处理一张表,订单表、商品表、用户表、区域表……日常操作就是把这些表拼起来。merge和concat是两种完全不同的“拼法”:merge是按公共字段匹配,类似SQL的JOIN;concat是物理上的拼接,要么上下堆行,要么左右堆列。
merged = pd.merge( order_df, product_df, on="商品ID", how="left" )这段代码按商品ID把订单表和商品表进行左连接,保留订单表的全部行,商品信息能匹配上的就填上,匹配不上的就是NaN。how参数里最常用的是left和inner,left适合以一张表为主表补全信息,inner适合只保留两边都能匹配上的记录。在实际业务中,左连接用的频率最高,因为你通常希望主表的行数不要因为连接而减少。
使用merge之前,最好先检查连接键是否有重复值。如果A表的某个键有多行,B表对应键也有多行,merge会产生笛卡尔积,行数会爆炸。写代码前可以用df["商品ID"].duplicated().sum()看看重复情况。再有就是列名对齐问题,两张表的公共列如果叫法不一致,需要在merge时分别指定left_on和right_on;合并后又会出现两个颜色相近的列名,用suffixes参数给它们加后缀,避免冲突。
concat的使用场景相对简单,核心就是上下堆叠。你从三个不同月份的文件里分别读入数据,结构完全一致,只要pd.concat([df1, df2, df3])就能拼成一张长表,远比一行一行append高效。它还有一个参数axis:axis=0是上下堆行,axis=1是左右堆列,默认是0,注意别搞反。
4.2 时间序列:从字符串到resample
时间序列处理是pandas的强项,但前提是先把时间列转成真正的datetime类型,而不是字符串。这一步不做,后面的排序、切片、按月汇总全部都是错的,而且你还很难发现错在哪里,因为字符串排序和日期排序的差异很隐蔽。
df["日期"] = pd.to_datetime(df["日期"]) df = df.set_index("日期") # 按月汇总销售额 monthly = df.resample("ME").agg({"销售额": "sum"}).reset_index()resample是pandas的时间聚合利器,它先把时间索引划分为多个时间段,然后再对每段做聚合。示例里的“ME”表示“Month End”,也就是以每个月最后一天作为分组边界。这里要特别注意版本问题:旧版本里常用“M”表示月末,但在新版本里“M”已经弃用并报警告,改成“ME”才是正道。如果搞不清自己项目用的什么版本,可以在requirements.txt里锁住版本,避免代码在别人环境下行为不一致。
有了月度汇总,算环比就顺理成章:
df["销售额环比"] = df["销售额"] / df["销售额"].shift(1) - 1shift(1)表示取前一行,也就是上一个周期的值。用当前值除以上一周期值再减1,就是环比增长率。这个思路简单但极其常用,无论是日报、周报还是月报,都离不开它。另一个常用的是diff(),它直接算前后两行的差值,适合看绝对变化量。
4.3 滚动窗口计算:rolling与expanding
滚动平均是时间序列里经常用到的平滑手段,比如某直播平台每天都有活跃用户数,单看某天可能起伏很大,看7天滚动平均才能看出来真实趋势。
df["活跃用户_7日均"] = df["活跃用户"].rolling(window=7, min_periods=3).mean()rolling(7)表示向前取7天窗口,包括当天在内的最近7天;min_periods=3表示如果窗口内有效数据不足3天,结果就是NaN。这个参数在你处理数据开头几天、窗口还没填满时特别有用,避免了前6天全是NaN的尴尬局面。滚动窗口默认是“右对齐”的,也就是当前日期的值是利用当前及之前的6天算出来的,这适合做趋势判断;如果你想做“未来7天均值”的某种探索,那就得调center参数或者自己手动shift,不要想当然。
expanding则是“扩展窗口”,从第一行开始一直累积到当前行,常用于计算累计平均值、累计总和。比如你判断销售额是否达到全年目标的某个节点,就可以用expanding().sum()算出截至每天的累计销售额。它和cumsum有点像,但更灵活,因为它能配合任意聚合函数。
5. 实战案例——一个完整的报表处理流程
5.1 场景描述:某电商平台的月度经营报表
纸上谈兵讲再多,不如完整跑一遍流程。我拿一个虚构的某电商平台订单数据来说事。假设有一张订单明细表,字段包括订单编号、下单日期、区域、商品类目、销售额、数量。需求是生成一份月度区域汇总报表,包含每个区域每个月的订单量、销售总额、客单价,同时输出一份按日销售额的趋势表,供上游可视化使用。
5.2 完整流程代码
import pandas as pd # 1. 读取原始数据 df = pd.read_csv( "订单明细.csv", encoding="utf-8-sig", parse_dates=["下单日期"], dtype={"销售额": "float64", "数量": "int32"}, ) # 2. 清洗 df = df.dropna(subset=["订单编号", "销售额"]) df = df.drop_duplicates(subset=["订单编号"]) df = df[df["销售额"] >= 0] # 剔除负金额异常记录 # 3. 新增月份字段 df["月份"] = df["下单日期"].dt.to_period("M").astype(str) # 4. 分组汇总:区域 + 月份 summary = df.groupby(["区域", "月份"]).agg( 订单量=("订单编号", "nunique"), 销售总额=("销售额", "sum"), 客单价=("销售额", "mean"), ).reset_index() # 5. 透视表:区域为行,月份为列 pivot = summary.pivot_table( index="区域", columns="月份", values="销售总额", aggfunc="sum", fill_value=0, ) # 6. 按日趋势 daily = df.groupby("下单日期").agg( 每日销售额=("销售额", "sum"), 每日订单量=("订单编号", "nunique"), ).reset_index() # 7. 输出Excel with pd.ExcelWriter("月度经营报表.xlsx") as writer: summary.to_excel(writer, sheet_name="汇总表", index=False) pivot.to_excel(writer, sheet_name="透视表") daily.to_excel(writer, sheet_name="每日趋势", index=False)这段代码覆盖了前面讲的大部分核心操作:读文件、清洗缺失值、去重、筛选异常、分组聚合、透视表、时间聚合、多sheet输出。步骤2里的顺序是刻意的:先删缺失行,再去重,再筛异常值,每一步都会改变行数,所以每执行完一步,都应该用df.shape确认行数变化是否符合预期。比如删除缺失值前是10万行,删完后变成9万8,多了200行NaN;drop_duplicates之后又变成9万5,说明有300个重复订单编号。
5.3 结果检查技巧:别急着把报表发出去
报表做完,最重要的不是写代码,而是核对数据。我自己吃过大亏之后养成了一个习惯:所有汇总结果,都必须从两个独立角度交叉验证一遍,才敢交付。
第一个检查是总数对齐。看汇总表的销售总额加总,是不是等于daily表的每日销售额加总?如果不等,说明分组逻辑或者去重逻辑有问题。第二个检查是订单量。这里用nunique统计订单编号,但如果你之前没做drop_duplicates,统计结果就会虚高;如果你拿这个数字跟业务同学核对,对方一查系统发现对不上,信任就直接打折扣。第三个检查是抽样验证。从汇总表里随机选一个区域和月份,回原数据里手动筛选看几笔,确保计算逻辑没有偏差。第四个检查是异常值。专门看一下销售额列是否存在极大值或负值,如果有,要判断是真实的大单还是录入错误。
这个过程听着繁琐,但真正做起来其实只要十几分钟,却能把报表出错的概率大幅降低。尤其在月底着急交付的时候,花这十几分钟绝对值得。
6. 性能优化与内存管理——数据量一大就得注意这些
6.1 用向量化思维替换for循环
数据量到百万行上下的时候,代码性能的差异就开始变得明显了。最常见的问题就是满屏for循环。pandas的底层是基于NumPy的向量化计算,它对一整个Series做运算时,循环发生在C语言层面,速度飞快;而如果你自己在Python层写for循环,每一行都要经过Python解释器,慢得不是一星半点。
# 不推荐:逐行循环计算 for i in range(len(df)): df.loc[i, "新列"] = df.loc[i, "销售额"] * 1.1 # 推荐:向量化一次搞定 df["新列"] = df["销售额"] * 1.1两段代码结果一致,但性能差距可能是几十倍。哪怕你只是给一个几万行的DataFrame加一列,用for循环也会明显感觉卡一下;用向量化则是瞬间完成。apply虽然比for循环快一些,但它本质上还是逐行调用Python函数,如果这段逻辑可以用向量化表达式替代,就别用apply。判断标准很简单:能直接对一列Series做的运算,就不要想着“一行一行来”。
6.2 dtype优化:让DataFrame瘦身一半
pandas的内存占用经常被忽略,直到你处理大文件时遇到MemoryError才追悔莫及。其实很多时候,在读取阶段用对dtype就能省下一半内存。
df["区域"] = df["区域"].astype("category") df["数量"] = df["数量"].astype("int32") df["销售额"] = df["销售额"].astype("float32")区域这种字符串列,去重后的值很少,非常适合转成category类型。category类型在底层只存储分类编码和分类映射表,而不是重复存一堆字符串,内存占用能大幅下降。数值列用int32、float32通常也够用,为什么非得默认的int64、float64?在大多数业务场景下,销售额的精度用float32完全符合要求。内存降下来了,数据读取和处理速度自然也会跟着提升,因为数据搬运量变小了。
还有一个容易被忽略的行为差异:category类型的列在groupby时,无序分类会按分类的自然顺序排序,而不是你直觉里的字母序或数字序。如果你希望按特定顺序,需要用pd.CategoricalDtype显式指定类别顺序,否则结果可能和你设想的不一致。这个坑比较隐蔽,遇到groupby结果排序不对劲的时候可以想一下是不是category类型在作祟。
6.3 大文件读取策略:不硬扛,分块处理
当单个文件大到快接近内存上限,read_csv就不好使了。这时候不是让你加内存条,而是换一种读取策略。
chunk_list = [] for chunk in pd.read_csv("超大文件.csv", chunksize=100000): # 对当前块做聚合 chunk_result = chunk.groupby("区域")["销售额"].sum() chunk_list.append(chunk_result) # 合并每块结果 result = pd.concat(chunk_list).groupby(level=0).sum()read_csv的chunksize参数会把文件按每块10万行的方式分批读取,每次只占一小部分内存。注意这里仍然是纯单机方案,它能解决“单文件稍大、超过几个G内存”的情况,但代价是你要自己处理分块和合并。如果文件已经大到几十个G这种级别,说实话就不该硬用pandas了,先考虑把数据导入数据库,用SQL做初步聚合,再把聚合后的结果拉出来用pandas做精细分析。方向选对,比所有技巧都重要。
7. 常见问题与排查技巧实录
7.1 高频报错速查表
我在教学和项目里汇总过一份pandas高频报错清单,大多数问题集中在下面这几类。
| 现象 | 常见原因 | 解决办法 |
|---|---|---|
| SettingWithCopyWarning | 对切片后的DataFrame做赋值的链式操作 | 用.loc定位后赋值,或提前.copy() |
| KeyError: "列名" | 列名不存在、拼写错误或含隐藏字符 | 先df.columns查看真实列名,用repr排查隐藏字符 |
| 不同类型比较出错 | 数值列被读成object,字符串和数字比较 | 用pd.to_numeric或astype统一类型 |
| 合并后行数暴涨 | 连接键存在重复值,发生笛卡尔积 | 先对连接键去重,或改用不同的关联方式 |
| MemoryError | 数据量超过内存承载 | 只读需要的列、分块读取,必要时换数据库 |
7.2 我在实际项目里的几条避坑心得
真正上手做项目之后,你会发现pandas报错本身并不可怕,可怕的是那些“不报错但结果不对”的坑。我有几条经验想分享。
第一,每次改变行数的操作之后都打印一下shape。删掉缺失值之后行数少了多少?去重之后又少了多少?这个习惯能帮你把很多潜在问题扼杀在摇篮里,不用等到最后出报表才发现总数对不上。第二,不要写一长串的链式调用。比如df.groupby(["A"]).agg(...).sort_values(...).reset_index()这种,看起来很高端,但一旦报错,你根本不知道是哪一环出了问题。拆成多行、用中间变量,排错成本低得多。第三,拿到任何外部文件,先用文本编辑器或read_csv(nrows=5)看一眼最原始的样子,再写处理逻辑。有些CSV分隔符根本不是逗号而是分号,有些表头自带空格或Unicode字符,这些都得在拿到数据的第一时间确认。
还有两个细节也很重要。一是浮点数判断,判断金额是否为0时绝对不要用x == 0,因为浮点数运算可能会得到0.000000001这种结果,要用abs(x) < 1e-6或者np.isclose。二是pandas版本问题,新版旧版API差异很大,有些代码今天能跑,换一个环境就崩,所以在项目里锁定版本号是个好习惯。
7.3 给新手的一个学习顺序建议
学pandas最忌讳上来就想背完所有API。坦白说我用了这么多年,也没记住全部函数。更高效的方法是用一个真实需求驱动学习:比如试着统计你自己一个月的支付宝账单、分析你所在班级的期末考试成绩、或者复现一份店铺经营日报。做项目的过程中你自然会发现需要用什么函数,遇到不会的再查文档、看报错,记忆会非常深刻。
跑通一个小项目之后,再回头补原理,比如索引对齐是怎么工作的、内存和性能如何取舍。这个“先实践、后原理”的顺序,比拿着一本厚书从头啃到尾要有效得多。等你有过两三个完整项目经验,绝大部分日常需求都不用查文档,手比脑子快,那才算真正上手了。
我个人在实际操作里还有个习惯:任何处理流程的第一步,永远先复制一份原始数据作为备份,代码里所有清洗操作都在副本上进行。这样即使中间逻辑写错了,也能随时回到最初状态重来。最后再分享一个小技巧:每做一步,就用to_pickle或to_csv把中间结果存一版,尤其是那种跑起来要几分钟的长流程,这样一旦后面出错,不用整个重跑,加载中间结果接着调试就行。这些小习惯看着不起眼,长期积累下来,效率差距就是这么一点一点拉开的。