写在前面
这篇不是教程,是工具书。
写脚本时最耗时的往往不是想逻辑,而是「这个操作叫什么来着」:删除列是drop还是del?取前几行是head还是iloc[:5]?重命名列是rename还是直接赋columns?每次卡在这儿,搜一次要两分钟。
所以我把常见操作整理成 60 条,按使用频率分类。建议收藏 + 用 Ctrl+F 搜方法名,别从头读。
先确认示例数据在手边:
importosimportmkdataifnotos.path.exists("data/sales.csv"):mkdata.main()print("示例数据就绪:",os.path.exists("data/sales.csv"))这段会检查第 1 篇生成的data/目录在不在,不在就自动补一次;已经有数据时只会打印一行示例数据就绪: True,不影响后面的结果。
图 1:60 条方法按 8 类归位。查表时先定位类别,再在类别里找方法,比从头翻快得多。
一、读取与写出(8 条)
| # | 做什么 | 写法 |
|---|---|---|
| 1 | 读 CSV | pd.read_csv("f.csv", encoding="utf-8-sig") |
| 2 | 只读部分列 | pd.read_csv("f.csv", usecols=["城市", "金额"]) |
| 3 | 指定类型 | pd.read_csv("f.csv", dtype={"学号": "str"}) |
| 4 | 读 Excel 多 sheet | pd.read_excel("f.xlsx", sheet_name=None) |
| 5 | 读 JSON | pd.read_json("f.json")或lines=True |
| 6 | 读数据库 | pd.read_sql("SELECT ...", conn) |
| 7 | 写 CSV(防乱码) | df.to_csv("o.csv", index=False, encoding="utf-8-sig") |
| 8 | 写多 sheet Excel | with pd.ExcelWriter("o.xlsx") as w: df.to_excel(w, sheet_name="明细", index=False) |
二、查看与探查(8 条)
| # | 做什么 | 写法 |
|---|---|---|
| 9 | 前几行 / 后几行 | df.head(5)/df.tail(5) |
| 10 | 形状 | df.shape |
| 11 | 列名 | df.columns.tolist() |
| 12 | 类型 | df.dtypes/df.info() |
| 13 | 数值统计 | df.describe() |
| 14 | 缺失统计 | df.isna().sum() |
| 15 | 缺失率 | df.isna().mean().round(3) |
| 16 | 重复行数 | df.duplicated().sum() |
三、选择与筛选(10 条)
| # | 做什么 | 写法 |
|---|---|---|
| 17 | 取一列 | df["金额"] |
| 18 | 取多列 | df[["城市", "金额"]] |
| 19 | 按位置取行 | df.iloc[0:5] |
| 20 | 按标签取行 | df.loc[0] |
| 21 | 按条件筛行 | df[df["金额"] > 100] |
| 22 | 多条件 | df[(df["城市"] == "杭州") & (df["金额"] > 100)] |
| 23 | 取反 | df[~(df["城市"] == "杭州")] |
| 24 | 名单筛选 | df[df["城市"].isin(["北京", "广州"])] |
| 25 | 区间筛选 | df[df["数量"].between(3, 5)] |
| 26 | 字符串包含 | df[df["产品"].str.contains("笔记本", na=False)] |
四、清洗(12 条)
| # | 做什么 | 写法 |
|---|---|---|
| 27 | 删缺失行 | df.dropna(subset=["金额"]) |
| 28 | 填固定值 | df["城市"].fillna("未知") |
| 29 | 填均值/中位数 | df["金额"].fillna(df["金额"].median()) |
| 30 | 分组填充 | df["金额"].fillna(df.groupby("城市")["金额"].transform("median")) |
| 31 | 前后向填充 | df["金额"].ffill()/.bfill() |
| 32 | 去重 | df.drop_duplicates() |
| 33 | 按组合键去重 | df.drop_duplicates(subset=["订单日期", "城市"]) |
| 34 | 类型转换 | df["数量"].astype("int8") |
| 35 | 安全转数值 | pd.to_numeric(df["金额"], errors="coerce") |
| 36 | 转日期 | pd.to_datetime(df["订单日期"], format="%Y-%m-%d") |
| 37 | 去首尾空格 | df.columns = df.columns.str.strip() |
| 38 | 重命名列 | df.rename(columns={"金额": "amount"}) |
五、变形(10 条)
| # | 做什么 | 写法 |
|---|---|---|
| 39 | 排序 | df.sort_values("金额", ascending=False) |
| 40 | 多列排序 | df.sort_values(["城市", "金额"], ascending=[True, False]) |
| 41 | 重置索引 | df.reset_index(drop=True) |
| 42 | 设索引 | df.set_index(["城市", "产品"]) |
| 43 | 长表转宽表 | df.set_index([...])["金额"].unstack(fill_value=0) |
| 44 | 宽表转长表 | df.melt(id_vars=["城市"], var_name="指标", value_name="值") |
| 45 | 新增列(计算) | df["金额"] = (df["数量"] * df["单价"]).round(2) |
| 46 | 条件新增列 | np.where(cond, "是", "否")/np.select(...) |
| 47 | 分箱 | pd.cut(df["金额"], bins=[0, 100, 1000], labels=["小", "大"]) |
| 48 | 横向拼接 | pd.concat([df1, df2], axis=1) |
六、聚合与合并(8 条)
| # | 做什么 | 写法 |
|---|---|---|
| 49 | 分组求和 | df.groupby("城市")["金额"].sum() |
| 50 | 多指标聚合 | df.groupby("城市").agg(订单数=("金额", "size"), 销售额=("金额", "sum")) |
| 51 | 组内占比 | df["金额"] / df.groupby("城市")["金额"].transform("sum") |
| 52 | 每组前 N 行 | df.sort_values("金额", ascending=False).groupby("城市").head(3) |
| 53 | 交叉表 | pd.crosstab(df["城市"], df["产品"]) |
| 54 | 透视表 | pd.pivot_table(df, index="城市", columns="产品", values="金额", aggfunc="sum") |
| 55 | 内连接 | df1.merge(df2, on="客户ID", how="left", validate="many_to_one") |
| 56 | 拼接多表 | pd.concat([df1, df2], ignore_index=True) |
七、时间序列(4 条)
| # | 做什么 | 写法 |
|---|---|---|
| 57 | 取年月日 | df["日期"].dt.year/.month/.day |
| 58 | 取星期/季度 | df["日期"].dt.dayofweek/.quarter |
| 59 | 按月汇总 | df.resample("ME").sum()(日频索引上) |
| 60 | 滚动平均 | df["收盘价"].rolling(5).mean() |
把最高频的 10 条跑一遍
光看表记不住,跑一遍才有印象。下面把「读 → 查 → 筛 → 补 → 分组 → 排序 → 导出」串成一条链:
importpandasaspd# 1 读df=pd.read_csv("data/sales.csv",encoding="utf-8-sig",usecols=["订单日期","城市","产品","数量","单价","金额"])# 2 查形状与缺失print("形状:",df.shape,"| 缺失:",int(df.isna().sum().sum()))# 3 按条件筛big=df[df["数量"].between(3,5)]print("数量 3-5 的行数:",len(big))# 4 补缺失(按公式重算,别填 0)df["金额"]=df["金额"].fillna((df["数量"]*df["单价"]).round(2))# 5 去重before=len(df)df=df.drop_duplicates()print("去重:",before,"->",len(df))# 6 分组聚合by_city=df.groupby("城市",dropna=False).agg(订单数=("金额","size"),销售额=("金额","sum")).round(2)print(by_city.sort_values("销售额",ascending=False).to_string())# 7 排序 + 取前 3print("---- 销售额 Top3 ----")print(by_city.sort_values("销售额",ascending=False).head(3).to_string())# 8 导出importos os.makedirs("output",exist_ok=True)by_city.reset_index().to_csv("output/quickref_city.csv",index=False,encoding="utf-8-sig")print("已导出 output/quickref_city.csv")形状: (16, 6) | 缺失: 3 数量 3-5 的行数: 8 去重: 16 -> 13 订单数 销售额 城市 杭州 5 2805.1 北京 3 1425.9 上海 2 772.5 广州 2 535.4 NaN 1 149.7 ---- 销售额 Top3 ---- 订单数 销售额 城市 杭州 5 2805.1 北京 3 1425.9 上海 2 772.5 已导出 output/quickref_city.csvimportosimportnumpyasnpimportpandasaspd os.makedirs("output",exist_ok=True)df=pd.read_csv("data/sales.csv",encoding="utf-8-sig")# 9 条件新增列(比 apply 快)df["是否大单"]=np.where(df["金额"].fillna(0)>500,"大单","普通")# 10 交叉表 + 透视print("城市 × 产品 订单数:")print(pd.crosstab(df["城市"],df["产品"]).to_string())print("----")pivot=pd.pivot_table(df,index="城市",columns="产品",values="金额",aggfunc="sum",fill_value=0)print("透视表(前两列):")print(pivot.iloc[:,:2].round(1).to_string())print("----")print("是否大单分布:")print(df["是否大单"].value_counts().to_string())城市 × 产品 订单数: 产品 A-笔记本 B-鼠标 C-键盘 D-显示器 城市 上海 0 2 2 0 北京 1 0 1 1 广州 0 2 1 0 杭州 2 1 0 2 ---- 透视表(前两列): 产品 A-笔记本 B-鼠标 城市 上海 0.0 299.0 北京 49.9 0.0 广州 0.0 179.4 杭州 349.3 59.8 ---- 是否大单分布: 是否大单 普通 13 大单 3按问题找方法:10 个高频场景
表格按功能分类,但人找东西是按「我想干什么」。这 10 条是问得最多的:
| 我想…… | 用这条 |
|---|---|
| 删掉某几列 | df.drop(columns=["a", "b"]) |
| 只保留某几列 | df[["a", "b", "c"]] |
| 把两列合并成一列 | df["a"].astype(str) + "-" + df["b"].astype(str) |
| 拆开一列成多列 | df["地址"].str.split("-", expand=True) |
| 查某列有哪些值 | df["城市"].value_counts()/.unique() |
| 找出重复的行 | df[df.duplicated(keep=False)] |
| 按某列排序后重编号 | df.sort_values("金额").reset_index(drop=True) |
| 计算每行占组内比例 | df["金额"] / df.groupby("城市")["金额"].transform("sum") |
| 找每组最大值那行 | df.loc[df.groupby("城市")["金额"].idxmax()] |
| 把宽表转回长表 | df.melt(id_vars=["城市"], var_name="指标", value_name="值") |
参数速查:最容易忘的几个
| 场景 | 参数 | 说明 |
|---|---|---|
| CSV 中文乱码 | encoding="utf-8-sig" | 读、写都用它 |
| 列名带 BOM | 同上 | 第一列名会带\ufeff |
| 导出不带行号 | index=False | 90% 的导出都要加 |
| 排序时空值位置 | na_position="first" | 默认在最后 |
| 合并防翻倍 | validate="many_to_one" | 键重复直接报错 |
| 分组保留空键 | dropna=False | 默认会丢掉 |
| 剪裁复制警告 | df.loc[cond, "列"] = 值 | 别先切片再赋值 |
| 打印完整表 | df.to_string(index=False) | 或用pd.set_option |
常见问题
Q1:为什么df.groupby("城市")["金额"].sum()和df.groupby("城市").sum()["金额"]结果不一样?
前者只对金额求和;后者对所有数值列求和后再取金额列。如果表里还有「数量」「单价」,后者的中间结果会多算几列,慢且容易误用。先选列再聚合是更清晰的写法。
Q2:melt和pivot_table是什么关系?
互为逆操作:pivot_table把长表变宽表(内层索引变列),melt把宽表变长表(列名变一列值)。画图前通常要melt,做报表汇总通常要pivot_table。
Q3:速查表里的方法记不住怎么办?
不用记全。真正每天用的是 15 个左右:read_csv、head、info、isna、dropna、fillna、astype、groupby、agg、merge、sort_values、to_excel、loc、iloc、query。其余的查表就行——查表两分钟,背下来不值。
小结
这份速查表按「读取 → 查看 → 选择 → 清洗 → 变形 → 聚合 → 时间 → 导出」排,基本覆盖了日常 90% 的操作。建议用法:收藏,用的时候 Ctrl+F 搜关键词(比如搜「去重」「透视」),把常用的十几条练成肌肉记忆,剩下的交给搜索。
下一篇换个角度:如果去面试数据分析岗,这些方法会以什么形式被考?20 道高频题,从概念到手写。