Python数据科学学习笔记之——NumPy入门

NumPy 入门

1、理解 Python 中的数据类型

1.1、Python 整型不仅仅是一个整型

标准的 Python 实现是用 C 语言编写的。这意味着每一个 Python 对象都是一个聪明的伪 C 语言结构体,该结构体不仅包含其值,还有其他信息。例如:当我们在 Python 中定义一个整型,例如:x = 10000 时,x 并不是一个 “原生” 整型,而是一个指针,指向一个 C 语言的复合结构体,结构体里包含了一些值。

查看 Python 源代码,可以发现整型(长整型)的定义,如下:

struct _longobject{
     
	long ob_refcnt;		//一个引用计数,它帮助 Python 默默地处理内存的分配和回收
	PyTypeObject *ob_type;	//将变量的类型编码
	size_t ob_size;	//指定接下来的数据成员的大小
	long ob_digit[1];	//包含我们希望 Python 变量表示的实际整型值
};

差异:C 语言整型本质上是对某个内存位置的标签,里面存储的字节会编码成整型而 Python 的整型其实是一个指针,指向包含这个 Python 对象所有信息的某个内存位置,其中包括可以转换成整型的字节。由于 Python 的整型结构体里面还包含了大量额外的信息,所以 Python 可以自由、动态地编码。

1.2、Python 列表不仅仅是一个列表

Python 中的标准可变多元素容器是列表。因为 Python 的动态特性,甚至可以创建一个异构的列表

L = [True,'2',3.0,4]
print([type(item) for item in L])
[<class 'bool'>, <class 'str'>, <class 'float'>, <class 'int'>]

为了获得这些灵活的类型,列表中的每一项必须包含各自的类型信息,引用类型和其他信息;也就是说,每一项都是一个完整的 Python 对象。

来看一个特殊的例子,如果列表中的所有变量都是同一类型的,那么很多信息都会是多余的——将数据存储在固定类型的数组中应该会更高效。动态类型的列表和固定类型的(NumPy 式)数组间的区别就在于此。

在实现层面,数组基本上包含一个指向连续数据块的指针。另一方面,Python 列表包含一个指向指针块的指针,这其中的每一个指针对应一个完整的 Python 对象。列表的优势是灵活,因为列表中的元素可以用任何类型的数据填充。固定类型的 NumPy 式数组缺乏这种灵活性,但是能更有效地存储和操作数据。

1.3、Python 中固定类型数组

Python 提供了几种将数据存储在有效的、固定类型的数据缓存中的选项。内置的数组(array)模块可以用于创建统一类型的密集数组

import array
L = list(range(10))
A = array.array('i',L)	#这里的 'i' 是一个数据类型码,表示数据为整型
array('i', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

Python 的数组对象提供了数组型数据的有效存储,而 NumPy 为该数据加上了高效的操作

从用 np 别名导入 NumPy 的标准做法:

import numpy as np

1.4、从 Python 列表创建数组

首先,可以用 np.array 从 Python 列表创建数组:

np.array([1,4,2,5,3])
[1,4,2,5,3]

NumPy 要求数组必须包含同一类型的数据。如果类型不匹配,NumPy 将会向上转型。如:

np.array([3.14,4,2,3])
[3.14 4.   2.   3.  ]

如果希望明确数组的数据类型,可以用 dtype 关键字:

np.array([1,2,3,4],dtype='float32')
[1. 2. 3. 4.]

NumPy 数组可以被指定为多维的。以下是用列表的列表初始化多维数组的一种方法:

#嵌套列表构成的多维数组
np.array([range(i,i+3) for i in [2,4,6]])
[[2 3 4]
 [4 5 6]
 [6 7 8]]

1.5、从头创建数组

面对大型数组的时候,用 NumPy 内置的方法从头创建数组是一种更高效的方法。

#创建一个长度为 10 的数组,数组的元素都是 0
np.zeros(10,dtype=int)
[0 0 0 0 0 0 0 0 0 0]

np.ones((3,5),dtype=float)
[[1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]]

#创建一个 3x5 的浮点型数组,数组的值都是 3.14
print(np.full((3,5),3.14))
[[3.14 3.14 3.14 3.14 3.14]
 [3.14 3.14 3.14 3.14 3.14]
 [3.14 3.14 3.14 3.14 3.14]]

#创建一个线性序列数组
#从 0 开始,到 20 结束,步长为 2
#(它和内置的 range() 函数类似)
print(np.arange(0,20,2))
[ 0  2  4  6  8 10 12 14 16 18]

#创建一个 5 个元素的数组,这 5 个数均匀地分布到 0~1
print(np.linspace(0,1,5))
[0.   0.25 0.5  0.75 1.  ]

#创建一个 3x3 的、在 0~1 均匀分布的随机数组成的数组
print(np.random.random((3,3)))
[[0.886326   0.93619859 0.59884604]
 [0.20913196 0.15508227 0.01782209]
 [0.20183124 0.5761326  0.19984512]]

#创建一个 3x3 的、均值为 0、标准差为 1 的正态分布的随机数数组
print(np.random.normal(0,1,(3,3)))
[[ 1.22672264 -1.25538307 -0.13381821]
 [ 1.18173765  0.95377135  0.52868231]
 [ 0.0503752   0.84407108 -0.06514662]]

#创建一个 3x3 的、[0,10) 区间的随机整型数组
print(np.random.randint(0,10,(3,3)))
[[2 9 6]
 [2 1 3]
 [2 0 5]]

#创建一个 3x3 的单位矩阵
print(np.eye(3))
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

#创建一个由 3 个整型数组成的未初始化的数组
#数组的值是内存空间中的任意值
print(np.empty(3))
[1. 1. 1.]

2、NumPy 数组基础

2.1、NumPy 数组的属性

定义三个随机数组:一个一维数组、一个二维数组和一个三维数组。我们将用 NumPy 的随机数生成器设置一组种子值,以确保每次程序执行时都可以生成同样的随机数组:

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)) #三维数组

每个数组都有 ndim(数组的维度)、shape(数组每个维度的大小)和 size(数组的总大小)属性

print("x3 ndim:",x3.ndim)   #数组的维度		x3 ndim: 3
print("x3 shape:",x3.shape) #数组每个维度的大小		x3 shape: (3, 4, 5)
print("x3 size:",x3.size)   #数组的总大小		x3 size: 60

另外一个有用的属性是 dtype,它是数组的数据类型:

print("dtype:",x3.dtype)    #数组的数据类型	dtype: int32

其他的属性包括每个数组元素字节大小的 itemsize,以及表示数组粽总字节大小的属性 nbytes

print("itemsize:",x3.itemsize,"bytes")  #每个数组元素字节大小	itemsize: 4 bytes
print("nbytes:",x3.nbytes,"bytes")  #数组总字节大小	nbytes: 240 bytes

一般来说,可以认为 nbytes = itemsize * size

2.2、数组索引:获取单个元素

在一维数组中,你可以通过中括号指定索引获取第 i 个值(从 0 开始计数)

print(x1)		#[5 0 3 3 7 9]
print(x1[0])	#5
print(x1[4])	#7

为了获取数组的末尾索引,可以使用负值索引

print(x1[-1])	#9
print(x1[-2])	#7

在多维数组中,可以用逗号分隔的索引元组获取元素

print(x2)
'''
[[3 5 2 4]
 [7 6 8 8]
 [1 6 7 7]]
'''
print(x2[0,0])	#3
print(x2[2,0])	#1
print(x2[2,-1])	#7

也可以使用上述方式修改元素值

x2[0,0] = 12
print(x2)
'''
[[12  5  2  4]
 [ 7  6  8  8]
 [ 1  6  7  7]]
'''

2.3、数组切片:获取子数组

切片符号用冒号(:)表示。为了获取数组 x 的一个切片,可以用以下方式:

x[start:end:step]

如果以上 3 个参数都未被指定,那么它们会被分别设置成默认值 start = 0,end = 维度的大小 和 step = 1。

  1. 一维数组

    x = np.arange(10)
    print(x)	#[0 1 2 3 4 5 6 7 8 9]
    
    print(x[:5])    #前五个元素	[0 1 2 3 4]
    print(x[5:])    #索引 5 之后的元素	[5 6 7 8 9]
    print(x[4:7])   #中间的子数组	[4 5 6]
    print(x[::2])   #每隔一个元素	[0 2 4 6 8]
    print(x[1::2])  #每隔一个元素,从索引 1 开始	[1 3 5 7 9]
    

    逆序数组的一种方法:

    print(x[::-1])  #所有元素,逆序	[9 8 7 6 5 4 3 2 1 0]
    print(x[5::-2]) #从索引 5 开始每隔一个元素逆序	[5 3 1]
    
  2. 多维数组

    print(x2)
    '''
    [[12  5  2  4]
     [ 7  6  8  8]
     [ 1  6  7  7]]
    '''
    print(x2[:2,:3])    #两行 三列
    '''
    [[12  5  2]
     [ 7  6  8]]
    '''
    print(x2[:3,::2])   #所有行,每隔一列
    '''
    [[12  2]
     [ 7  8]
     [ 1  7]]
    '''
    

    同样也可以逆序:

    print(x2[::-1,::-1])    #逆序
    '''
    [[ 7  7  6  1]
     [ 8  8  6  7]
     [ 4  2  5 12]]
    '''
    
  3. 获取数组的行和列

    一种常见的需求是获取数组的单行与单列用一个冒号(:)表示空切片

    print(x2[:,0])  #x2 的第一列	[12  7  1]
    print(x2[0,:])  #x2 的第一行	[12  5  2  4]
    

    获取行时,出于语法的简洁考虑,可以省略空的切片:

    print(x2[0])    #等于 x2[0,:]	[12  5  2  4]
    
  4. 非副本视图的子数组

    数组切片返回的是数组数据的视图,而不是数值数据的副本。

    print(x2)
    '''
    [[12  5  2  4]
     [ 7  6  8  8]
     [ 1  6  7  7]]
    '''
    x2_sub = x2[:2,:2]
    print(x2_sub)
    '''
    [[12  5]
     [ 7  6]]
    '''
    

    现在如果修改这个数组,将会看到原始的数组也被修改了。结果如下:

    x2_sub[0,0] = 99
    print(x2_sub)
    '''
    [[99  5]
     [ 7  6]]
    '''
    print(x2)
    '''
    [[99  5  2  4]
     [ 7  6  8  8]
     [ 1  6  7  7]]
    '''
    

    它意味着在处理非常大的数据集时,可以获取或处理这些数据集的片段,而不用复制底层的数据缓存。

  5. 创建数组的副本

    可以简单地通过 copy() 来实现。

    x2_sub_copy = x2[:2,:2].copy()
    print(x2_sub_copy)
    '''
    [[99  5]
     [ 7  6]]
    '''
    

    如果修改这个子数组,原始的数组不会被改变:

    x2_sub_copy[0,0] = 42
    print(x2_sub_copy)
    '''
    [[42  5]
     [ 7  6]]
    '''
    print(x2)
    '''
    [[99  5  2  4]
     [ 7  6  8  8]
     [ 1  6  7  7]]
    '''
    

2.4、数组的变形

通过 reshape() 函数来实现。

例如:如果你希望将数字 1~9 放入一个 3 × 3 的矩阵中,可以采用如下方法:

grid = np.arange(1,10).reshape((3,3))
print(grid)
'''
[[1 2 3]
 [4 5 6]
 [7 8 9]]
'''

另外一个常见的变形模式是将一个一维数组转变为二维的行或列的矩阵。你可以通过 reshape 方法来实现,或者更简单地在一个切片操作中利用 newaxis 关键字:

x = np.array([1,2,3])
print(x.reshape((1,3))) #通过变形获得行向量	[[1 2 3]]
#通过 newaxis 获得的行向量
print(x[np.newaxis,:])	#[[1 2 3]]
print(x.reshape((3,1)))
'''
[[1]
 [2]
 [3]]
'''
print(x[:,np.newaxis])
'''
[[1]
 [2]
 [3]]
'''

2.5、数组的拼接与分裂

  1. 数组的拼接

    拼接或连接 NumPy 中的两个数组主要由 np.concatenate、np.vstack、np.hstack 例程实现。

    x = np.array([1,2,3])
    y = np.array([3,2,1])
    print(np.concatenate([x,y]))	#[1 2 3 3 2 1]
    

    np.concatenate 也可以用于二维数组的拼接:

    grid = np.array([[1,2,3],
                     [4,5,6]])
    #沿着第一个轴拼接
    print(np.concatenate([grid,grid]))
    '''
    [[1 2 3]
     [4 5 6]
     [1 2 3]
     [4 5 6]]
    '''
    
    #沿第二个轴拼接
    print(np.concatenate([grid,grid],axis=1))
    '''
    [[1 2 3 1 2 3]
     [4 5 6 4 5 6]]
    '''
    

    沿着固定维度处理数组时,使用 np.vstack(垂直栈)和 np.hstack(水平栈)函数会更简洁:

    x = np.array([1,2,3])
    grid = np.array([[9,8,7]
                     [6,5,4]])
    
    #垂直栈数组
    print(np.vstack([x,grid]))
    '''
    [[1 2 3]
     [9 8 7]
     [6 5 4]]
    '''
    
    #水平栈
    y = np.array([[99],
                  [99]])
    print(np.hstack([grid,y]))
    '''
    [[ 9  8  7 99]
     [ 6  5  4 99]]
    '''
    
  2. 数组的分裂

    分裂可以使用 np.split、np.hsplit 和 np.vsplit 函数来实现。可以向以上函数传递一个索引列表作为参数,索引列表记录的是分裂点位置:

    x = [1,2,3,99,99,3,2,1]
    x1,x2,x3 = np.split(x,[3,5])
    print(x1,x2,x3)		#[1 2 3] [99 99] [3 2 1]
    

    注意:N 分裂将会得到 N+1 个子数组

    相关的 np.hsplit 和 np.vsplit 函数用法也是一样:

    grid = np.arange(16).reshape((4,4))
    print(grid)
    '''
    [[ 0  1  2  3]
     [ 4  5  6  7]
     [ 8  9 10 11]
     [12 13 14 15]]
    '''
    upper,lower = np.vsplit(grid,[2])
    print(upper)
    '''
    [[0 1 2 3]
     [4 5 6 7]]
    '''
    print(lower)
    '''
    [[ 8  9 10 11]
     [12 13 14 15]]
    '''
    
    left,right = np.hsplit(grid,[2])
    print(left)
    '''
    [[ 0  1]
     [ 4  5]
     [ 8  9]
     [12 13]]
    '''
    print(right)
    '''
    [[ 2  3]
     [ 6  7]
     [10 11]
     [14 15]]
    '''
    

3、NumPy 数组的计算:通用函数

3.1、通用函数介绍

NumPy 为很多类型的操作提供了非常方便的、静态类型的、可编译程序的接口,也被称作向量操作。

NumPy 中的向量操作的通过通用函数实现的。通用函数的主要目的是对 NumPy 数组中的值执行更快的重复操作

3.2、探索 NumPy 的通用函数

通用函数有两种存在形式:一元通用函数对单个输入操作二元通用函数对两个输入操作

3.2.1、数组的运算

NumPy 通用函数的使用方式非常自然,因为它用到了 Python 原生的算术运算符,标准的加、减、乘、除都可以使用:

x = np.arange(4)
print("x       =",x)	# x = [0 1 2 3]
print("x + 5 =",x + 5)	# x + 5 = [5 6 7 8]
print("x - 5 =",x - 5)	# x - 5 = [-5 -4 -3 -2]
print("x * 2 =",x * 2)	# x * 2 = [0 2 4 6]
print("x / 2 =",x / 2)	# x / 2 = [0.  0.5 1.  1.5]
print("x // 2 =",x // 2)	# x // 2 = [0 0 1 1]

还有求负数、** 表示的指数运算和 % 表示的求模运算的一元通用函数:

print("-x =",-x)	# -x = [ 0 -1 -2 -3]
print("x ** 2 =",x ** 2)	# x ** 2 = [0 1 4 9]
print("x % 2 =",x % 2)	# x % 2 = [0 1 0 1]

所有这些算术运算符都是 NumPy 内置函数的简单封装器:

运算符 对应的通用函数 描述
+ np.add 加法运算
- np.subtract 减法运算
- np.negative 负数运算
* np.multiply 乘法运算
/ np.divide 除法运算
// np.floor_divide 向下整除运算
** np.power 指数运算
% np.mod 模/取余

3.2.2、绝对值

正如 NumPy 能理解 Python 内置的运算操作,NumPy 也可以理解 Python 内置的绝对值函数:

x = np.array([-2,-1,0,1,2])
print(abs(x))	# [2 1 0 1 2]

对应的通用函数是 np.absolute,该函数也可以用别名 np.abs 来访问:

print(np.absolute(x))	# [2 1 0 1 2]
print(np.abs(x))	# [2 1 0 1 2]

这个通用函数也可以用来处理复数。当处理复数时,绝对值返回的是该复数的模:

x = np.array([3-4j,4-3j,2+0j,0+1j])
print(np.abs(x))	#[5. 5. 2. 1.]

3.2.3、三角函数

定义的角度数组:

theta = np.linspace(0,np.pi,3)

现在可以对这些值进行三角函数的计算:

print("theta =",theta)	# theta = [0.         1.57079633 3.14159265]
print("sin(theta) =",np.sin(theta))	# sin(theta) = [0.0000000e+00 1.0000000e+00 1.2246468e-16]
print("cos(theta) =",np.cos(theta))	# cos(theta) = [ 1.000000e+00  6.123234e-17 -1.000000e+00]
print("tan(theta) =",np.tan(theta))	# tan(theta) = [ 0.00000000e+00  1.63312394e+16 -1.22464680e-16]

逆三角函数同样可以使用:

x = [-1,0,1]	
print("x =",x)		# x = [-1, 0, 1]
print("arcsin(x) =",np.arcsin(x))	# arcsin(x) = [-1.57079633  0.          1.57079633]
print("arccos(x) =",np.arccos(x))	# arccos(x) = [3.14159265 1.57079633 0.        ]
print("arctan(x) =",np.arctan(x))	# arctan(x) = [-0.78539816  0.          0.78539816]

3.2.4、指数和对数

指数运算:

x = [-1,0,1]
print("x =",x)		# x = [1, 2, 3]
print("arcsin(x) =",np.arcsin(x))	# e^x = [ 2.71828183  7.3890561  20.08553692]
print("arccos(x) =",np.arccos(x))	# 2^x = [2. 4. 8.]
print("arctan(x) =",np.arctan(x))	# 3^x = [ 3  9 27]

对数运算:

x = [1,2,4,10]
print("x =",x)	# x = [1, 2, 4, 10]
print("ln(x) =",np.log(x))	# ln(x) = [0.         0.69314718 1.38629436 2.30258509]
print("log2(x) =",np.log2(x))	# log2(x) = [0.         1.         2.         3.32192809]
print("log10(x) =",np.log10(x))	# log10(x) = [0.         0.30103    0.60205999 1.        ]

还有一些特殊的版本,对于非常小的输入值可以保持较好的精度:

x = [0,0.001,0.01,0.1]
print("exp(x) - 1 =",np.expm1(x))	# exp(x) - 1 = [0.         0.0010005  0.01005017 0.10517092]
print("log(1+x) =",np.log1p(x))	# log(1+x) = [0.         0.0009995  0.00995033 0.09531018]

3.3、高级的通用函数特性

3.3.1、指定输出

所有的通用函数都可以通过 out 参数来指定计算结果的存放位置

x = np.arange(5)
y = np.empty(5)
np.multiply(x,10,out=y)
print(y)	# [ 0. 10. 20. 30. 40.]

这个特性也可以被用作数组视图,例如可以将计算结果写入指定数组的每隔一个元素的位置:

y = np.zeros(10)
np.power(2,x,out=y[::2])
print(y)	# [ 1.  0.  2.  0.  4.  0.  8.  0. 16.  0.]

慎重使用 out 可以有效地节约内存。

3.3.2、聚合

有些聚合功能可以直接在对象上计算。例如,如果我们希望用一个特定的运算 reduce 一个数组,那么可以用任何通用函数的 reduce 方法。一个 reduce 方法会对给定的元素和操作重复执行,直至得到单个元素。

例如,对 add 通用函数调用 reduce 方法会返回数组中所有元素的和

x = np.arange(1,6)
print(x)	# [1 2 3 4 5]
print(np.add.reduce(x))	# 15

同样,对 multiply 通用函数调用 reduce 方法会返回数组中所有元素的乘积

print(np.multiply.reduce(x))	# 120

如果需要存储每次计算的中间结果,可以使用 accumulate

print(np.add.accumulate(x))		# [ 1  3  6 10 15]
print(np.multiply.accumulate(x))	# [  1   2   6  24 120]

3.3.3、外积

任何通用函数都可以用 outer 方法获得两个不同输入数组所有元素对的函数运算结果。这意味着,你可以用一行代码实现一个乘法表:

x = np.arange(1,6)
print(np.multiply.outer(x,x))
'''
[[ 1  2  3  4  5]
 [ 2  4  6  8 10]
 [ 3  6  9 12 15]
 [ 4  8 12 16 20]
 [ 5 10 15 20 25]]
'''

通用函数另外一个非常有用的特性是它能够操作不同大小和形状的数组,一组这样的操作被称作广播


4、聚合:最小值、最大值和其他值

4.1、数组值求和

NumPy 的 sum 函数在编译码中执行操作,所以 NumPy 的操作计算得更快一些。

big_array = np.random.random(1000000)
print(np.sum(big_array))	# 500209.12067471276

4.2、最小值和最大值

print(np.max(big_array))	# 0.9999994392723005
print(np.min(big_array))	# 1.4057692298008462e-06

一种更简洁的语法形式是数组对象直接调用这些方法:

print(big_array.min(),big_array.max(),big_array.sum())	
# 1.4057692298008462e-06 0.9999994392723005 500209.12067471276

4.2.1、多维度的聚合

一种常见的聚合操作是沿着一行或一列聚合。例如,假如你有一些数据存在二维数组上:

M = np.random.random((3,4))
print(M)
'''
[[0.0049466  0.25863997 0.62346477 0.90474173]
 [0.71661557 0.699582   0.80401456 0.60471376]
 [0.43905815 0.73525983 0.3703232  0.57361603]]
'''

默认情况下,每一个 NumPy 聚合函数将会返回对整个数组的聚合结果

print(M.sum())	# 6.734976167538792

聚合函数还有一个参数,用于指定沿着哪条轴方向进行聚合。例如,可以通过指定 axis = 0 找到找到每一列的最小值:

print(M.min(axis = 0))	# [0.0049466  0.25863997 0.3703232  0.57361603]

axis 关键字指定的是数组将会被折叠的维度,而不是将要返回的维度。因此指定 axis = 0 意味着第一个轴将要被折叠——对于二维数组,这意味着每一列的值将被聚合。

4.2.2、其他聚合函数

函数名称 NaN 安全版本 描述
np.sum np.nansum 计算元素的和
np.prob np.nanprob 计算元素的积
np.mean np.nanmmean 计算元素的平均值
np.std np.nanstd 计算元素的标准差
np.var np.nanvar 计算元素的方差
np.min np.nanmin 找出最小值
np.max np.nanmax 找出最大值
np.argmin np.nanargmin 找出最小值的索引
np.argmax np.nanargmax 找出最大值的索引
np.median np.nanmedia 计算元素的中位数
np.percentile np.nanpercentile 计算基于元素排序的统计值
np.any N/A 验证是否存在元素为真
np.all N/A 验证所有元素是否为真

5、广播

广播可以简单理解为用于不同大小数组的二元通用函数(加、减、乘等)的一组规则

5.1、广播的介绍

广播允许二元运算符可以用于不同大小的数组。例如,可以简单地将一个标量(可以认为是一个零维的数组)和一个数组相加。

a = np.array([0,1,2])
print(a + 5)	# [5 6 7]

5.2、广播的规则

NumPy 的广播遵循一组严格的规则,设定这组规则是为了决定两个数组间的操作。

  • 规则1:如果两个数组的维度数不一样,那么小维度数组的形状将会在最左边补 1;
  • 规则 2:如果两个数组的形状在任何一个维度上都不匹配,那么数组的形状会沿着维度为 1 的维度扩展以匹配另外一个数组的形状;
  • 规则 3:如果两个数组的形状在任何一个维度上都不匹配并且没有任何一个维度等于 1,那么会引发异常。

5.3、广播的实际应用

5.3.1、数组的归一化

通用函数可以避免用户写一些很慢的 Python 循环。数组的归一化进一步扩展了这个功能。假设你有一个有 10 个观测值的数组,每个观测值包含 3 个数值。按照惯例,我们将用一个 10 × 3 的数组存放该数据:

X = np.random.random((10,3))

我们可以计算每个特征的均值,计算方法是利用 mean 函数沿着第一个维度聚合:

Xmean = X.mean(0)
print(Xmean)	# [0.51985373 0.29768565 0.38901366]

现在通过从 X 数组的元素中减去这个均值实现归一化(该操作是一个广播操作):

X_centered = X - Xmean

为了进一步核对我们的处理是否正确,可以查看一下归一化的数组的均值是否接近 0:

print(X_centered.mean(0))	# [-1.11022302e-17  3.88578059e-17  0.00000000e+00]

5.3.2、画一个二维数组

我们希望定义一个函数 z = f(x,y),可以用广播沿着数值区间计算该函数:

# x 和 y 表示 0~50 个步长的序列
x = np.linspace(0,5,50)
y = np.linspace(0,5,50)[:,np.newaxis]

z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)

我们将用 Matplotlib 来画这个二维数组:

import matplotlib.pyplot as plt
plt.imshow(z,origin='lower',extent=[0,5,0,5],cmap='viridis')
plt.colorbar()

6、比较、掩码和布尔逻辑

用掩码开查看和操作 NumPy 数组中的值。当你想基于某些准则来抽取、修改、计数或对一个数组中的值进行其他操作时,掩码就可以派上用场了。

6.1、和通用函数类似的比较操作

NumPy 不仅实现了 +、-、×、/ 和其他一些运算符的数组逐个元素操作,还实现了如 < 和 > 的逐个元素的比较的通用函数。这些比较运算的结果是一个布尔数据类型的数组。一种有 6 种这种操作:

x = np.array([1,2,3,4,5])
print(x < 3)	# [ True  True False False False]
print(x > 3)	# [False False False  True  True]
print(x <= 3)	# [ True  True  True False False]
print(x >= 3)	# [False False  True  True  True]
print(x != 3)	# [ True  True False  True  True]
print(x == 3)	# [False False  True False False]
运算符 对应的通用函数
== np.equal
!= np.not_equal
< np.less
<= np.less_equal
> np.greater
>= np.greater_equal

和算术运算通用函数一样,这些比较运算通用函数也可以用于任意形状、大小的数组

6.2、操作布尔数组

首先给定一个二维数组:

x = np.array([[5,0,3,3],
              [7,9,3,5],
              [2,4,7,6]])
print(x)

6.2.1、统计记录的个数

如果要统计布尔数组中 True 记录的个数,可以使用 np.count_nonzero 函数

#统计有多少值小于 6
print(np.count_nonzero(x < 6))	# 8

也可以使用 sum,使用 sum 的好处是可以和其他 NumPy 聚合函数一样,这个求和可以沿着行或列进行:

#每行有多少值小于 6
print(np.sum(x < 6,axis=1))		# [4 2 2]

如要快速检查任意或所有这些值是否为 True,可以用 np.any() 或 np.all()

#有没有大于 8 的
print(np.any(x > 8))	# True
#是否所有的值都小于 10
print(np.all(x < 10))	# True

6.2.2、布尔运算

运算符 对应的通用函数
& np.bitwise_and
| np.bitwise_or
^ np.bitwise_xor
~ np.bitwise_not

6.3、将布尔数组作为掩码

通过掩码选择数据的子数据集。例如,我们希望抽取出 x 矩阵中小于 5 的元素:

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

如前面的方法,利用比较运算符可以得到一个布尔数组:

print(x < 5)
'''
[[False  True  True  True]
 [False False  True False]
 [ True  True False False]]
'''

现在为了将这些值选出,可以进行简单的索引,即掩码操作:

print(x[x < 5])		# [0 3 3 3 2 4]

现在返回的是一个一维数组,它包含了所有满足条件的值。


7、花哨的索引

7.1、探索花哨的索引

它意味着传递一个数组来一次性获得多个数组元素

rand = np.random.RandomState(42)

x = rand.randint(100,size=10)
print(x)	# [51 92 14 71 60 20 82 86 74 74]

假设我们希望获得三个不同的元素,可以用下列方式实现:

print([x[3],x[7],x[2]])		# [71, 86, 14]

另一种方法是通过传递索引的单个列表或数组来获得同样的结果:

ind = [3,7,2]
print(x[ind])	# [71 86 14]

利用花哨的索引结果的形状与索引数组的形状一致,而不是与被索引数组的形状一致:

ind = np.array([[3,7],
               [4,5]])
print(x[ind])	
'''
[[71 86]
 [60 20]]
'''

花哨的索引也对多个维度适用。假设我们有以下数组:

X = np.arange(12).reshape((3,4))
print(X)
'''
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
'''

和标准索引一样,第一个索引指的是行,第二个索引指的是列:

row = np.array([0,1,2])
col = np.array([2,1,3])
print(X[row,col])		# [ 2  5 11]

7.2、组合索引

print(X)
'''
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
'''

可以将花哨的索引与简单的索引相组合使用:

print(X[2,[2,0,1]])		# [10  8  9]

也可以将花哨的索引获和切片组合使用:

print(X[1:,[2,0,1]])
'''
[[ 6  4  5]
 [10  8  9]]
'''

更可以将花哨的索引与掩码组合使用:

mask = np.array([1,0,1,0],dtype=bool)
print(X[row[:,np.newaxis],mask])
'''
[[ 0  2]
 [ 4  6]
 [ 8 10]]
'''

7.3、用花哨的索引修改值

假设我们有一个索引数组,并且希望设置数组中对应的值:

x = np.arange(10)
i = np.array([2,1,8,4])
x[i] = 99
print(x)	# [ 0 99 99  3 99  5  6  7 99  9]

可以用任何的赋值操作来实现:

x[i] -= 10
print(x)	# [ 0 89 89  3 89  5  6  7 89  9]

不过操作重复的索引可能会产生一些出乎意料的结果:

x = np.zeros(10)
x[[0,0]] = [4,6]
print(x)		# [6. 0. 0. 0. 0. 0. 0. 0. 0. 0.]		4 被覆盖了

i = [2,3,3,4,4,4]
x[i] += 1
print(x)		# [6. 0. 1. 1. 1. 0. 0. 0. 0. 0.]

没有发生累加。改变的方法是使用 at() 方法:

x = np.zeros(10)
np.add.at(x,i,1)
print(x)		# [0. 0. 1. 2. 3. 0. 0. 0. 0. 0.]

8、数组的排序

8.1、NumPy 中的快速排序:np.sort 和 np.argsort

默认情况下,np.sort 的排序算法是快速排序。

如果想在不修改原始输入数组的基础上返回一个排好序的数组,可以用 np.sort:

x = np.array([2,1,4,3,5])
print(np.sort(x))	# [1 2 3 4 5]

如果希望用排好序的数组替代原始数组,可以使用数组的 sort 方法:

x.sort()
print(x)	# [1 2 3 4 5]

另外一个相关的函数是 argsort,该函数返回的是原始数组排好序的索引值

x = np.array([2,1,4,3,5])
i = np.argsort(x)
print(i)		# [1 0 3 2 4]

这些索引可以被用于(通过花哨的索引)创建有序数组:

print(x[i])		# [1 2 3 4 5]

沿着行或列进行排序

NumPy 排序算法的一个有用的功能是通过 axis 参数,沿着多维数组的行或列进行排序:

rand = np.random.RandomState(42)
X = rand.randint(0,10,(4,6))
print(X)
'''
[[6 3 7 4 6 9]
 [2 6 7 4 3 7]
 [7 2 5 4 1 7]
 [5 1 4 0 9 5]]
'''

对 X 的每一列进行排序:

print(np.sort(X,axis=0))
'''
[[2 1 4 0 1 5]
 [5 2 5 4 3 7]
 [6 3 7 4 6 7]
 [7 6 7 4 9 9]]
'''

对 X 的每一行进行排序:

print(np.sort(X,axis=1))
'''
[[3 4 6 6 7 9]
 [2 3 4 6 7 7]
 [1 2 4 5 7 7]
 [0 1 4 5 5 9]]
'''

8.2、部分排序:分隔

有时候我们并不希望对整个数组进行排序,仅仅希望找到数组中第 K 小的值,NumPy 的 np.partition 函数提供了该功能。np.partition 函数输入的数组和数字 K,输出结果是一个新数组,最左边的是第 K 小的值,往右的任意顺序的其他值

x = np.array([7,2,3,1,6,5,4])
print(np.partition(x,3))	# [2 1 3 4 6 5 7]

注意,结果数组中前三个值是数组中最小的三个值,剩下的位置是原始数组中剩下的值在这两个分隔区中,元素都是任意排序的

与排序类似,也可以沿着多维数组任意的轴进行分隔:

print(X)
'''
[[6 3 7 4 6 9]
 [2 6 7 4 3 7]
 [7 2 5 4 1 7]
 [5 1 4 0 9 5]]
'''
print(np.partition(X,2,axis=1))
'''
[[3 4 6 7 6 9]
 [2 3 4 7 6 7]
 [1 2 4 5 7 7]
 [0 1 4 5 9 5]]
'''

9、结构化数据:NumPy 的结构化数组

假定现在有关一些人的分类数据(如姓名、年龄和体重),现在需要存储这些数据用于 Python 项目。

通过指定复合数据类型,可以构造一个结构化数组:

data = np.zeros(4,dtype={
     'names':('name','age','weight'),'formats':('U10','i4','f8')})
print(data.dtype)
# [('name', '

现在生成了一个空的数组容器,可以将列表数据放入数组中:

name = ['Alice','Bob','Cathy','Doug']
age = [25,45,37,19]
weight = [55.0,85.5,68.0,61.5]

data['name'] = name
data['age'] = age
data['weight'] = weight
print(data)	
# [('Alice', 25, 55. ) ('Bob', 45, 85.5) ('Cathy', 37, 68. ) ('Doug', 19, 61.5)]

所有的数据都放在了一个内存块中。

结构化数组的方便之处在于,你可以通过索引值或名称查看相应的值:

#获取所有名字
print(data['name'])		# ['Alice' 'Bob' 'Cathy' 'Doug']
#获取数据第一行
print(data[0])		# ('Alice', 25, 55.)

利用布尔掩码,还可以做一些更复杂的操作,如按照年龄进行筛选:

print(data[data['age'] < 30]['name'])	# ['Alice' 'Doug']

9.1、生成结构化数组

结构化数组的数据类型有多种制定方式。此前我们看到的是采用字典的方法

print(np.dtype({
     'names':('name','age','weight'),
                'formats':('U10','i4','f8')}))
# [('name', '

符合类型也可以是元组列表

print(np.dtype([('name','S10'),('age','i4'),('weight','f8')]))
# [('name', 'S10'), ('age', '
NumPy 数据类型符号 描述
‘b’ 字节型
‘i’ 有符号整型
‘u’ 无符号整型
‘f’ 浮点数
‘c’ 复数浮点数
‘S’、‘a’ 字符串
‘U’ Unicode 编码字符串
‘V’ 原生数据、raw data(空,void)

9.2、更高级的复合类型

你可以创建一种类型,其中每个元素都包含一个数组或矩阵。

tp = np.dtype([('id','i8'),('mat','f8',(3,3))])
X = np.zeros(1,dtype=tp)
print(X[0])		# (0, [[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]])
print(X['mat'][0])	
'''
[[0. 0. 0.]
 [0. 0. 0.]
 [0. 0. 0.]]
'''

9.3、记录数组:结构化数组的扭转

NumPy 还提供了 np.recarry 类。它和前面介绍的结构化数组几乎相同,但是它有一个独特第特征:域可以像属性一样获取,而不是像字典的键那样获取。前面的例子通过以下方式获取年龄:

print(data['age'])	# [25 45 37 19]

如果将这些数据当做一个记录数组,我们可以用很少的按键获取这个结果:

data_rec = data.view(np.recarray)
print(data_rec.age)		# [25 45 37 19]

记录数组的不好之处在于,即使使用同样的语法,在获取域时也会有一些额外的开销。

你可能感兴趣的:(Python数据科学,机器学习,python)