Python Numpy ndarray数组:从入门到精通
Python NumPy ndarray 数组:从入门到精通
NumPy(Numerical Python)是 Python 中用于科学计算的基础包。它提供了一个强大的 N 维数组对象 ndarray
,以及用于处理这些数组的各种工具。ndarray
是 NumPy 的核心数据结构,理解它是掌握 NumPy 乃至整个 Python 科学计算生态系统的关键。
本文将带你深入探索 ndarray
,从基础概念到高级应用,助你成为 NumPy 数组操作高手。
1. ndarray 基础:初识多维数组
1.1 什么是 ndarray?
ndarray
(N-dimensional array)是一个多维的、同构的数组。这意味着:
- 多维: 它可以是一维的(类似于列表)、二维的(类似于矩阵)、三维的(类似于立方体),甚至更高维度的。
- 同构: 数组中的所有元素必须是相同的数据类型(例如,所有元素都是整数、浮点数或复数)。
这种设计使得 ndarray
在存储和操作大量数值数据时非常高效。
1.2 创建 ndarray
有多种方法可以创建 ndarray
:
-
从列表或元组创建:
```python
import numpy as np从列表创建
arr1 = np.array([1, 2, 3, 4, 5]) # 一维数组
arr2 = np.array([[1, 2, 3], [4, 5, 6]]) # 二维数组从元组创建
arr3 = np.array((7, 8, 9))
``` -
使用内置函数创建:
```python
创建全零数组
zeros_arr = np.zeros((2, 3)) # 2x3 的全零数组
创建全一数组
ones_arr = np.ones((3, 4), dtype=np.int32) # 3x4 的全一数组,指定数据类型为 int32
创建单位矩阵
eye_arr = np.eye(4) # 4x4 的单位矩阵
创建指定范围的数组
range_arr = np.arange(10, 20, 2) # 从 10 到 20(不包括 20),步长为 2
创建等差数列
linspace_arr = np.linspace(0, 1, 5) # 从 0 到 1(包括 1),生成 5 个等间距的数
创建随机数数组
random_arr = np.random.rand(2, 2) # 2x2 的随机数数组(0 到 1 之间)
random_int_arr = np.random.randint(1, 10, (3, 3)) # 3x3 的随机整数数组(1 到 10 之间)
```
1.3 ndarray 的属性
ndarray
对象具有许多有用的属性,可以帮助你了解数组的特性:
ndarray.ndim
: 数组的维度(轴的数量)。ndarray.shape
: 数组的形状,一个表示每个维度大小的元组。ndarray.size
: 数组中元素的总数。ndarray.dtype
: 数组中元素的数据类型。ndarray.itemsize
: 数组中每个元素占用的字节数。ndarray.data
: 包含数组实际元素的缓冲区(通常不需要直接访问)。
```python
arr = np.array([[1, 2, 3], [4, 5, 6]])
print("维度:", arr.ndim) # 输出:维度: 2
print("形状:", arr.shape) # 输出:形状: (2, 3)
print("元素总数:", arr.size) # 输出:元素总数: 6
print("数据类型:", arr.dtype) # 输出:数据类型: int64 (或 int32,取决于系统)
print("元素字节数:", arr.itemsize) # 输出:元素字节数: 8 (或 4,取决于系统和数据类型)
```
2. ndarray 索引、切片和迭代
2.1 索引
ndarray
的索引类似于 Python 列表,但更加强大,支持多维索引:
```python
arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
访问单个元素
print(arr[0, 0]) # 输出:1 (第一行第一列)
print(arr[1, 2]) # 输出:6 (第二行第三列)
访问整行或整列
print(arr[0]) # 输出:[1 2 3] (第一行)
print(arr[:, 1]) # 输出:[2 5 8] (第二列)
```
2.2 切片
切片用于提取数组的一部分:
```python
arr = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
提取子数组
sub_arr = arr[0:2, 1:3] # 提取前两行,第二、三列
print(sub_arr)
输出:
[[2 3]
[6 7]]
使用步长
print(arr[::2, ::2]) # 每隔一行、一列取一个元素
输出:
[[ 1 3]
[ 9 11]]
```
2.3 迭代
你可以使用 for
循环迭代 ndarray
:
```python
arr = np.array([[1, 2], [3, 4]])
迭代行
for row in arr:
print(row)
输出:
[1 2]
[3 4]
迭代所有元素
for element in arr.flat:
print(element)
输出:
1
2
3
4
```
3. ndarray 运算
NumPy 的强大之处在于其高效的数组运算能力。
3.1 基本算术运算
ndarray
支持元素级的基本算术运算(加、减、乘、除、幂等):
```python
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(a + b) # 输出:[5 7 9]
print(a - b) # 输出:[-3 -3 -3]
print(a * b) # 输出:[ 4 10 18]
print(a / b) # 输出:[0.25 0.4 0.5 ]
print(a ** 2) # 输出:[1 4 9]
```
这些运算是元素级的,即分别对数组中对应位置的元素进行运算。
3.2 广播 (Broadcasting)
广播是 NumPy 中一种强大的机制,它允许不同形状的数组之间进行算术运算。NumPy 会自动“拉伸”较小的数组以匹配较大数组的形状,只要它们满足一定的兼容性规则:
- 如果两个数组的维度不同,则维度较少的数组会在其形状的前面补 1,直到维度相同。
- 如果两个数组在某个维度上的大小相同,或者其中一个数组在该维度上的大小为 1,则它们在该维度上是兼容的。
- 如果两个数组在所有维度上都兼容,则它们可以一起广播。
```python
a = np.array([[1, 2, 3], [4, 5, 6]])
b = np.array([10, 20, 30])
print(a + b)
输出:
[[11 22 33]
[14 25 36]]
```
在这个例子中,b
是一维数组,a
是二维数组。b
被广播到 a
的每一行,进行相加操作。
3.3 通用函数 (ufunc)
NumPy 提供了大量的通用函数(ufunc),用于对数组进行元素级的操作。这些函数包括:
- 数学函数:
np.sin
,np.cos
,np.exp
,np.log
,np.sqrt
等。 - 统计函数:
np.sum
,np.mean
,np.std
,np.min
,np.max
,np.argmin
,np.argmax
等。 - 逻辑函数:
np.all
,np.any
,np.isnan
,np.isinf
等。 - 比较函数:
np.greater
,np.less
,np.equal
,np.not_equal
等。
```python
arr = np.array([1, 4, 9, 16])
print(np.sqrt(arr)) # 输出:[1. 2. 3. 4.]
print(np.sum(arr)) # 输出:30
print(np.mean(arr)) # 输出:7.5
print(np.argmax(arr)) # 输出:3 (最大值的索引)
```
3.4 线性代数
NumPy 提供了 numpy.linalg
模块,用于进行线性代数运算:
```python
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
矩阵乘法
print(np.dot(a, b))
或者使用 @ 运算符 (Python 3.5+)
print(a @ b)
输出:
[[19 22]
[43 50]]
矩阵的逆
a_inv = np.linalg.inv(a)
print(a_inv)
求解线性方程组
例如,求解 Ax = b
b = np.array([5, 11])
x = np.linalg.solve(a, b)
print(x) # 输出:[1. 2.]
```
4. ndarray 进阶:塑形、拼接、分割
4.1 塑形 (Reshaping)
reshape()
方法可以改变数组的形状,只要新形状与原数组的元素总数保持一致:
```python
arr = np.arange(12) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
new_arr = arr.reshape(3, 4)
print(new_arr)
输出:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
使用 -1 自动计算维度大小
another_arr = arr.reshape(2, -1) # -1 表示该维度的大小由其他维度推断
print(another_arr)
输出:
[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]]
```
4.2 拼接 (Concatenation)
concatenate()
函数可以将多个数组沿指定轴拼接起来:
```python
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6]])
沿行方向拼接 (axis=0)
row_concat = np.concatenate((a, b), axis=0)
print(row_concat)
输出:
[[1 2]
[3 4]
[5 6]]
沿列方向拼接 (axis=1)
col_concat = np.concatenate((a, b.T), axis=1) # b.T 表示 b 的转置
print(col_concat)
输出:
[[1 2 5]
[3 4 6]]
使用 vstack 和 hstack 简化拼接
vstacked = np.vstack((a, b)) # 垂直堆叠,等同于 np.concatenate((a, b), axis=0)
hstacked = np.hstack((a, b.T)) # 水平堆叠,等同于 np.concatenate((a, b.T), axis=1)
```
4.3 分割 (Splitting)
split()
函数可以将数组沿指定轴分割成多个子数组:
```python
arr = np.arange(12).reshape(3, 4)
沿行方向分割 (axis=0)
row_split = np.split(arr, 3, axis=0) # 分割成 3 个子数组
print(row_split)
输出:
[array([[0, 1, 2, 3]]), array([[4, 5, 6, 7]]), array([[ 8, 9, 10, 11]])]
沿列方向分割 (axis=1)
col_split = np.split(arr, 2, axis=1) # 分割成 2 个子数组
print(col_split)
输出:
[array([[0, 1],
[4, 5],
[8, 9]]),
array([[ 2, 3],
[ 6, 7],
[10, 11]])]
使用 vsplit 和 hsplit 简化分割
vsplit_arr = np.vsplit(arr, 3) # 垂直分割,等同于 np.split(arr, 3, axis=0)
hsplit_arr = np.hsplit(arr, 2) # 水平分割,等同于 np.split(arr, 2, axis=1)
```
5. 高级技巧
5.1 布尔索引
布尔索引允许你使用布尔数组(由比较运算产生)来选择数组中的元素:
python
arr = np.array([1, 2, 3, 4, 5, 6])
bool_arr = arr > 3 # 生成布尔数组 [False False False True True True]
result = arr[bool_arr] # 选择 arr 中对应 bool_arr 为 True 的元素
print(result) # 输出:[4 5 6]
5.2 花式索引 (Fancy Indexing)
花式索引允许你使用整数数组来选择数组中的元素:
```python
arr = np.array([10, 20, 30, 40, 50, 60])
indices = np.array([1, 3, 5])
result = arr[indices] # 选择索引为 1, 3, 5 的元素
print(result) # 输出:[20 40 60]
多维花式索引
arr2d = np.arange(12).reshape(3, 4)
row_indices = np.array([0, 2])
col_indices = np.array([1, 3])
result2d = arr2d[row_indices, col_indices]
print(result2d) #输出 [ 1 11]
```
5.3 结构化数组
结构化数组允许你在一个数组中存储不同类型的数据,类似于表格或数据库中的记录:
```python
定义结构化数据类型
dtype = np.dtype([('name', 'U10'), ('age', 'i4'), ('weight', 'f8')])
创建结构化数组
data = np.array([('Alice', 25, 55.5), ('Bob', 30, 70.2)], dtype=dtype)
print(data['name']) # 输出:['Alice' 'Bob']
print(data['age']) # 输出:[25 30]
print(data[0]) # 输出:('Alice', 25, 55.5) (第一条记录)
```
5.4 内存视图 (Views) 与副本 (Copies)
在 NumPy 中,对数组进行切片操作通常会返回一个视图(view),而不是副本。这意味着视图与原始数组共享相同的底层数据缓冲区。修改视图会影响原始数组,反之亦然。
```python
arr = np.arange(10)
view = arr[2:5] # 创建一个视图
view[0] = 100 # 修改视图
print(arr) # 输出:[ 0 1 100 3 4 5 6 7 8 9] (原始数组也被修改)
如果需要创建副本,可以使用 copy() 方法
arr = np.arange(10)
copy = arr[2:5].copy() # 创建一个副本
copy[0] = 100
print(arr) #输出 [0 1 2 3 4 5 6 7 8 9]
print(copy) #输出[100 3 4]
```
了解视图和副本的区别对于避免意外修改数据非常重要。
总结
NumPy 的 ndarray
是 Python 科学计算的基石。通过本文,你已经掌握了 ndarray
的基本概念、创建方法、索引、切片、运算、塑形、拼接、分割以及一些高级技巧。
要成为 NumPy 专家,还需要不断练习和探索。以下是一些建议:
- 阅读官方文档: NumPy 的官方文档非常详细,是学习的最佳资源。
- 练习、练习、再练习: 尝试解决各种数组操作问题,熟能生巧。
- 学习 NumPy 的周边库: SciPy、Matplotlib、Pandas 等库都构建在 NumPy 之上,学习它们可以扩展你的数据分析能力。
希望这篇文章能帮助你精通 NumPy ndarray
!