NumPy 是 Python 数据科学生态中的核心数值计算库,二维数组(矩阵)的索引访问是数据提取、清洗和特征工程的基本功。本文围绕 array[row, column] 这一核心语法,带你掌握单元素访问、行/列提取、切片、布尔筛选和花式索引,并讨论性能优化与常见错误处理。
一、理解二维数组的结构
NumPy 二维数组由行和列组成,每一行通常对应一个样本,每一列对应一个特征。使用 shape 属性可以查看行列数:
- import numpy as np
- # 创建 3x4 的二维数组
- arr = np.array([
- [1, 2, 3, 4],
- [5, 6, 7, 8],
- [9, 10, 11, 12]
- ])
- print(arr.shape) # (3, 4)
复制代码
二、基本索引:单一元素、整行、整列
索引从 0 开始,也支持负数(-1 表示最后一行/列)。
- # 单元素:第 2 行第 3 列、最后一行最后一列
- print(arr[1, 2]) # 7
- print(arr[-1, -1]) # 12
- # 整行:第 0 行、最后一行
- print(arr[0])
- print(arr[-1])
- # 整列:第一列、第三列,冒号表示取所有行
- print(arr[:, 0])
- print(arr[:, 2])
复制代码
三、高级索引:切片、布尔索引与花式索引
3.1 切片索引:范围访问
切片支持 [start:stop:step],可独立作用于行和列。
- # 前两行全部列
- print(arr[:2, :])
- # 所有行的第 2 到第 3 列(注意不包含结束索引)
- print(arr[:, 1:3])
- # 前两行的前两列
- print(arr[:2, :2])
- # 每隔一行取一行
- print(arr[::2, :])
- # 反转行顺序
- print(arr[::-1, :])
复制代码
3.2 布尔索引:条件筛选
布尔索引根据条件生成 True/False 掩码,再通过掩码提取满足条件的元素或行。
- data = np.random.randint(1, 20, size=(5, 4))
- mask = data > 10
- print(mask) # 布尔矩阵
- print(data[mask]) # 所有大于 10 的元素
- # 按行条件筛选:第一列大于 5 的行
- row_mask = data[:, 0] > 5
- print(data[row_mask])
复制代码
注意:布尔掩码的长度必须与对应维度长度一致,否则会抛出 ValueError。
3.3 花式索引:整数数组精确定位
花式索引允许传入整数列表,按指定顺序选择行或行列组合。
- test_arr = np.arange(20).reshape(4, 5)
- # 选择第 0、2、3 行
- print(test_arr[[0, 2, 3]])
- # 同时指定行和列索引,返回对应位置的元素
- row_idx = [0, 1, 2]
- col_idx = [1, 3, 4]
- print(test_arr[row_idx, col_idx])
- # 用花式索引重新排列行顺序
- print(test_arr[[3, 1, 0, 2]])
复制代码
四、实际应用:数据清洗与特征工程
在数据分析中,索引操作常用于数据筛选和特征构造。
4.1 数据清洗
- # 模拟销售数据:产品名、销量、金额、地区
- sales_data = np.array([
- ['Product_A', '150', '1200.50', 'North'],
- ['Product_B', '200', '1800.75', 'South'],
- ['Product_C', '80', '950.25', 'East'],
- ['Product_D', '300', '2500.00', 'West'],
- ['Product_E', '120', '1100.80', 'North']
- ])
- prices = sales_data[:, 2].astype(float)
- high_value_mask = prices > 1500
- print(sales_data[high_value_mask])
- region_mask = sales_data[:, 3] == 'North'
- print(sales_data[region_mask])
复制代码
4.2 特征工程
- features = np.random.randn(100, 10) # 100 样本,10 特征
- # 取前 5 个特征
- selected_features = features[:, :5]
- # 通过标准差剔除极端样本
- feature_1_std = np.std(features[:, 0])
- outlier_mask = np.abs(features[:, 0]) <= 3 * feature_1_std
- cleaned_features = features[outlier_mask]
- # 构建交互特征
- interaction_feature = features[:, 0] * features[:, 1]
复制代码
五、性能优化:避免循环,分清视图与副本
5.1 避免 Python 循环
逐个元素遍历是性能杀手。用布尔向量化操作替代双重循环,效率可提升几十倍甚至更多。
- import time
- def inefficient_access(arr):
- result = []
- for i in range(arr.shape[0]):
- for j in range(arr.shape[1]):
- if arr[i, j] > 5:
- result.append(arr[i, j])
- return np.array(result)
- def efficient_access(arr):
- return arr[arr > 5]
- large_array = np.random.randint(0, 10, size=(1000, 1000))
- start = time.time()
- r1 = inefficient_access(large_array)
- t1 = time.time() - start
- start = time.time()
- r2 = efficient_access(large_array)
- t2 = time.time() - start
- print(f"循环耗时: {t1:.4f}s")
- print(f"布尔索引耗时: {t2:.4f}s")
- print(f"性能提升约: {t1/t2:.2f} 倍")
复制代码
5.2 视图与副本
切片返回的可能是视图(共享内存),修改视图会改动原数组;如果希望独立操作,使用 .copy() 显式创建副本。
- original = np.arange(12).reshape(3, 4)
- view = original[1:3, :] # 视图
- view[0, 0] = 999
- print(original) # 原数组已被修改
- copy = original[1:3, :].copy() # 副本
- copy[0, 0] = 888
- print(original) # 原数组不受影响
复制代码
六、错误处理与调试技巧
常见错误包括索引越界和布尔掩码维度不匹配。可以封装一个安全索引函数,统一处理边界检查。
- def safe_indexing(array, row_idx, col_idx=None):
- try:
- if col_idx is None:
- if isinstance(row_idx, int):
- if 0 <= row_idx < array.shape[0]:
- return array[row_idx]
- else:
- raise IndexError(f"行索引 {row_idx} 超出范围")
- return array[row_idx]
- else:
- if isinstance(row_idx, int) and isinstance(col_idx, int):
- if not (0 <= row_idx < array.shape[0]):
- raise IndexError(f"行索引 {row_idx} 超出范围")
- if not (0 <= col_idx < array.shape[1]):
- raise IndexError(f"列索引 {col_idx} 超出范围")
- return array[row_idx, col_idx]
- except Exception as e:
- print(f"索引访问失败: {e}")
- return None
- # 测试
- test_data = np.arange(12).reshape(3, 4)
- print(safe_indexing(test_data, 1, 2)) # 正常工作
- print(safe_indexing(test_data, 5, 2)) # 打印错误并返回 None
复制代码
七、扩展:三维数组与结构化数组
三维数组使用三个索引,可以用 ... 简化高维切片。结构化数组则允许按字段名访问。
- # 三维数组 (2, 3, 4)
- three_d = np.arange(24).reshape(2, 3, 4)
- print(three_d[0]) # 第一层
- print(three_d[0, 1, 2]) # 第一层第二行第三列
- print(three_d[..., 1, :]) # 等价于 three_d[:, 1, :]
- # 结构化数组
- dtype = [('name', 'U10'), ('age', 'i4'), ('weight', 'f4')]
- people = np.array([('Alice', 25, 55.5), ('Bob', 30, 70.2), ('Charlie', 35, 80.0)], dtype=dtype)
- print(people['name'])
- print(people[people['age'] < 30])
复制代码
八、实战:用户行为数据分析
下面模拟电商用户数据,综合运用布尔索引、切片和统计方法,完成一次典型分析流程。
- np.random.seed(42)
- n_users = 1000
- user_ids = np.arange(1, n_users + 1)
- ages = np.random.randint(18, 70, n_users)
- purchase_counts = np.random.poisson(5, n_users)
- total_spending = np.random.lognormal(5, 1, n_users)
- # 组合成二维数组:用户ID、年龄、购买次数、消费金额
- user_data = np.column_stack([user_ids, ages, purchase_counts, total_spending])
- print(user_data.shape) # (1000, 4)
- # 按年龄分段
- young = user_data[user_data[:, 1] < 30]
- middle = user_data[(user_data[:, 1] >= 30) & (user_data[:, 1] < 50)]
- senior = user_data[user_data[:, 1] >= 50]
- print("平均消费金额:")
- print(f"年轻用户: {np.mean(young[:, 3]):.2f}")
- print(f"中年用户: {np.mean(middle[:, 3]):.2f}")
- print(f"老年用户: {np.mean(senior[:, 3]):.2f}")
- # 高价值客户:消费金额前 10%
- spending_threshold = np.percentile(user_data[:, 3], 90)
- high_value = user_data[user_data[:, 3] >= spending_threshold]
- # 高频购买用户:购买次数前 10%
- purchase_threshold = np.percentile(user_data[:, 2], 90)
- frequent_buyers = user_data[user_data[:, 2] >= purchase_threshold]
- # 既是高价值又是高频的“超级客户”
- super_mask = (user_data[:, 3] >= spending_threshold) & (user_data[:, 2] >= purchase_threshold)
- super_customers = user_data[super_mask]
- print(f"超级客户数量: {len(super_customers)}")
- if len(super_customers) > 0:
- print(f"平均年龄: {np.mean(super_customers[:, 1]):.1f}")
- print(f"平均购买次数: {np.mean(super_customers[:, 2]):.1f}")
- print(f"平均消费金额: {np.mean(super_customers[:, 3]):.2f}")
复制代码
九、常见陷阱与最佳实践
- 视图与副本混淆:切片返回视图,修改会波及原数组;明确需要独立数据时务必 .copy()。
- 布尔索引维度不匹配:掩码长度必须与目标轴长度一致,否则报 ValueError。
- 避免层层循环:尽量使用向量化操作和布尔掩码,既简洁又高效。
- 先用 shape 确认维度,再设计索引表达式,可减少越界错误。
掌握 NumPy 二维数组索引,等于掌握了数据处理的“定位系统”。从基础的单元素访问到高级的布尔/花式索引,再配合视图与副本的正确使用,能让你的数据分析与机器学习代码更健壮、更高效。 |