Skip to content

NumPy 数组基础

在这一章中,我们将深入了解 NumPy 数组(ndarray)的基础知识,包括数组的创建、属性、数据类型等核心概念。

什么是 NumPy 数组

NumPy 数组(ndarray)是 NumPy 库的核心数据结构,它是一个多维数组对象,具有以下特点:

  • 同质性:数组中的所有元素必须是相同的数据类型
  • 固定大小:数组创建后大小固定,不能动态改变
  • 高效性:使用连续的内存存储,操作速度快
  • 多维性:支持任意维度的数组

数组的创建方法

1. 从 Python 列表创建

python
import numpy as np

# 一维数组
array_1d = np.array([1, 2, 3, 4, 5])
print("一维数组:", array_1d)
print("数据类型:", array_1d.dtype)
print("形状:", array_1d.shape)
print()

# 二维数组
array_2d = np.array([[1, 2, 3], [4, 5, 6]])
print("二维数组:")
print(array_2d)
print("数据类型:", array_2d.dtype)
print("形状:", array_2d.shape)
print()

# 三维数组
array_3d = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
print("三维数组:")
print(array_3d)
print("数据类型:", array_3d.dtype)
print("形状:", array_3d.shape)

输出结果:

一维数组: [1 2 3 4 5]
数据类型: int32
形状: (5,)

二维数组:
[[1 2 3]
 [4 5 6]]
数据类型: int32
形状: (2, 3)

三维数组:
[[[1 2]
  [3 4]]

 [[5 6]
  [7 8]]]
数据类型: int32
形状: (2, 2, 2)

2. 使用内置函数创建

创建特殊数组

python
import numpy as np

# 全零数组
zeros_1d = np.zeros(5)
zeros_2d = np.zeros((3, 4))
print("一维全零数组:", zeros_1d)
print("二维全零数组:")
print(zeros_2d)
print()

# 全一数组
ones_1d = np.ones(4)
ones_2d = np.ones((2, 3))
print("一维全一数组:", ones_1d)
print("二维全一数组:")
print(ones_2d)
print()

# 单位矩阵
identity = np.eye(3)
print("3x3单位矩阵:")
print(identity)
print()

# 填充特定值的数组
full_array = np.full((2, 3), 7)
print("填充数字7的数组:")
print(full_array)

输出结果:

一维全零数组: [0. 0. 0. 0. 0.]
二维全零数组:
[[0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]

一维全一数组: [1. 1. 1. 1.]
二维全一数组:
[[1. 1. 1.]
 [1. 1. 1.]]

3x3单位矩阵:
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

填充数字7的数组:
[[7 7 7]
 [7 7 7]]

创建数值序列

python
import numpy as np

# 等差数列
arange_array = np.arange(10)  # 0到9
print("0到9:", arange_array)

arange_step = np.arange(2, 10, 2)  # 从2开始,到10结束,步长为2
print("2到10,步长2:", arange_step)

arange_float = np.arange(0, 1, 0.1)  # 浮点数步长
print("0到1,步长0.1:", arange_float)
print()

# 等间距数列
linspace_array = np.linspace(0, 10, 5)  # 0到10之间等分5个点
print("0到10等分5点:", linspace_array)

linspace_exclude = np.linspace(0, 10, 5, endpoint=False)  # 不包含终点
print("0到10等分5点(不含终点):", linspace_exclude)
print()

# 对数空间
logspace_array = np.logspace(0, 2, 5)  # 10^0 到 10^2 之间等分5个点
print("对数空间:", logspace_array)

输出结果:

0到9: [0 1 2 3 4 5 6 7 8 9]
2到10,步长2: [2 4 6 8]
0到1,步长0.1: [0.  0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9]

0到10等分5点: [ 0.   2.5  5.   7.5 10. ]
0到10等分5点(不含终点): [0. 2. 4. 6. 8.]

对数空间: [  1.           3.16227766  10.          31.6227766  100.        ]

创建随机数组

python
import numpy as np

# 设置随机种子以获得可重复的结果
np.random.seed(42)

# 0到1之间的随机数
random_uniform = np.random.random(5)
print("0-1随机数:", random_uniform)

# 指定范围的随机整数
random_int = np.random.randint(1, 10, 5)
print("1-10随机整数:", random_int)

# 正态分布随机数
random_normal = np.random.normal(0, 1, 5)  # 均值0,标准差1
print("正态分布随机数:", random_normal)

# 多维随机数组
random_2d = np.random.random((3, 3))
print("3x3随机数组:")
print(random_2d)

输出结果:

0-1随机数: [0.37454012 0.95071431 0.73199394 0.59865848 0.15601864]
1-10随机整数: [6 4 8 8 3]
正态分布随机数: [ 1.76405235  0.40015721  0.97873798  2.2408932   1.86755799]
3x3随机数组:
[[0.95008842 0.4879643  0.22479665]
 [0.19806286 0.76053071 0.16911084]
 [0.08833981 0.68535982 0.95339335]]

数组的基本属性

python
import numpy as np

# 创建一个示例数组
array = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])

print("示例数组:")
print(array)
print()

# 基本属性
print(f"数组维度数量 (ndim): {array.ndim}")
print(f"数组形状 (shape): {array.shape}")
print(f"数组元素总数 (size): {array.size}")
print(f"数据类型 (dtype): {array.dtype}")
print(f"每个元素字节数 (itemsize): {array.itemsize}")
print(f"数组总字节数 (nbytes): {array.nbytes}")
print(f"数组内存布局 (flags): ")
print(array.flags)

输出结果:

示例数组:
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]

数组维度数量 (ndim): 2
数组形状 (shape): (3, 4)
数组元素总数 (size): 12
数据类型 (dtype): int32
每个元素字节数 (itemsize): 4
数组总字节数 (nbytes): 48
数组内存布局 (flags): 
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False

数据类型详解

NumPy 支持的数据类型

python
import numpy as np

# 整数类型
int8_array = np.array([1, 2, 3], dtype=np.int8)      # 8位整数
int16_array = np.array([1, 2, 3], dtype=np.int16)    # 16位整数
int32_array = np.array([1, 2, 3], dtype=np.int32)    # 32位整数
int64_array = np.array([1, 2, 3], dtype=np.int64)    # 64位整数

print("int8:", int8_array.dtype, "字节数:", int8_array.itemsize)
print("int16:", int16_array.dtype, "字节数:", int16_array.itemsize)
print("int32:", int32_array.dtype, "字节数:", int32_array.itemsize)
print("int64:", int64_array.dtype, "字节数:", int64_array.itemsize)
print()

# 浮点数类型
float16_array = np.array([1.0, 2.0, 3.0], dtype=np.float16)  # 半精度
float32_array = np.array([1.0, 2.0, 3.0], dtype=np.float32)  # 单精度
float64_array = np.array([1.0, 2.0, 3.0], dtype=np.float64)  # 双精度

print("float16:", float16_array.dtype, "字节数:", float16_array.itemsize)
print("float32:", float32_array.dtype, "字节数:", float32_array.itemsize)
print("float64:", float64_array.dtype, "字节数:", float64_array.itemsize)
print()

# 布尔类型
bool_array = np.array([True, False, True], dtype=np.bool_)
print("bool:", bool_array.dtype, "字节数:", bool_array.itemsize)
print("布尔数组:", bool_array)
print()

# 复数类型
complex_array = np.array([1+2j, 3+4j], dtype=np.complex128)
print("complex128:", complex_array.dtype, "字节数:", complex_array.itemsize)
print("复数数组:", complex_array)

输出结果:

int8: int8 字节数: 1
int16: int16 字节数: 2
int32: int32 字节数: 4
int64: int64 字节数: 8

float16: float16 字节数: 2
float32: float32 字节数: 4
float64: float64 字节数: 8

bool: bool 字节数: 1
布尔数组: [ True False  True]

complex128: complex128 字节数: 16
复数数组: [1.+2.j 3.+4.j]

数据类型转换

python
import numpy as np

# 创建整数数组
int_array = np.array([1, 2, 3, 4, 5])
print("原始整数数组:", int_array, "类型:", int_array.dtype)

# 转换为浮点数
float_array = int_array.astype(np.float64)
print("转换为浮点数:", float_array, "类型:", float_array.dtype)

# 转换为字符串
string_array = int_array.astype(np.str_)
print("转换为字符串:", string_array, "类型:", string_array.dtype)

# 浮点数转换为整数(会截断小数部分)
float_data = np.array([1.7, 2.3, 3.9])
int_from_float = float_data.astype(np.int32)
print("浮点数:", float_data)
print("转换为整数:", int_from_float)

# 字符串转换为数字
string_numbers = np.array(['1', '2', '3'])
numbers = string_numbers.astype(np.int32)
print("字符串数组:", string_numbers)
print("转换为数字:", numbers)

输出结果:

原始整数数组: [1 2 3 4 5] 类型: int32
转换为浮点数: [1. 2. 3. 4. 5.] 类型: float64
转换为字符串: ['1' '2' '3' '4' '5'] 类型: <U11
浮点数: [1.7 2.3 3.9]
转换为整数: [1 2 3]
字符串数组: ['1' '2' '3']
转换为数字: [1 2 3]

数组的形状操作

改变数组形状

python
import numpy as np

# 创建一维数组
array_1d = np.arange(12)
print("原始一维数组:", array_1d)
print("形状:", array_1d.shape)
print()

# 重塑为二维数组
array_2d = array_1d.reshape(3, 4)
print("重塑为3x4:")
print(array_2d)
print("形状:", array_2d.shape)
print()

# 重塑为三维数组
array_3d = array_1d.reshape(2, 2, 3)
print("重塑为2x2x3:")
print(array_3d)
print("形状:", array_3d.shape)
print()

# 自动计算维度(使用-1)
auto_reshape = array_1d.reshape(4, -1)  # 自动计算列数
print("自动计算维度(4, -1):")
print(auto_reshape)
print("形状:", auto_reshape.shape)

输出结果:

原始一维数组: [ 0  1  2  3  4  5  6  7  8  9 10 11]
形状: (12,)

重塑为3x4:
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
形状: (3, 4)

重塑为2x2x3:
[[[ 0  1  2]
  [ 3  4  5]]

 [[ 6  7  8]
  [ 9 10 11]]]
形状: (2, 2, 3)

自动计算维度(4, -1):
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]
形状: (4, 3)

展平数组

python
import numpy as np

# 创建二维数组
array_2d = np.array([[1, 2, 3], [4, 5, 6]])
print("原始二维数组:")
print(array_2d)
print()

# 展平为一维数组
flattened = array_2d.flatten()
print("flatten():", flattened)
print("原数组是否改变:", array_2d)
print()

# 展平为一维数组(返回视图)
raveled = array_2d.ravel()
print("ravel():", raveled)
print("修改ravel结果:")
raveled[0] = 999
print("ravel结果:", raveled)
print("原数组:", array_2d)  # 原数组也会改变

输出结果:

原始二维数组:
[[1 2 3]
 [4 5 6]]

flatten(): [1 2 3 4 5 6]
原数组是否改变: [[1 2 3]
 [4 5 6]]

ravel(): [1 2 3 4 5 6]
修改ravel结果:
ravel结果: [999   2   3   4   5   6]
原数组: [[999   2   3]
 [  4   5   6]]

数组的拼接和分割

数组拼接

python
import numpy as np

# 创建两个数组
array1 = np.array([1, 2, 3])
array2 = np.array([4, 5, 6])

print("数组1:", array1)
print("数组2:", array2)
print()

# 水平拼接(沿着列方向)
horizontal = np.hstack([array1, array2])
print("水平拼接:", horizontal)

# 垂直拼接(沿着行方向)
vertical = np.vstack([array1, array2])
print("垂直拼接:")
print(vertical)
print()

# 二维数组拼接
array2d_1 = np.array([[1, 2], [3, 4]])
array2d_2 = np.array([[5, 6], [7, 8]])

print("二维数组1:")
print(array2d_1)
print("二维数组2:")
print(array2d_2)
print()

# 沿轴0拼接(行方向)
concat_axis0 = np.concatenate([array2d_1, array2d_2], axis=0)
print("沿轴0拼接:")
print(concat_axis0)

# 沿轴1拼接(列方向)
concat_axis1 = np.concatenate([array2d_1, array2d_2], axis=1)
print("沿轴1拼接:")
print(concat_axis1)

输出结果:

数组1: [1 2 3]
数组2: [4 5 6]

水平拼接: [1 2 3 4 5 6]
垂直拼接:
[[1 2 3]
 [4 5 6]]

二维数组1:
[[1 2]
 [3 4]]
二维数组2:
[[5 6]
 [7 8]]

沿轴0拼接:
[[1 2]
 [3 4]
 [5 6]
 [7 8]]
沿轴1拼接:
[[1 2 5 6]
 [3 4 7 8]]

数组分割

python
import numpy as np

# 创建数组
array = np.arange(12)
print("原始数组:", array)

# 等分分割
split_equal = np.split(array, 3)  # 分成3个相等的部分
print("等分为3部分:")
for i, part in enumerate(split_equal):
    print(f"  部分{i+1}: {part}")
print()

# 指定分割点
split_points = np.split(array, [3, 7])  # 在索引3和7处分割
print("在索引3和7处分割:")
for i, part in enumerate(split_points):
    print(f"  部分{i+1}: {part}")
print()

# 二维数组分割
array_2d = np.arange(12).reshape(3, 4)
print("二维数组:")
print(array_2d)
print()

# 水平分割
hsplit_result = np.hsplit(array_2d, 2)  # 分成2列
print("水平分割:")
for i, part in enumerate(hsplit_result):
    print(f"  部分{i+1}:")
    print(part)
print()

# 垂直分割
vsplit_result = np.vsplit(array_2d, 3)  # 分成3行
print("垂直分割:")
for i, part in enumerate(vsplit_result):
    print(f"  部分{i+1}: {part}")

输出结果:

原始数组: [ 0  1  2  3  4  5  6  7  8  9 10 11]
等分为3部分:
  部分1: [0 1 2 3]
  部分2: [4 5 6 7]
  部分3: [ 8  9 10 11]

在索引3和7处分割:
  部分1: [0 1 2]
  部分2: [3 4 5 6]
  部分3: [ 7  8  9 10 11]

二维数组:
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]

水平分割:
  部分1:
[[0 1]
 [4 5]
 [8 9]]
  部分2:
[[ 2  3]
 [ 6  7]
 [10 11]]

垂直分割:
  部分1: [[0 1 2 3]]
  部分2: [[4 5 6 7]]
  部分3: [[ 8  9 10 11]]

数组的复制

视图 vs 副本

python
import numpy as np

# 创建原始数组
original = np.array([1, 2, 3, 4, 5])
print("原始数组:", original)
print()

# 创建视图(共享内存)
view = original.view()
print("视图:", view)
print("是否共享内存:", np.shares_memory(original, view))

# 修改视图
view[0] = 999
print("修改视图后:")
print("原始数组:", original)
print("视图:", view)
print()

# 创建副本(独立内存)
copy = original.copy()
print("副本:", copy)
print("是否共享内存:", np.shares_memory(original, copy))

# 修改副本
copy[1] = 888
print("修改副本后:")
print("原始数组:", original)
print("副本:", copy)

输出结果:

原始数组: [1 2 3 4 5]

视图: [1 2 3 4 5]
是否共享内存: True
修改视图后:
原始数组: [999   2   3   4   5]
视图: [999   2   3   4   5]

副本: [999   2   3   4   5]
是否共享内存: False
修改副本后:
原始数组: [999   2   3   4   5]
副本: [999 888   3   4   5]

实际应用示例

示例1:图像数据处理

python
import numpy as np

# 模拟一个简单的灰度图像(8x8像素)
image = np.random.randint(0, 256, (8, 8), dtype=np.uint8)
print("原始图像数据:")
print(image)
print(f"图像形状: {image.shape}")
print(f"数据类型: {image.dtype}")
print(f"像素值范围: {image.min()} - {image.max()}")
print()

# 图像处理操作
# 1. 增加亮度
brighter = np.clip(image + 50, 0, 255).astype(np.uint8)
print("增加亮度后的像素值范围:", f"{brighter.min()} - {brighter.max()}")

# 2. 图像二值化
threshold = 128
binary = (image > threshold).astype(np.uint8) * 255
print(f"二值化(阈值{threshold})后的唯一值:", np.unique(binary))

# 3. 计算图像统计信息
print(f"平均像素值: {np.mean(image):.2f}")
print(f"标准差: {np.std(image):.2f}")
print(f"中位数: {np.median(image)}")

示例2:科学计算

python
import numpy as np

# 创建实验数据
time = np.linspace(0, 10, 100)  # 时间从0到10秒,100个数据点
frequency = 2  # 频率2Hz
amplitude = 5  # 振幅5
noise_level = 0.5  # 噪声水平

# 生成带噪声的正弦波信号
signal = amplitude * np.sin(2 * np.pi * frequency * time)
noise = np.random.normal(0, noise_level, len(time))
noisy_signal = signal + noise

print(f"时间范围: {time[0]:.2f} - {time[-1]:.2f} 秒")
print(f"数据点数: {len(time)}")
print(f"信号统计:")
print(f"  平均值: {np.mean(noisy_signal):.3f}")
print(f"  标准差: {np.std(noisy_signal):.3f}")
print(f"  最大值: {np.max(noisy_signal):.3f}")
print(f"  最小值: {np.min(noisy_signal):.3f}")

# 简单的信号处理
# 计算移动平均来平滑信号
window_size = 5
smoothed_signal = np.convolve(noisy_signal, np.ones(window_size)/window_size, mode='valid')
print(f"\n平滑后的信号长度: {len(smoothed_signal)}")
print(f"平滑后的标准差: {np.std(smoothed_signal):.3f}")

性能比较

python
import numpy as np
import time

# 比较 NumPy 数组和 Python 列表的性能
size = 1000000

# Python 列表操作
start_time = time.time()
python_list = list(range(size))
python_result = [x * 2 for x in python_list]
list_time = time.time() - start_time

# NumPy 数组操作
start_time = time.time()
numpy_array = np.arange(size)
numpy_result = numpy_array * 2
numpy_time = time.time() - start_time

print(f"数据大小: {size:,} 个元素")
print(f"Python 列表时间: {list_time:.4f} 秒")
print(f"NumPy 数组时间: {numpy_time:.4f} 秒")
print(f"NumPy 速度提升: {list_time/numpy_time:.1f}x")

# 内存使用比较
import sys
list_memory = sys.getsizeof(python_list) + sum(sys.getsizeof(x) for x in python_list[:100])  # 估算
numpy_memory = numpy_array.nbytes

print(f"\nPython 列表内存使用(估算): {list_memory:,} 字节")
print(f"NumPy 数组内存使用: {numpy_memory:,} 字节")
print(f"内存效率提升: {list_memory/numpy_memory:.1f}x")

本章小结

在这一章中,我们深入学习了:

  • NumPy 数组的基本概念和特点
  • 多种数组创建方法
  • 数组的基本属性和信息获取
  • 数据类型系统和类型转换
  • 数组形状操作(重塑、展平)
  • 数组的拼接和分割
  • 视图和副本的区别
  • 实际应用示例
  • 性能优势分析

下一步

在下一章中,我们将学习 NumPy 数组的索引和切片操作,这是数据操作的基础技能。

练习题

  1. 创建一个 5x5 的数组,填充从 1 到 25 的数字,然后将其重塑为 25x1 的数组
  2. 创建两个 3x3 的随机数组,将它们水平和垂直拼接
  3. 创建一个包含 100 个随机数的数组,将其分割为 10 个相等的部分
  4. 比较不同数据类型(int8, int32, float32, float64)的内存使用情况
  5. 创建一个模拟温度数据的数组,实现数据的标准化(均值为0,标准差为1)

本站内容仅供学习和研究使用。