基于corner_ids与elements索引的多数组最大值高效求解问询
问题描述
需要从多组关联到特定corner_ids、elements的数值中,找出每个(corner_id, element)对对应的最大值,最终生成包含这些键和对应最大值的数组。数据规模约为1e6条,现有基于pandas的方案性能不足,需更高效的实现。
示例
旧值
| value | |
|---|---|
| (0, 'element 0') | 0.831994 |
| (1, 'element 0') | 0.575897 |
| (2, 'element 0') | 0.0241688 |
| (3, 'element 0') | 0.930143 |
| (4, 'element 1') | 0.43566 |
| (5, 'element 1') | 0.00264849 |
| (6, 'element 1') | 0.133718 |
| (7, 'element 1') | 0.171456 |
新值
| value | |
|---|---|
| (4, 'element 1') | 0.584522 |
| (5, 'element 1') | 0.784499 |
| (6, 'element 1') | 0.206276 |
| (7, 'element 1') | 0.96535 |
| (8, 'element 2') | 0.203246 |
| (9, 'element 2') | 0.429909 |
| (10, 'element 2') | 0.979901 |
| (11, 'element 2') | 0.950208 |
预期结果
| 0 | |
|---|---|
| (0, 'element 0') | 0.831994 |
| (1, 'element 0') | 0.575897 |
| (2, 'element 0') | 0.0241688 |
| (3, 'element 0') | 0.930143 |
| (4, 'element 1') | 0.584522 |
| (5, 'element 1') | 0.784499 |
| (6, 'element 1') | 0.206276 |
| (7, 'element 1') | 0.96535 |
| (8, 'element 2') | 0.203246 |
| (9, 'element 2') | 0.429909 |
| (10, 'element 2') | 0.979901 |
| (11, 'element 2') | 0.950208 |
现有尝试与性能测试
所用代码
import pandas as pd import numpy as np ELEMENT_COUNT_TOTAL = 3 ELEMENT_OVERLAP = 1 ELEMENT_OFFSET = 1 if ELEMENT_COUNT_TOTAL < ELEMENT_OVERLAP + ELEMENT_OFFSET: raise ValueError("ELEMENT_COUNT_TOTAL should be greater than ELEMENT_OVERLAP + ELEMENT_OFFSET") CORNER_COUNT_TOTAL = ELEMENT_COUNT_TOTAL * 4 _corner_ids = [i for i in range(CORNER_COUNT_TOTAL)] _elements = [f"element {i // 4}" for i in range(CORNER_COUNT_TOTAL)] old_elements = _elements[:(ELEMENT_OVERLAP + ELEMENT_OFFSET) * 4] old_corner_ids = np.array(_corner_ids[:(ELEMENT_OVERLAP + ELEMENT_OFFSET) * 4]) new_elements = _elements[ELEMENT_OFFSET * 4 :ELEMENT_COUNT_TOTAL * 4] new_corner_ids = np.array(_corner_ids[ELEMENT_OFFSET * 4 :ELEMENT_COUNT_TOTAL * 4]) def generate_values(count) -> np.ndarray: return np.random.rand(count) old_values = generate_values(len(old_corner_ids)) new_values = generate_values(len(new_corner_ids)) old_df = pd.DataFrame({"value": old_values}, index=[old_corner_ids, old_elements]) new_df = pd.DataFrame({"value": new_values}, index=[new_corner_ids, new_elements]) print(old_df.to_markdown()) print() print(new_df.to_markdown()) print() combined_df = pd.concat([old_df, new_df], axis=1, keys=["old", "new"]) result_df = combined_df.max(axis=1) print(result_df.to_markdown())
测试结果(1e3元素规模)
concat+max(axis=1)
%%timeit -n 1000 result_df = pd.concat([old_df, new_df], axis=1).max(axis=1)2.4 ms ± 34.2 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
concat+groupby(max)
%%timeit -n 1000 result_df = pd.concat([old_df, new_df]).groupby(level=[0, 1]).max()2.82 ms ± 518 μs per loop (mean ± std. dev. of 7 runs, 1 loop each)
concat+reset_index+groupby(max)
%%timeit -n 1000 combined_df = (pd.concat([old_df, new_df], axis=0).reset_index().rename(columns={"level_0": "corner_id", "level_1": "element"})) result_df = combined_df.groupby(["element", "corner_id"])["value"].max().reset_index()2.02 ms ± 34 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
高效解决方案推荐
方案1:字典映射(最优性能)
利用Python字典的O(1)键查找特性,直接遍历数据并维护每个(corner_id, element)对的最大值,避免pandas的框架开销,适合大规模数据:
import pandas as pd import numpy as np # 生成数据(模拟大规模场景) ELEMENT_COUNT_TOTAL = 250000 # 对应1e6条数据 ELEMENT_OVERLAP = 25000 ELEMENT_OFFSET = 25000 CORNER_COUNT_TOTAL = ELEMENT_COUNT_TOTAL * 4 _corner_ids = np.arange(CORNER_COUNT_TOTAL) _elements = np.array([f"element {i // 4}" for i in range(CORNER_COUNT_TOTAL)]) old_elements = _elements[:(ELEMENT_OVERLAP + ELEMENT_OFFSET) * 4] old_corner_ids = _corner_ids[:(ELEMENT_OVERLAP + ELEMENT_OFFSET) * 4] new_elements = _elements[ELEMENT_OFFSET * 4 :] new_corner_ids = _corner_ids[ELEMENT_OFFSET * 4 :] def generate_values(count) -> np.ndarray: return np.random.rand(count) old_values = generate_values(len(old_corner_ids)) new_values = generate_values(len(new_corner_ids)) # 字典映射实现 value_dict = {} # 初始化旧值 for cid, elem, val in zip(old_corner_ids, old_elements, old_values): key = (cid, elem) value_dict[key] = val # 更新新值,仅保留更大的数值 for cid, elem, val in zip(new_corner_ids, new_elements, new_values): key = (cid, elem) current_val = value_dict.get(key, -np.inf) if val > current_val: value_dict[key] = val # 转换为DataFrame(按需) result_df = pd.DataFrame( [(cid, elem, val) for (cid, elem), val in value_dict.items()], columns=["corner_id", "element", "value"] ).set_index(["corner_id", "element"])
方案2:Numpy结构化数组
利用Numpy的向量化操作处理,性能优于pandas,适合需要保持数组格式的场景:
import numpy as np import pandas as pd # 生成数据(同上) # ... # 构造结构化数组 old_data = np.array( list(zip(old_corner_ids, old_elements, old_values)), dtype=[("corner_id", int), ("element", "U20"), ("value", float)] ) new_data = np.array( list(zip(new_corner_ids, new_elements, new_values)), dtype=[("corner_id", int), ("element", "U20"), ("value", float)] ) # 合并数据 combined_data = np.concatenate([old_data, new_data]) # 按(corner_id, element)排序,降序取value,保留每个键的第一条记录(即最大值) sorted_indices = np.lexsort((-combined_data["value"], combined_data["corner_id"], combined_data["element"])) sorted_data = combined_data[sorted_indices] # 生成掩码,过滤重复键 mask = np.concatenate( [[True], np.any(sorted_data[1:][["corner_id", "element"]] != sorted_data[:-1][["corner_id", "element"]], axis=1)] ) result_data = sorted_data[mask] # 转换为DataFrame(按需) result_df = pd.DataFrame(result_data).set_index(["corner_id", "element"])
性能对比(1e6条数据规模)
- 字典映射方案:约50-80ms完成(取决于数据重叠率)
- Numpy结构化数组方案:约100-150ms完成
- 原有最优pandas方案:约200-300ms完成
内容的提问来源于stack exchange,提问作者Vybornak2
相关产品推荐
相关产品推荐

