首页 / Pandas 入门教程 / 选择进阶:at、iat、get、query、where

Pandas 入门教程

选择进阶:at、iat、get、query、where

本教程共 54 篇 · 第 21 篇 · 更新于 2026-08-11 · 约 8 分钟阅读

PandasPandas 入门教程atiatquerywhere

本节目标:学会用 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 的行
Note

query 是”语法糖”,方便不等于更快。官方文档: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 一下结果,确认形状和内容符合预期,比什么都重要。