numpy(六)——数组的变形,转置与换轴
import numpy as np
numpy中的数组变形有reshape和resize两种方法,在第一篇中已经略有涉及。此二者作用相差不大,不过至细微处亦有颇多不同,故于此详加讲述,希能澄清。
arr_shape = np.arange(20).reshape(4,5)
arr_shape
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19]])
首先,reshape和resize都有两种使用方法,一种是作为numpy的顶层函数使用,一种是作为ndarray的对象方法使用。
arr_reshape_1 = np.reshape(arr_shape,(5,4))
arr_reshape_1
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15],
[16, 17, 18, 19]])
arr_reshape_1[0][0] = 999
print(arr_reshape_1)
print("--"*10)
print(arr_shape)
[[999 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[ 12 13 14 15]
[ 16 17 18 19]]
--------------------
[[999 1 2 3 4]
[ 5 6 7 8 9]
[ 10 11 12 13 14]
[ 15 16 17 18 19]]
arr_reshape_2 = arr_shape.reshape(2,10)
arr_reshape_2
array([[999, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]])
arr_reshape_2[0][1] = 999
print(arr_reshape_2)
print("--"*10)
print(arr_shape)
[[999 999 2 3 4 5 6 7 8 9]
[ 10 11 12 13 14 15 16 17 18 19]]
--------------------
[[999 999 2 3 4]
[ 5 6 7 8 9]
[ 10 11 12 13 14]
[ 15 16 17 18 19]]
- 可见,无论是使用np顶层函数还是ndarray对象方法,这两种使用方法的结果是完全一致的,那就是:返回一个原数组的视图
我们再来看resize方法
arr_shape_1 = np.arange(20).reshape(4,5)
arr_shape_1
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19]])
arr_resize_1 = np.resize(arr_shape_1,(5,4))
arr_resize_1
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15],
[16, 17, 18, 19]])
arr_shape_1
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19]])
arr_resize_1[0][0] = 999
print(arr_resize_1)
print("--"*10)
print(arr_shape_1)
[[999 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[ 12 13 14 15]
[ 16 17 18 19]]
--------------------
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]
[15 16 17 18 19]]
arr_resize_2 = arr_shape_1.resize(2,10)
print(type(arr_resize_2))
<class 'NoneType'>
arr_shape_1
array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]])
- 可见,当使用resize改变数组的形状时,情况与reshape甚有不同:
- 作为顶层函数的resize,会返回换一个新的数组对象,并且这个对象不是原数组的视图,而是一个独立的副本
- 作为对象方法的resize,不回返回新对象,而是原地修改原数组
resize方法另有一个神奇的特点值得注意:
arr = np.arange(12).reshape(3,4)
arr
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
np.resize(np.arange(20),(5,5))
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19],
[ 0, 1, 2, 3, 4]])
np.resize(np.arange(20),(3,4))
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
- 可以看出,当使用顶层函数方式的resize时,哪怕resize中传入的形状与需要被变形的数组的长度不一致,resize函数也可以自动适应,多退少补,非常的人性化,想报错都难
- 但是作为对象方法的ndarray和reshape,则是不能进行这样子的自动适应的,会报错
arr = np.arange(12).reshape(3,4)
arr
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
arr.resize(4,4)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-33-ca80ab1e27b3> in <module>
----> 1 arr.resize(4,4)
ValueError: cannot resize this array: it does not own its data
arr.reshape(4,4)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-34-966f28412ea6> in <module>
----> 1 arr.reshape(4,4)
ValueError: cannot reshape array of size 12 into shape (4,4)
np.reshape(arr,(4,4))
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-35-67e91a25795c> in <module>
----> 1 np.reshape(arr,(4,4))
~\AppData\Local\conda\conda\envs\DataScience\lib\site-packages\numpy\core\fromnumeric.py in reshape(a, newshape, order)
230 [5, 6]])
231 """
--> 232 return _wrapfunc(a, 'reshape', newshape, order=order)
233
234
~\AppData\Local\conda\conda\envs\DataScience\lib\site-packages\numpy\core\fromnumeric.py in _wrapfunc(obj, method, *args, **kwds)
55 def _wrapfunc(obj, method, *args, **kwds):
56 try:
---> 57 return getattr(obj, method)(*args, **kwds)
58
59 # An AttributeError occurs if the object does not have
ValueError: cannot reshape array of size 12 into shape (4,4)
arr_trans = np.arange(20).reshape(4,5)
arr_trans
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19]])
arr_T = arr_trans.T
arr_T
array([[ 0, 5, 10, 15],
[ 1, 6, 11, 16],
[ 2, 7, 12, 17],
[ 3, 8, 13, 18],
[ 4, 9, 14, 19]])
arr_T[0][0] = 999
print(arr_trans)
print("--"*10)
print(arr_T)
[[999 1 2 3 4]
[ 5 6 7 8 9]
[ 10 11 12 13 14]
[ 15 16 17 18 19]]
--------------------
[[999 5 10 15]
[ 1 6 11 16]
[ 2 7 12 17]
[ 3 8 13 18]
[ 4 9 14 19]]
arr_t = np.transpose(arr_trans)
arr_t
array([[999, 5, 10, 15],
[ 1, 6, 11, 16],
[ 2, 7, 12, 17],
[ 3, 8, 13, 18],
[ 4, 9, 14, 19]])
arr_t[0][1] = 999
print(arr_t)
print("--"*10)
print(arr_T)
print("--"*10)
print(arr_trans)
[[999 999 10 15]
[ 1 6 11 16]
[ 2 7 12 17]
[ 3 8 13 18]
[ 4 9 14 19]]
--------------------
[[999 999 10 15]
[ 1 6 11 16]
[ 2 7 12 17]
[ 3 8 13 18]
[ 4 9 14 19]]
--------------------
[[999 1 2 3 4]
[999 6 7 8 9]
[ 10 11 12 13 14]
[ 15 16 17 18 19]]
-
可以看到,数组的转置也可以有顶层的transpose函数和.T属性这两种方式进行操作。不过庆幸的是,这两种方法的结果完全一致:返回的是原数组的视图(所以你再也不必像记忆resize方法那样搞得自己精神分裂了)
-
换轴
-
使用newaxis增轴《Python数据科学手册》P60
使用np.newaxis属性可以改变原数组的形状并且增加原数组的维度
arr_axis = np.arange(12).reshape(3,4)
arr_axis
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
arr_newAxis_1 = arr_axis[:,np.newaxis,:]
print(arr_newAxis_1)
print("--"*10)
print(arr_newAxis_1.shape)
[[[ 0 1 2 3]]
[[ 4 5 6 7]]
[[ 8 9 10 11]]]
--------------------
(3, 1, 4)
arr_newAxis_0 = arr_axis[np.newaxis,:,:]
print(arr_newAxis_0)
print("--"*10)
print(arr_newAxis_0.shape)
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]]
--------------------
(1, 3, 4)
arr_newAxis_2 = arr_axis[:,:,np.newaxis]
print(arr_newAxis_2)
print("--"*10)
print(arr_newAxis_2.shape)
[[[ 0]
[ 1]
[ 2]
[ 3]]
[[ 4]
[ 5]
[ 6]
[ 7]]
[[ 8]
[ 9]
[10]
[11]]]
--------------------
(3, 4, 1)
- 可见,增轴后的数组的形状如何,其实取决于np.newaxis的位置
arr_newAxis_0[0][0][0] = 999
print(arr_newAxis_0)
print("--"*10)
print(arr_newAxis_1)
print("--"*10)
print(arr_newAxis_2)
print("--"*10)
print(arr_axis)
[[[999 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]]
--------------------
[[[999 1 2 3]]
[[ 4 5 6 7]]
[[ 8 9 10 11]]]
--------------------
[[[999]
[ 1]
[ 2]
[ 3]]
[[ 4]
[ 5]
[ 6]
[ 7]]
[[ 8]
[ 9]
[ 10]
[ 11]]]
--------------------
[[999 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
-
可见,使用newaxis增轴后的结果,也是原数组的视图
-
使用swapaxes换轴《利用Python进行数据分析 第二版》P105
该方法接收一对轴编号作为参数,并且对轴进行调整用于重组数据
arr_swap = np.arange(18).reshape(2,3,3)
arr_swap
array([[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8]],
[[ 9, 10, 11],
[12, 13, 14],
[15, 16, 17]]])
arr_swap_1 = arr_swap.swapaxes(0,1)
arr_swap_1
array([[[ 0, 1, 2],
[ 9, 10, 11]],
[[ 3, 4, 5],
[12, 13, 14]],
[[ 6, 7, 8],
[15, 16, 17]]])
print(arr_swap_1.shape)
print(arr_swap.shape)
(3, 2, 3)
(2, 3, 3)
- 轴编号就是数组形状元组的索引。因此,交换0,1的轴编号,就是将数组形状从323变成233
arr_swap_1[0][0][0] = 999
print(arr_swap)
print("--"*10)
print(arr_swap_1)
[[[999 1 2]
[ 3 4 5]
[ 6 7 8]]
[[ 9 10 11]
[ 12 13 14]
[ 15 16 17]]]
--------------------
[[[999 1 2]
[ 9 10 11]]
[[ 3 4 5]
[ 12 13 14]]
[[ 6 7 8]
[ 15 16 17]]]
- 可见,使用swapaxes换轴后,结果还是原数组的视图