python数据分析

  1. NumPy数组
    1. ndarray
    2. 创建数组
    3. 形状与视图
    4. 索引和切片
    5. 广播
    6. 迭代器
    7. 堆叠与分割
    8. 数组元素的修改

NumPy数组

ndarray

NumPy提供了一个名为ndarray的多维数组对象。使用array()构造一个ndarray。

1
2
def array(object, dtype=None, copy=True, order='K', subok=False, ndmin=0):
pass

ndarray拥有以下属性:

  • 指向数组的指针data
  • 数据类型dtype
  • 数组形状shape
  • 跨度元组strides,描述跳到下一维度所需的步长

ndarray还主要附加了以下属性:

  • 每个元素的字节数itemsize
  • 总元素个数size
  • 总字节数nbytes
  • 维度ndim
  • 内存信息flags
1
2
3
4
5
6
7
8
9
10
A = np.array([[1, 2], [3, 4], [5, 6]])
print(A)
print(A.data) # <memory at 0x000001373402E668>
print(A.dtype) # int32
print(A.itemsize) # 4
print(A.size) # 6
print(A.nbytes) # 24
print(A.ndim) # 2
print(A.shape) # (3, 2)
print(A.strides) # (8, 4)

单个数字被认为是0维数组,形状和跨度为空。

1
2
3
4
B = np.array(1)
print(B.ndim) # 0
print(B.shape) # ()
print(B.strides) # ()

但是,仍然可以在构造时选择更高的维数。

1
2
3
4
C = np.array([1, 2, 3], ndmin=3)
print(C) # [[[1 2 3]]]
print(C.shape) # (1, 1, 3)
print(C.strides) # (12, 12, 4)

修改shape/dtype/strides,会使得ndarray重新解释自己,必要时重新拷贝自己并改变已有的属性。然而一并发生的数据类型重新解释及内存的不连续并不是总我们所期望的。不要直接这样修改。

1
2
3
4
5
D = np.array([1, 0, 0, 0])
print(D.data) # <memory at 0x0000024428C92048>
D.dtype = 'complex'
print(D.data) # <memory at 0x0000024428C92048>
print(D) # [5.e-324+0.j]
1
2
3
4
5
6
7
8
E = np.array([[1, 0], [0, 0], [0, 0]])
print(E.data) # <memory at 0x000001F474E6E668>
E.shape = (6, 1)
print(E.data) # <memory at 0x000001F474E6E668>
print(E.strides) # (4, 4)
E.shape = (6,)
print(E.data) # <memory at 0x000001F474F52048>
print(E.strides) # (4,)
1
2
3
4
5
6
7
8
9
F = np.array([[1, 2], [3, 4], [5, 6]])
print(F.strides) # (8, 4)
F.strides = (4, 8)
print(F)
# [[1 3]
# [2 4]
# [3 5]]
F.shape = (2, 3) # AttributeError: incompatible shape for a non-contiguous array
F.resize((2, 3)) # ValueError: resize only works on single-segment arrays

asarray()可以把列表、元组等转换为ndarray。

1
2
3
4
5
6
7
8
9
10
11
12
X = [[1, 2, 3], [4, 5, 6]]
A = np.asarray(X)
# [[1 2 3]
# [4 5 6]]
print(A.dtype) # int32
print(A.shape) # (2, 3)

X = [[1, 2, 3], [4, 5]]
A = np.asarray(X)
# [list([1, 2, 3]) list([4, 5])]
print(A.dtype) # object
print(A.shape) # (2,)

创建数组

arange()`用于生成按顺序间隔变化的向量。

1
def arange(start=None, stop, step=None, , dtype=None)
1
2
3
A = np.arange(6)  # [0 1 2 3 4 5]
A = np.arange(6, 12)) # [ 6 7 8 9 10 11]
A = np.arange(12, 6, -2) # [12 10 8]

小数间隔被建议使用linspace()。num表示总计的点数。

start和stop可以是ndarray,NumPy会进行广播。

1
def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0)
1
2
3
4
5
6
7
A = np.linspace(0, 1, num=3)  # [0.  0.5 1. ]
A = np.linspace(1, 0, num=3) # [1. 0.5 0. ]
A = np.linspace(0, 1, num=5, endpoint=False) # [0. 0.2 0.4 0.6 0.8]
A = np.linspace([0, 1], 2, 3)
# [[0. 1. ]
# [1. 1.5]
# [2. 2. ]]

logspace()生成等比数列,使用方法类似linspace()

1
2
A = np.logspace(0, 9, 10, base=2)
# [ 1. 2. 4. 8. 16. 32. 64. 128. 256. 512.]

通过形状来创建数组:

1
2
3
4
5
6
7
8
9
10
A = np.zeros((2, 3))
# [[0. 0. 0.]
# [0. 0. 0.]]
A = np.ones((2, 3))
# [[1. 1. 1.]
# [1. 1. 1.]]
A = np.full((2, 3), 233)
# [[233 233 233]
# [233 233 233]]
A = np.empty((2, 3))

empty()所生成的数组未经过初始化,请在使用前手动初始化。

通过已有形状来创建同样形状的数组:

1
2
3
4
5
6
7
8
9
10
11
A = np.arange(8).reshape((2, 4))
B = np.zeros_like(A)
# [[0 0 0 0]
# [0 0 0 0]]
B = np.ones_like(A)
# [[1 1 1 1]
# [1 1 1 1]]
B = np.full_like(A, 233)
# [[233 233 233 233]
# [233 233 233 233]]
B = np.empty_like(A)

单位矩阵使用identity()eye()

1
2
def identity(n, dtype=None)
def eye(N, M=None, k=0, dtype=float, order='C'):
1
2
3
4
A = np.eye(3, 4, -1)
# [[0. 0. 0. 0.]
# [1. 0. 0. 0.]
# [0. 1. 0. 0.]]

形状与视图

flatten()返回拉平后的向量,ravel()返回ndarray的一个拉平后的视图。

视图是对同一块内存区域的不同ndarray解释,NumPy就是通过视图来控制数组内存的。来自相同内存的不同视图是不同的变量,但通过视图修改内存会影响到所有的视图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
A = np.arange(6).reshape(2, 3)
B = A.flatten()
# [0 1 2 3 4 5]
C = A.ravel()
D = A.ravel()
# [0 1 2 3 4 5]
print(A.data) # <memory at 0x0000022B5F10E668>
print(C.data) # <memory at 0x0000022B5F1F2048>
print(D.data) # <memory at 0x0000022B5F1F2048>
print(D is C) # False
C[3] = 10
print(A)
# [[ 0 1 2]
# [10 4 5]]

reshape()返回更改形状后的数组视图,resize()则在原数组上修改。

1
2
3
4
5
A = np.arange(6).reshape(2, 3)
# [[0 1 2]
# [3 4 5]]
A.resize(6)
# [0 1 2 3 4 5]

transpose()obj.T返回数组的转置的视图。

1
2
3
4
5
6
7
8
A = np.arange(6).reshape(2, 3)
B = np.transpose(A)
C = A.T
D = A.transpose()
print(A.data) # <memory at 0x000001AEF864B668>
print(B.data) # <memory at 0x000001AEF864B668>
print(C.data) # <memory at 0x000001AEF864B668>
print(D.data) # <memory at 0x000001AEF864B668>

NumPy中的转置本质上是内存重新解读——将C风格连续性转变为F风格连续性,即shape和strides的倒序。可以传入各坐标轴的映射关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
A = np.arange(24).reshape(2, 3, 4)
print(A)
print(A.shape) # (2, 3, 4)
print(A.strides) # (48, 16, 4)
print(A.flags) # C_CONTIGUOUS : True
B = A.transpose()
print(B.shape) # (4, 3, 2)
print(B.strides) # (4, 16, 48)
print(B.flags) # F_CONTIGUOUS : True
C = A.transpose(0, 2, 1)
print(C.shape) # (2, 4, 3)
print(C.strides) # (48, 4, 16)
print(C.flags) # C_CONTIGUOUS : False F_CONTIGUOUS : False

因此,转置并不会把一维向量转化为“列向量”,所谓的“列向量”指的是n×1矩阵,维度是2。

可以用view()生成视图。如果需要直接拷贝的话,使用copy()

1
2
def view(dtype=None, type=None)
def copy(order='C')

索引和切片

可以使用方括号对数组进行索引和切片,而不建议使用多维数组风格。索引和切片本质上也是视图。

1
2
3
4
5
6
7
8
9
A = np.arange(18).reshape(3, 6)
B = A[:, 1::2]
# [[ 1 3 5]
# [ 7 9 11]
# [13 15 17]]
print(A.data) # <memory at 0x0000012E71C6C668>
print(B.data) # <memory at 0x0000012E71C6C668>
print(B.shape) # (3, 3)
print(B.strides) # (24, 8)

切片会导致数据不连续,程序将无法调用resize()。使用ascontiguousarray()保证连续性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
A = np.arange(18)
A.resize(3, 6)
print(A.data) # <memory at 0x0000023751B6E668>
print(A.flags) # C_CONTIGUOUS : True OWNDATA : True
B = A[:, 1::2]
# [[ 1 3 5]
# [ 7 9 11]
# [13 15 17]]
print(B.data) # <memory at 0x0000023751B6E668>
print(B.flags) # C_CONTIGUOUS : False OWNDATA : False
# B.resize(9) # ValueError: resize only works on single-segment arrays
C = np.ascontiguousarray(B)
print(C.data) # <memory at 0x0000023751B6E668>
print(C.flags) # C_CONTIGUOUS : True OWNDATA : True
print(A.flags) # C_CONTIGUOUS : True OWNDATA : True
C.resize(9) # [ 1 3 5 7 9 11 13 15 17]
print(C.data) # <memory at 0x0000023751C53048>

省略号以自动补全。

1
2
3
4
5
6
7
8
A = np.arange(24).reshape(2,3,4)
B=A[1,...]
# [[12 13 14 15]
# [16 17 18 19]
# [20 21 22 23]]
B=A[...,1]
# [[ 1 5 9]
# [13 17 21]]

在方括号中传入相同形状的索引数组,从原数组中取出同样形状的数组。

1
2
3
4
5
6
7
8
9
A = np.arange(12).reshape(3, 4)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
B = A[[0, 0, 1], [0, 1, 2]]
# [0 1 6]
B = A[[[0, 0], [2, 2]], [[0, 3], [0, 3]]]
# [[ 0 3]
# [ 8 11]]

如果需要取出横竖交叉的子矩阵,可以使用“小把戏”ix_()

1
2
3
4
5
6
7
8
9
A = np.arange(12).reshape(3, 4)
index = np.ix_([0, 2, 1], [0, 3, 1])
# (array([[0],
# [2],
# [1]]), array([[0, 3, 1]]))
B = A[index]
# [[ 0 3 1]
# [ 8 11 9]
# [ 0 3 1]]

也可以使用布尔值来选取数组。

1
2
3
4
5
6
7
8
9
10
11
A = np.random.rand(2, 3)
# [[0.01026206 0.20146929 0.81601537]
# [0.60675752 0.08648162 0.21315679]]
print(A > 0.5)
# [[False False True]
# [ True False False]]
A[(A <= 0.5)] = 0
# [[0. 0. 0.81601537]
# [0.60675752 0. 0. ]]
print(A[A > 0.5])
# [0.81601537 0.60675752]

广播

在数组进行运算,但数组形状不相同时,NumPy会尝试对数组进行广播——在其他方向上进行重复。

1
2
3
4
5
6
7
8
9
10
A = np.array([[0, 0, 0],
[10, 10, 10],
[20, 20, 20],
[30, 30, 30]])
B = np.array([1, 2, 3])
print(A+B)
# [[ 1 2 3]
# [11 12 13]
# [21 22 23]
# [31 32 33]]

broadcast_to()将数组尝试广播到指定的形状。

1
2
3
4
A=np.arange(3)
B=np.broadcast_to(A, (2,3))
# [[0 1 2]
# [0 1 2]]

可以讲一个数组用broadcast()广播到另一个数组上,统一遍历。

1
2
3
4
5
6
7
8
9
10
A = np.array([[1], [2], [3]])
B = np.array([4, 5, 6])

# 对B广播A
X = np.broadcast(A, B)
print(X.shape) # (3, 3)

for x, y in X:
print("%d:%d" % (x, y), end=" ")
# 1:4 1:5 1:6 2:4 2:5 2:6 3:4 3:5 3:6

迭代器

数组的flat属性返回一个flatiter类型的迭代器。注意对迭代器的直接赋值会被解释为对所有元素赋值。

1
2
3
4
5
6
7
8
9
10
A = np.arange(0, 24, 2).reshape(3, 4)
B = A.flat[[[1, 3, 5], [2, 4, 6]]]
# [[ 2 6 10]
# [ 4 8 12]]
for element in A.flat:
print(element)
A.flat=10
# [[10 10 10 10]
# [10 10 10 10]
# [10 10 10 10]]

这是NumPy内置的eye()的实现。

1
2
3
4
5
6
7
8
9
10
11
12
def eye(N, M=None, k=0, dtype=float, order='C'):
if M is None:
M = N
m = zeros((N, M), dtype=dtype, order=order)
if k >= M:
return m
if k >= 0:
i = k
else:
i = (-k) * M
m[:M-k].flat[i::M+1] = 1
return m

可以使用nditer()遍历数组,顺序与数组内存布局一致,因此矩阵和它的转置遍历次序相同,也可以指定迭代顺序。

如果要求在迭代时修改数据的值,则需要指定迭代器是可写的。

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 = np.arange(12).reshape(3, 4)

for x in np.nditer(A):
print(x, end=" ")
# 0 1 2 3 4 5 6 7 8 9 10 11

B = A.T
for x in np.nditer(B):
print(x, end=" ")
# 0 1 2 3 4 5 6 7 8 9 10 11

for x in np.nditer(B, order='C'):
print(x, end=" ")
# 0 4 8 1 5 9 2 6 10 3 7 11

for x in np.nditer(A, op_flags=['readwrite']):
x[...] = 2**x
# [[ 1 2 4 8]
# [ 16 32 64 128]
# [ 256 512 1024 2048]]

for x in np.nditer(A, flags=['external_loop'], order='F'):
print(x, end=" ")
# [ 1 16 256] [ 2 32 512] [ 4 64 1024] [ 8 128 2048]

可以同时迭代多个数组,也可以与广播机制配合使用。

1
2
3
4
5
A = np.arange(0, 12).reshape(3, 4)
B = np.arange(4)
for x, y in np.nditer([A, B]):
print("%d:%d" % (x, y), end=" ")
# 0:0 1:1 2:2 3:3 4:0 5:1 6:2 7:3 8:0 9:1 10:2 11:3

堆叠与分割

对数组进行堆叠的函数主要有:

1
2
3
4
def concatenate((a1, a2, ...), axis=0, out=None)
def stack(arrays, axis=0, out=None)
def vstack(tup)
def vstack(tup)
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
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])

print(np.concatenate((A, B)))
print(np.vstack((A, B)))
# [[1 2]
# [3 4]
# [5 6]
# [7 8]]
print(np.concatenate((A, B), axis=1))
print(np.hstack((A, B)))
# [[1 2 5 6]
# [3 4 7 8]]
print(np.stack((A, B)))
# [[[1 2]
# [3 4]]
#
# [[5 6]
# [7 8]]]
print(np.stack((A, B), axis=1))
# [[[1 2]
# [5 6]]
#
# [[3 4]
# [7 8]]]
print(np.dstack((A, B)))
print(np.stack((A, B), axis=2))
# [[[1 5]
# [2 6]]
#
# [[3 7]
# [4 8]]]

数组元素的修改

append用于在矩阵后追加元素,默认情况下axis=None,此时会将所有元素拉平后追加。可以指定相应的方向。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
A = np.arange(12).reshape(3, 4)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
B = np.append(A, 12)
# [ 0 1 2 3 4 5 6 7 8 9 10 11 12]
C = np.append(A, [12, 13, 14, 15])
# [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
D = np.append(A, [[12, 13, 14, 15]], axis=0)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]
# [12 13 14 15]]
E = np.append(A, [[12], [13], [14]], axis=1)
# [[ 0 1 2 3 12]
# [ 4 5 6 7 13]
# [ 8 9 10 11 14]]

insert可以在多个位置插入元素,实现类似append。第二参数可以传入多个位置。

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.arange(12).reshape(3, 4)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
B = np.insert(A, 1, 12)
# [ 0 12 1 2 3 4 5 6 7 8 9 10 11]
B2 = np.insert(A, [1, 3, 5], 12)
# [ 0 12 1 2 12 3 4 12 5 6 7 8 9 10 11]
C = np.insert(A, 1, [12, 13, 14, 15])
# [ 0 12 13 14 15 1 2 3 4 5 6 7 8 9 10 11]
D = np.insert(A, 1, [[12, 13, 14, 15]], axis=0)
# [[ 0 1 2 3]
# [12 13 14 15]
# [ 4 5 6 7]
# [ 8 9 10 11]]
E = np.insert(A, 1, np.array([[12], [13], [14]]), axis=1)
# [[ 0 12 13 14 1 2 3]
# [ 4 12 13 14 5 6 7]
# [ 8 12 13 14 9 10 11]]
E2 = np.insert(A, [1], np.array([[12], [13], [14]]), axis=1)
# [[ 0 12 1 2 3]
# [ 4 13 5 6 7]
# [ 8 14 9 10 11]]
E3 = np.insert(A, [1], np.array([[12, 13, 14]]), axis=1)
# [[ 0 12 13 14 1 2 3]
# [ 4 12 13 14 5 6 7]
# [ 8 12 13 14 9 10 11]]
E4 = np.insert(A, 1, [[12], [13], [14]], axis=0)
# [[ 0 1 2 3]
# [12 12 12 12]
# [13 13 13 13]
# [14 14 14 14]
# [ 4 5 6 7]
# [ 8 9 10 11]]

delete用于删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
A = np.arange(12).reshape(3, 4)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
B = np.delete(A, 1)
# [ 0 2 3 4 5 6 7 8 9 10 11]
B2 = np.delete(A, [1, 3, 5])
# [ 0 2 4 6 7 8 9 10 11]
C = np.delete(A, np.s_[::2])
# [ 1 3 5 7 9 11]
D = np.delete(A, 1, axis=0)
# [[ 0 1 2 3]
# [ 8 9 10 11]]
E = np.delete(A, 1, axis=1)
# [[ 0 2 3]
# [ 4 6 7]
# [ 8 10 11]]
E2 = np.delete(A, np.s_[::2], axis=1)
# [[ 1 3]
# [ 5 7]
# [ 9 11]].

article_txt
目录