如何无需循环处理Numpy二维数组元素并解决索引广播不匹配错误?
这问题我之前优化代码时也踩过坑!numpy的花式索引规则有时候确实容易让人懵,咱们先理清楚为啥直接用traveltime_2d_array[some_indexes, list_of_column_indexes]会报错:
{IndexError}shape mismatch: indexing arrays could not be broadcast together with shapes (4,) (8,)
原因是numpy在使用两个数组做索引时,会尝试广播它们的形状。你的some_indexes是长度为4的一维数组,list_of_column_indexes是长度为8的一维数组,这两个形状没办法完成广播,所以触发了索引错误。而你原来的for循环,是对每个行索引v,单独取list_of_column_indexes对应的所有列——本质上是每个行索引都要和所有列索引配对,这正是广播能帮我们实现的,只要调整行索引的形状就行。
矢量化实现方案
只需要给行索引增加一个维度,让它能和列索引广播,然后直接取每行的最小值即可,完全去掉for循环:
import numpy as np # 1. 给行索引增加一个维度,从(4,)变成(4,1) some_indexes_2d = np.array(some_indexes)[:, None] # 等价于np.expand_dims(some_indexes, axis=1) # 2. 此时索引会自动广播,得到形状为(4,8)的子数组,对应每个行索引的指定列 selected_times = traveltime_2d_array[some_indexes_2d, list_of_column_indexes] # 3. 取每行的最小值,赋值给travel_time_vec对应的位置 travel_time_vec[some_indexes] = selected_times.min(axis=1)
原理说明
把some_indexes变成(4,1)的二维数组后,numpy会把它和(8,)的列索引数组广播成(4,8)的形状:每个行索引(共4个)都会和所有8个列索引配对,刚好和你原来for循环里traveltimes = traveltime_2d_array[v, list_of_column_indexes]的逻辑完全一致。然后用min(axis=1)对每行取最小值,一次性得到所有行的最优结果,效率比for循环高得多,尤其是当数据量很大的时候。
验证示例
假设我们有一个简单的测试数组:
traveltime_2d_array = np.array([ [10, 20, 5, 15], [30, 5, 25, 10], [12, 8, 22, 18], [7, 14, 9, 21] ]) some_indexes = [0, 2] # 行索引,长度2 list_of_column_indexes = [1, 3] # 列索引,长度2 # 原循环实现 travel_time_vec = np.zeros(4) for v in some_indexes: traveltimes = traveltime_2d_array[v, list_of_column_indexes] best_index = np.argmin(traveltimes) travel_time_vec[v] = traveltimes[best_index] # 结果:travel_time_vec = [15., 0., 18., 0.] # 矢量化实现 some_indexes_2d = np.array(some_indexes)[:, None] selected_times = traveltime_2d_array[some_indexes_2d, list_of_column_indexes] travel_time_vec_vect = np.zeros(4) travel_time_vec_vect[some_indexes] = selected_times.min(axis=1) # 结果:travel_time_vec_vect = [15., 0., 18., 0.],和循环结果完全一致
内容的提问来源于stack exchange,提问作者Mr_and_Mrs_D

