数据分析基础numpy

前面章节都是讲述python基础的,因为自己基本已经掌握,所以跳过。

一、numpy简介

  • numpy:开源的python科学计算模块,用于数据快速处理;
  • numpy支持矩阵与数组操作,计算速度快,是Python中科学计算的基础库;
  • numpy优点:
    • 底层使用C语言实现,计算速度快
    • numpy支持均值,累积和,方差等运算,可以直接使用;
    • numpy处理数据方式灵活,支持excel, csv等多种方式数据导入;

二、numpy安装

方式1:pip install numpy

方式2:anaconda环境:自带numpy,不用安装

numpy官方文档:https://numpy.org/doc/

numpy源码:https://github.com/numpy/numpy

三、numpy使用

1
2
3
4
5
6
7
8
9
10
11
#感受numpy
import numpy as np
values = np.array([1,2,3,4,5])
print(values)
print(type(values))
print(values.shape)

#执行结果:
[1 2 3 4 5]
<class 'numpy.ndarray'>
(5,)

3.1 ndarray

  • 1.numpy中基本数据结构;
  • 2.ndarray对象索引从0开始
  • 3.所有元素是同一种类型;
  • 4.与列表类似,支持切片等操作;

在NumPy中,ndarray是核心的多维数组对象,其名称是N-dimensional array的缩写,直译为“N维数组”

3.2 创建ndarray对象

3.2.1 array方法

格式:numpy.array(object, dtype=None, copy=True, order='K', subok=False, ndmin=0)

  • object(输入对象,必填)

    • 这是你想要转换的原始数据。它可以是 Python 列表、元组、多维嵌套列表,甚至是另一个现有的 NumPy 数组。
  • dtype(数据类型,选填)

    指定生成的数组内部元素的硬性数据类型(如 np.int32, np.float64, np.bool_)。

    • 如果不传(默认):NumPy 会启动自动推断机制。如果列表里同时有整数和浮点数,它会自动统一提升为 float64;如果混入了 None 或字符串,则会像我们之前讨论过的那样,降级退化为 object 类型。
  • copy(是否复制,默认 True)

    决定是否在内存中开辟一块全新的空间来复制这份数据。

    • True:无论如何,都在内存里完整复制一份新数据,修改新数组绝对不会影响原对象。
    • False:如果输入对象本身已经是一个 ndarray,NumPy 会尽可能直接引用原内存地址(不复制),省内存且速度极快。
  • order(内存布局顺序,默认 'K')

    决定多维数组在计算机底层内存底层的行与列是怎么一维线性排列的。

    • 'C'C 语言风格。在内存中按行连续存放(一行一行存)。
    • 'F'Fortran 语言风格。在内存中按列连续存放(一列一列存)。
    • 'K'(默认):保留原有风格。自动检测输入对象的内存布局,尽可能贴近并维持它原本的顺序。
  • subok(是否允许子类,默认 False)

    • False(默认):强行把返回的对象转换为最标准的 np.ndarray 基本类型。
    • True:如果输入对象是 ndarray 的子类(例如矩阵子类 np.matrix),则会保留子类的身份,允许子类传递。
  • ndmin(最小维度,默认 0)

    强制指定生成的数组必须满足的最小维度

    • 如果你传入一个普通的扁平列表 [1, 2, 3](原本是一维数组),若设置 ndmin=2,NumPy 会自动在外面给你套一层中括号,强行将其升维拉伸成一个二维矩阵 [[1, 2, 3]](形状为 (1, 3))。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#理解:objec,dtype,ndmin
#一维ndarray
arr1=np.array([1,2,3])
print(arr1)
#二维ndarray,元素类型为float32
arr2=np.array([1,2,3], dtype='f' ,ndmin=2)
print(arr2)
print(f"type(arr1)={type(arr1)} ,arr1.dtype={arr1.dtype} ,arr1.ndim={arr1.ndim}")
print(f"type(arr2)={type(arr2)} ,arr2.dtype={arr2.dtype} ,arr2.ndim={arr2.ndim}")

#执行结果:
[1 2 3]
[[1. 2. 3.]]
type(arr1)=<class 'numpy.ndarray'> ,arr1.dtype=int32 ,arr1.ndim=1
type(arr2)=<class 'numpy.ndarray'> ,arr2.dtype=float32 ,arr2.ndim=2

3.2.2 ndarray轴与秩

  • 轴(axis):每一个线性的数组称为是一个轴;
    • 第一个轴(axis=0):第一层数组,
    • 第二个轴(axis=1):数组里的数组
    • 依次类推;
  • 秩(rank):维度
  • 示意图:
    • image-20250610221842855

3.2.3 ndarray相关属性

参数 说明 用途说明或示例
object 类似数组对象,例如:序列、range对象等 用于构造数组的原始数据。
示例:np.array([1, 2, 3]),其中 [1, 2, 3] 是 object 参数
dtype 元素类型 指定数组中元素的数据类型。
示例:np.array([1, 2, 3], dtype=float) 生成 float 类型数组
order 数据在内存排列形式 'C' 表示按行优先(C语言风格),'F' 表示按列优先(Fortran风格)。
示例:np.array([[1,2],[3,4]], order='F')
ndmin 指定维度 指定数组的最小维度。
示例:np.array([1, 2, 3], ndmin=2) 结果为 [[1, 2, 3]](二维)
1
2
3
4
5
6
7
8
9
10
11
12
13
nd = np.array([1,2,3], ndmin=2)
print(f"nd={nd}")
print(f'ndim:{nd.ndim}')
print(f'shape:{nd.shape}')
print(f'size:{nd.size}')
print(f'dtype:{nd.dtype}')

#执行结果
nd=[[1 2 3]]
ndim:2
shape:(1, 3)
size:3
dtype:int32

为什么 shape:(1, 3)

  • 你使用了参数 ndmin=2,这是指定最小维度为2维
  • 原始数据 [1, 2, 3] 是一维数组(形状为 (3,))。
  • 使用 ndmin=2 后,NumPy 自动在最前面加一维,使其变为二维数组:形状变成 (1, 3),即 1行3列
1
2
[1, 2, 3]      →       [[1, 2, 3]]
shape=(3,) shape=(1, 3)

为什么 size: 3

  • size 表示数组中总元素个数
  • 虽然 nd 的形状是 (1, 3),它的元素还是 1, 2, 3,总共 3 个数。
  • 所以:size = 1 × 3 = 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
nd2 = np.array([[1,2,3,4],[5,6,7,8]], ndmin=2)
print(f"nd2={nd2}") #[[1 2 3 4] [5 6 7 8]]
print(f'ndim:{nd2.ndim}') #2
print(f'shape:{nd2.shape}') #(2, 4)
print(f'size:{nd2.size}') #8
print(f'dtype:{nd2.dtype}') #int32

#执行结果
nd2=[[1 2 3 4]
[5 6 7 8]]
ndim:2
shape:(2, 4)
size:8
dtype:int32

3.2.4 创建ndarray对象常用方法

方法 说明
np.zeros(shape, dtype=float, order='C') 根据指定shape创建默认值为0的ndarray对象
np.empty(shape, dtype=float, order='C') 根据指定shape创建默认值为随机数的ndarray对象
np.ones(shape, dtype=float, order='C') 根据指定shape创建默认值为1的ndarray对象
np.full(shape, fill_value, dtype=None, order='C') 根据指定shape创建默认值为fill_value的ndarray对象
np.arange([start, ]stop, [step, ]dtype=None) 类似于range, 返回ndarray对象
np.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0) 根据给定起始值与数量,返回ndarray对象
np.zeros_like/empty_like/ones_like(a, dtype=None, order='K', subok=True) 根据给定array返回相同形状的ndarray对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#一维
a = np.zeros(10)
print('zeros:\n',a)
#二维
a = np.ones((2,10))
print('ones:\n',a)
#根据指定数据形状
b = np.empty_like(a)
print('empty_like:\n',b)
#arange:
a = np.arange(0,20, 2)
print('arange:\n',a)
#linspace
c = np.linspace(1, 5, num=5)
print('linspace1:\n', c)
c = np.linspace(1, 5, num=7)
print('linspace2:\n', c)

#执行结果:
zeros:
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
ones:
[[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]]
empty_like:
[[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]]
arange:
[ 0 2 4 6 8 10 12 14 16 18]
linspace1:
[1. 2. 3. 4. 5.]
linspace2:
[1. 1.66666667 2.33333333 3. 3.66666667 4.33333333
5. ]

为什么b的值与c = np.linspace(1, 5, num=5); print('linspace1:\n', c) 一样?

  1. empty_like 的预期行为 np.empty_like(a) 会创建一个与 a 形状和数据类型相同的数组,但 ​​不初始化内存​​(即内存中的值是随机的)。理论上,输出应为随机值。

  2. 实际输出全1的可能原因

    • 内存复用:NumPy 可能复用了 a 的内存空间(尤其是当 a 刚被释放时),导致 b 直接继承了 a 的值。
    • 内存清零优化:某些系统或环境(如 Docker、特定版本的 NumPy)可能在分配内存时自动清零,导致“未初始化”的值表现为0或重复之前的数据。
    • 缓存机制:操作系统的内存管理可能将之前存储 a 的内存块快速分配给 b,而未覆盖原有数据。
  3. 关键点 empty_like​不保证​​输出随机值,它只是 ​**​不主动初始化内存释放时),导致 b 直接继承了 a 的值。

    • 实际值取决于内存的当前状态。
  4. 生成随机数时指定形状:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    a = np.ones((2,10))
    b = np.random.rand(*a.shape) # 生成随机数组
    print(b)

    #执行结果:
    [[0.2423814 0.31150916 0.03527437 0.83015459 0.82835642 0.41430738
    0.3986594 0.60127557 0.31258882 0.85706738]
    [0.8419892 0.37580216 0.93050322 0.87510586 0.03349094 0.66328732
    0.96759445 0.15272788 0.32526041 0.97141136]]

3.2.5 np.radom相关方法

可以用来产生随机小数或整数。

方法 说明
np.random.rand(d0, d1, ..., dn) 根据给定形状产生随机值
np.random.randint(low, high=None, size=None, dtype='l') 根据指定范围产生整数

np.random.randint 方法 dtype 参数的意义

  • 作用 dtype 控制输出随机整数的数据类型,例如 int32int64np.uint8 等。默认值为 'l'(即 np.int_,通常对应 int64int32,具体取决于平台)
  • 可选值
    • 支持标准的整数类型字符串(如 'i''l''u')或具体的 NumPy 类型(如 np.int32)。
    • 例如:dtype='int64' 会生成 64 位有符号整数
  • 默认行为 若未显式指定 dtype,函数会使用默认的 'l'(长整型),其实际位宽由系统决定
  • 注意事项
    • 如果指定的 dtype 范围无法覆盖 [low, high) 区间(如 dtype='uint8'high=1000),可能导致溢出或未定义行为
    • 在需要跨平台一致性时,建议显式指定 dtype(如 dtype=np.int64
1
2
3
4
5
6
7
8
9
10
11
12
#根据指定形状产生随机值(每个元素大小在[0,1)之间)
print("random.rand:\n", np.random.rand(2,3))
#根据指定形状产生指定范围随机值(每个元素大小在[10,20)之间)
print('random.randint:\n', np.random.randint(10, 20, size=(2,10)))

#执行结果:
random.rand:
[[0.06593713 0.12196725 0.89109085]
[0.46266052 0.35745067 0.68436258]]
random.randint:
[[19 17 17 11 16 10 11 16 18 15]
[19 12 10 16 15 18 19 13 13 16]]

3.2.6 reshape方法

格式:array.reshape(shape, order='C')

作用:调整array的新装,返回新的ndarray对象

1
2
3
4
5
6
7
8
9
10
11
12
#示例:
a = np.arange(10)
b = a.reshape(2,5)
print('a.reshape(2,5):\n',b)
print('b.reshape(10):\n', b.reshape(10))

#执行结果:
a.reshape(2,5):
[[0 1 2 3 4]
[5 6 7 8 9]]
b.reshape(10):
[0 1 2 3 4 5 6 7 8 9]

  • 理解下order中的C与F:
    • 二维array对象:
      • a = [[1,2],[3,4]]
    • C代表在C语言中数据在内存存储方式;
      • a[0][0],a[0][1],a[1][0],a[1][1]
    • F代表在Fortran语言中数据在内存存储方式;
      • a[0][0],a[1][0],a[0][1],a[1][1]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#示例:
a = np.arange(10)
b = a.reshape(2,5)
print('b:\n', b)
c = b.reshape(10, order='C')
d = b.reshape(10, order='F')
print('c:\n',c)
print('d:\n',d)

#执行结果:
b:
[[0 1 2 3 4]
[5 6 7 8 9]]
c:
[0 1 2 3 4 5 6 7 8 9]
d:
[0 5 1 6 2 7 3 8 4 9]

3.2.7 ndarray对象转其他数据结构

a = np.arange(10)

方法 说明
a.tolist() 转成列表
a.tostring(order='C') 转成 bytes。此方法在NumPy 1.19.0中已被移除
a.tobytes(order='C') 转成 bytes
a.tofile(fid, sep="", format="%s") 保存到文件,fid:打开文件或者路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#示例:
a = np.arange(10)
print(a.tolist())
print(a.tobytes())
with open('test.txt', 'w') as fid:
# 将一维数组转换为字符串并写入文件
#a_str = a.astype(str)
#print(a_str)
#a_str.tofile(fid, sep="\n", format="%s")
a.tofile(fid, sep="\n", format="%s")

#执行结果:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
b'\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\t\x00\x00\x00\x00\x00\x00\x00'
#最后的a.tofile(...)将在当前目录下产生一个文件test.txt,内容如下:
1
2
3
4
5
6
7
8
9
10
0
1
2
3
4
5
6
7
8
9

3.3 numpy的数据类型

类型 类型代码 说明
int8/16/32/64 i1/i2/i4/i8 有符号8/16/32/64位整数
uint8/16/32/64 u1/u2/u4/u8 无符号8/16/32/64位整数
float16/32/64 f2/f4/f8 半精度16位浮点数 / 单精度32位浮点数 / 双精度64位浮点数
complex64/128 c8/c16 单/双精度复数(实部虚部各占一半)
bool b1 布尔类型(True/False)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#示例:
import numpy as np有符号8/16/32/64位整数 |
|
int8 = np.array([1, 2, 3], dtype='i1') # 使用 i1 表示 int8
print(f"int8={int8}")
int16 = np.array([1, 2, 3], dtype='i2') # 使用 i2 表示 int16
print(f"int16={int16}")
arr_float16 = np.array([1.0, 2.0], dtype='f2') # 使用 f2 表示 float16
print(f"arr_float16={arr_float16}")

arr_float32 = np.array([1.0, 2.0], dtype='f4') # 使用 f4 表示 float32
print(f"arr_float32={arr_float32}")
arr_complex = np.array([1+2j], dtype='c16') # 双精度复数
print(f"arr_complex={arr_complex}")
arr_bool = np.array([True, False], dtype='b1') # 布尔类型
print(f"arr_bool={arr_bool}")


#执行结果:
int8=[1 2 3]
int16=[1 2 3]
arr_float16=[1. 2.]
arr_float32=[1. 2.]
arr_complex=[1.+2.j]
arr_bool=[ True False]

3.4 numpy访问与修改

numpy访问与列表类似,支取切片操作。

3.4.1 一维array

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 输出:import numpy as np
a = np.arange(25)
#索引0对应值:
v = a[0]
print(v)
#索引为10值:
v = a[10]
print(v)
#切片操作:
v = a[10:20]
print(v)
v = a[20:]
print(v)
v = a[10:20:2]
print(v)
v = a[20:10:-2]
print(v)
1
2
3
4
5
6
7
#执行结果:
0
10
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24]
[10 12 14 16 18]
[20 18 16 14 12]

3.4.2 理解numpy中的轴

一维array,轴:0

二维array,轴:0,1

三维array,轴:0,1,2

image-20250611221003668

3.4.3 二维array

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np
a = np.arange(25)
#2x2矩阵
a = a.reshape(5,5)
print(a)
#取a[0]
print('->a[0]:\t\t',a[0])
#取a[0][0]
print('->a[0][0]:\t',a[0][0])

#执行结果:
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]
[15 16 17 18 19]
[20 21 22 23 24]]
->a[0]: [0 1 2 3 4]
->a[0][0]: 0

3.4.4 三维array

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import numpy as np
a = np.arange(27)
#3维数组
a = a.reshape(3,3,3)
print(f"a:\n{a}")
#取a[0]
print(f"->a[0]:\n{a[0]}")
#取a[0][0]
print('->a[0][0]:\n',a[0][0])
print('->a[0][0][0]:\n',a[0][0][0])


# 执行结果:
a:
[[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]]

[[ 9 10 11]
[12 13 14]
[15 16 17]]

[[18 19 20]
[21 22 23]
[24 25 26]]]
->a[0]:
[[0 1 2]
[3 4 5]
[6 7 8]]
->a[0][0]:
[0 1 2]
->a[0][0][0]:
0

3.4.5 多维数据取值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import numpy as np
a = np.arange(25)
#5x5矩阵
a = a.reshape(5,5)
print(f"a:\n{a}")
print("-------------------------------------")
#取行
print(f"a[0]:\n{a[0]}") # 第0行
#取某个元素
print(f"a[0][1]:\n{a[0][1]}") # 第0行第1列
print(f"a[0,1]:\n{a[0,1]}") # 第0行第1列,等价于上面

#执行结果:
a:
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]
[15 16 17 18 19]
[20 21 22 23 24]]
-------------------------------------
a[0]:
[0 1 2 3 4]
a[0][1]:
1
a[0,1]:
1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#取指定多行:
print(f"a[[1,2,4]]:\n{a[[1,2,4]]}") # 第1、2、4行

#切片取行:
"""
在Python和NumPy中,切片操作遵循 start:stop:step的通用语法规则,其中:
start:起始索引(包含该位置)。
stop:结束索引(不包含该位置)。
step:步长,即取下一个元素时的索引增量。
"""
# 所以如下命令表示取a第0行到第5行(前开后闭),步长为2。即表示取第0、2、4行
print(f"a[:5:2]:\n{a[:5:2]}")
print("-------------------------------------")

#切片取列,取第1列
print(f"a[:,1]\n{a[:,1]}")
# 第一个维度(行)用 :表示“所有行”
# 第二个维度(列)用 1表示“第 2 列”
"""
正确记忆口诀:
a[行索引, 列索引]
:表示“所有”
"""
print(f"a[:,1]\n{a[:,1]}")
# 取所有行的第1、3列
print(f"a[:,[1,3]]\n{a[:,[1,3]]}")
# 所有行,第1列开始到最后一列,间一列取一列
print(f"a[:,1::2]\n{a[:,1::2]}")
# 对第0行到第3行,第1列开始到最后一列,间一列取一列
print(f"a[0:3,1::2]\n{a[0:3,1::2]}")
# 取所有行,步长为-1(即反向步进)
print(f"a[:, ::-1]\n{a[:, ::-1]}")
print("-------------------------------------")

#执行结果:
a[[1,2,4]]:
[[ 5 6 7 8 9]
[10 11 12 13 14]
[20 21 22 23 24]]
a[:5:2]:
[[ 0 1 2 3 4]
[10 11 12 13 14]
[20 21 22 23 24]]
a[:,1]
[ 1 6 11 16 21]
-------------------------------------
>>> print(f"a[:,1]\n{a[:,1]}")
a[:,1]
[ 1 6 11 16 21]
>>> print(f"a[:,[1,3]]\n{a[:,[1,3]]}")
a[:,[1,3]]
[[ 1 3]
[ 6 8]
[11 13]
[16 18]
[21 23]]
>>> print(f"a[:,1::2]\n{a[:,1::2]}")
a[:,1::2]
[[ 1 3]
[ 6 8]
[11 13]
[16 18]
[21 23]]
>>> print(f"a[0:3,1::2]\n{a[0:3,1::2]}")
a[0:3,1::2]
[[ 1 3]
[ 6 8]
[11 13]]
>>> print(f"a[:, ::-1]\n{a[:, ::-1]}")
a[:, ::-1]
[[ 4 3 2 1 0]
[ 9 8 7 6 5]
[14 13 12 11 10]
[19 18 17 16 15]
[24 23 22 21 20]]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#创建一个三维数组
b = np.arange(27)
b = b.reshape(3,3,3)
print(f"b:\n{b}") # 三维数组
print(f"b[[[0,2]]]:\n{b[[[0,2]]]}") # 第0、2层
b[[[0,2]]] = 100
print(f"b after modification:\n{b}") # 修改后的三维数组

#执行结果:
b:
[[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]]

[[ 9 10 11]
[12 13 14]
[15 16 17]]

[[18 19 20]
[21 22 23]
[24 25 26]]]
b[[[0,2]]]:
[[[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]]

[[18 19 20]
[21 22 23]
[24 25 26]]]]
b after modification:
[[[100 100 100]
[100 100 100]
[100 100 100]]

[[ 9 10 11]
[ 12 13 14]
[ 15 16 17]]

[[100 100 100]
[100 100 100]
[100 100 100]]]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
print("-------------------------------------")
#切片取行:
print(f"a[:5:2]:\n{a[:5:2]}") # 每隔一行取一次
#取第一列:
print(f"a[:,1]:{a[:,1]} , type(a[:,1])={type(a[:,1])}\n") # 所有行的第二列
print("-------------------------------------")
#取指定多列
print(f"a[:,[1,3]]:\n{a[:,[1,3]]}") # 所有行的第二、四列
#切片取指定多列
print(f"a[:,::2]:\n{a[:,::2]}") # 所有行的偶数列



#执行结果:
-------------------------------------
a[:5:2]:
[[ 0 1 2 3 4]
[10 11 12 13 14]
[20 21 22 23 24]]
a[:,1]:[ 1 6 11 16 21] , type(a[:,1])=<class 'numpy.ndarray'>

-------------------------------------
a[:,[1,3]]:
[[ 1 3]
[ 6 8]
[11 13]
[16 18]
[21 23]]
a[:,::2]:
[[ 0 2 4]
[ 5 7 9]
[10 12 14]
[15 17 19]
[20 22 24]]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int(f"a[:,[1,3]]:\n{a[:,[1,3]]}")  # 所有行的第二、四列print("-------------------------------------")
#取指定多个元素
"""
花式索引(Fancy Indexing)​​,用于从二维数组中提取特定位置的元素
​​[1,3,4]​​:表示行索引(第2、4、5行,索引从0开始)。
​​[2,3,4]​​:表示列索引(第3、4、5列)。
​​配对规则​​:行索引和列索引按位置一一对应,提取 (行,列) 坐标处的元素
"""
print(f"a[[1,3,4],[2,3,4]]:\n{a[[1,3,4],[2,3,4]]}") # 取(1,2)、(3,3)、(4,4)位置的元素

print(f"a[1:4, 2:5]:\n{a[1:4, 2:5]}") # 切片取指定范围的元素
#取指定行列
print(f"a[1:3,:3]:\n{a[1:3,:3]}") # 取第2、3行和前3列的元素


#执行结果:
-------------------------------------
a[[1,3,4],[2,3,4]]:
[ 7 18 24]
a[1:4, 2:5]:
[[ 7 8 9]
[12 13 14]
[17 18 19]]
a[1:3,:3]:
[[ 5 6 7]
[10 11 12]]
索引方式 示例 作用 返回值类型
花式索引 a[[1,3,4],[2,3,4]] 提取多个指定坐标的元素 一维数组
多维索引(单个) a[1,2] 提取单个元素 标量值
切片索引 a[1:4, 2:5] 提取连续的行和列的子数组 二维数组
操作类型 语法示例 功能说明 返回值类型
单行访问 a[0] 获取第1行所有元素 一维数组
单元素访问 a[0][1]a[0,1] 获取第1行第2列的元素(两种写法等价) 标量值
多行访问 a[[1,2,4]] 获取第2、3、5行的所有元素(花 返回值类型
:--------------: :----------------式索引) 二维数组
切片取行 a[:5:2] 每隔一行取一次(步长2),获取第1、3、5行 二维数组
单列访问 a[:,1] 获取所有行的第2列 一维数组
多列访问 a[:,[1,3]] 获取所有行的第2、4列(花式索引) 二维数组
切片取列 a[:,::2] 获取所有行的偶数列(步长2) 二维数组
坐标点访问 a[[1,3,4],[2,3,4]] 获取(1,2)、(3,3)、(4,4)位置的元素(行/列索引一一配对) 一维数组
矩形区域切片 a[1:4, 2:5] 获取第2-4行、第3-5列的子矩阵(左闭右开) 二维数组
混合切片 a[1:3,:3] 获取第2-3行、前3列的子矩阵 二维数组

关键说明:

  1. 花式索引:通过列表指定不连续的位置(如a[[1,3,4]]),返回的是副本而非视图

  2. 切片语法start:stop:step,左闭右开区间,可用于行/列维度

  3. 多维索引:逗号分隔不同维度的索引(如a[0,1]a[0][1]更高效)

  4. 类型区别

    • 单行/单列返回一维数组
    • 多行/多列返回二维数组
    • 标量访问返回Python原生类型
  5. 扩展应用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    布尔索引(条件筛选)布尔索引(条件筛选)# 高级花式索引(获取矩形区域)
    a[np.ix_([1,3,4], [2,3,4])] # 等价于a[1:5:2, 2:5]
    #执行结果:
    [[ 7 8 9]
    [17 18 19]
    [22 23 24]]

    # 布尔索引(条件筛选)
    a[a > 20] # 获取所有大于20的元素
    #执行结果:
    [21 22 23 24]

3.4.6 array修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#3.4.6 array修改
import numpy as np
a = np.arange(10)
print(a) #[0 1 2 3 4 5 6 7 8 9]

#修改一个元素
a[0] = 30
print(a) #[30 1 2 3 4 5 6 7 8 9]
#修改多个元素
a[:5] = 0
print(a) #[0 0 0 0 0 5 6 7 8 9]

a[1:3] = [20, 30]
print(a) #[0 20 30 0 0 5 6 7 8 9]

3.5 numpy计算

3.5.1 numpy广播:boardcasting

基本运算被应用到array所有的元素中。

1
2
3
4
5
a = np.arange(10)
print(a)
print(a*10)
print(a+1)
print(a/2)
1
2
3
4
5
#执行结果
[0 1 2 3 4 5 6 7 8 9]
[ 0 10 20 30 40 50 60 70 80 90]
[ 1 2 3 4 5 6 7 8 9 10]
[0. 0.5 1. 1.5 2. 2.5 3. 3.5 4. 4.5]

3.5.2 array之间计算

1
2
3
4
5
6
7
8
9
#3.5.2 array之间计算
a = np.arange(5)
b = np.array([1,2,3,4,5])
# a与b的数据类型都是: <class 'numpy.ndarray'>

print(a)
print(b)
print(a+b)
print(a*b)
1
2
3
4
[0 1 2 3 4]
[1 2 3 4 5]
[1 3 5 7 9]
[ 0 2 6 12 20]

3.5.3 多维array之间计算

1
2
3
4
5
6
7
#3.5.3 多维array之间计算
t = np.arange(10).reshape(2,5)
a = np.ones_like(t)
b = np.full_like(t, 10)
print(a)
print(b)
print(a+b)
1
2
3
4
5
6
[[1 1 1 1 1]
[1 1 1 1 1]]
[[10 10 10 10 10]
[10 10 10 10 10]]
[[11 11 11 11 11]
[11 11 11 11 11]]

3.5.4 基本计算

主要包括:求和,均值,方差,累积和等;

numpy模块与array对象都支持这些方法,使用方式也类似,我们来看一种即可;

这些方法参数类似,我们以sum为例:

1
2
np.sum(a, axis=None, dtype=None, out=None, keepdims=, initial=)
a.sum(axis=None, dtype=None, out=None, keepdims=False)

numpy中常用计算相关方法:

方法 说明
np.mean() 计算均值
np.max() 最大值
np.min() 最小值
np.cumsum() 计算累加和
np.std() 计算标准差
np.var() 计算方差(方差是各数据点与均值之差的平方的平均值)
np.cov() 计算协方差
np.average() 计算均值
np.max() 均值最大值
np.median() 计算中位数
np.ptp() 计算极值(最大值与最小值差)
1
2
3
4
5
6
7
8
9
10
#3.5.4 基本计算
import numpy as np
a = np.arange(10)
print(a)
#求和,最大,最小,极差
print(np.sum(a), np.max(a), np.min(a), np.ptp(a))
#方差,中位数,
print(np.var(a), np.median(a))
#累加和:[a[0], a[0]+a[1], a[0]+a[1]+a[2], ...]
print(np.cumsum(a))
1
2
3
4
[0 1 2 3 4 5 6 7 8 9]
45 9 0 9
8.25 4.5
[ 0 1 3 6 10 15 21 28 36 45]

3.5.5 多维array计算

3.5.5.1 示例

多维array指定axis,可以得到不同效果,在计算常用指标,发挥奇效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#3.5.5 多维array计算
import numpy as np
a = np.arange(10)
a = a.reshape(2,5)
print(a)
#指定axis为1,按行进行计算
print(np.sum(a, axis=1))
#指定axis为0:按列进行计算
print(np.sum(a, axis=0))
#指定axis为None:整体计算
print(np.sum(a, axis=None))
"""
想象数组是 (行, 列):
axis=0​ 是行维度​ → 操作是跨行的 → 对每列操作
axis=1​ 是列维度​ → 操作是跨列的 → 对每行操作
用这个技巧:指定哪个axis,哪个维度就“消失”(被聚合了)。
np.sum(a, axis=0)→ 形状从 (2,5) 变成 (5,) (行维度没了)
np.sum(a, axis=1)→ 形状从 (2,5) 变成 (2,) (列维度没了)
"""

#执行结果:
[[0 1 2 3 4]
[5 6 7 8 9]]
[10 35]
[ 5 7 9 11 13]
45
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np
a = np.arange(9)
a = a.reshape(3, 3)
print(a)
#指定axis为1,按行进行计算
print(np.sum(a, axis=1))
#指定axis为0:按列进行计算
print(np.sum(a, axis=0))
#指定axis为None:整体计算
print(np.sum(a, axis=None))

#执行结果:
[[0 1 2]
[3 4 5]
[6 7 8]]
[ 3 12 21]
[ 9 12 15]
36
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
b = np.arange(24).reshape(2,3,4)  # 形状 (深度, 行, 列)
# axis=0 → 跨深度(第1个维度)
# axis=1 → 跨行(第2个维度)
# axis=2 → 跨列(第3个维度)
>>> b
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]],

[[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]]])

# 可以很鲜明地看到,axis指定哪个维度,哪个维度就进行聚合然后消失了
# 指定axis=0,维度0聚合然后消失,形状从(2,3,4)变成(3,4)
>>> np.sum(b, axis=0)
array([[12, 14, 16, 18],
[20, 22, 24, 26],
[28, 30, 32, 34]])

# 指定axis=1,维度1聚合然后消失,形状从(2,3,4)变成(2,4)。
>>> np.sum(b, axis=1)
array([[12, 15, 18, 21],
[48, 51, 54, 57]])

# 指定axis=2,维度2聚合然后消失,形状从(2,3,4)变成(2,3)
>>> np.sum(b, axis=2)
array([[ 6, 22, 38],
[54, 70, 86]])
# 相当于没有指定维度,则对所有数据进行求和
>>> np.sum(b, axis=None)
276

3.5.5.2 np.sum(b, axis=2)语句的字面含义与运行结果

这两行代码展示了 NumPy 中最核心的两个操作:高维张量的构建(维度重塑)沿指定轴的降维聚合(求和)

执行这段代码后的最终输出结果为一个 (2, 3) 的二维矩阵:

Python

1
2
[[  6,  22,  38],
[ 54, 70, 86]]

3.5.5.3 第一步:reshape(2, 3, 4) 的高维空间构建

首先,np.arange(24) 生成了一个包含 0 到 23 的一维连续整型数组。接下来的 .reshape(2, 3, 4) 将这 24 个数字重新揉成了一个 三维张量(你可以把它想象成一本书,或者一栋建筑)。

可以把这三个维度(形状为 (2, 3, 4))拆解为如下的空间结构:

  • axis=0(长度为 2):代表有 2 个页面(或 2 个独立矩阵,即“层”)。
  • axis=1(长度为 3):代表每个页面里有 3 行
  • axis=2(长度为 4):代表每行里面有 4 列(即一维线条上的 4 个连续数字)。

在内存中,三维数组 b 的物理分布和逻辑排布如下:

1
2
3
4
5
6
7
8
9
# 第 0 页 (b[0, :, :])
[[ 0, 1, 2, 3], # 第 0 行
[ 4, 5, 6, 7], # 第 1 行
[ 8, 9, 10, 11]] # 第 2 行

# 第 1 页 (b[1, :, :])
[[12, 13, 14, 15], # 第 0 行
[16, 17, 18, 19], # 第 1 行
[20, 21, 22, 23]] # 第 2 行

3.5.5.4 第二步:np.sum(b, axis=2) 的沿轴降维机制

这是最关键的聚合运算。在 NumPy 中,“沿着某个 axis 操作”的本质含义是:把该 axis 对应的维度彻底“压扁、熔化”,而保持其他维度的结构不改变。

由于指定了 axis=2(列维,长度为 4):

  1. NumPy 会锁定 axis=0(页)和 axis=1(行)的相对位置不变。
  2. 拿着扫描仪进入每一个具体的“行”,把该行内部的 4 个列数字全部加在一起,融合成一个单一的数字
3.5.5.4.1 底层具体的求和演变路径
  • 在第 0 页:
    • 第 0 行:将 [0, 1, 2, 3] 熔化求和 \(\rightarrow 0 + 1 + 2 + 3 = \mathbf{6}\)
    • 第 1 行:将 [4, 5, 6, 7] 熔化求和 \(\rightarrow 4 + 5 + 6 + 7 = \mathbf{22}\)
    • 第 2 行:将 [8, 9, 10, 11] 熔化求和 \(\rightarrow 8 + 9 + 10 + 11 = \mathbf{38}\)
  • 在第 1 页:
    • 第 0 行:将 [12, 13, 14, 15] 熔化求和 \(\rightarrow 12 + 13 + 14 + 15 = \mathbf{54}\)
    • 第 1 行:将 [16, 17, 18, 19] 熔化求和 \(\rightarrow 16 + 17 + 18 + 19 = \mathbf{70}\)
    • 第 2 行:将 [20, 21, 22, 23] 熔化求和 \(\rightarrow 20 + 21 + 22 + 23 = \mathbf{86}\)

3.5.5.5 形状消减公式(记忆捷径)

在进行任意高维数组的聚合计算(如 sum, mean, max)时,可以通过一个纯数学的“消减规则”来瞬间推导输出结果的形状:

  • 原始形状:(2, 3, 4)
  • 指定轴:axis=2
  • 消减结果:直接把索引为 2 的那个数字从元组里无情擦除 \(\rightarrow\) 剩下 (2, 3)

所以,原本包含 24 个元素的三维张量,经过 axis=2 的洗礼后,成功“退化”并坍缩为一个拥有 2 页、每页 3 行的 (2, 3) 二维矩阵

3.5.6 numpy其他计算相关方法

方法 说明
np.sqrt() 计算平方根(对每个元素计算平方根)
np.log() 计算对数
np.exp() 计算自然数的指数值
np.cos/sin/tan/ 三角函数
np.std() 计算标准差
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 3.5.6 numpy其他计算相关方法
import numpy as np
a = np.arange(10)
a = a.reshape(2,5)
>>> print(a)
[[0 1 2 3 4]
[5 6 7 8 9]]

print(np.sqrt(a))
print(np.log(a))
print(np.exp(a))
print(np.cos(a))
print(np.std(a))

>>> print(np.sqrt(a))
[[0. 1. 1.41421356 1.73205081 2. ]
[2.23606798 2.44948974 2.64575131 2.82842712 3. ]]
>>> print(np.log(a))
<stdin>:1: RuntimeWarning: divide by zero encountered in log
[[ -inf 0. 0.69314718 1.09861229 1.38629436]
[1.60943791 1.79175947 1.94591015 2.07944154 2.19722458]]
>>> print(np.exp(a))
[[1.00000000e+00 2.71828183e+00 7.38905610e+00 2.00855369e+01
5.45981500e+01]
[1.48413159e+02 4.03428793e+02 1.09663316e+03 2.98095799e+03
8.10308393e+03]]
>>> print(np.cos(a))
[[ 1. 0.54030231 -0.41614684 -0.9899925 -0.65364362]
[ 0.28366219 0.96017029 0.75390225 -0.14550003 -0.91113026]]
>>> print(np.std(a))
2.8722813232690143

3.6 numpy数据拼分割

3.6.1 多个array拼接

3.6.1.1 拼接的方法concatenate

拼接多个数组:concatenate((a1, a2, ...), axis=0, out=None)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
a = np.arange(1,5).reshape(2,2)
b = np.arange(5,9).reshape(2,2)
print(f"a= {a}")
a= [[1 2]
[3 4]]
print(f"b= {b}")
b= [[5 6]
[7 8]]

"""
可以看到:
以1轴进行拼接后,两个(2,2)形状的ndarray对象拼接后,得到的结果仍是ndarray类型,但形状变成了(2,4)
以0轴进行拼接后,两个(2,2)形状的ndarray对象拼接后,得到的结果仍是ndarray类型,但形状变成了(4,2)
"""
# 以1轴进行拼接
np.concatenate((a,b), axis=1)
array([[1, 2, 5, 6],
[3, 4, 7, 8]])

# 以0轴进行拼接
np.concatenate((a,b), axis=0)
array([[1, 2],
[3, 4],
[5, 6],
[7, 8]])

3.6.1.2 多个array拼接时的要求

比如要求它们的形状必须是一样的吗?

在 NumPy 中,多个数组(array)进行拼接时,不一定要求它们的形状(shape)完全一样,但它们必须满足一个非常严格的数学和空间几何规则。

这个规则可以概括为一句话:除了指定的拼接轴(axis)之外,其他维度的长度必须完全相同。

为了深刻理解拼接,可以从“一维、二维以及高维”三个场景由浅入深地来看:

3.6.1.2.1. 一维数组(Vectors):最宽松

对于一维数组,拼接(np.concatenatenp.append完全不要求形状一样,可以把任意长度的一维数组像接火车一样串联起来。

1
2
3
4
5
6
7
import numpy as np

a = np.array([1, 2, 3]) # 形状是 (3,)
b = np.array([4, 5]) # 形状是 (2,)

result = np.concatenate((a, b))
print(result) # 输出: [1 2 3 4 5] 形状变成 (5,)
3.6.1.2.2. 二维数组(Matrices):最经典的“对齐”规则

二维数组有两根轴:axis=0(纵向拼接,向下堆叠)和 axis=1(横向拼接,向右横摆)。

场景 A:纵向堆叠(axis=0

如果想把两个表格上下拼在一起,它们必须满足:列数(width)必须完全一样。至于行数(height),多长多短都可以。

  • 数组 A 的形状是 (行数1, 列数)
  • 数组 B 的形状是 (行数2, 列数)
1
2
3
4
5
6
7
8
9
10
11
a = np.array([[1, 2], 
[3, 4]]) # 形状 (2, 2)
b = np.array([[5, 6]]) # 形状 (1, 2) <- 行数不同(1和2),但列数都是2

# 上下拼接
res = np.concatenate((a, b), axis=0)
print(res)
# 输出:
# [[1 2]
# [3 4]
# [5 6]] 形状完美对齐,变成 (3, 2)
场景 B:横向拼接(axis=1

如果想把两个表格左右拼在一起,它们必须满足:行数(height)必须完全一样

  • 数组 A 的形状是 (行数, 列数1)
  • 数组 B 的形状是 (行数, 列数2)
1
2
3
4
5
6
7
8
9
10
11
a = np.array([[1, 2], 
[3, 4]]) # 形状 (2, 2)
b = np.array([[5, 6, 7],
[8, 9, 10]]) # 形状 (2, 3) <- 列数不同(2和3),但行数都是2

# 左右拼接
res = np.concatenate((a, b), axis=1)
print(res)
# 输出:
# [[ 1 2 5 6 7]
# [ 3 4 8 9 10]] 形状变成 (2, 5)

3.6.1.3. 高维数组(三维及以上):推广通用公式

在处理大模型中的 Embedding、图片数据(如 [Batch, Channel, Height, Width])时,拼接的规则依然遵循相同的逻辑。

假设有两个四维数组想要拼接:

  • 数组 A 的形状是:(2, 3, 4, 5)
  • 数组 B 的形状是:(2, 3, 9, 5)

观察它们的维度,只有 第三个维度(索引为2) 的数字不同(一个是 4,一个是 9)。因此,这两个数组只能axis=2 上进行拼接。

1
2
3
4
5
A=np.arange(120).reshape((2, 3, 4, 5))
B=np.arange(270).reshape((2, 3, 9, 5))
# 只有这样会成功,拼接后的形状变为 (2, 3, 13, 5)
c = np.concatenate((A, B), axis=2)
print(c.shape) # 输出:(2, 3, 13, 5)

如果尝试在 axis=0axis=1 上拼接它们,NumPy 就会直接抛出经典的错误:ValueError: all the input array dimensions except for the concatenation axis must match exactly

1
2
3
4
5
d=np.concatenate((A, B), axis=0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<__array_function__ internals>", line 200, in concatenate
ValueError: all the input array dimensions except for the concatenation axis must match exactly, but along dimension 2, the array at index 0 has size 4 and the array at index 1 has size 9

3.6.1.4 记忆小绝招

在实际工程开发(比如处理 RAG 向量召回矩阵、或者特征拼接)时,可以这样在脑海中脑补:

  • axis=0(纵向):像在积木塔往上叠积木,新积木和旧积木的底面积(其余维度)必须严丝合缝,高度(指定轴)无所谓。
  • axis=1(横向):像两个人并排站立,两人的身高(其余维度)必须一样高,身材胖瘦(指定轴)无所谓。 array at index 1 has size 9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#### 3.6.1.4 记忆小绝招

尓。

### 3.6.2 多个数组的堆叠

#### 3.6.2.1 堆叠操作

基本形式:`np.stack(arrays, axis=0, out=None) `

基本理解:arrays 沿着axis进行堆叠,类似穿起来,而不是拼接。例如:

```python
a = np.arange(0,2)
b = np.arange(2,4)
c = np.arange(4,6)

# 沿着axis=0这个维度进行拼接
d1=np.stack((a,b,c), axis=0)
# 沿着axis=1这个维度进行拼接
d2=np.stack((a,b,c), axis=1)

>>> print(a)
[0 1]
>>> print(b)
[2 3]
>>> print(c)
[4 5]
>>> print(d1) #结果是(3,2)
[[0 1]
[2 3]
[4 5]]
>>> print(d2) #结果是(2,3)
[[0 2 4]
[1 3 5]]

3.6.2 多个数组的堆叠

3.6.2.1 堆叠示例

np.stack(arrays, axis=0, out=None) 基本理解:arrays,沿着axis进行堆叠,类似穿起来,而不是拼接 例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
a = np.arange(0,2)
b = np.arange(2,4)
c = np.arange(4,6)
'''
[0 1]
[2 3]
[4 5] ->
沿着axis=0拼接:
[[0 1]
[2 3]
[4 5]]
'''
np.stack((a,b,c), axis=0)
1
2
3
4
5
6
7
8
9
10
11
12
a = np.arange(0,2)
b = np.arange(2,4)
c = np.arange(4,6)
'''
[0 1]
[2 3]
[4 5] ->
沿着axis=1拼接:
[[0 2 4]
[1 3 5]]
'''
np.stack((a,b,c), axis=1)

3.6.2.2 堆叠时的运行机制

为了彻底看清 axis=0axis=1 这两根轴在底层是如何像“隐形魔术手”一样操纵数据排列的,现在把这两个堆叠过程拆解为准备、升级、对齐、组合四个核心机制。

先看下三个初始的一维数组(形状都是 (2,)):

  • a = [0, 1]
  • b = [2, 3]
  • c = [4, 5]
3.6.2.2.1 机制一:d1 = np.stack((a, b, c), axis=0) 详细拆解

axis=0 时,NumPy 的核心机制是:在最外层(索引为 0 的位置)强行塞入一个全新的轴(维度),然后沿着行的方向,把数组一个接一个“上下堆叠”起来。

3.6.2.2.1.1 底层演变三步走:
  1. 开辟新维度(轴 0)

    NumPy 发现要在第 0 维(最外层)建新维度,于是先把 a, b, c 从一维的 (2,) 转换为临时的二维结构 (1, 2)

    • a 变成 [[0, 1]](第 0 轴长度为 1,第 1 轴长度为 2)
    • b 变成 [[2, 3]]
    • c 变成 [[4, 5]]
  2. 纵向堆叠(上下落盘子)

    因为新轴建在最外层(控制行数),NumPy 就像叠盘子一样,把这三份数据在纵向(向下)的维度上合并:

    • a 放在第 0 行
    • b 放在第 1 行
    • c 放在第 2 行
  3. 最终成型

    新轴(第 0 轴)容纳了 3 个数组,长度变成了 3;原来的轴(第 1 轴)长度依然是 2

    所以,d1.shape 变成了 (3, 2)(3行2列)。

    数据呈现为:

    1
    2
    3
    array([[0, 1],   # 这一行来自 a
    [2, 3], # 这一行来自 b
    [4, 5]]) # 这一行来自 c
3.6.2.2.2 机制二:d2 = np.stack((a, b, c), axis=1) 详细拆解

这是最让人头疼、也最容易混淆的操作。当 axis=1 时,NumPy 的核心机制是:把全新开辟的轴塞在内部(索引为 1 的位置),然后沿着列的方向,把数组一列一列“横向并排立起来”。

3.6.2.2.2.1 底层演变三步走:
  1. 开辟新维度(轴 1)

    NumPy 发现要在第 1 维(里层)建新维度。它会把 a, b, c(2,) 转换成形状为 (2, 1) 的列向量结构:

    • a 变成 [[0], [1]](第 0 轴长度为 2,第 1 轴长度为 1)
    • b 变成 [[2], [3]]
    • c 变成 [[4], [5]]
  2. 横向堆叠(并排立木棍)

    因为新轴建在内部(控制列数),NumPy 就像并排立木棍一样,在横向(向右)的维度上把它们组合在一起:

    • 在第一行:把 a 的第 1 个元素 0b 的第 1 个元素 2c 的第 1 个元素 4 横向拼成一行 [0, 2, 4]
    • 在第二行:把 a 的第 2 个元素 1b 的第 2 个元素 3c 的第 2 个元素 5 横向拼成一行 [1, 3, 5]
  3. 最终成型

    原先的轴(第 0 轴)长度依然是 2(保持 2 行不变);新生成的轴(第 1 轴)因为塞进了 3 个数组,长度变成了 3(变成 3 列)。

    所以,d2.shape 变成了 (2, 3)(2行3列)。

    数据呈现为:

    1
    2
    array([[0, 2, 4],   # 第一行:包含了 a, b, c 的各自第一个元素
    [1, 3, 5]]) # 第二行:包含了 a, b, c 的各自第二个元素
3.6.2.2.3 黄金总结:一张表看清本质区别

为了方便你在后续开发(如拼接深度学习特征向量)时瞬间反应过来,请牢记这个对照:

操作命令 新轴插入位置 几何动作印象 最终形状 (Shape) 最终数据特点
np.stack(..., axis=0) 最外层(前面) 叠盘子(上下堆叠) (3, 2) 每一个原始数组占一行
np.stack(..., axis=1) 最内层(后面) 立栅栏(左右并排) (2, 3)
3.6.2.2.4 np.stack 的硬性要求

无论是沿着哪根轴堆叠,np.stack() 要求参与堆叠的所有数组的形状(Shape)必须完全一模一样。 比如在你的例子中,a, b, c 的形状全部都是 (2,),所以它们能完美堆叠。如果突然加入一个 x = np.array([6, 7, 8])(形状是 (3,)),程序就会直接报错,因为大小不一致的物体是无法整齐地“叠”成规则的高维实体的。

3.6.3 np.hstack与np.vstack

3.6.3.1. 示例操作

1
2
3
4
5
6
7
8
9
10
11
12
13
a = np.array([1,2])
b = np.array([3,4])
c = np.array([5,6])

d1= np.hstack((a,b,c))
d2= np.vstack((a,b,c))

print(d1)
[1 2 3 4 5 6]
print(d2)
[[1 2]
[3 4]
[5 6]]

3.6.3.2. hstack与vstack运行机制

在 NumPy 中,np.vstacknp.hstack 是为了让开发者不需要死记硬背 axis(轴)的数字,而设计的两个高度直观的函数。它们的名字非常具象:

  • np.vstackv 代表 Vertical(纵向),意思是垂直地、上下地堆叠数组
  • np.hstackh 代表 Horizontal(横向),意思是水平地、左右地拼接数组

在底层,它们其实是 np.concatenate(拼接)的包装函数。下面分别来看一维、二维以及高维数据在这两个函数下的底层机制。

3.6.3.2.1. 经典二维数据下的机制(最直观)

假设有两个二维矩阵 \(A\)\(B\)

  • \(A\) 形状为 (2, 3)(2行3列)
  • \(B\) 形状为 (2, 3)(2行3列)
np.vstack((A, B)) 机制
  • 几何动作:把 \(B\) 直接放到 \(A\)正下方
  • 对齐要求:既然是上下放,它们的宽度(列数)必须完全一样
  • 最终形状:变成 (4, 3)(行数相加,列数不变)。
  • 等价命令np.concatenate((A, B), axis=0)
np.hstack((A, B)) 机制
  • 几何动作:把 \(B\) 靠在 \(A\)右侧
  • 对齐要求:既然是左右并排,它们的高度(行数)必须完全一样
  • 最终形状:变成 `(2,*几何动作:把 \(B\) 直接放到 \(A\)正下方**。
  • 6)`(行数不变,列数相加)。
  • 等价命令np.concatenate((A, B), axis=1)

(注:如果处理的是一维数组 (N,)vstack 会把它们变成二维的上下行;而 hstack 则会把它们接成一条更长的一维线。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
A=np.array([1])
B=np.array([2])
print(A)
array([1])
print(B)
array([2])

C1=np.vstack((A,B)) # 等价于 np.concatenate((A,B), axis=0)。要求A、B在axis=0之外的其他维度上的形状一样
print(C1)
[[1]
[2]]

C2=np.hstack((A,B)) # 等价于 np.concatenate((A,B), axis=1)。要求A、B在axis=1之外的其他维度上的形状一样
print(C2)
[1 2]
3.6.3.2.2. 3维甚至更高维度的数据也可以进行吗?

答案是:完全可以! 当数据升到 3 维、4 维甚至更高维度时,np.vstacknp.hstack 的核心底层机制依然死死锚定在第 0 轴第 1 轴上,绝对不会因为维度的拔高而发生改变。

可以用一个通用的公式来总结高维数据下的变化机制:

函数 永远等价于 形状变化规则 硬性对齐要求
np.vstack np.concatenate(..., axis=0) 只有第 0 轴的长度相加,其余轴不变 除了第 0 轴外,其余维度的数字必须完全相同
np.hstack np.concatenate(..., axis=1) 只有第 1 轴的长度相加,其余轴不变 除了第 1 轴外,其余维度的数字必须完全相同
3.6.3.2.3. 高维情况下的具体机制与实例演示

以大模型特征或图像处理中常见的 3维数组 (Depth, Height, Width) 为例:

假设有两个三维数组:

  • 数组 \(A\) 的形状是 (2, 3, 5)(可以想象成有 2 层,每层是 3行5列 的表格)
  • 数组 \(B\) 的形状是 (2, 3, 5)
场景一:高维下的 np.vstack((A, B))

根据公式,vstack 永远在 axis=0(最外层维度) 上做加法。

1
2
3
4
5
6
7
import numpy as np

A = np.ones((2, 3, 5))
B = np.ones((2, 3, 5))

C_v = np.vstack((A, B))
print(C_v.shape) # 输出: (4, 3, 5)
  • 机制解释:最外层的数字从 2 变成了 4\(2+2\)),而内层的 35 保持岿然不动。在空间上,这相当于你手里有两个由立体方块组成的“集装箱”,vstack 直接把第二个集装箱放在第一个集装箱的正下方
场景二:高维下的 np.hstack((A, B))

根据公式,hstack 永远在 axis=1(次外层维度,即矩阵的行维度) 上做加法。

1
2
C_h = np.hstack((A, B))
print(C_h.shape) # 输出: (2, 6, 5)
  • 机制解释:中间那层的数字从 3 变成了 6\(3+3\)),而最外层的 2 和最内层的 5 完美保持不变。在空间上,这相当于把两个集装箱在它们各自内部的行方向上进行延伸和横向加宽
3.6.3.2.4. 高维拼接时的“翻车”陷阱

因为 vstackhstack 的作用轴是被牢牢焊死的(一个是 axis=0,一个是 axis=1),所以如果遇到更深的维度(比如第 2 轴、第 3 轴)不同,这两个函数就会直接瘫痪。

举个例子:

  • 数组 \(X\) 的形状是 (2, 3, 4)
  • 数组 \(Y\) 的形状是 (2, 3, 9) —— 注意:只有最内层(axis=2)的长度不同(一个是4,一个是9)

此时:

  • 执行 np.vstack((X, Y))报错(因为要求除了 axis=0 外其他必须相同,但最后一位 4 和 9 无法对齐)。
  • 执行 np.hstack((X, Y))报错(因为要求除了 axis=1 外其他必须相同,4 和 9 依然对齐失败)。

💡 这种时候该怎么办?

遇到这种超越了 hv 传统管辖范围的更高轴拼接,就必须请出它们的底层老大哥 np.concatenate,并明确点名你想拼接的那根轴:

1
2
3
# 明确在 axis=2(第三个维度)上进行拼接
result = np.concatenate((X, Y), axis=2)
print(result.shape) # 成功输出: (2, 3, 13)
3.6.3.2.5. 核心记忆终极法则

在处理任何维度的 NumPy 数组时:

  • 只要听到 vstack,脑海里就画一个垂直向下的箭头,它只负责在最外层开辟的第 0 个数字上做加法。
  • 只要听到 hstack,脑海里就画一个水平向右的箭头,它只负责在括号进来第二层的第 1 个数字上做加法。

3.6.4 numpy分割

split方法:将指定的array按照aixs分割成制定值。

3.6.4.1 numpy分割示例

格式:np.split(ary, indices_or_sections, axis=0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a = np.arange(4)
#1维数组,按照axis=0进行切分,分成2份。可以看到(4,)变成两个(2,)
b = np.split(a, 2, axis=0)
print(a)
[0 1 2 3]

print(b)
[array([0, 1]), array([2, 3])]

print(type(b)) #b的类型是list
<class 'list'>

print(b[0])
[0 1]

print(type(b[0])) #b[0] 类型是numpy.ndarray
<class 'numpy.ndarray'>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
a = np.arange(8).reshape(2,4)
print(a)
[[0 1 2 3]
[4 5 6 7]]

#2维数组,按照axis=0进行切分,分成2份。可以看到(2,4)变成两个(1,4)
b1 = np.split(a, 2, axis=0)
print(b1)
[array([[0, 1, 2, 3]]), array([[4, 5, 6, 7]])]

#按照axis=1进行切分,分成2份。可以看到(2,4)变成两个(2,2)
b2 = np.split(a, 2, axis=1)
print(b2)
[array([[0, 1],
[4, 5]]), array([[2, 3],
[6, 7]])]

3.6.4.2 机制一:等量切分(传入一个整数 N)

当给第二个参数传入一个整数 \(N\) 时,NumPy 会尝试把数组平均分成 \(N\) 等份

3.6.4.2.1 基础代码示例(一维数组)
1
2
3
4
5
6
7
8
9
10
import numpy as np

x = np.array([10, 20, 30, 40, 50, 60])

# 传入整数 3,表示平均分成 3 等份
result = np.split(x, 3)

print(result)
# 输出一个包含3个数组的列表:
# [array([10, 20]), array([30, 40]), array([50, 60])]
3.6.4.2.2 核心陷阱:不能整除时会直接报错

等量切分有一个硬性要求:总长度必须能被 \(N\) 整除

如果上述数组 x 只有 5 个元素,而你尝试 np.split(x, 3),NumPy 绝不会自作聪明地分成 (2, 2, 1),而是会直接抛出经典的错误:

1
ValueError: array split does not result in an equal division

3.6.4.3 机制二:指定位置切分(传入一个索引列表 [i, j, ...])

如果想切出大小不等的数组,或者想在特定的位置“切刀”,就需要传入一个排好序的整数列表

NumPy 会把这些数字当作切刀的下标(索引位置)。其底层的切分区间符合 Python 典型的左闭右开原则:

  • 第一块:[:i]
  • 第二块:[i:j]
  • 第三块:[j:]
3.6.4.3.1 基础代码示例(一维数组)
1
2
3
4
5
6
7
8
9
10
11
x = np.array([10, 20, 30, 40, 50, 60])

# 在索引 2 和 5 的位置各切一刀
# 区间分别为: [:2], [2:5], [5:]
result = np.split(x, [2, 5])

print(result)
# 输出:
# [array([10, 20]), # 对应 x[:2]
# array([30, 40, 50]), # 对应 x[2:5]
# array([60])] # 对应 x[5:]

这种方式非常安全,不会出现因为指定维度上的长度无法被N整除而报错。

3.6.4.4 机制三:二维及高维数组下的切分(引入 axis)

在处理多维数据(如特征矩阵、图像矩阵)时,axis 参数决定了你是横着切(按行) 还是 竖着切(按列)

假设有一个 (4, 4) 的二维矩阵 A

1
2
3
4
5
6
A = np.arange(16).reshape(4, 4)
print(A)
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]
3.6.4.4.1 axis=0(默认值):纵向切(切断行)

拿着刀水平横着砍过去,把行给切开。

1
2
3
4
5
6
7
8
# 沿着 axis=0 平均切成 2 份
res_v = np.split(A, 2, axis=0)
# 结果包含两个 (2, 4) 的矩阵:前两行一包,后两行一包
print(res_v)
[array([[0, 1, 2, 3],
[4, 5, 6, 7]]),
array([[ 8, 9, 10, 11],
[12, 13, 14, 15]])]
3.6.4.4.2 axis=1:横向切(切断列)

拿着刀垂直竖着劈下来,把列给切开。

1
2
3
4
5
6
7
8
9
10
11
12
# 沿着 axis=1 平均切成 2 份
res_h = np.split(A, 2, axis=1)
# 结果包含两个 (4, 2) 的矩阵:左边两列一包,右边两列一包
print(res_h)
[array([[ 0, 1],
[ 4, 5],
[ 8, 9],
[12, 13]]),
array([[ 2, 3],
[ 6, 7],
[10, 11],
[14, 15]])]

3.6.4.5 机制四:衍生的小兄弟:np.vsplit 与 np.hsplit

为了省去写 axis 的麻烦,NumPy 同样提供了两个具象化函数,它们的底层完全是基于 np.split 实现的:

  • np.vsplit(A, 2):等价于 np.split(A, 2, axis=0)(Vertical,垂直切开,把行拆散)。
  • np.hsplit(A, 2):等价于 np.split(A, 2, axis=1)(Horizontal,水平切开,把列拆散)。

3.6.4.6 核心总结

  • 平均分:第二个参数传整数(注意必须能整除)。
  • 自由分:第二个参数传包含索引的列表
  • 控制方向axis=0 砍断行(上下分家),axis=1 砍断列(左右分家)。

3.6.5 vsplit与hsplit

  • vsplit沿着垂直轴切分

    • ```python a = np.arange(10) b = np.hsplit(a, 5) # 等价于 np.split(a, 5, axis=1) print(b) [array([0, 1]), array([2, 3]), array([4, 5]), array([6, 7]), array([8, 9])]
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10



      - hsplit沿着水平轴切分

      - ```python
      a = np.arange(10).reshape(2,5)
      b = np.vsplit(a, 2) # 等价于 np.split(a, 2, axis=0)
      print(b)
      [array([[0, 1, 2, 3, 4]]), array([[5, 6, 7, 8, 9]])]

3.7 numpy其他操作

3.7.1 nan与缺省值处理

  • nan是numpy和pandas中用于标识缺失数据
  • none:Python 中对象,不能与Nan混淆一起
  • 一个例子:某同学两次考试(每次考两门),第1次、第2次考试中都有一门考试因为某些情况没有参加,数据格式如下:
1
2
3
4
5
6
import numpy as np
v1 = np.array([[90, np.nan],[np.nan, 100]])
print(v1)
#输出如下
[[ 90. nan]
[ nan 100.]]

3.7.2 nan判断

3.7.2.1 nan判断示例

1
2
3
4
5
6
7
8
9
10
11
import numpy as np
v1 = np.array([[90, np.nan],[np.nan, 100]])
print(v1)
#输出如下
[[ 90. nan]
[ nan 100.]]

np.isnan(v1)
#输出如下
array([[False, True],
[ True, False]])

3.7.3 boolean索引

3.7.2.1 boolean索引示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
v1 = np.array([[90, np.nan],[np.nan, 100]])
print(v1)
#输出如下
[[ 90. nan]
[ nan 100.]]

print(np.isnan(v1))
#输出如下
[[False True]
[ True False]]

print(v1[np.isnan(v1)])
# 输出如下(np.isnan(v1) 中有多少个True,返回的一维数据中就有多少个nan) :
[nan nan]

3.7.2.2 语句的字面含义与运行结果

最后一个语句 v1[np.isnan(v1)] 在 NumPy 中被称为布尔索引(Boolean Indexing) 或 掩码(Mask)提取

它的字面意思是:“从矩阵 v1 中,把所有满足 np.isnan(v1)True 的元素挑选出来,并组合成一个新数组返回。”

因为在你的矩阵中,第 1 行第 2 列、第 2 行第 1 列的元素是 nan(空值/非数字),所以执行该语句后,NumPy 把它们给抓了出来,返回了 array([nan, nan])

3.7.2.3 底层图解与执行机制

这个语句的底层演变可以拆解为以下三个步骤:

3.7.2.3.1 第一步:生成布尔掩码(Mask)

首先,np.isnan(v1) 会对矩阵中的每个位置进行逻辑判断,生成一个形状完全一模一样、由 TrueFalse 组成的“布尔矩阵”:

1
2
[[False True]
[True False]]
3.7.2.3.2 第二步:位置对齐与筛选

接着,NumPy 会把这个布尔矩阵像一件“镂空的衣服”一样平铺在原始矩阵 v1 上。只有布尔矩阵中对应位置为 True 的地方,底下的数据才会被允许“漏出来”:

1
2
3
#原始矩阵 v1:             布尔掩码矩阵:            最终筛选通过:
[[ 90, np.nan], -> [[False, True], ->
[np.nan, 100]] [True, False]] [np.nan np.nan]
3.7.2.3.3 第三步:维度展平(Flattening)

这是最核心的一点机制:无论� 会把这个布尔矩阵像一件“镂空的衣服”一样平�始数组 v1 是二维、三维还是更高维度,一旦使用布尔索引进行数据提取,返回的结果永远会退化(展平)为一维数组。

因为符合 True 条件的元素在空间上可能是不连续的,NumPy 无法保证它们还能拼成一个规则的二维矩阵,所以统一将它们抽离出来,在线性队列中排好。即使最终只找到了一个 nan,它也会被包裹在一维数组 array([nan]) 当中。

3.7.2.4 这种语法的实际开发应用场景

在日常的数据清洗、临床数据集构建或机器学习特征工程中,这种“数组[布尔条件]”的语法出镜率极高。它的真正威力和正确用法通常有以下两种:

3.7.2.4.1 场景一:统计缺失值的数量

在清洗大型矩阵时,我们想快速知道里面一共有多少个 nan 损坏了数据,可以配合 len() 或者是 .size 属性:

1
2
3
# 提取出所有的 nan,然后看这个一维数组有多长
nan_count = v1[np.isnan(v1)].size
print(nan_count) # 输出: 1
3.7.2.4.2 场景二:空值非法数据的清洗与替换(最核心用法)

在实际写算法时,nan 参与计算会导致整个矩阵的均值、标准差全部变成 nan。我们通常会利用这个语法把 nan 揪出来,并直接原地安全替换为 0 或者是中位数:

1
2
3
4
5
6
7
8
9
10
11
12
print(v1)
# 输出:
[[ 90. nan]
[ nan 100.]]

# 把矩阵里所有是 nan 的位置,全部强行赋值为 0
v1[np.isnan(v1)] = 0

print(v1)
# 输出:
[[ 90. 0.]
[ 0. 100.]] -> 原来的 nan 已经被完美洗成了 0,矩阵可以正常参与后续运算了

3.7.4 np.all与np.any

3.7.4.1 np.all与np.any示例

1
2
3
4
5
6
7
8
9
10
11
12
import numpy as np
mask = np.array([[ True, True, False],
[False, True, True],
[True, True, True]])

# 纵向扫描(每次扫描一列)
print(np.all(mask, axis=0))
[False True False]

# 横向扫描(每次扫描一行)
print(np.all(mask, axis=1))
[False False True]

3.7.4.2 方法的基本概念与核心区别

在 NumPy 中,np.all()np.any() 是专门用来对布尔数组(或者可以隐式转换为布尔值的逻辑数组)进行全局逻辑组合判断的函数。它们类似于 Python 原生的 all()any(),但在处理大规模多维矩阵时,由于底层经过了 C 语言优化,性能要高效得多。

它们的本质区别可以总结为一句话:

  • np.all()全真才为真。只有当数组中的所有元素都为 True(或非零值)时,才返回 True;只要有一个是 False,就返回 False
  • np.any()一真即为真。只要数组中存在至少一个 True(或非零值),就返回 True;只有当全盘皆为 False 时,才返回 False

3.7.4.3 一维数组下的基础行为

在一维数组中,这两个函数会将整个数组压扁进行单一的条件判定。

3.7.4.3.1 基础代码示例
1
2
3
4
5
6
7
import numpy as np

# 假设这是一组特征完整性检查的布尔结果
checks = np.array([True, True, False, True])

print(np.all(checks)) # 输出: False (因为阵列里夹杂了一个 False)
print(np.any(checks)) # 输出: True (因为好歹有 True 存在)
3.7.4.3.2 隐式数值转换规则

如果传入的不是布尔数组,而是普通的数值数组,NumPy 会自动遵循标准计算机逻辑:0 代表 False,任何非 0 数字(包括负数)都代表 True

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

print(np.all(x)) # 输出: False (因为最后一位是 0,相当于 False)
print(np.any(x)) # 输出: True (因为前三位都是非 0 数)

3.7.4.4 二维及高维矩阵下的轴向判断(引入 axis)

当处理矩阵或高维张量时,如果不指定 axis 参数,np.allnp.any 依然会默认把整个矩阵展平为一条线,最终只吐出一个总的 TrueFalse

但通过指定 axis,我们可以实现按行按列的批量逻辑扫描。

假设我们有以下二维布尔矩阵 mask

1
2
mask = np.array([[ True,  True],
[False, True]])
3.7.4.4.1 axis=0:纵向扫描(按列压缩)

拿着扫描仪从上往下看每一列。

1
2
3
4
5
6
7
8
9
print(np.all(mask, axis=0))  
# 输出: array([False, True])
# 机制解释:
# 第一列 [True, False] -> 并不全为真 -> 得到 False
# 第二列 [True, True] -> 全部为真 -> 得到 True

print(np.any(mask, axis=0))
# 输出: array([ True, True])
# 机制解释:两列中都至少包含一个 True,所以全通过
3.7.4.4.2 axis=1:横向扫描(按行压缩)

拿着扫描仪从左往右看每一行。

1
2
3
4
5
print(np.all(mask, axis=1))  
# 输出: array([ True, False])
# 机制解释:
# 第一行 [True, True] -> 全部为真 -> 得到 True
# 第二行 [False, True] -> 含有假值 -> 得到 False

3.7.4.5 数据清洗与验证中的经典应用场景

这两个函数在数据清洗、数据工程(例如过滤非法数据或空值)中极为常用。

3.7.4.5.1 场景一:批量检查矩阵中是否存在任何缺失值(NaN)

这是最经典的高频组合拳:

1
2
3
4
5
6
v1 = np.array([[90, 100],
[np.nan, 100]])

# 检查整个矩阵里是不是有任何地方是空值(执行“np.isnan(v1)”后,v1中某元素如果为nan,返回结果中对应位置就是True)
if np.any(np.isnan(v1)):
print("警告:矩阵中包含不合法缺失值,需要清洗!")
3.7.4.5.2 场景二:安全比对两个大型矩阵是否完全一致

在测试算法或检查前后数据导出是否发生损坏时,不要使用 if A == B:(这会报错),而要用 np.all

1
2
3
4
5
6
matrix_a = np.array([1, 2, 3])
matrix_b = np.array([1, 2, 3])

# 只有当两个矩阵在物理位置上的每个元素都相等时,才判定为完全一致
if np.all(matrix_a == matrix_b):
print("两个矩阵完全相同")

3.7.4.6 注意None

3.7.4.6.1 None示例
1
2
3
4
5
6
7
8
9
10
11
12
a = np.array([1, None])
print(a)
# 输出
[1 None]

print(np.any(a))
# 输出
1

print(np.all(a))
# 输出
None
3.7.4.6.2 为什么数组形状是 [1 None]

当在 NumPy 中把数字 1 和 Python 的原生 None 强行混在一个列表里创建数组时:

  • NumPy 无法找到一个统一的数字类型(如 intfloat)来同时兼容它们。
  • 为了不丢失信息,NumPy 只能采取妥协策略:将整个数组的类型提升为最底层的 object(对象类型)

此时,数组 a 内部存储的不再是连续的底层数字,而是两个指向 Python 内存对象的指针。所以打印出来就是原汁原味的 [1 None]

3.7.4.6.3 为什么 np.any(a) 的输出是 1

np.any() 的底层机制是“一真即为真”,它在对象数组上会退化为 Python 原生的布尔逻辑短路求值。

3.7.4.6.3.1 执行演变过程
  1. np.any() 开始逐个检查数组里面的元素。
  2. 遇到第一个元素 1,根据 Python 的隐式类型转换,非零数字 1 的布尔值是 True
  3. 触发短路逻辑:既然已经找到了一个为 True 的元素,np.any() 认为没有必要再往下看了。它直接把当前这个判定为真的元素本身(也就是 1 作为结果返回了,而不是返回标准的布尔值 True
3.7.4.6.3.2 为什么纯数字数组会返回标准的 True 或 False

在例子 np.array([1, None]) 中,数组的底层数据类型(dtype)是 object(对象类型)。因为里面装的是原生 Python 对象,NumPy 只能被迫调用 Python 内置的逻辑,从而触发了 Python 对象独有的“返回元素本身”的短路特性。

而在现在的语句中:

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

这是一个纯整型数组,其底层的 dtype 是标准的 int64(或 int32

当对一个纯数字类型的 NumPy 数组调用 np.any() 时,它不会再去调用慢吞吞的 Python 对象短路逻辑,而是直接在底层走 C 语言级别的向量化优化操作

3.7.4.6.3.3 C 语言层面的执行机制

对于强类型的数字数组,np.any() 在底层的执行演变过程可以精简地拆解为两步:

NumPy 根本不关心里面是几,它直接把整个整型数组看作一个布尔状态。

在 C 语言的底层运算里:

  • 0 被视为 0(False)

  • 任何非 0 数字(如 1, 2, 3)都被统一等价视为 1(True)

因为底层是强类型的 C 运算,它的函数签名已经严格固定了返回值类型。只要在其高效的并行扫描中发现有任何一个非零元素,它在 C 层面就会直接吐出标准的布尔信号,映射回 Python 层面就是标准的 TrueFalse,而绝对不会把中间的数字 123 扔出来。

3.7.4.6.3.4 核心对比总结

为了让防止在数据工程和算法开发中混淆,可以用一张简单的对比表来彻底分清它们的本质:

数组类型 (dtype) 数组内容示例 np.any() / np.all() 的返回值 底层行为机制
object (对象数组) [1, None]['a', False] 元素本身 (1, None, 'a') 调用 Python 原生对象逻辑,触发对象短路求值
数字类型 (int/float) [1, 2, 3][0, 0.0] 标准布尔值 (True / False) 经过 C 语言向量化高能优化,严格限定布尔输出
3.7.4.6.4 为什么 np.all(a) 的输出是 None

np.all() 的底层机制是“全真才为真”,同样遵循 Python 对象的逻辑短路求值。

3.7.4.6.4.1 执行演变过程
  1. np.all() 开始挨个扫描数组。
  2. 第一个元素 1 是真值(True),满足条件,于是继续往下看。
  3. 遇到第二个元素 None。在 Python 中,None 的布尔值是 False
  4. 触发短路逻辑:一旦撞到了假值,整个“全真”的假设就破灭了。np.all() 立刻停止扫描,并将这个导致判定失败的罪魁祸首本身(也就是 None 直接返回。

3.7.4.7 np.where

  • 形式:where(condition, [x, y])

    • 如果给定x,y,满足条件condition,输出x,不满足输出y;

      • 如果没有x,y,返回满足条件对应的值的索引。
  • 示例:随机生成成绩单,判断是否及格

1
2
3
4
5
6
7
8
9
10
a = np.random.randint(30,100, 10)
print(a)
# 输出:
[49 43 64 77 96 93 41 47 76 31]

b = np.where(a>=60, 'pass', 'failed')
print(b)
# 输出:
['failed' 'failed' 'pass' 'pass' 'pass' 'pass' 'failed' 'failed' 'pass'
'failed']
1
2
3
4
5
# 获取满足条件的索引:
c = np.where(a>=60)
print(c)
# 输出:
(array([2, 3, 4, 5, 8], dtype=int64),)
1
2
3
4
# 获取满足条件值:
a[c] # a[np.where(a>=60)]
# 输出:
array([64, 77, 96, 93, 76])

3.8 numpy与ndarray的关系

在 Python 数据科学和 AI 开发中,numpyndarray 是密不可分的两个核心概念。简单来说,它们的关系是“工具箱”与“主打工具”的关系,或者说是“模块(Module)”与“数据类型(Data Type)”的关系。

可以从以下几个维度来彻底理清它们的关系:

3.8.1. 概念上的根本区别

  • numpy 是一个库 / 模块 (Library / Module)
    • 它是 Python 的一个第三方开源数学计算库(全称 Numerical Python)。
    • 它是一个容纳了各种功能的大集合,里面不仅包含高效的数据结构,还包含了成百上千个用于线性代数、傅里叶变换、随机数生成的函数和方法(如 np.sin(), np.dot(), np.linalg.solve())。
  • ndarray 是一个类 / 数据类型 (Class / Data Type)
    • 它是 NumPy 库中定义的核心多维数组对象(全称 N-dimensional array object)。
    • 它是真实在内存中开辟空间、存放你数据的实体数据结构。在 NumPy 中创建的所有矩阵、向量、多维数组,在 Python 里的实际类型(type())都是 numpy.ndarray

3.8.2. 代码中的代码关联(代码演示)

通过几行简单的代码,就能直观地看到它们在程序中的位置:

Python

1
2
3
4
5
6
7
import numpy as np  # 1. 导入的是 numpy 库

# 2. 使用 numpy 库里的函数创建了一个数组
data = np.array([1, 2, 3, 4])

# 3. 打印这个数据的类型
print(type(data))

输出结果:

1
<class 'numpy.ndarray'>
  • 解释:你通过 import numpy 引入了整个工具箱,当你调用 np.array() 函数去生产一个数组时,这个函数返回给你的成品,就是一个 ndarray 类型的对象。

3.8.3. 命名上的小陷阱:np.arraynp.ndarray

像很多刚接触数据科学的开发者一样,我产生过一个疑问:为什么创建时用 np.array,而类型显示的却是 np.ndarray

  • np.array() 是一个便利的“工厂函数(Factory Function)”。它负责接收传给它的 Python 列表或元组,并在后台对其进行格式检查、内存分配,最终实例化并返回一个 ndarray 对象。
  • np.ndarray 是具体的“类(Class)”。虽然也可以直接通过 np.ndarray(...) 的方式去实例化一个数组,但那种方式属于底层构造,参数非常晦涩难懂(需要手动指定内存步长、数据类型指针等),非常不直观。因此官方推荐一律使用 np.array() 来创建。

3.8.4. 为什么 numpy 必须依赖 ndarray

NumPy 之所以能够跑得比 Python 原生列表快几十甚至上百倍,全靠 ndarray 的底层设计:

  1. 连续内存分配:Python 列表里存的是对象的指针,数据在内存里是散落的;而 ndarray 在底层(C语言层面)开辟的是一段完全连续的原始内存块
  2. 单一数据类型(Homogeneous)ndarray 要求内部所有元素必须是同一种类型(比如全是 float32 或全是 int64)。这样 CPU 就可以利用 SIMD(单指令流多数据流)指令集进行并行向量化计算,这也是为什么在做 RAG 应用或者大模型特征处理时,向量加减乘除能瞬间完成的原因。

3.8.5. 总结

  • numpy 是写的代码里 import 的那个名字,是整个庞大的计算生态。
  • ndarraynumpy 里面专门用来装载多维矩阵数据的容器
  • numpy(工具箱)提供的 np.array() 方法

数据分析基础numpy
https://jiangsanyin.github.io/2026/06/08/数据分析基础numpy/
作者
sanyinjiang
发布于
2026年6月8日
许可协议