python中的数据操作几乎等同于NumPy数组操作,另外一个重要的工具包pandas也是构建在Numpy数组的基础之上的。
首先定义三个随机的数组:一个一维数组、二维数组和三维数组。
In[1]: import numpy as np
np.random.seed(0) # 设置随机数种子
x1 = np.random.randint(10,size=6) # 一维数组
x2 = np.random.randint(10,size=(3,4)) # 二维数组
x3 = np.random.randint(10,size=(3,4,5)) # 三维数组
每个数组有nidm(数组的维度)、shape(数组每个维度的大小)和size(数组的总大小)属性:
In[2]: print("x3 ndim:",x3.ndim)
print("x3 shape:",x3.shape)
print("x3 size:",x3.size)
x3 ndim: 3
x3 shape: (3, 4, 5)
x3 size: 60
另外一个有用的属性是dtype,它是数组的数据类型
In[3]: print("dtype:",x3.dtype)
dtype: int64
其他的属性包括表示每个数组元素字节大小的itemsize,以及表示数组总字节大小的属性nbytes:
In[4]: print("itemsize:",x3.itemsize,"bytes")
print("nbytes:",x3.nbytes,"bytes")
itemsize: 8 bytes
nbytes: 480 bytes
一般来说,可以认为itemsize与size的乘积大小和nbytes相等。
获取单个元素的索引方式与python的标准列表索引没什么区别,在一维数组中:
在多维数组中,可以使用逗号分隔的索引元祖获取元素:
In [5]: x2
Out[5]:
array([[5, 0, 3, 3],
[7, 9, 3, 5],
[2, 4, 7, 6]])
In [6]: x2[2,3]
Out[6]: 6
In [7]: x2[2,-1]
Out[7]: 6
也可以用以上索引方式修改元素值:
In [8]: x2[2,3] = 1
In [9]: x2
Out[9]:
array([[5, 0, 3, 3],
[7, 9, 3, 5],
[2, 4, 7, 1]])
要注意一点:Numpy数组是固定类型的,你将一个浮点值插入一个整型数组时,浮点值会被截短成整型。并且这种截短时自动完成的,不会给你提示或警告。
In [11]: x1[0] = 3.14159 #会被自动截短
In [12]: x1
Out[12]: array([3, 8, 1, 6, 7, 7])
NumPy切片语法和python列表多标准切片语法相同,为了获取数组x的一个切片,可以使用:
x[start:stop:step]
如果以上三个参数都未指定,那么它们会被分别设置默认值start=0,stop=维度的大小和step=1.
1.3.1 一维子数组
In [18]: x = np.arange(10)
In [19]: x
Out[19]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [20]: x[:5] # 前5个元素
Out[20]: array([0, 1, 2, 3, 4])
In [21]: x[5:] #索引5之后的元素
Out[21]: array([5, 6, 7, 8, 9])
In [22]: x[4:7] #中间的子数组
Out[22]: array([4, 5, 6])
In [23]: x[::2] # 每隔一个元素
Out[23]: array([0, 2, 4, 6, 8])
In [24]: x[1::2] #每隔一个元素,从索引1开始
Out[24]: array([1, 3, 5, 7, 9])
步长值step为负,可以非常方便的逆序:
In [25]: x[::-1] # 所有元素,逆序
Out[25]: array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])
In [26]: x[5::-2] # 从索引5开始,每隔一个元素逆序
Out[26]: array([5, 3, 1])
1.3.2 多维子数组
多维切片也采用同样的方式处理,用冒号分隔。
In [27]: x2
Out[27]:
array([[5, 0, 3, 3],
[7, 9, 3, 5],
[2, 4, 7, 1]])
In [28]: x2[:2,:3] #两行,三列
Out[28]:
array([[5, 0, 3],
[7, 9, 3]])
In [29]: x2[:,::2] #所有行,每隔一列
Out[29]:
array([[5, 3],
[7, 3],
[2, 7]])
子数组也可以同时被逆序:
In [30]: x2[::-1,::-1]
Out[30]:
array([[1, 7, 4, 2],
[5, 3, 9, 7],
[3, 3, 0, 5]])
1.3.3 获取数组的行和列
一种常见的需求是获取数组的单行和单列,用索引和切片组合起来实现:
In [27]: x2
Out[27]:
array([[5, 0, 3, 3],
[7, 9, 3, 5],
[2, 4, 7, 1]])
In [31]: print(x2[:,0]) # x2的第一列
[5 7 2]
In [32]: print(x2[0,:]) # x2的第一行
[5 0 3 3]
在获取行时,出于语法的简洁性,可以省略空的切片:
In [33]: print(x2[0])
[5 0 3 3]
1.3.4 非副本视图的子数组
Numpy数组切片返回的是数组数据的视图,而不是像python列表切片是值的副本。因此直接修改切片出来的子数组,会看到原始数组也被修改。
In [43]: x2
Out[43]:
array([[5, 0, 3, 3],
[7, 9, 3, 5],
[2, 4, 7, 1]])
In [44]: x2_sub = x2[:2,:2]
# 切片
In [45]: print(x2_sub)
[[5 0]
[7 9]]
# 从新赋值
In [46]: x2_sub[0,0] = 99
In [47]: x2
Out[47]:
array([[99, 0, 3, 3],
[ 7, 9, 3, 5],
[ 2, 4, 7, 1]])
这种默认的处理方式实际上非常有用:它意味着在处理非常大的数据集时,可以获取或处理这些数据集的片段,而不用复制底层的数据缓存。
1.3.5 创建数组的副本
根据业务的不同,有时候复制数组里的数据或子数组也是非常有用的,通过copy()
方法实现:
In [47]: x2
Out[47]:
array([[99, 0, 3, 3],
[ 7, 9, 3, 5],
[ 2, 4, 7, 1]])
In [48]: x2_sub_copy = x2[:2,:2].copy()
In [49]: print(x2_sub_copy)
[[99 0]
[ 7 9]]
如果修改这个子数组,原始的数组不会改变:
In [50]: x2_sub_copy[0,0] = 18
In [51]: print(x2_sub_copy)
[[18 0]
[ 7 9]]
In [52]: x2
Out[52]:
array([[99, 0, 3, 3],
[ 7, 9, 3, 5],
[ 2, 4, 7, 1]])
Numpy数组变形最灵活的实现方式是通过reshape()函数实现.
In [53]: grid = np.arange(1,10).reshape((3,3))
In [54]: print(grid)
[[1 2 3]
[4 5 6]
[7 8 9]]
需要注意:原始数组的大小必须和变形后数组的大小一致。
如果满足这个条件,reshape方法会用到原始数组的一个非副本视图,但实际情况是,在非连续的数据缓存的情况下,返回非副本视图往往不可能实现。
另外常见的模式:将一个一维数组转变为二维的行或列的矩阵。
In [62]: x = np.array([1,2,3])
#通过变形获得的行向量
In [63]: x.reshape((1,3))
Out[63]: array([[1, 2, 3]])
#通过newaxis获得的行向量
In [64]: x[np.newaxis,:]
Out[64]: array([[1, 2, 3]])
#通过变形获得的列向量
In [66]: x.reshape((3,1))
Out[66]:
array([[1],
[2],
[3]])
# 通过newaxis获得的列向量
In [67]: x[:,np.newaxis]
Out[67]:
array([[1],
[2],
[3]])
1.4 数组拼接和分裂
1.4.1 数组的拼接
拼接或连接NumPy中的两个数组主要由np.concatenate、np.vstack和np.hstack实现。
np.concatenate
将数组元组或数组列表作为第一个参数:
In [68]: x = np.array([1,2,3])
In [69]: y = np.array([3,2,1])
# 拼接2个数组
In [70]: np.concatenate([x,y])
Out[70]: array([1, 2, 3, 3, 2, 1])
In [71]: z = [6,6,6]
# 一次性拼接两个以上的数组
In [72]: print(np.concatenate([x,y,z]))
[1 2 3 3 2 1 6 6 6]
np.concatenate
也可以用于二维数组的拼接:
In [73]: grid = np.array([[1,2,3],
[4,5,6]])
# 沿着第一个轴拼接
In [74]: np.concatenate([grid,grid])
Out[74]:
array([[1, 2, 3],
[4, 5, 6],
[1, 2, 3],
[4, 5, 6]])
# 沿着第二个拼接(从0开始索引)
In [75]: np.concatenate([grid,grid],axis=1)
Out[75]:
array([[1, 2, 3, 1, 2, 3],
[4, 5, 6, 4, 5, 6]])
沿着固定维度处理数组时,使用np.vstack
(垂直栈)和np.hstack
(水平栈)函数会更简洁:
In [76]: x = np.array([1,2,3])
In [79]: grid = np.array([[9,8,7],
[6,5,4]])
# 垂直栈数组
In [80]: np.vstack([x,grid])
Out[80]:
array([[1, 2, 3],
[9, 8, 7],
[6, 5, 4]])
In [81]: y = np.array([[99],
[99]])
# 水平栈数组
In [82]: np.hstack([grid,y])
Out[82]:
array([[ 9, 8, 7, 99],
[ 6, 5, 4, 99]])
np.dstack
将沿着第三个维度拼接数组
1.4.2 数组的分裂
分裂可以通过np.split
、np.hsplit
和np.vsplit
函数来实现,可以向以上函数传递一个索引列表作为参数,索引列表记录的是分裂点位置。
In [83]: x = [1,2,3,99,99,3,2,1]
In [84]: x1,x2,x3 = np.split(x,[3,5])
In [85]: print(x1,x2,x3)
[1 2 3] [99 99] [3 2 1]
注意:N分裂点会得到N+1个子数组。
In [86]: grid = np.arange(16).reshape((4,4))
In [87]: grid
Out[87]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
# 垂直分裂数组
In [88]: upper,lower = np.vsplit(grid,[2])
In [90]: print(upper)
[[0 1 2 3]
[4 5 6 7]]
In [91]: print(lower)
[[ 8 9 10 11]
[12 13 14 15]]
# 水平分裂数组
In [92]: left,right = np.hsplit(grid,[3])
In [93]: print(left)
[[ 0 1 2]
[ 4 5 6]
[ 8 9 10]
[12 13 14]]
In [94]: print(right)
[[ 3]
[ 7]
[11]
[15]]
同样,np.dsplit
将数组沿着第三个维度分裂。