跳到正文
本地阅读 · 索引、筛选与赋值

索引、筛选与赋值

计算机基础 · pandas

前置知识:表结构与数据读写

本章目录

本章学习目标

  • 区分标签定位和位置定位
  • 组合布尔条件并安全赋值
  • 理解 Series 算术的标签对齐

标签坐标和位置坐标

loc 使用索引或列标签,iloc 使用从零开始的位置。若索引恰好也是从零开始的整数,两种选择可能暂时给出相同结果,但排序以后这个巧合就消失了。loc[2] 查找标签二,不是第三行;iloc[2] 永远取当前排序的第三行。标签切片通常包含终点,位置切片与 Python 一样不含终点。

将 order_id 设置为索引可以方便按订单查找,但应先检查唯一性,防止 loc 返回多行却被当成单行处理。复合索引用于股票和日期面板时,每个层级都影响选择;初学时先保留普通键列和明确过滤,等能够解释结果再引入 MultiIndex。丢失索引标签时,reset_index 只是改变表示,不会修复实体关系。

布尔筛选与条件缺失

多条件筛选用括号包围每个条件,再使用 &、|、~。Python 的 and/or 面向单个真值,不能直接判断一整列。筛选已支付订单应使用 status.isin(['paid','refunded']),因为退款订单已经发生支付,净额再减退款;只选择 paid 会丢失退款流程。筛选金额和日期时确保列已转换成正确类型,字符串比较并不等价于数值和日期比较。

可空布尔条件可能包含缺失。应明确缺失是否应被排除或进入待核对集合,例如先 mask.fillna(False) 并另外报告缺失数。筛选不会重新编号索引,这对回填结果很重要;不要随意 reset_index 后再把一列赋给原表,否则标签变化可能导致赋值错位。

小数据:标签自动对齐

Series a 的索引依次是用户一、用户二,值为十、二十;Series b 的索引依次是用户二、用户一,值为一、二。a+b 的结果是用户一十二、用户二二十一,因为 pandas 按标签匹配,并没有把第一行和第一行相加。换成 NumPy 数组以后,位置相加才得到十一、二十二。两种算法都有用途,区别在于业务对应关系由标签还是位置表达。

若标签只存在于一侧,普通相加会得到缺失。add(fill_value=0) 可表达缺少一侧时用零,但两侧都缺失仍应缺失。标签重复时算术可能扩大结果,分析时要检查索引唯一。网页演示是固定两用户数据,先交换右侧顺序观察自动对齐,再解释为什么改动位置不改变用户合计。

一次完整的赋值

修改原表使用 df.loc[mask,'col']=value,把行和列选择放在同一个赋值表达式中。不要写 df[mask]['col']=value,链式索引产生的中间对象是否共享底层数据,在旧版本中会有不可靠行为,在 Copy-on-Write 下也不能用来修改原对象。选择出独立分析表时调用 copy,然后在新表上明确修改。

示例新增 paid_flag,先给满足条件的行写 True,再给其余行写 False,并断言标记与筛选条件一致。赋值右侧如果是 Series,仍然按索引标签对齐;右侧如果是数组,则主要按照位置和形状。先核对索引和长度,再执行赋值。改变 copy 参数或关闭警告不是解决数据语义问题的方法。

练习中的检查习惯

对筛选结果应同时检查行数、主键集合和条件是否全部满足。条件太宽会多出记录,条件太窄会丢失记录;只看打印出的头几行无法发现后面的错误。对空筛选不要索引第一行,先检查 empty。访问不存在的标签通常报 KeyError,这提示数据合同变化,而不是应统一填零。

面试题常把排序、筛选、重设索引和赋值连在一起。逐步写出每一步的索引以及结果类型,比记住某一条链式代码更稳妥。SQL 一般不会像 Series 算术那样按行标签自动对齐,而是在 JOIN 的键和条件中声明对应关系,因此 pandas 的方便行为也必须被明确检查。

版本与复现约定

本课程 pandas 示例以 2.3.3 为验证基线,页面运行记录给出实际版本。pandas 3.x 默认使用 Copy-on-Write:从表中取出的对象发生修改时,不据此假定原表同步变化。跨版本都应使用一次完整的 loc 赋值表达式;需要独立工作表时显式 copy。阅读网上的旧示例时,先核对版本、输入类型和索引,再判断结果能否复现。

交互演示:观察机制

完整示例与实际输出

from pathlib import Path
import pandas as pd
import numpy as np
from _shared import load
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "outputs"
OUT.mkdir(exist_ok=True)
orders = load("orders").copy()
mask = orders["status"].isin(["paid","refunded"])
selected = orders.loc[mask,["order_id","status","amount"]].copy()
orders.loc[mask,"paid_flag"] = True
orders.loc[~mask,"paid_flag"] = False
assert len(selected) == int(mask.sum())
assert orders.loc[mask,"paid_flag"].eq(True).all()
a = pd.Series([10,20],index=["u1","u2"])
b = pd.Series([1,2],index=["u2","u1"])
aligned = a+b
assert aligned.to_dict() == {"u1":12,"u2":21}
print("标签对齐:",aligned.to_dict())
print("位置相加:",(a.to_numpy()+b.to_numpy()).tolist())
print(selected.head().to_string(index=False))
assert len(orders.iloc[:0]) == 0

实际运行通过CPU Python execution.

标签对齐: {'u1': 12, 'u2': 21}
位置相加: [11, 22]
 order_id status  amount
        1   paid    48.0
        2   paid   240.0
        3   paid    12.5
        4   paid    48.0
        5   paid   240.0

五道自测:先回答,再核对

01 · loc[2] 总是第三行吗?

查看提示

标签和位置。

查看参考答案

不是;它查标签 2,第三行应用 iloc[2]。

02 · 两个 Series 相加怎样匹配?

查看提示

试着交换一侧顺序。

查看参考答案

按索引标签匹配,未匹配处通常为缺失。

03 · 为什么多条件要括号和 &?

查看提示

运算优先级以及逐元素逻辑。

查看参考答案

括号限定每个比较,&逐元素组合;and 不能判断整列真值。

04 · 如何稳定修改原表的一列?

查看提示

只进行一次选择赋值。

查看参考答案

df.loc[mask,'col']=value,不使用链式赋值。

05 · 筛选后索引会重新编号吗?

查看提示

观察索引。

查看参考答案

通常不会,保持原标签;显式 reset_index 才改变编号。

两项动手任务

任务 1 · 给所有订单标记是否已经支付,并核对退款订单。

提示

isin 包含 paid 和 refunded。

完整参考解答与检查方式
from pathlib import Path
import pandas as pd
import numpy as np
from _shared import load
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "outputs"
OUT.mkdir(exist_ok=True)
orders = load("orders").copy()
mask = orders["status"].isin(["paid","refunded"])
selected = orders.loc[mask,["order_id","status","amount"]].copy()
orders.loc[mask,"paid_flag"] = True
orders.loc[~mask,"paid_flag"] = False
assert len(selected) == int(mask.sum())
assert orders.loc[mask,"paid_flag"].eq(True).all()
a = pd.Series([10,20],index=["u1","u2"])
b = pd.Series([1,2],index=["u2","u1"])
aligned = a+b
assert aligned.to_dict() == {"u1":12,"u2":21}
print("标签对齐:",aligned.to_dict())
print("位置相加:",(a.to_numpy()+b.to_numpy()).tolist())
print(selected.head().to_string(index=False))
assert len(orders.iloc[:0]) == 0

任务 2 · 构造标签不一致的两列,用零补单侧缺失求和。

提示

使用 add(fill_value=0)。

完整参考解答与检查方式
from pathlib import Path
import pandas as pd
import numpy as np
from _shared import load
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "outputs"
OUT.mkdir(exist_ok=True)
a=pd.Series([10,20],index=["a","b"])
b=pd.Series([3,4],index=["b","c"])
result=a.add(b,fill_value=0)
assert result.to_dict()=={"a":10,"b":23,"c":4}
print(result.to_string())

官方资料与掌握状态

完成两项任务后,按独立完成程度记录。状态会尽量保存在当前浏览器,可导出备份。

动手任务记录

掌握状态与复习

关掉资料独立完成后记 A;提示后完成记 B;需要重学记 C。

记录尽量保存在当前浏览器,建议定期导出备份。