Python NumPy Concatenate使用方法


Python NumPy Concatenate 深入解析:掌握数组拼接的艺术

在数据科学、机器学习和科学计算领域,NumPy (Numerical Python) 是 Python 生态系统中不可或缺的基石。它提供了强大的 N 维数组对象(ndarray)以及一系列用于高效操作这些数组的函数。数组操作中,一个极其常见且重要的任务就是将多个数组合并或连接成一个更大的数组。NumPy 提供了 numpy.concatenate 函数来优雅地完成这项任务。

本文将深入探讨 numpy.concatenate 函数的方方面面,从基本用法到高级技巧,涵盖其语法、参数、不同维度数组的拼接、与其他相关函数的比较、性能考量以及常见陷阱。通过阅读本文,您将能够熟练掌握使用 numpy.concatenate 拼接 NumPy 数组的艺术。

1. NumPy 和数组拼接简介

NumPy 是什么?

NumPy 是一个开源的 Python 库,专注于数值计算。其核心是 ndarray 对象,这是一个同构(所有元素类型相同)、多维的数组。相比 Python 内置的列表(list),NumPy 数组在存储和计算上更为高效,特别是在处理大型数据集时。NumPy 提供了大量的数学函数来操作这些数组,是 SciPy、Pandas、Matplotlib、Scikit-learn 等众多科学计算库的基础。

为什么需要数组拼接?

在实际数据处理中,数据往往不是一次性完整获取的,或者需要将不同来源、不同处理阶段的数据整合在一起。例如:

  • 将分批加载的数据集合并成一个完整的数据集。
  • 将不同特征(列)的数据合并到一个特征矩阵中。
  • 在图像处理中,将多个图像通道或图像块拼接起来。
  • 在时间序列分析中,连接不同时间段的数据。

这些场景都需要将两个或多个数组按照特定的方式连接起来,numpy.concatenate 正是为此而生。

2. numpy.concatenate 函数详解

numpy.concatenate 函数用于沿指定的轴(axis)连接一系列数组。

基本语法:

python
numpy.concatenate((a1, a2, ...), axis=0, out=None, dtype=None, casting='same_kind')

参数解析:

  1. a1, a2, ... (必需): 这是一个包含待连接数组的序列(通常是元组 tuple 或列表 list)。这些数组必须具有相同的形状(shape),除了在连接轴(axis)所对应的维度上。例如,如果沿 axis=0 连接,则除了第一个维度(行数)可以不同外,其他所有维度(如列数、深度等)的大小必须完全一致。

    • 注意: 必须将数组序列放在一个元组或列表中传递给函数,例如 np.concatenate((arr1, arr2)) 而不是 np.concatenate(arr1, arr2)。这是初学者常见的错误。
  2. axis (可选): 指定连接发生的轴(维度)。

    • 默认值为 0。这意味着默认情况下,数组是沿着第一个轴(对于二维数组来说是行方向,即垂直堆叠)进行连接。
    • axis=1 表示沿第二个轴连接(对于二维数组是列方向,即水平堆叠)。
    • 对于更高维的数组,axis 可以取 0, 1, 2, ... 直至 N-1(其中 N 是数组的维度数)。
    • 如果 axis 被设置为 None,则数组在连接之前会被展平(flattened)为一维数组。
  3. out (可选): 如果提供,则结果将直接存放到这个预先分配好的 ndarray 中。这个数组必须具有正确的形状和类型(dtype)以容纳输出。如果不提供(默认 None),函数会创建一个新的数组来存放结果。使用 out 参数可以避免不必要的内存分配,在某些对性能要求极高的场景下可能有用,但一般情况下较少使用。

  4. dtype (可选): 可以强制指定输出数组的数据类型(dtype)。如果未指定,NumPy 会根据输入数组的类型推断出合适的类型(通常是能够容纳所有输入数组数据而无信息损失的类型,例如 intfloat 连接会得到 float)。

  5. casting (可选): 控制数据类型转换的规则。可选值包括:

    • 'no':不允许任何类型转换。
    • 'equiv':只允许字节顺序的更改。
    • 'safe':只允许可以保证不损失精度的转换(例如 int32int64)。
    • 'same_kind' (默认):允许在相同类型种类内的安全转换(例如 float32float64),或者 uintint 等。
    • 'unsafe':允许任何数据转换,可能会损失精度或导致数值溢出。

3. 基本用法示例

在开始示例之前,首先确保导入 NumPy 库:

python
import numpy as np

示例 1: 连接一维数组

对于一维数组,只有一个轴 axis=0 可供连接(或者 axis=None 展平,效果相同)。

```python
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
arr3 = np.array([7, 8])

默认沿 axis=0 连接

result = np.concatenate((arr1, arr2, arr3))
print("arr1:", arr1, "shape:", arr1.shape)
print("arr2:", arr2, "shape:", arr2.shape)
print("arr3:", arr3, "shape:", arr3.shape)
print("\nConcatenated result (axis=0 or default):")
print(result)
print("Result shape:", result.shape)

使用 axis=None 效果相同 (因为输入已是1D)

result_none = np.concatenate((arr1, arr2, arr3), axis=None)
print("\nConcatenated result (axis=None):")
print(result_none)
print("Result shape:", result_none.shape)
```

输出:

```
arr1: [1 2 3] shape: (3,)
arr2: [4 5 6] shape: (3,)
arr3: [7 8] shape: (2,)

Concatenated result (axis=0 or default):
[1 2 3 4 5 6 7 8]
Result shape: (8,)

Concatenated result (axis=None):
[1 2 3 4 5 6 7 8]
Result shape: (8,)
```

示例 2: 连接二维数组 (沿 axis=0 - 垂直堆叠)

当沿 axis=0 连接二维数组时,数组的列数(即 shape[1])必须相同。结果数组的行数是所有输入数组行数之和,列数保持不变。

```python
arr_2d_1 = np.array([[1, 2],
[3, 4]]) # Shape: (2, 2)

arr_2d_2 = np.array([[5, 6]]) # Shape: (1, 2)

arr_2d_3 = np.array([[7, 8],
[9, 10],
[11, 12]]) # Shape: (3, 2)

检查列数是否匹配 (shape[1] 都是 2)

print("arr_2d_1 shape:", arr_2d_1.shape)
print("arr_2d_2 shape:", arr_2d_2.shape)
print("arr_2d_3 shape:", arr_2d_3.shape)

沿 axis=0 连接 (垂直堆叠)

result_axis0 = np.concatenate((arr_2d_1, arr_2d_2, arr_2d_3), axis=0)
print("\nConcatenated result (axis=0):")
print(result_axis0)
print("Result shape:", result_axis0.shape) # Shape: (2+1+3, 2) = (6, 2)
```

输出:

```
arr_2d_1 shape: (2, 2)
arr_2d_2 shape: (1, 2)
arr_2d_3 shape: (3, 2)

Concatenated result (axis=0):
[[ 1 2]
[ 3 4]
[ 5 6]
[ 7 8]
[ 9 10]
[11 12]]
Result shape: (6, 2)
```

示例 3: 连接二维数组 (沿 axis=1 - 水平堆叠)

当沿 axis=1 连接二维数组时,数组的行数(即 shape[0])必须相同。结果数组的列数是所有输入数组列数之和,行数保持不变。

```python
arr_2d_4 = np.array([[1, 2],
[3, 4]]) # Shape: (2, 2)

arr_2d_5 = np.array([[5],
[6]]) # Shape: (2, 1)

arr_2d_6 = np.array([[7, 8, 9],
[10, 11, 12]]) # Shape: (2, 3)

检查行数是否匹配 (shape[0] 都是 2)

print("arr_2d_4 shape:", arr_2d_4.shape)
print("arr_2d_5 shape:", arr_2d_5.shape)
print("arr_2d_6 shape:", arr_2d_6.shape)

沿 axis=1 连接 (水平堆叠)

result_axis1 = np.concatenate((arr_2d_4, arr_2d_5, arr_2d_6), axis=1)
print("\nConcatenated result (axis=1):")
print(result_axis1)
print("Result shape:", result_axis1.shape) # Shape: (2, 2+1+3) = (2, 6)
```

输出:

```
arr_2d_4 shape: (2, 2)
arr_2d_5 shape: (2, 1)
arr_2d_6 shape: (2, 3)

Concatenated result (axis=1):
[[ 1 2 5 7 8 9]
[ 3 4 6 10 11 12]]
Result shape: (2, 6)
```

示例 4: 维度不匹配导致错误

如果尝试连接的数组在非连接轴上的维度不匹配,NumPy 会抛出 ValueError

```python
arr_err_1 = np.array([[1, 2, 3], [4, 5, 6]]) # Shape (2, 3)
arr_err_2 = np.array([[7, 8], [9, 10]]) # Shape (2, 2)

try:
# 尝试沿 axis=0 连接,列数不同 (3 vs 2)
np.concatenate((arr_err_1, arr_err_2), axis=0)
except ValueError as e:
print(f"\nError (axis=0): {e}")

try:
# 尝试沿 axis=1 连接,行数相同,可以连接
result_ok = np.concatenate((arr_err_1, arr_err_2), axis=1)
print("\nConcatenation along axis=1 is possible if rows match:")
print(result_ok)
print("Result shape:", result_ok.shape) # Shape (2, 3+2) = (2, 5)
except ValueError as e:
print(f"\nError (axis=1): {e}")

arr_err_3 = np.array([[11, 12, 13]]) # Shape (1, 3)
try:
# 尝试将 (2, 3) 和 (1, 3) 沿 axis=1 连接,行数不同 (2 vs 1)
np.concatenate((arr_err_1, arr_err_3), axis=1)
except ValueError as e:
print(f"\nError (axis=1): {e}")
```

输出:

```
Error (axis=0): all the input array dimensions except for the concatenation axis must match exactly, but along dimension 1, the array at index 0 has size 3 and the array at index 1 has size 2

Concatenation along axis=1 is possible if rows match:
[[ 1 2 3 7 8]
[ 4 5 6 9 10]]
Result shape: (2, 5)

Error (axis=1): all the input array dimensions except for the concatenation axis must match exactly, but along dimension 0, the array at index 0 has size 2 and the array at index 1 has size 1
```

这个错误信息非常清晰地指出了问题所在:除了连接轴之外的所有维度必须精确匹配。

4. 连接更高维度的数组

numpy.concatenate 的原理同样适用于三维或更高维度的数组。关键仍然是:除了连接轴 axis 对应的维度可以不同外,其他所有维度的尺寸必须严格一致。

示例 5: 连接三维数组

假设我们有表示图像的数据,形状为 (height, width, channels)

```python

假设有两个 64x64 像素的 RGB 图像 (3 channels)

img1 = np.random.rand(64, 64, 3) # Shape (64, 64, 3)
img2 = np.random.rand(64, 64, 3) # Shape (64, 64, 3)

另一个 32x64 像素的 RGB 图像

img3 = np.random.rand(32, 64, 3) # Shape (32, 64, 3)

再一个 64x32 像素的 RGB 图像

img4 = np.random.rand(64, 32, 3) # Shape (64, 32, 3)

1. 沿 axis=0 连接 (垂直堆叠): img1 和 img3

要求 shape[1] (width) 和 shape[2] (channels) 必须相同 (64 和 3)

result_3d_axis0 = np.concatenate((img1, img3), axis=0)
print(f"img1 shape: {img1.shape}, img3 shape: {img3.shape}")
print(f"Concatenated (axis=0) shape: {result_3d_axis0.shape}") # Expected: (64+32, 64, 3) = (96, 64, 3)

2. 沿 axis=1 连接 (水平堆叠): img1 和 img4

要求 shape[0] (height) 和 shape[2] (channels) 必须相同 (64 和 3)

result_3d_axis1 = np.concatenate((img1, img4), axis=1)
print(f"\nimg1 shape: {img1.shape}, img4 shape: {img4.shape}")
print(f"Concatenated (axis=1) shape: {result_3d_axis1.shape}") # Expected: (64, 64+32, 3) = (64, 96, 3)

3. 沿 axis=2 连接 (沿通道/深度方向): img1 和 img2 (假设要合并特征)

要求 shape[0] (height) 和 shape[1] (width) 必须相同 (64 和 64)

通常这种操作可能意义不大,除非通道代表不同特征

result_3d_axis2 = np.concatenate((img1, img2), axis=2)
print(f"\nimg1 shape: {img1.shape}, img2 shape: {img2.shape}")
print(f"Concatenated (axis=2) shape: {result_3d_axis2.shape}") # Expected: (64, 64, 3+3) = (64, 64, 6)

4. 尝试错误连接: img1 和 img4 沿 axis=0

shape[1] (width) 不同 (64 vs 32)

try:
np.concatenate((img1, img4), axis=0)
except ValueError as e:
print(f"\nError concatenating img1 and img4 along axis=0: {e}")
```

输出:

```
img1 shape: (64, 64, 3), img3 shape: (32, 64, 3)
Concatenated (axis=0) shape: (96, 64, 3)

img1 shape: (64, 64, 3), img4 shape: (64, 32, 3)
Concatenated (axis=1) shape: (64, 96, 3)

img1 shape: (64, 64, 3), img2 shape: (64, 64, 3)
Concatenated (axis=2) shape: (64, 64, 6)

Error concatenating img1 and img4 along axis=0: all the input array dimensions except for the concatenation axis must match exactly, but along dimension 1, the array at index 0 has size 64 and the array at index 1 has size 32
```

5. 特殊情况:axis=None

如前所述,当 axis=None 时,所有输入数组会在连接前被展平成一维数组。

```python
arr_nd_1 = np.arange(6).reshape(2, 3) # [[0, 1, 2], [3, 4, 5]]
arr_nd_2 = np.arange(10, 14).reshape(2, 2) # [[10, 11], [12, 13]]

print("arr_nd_1:\n", arr_nd_1)
print("arr_nd_2:\n", arr_nd_2)

使用 axis=None 连接

result_none_nd = np.concatenate((arr_nd_1, arr_nd_2), axis=None)
print("\nConcatenated result (axis=None):")
print(result_none_nd) # [ 0 1 2 3 4 5 10 11 12 13]
print("Result shape:", result_none_nd.shape) # (10,)
```

输出:

```
arr_nd_1:
[[0 1 2]
[3 4 5]]
arr_nd_2:
[[10 11]
[12 13]]

Concatenated result (axis=None):
[ 0 1 2 3 4 5 10 11 12 13]
Result shape: (10,)
```

6. 数据类型 (dtype) 和类型转换 (casting)

当连接的数组具有不同的数据类型时,NumPy 会自动进行类型提升(upcasting)以避免信息丢失,除非显式指定 dtypecasting 规则。

示例 6: 自动类型提升

```python
arr_int = np.array([1, 2, 3]) # dtype: int64 (or int32, depending on system)
arr_float = np.array([4.0, 5.5, 6.1]) # dtype: float64

result_mixed = np.concatenate((arr_int, arr_float))
print("arr_int dtype:", arr_int.dtype)
print("arr_float dtype:", arr_float.dtype)
print("\nConcatenated mixed types:")
print(result_mixed)
print("Result dtype:", result_mixed.dtype) # Usually promotes to float64
```

输出:

```
arr_int dtype: int64
arr_float dtype: float64

Concatenated mixed types:
[1. 2. 3. 4. 5.5 6.1]
Result dtype: float64
```

示例 7: 使用 dtype 参数强制类型

```python

强制输出为整数 (浮点数的小数部分会被截断)

result_forced_int = np.concatenate((arr_int, arr_float), dtype=np.int32)
print("\nConcatenated forced to int32:")
print(result_forced_int)
print("Result dtype:", result_forced_int.dtype)

强制输出为布尔型 (非零为True, 零为False)

result_forced_bool = np.concatenate((arr_int, arr_float), dtype=bool)
print("\nConcatenated forced to bool:")
print(result_forced_bool)
print("Result dtype:", result_forced_bool.dtype)
```

输出:

```
Concatenated forced to int32:
[1 2 3 4 5 6]
Result dtype: int32

Concatenated forced to bool:
[ True True True True True True]
Result dtype: bool
```

示例 8: 使用 casting 参数控制转换

```python
arr_i8 = np.array([100, 120], dtype=np.int8)
arr_i16 = np.array([300, 400], dtype=np.int16)

默认 'same_kind' 允许 int8 -> int16

result_same_kind = np.concatenate((arr_i8, arr_i16), casting='same_kind')
print("\nCasting 'same_kind' (default):")
print(result_same_kind, result_same_kind.dtype) # Likely int16

'safe' 也允许 int8 -> int16

result_safe = np.concatenate((arr_i8, arr_i16), casting='safe')
print("\nCasting 'safe':")
print(result_safe, result_safe.dtype) # Likely int16

尝试将 int16 -> int8 (可能不安全), 用 'no' 会失败

try:
np.concatenate((arr_i16, arr_i8), casting='no')
except TypeError as e:
print(f"\nError with casting='no': {e}")

用 'unsafe' 允许 int16 -> int8 (可能导致数据溢出/改变)

result_unsafe = np.concatenate((arr_i16, arr_i8), dtype=np.int8, casting='unsafe')
print("\nCasting 'unsafe' (forcing int8):")
print(result_unsafe, result_unsafe.dtype) # 300 -> 44, 400 -> -112 (due to overflow in int8)
```

输出:

```
Casting 'same_kind' (default):
[100 120 300 400] int16

Casting 'safe':
[100 120 300 400] int16

Error with casting='no': Cannot cast array from dtype('int16') to dtype('int8') according to the rule 'no'

Casting 'unsafe' (forcing int8):
[ 44 -112 100 120] int8
``
注意
unsafe转换导致300400int8`(范围 -128 到 127)中溢出,变成了不同的值。

7. 与相关函数的比较

NumPy 提供了其他一些与数组连接相关的函数,了解它们的区别有助于选择最合适的工具:

  • numpy.vstack(tup):

    • 垂直堆叠数组(沿 axis=0)。
    • 对于输入数组,它会自动处理维度的增加。例如,可以将一维数组堆叠成二维数组。
    • 等效于 np.concatenate(tup, axis=0),但语法更简洁,尤其是在处理一维数组堆叠成二维数组时。
    • tup:包含待堆叠数组的元组。

    ```python
    a = np.array([1, 2, 3])
    b = np.array([4, 5, 6])
    vstacked = np.vstack((a, b))
    print("\nvstack example:")
    print(vstacked)

    [[1 2 3]

    [4 5 6]]

    print("vstack shape:", vstacked.shape) # (2, 3)

    与 concatenate 对比

    需要先将 a, b 变为 (1, 3) 的二维数组

    a_2d = a[np.newaxis, :] # or a.reshape(1, -1) -> shape (1, 3)
    b_2d = b[np.newaxis, :] # or b.reshape(1, -1) -> shape (1, 3)
    concatenated_v = np.concatenate((a_2d, b_2d), axis=0)
    print("Equivalent concatenate (axis=0):")
    print(concatenated_v)
    print("Shape:", concatenated_v.shape) # (2, 3)
    ```

  • numpy.hstack(tup):

    • 水平堆叠数组(沿 axis=1,但对一维数组是沿 axis=0)。
    • 对于一维数组,它会将它们连接成一个更长的一维数组(行为类似 concatenate 的默认情况)。
    • 对于二维或更高维数组,它沿第二个轴(axis=1)连接。
    • tup:包含待堆叠数组的元组。

    ```python
    a = np.array([1, 2, 3])
    b = np.array([4, 5, 6])
    hstacked_1d = np.hstack((a, b))
    print("\nhstack example (1D arrays):")
    print(hstacked_1d) # [1 2 3 4 5 6]
    print("hstack shape (1D):", hstacked_1d.shape) # (6,)

    c = np.array([[1], [2], [3]]) # shape (3, 1)
    d = np.array([[4], [5], [6]]) # shape (3, 1)
    hstacked_2d = np.hstack((c, d))
    print("\nhstack example (2D arrays):")
    print(hstacked_2d)

    [[1 4]

    [2 5]

    [3 6]]

    print("hstack shape (2D):", hstacked_2d.shape) # (3, 2) - equivalent to concatenate axis=1

    与 concatenate 对比 (2D case)

    concatenated_h = np.concatenate((c, d), axis=1)
    print("Equivalent concatenate (axis=1):")
    print(concatenated_h)
    print("Shape:", concatenated_h.shape) # (3, 2)
    ```

  • numpy.dstack(tup):

    • 沿第三个轴(深度轴,axis=2)堆叠数组。
    • 输入数组的 shape[0]shape[1] 必须匹配。
    • 如果输入是二维数组 (M, N)dstack 会将它们视为 (M, N, 1),然后堆叠成 (M, N, K)(K 是数组数量)。
    • 如果输入是一维数组 (N,)dstack 会将它们视为 (1, N, 1),然后堆叠成 (1, N, K)

    ```python
    a = np.array([[1, 2], [3, 4]]) # (2, 2)
    b = np.array([[5, 6], [7, 8]]) # (2, 2)
    dstacked = np.dstack((a, b))
    print("\ndstack example (2D arrays):")
    print(dstacked)

    [[[1 5]

    [2 6]]

    [[3 7]

    [4 8]]]

    print("dstack shape:", dstacked.shape) # (2, 2, 2)

    与 concatenate 对比

    需要先将 a, b 增加一个维度变为 (2, 2, 1)

    a_3d = a[:, :, np.newaxis] # shape (2, 2, 1)
    b_3d = b[:, :, np.newaxis] # shape (2, 2, 1)
    concatenated_d = np.concatenate((a_3d, b_3d), axis=2)
    print("Equivalent concatenate (axis=2):")
    print(concatenated_d)
    print("Shape:", concatenated_d.shape) # (2, 2, 2)
    ```

  • numpy.stack(arrays, axis=0):

    • 这个函数与 concatenate 不同,它沿一个新轴连接数组。
    • 所有输入数组必须具有完全相同的形状。
    • 结果数组的维度会比输入数组多一维。
    • arrays:包含待堆叠数组的序列。
    • axis:指定新轴插入的位置。

    ```python
    a = np.array([1, 2, 3]) # shape (3,)
    b = np.array([4, 5, 6]) # shape (3,)

    Stack along a new axis 0

    stacked_0 = np.stack((a, b), axis=0)
    print("\nstack example (axis=0):")
    print(stacked_0)

    [[1 2 3]

    [4 5 6]]

    print("stack shape (axis=0):", stacked_0.shape) # (2, 3) - 新轴在前面

    Stack along a new axis 1

    stacked_1 = np.stack((a, b), axis=1)
    print("\nstack example (axis=1):")
    print(stacked_1)

    [[1 4]

    [2 5]

    [3 6]]

    print("stack shape (axis=1):", stacked_1.shape) # (3, 2) - 新轴在中间
    ``
    注意
    stack的结果与vstack/hstack可能看起来相似,但概念不同:stack增加了维度,而concatenate(及其变种vstack/hstack/dstack`) 是在现有维度上扩展。

  • numpy.append(arr, values, axis=None):

    • values 追加到数组 arr 的末尾。
    • 如果指定了 axisvalues 必须具有与 arr 兼容的形状(除了 axis 维度)。
    • 如果 axis=None(默认),arrvalues 都会先被展平。
    • 重要: np.append 不是原地操作,它会创建一个新的数组。并且,在循环中反复调用 np.append 通常效率很低,因为它每次都需要重新分配内存并复制整个数组。如果需要多次添加,推荐先将所有要添加的数据收集到 Python 列表中,最后再一次性调用 np.concatenate

    ```python
    arr = np.array([1, 2, 3])
    appended_none = np.append(arr, [4, 5, 6]) # axis=None (default)
    print("\nappend example (axis=None):")
    print(appended_none) # [1 2 3 4 5 6]

    arr_2d = np.array([[1, 2], [3, 4]])
    values_2d = np.array([[5, 6]]) # Shape (1, 2)
    appended_axis0 = np.append(arr_2d, values_2d, axis=0)
    print("\nappend example (axis=0):")
    print(appended_axis0)

    [[1 2]

    [3 4]

    [5 6]]

    低效循环示例 (不推荐)

    my_list = []
    for i in range(5):
    # 每次 append 都创建新数组,效率低
    my_list = np.append(my_list, [i, i+1])
    print("\nInefficient append in loop:", my_list)

    高效方法

    data_to_add = []
    for i in range(5):
    data_to_add.append(np.array([i, i+1])) # 添加 NumPy 数组到列表
    result_efficient = np.concatenate(data_to_add, axis=0)
    print("Efficient concatenation:", result_efficient)
    ```

总结选择:

  • 需要沿现有轴合并数组,且维度匹配规则符合 -> np.concatenate
  • 需要垂直堆叠(axis=0),尤其是处理 1D 转 2D -> np.vstack
  • 需要水平堆叠(axis=1 for 2D+, axis=0 for 1D) -> np.hstack
  • 需要沿深度轴堆叠(axis=2) -> np.dstack
  • 需要将形状相同的数组堆叠到一个的维度 -> np.stack
  • 需要将值追加到单个数组末尾(偶尔使用可以,循环中避免) -> np.append (但通常用 concatenate 构建更好)。

8. 性能考量

numpy.concatenate 操作通常涉及内存分配和数据复制。当连接大型数组或进行大量连接操作时,性能可能成为一个因素。

  1. 内存分配: concatenate 会创建一个新的数组来存储结果。这个新数组的大小是所有输入数组在连接轴上大小的总和。如果结果数组非常大,可能会消耗大量内存。
  2. 数据复制: 函数需要将所有输入数组的数据复制到新的内存位置。这是一个 O(N) 操作,其中 N 是所有输入数组元素的总数。
  3. 避免循环中的 concatenate:append 部分所述,在循环中反复调用 concatenate(或 append)来逐步构建大数组是非常低效的。每次调用都会触发内存重新分配和数据复制。更好的策略是:

    • 将需要连接的小数组收集在一个 Python 列表中。
    • 循环结束后,调用一次 np.concatenate(list_of_arrays, axis=...)

    ```python

    假设要合并 1000 个小数组

    num_arrays = 1000
    list_of_arrays = []
    for _ in range(num_arrays):
    small_arr = np.random.rand(10, 5) # Example small array (10, 5)
    list_of_arrays.append(small_arr)

    一次性高效连接 (沿 axis=0)

    final_array = np.concatenate(list_of_arrays, axis=0)
    print(f"\nEfficiently concatenated shape: {final_array.shape}") # Expected: (10000, 5)
    ```

  4. out 参数: 在极少数需要极致性能且可以预先计算输出数组形状和类型的情况下,使用 out 参数可以避免分配新数组的开销。但这需要手动管理输出数组,使用场景有限。

9. 常见陷阱与最佳实践

  1. 忘记将数组序列放入元组/列表: np.concatenate(arr1, arr2) 是错误的,应该是 np.concatenate((arr1, arr2))
  2. 维度不匹配: 最常见的 ValueError 来源。仔细检查待连接数组的形状,确保除了连接轴 axis 外,其他所有轴的尺寸都严格相等。
  3. 选错 axis: 混淆 axis=0(垂直/行)和 axis=1(水平/列)或其他更高维度。可视化或打印形状有助于理解。
  4. 低效的增量构建: 避免在循环中重复调用 concatenateappend。优先使用列表收集,然后一次性连接。
  5. 混淆 concatenatestack: concatenate 在现有维度上扩展,stack 增加一个新维度。根据需求选择。
  6. 数据类型意外提升: 注意混合类型连接可能导致数据类型向更通用的类型(如 float)提升。如果需要特定类型,使用 dtype 参数。

最佳实践总结:

  • 始终将输入数组放在元组或列表中传递。
  • 连接前验证数组形状是否满足要求。
  • 清晰地指定 axis 参数。
  • 需要增量构建大数组时,先收集到 Python 列表,最后调用一次 concatenate
  • 理解 concatenate, stack, vstack, hstack, dstack 的区别,选用最合适的函数。
  • 关注数据类型,必要时使用 dtypecasting 控制输出类型。

10. 结论

numpy.concatenate 是 NumPy 库中一个功能强大且用途广泛的函数,用于沿指定轴合并多个数组。掌握其用法对于高效处理和组织 NumPy 数组至关重要。通过理解其参数(特别是 arrays 序列和 axis)、维度匹配规则、与 vstack/hstack/stack 等相关函数的区别,以及性能方面的考量,您可以更加自信和有效地在数据分析、科学计算和机器学习项目中使用 NumPy 进行数组操作。

记住检查维度、正确传递参数、并在需要时考虑性能优化策略(如避免循环内连接),numpy.concatenate 将成为您数据处理工具箱中的得力助手。


THE END