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,直到维度相同。
  2. 如果两个数组在某个维度上的大小相同,或者其中一个数组在该维度上的大小为 1,则它们在该维度上是兼容的。
  3. 如果两个数组在所有维度上都兼容,则它们可以一起广播。

```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

THE END