数据分析岗 Pandas 面试题 20 道(上):概念与读代码

写在前面

前两篇讲了报错,这篇换个场景:面试。

Pandas 面试有个特点------不考记忆,考理解 。没人会问你 read_csv 有哪些参数(能查),但一定会问「loc 和 iloc 有什么区别」「为什么会有 SettingWithCopyWarning」------因为这些问题能看出你是否真的想过数据是怎么存的。

这篇挑出 10 道高频题,每题三段:面试官想听什么 → 标准答案 → 能跑的代码。代码不是装饰,面试时如果能说「我现场验证一下」,说服力完全不一样。

答案建议背「结论 + 一个例子」,别背长篇大论。

先确认示例数据在手边:

python 复制代码
import os
import mkdata

if not os.path.exists("data/sales.csv"):
    mkdata.main()
print("示例数据就绪:", os.path.exists("data/sales.csv"))

这段会检查第 1 篇生成的 data/ 目录在不在,不在就自动补一次;已经有数据时只会打印一行 示例数据就绪: True,不影响后面的结果。

第 1 题:Series 和 DataFrame 的关系

想听什么:你是不是把它当成「Excel 的一列和一张表」在理解。

答:Series 是带索引的一维数组,DataFrame 是共享同一套行索引的多个 Series。取一列得到 Series,取多列得到 DataFrame。

python 复制代码
import pandas as pd

df = pd.DataFrame({"城市": ["杭州", "上海"], "金额": [149.7, 149.5]})
print("取一列:", type(df["城市"]).__name__)
print("取多列:", type(df[["城市"]]).__name__)
print("一列本身有索引:", df["城市"].index.tolist())
text 复制代码
取一列: Series
取多列: DataFrame
一列本身有索引: [0, 1]

第 2 题:loc 和 iloc 的区别

想听什么:区不区分「标签」和「位置」。

答 :loc 按标签(索引值、列名),iloc 按位置(第几行第几列)。切片时 loc 含右端点、iloc 不含,和 Python 列表一致。

python 复制代码
import pandas as pd

df = pd.DataFrame({"金额": [10, 20, 30, 40]}, index=[100, 200, 300, 400])
print("loc[200](标签):", df.loc[200, "金额"])
print("iloc[1](位置):", df.iloc[1, 0])
print("loc[100:300] 行数:", len(df.loc[100:300]), "(含右端点)")
print("iloc[0:2] 行数:", len(df.iloc[0:2]), "(不含)")
text 复制代码
loc[200](标签): 20
iloc[1](位置): 20
loc[100:300] 行数: 3 (含右端点)
iloc[0:2] 行数: 2 (不含)

加分点:索引不是 0...n-1 时,loc[0] 会报 KeyError,因为它找的是标签 0。

第 3 题:视图和副本怎么区分

想听什么 :知不知道 SettingWithCopyWarning 背后的机制。

答 :df[条件] 返回的可能是视图也可能是副本,pandas 无法保证,所以赋值时给警告。规范写法是用 loc 一次完成,或显式 .copy()。

python 复制代码
import pandas as pd

df = pd.DataFrame({"城市": ["杭州", "上海"], "金额": [1.0, None]})
sub = df[df["城市"] == "杭州"]        # 可能是视图
sub["金额"] = sub["金额"].fillna(0)    # 触发警告的经典写法
print("sub 已改:", sub["金额"].tolist())
print("原表未变:", df["金额"].tolist())
print("----")
df.loc[df["城市"] == "杭州", "金额"] = 0   # 推荐写法
print("loc 改完原表:", df["金额"].tolist())
text 复制代码
sub 已改: [1.0]
原表未变: [1.0, nan]
----
loc 改完原表: [0.0, nan]

第 4 题:缺失值怎么处理,NaN 和 None 有什么区别

想听什么 :有没有「按业务选策略」的意识,而不是只会 dropna。

答 :None 是 Python 对象,NaN 是浮点缺失值,pandas 里数值列的缺失统一是 NaN。处理分三步:先量化(isna().sum()、缺失率),再判断(关键字段 vs 可补字段),最后才是删或填。

python 复制代码
import numpy as np
import pandas as pd

df = pd.DataFrame({"金额": [100.0, np.nan, 300.0], "城市": ["杭州", None, "北京"]})
print("缺失统计:")
print(df.isna().sum().to_string())
print("缺失率:", (df.isna().mean() * 100).round(1).tolist())
print("----")
print("删:", len(df.dropna(subset=["金额"])), "行")
print("填中位数:", df["金额"].fillna(df["金额"].median()).tolist())
print("分类填未知:", df["城市"].fillna("未知").tolist())
text 复制代码
缺失统计:
金额    1
城市    1
缺失率: [33.3, 33.3]
----
删: 2 行
填中位数: [100.0, 200.0, 300.0]
分类填未知: ['杭州', '未知', '北京']

第 5 题:groupby 的原理

想听什么:知不知道「拆分---应用---合并」这三步。

答 :groupby 先按键把行拆成若干组,对每组应用函数,再把结果按组键合并。理解这三步,就能解释「为什么 agg 返回的索引是组键」「为什么 transform 返回等长结果」。

python 复制代码
import pandas as pd

df = pd.DataFrame({
    "城市": ["杭州", "杭州", "上海", "上海"],
    "金额": [100, 200, 300, 400],
})
print("agg(每组一行):")
print(df.groupby("城市")["金额"].sum().to_string())
print("----")
print("transform(贴回原表,等长):")
print(df.groupby("城市")["金额"].transform("sum").tolist())
print("----")
print("组内占比:", (df["金额"] / df.groupby("城市")["金额"].transform("sum")).round(3).tolist())
text 复制代码
agg(每组一行):
城市
上海    700
杭州    300
----
transform(贴回原表,等长):
[300, 300, 700, 700]
----
组内占比: [0.333, 0.667, 0.429, 0.571]

第 6 题:merge 的四种连接

想听什么:知不知道「行数为什么会变」。

答 :inner 只留两边都有的键;left 保留左表全部;right 保留右表全部;outer 两边都保留。行数变化来自「键重复」和「键缺失」,可以用 validate= 防翻倍、indicator=True 查匹配。

python 复制代码
import pandas as pd

left = pd.DataFrame({"id": [1, 2, 3], "金额": [10, 20, 30]})
right = pd.DataFrame({"id": [2, 3, 4], "城市": ["上海", "北京", "广州"]})
for how in ["inner", "left", "right", "outer"]:
    m = left.merge(right, on="id", how=how)
    print(f"{how:<6s} 行数 {len(m)},缺失城市 {m['城市'].isna().sum()}")
text 复制代码
inner  行数 2,缺失城市 0
left   行数 3,缺失城市 1
right  行数 3,缺失城市 0
outer  行数 4,缺失城市 1

第 7 题:apply、map、applymap 的区别

想听什么:知不知道该用哪个、以及性能差异。

答 :map 作用于 Series 的每个元素(常配字典查表);apply 作用于 Series 或 DataFrame 的行/列,能拿到整行;applymap 已在 pandas 2.1 废弃,DataFrame 逐元素用 df.map()。性能上向量化 > map > apply > iterrows。

python 复制代码
import pandas as pd

df = pd.DataFrame({"城市": ["杭州", "上海"], "金额": [100, 200]})
coef = {"杭州": 1.0, "上海": 1.05}
print("map 查表:", df["城市"].map(coef).tolist())
print("apply 按行:", df.apply(lambda r: r["金额"] * 2, axis=1).tolist())
print("DataFrame.map(替代 applymap):", df[["金额"]].map(lambda x: x + 1)["金额"].tolist())
text 复制代码
map 查表: [1.0, 1.05]
apply 按行: [200, 400]
DataFrame.map(替代 applymap): [101, 201]

第 8 题:怎么处理重复值

想听什么:区不区分「完全重复」和「业务主键重复」。

答 :duplicated() 默认比所有列,subset=[...] 可指定业务主键,keep 控制保留哪一条。真实场景里业务主键重复更常见------比如同一订单被录了两次但金额不同。

python 复制代码
import pandas as pd

df = pd.DataFrame({
    "订单号": ["A1", "A1", "A2"],
    "金额": [100, 100, 200],
})
print("完全重复行:", int(df.duplicated().sum()))
print("按订单号重复:", int(df.duplicated(subset=["订单号"]).sum()))
print("保留最后一条:", df.drop_duplicates(subset=["订单号"], keep="last").to_dict("records"))
text 复制代码
完全重复行: 1
按订单号重复: 1
保留最后一条: [{'订单号': 'A1', '金额': 100}, {'订单号': 'A2', '金额': 200}]

第 9 题:怎么把宽表转成长表,反过来呢

想听什么 :知不知道 melt / pivot_table(或 stack/unstack)这一对。

答 :长转宽用 pivot_table 或 set_index().unstack();宽转长用 melt。做透视报表用前者,画图或喂模型用后者。

python 复制代码
import pandas as pd

wide = pd.DataFrame({"城市": ["杭州", "上海"], "笔记本": [100, 200], "鼠标": [50, 60]})
print("宽表:")
print(wide.to_string(index=False))
long = wide.melt(id_vars="城市", var_name="产品", value_name="金额")
print("melt 之后:")
print(long.to_string(index=False))
print("----")
print("再 pivot 回去:")
print(long.pivot_table(index="城市", columns="产品", values="金额", aggfunc="sum").to_string())
text 复制代码
宽表:
城市  笔记本  鼠标
杭州  100  50
上海  200  60
melt 之后:
城市  产品  金额
杭州 笔记本 100
上海 笔记本 200
杭州  鼠标  50
上海  鼠标  60
----
再 pivot 回去:
产品  笔记本  鼠标
城市         
上海  200  60
杭州  100  50

第 10 题:数据量太大内存不够怎么办

想听什么 :有没有实战经验,而不是只会 read_csv。

答 :四个层次------读的时候只读需要的列(usecols)并指定类型(dtype);内存里降级(int8/float32)和文本转 category;实在装不下就分块(chunksize)或让数据库先聚合;长期方案换 Parquet 列式存储。

python 复制代码
import numpy as np
import pandas as pd

df = pd.DataFrame({
    "城市": np.random.choice(["杭州", "上海"], 1000),
    "金额": np.random.rand(1000).round(4),
})
before = df.memory_usage(deep=True).sum() / 1024
df["城市"] = df["城市"].astype("category")
df["金额"] = df["金额"].astype("float32")
after = df.memory_usage(deep=True).sum() / 1024
print(f"内存:{before:.1f} KB -> {after:.1f} KB")
print("类型:", df.dtypes.to_dict())
text 复制代码
内存:21.6 KB -> 5.0 KB
类型: {'城市': CategoricalDtype(categories=['上海', '杭州'], ordered=False, categories_dtype=str), '金额': dtype('float32')}

面试答题的三个技巧

  1. 先给结论,再给例子:面试官不要听定义复述,要听「所以什么情况下会踩坑」;
  2. 主动提边界 :比如答 loc/iloc 时补一句「索引非默认值时 loc[0] 会报错」,这是加分项;
  3. 不确定就现场验证 :print(df.head()) 跑一下,比硬背可信。

常见问题

Q1:被问到没准备过的 API 怎么办?

说清「我知道它大概是干什么的,具体参数我会查文档」,然后讲你会怎么查、怎么验证。承认不知道 + 展示检索能力,比胡编好得多。

Q2:面试官问「你平时怎么处理脏数据」,怎么答得有层次?

按「探查 → 量化 → 判断 → 处理 → 校验」五步答,每步给一个方法(info / isna().sum() / 缺失率阈值 / fillna 或 dropna / duplicated().sum())。有流程感比堆方法名强。

Q3:要不要背代码?

不用背完整代码,背方法名 + 关键参数 :merge(validate=)、to_numeric(errors="coerce")、transform("sum")、drop_duplicates(subset=)。这些是「会用」和「听说过」的分界线。

小结

这 10 道题的共同点是:都在问「为什么」,而不是「怎么用」。Series 与 DataFrame 的关系、loc/iloc、视图副本、缺失值策略、groupby 三步、merge 四种连接、apply 家族、重复值口径、宽长表互转、大数据处理------把这十个讲清楚,Pandas 的基本功就到位了。

下一篇是下半场:10 道手写题,包括每组 TopN、连续日期、宽长表转换、去重找重复,全部给可运行答案。

相关推荐
Dmz丶40 分钟前
前端转 AI 100 天 Day 37:Few-shot 与 CoT——让模型学会“推理”
python
孟健1 小时前
我用Grok Bot本地剪口播视频,3段提示词搞定气口切除、字幕与封面
人工智能·python·aigc
奥惟斯1 小时前
GEO 上线前,先把这 3 类实体信号地基补上
人工智能·深度学习·机器学习·自然语言处理·数据挖掘·数据分析·知识图谱
2601_966949652 小时前
如何封装 qd.klines.batch(),让批量 K 线查询函数在多个策略中复用?
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
神仙别闹3 小时前
基于Python实现英词语自动对齐系统
开发语言·python
老歌老听老掉牙3 小时前
圆柱端面四平面交线及其在yoz面投影的几何解析与 Python 实现
开发语言·python·平面
我要见SA姐14 小时前
Chrome 扩展安全攻防实录:从权限滥用到供应链投毒
运维·人工智能·python·机器学习·自动化
2601_962885724 小时前
给 backtrader 接 A 股数据:用 AlphaFeed 喂PandasData
python
YYYing.4 小时前
【Agent系列 (十一) 】上下文工程
人工智能·python·ai·agent·上下文工程