人工智能之Numpy与Pandas培训文档
创建时间:2018-09-09  访问量:3290  4  0

人工智能之Numpy与Pandas培训文档

数组的索引

ndarray数组可以使用Python序列的索引形式(如x[obj])来进行索引,x是ndarray数组对象,obj是对应的选择对象,这个选择就是根据不同的索引方式来确定的,numpy中的索引方式有如下几种:

  • 基本索引与切片
  • 花式索引
  • 布尔索引

 

基本的索引与切片

首先说明一下,索引与切片只是两个不同的说法,索引的方式是用于切片的,就是对数组进行索引后选出新的数组的过程称为切片。

基本的切片继承了Python多维列表中的切片的基本概念。先来看看一维数组的索引与切片,基本的切片的语法是i:j:k形式,i是起始位置,j是终止位置,k是步长。

>>> import numpy as np
>>> arr1 = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> arr1
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> arr1[1:7:2] # 选取了编号1、3、5对应位置的元素,注意不包括终止位置7
array([1, 3, 5])

如果步长为负数,就从开始位置向位置减小的方向移动,如果i或j是负数则表示的位置是数组相应维度元素的个数n + i和n + j,因为i、j是负数,所以对应位置是n - |i|和n - |j|。如下所示:

>>> arr1[-3:3:-1] # i为-3其实是长度10-3=7的位置,相当于arr1[7:3:-1],因为步长-1,所示起始位置大于终止位置
array([7, 6, 5, 4])
>>> arr1[-2:10] # 不指定步长k,黙认是1,-2其实是位置8,相当于arr1[8:10:1]
array([8, 9])
>>>

如果对于多维数组进行这样的索引是什么效果?

>>> x = np.array([[[1],[2],[3]], [[4],[5],[6]]]) # x是3维,2行3列深度为1
>>> x.shape
(2L, 3L, 1L)
>>> x[1:2] # 这个索引其实是对第1维的数组进行索引,也就等于选中的第2行的所有数据(不包括索引终止位置)
array([[[4],
        [5],
        [6]]])

如果我们要选择第1维与第2维的数据,每个维度的索引对象是,号分隔,如下所示:

>>> x[:,0]
array([[1],
       [4]])
>>> x[...,0]
array([[1, 2, 3],
       [4, 5, 6]])

这里使用了...省略号和:冒号,都是用它们指定第1维度的数据,第2维度是0,表示第1列位置的数据,深度没有,表示全部,关键这个行的位置是怎么选择的呢?

这里先说下冒号(:),这个其实是0:3的简写,其实相当于x[0:,0],所有行都选中了,其它选择的是第1行第1列和第2行第1列,结果是:array([[1],[4]])。能想明白吗?

省略号(...)其实是冒号(:)的扩展,它被用于生成一个与x.ndim长度相同的选择元组,而且只能出现一次。什么意思呢,...就省略号,首先我们看看x[...]是多少?

>>> x[...]    
array([[[1],  
        [2],  
        [3]], 
              
       [[4],  
        [5],  
        [6]]])
>>> x         
array([[[1],  
        [2],  
        [3]], 
              
       [[4],  
        [5],  
        [6]]])
>>> x[:,:,:]  
array([[[1],  
        [2],  
        [3]], 
              
       [[4],  
        [5],  
        [6]]])
>>>           

我们看到上面的x/x[...]/x[:,:,:]结果是一样的。其实对于x[...,0],看上去只选择了二维数据,其实这个...号已经将这个索引选择扩展成了3维了,那么也就是说这个0是最后一维的数据,第1维与第2维数据全选了,也就相当于x[:,:,0]。

>>> x[:,:,0]
array([[1, 2, 3],
       [4, 5, 6]])

这个是怎么选择的呢,其实选择的是第1行第1列第1层,第1行第2列第1层,第1行第3列第1层,第2行第1列第层,第2行第2列第1层,第2行第3列第一层,就是上面的结果了,这个还真不好描述,写的有点罗嗦,大家理解了瞟一眼就好了,不理解对着输出结果想象一下。

 

下面我们来看看二维数组的索引,首先创建一个二维数组:

>>> x = np.arange(32).reshape(8,4)
>>> x
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],
       [24, 25, 26, 27],
       [28, 29, 30, 31]])

我们在这个二维数组平面图上,画任意一个矩形框起的数据都可以通过索引与切片获取。

1、获取下图红框中的9。9的位置是第3行第2列即坐标是(2,1)的位置:

>>> x[(2,1)]  # 方括号中使用元组指定位置对象
9             
>>> x[2,1] # 通常使用这种简写形式,下面不再赘述
9             

2、获取下图红框中的数据,第2行、第4、5行,每行的所有列都选择了:

>>> x[[1,3,4],:] # 第1维选择1、3、4行
array([[ 4,  5,  6,  7],
       [12, 13, 14, 15],
       [16, 17, 18, 19]])

3、获取下图红框中的数据:

>>> x[:,1:3] # 第2维选择第2、3列,不包括第4列
array([[ 1,  2],
       [ 5,  6],
       [ 9, 10],
       [13, 14],
       [17, 18],
       [21, 22],
       [25, 26],
       [29, 30]])

4、获取下图红框中的数据:

>>> x[2:5,1:3] # 第3、4行与第2、3列,不包括终止位置
array([[ 9, 10],
       [13, 14],
       [17, 18]])

 

三维数组的索引切片与二维相似,举个例子看一下:

>>> x                      
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],   
       [24, 25, 26, 27],   
       [28, 29, 30, 31]])
>>> y = x.reshape(2,4,4)   
>>> y
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],  
        [24, 25, 26, 27],  
        [28, 29, 30, 31]]])
>>> y[0:2,2:3,1]  # 获取第1、2行,第3列,第2层的元素         
array([[ 9],               
       [25]])              
>>>                        

 

花式索引

举例:

>>> x = np.array([[1, 2], [3, 4], [5, 6]])
>>> x[[0,1,2],[0,1,0]]
array([1, 4, 5])

上面通过花式索引得到的数据,怎么理解呢?外面一层[]我们不看,反正索引都要放在里面,我们关注里面的以逗号(,)分隔的方括号,逗号左边的方括号选择的是行,右边选择的是列,对应方法是这样子的,行[0,1,2],列[0,1,0],我们选择的位置是(0,0),(1,1),(2,0)这三位置上的元素。明白了吗?

上面的二维数组索引的结果是个一维的,如果想得到二维的结果,如下所示:

>>> x1[[[0],[1],[2]],[[0],[1],[0]]]
array([[1],
       [4],
       [5]])

 

布尔索引

创建一个4行3列的随机数组
 

>>> x = np.random.random((4,3))
>>> x
array([[0.52713239, 0.3252963 , 0.1652478 ],
       [0.04816616, 0.08303095, 0.88061306],
       [0.67939081, 0.39339805, 0.99020645],
       [0.36327311, 0.58621119, 0.26445315]])

我们将其中大于0.5的元素索引出来,如下所示:

>>> x[x > 0.5]
array([0.52713239, 0.88061306, 0.67939081, 0.99020645, 0.58621119])

那x>0.5的结果是什么呢?

>>> x>0.5
array([[ True, False, False],
       [False, False,  True],
       [ True, False,  True],
       [False,  True, False]])

下面看个实际用例:

>>> x = np.array([[98,87,90],[80,79,83],[74,87,78]],dtype=int)
>>> x # 每行表示一个人的成绩
array([[98, 87, 90],
       [80, 79, 83],
       [74, 87, 78]])
>>> y = np.array(['Lilei','Lile','Sam'],dtype=str) 
>>> y # 分别是这三个人的成绩
array(['Lilei', 'Lile', 'Sam'], dtype='|S5')
>>> columns = np.array([0,1,2],dtype=int) # 第0、1、2列表示每个人的所有成绩
>>> rows = (y == 'Lilei') # 得到Lilei的成绩
>>> x[np.ix_(rows,columns)]
array([[98, 87, 90]])

这就是np.ix_()的功劳,y == 'Lilei'会得到一个bool数组:array([ True, False, False]);columns是在选好行后,要选哪些列(可以是bool数据或整)。然后这个rows与columns通过np.ix_()方法生成一个合适的索引。这就很方便的通过两个一维数组来索引一个二维数据。

np.ix_()方法其实就是个广播,上例中将rows行按给定列广播,得到的真实的bool索引是这样子的:

array([[ True, True, True],
       [ False, False, False],
       [ False, False, False]])

所以选择的是x的第1行的数据。

 

ndarray的索引与切片得到的数组是个视图

以上通过numpy索引切片得到的数组都是个视图,这意味着,改变切片数组中的元素,也会改变原数组中相应位置上的元素。

>>> x = np.random.random((2,3)) # 生成一个2行3列的随机数组
>>> x
array([[0.91732769, 0.40664003, 0.53870422],
       [0.06290647, 0.44243575, 0.12777547]])
>>> x[x<0.5] = np.nan # 将所有小于0.5的元素赋值成np.nan,这是个无效数值或叫作空
>>> x
array([[0.91732769,        nan, 0.53870422],
       [       nan,        nan,        nan]])
>>>

上面我们只是为了测试将小于0.5的元素置成空(np.nan),在实际应用中大部分都是因为数据中有部分无效数据如np.nan,我们要做数据分析前需要将这些np.nan的无效数据设置成0,这个过程叫数据清洗,用numpy来清洗数据是不是很方便?

 

矩阵转置与轴对换

矩阵的转置即轴对换,可以通过ndarray.transpose()或ndarray.T属性来获得,行轴与列轴对换。