Pandas是否提供多布尔列向量化类集合比较的原生方法
import pandas as pd animals = pd.DataFrame({'name': ['ostrich', 'parrot', 'platypus'], 'legs': [2, 2, 4], 'flight': [False, True, False], 'beak': [True, True, True], 'feathers': [True, True, False]})
| name | legs | flight | beak | feathers |
|---|---|---|---|---|
| ostrich | 2 | ✔ | ✔ | |
| parrot | 2 | ✔ | ✔ | ✔ |
| platypus | 4 | ✔ |
现有可用功能
Pandas可以便捷地对整列(即Series)执行条件判断,返回的布尔值Series可通过*布尔索引(boolean indexing)*机制实现DataFrame筛选:
bipeds = (animals.legs == 2) print(animals[bipeds]) name legs flight beak feathers 0 ostrich 2 False True True 1 parrot 2 True True True
在实际业务场景中,所有这类比较条件均从文本检索字符串的解析结果生成,因此需要以程序化方式构造筛选条件(开发者已知晓Pandas的query接口,但当前需求与该接口的能力不匹配)。实现这类比较逻辑的函数编写十分简便:
def comp_search(df, column_name, comp, value): return getattr(df[column_name], f'__{comp}__')(value) bipeds = comp_search(animals, 'legs', 'eq', 2)
对于任意单布尔列的筛选,仅需类似animals[animals.feathers]的写法即可实现。
核心需求
需要针对一组布尔列开展类集合比较操作:例如筛选出至少具备某类特征集合、或特征集合是某指定集合子集的所有记录。参考前述单列比较的实现逻辑,预期的类集合比较写法形如:
set(df[features]) <= set(values)
对应的比较条件构造函数的预期实现形式如下:
def set_comp_search(df, column_names, comp, values): return getattr(set(df[column_names]), f'__{comp}__')(set(values))
但上述写法均无法正常运行:对DataFrame调用set()仅会生成由列名组成的普通集合,无法实现逐行的集合比对逻辑。
可行但效率极低的实现方案
上述需求可通过apply方法逐行将行内布尔值转换为集合,再对生成的集合Series执行比较实现:
def row_to_set(row): return set( label for label, value in zip(row.index, row) if value ) def set_comp_search(df, column_names, comp, values): series_of_sets = df[column_names].apply(row_to_set, axis=1) return getattr(series_of_sets, f'__{comp}__')(set(values))
这类写法虽然简洁,但当源DataFrame规模达到数千行时,逐行迭代的apply实现会出现严重的性能下降。
可行但属于重复造轮子的实现方案
如果为每类集合比较硬编码等价的布尔表达式,得到的比较逻辑是向量化执行的(即针对整列批量计算,而非Python层逐行迭代):
def set_comp_search(df, column_names, comp, values): other_column_names = set(column_names) - set(values) value_columns = df[values] other_columns = df[other_column_names] if comp == 'gt': # 包含所有检索特征,且至少包含一个其他特征 return value_columns.all(axis=1) & other_columns.any(axis=1) if comp == 'ge': # 包含所有检索特征 return value_columns.all(axis=1) if comp == 'eq': # 包含所有检索特征,且不包含其他任何特征 return value_columns.all(axis=1) & ~other_columns.any(axis=1) if comp == 'le': # 不包含任何其他特征 return ~other_columns.any(axis=1) if comp == 'lt': # 未包含所有检索特征,且不包含任何其他特征 return ~value_columns.all(axis=1) & ~other_columns.any(axis=1)
例如需要实现set(animals[features]) > {'beak'}的比较逻辑时,调用方式如下:
more_than_beak = set_comp_search(animals, {'flight', 'beak', 'feathers'}, 'gt', {'beak'}) # 等价于布尔逻辑:(animals.beak) & (animals.flight | animals.feathers) print(animals[more_than_beak]) name legs flight beak feathers 0 ostrich 2 False True True 1 parrot 2 True True True # 正确排除platypus的记录
除了写法较繁琐外,这类实现的运行速度完全满足要求。但开发者认为这类实现属于重复开发:该需求的应用场景与Series.str的能力高度相似,只是需要支持DataFrame、Series序列或numpy数组的操作,而非单Series操作,可惜Pandas目前没有配套的DataFrame.set模块。
因此提出核心问题:Pandas是否提供针对布尔列集合的向量化类比较原生方法?
(开发者已查阅同类多布尔列筛选的相关问题,虽然场景相似,但其能力无法覆盖集合类比较的需求)
内容的提问来源于stack exchange,提问作者CrazyChucky

