为什么使用NumPy

Python 列表是出色的通用容器。他们可以 “异构”,这意味着它们可以包含各种类型的元素, 当用于对少数人执行单个操作时,它们的速度非常快 的元素。

根据数据的特征和操作类型 需要执行,其他容器可能更合适;通过利用 这些特性,我们可以提高速度,减少内存消耗,并且 提供用于执行各种常见处理任务的高级语法。 当存在大量 “同质” (相同类型) 数据时,NumPy 会大放异彩 在 CPU 上进行处理。

什么是数组(ndarray

在计算机中,数组是一种用于存储和检索的结果数据。我们经常把数组当作网络来谈论,每一次消息都是数据元素。例如列表:

\begin{array}{|c|c|c|c|} \hline 1 & 5 & 2 & 0 \\ \hline 8 & 3 & 6 & 1\\ \hline 1 & 7 & 2 & 9 \\ \hline \end{array}

二维数组类似一个表:

\begin{array}{|c|c|c|c|} \hline 1 & 5 & 2 & 0 \\ \hline 8 & 3 & 6 & 1\\ \hline 1 & 7 & 2 & 9 \\ \hline \end{array}

三维数组则像,二维数组向一个方向累积在一起的样子

\begin{array}{ccc} \begin{array}{|c|c|c|} \hline 1 & 2 & 3 \\ \hline 4 & 5 & 6 \\ \hline 7 & 8 & 9 \\ \hline \end{array} & \begin{array}{|c|c|c|} \hline 10 & 11 & 12 \\ \hline 13 & 14 & 15 \\ \hline 16 & 17 & 18 \\ \hline \end{array} & \begin{array}{|c|c|c|} \hline 19 & 20 & 21 \\ \hline 22 & 23 & 24 \\ \hline 25 & 26 & 27 \\ \hline \end{array} \end{array}

大多数 NumPy 数组都有一些限制。例如:

- 数组的所有元素必须属于同一类型的数据。

- 创建后,数组的总大小无法更改。

- 形状必须是“矩形”,而不是“锯齿状”;例如,每行 二维数组必须具有相同的列数,而锯齿状并不会因为一维变量的改变而影响,而只会因为二维变量而影响。

当满足这些条件时,NumPy 会利用这些特征来 使阵列更快、内存效率更高、使用更方便 限制较少的数据结构。

数组基础知识

可以通过 array() 来初始化数组(ndarray):

> import numpy as np  
> a = np.array([1, 2, 3, 4, 5, 6])
> a
[1, 2, 3, 4, 5, 6]

可以通过多种方式访问数组的元素。例如,我们可以像访问普通列表那样,使用方括号内元素的整数索引来访问单个元素:

> a[0]
1

这种访问方式还包括切片访问:

> a
[1,2,3]

与原始列表一样,NumPy 数组是可变的:

> a[0] = 10
> a
[10, 2, 3, 4, 5, 6]

在 Python 自带的列表容器中,切片操作获取到的是一个新列表,它在物理内存中与原列表不同。然而,NumPy 数组的切片操作获取到的是原数组的视图(view),这类似于 SQL 中的视图查询,区别在于 NumPy 的视图可以修改原数据,而 SQL 的视图不能。

示例:

> a = np.array([1, 2, 3, 4, 5, 6])
> b = a[:3]
> b[0] = 100
[100   2   3   4   5   6]

相反,在 Python 的列表中,切片操作不会影响原列表:

> a = [1, 2, 3, 4, 5, 6]
> b = a[:3]
> b[0] = 100
> a
[1, 2, 3, 4, 5, 6]

矩阵元素的引用通常是按照行索引(row index)和列索引(column index)的顺序进行的。这种做法在二维数组中是直观且常用的。但当处理多维数组(不仅仅是二维)时,需要一种更通用的方式来理解和操作这些数据

  1. 标量 (Scalar):在编程或科学计算中,标量是没有维度的数值,即零维数组 (0-D array)。这是最简单的类型,代表单个值。

  2. 向量 (Vector):一维数组 (1-D array) 通常被称为向量。它可以看作是一系列按顺序排列的数字。

  3. 矩阵 (Matrix):二维数组 (2-D array) 通常称为矩阵。这类似于一个表格或网格,包含行和列。

  4. 张量 (Tensor):当数组的维度大于二维(例如 3-D、4-D 或更高),通常称为张量 (Tensor)。张量在多维数据处理中非常常见,特别是在机器学习和深度学习中。

数组的属性

1. ndim

**描述**:表示数组的维度数,也就是数组有多少层嵌套。对一维数组 `ndim` 返回 1,二维数组返回 2,以此类推。  
**示例**:

```python
import numpy as np
a = np.array([[[1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3]]])
print(a.ndim)
# 输出: 3
```

在此例中,`a` 是一个三维数组,因此 `a.ndim` 返回 3。

2. shape

**描述**:返回一个包含每个维度的元素个数的元组,从外层到内层依次列出。它表示数组的形状,每个维度有多少元素。  
**示例**:

```python
print(a.shape)
# 输出: (2, 2, 3)
print(len(a.shape) == a.ndim)
# 输出: True
```

在此例中,数组 `a` 有两个外层元素,每个外层元素又包含两个元素,而最内层的每个元素有三个数值,因此 `shape` 返回 `(2, 2, 3)`。

3. size

**描述**:返回数组中元素的总数,即数组所有维度中元素个数的乘积。它表示数组中所有数据项的总数量。  
**示例**:

```python
print(a.size)
# 输出: 12
import math
print(a.size == math.prod(a.shape))
# 输出: True
```

数组 `a` 的形状是 `(2, 2, 3)`,因此总共有 `2 × 2 × 3 = 12` 个元素。

4. dtype

**描述**:返回数组中元素的数据类型。NumPy 数组中的所有元素必须是相同的数据类型,`dtype` 属性记录了这种类型。  
**示例**:

```python
print(a.dtype)
# 输出: dtype('int64')
```

在此例中,数组 `a` 中的所有元素都是整数类型,`dtype` 显示为 `int64`。

如何创建基本数组

除了从一系列元素创建数组外,NumPy 提供了多种简便的方式来创建特定类型的数组。

np.zeros()

  1. 创建一个全为 0 的数组
使用 `np.zeros()` 可以轻松创建一个指定大小的全零数组。

示例:

```python
import numpy as np
np.zeros(2)
# 输出: array([0., 0.])
```

np.ones()

  1. 创建一个全为 1 的数组
使用 `np.ones()` 可以创建一个指定大小的全一数组。

示例:

```python
np.ones(2)
# 输出: array([1., 1.])
```

np.empty()

  1. 创建一个空数组
使用 `np.empty()` 可以创建一个未初始化的数组,其初始内容是内存中的随机值。这个函数的优势在于速度,适用于你打算在之后填充数组时使用。<span style="color:#007777">这种初始化是依照函数内的规则直接去取合法地址,并不会在意合法地址中是否有参数,毕竟对于用户而言,这就是一个空的数组</span>

示例:

```python
np.empty(2)
# 输出: array([1., 1.  ])  # 内容可能会有所不同,但大概率是上一个连续地址
```

np.arange()

  1. 创建一个包含一系列元素的数组
使用 `np.arange()` 可以创建一个包含从 0 开始的一系列整数的数组。

示例:

```python
np.arange(4)
# 输出: array([0, 1, 2, 3])
```

np.arange()

  1. 创建一个带有步长的数组
`np.arange()` 还可以通过指定开始值、结束值和步长来生成一个均匀间隔的数组。

示例:

```python
np.arange(2, 9, 2)
# 输出: array([2, 4, 6, 8])
```

np.linspace()

  1. 创建一个线性间隔的数组
使用 `np.linspace()` 可以创建一个包含指定数量值的数组,这些值在线性空间中均匀分布。你需要指定开始值、结束值和元素数量。

示例:

-   左闭右闭

```python
np.linspace(0, 10, num=5 , endpoint=True)
# 输出: array([ 0. ,  2.5,  5. ,  7.5, 10. ])
```

-   左闭右开

```python
np.linspace(0, 10, num=5 , endpoint=False)
# 输出: array([ 0. ,  2.5,  5. ,  7.5])
```

指定数据类型: dtype

  1. 指定数据类型
默认情况下,NumPy 数组的数据类型是浮点型 (`np.float64`),但你可以通过 `dtype` 关键字参数来显式指定其他数据类型。

示例:

```python
x = np.ones(2, dtype=np.int64)
x
# 输出: array([1, 1])
```

numpy.fromfunction

  1. 自定义函数产生 ndarray

numpy.fromfunction 的函数签名

numpy.fromfunction(function, shape, *, dtype=<class 'float'>, like=None, **kwargs)

参数解释

  1. function:

    • 类型: callable
    • 传入的计算函数,用于根据输入的索引生成数组的元素。该函数应该接受与 shape 维度数相同的参数,每个参数是包含坐标索引的数组。
  2. shape:

    • 类型: tuple of ints
    • 数组的形状,指定生成的数组的尺寸。每个维度的大小由元组中的整数定义。
  3. dtype:

    • 类型: 数据类型,可选,默认值为 float
    • 生成数组的元素类型。如果没有指定,默认为 float
  4. like:

    • 类型: 数组类对象,可选
    • 参考的数组对象。如果提供了 like 参数,生成的数组将模仿该对象的类型(如 numpy 或自定义子类)。
  5. kwargs:

    • 传递给 function 的其他可选参数。

numpy.fromfunction 方法可以通过接收一个计算函数以及数组的形状,生成一个符合条件的 ndarray。第一个参数是一个函数,该函数的参数将接收一组数组的索引。第二个参数是数组的形状。

示例 1: 一维数组

import numpy as np

def funcA(i):
    # 输出索引值 i
    print("i=", i)
    # 根据索引计算并返回结果
    return (i % 4) + 1

# 创建一维数组,形状为 (10,)
arr1 = np.fromfunction(funcA, (10,))
print("Result array:\n", arr1)

输出:

i= [0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
Result array:
 [1. 2. 3. 4. 1. 2. 3. 4. 1. 2.]

解析:

  • funcA 接受一个索引数组 i,并对其进行取余运算 (i % 4),以实现循环赋值的效果。
  • 输出的结果是一个长度为 10 的数组,每 4 个元素形成一个重复的循环。

示例 2: 二维数组

def funcB(i, j):
    # 输出索引值 i 和 j
    print("i=", i, "\n")
    print("j=", j, "\n")
    # 返回 i 和 j 的和
    return i + j

# 创建二维数组,形状为 (2, 2)
arr2 = np.fromfunction(funcB, (2, 2))
print("Result array:\n", arr2)

输出:

i= [[0. 0.]
    [1. 1.]] 

j= [[0. 1.]
    [0. 1.]] 

Result array:
[[0. 1.]
 [1. 2.]]

解析:

  • funcB 接收两个索引数组 ij,其中 i 是行索引,j 是列索引。
  • 返回的结果是对应索引值的加和,因此生成了一个二维数组,每个元素是对应的 ij 相加的结果。

数组排序与连接

1. 排序数组(np.sort()

使用 np.sort() 可以轻松对数组进行排序。调用该函数时,你可以指定排序的轴(axis)、排序方式(kind),以及排序顺序(order)。

例如,给定以下数组:


arr = np.array([2, 1, 5, 3, 7, 4, 6, 8])

你可以通过 np.sort() 将数组按升序排序:


np.sort(arr)

# 输出: array([1, 2, 3, 4, 5, 6, 7, 8])

除了 sort 函数(它返回一个排序后的数组副本)外,NumPy 还提供了一些其他与排序相关的函数:

  • argsort: 按指定轴进行间接排序,返回排序后的索引。

  • lexsort: 按多个键进行间接稳定排序。

  • searchsorted: 在已排序的数组中查找元素。

  • partition: 进行部分排序。

你可以阅读更多关于排序的内容,参考 NumPy 官方文档

2. 连接数组(np.concatenate()

你可以使用 np.concatenate() 将两个或多个数组连接在一起。

例如,给定以下两个数组:


a = np.array([1, 2, 3, 4])

b = np.array([5, 6, 7, 8])

可以将它们按顺序连接:


np.concatenate((a, b))

# 输出: array([1, 2, 3, 4, 5, 6, 7, 8])

如果你有以下二维数组:


x = np.array([[1, 2], [3, 4]])

y = np.array([[5, 6]])

可以通过指定 axis=0 沿行方向连接它们:


np.concatenate((x, y), axis=0)

# 输出:
# array([[1, 2],
#        [3, 4],
#        [5, 6]])

3. 从数组中删除元素

要从数组中删除元素,可以使用索引操作选择你想要保留的元素,这种方法非常简单有效。

数组的形状重塑(arr.reshape()

np.reshape()

你可以使用 reshape() 方法在不改变数组数据的情况下赋予数组新的形状。不过需要记住的是,使用 reshape 方法时,新数组的元素数量必须与原始数组相同。如果你的原数组有 12 个元素,那么新的数组也必须有总共 12 个元素。

示例:

假设你有以下数组:

import numpy as np
a = np.arange(6)
print(a)
# 输出: [0 1 2 3 4 5]

你可以使用 reshape() 将其重塑为一个 3 行 2 列的数组:

b = a.reshape(3, 2)
print(b)
# 输出:
# [[0 1]
#  [2 3]
#  [4 5]]

使用 np.reshape() 的可选参数:

  1. 数组: a 是要被重塑的数组。

  2. newshape: 这是你想要的新形状。你可以指定一个整数或者一个整数元组。如果你指定一个整数,结果将是一个具有该长度的数组。新形状必须与原始形状兼容。

  3. order:

    • 'C': 按照 C 语言风格的索引顺序读取/写入元素(按行读取)。
    • 'F': 按照 Fortran 风格的索引顺序读取/写入元素(按列读取)。
    • 'A': 如果数组在内存中是 Fortran 连续的,则使用 Fortran 风格,否则使用 C 风格。这是一个可选参数,通常不需要指定。

示例:

使用 np.reshape() 指定新形状和顺序:

np.reshape(a, shape=(1, 6), order='C')
# 输出: array([[0, 1, 2, 3, 4, 5]])
a.shap()
# 输出:(1,6)

如何将 1D 数组转换为 2D 数组 (如何向数组添加新轴)

你可以使用 np.newaxisnp.expand_dims 来增加现有数组的维度。

np.newaxis

  • np.newaxis:每使用一次 np.newaxis,就会增加数组的一个维度。比如,一维数组会变成二维数组,二维数组会变成三维数组,依此类推。

例如,给定一个一维数组:

a = np.array([1, 2, 3, 4, 5, 6])
print(a.shape)
# 输出: (6,)

你可以使用 np.newaxis 来添加一个新轴,将其转换为二维数组:

a2 = a[np.newaxis, :]
print(a2.shape)
# 输出: (1, 6)

你还可以通过 np.newaxis 明确地将一维数组转换为行向量或列向量。例如,将一维数组转换为行向量可以通过在第一个维度插入一个新轴来实现:

row_vector = a[np.newaxis, :]
print(row_vector.shape)
# 输出: (1, 6)

如果想将一维数组转换为列向量,可以在第二个维度插入一个新轴:

col_vector = a[:, np.newaxis]
print(col_vector.shape)
# 输出: (6, 1)

np.expand_dims

  • np.expand_dims:你还可以使用 np.expand_dims 在指定位置插入一个新轴。

例如,给定一个一维数组:

a = np.array([1, 2, 3, 4, 5, 6])
print(a.shape)
# 输出: (6,)

使用 np.expand_dims 在索引位置 1 处插入一个轴:

b = np.expand_dims(a, axis=1)
print(b.shape)
# 输出: (6, 1)

也可以在索引位置 0 处插入一个轴:

c = np.expand_dims(a, axis=0)
print(c.shape)
# 输出: (1, 6)

总结

  • np.newaxisnp.expand_dims 都可以用来增加数组的维度。
  • np.newaxis 更直观地通过索引添加新维度,而 np.expand_dims 允许在指定的维度插入一个新轴。