Pandas弃用lookup()后向量化交叉表查表的替代方案
Pandas 弃用
lookup()方法的向量化替代方案 Pandas 旧版的lookup()方法已被标记为弃用,官方文档给出的几个原生替代方案均存在适配问题:loc()默认不支持逐行批量向量化匹配、melt()实现逻辑冗余繁琐、factorize()无法匹配当前使用场景,需要找到和原方法效果完全一致、支持向量化的高性能长期兼容实现。
问题场景
涉及两类核心数据表:
- 基础明细表
df为两列结构,分别存储坐标值x、y,构造示例代码如下:
import random import pandas as pd k = 20 y = random.choices(range(1,4),k=k) x = random.choices(range(1,7),k=k) tuples = list(zip(x,y)) df = pd.DataFrame(tuples, columns=["x", "y"])
- 交叉表格式的查询表,以一致性表
Cij为例,行索引为y取值,列名为x取值,表内存储对应坐标的匹配值,结构示例如下:
Concordance table (Cij): x 1 2 3 4 5 6 RTotal y 1 16 15 13 NaN 5 NaN 108 2 NaN 12 NaN 15 NaN NaN 87 3 NaN NaN 6 NaN 13 14 121
核心需求:基于df中每一行的(x,y)值对,在Cij中批量匹配对应值,为df新增Crc列。原lookup()实现代码非常简洁:
df["Crc"] = Cij.lookup(df["y"],df["x"])
补充约束与可复现代码
- 性能约束:交叉表
Cij的规模始终很小,填充Cij时使用双层循环是可接受的;但基础表df的数据量可能非常大,查找操作必须使用向量化实现,不接受逐行循环的低效方案。 - 可直接运行的复现代码如下:
data = [[1,1],[1,1],[1,2],[1,2],[1,2],[1,3],[1,3],[1,5],[2,2],[2,4],[2,4],[2,4],[2,4],[2,4],[3,3],[3,3],[3,5],[3,5],[3,5],[3,6],[3,6],[3,6],[3,6],[3,6]] df = pd.DataFrame(data, columns=["y", "x"]) # 生成交叉表基础结构 ct_a = pd.crosstab(df["y"], df["x"]) Cij = pd.DataFrame([], index=ct_a.index, columns=ct_a.columns) # 同结构查询表之一 # 逐行逐列填充Cij值 for i in range(ct_a.shape[0]): for j in range(ct_a.shape[1]): if ct_a.iloc[i,j] != 0: Cij.iloc[i,j]= ct_a.iloc[i+1:,j+1:].sum().sum()+ct_a.iloc[:i,:j].sum().sum() # 原弃用方法实现的向量化查找,需要替换为长期兼容的实现 df["Crc"] = Cij.lookup(df["y"],df["x"])
推荐实现方案
官方推荐的高性能向量化替代方案基于NumPy高级索引实现,性能和原lookup()完全一致,无冗余逻辑,代码简洁度接近原方法:
# 将行、列标签转换为对应数组位置索引,通过NumPy索引批量取值 df["Crc"] = Cij.to_numpy()[Cij.index.get_indexer(df["y"]), Cij.columns.get_indexer(df["x"])]
方案说明
- 实现完全向量化,即使
df达到百万行级别也能毫秒级完成计算,性能和原lookup()方法无差异 - 逻辑和原
lookup()完全对齐:按传入的行标签序列、列标签序列逐行匹配对应位置的值,不存在结果偏差 - 不需要对表做变形、长宽表转换等冗余操作,代码维护成本低
- 对NaN值的处理和原方法完全一致,无对应有效值的位置会自动返回NaN,和原方法输出结果100%匹配
注意:不要直接使用
loc[df['y'], df['x']]实现,该写法会生成笛卡尔积结果,不符合逐行匹配的预期。Pandas 2.0+版本也可以直接使用Cij.reindex(index=df['y'], columns=df['x'])配合对角线取值,但NumPy索引方案的兼容性覆盖所有仍在维护的Pandas版本。
内容的提问来源于stack exchange,提问作者Jakob
相关产品推荐
相关产品推荐

