选择进阶:at、iat、get、query、where
本教程共 54 篇 · 第 21 篇 · 更新于 2026-08-11 · 约 8 分钟阅读
本节目标:学会用 at/iat 快速读写单个值、用 get 安全取值、用 query 写简洁条件、用 where/mask 做条件替换。
at 和 iat:单值快车道
loc 和 iloc 要处理的情况多,取单个值时有点”杀鸡用牛刀”。只取或改一个值时,用 at(按标签)和 iat(按位置):
import pandas as pd
import numpy as np
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五"],
"年龄": [25, 30, 28],
"城市": ["北京", "上海", "广州"],
}, index=["a", "b", "c"])
print(df.at["b", "年龄"]) # 30,标签 b 行的年龄
print(df.iat[1, 1]) # 30,第 1 行第 1 列
# 赋值
df.at["a", "年龄"] = 26
df.iat[2, 2] = "深圳"
print(df)
和 loc/iloc 的对应关系:at 是 loc 的标量版,iat 是 iloc 的标量版。底层实现上,at/iat 跳过了很多类型判断,所以在循环里反复取值时明显更快。
注意两个边界行为:
at给不存在的标签赋值,会新增一行,数据会变多,不是拼写错误的话要小心at读取不存在的标签会抛 KeyError,iat的位置越界会抛 IndexError,都不像切片那样静默容忍
可以自己感受一下差距:
import pandas as pd
import numpy as np
import time
df = pd.DataFrame(np.random.randn(1000, 10))
n = 10000
t0 = time.time()
for _ in range(n):
_ = df.iloc[0, 0]
t1 = time.time()
for _ in range(n):
_ = df.iat[0, 0]
t2 = time.time()
print("iloc 耗时:", round(t1 - t0, 4))
print("iat 耗时:", round(t2 - t1, 4))
每次跑出来的数字不一样,但 iat 通常更快。量级差异才说明问题。
Tip批量取多个值用 loc/iloc;只取一两个值用 at/iat。性能差异在几万次循环里才明显,日常小数据怎么写都行,但养成好习惯没坏处。
get:取不到也不报错
用 [] 或 loc 取不存在的标签会抛 KeyError。有些场景希望”取不到就返回默认值”,用 get:
import pandas as pd
import numpy as np
s = pd.Series([10, 20, 30], index=["a", "b", "c"])
print(s.get("a")) # 10
print(s.get("x")) # None,不报错
print(s.get("x", -1)) # -1,指定默认值
DataFrame 的 get 按列名取:
import pandas as pd
import numpy as np
df = pd.DataFrame({"姓名": ["张三", "李四"], "年龄": [25, 30]})
print(df.get("年龄")) # 一列 Series
print(df.get("不存在的列", "没有这列"))
配套的小技巧:想先检查列存不存在,用 Python 的 in:
import pandas as pd
import numpy as np
df = pd.DataFrame({"姓名": ["张三"], "年龄": [25]})
print("年龄" in df.columns) # True
print("城市" in df.columns) # False
in 负责判断,get 负责兜底,两个配合,代码很干净。
典型用法:代码要处理多份结构不完全一样的数据,某列可能存在也可能不存在。用 get 带上默认值,就不用先 if 判断”列在不在”了。
query:把条件写成字符串
条件筛选写多了,括号和 & 很烦人。query 允许把条件写成字符串,语法接近 SQL 和自然语言:
import pandas as pd
import numpy as np
df = pd.DataFrame({
"姓名": ["张三", "李四", "王五", "赵六"],
"年龄": [25, 30, 28, 35],
"城市": ["北京", "上海", "广州", "北京"],
"薪资": [12000, 15000, 11000, 18000],
})
print(df.query("年龄 > 26 and 城市 == '北京'"))
print(df.query("薪资 > 12000 or 年龄 < 28"))
字符串里直接用 and、or、not,不用再跟括号和位运算符搏斗。判断是否属于集合用 in:
import pandas as pd
import numpy as np
df = pd.DataFrame({"城市": ["北京", "上海", "广州", "北京"]})
print(df.query("城市 in ['北京', '上海']"))
外部变量用 @ 前缀引用:
import pandas as pd
import numpy as np
df = pd.DataFrame({"年龄": [25, 30, 28, 35], "薪资": [12000, 15000, 11000, 18000]})
min_salary = 12000
print(df.query("薪资 >= @min_salary"))
列名含空格或特殊字符时,用反引号包起来。比如列名叫”姓 名”或者”所在.城市”:
import pandas as pd
import numpy as np
df2 = pd.DataFrame({"姓 名": ["张三", "李四"], "年龄": [25, 30]})
print(df2.query("`姓 名` == '张三'"))
query 里还能直接引用索引,把行索引当普通列用:
import pandas as pd
import numpy as np
df3 = pd.DataFrame({"年龄": [25, 30, 28]}, index=["a", "b", "c"])
print(df3.query("index == 'b'")) # 取索引为 b 的行
Notequery 是”语法糖”,方便不等于更快。官方文档:
query配上numexpr引擎(pip install numexpr)时,在大表(约 10 万行以上)上可能比 Python 循环略快;小数据两者差别不大。
where:条件不满足就替换
布尔筛选会改变形状(行变少)。where 保持形状不变:满足条件的值保留,不满足的变成 NaN 或你指定的值:
import pandas as pd
import numpy as np
s = pd.Series([-2, 3, -1, 4])
print(s[s > 0]) # 布尔筛选:只剩 3 个值,形状变了
print(s.where(s > 0)) # 不满足的变成 NaN,形状不变
print(s.where(s > 0, 0)) # 不满足的变成 0
同一份数据对比着看,两者区别一目了然:筛选是”砍掉”,where 是”保留位置、替换内容”。
DataFrame 同理,逐格判断:
import pandas as pd
import numpy as np
df = pd.DataFrame({"A": [1, 5, 3], "B": [4, 2, 8]})
print(df.where(df > 3, 0)) # 大于 3 保留,其余变 0
记忆口诀:where(条件, 替换值) = “满足条件留着,不满足换成替换值”。
mask:where 的镜像操作
mask 和 where 逻辑正好相反:条件满足的才被替换:
import pandas as pd
import numpy as np
df = pd.DataFrame({"A": [1, 5, 3], "B": [4, 2, 8]})
print(df.mask(df > 3, 0)) # 大于 3 的变成 0
where 和 mask 都返回新对象,不改原数据。想保留修改结果,记得赋值给新变量。mask 的常见用法是把异常值先换成 NaN,再交给缺失值处理流程(如 §25 所述)。
where 的替换值不一定是标量,也可以是 Series,按位置逐格替换。比如”分数低于 60 的,用各自的平时分补上”这种逻辑,就可以把平时分 Series 传进去。
什么时候用哪个
- 只想留下满足条件的行:用布尔筛选
df[cond](如 §20 所述) - 想保留原形状、只替换值:用 where / mask
- 条件来自变量、要写进字符串:用 query
- 判断键是否存在:用 get
这几个工具是互补的,不是替代关系。
和 np.where 的关系
numpy 的三参数版 np.where(条件, 是, 否) 语义和 df.where 几乎一致,还能直接造新列:
import pandas as pd
import numpy as np
df = pd.DataFrame({"年龄": [25, 30, 28, 35], "薪资": [12000, 15000, 11000, 18000]})
df["级别"] = np.where(df["薪资"] >= 12000, "高", "低")
print(df)
条件不止两个分支时,用 np.select 更合适:
import pandas as pd
import numpy as np
df = pd.DataFrame({"薪资": [8000, 12000, 20000]})
conditions = [df["薪资"] >= 15000, df["薪资"] >= 10000]
choices = ["高", "中"]
df["档位"] = np.select(conditions, choices, default="低")
print(df)
本章工具速查
| 方法 | 用途 | 场景 |
|---|---|---|
| at / iat | 单值读写 | 循环里快速取值 |
| get | 安全取值 | 不确定键是否存在 |
| query | 字符串条件 | 条件复杂想写简洁 |
| where / mask | 保持形状的条件替换 | 把不满足条件的值换掉 |
选哪个工具,先问自己三个问题:取一个值还是多个值?键可能不存在吗?条件写起来麻不麻烦?答案清楚了,工具自然就选对了。
最后来个组合小例子,把本节的工具串一遍:
import pandas as pd
import numpy as np
df = pd.DataFrame({"姓名": ["张三", "李四", "王五"], "薪资": [8000, 12000, 20000]})
print(df.at[2, "姓名"]) # 单值快取
print(df.get("年龄", "没有年龄列")) # 安全取值
median_salary = df["薪资"].median() # 先算中间结果
print(df.query("薪资 >= @median_salary")) # 字符串条件 + 变量
注意最后一行:@ 后面跟变量名,query 会把变量先取出来再比较,这就是它灵活的地方。
最后提醒一句:这些方法读起来都差不多,用错也不报错,只是结果不符合预期。写完代码后顺手 print 一下结果,确认形状和内容符合预期,比什么都重要。