Python数据分析入门:NumPy数组操作全攻略(附实战代码)
如果你刚开始接触Python数据分析,面对海量数据时,可能会觉得用Python原生的列表(list)来处理既慢又麻烦。比如,你想对一组数据里的每个元素都加上10,用列表推导式当然可以,但当你需要处理成千上万行数据,或者进行矩阵乘法、统计计算时,原生列表就显得力不从心了。这时,NumPy就该登场了。它不是一个简单的库,而是整个Python科学计算生态的基石。无论是你之后想用Pandas做数据分析,用Scikit-learn搞机器学习,还是用Matplotlib画图,都绕不开NumPy。它最核心的魅力,在于其ndarray对象——一个高效、灵活的多维数组容器。今天这篇文章,我就从一个实践者的角度,带你从零开始,彻底搞懂NumPy数组的创建、操作和运算,并附上大量可直接运行的代码,让你看完就能上手干活。
1. 从零构建你的第一个NumPy数组
安装NumPy非常简单,打开你的终端或命令提示符,一行命令搞定:
pip install numpy
安装完成后,在Python脚本或Jupyter Notebook中,我们习惯用np作为别名导入:
import numpy as np
print(f"当前NumPy版本:{np.__version__}")
1.1 多种创建数组的“起手式”
创建数组是第一步,NumPy提供了极其丰富的方式,远不止从列表转换那么简单。
从Python序列创建:这是最直观的方式,np.array()函数能将列表、元组等转换为ndarray。
# 从列表创建一维数组
list_data = [1, 2, 3, 4, 5]
arr_from_list = np.array(list_data)
print(arr_from_list) # 输出:[1 2 3 4 5]
# 从嵌套列表创建二维数组(矩阵)
matrix_data = [[1, 2, 3], [4, 5, 6]]
arr_2d = np.array(matrix_data)
print(arr_2d)
# 输出:
# [[1 2 3]
# [4 5 6]]
注意:
np.array()会尝试推断数组元素的数据类型(dtype)。如果源数据中混有整数和浮点数,结果会自动提升为浮点型(float)。
生成特定数值范围的数组:模拟数据或创建序列时非常常用。
np.arange(start, stop, step):类似Python的range(),但生成的是数组。np.linspace(start, stop, num):在指定区间内生成等间隔的num个点。
# 生成0到9的整数数组
arr_range = np.arange(10)
print(arr_range) # [0 1 2 3 4 5 6 7 8 9]
# 生成1到5之间,包含6个点的等差数列
arr_lin = np.linspace(1, 5, 6)
print(arr_lin) # [1. 1.8 2.6 3.4 4.2 5. ]
创建特殊结构的数组:在做科学计算时,我们经常需要全零、全一或未初始化的数组作为“画布”。
# 创建一个3行4列的全零矩阵
zeros_arr = np.zeros((3, 4))
print(zeros_arr)
# 创建一个2x3x4的三维全一数组
ones_arr = np.ones((2, 3, 4))
print(ones_arr.shape) # 输出形状:(2, 3, 4)
# 创建一个2x2的单位矩阵(主对角线为1,其余为0)
eye_arr = np.eye(2)
print(eye_arr)
# 输出:
# [[1. 0.]
# [0. 1.]]
# 创建一个3x3的未初始化数组(内容为内存残留值,速度最快)
empty_arr = np.empty((3, 3))
print(empty_arr)
利用随机数生成数组:数据分析和机器学习中,生成模拟数据或初始化参数离不开随机数组。
# 生成一个2x3的数组,元素为[0.0, 1.0)区间的均匀分布随机数
random_arr = np.random.rand(2, 3)
print(random_arr)
# 生成一个符合标准正态分布(均值为0,标准差为1)的3x3数组
normal_arr = np.random.randn(3, 3)
print(normal_arr)
# 生成一个包含5个随机整数的数组,范围在[10, 20)
randint_arr = np.random.randint(10, 20, size=5)
print(randint_arr)
1.2 理解数组的“身份证”:核心属性
创建一个数组后,我们首先要了解它的基本信息。NumPy数组有几个关键属性,就像它的身份证。
# 创建一个示例数组
example_arr = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
# 1. ndim: 数组的维度(轴)数量
print("维度数量 (ndim):", example_arr.ndim) # 输出:2
# 2. shape: 数组的形状,一个元组,表示每个维度上的大小
print("形状 (shape):", example_arr.shape) # 输出:(3, 4) 表示3行4列
# 3. size: 数组中元素的总数
print("元素总数 (size):", example_arr.size) # 输出:12
# 4. dtype: 数组中元素的数据类型
print("数据类型 (dtype):", example_arr.dtype) # 输出:int32 或 int64(取决于系统)
# 5. itemsize: 每个元素占用的字节数
print("每个元素字节数 (itemsize):", example_arr.itemsize, "bytes")
# 6. nbytes: 整个数组数据占用的总字节数 (等于 size * itemsize)
print("数组总字节数 (nbytes):", example_arr.nbytes, "bytes")
理解这些属性是进行后续所有操作的基础。特别是shape和dtype,在数据对齐、广播和性能优化中至关重要。
2. 庖丁解牛:数组的索引、切片与迭代
掌握了创建数组,下一步就是如何精准地获取和修改数组中的元素。NumPy的索引和切片语法非常强大且直观。
2.1 一维数组:和列表几乎一样
对于一维数组,其操作方式与Python列表高度相似。
arr_1d = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
# 基础索引:获取单个元素
print(arr_1d[5]) # 输出:5
# 负数索引:从末尾开始计数
print(arr_1d[-2]) # 输出:8
# 切片:[start:stop:step]
print(arr_1d[2:7]) # 输出:[2 3 4 5 6] (索引2到6,不包含7)
print(arr_1d[::2]) # 输出:[0 2 4 6 8] (步长为2)
print(arr_1d[5:1:-1]) # 输出:[5 4 3 2] (反向切片)
2.2 多维数组:用逗号分隔维度
这是NumPy的精华所在。对于二维数组(矩阵),索引格式为[行, 列];对于更高维度,则依此类推。
arr_2d = np.array([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]])
# 获取单个元素:第2行(索引1),第3列(索引2)
print(arr_2d[1, 2]) # 输出:7
# 获取整行:第0行
print(arr_2d[0, :]) # 输出:[1 2 3 4]
# 简写为:
print(arr_2d[0]) # 输出相同
# 获取整列:第2列
print(arr_2d[:, 2]) # 输出:[ 3 7 11]
# 获取子区域:前两行的后三列
sub_arr = arr_2d[:2, 1:]
print(sub_arr)
# 输出:
# [[2 3 4]
# [6 7 8]]
使用省略号(...):当数组维度很高时,可以用省略号代表剩下的所有维度。
arr_3d = np.random.rand(3, 4, 5) # 一个3x4x5的三维数组
# 获取第一个“块”(第一维)的所有数据
print(arr_3d[0, ...].shape) # 输出:(4, 5)
# 等价于 arr_3d[0, :, :]
# 获取所有“块”的第一行、所有列
print(arr_3d[..., 0, :].shape) # 输出:(3, 5)
# 等价于 arr_3d[:, 0, :]
2.3 花式索引:用数组来索引
这是NumPy非常强大的特性,允许你使用整数数组或布尔数组作为索引,进行非连续或条件性的元素选取。
整数数组索引:
arr = np.arange(10, 20)
print(arr) # [10 11 12 13 14 15 16 17 18 19]
# 用一个整数列表作为索引,取出对应位置的元素
indices = [1, 3, 5]
print(arr[indices]) # 输出:[11 13 15]
# 对于多维数组,可以分别指定每个维度的索引数组
arr_2d = np.array([[1,2], [3,4], [5,6]])
rows = [0, 1, 2]
cols = [0, 1, 0]
print(arr_2d[rows, cols]) # 输出:[1 4 5] (取(0,0), (1,1), (2,0)位置的元素)
布尔数组索引(掩码):这是数据清洗和筛选的利器。
arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
# 创建一个布尔条件数组
mask = arr > 5
print(mask) # 输出:[False False False False False True True True True]
# 使用布尔数组索引,只返回条件为True的元素
print(arr[mask]) # 输出:[6 7 8 9]
# 更简洁的写法
print(arr[arr % 2 == 0]) # 输出所有偶数:[2 4 6 8]
2.4 遍历数组元素
虽然向量化操作是NumPy的首选,但有时遍历仍有必要。
arr_2d = np.array([[1, 2], [3, 4]])
# 直接迭代:遍历的是第一维(行)
for row in arr_2d:
print(row)
# 输出:
# [1 2]
# [3 4]
# 使用flat属性迭代所有元素(展平后迭代)
for element in arr_2d.flat:
print(element, end=' ')
# 输出:1 2 3 4
# 使用np.nditer进行更可控的迭代(例如以特定顺序)
for x in np.nditer(arr_2d, order='F'): # Fortran顺序,列优先
print(x, end=' ')
# 输出:1 3 2 4
3. 重塑、连接与拆分:改变数组的形态
数据处理中,经常需要改变数组的排列方式,或者将多个数组合并、分割。
3.1 改变数组形状
reshape()是最常用的方法,它返回一个新视图(如果内存连续),而不改变原数据。
arr = np.arange(12)
print(arr) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
# 重塑为3行4列的矩阵
arr_reshaped = arr.reshape(3, 4)
print(arr_reshaped)
# 输出:
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
# 使用-1自动计算该维度大小:将12个元素排成2行,列数自动计算
arr_auto = arr.reshape(2, -1)
print(arr_auto.shape) # 输出:(2, 6)
# 注意:reshape要求新形状的元素总数(size)必须与原数组一致,否则会报错。
resize()方法则会直接修改原数组的形状,如果新形状更大,会用0填充;如果更小,则截断数据。
arr = np.array([1, 2, 3, 4])
arr.resize(3, 3) # 原数组被改变
print(arr)
# 输出:新形状(3,3)需要9个元素,不足部分用0填充
# [[1 2 3]
# [4 0 0]
# [0 0 0]]
展平数组:将多维数组变成一维。
arr_2d = np.array([[1,2], [3,4]])
print(arr_2d.ravel()) # 返回视图:[1 2 3 4]
print(arr_2d.flatten()) # 返回副本:[1 2 3 4]
ravel()通常返回视图(更快),flatten()总是返回副本(更安全)。
3.2 数组的连接与堆叠
将多个数组合并成一个。
| 函数 | 功能描述 | 常用场景 |
|---|---|---|
np.concatenate() | 沿指定轴连接数组序列 | 通用的连接操作 |
np.vstack() | 垂直堆叠(按行连接) | 合并具有相同列数的数据集 |
np.hstack() | 水平堆叠(按列连接) | 合并具有相同行数的特征 |
np.column_stack() | 将一维数组作为列堆叠成二维数组 | 构建特征矩阵 |
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
# 垂直堆叠(增加行数)
v_stack = np.vstack((a, b))
print("vstack:\n", v_stack)
# 输出:
# [[1 2]
# [3 4]
# [5 6]
# [7 8]]
# 水平堆叠(增加列数)
h_stack = np.hstack((a, b))
print("hstack:\n", h_stack)
# 输出:
# [[1 2 5 6]
# [3 4 7 8]]
# 沿指定轴连接(axis=0 同 vstack, axis=1 同 hstack)
concat_axis0 = np.concatenate((a, b), axis=0)
print("concatenate axis=0:\n", concat_axis0)
3.3 数组的拆分
与连接相反,将一个数组拆分成多个较小的数组。
arr = np.arange(16).reshape(4, 4)
print("原数组:\n", arr)
# 水平拆分成2个数组(按列拆分)
h_split = np.hsplit(arr, 2)
print("\n水平拆分成2部分:")
for part in h_split:
print(part)
# 输出两个 4x2 的数组
# 垂直拆分成2个数组(按行拆分)
v_split = np.vsplit(arr, 2)
print("\n垂直拆分成2部分:")
for part in v_split:
print(part)
# 输出两个 2x4 的数组
# 使用array_split进行不等量拆分
arr_1d = np.arange(10)
split_uneven = np.array_split(arr_1d, 4) # 将10个元素分成4份
print("\n不等量拆分结果:", [a.tolist() for a in split_uneven])
# 输出:[[0, 1, 2], [3, 4, 5], [6, 7], [8, 9]]
4. 向量化运算与通用函数:效率的源泉
NumPy之所以快,核心在于向量化。它允许你对整个数组进行操作,而无需编写显式循环,这些操作在底层由预编译的C代码执行。
4.1 基本的算术与比较运算
所有算术运算符(+, -, *, /, **, %)和比较运算符(>, <, ==, !=等)都是按元素进行的。
a = np.array([1, 2, 3, 4])
b = np.array([5, 6, 7, 8])
print("加法:", a + b) # [ 6 8 10 12]
print("乘法:", a * b) # [ 5 12 21 32]
print("幂运算:", a ** 2) # [ 1 4 9 16]
print("比较:", a > 2) # [False False True True]
广播机制:这是NumPy最神奇的特性之一。它允许不同形状的数组进行算术运算,前提是它们的形状是“兼容”的。规则简要来说:
- 如果两个数组的维度数不同,将维度较小的数组的形状前面补1。
- 对于每个维度,大小必须相等,或者其中一个为1,或者其中一个不存在(补1后)。
- 在大小为1的维度上,数组会沿着该维度“广播”以匹配另一个数组的大小。
# 一个2x3的数组
matrix = np.array([[1, 2, 3],
[4, 5, 6]])
# 一个长度为3的一维数组
vector = np.array([10, 20, 30])
# 广播发生:vector被“拉伸”成2x3,与matrix相加
result = matrix + vector
print(result)
# 输出:
# [[11 22 33]
# [14 25 36]]
# 相当于 vector 被复制成了 [[10,20,30], [10,20,30]]
4.2 强大的通用函数
通用函数(ufunc)是对ndarray中每个元素进行操作的函数。NumPy提供了大量的ufunc,涵盖数学、逻辑、统计等。
数学函数:
arr = np.array([0, np.pi/2, np.pi])
print("正弦值:", np.sin(arr)) # [0.0000000e+00 1.0000000e+00 1.2246468e-16]
print("指数:", np.exp(arr)) # [ 1. 4.81047738 23.14069263]
print("平方根:", np.sqrt([1, 4, 9])) # [1. 2. 3.]
统计聚合函数:这些函数通常可以沿指定轴(axis)操作。
arr = np.random.randn(4, 5) # 4x5的正态分布随机数组
print("数组:\n", arr)
print("\n全局求和:", np.sum(arr))
print("沿axis=0求和(按列,结果形状(5,)):", np.sum(arr, axis=0))
print("沿axis=1求和(按行,结果形状(4,)):", np.sum(arr, axis=1))
print("\n全局均值:", np.mean(arr))
print("每列的标准差:", np.std(arr, axis=0))
print("每行的最小值:", np.min(arr, axis=1))
print("每列的最大值索引:", np.argmax(arr, axis=0))
逻辑函数:
a = np.array([1, 2, 3, 4])
b = np.array([4, 3, 2, 1])
print("逐元素比较取大:", np.maximum(a, b)) # [4 3 3 4]
print("逐元素比较取小:", np.minimum(a, b)) # [1 2 2 1]
# np.where: 三元向量操作,相当于 if-else 的向量化版本
condition = a > 2
print("where函数:", np.where(condition, a, b))
# 输出:a中大于2的元素保留,否则用b中对应元素替换
# [4 3 3 4]
4.3 矩阵的线性代数运算
虽然NumPy本身提供基础的线性代数功能,但对于复杂的矩阵运算,通常推荐使用numpy.linalg子模块或专门的库如SciPy。
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
# 矩阵乘法(注意不是逐元素乘法)
dot_product = np.dot(A, B)
# 等价于 A @ B (Python 3.5+ 运算符)
print("矩阵乘法 A @ B:\n", A @ B)
# 输出:
# [[19 22]
# [43 50]]
# 转置
print("A的转置:\n", A.T)
# 求逆矩阵
A_inv = np.linalg.inv(A)
print("A的逆矩阵:\n", A_inv)
# 验证:A * A_inv 应近似于单位矩阵
print("验证 A * A_inv:\n", np.round(A @ A_inv, 10))
5. 视图与副本:避免意想不到的修改
这是NumPy中一个关键且容易出错的概念,关系到数据的安全性和内存效率。
5.1 完全不复制:简单的赋值
在Python中,变量是对象的引用。简单的赋值b = a不会创建新的数组对象,只是创建了一个指向同一数据的新引用。
a = np.array([1, 2, 3, 4])
b = a # b是a的别名,指向同一块内存
print(b is a) # True,是同一个对象
b[0] = 99
print(a) # [99 2 3 4] !!!修改b影响了a
提示:如果你不想关联两个变量,就不要使用简单的赋值。
5.2 视图:共享数据,不同元数据
视图(view)是一个新的数组对象,但它与原始数组共享同一份数据。修改视图的数据会影响原始数组。创建视图的常见操作有:
- 使用
.view()方法。 - 数组切片(绝大多数情况下)。
reshape()(当数组内存连续时)。
a = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
v = a.view() # v是a的一个视图
print(v is a) # False,是不同的对象
print(v.base is a) # True,v的数据基于a
v[0] = 100
print(a[0]) # 100!通过视图修改了原始数据
# 切片是更常见的视图
slice_view = a[3:7] # 切片创建视图
slice_view[:] = 0
print(a) # [100 1 2 0 0 0 0 7 8 9]
视图的优势是零拷贝,速度快,节省内存。但你必须清楚知道你在操作视图,否则可能无意中污染了原始数据。
5.3 副本:数据的完全独立拷贝
副本(copy)是数据的完整拷贝,会占用新的内存。对副本的修改不会影响原始数组。创建副本使用.copy()方法。
a = np.array([1, 2, 3])
c = a.copy() # 创建a的完整副本
print(c is a) # False
c[0] = 999
print(a) # [1 2 3] 原始数组未受影响
print(c) # [999 2 3]
何时使用副本?
- 当你需要对数据进行修改,但又想保留原始数据时。
- 当切片操作返回视图,但你想切断与原始数据的联系时。
- 当函数需要修改输入数组,但你不希望影响函数外部的原始数据时。
一个常见的实践是:在对大数据集进行切片并计划修改切片结果时,如果不希望影响原数据,就立即调用.copy()。
big_data = np.random.rand(10000)
# 我们只想修改数据的一部分,且不影响原数据
subset = big_data[1000:2000].copy()
subset *= 2 # 安全地修改副本
理解视图和副本的区别,是写出高效且正确NumPy代码的关键。我个人的经验是,在不确定时,尤其是在函数中接收数组参数并可能修改它时,先考虑是否需要内部拷贝,这能避免很多隐蔽的bug。
&spm=1001.2101.3001.5002&articleId=155049306&d=1&t=3&u=f486cf62770048898fd51e711609293b)
964

被折叠的 条评论
为什么被折叠?



