Python 全栈 60 天精通之路

Day 29:NumPy 通过这五大功能顺利入门 + 10 道练习题

发布日期:2022年3月12日 18:02 阅读: 242 访问: 243

NumPy 更高效Python 已经提供了很多丰富的内置包,我们为什么还要学习 NumPy 呢?先看一个例子,找寻学习 NumPy 的必要性和重要性。打开 IPython,创建 Python 的列表 a 对象。然后,使用列表生成式,创建一个元素都为原来两倍的新列表 a2,并统计这一行的用时为 95.7 ms。<code clas

NumPy 更高效

Python 已经提供了很多丰富的内置包,我们为什么还要学习 NumPy 呢?

先看一个例子,找寻学习 NumPy 的必要性和重要性。

打开 IPython,创建 Python 的列表 a 对象。然后,使用列表生成式,创建一个元素都为原来两倍的新列表 a2,并统计这一行的用时为 95.7 ms。

In [76]: a = list(range(1000000))

In [77]: %time a2 = [i*2 for i in a]
Wall time: 95.7 ms

使用 NumPy,创建同样大小和取值的数组 na。然后,对每个元素乘以 2,返回一个新数组 na2,用时为 2 ms。

In [78]: import numpy as np

In [79]: na = np.array(range(1000000))

In [80]: %time na2 = na * 2
Wall time: 2 ms

完成同样的都对元素乘以 2 的操作,NumPy 比 Python 快了 45 倍之多。

这就是我们要学好 NumPy 的一个重要理由,它在处理更大数据量时,处理效率明显快于 Python;并且内置的向量化运算和广播机制,使得使用 NumPy 更加简洁,会少写很多嵌套的 for 循环,因此代码的可读性大大增强。

NumPy 计算为什么这么快呢?

有多个原因:

  • Python 的 list 是一个通用结构。它能包括任意类型的对象,并且是动态类型。
  • NumPy 的 ndarray 是静态、同质的类型,当 ndarray 对象被创建时,元素的类型就确定。由于是静态类型,所以 ndarray 间的加、减、乘、除用 C 和 Fortran 实现才成为可能,所以运行起来就会更快。根据官当介绍,底层代码用 C 语言和 Fortran 语言实现,实现性能无限接近 C 的处理效率。

由此可见,NumPy 就非常适合做大规模的数值计算和数据分析。

今天,我们一起学习 NumPy 的基本使用,借助实际的 iris 数据集,使用例子帮助大家最快掌握 NumPy 那些最高频使用的函数。

创建 NumPy 数组

创建一个 ndarray 数组对象,有很多种方法。array 函数能创建新的数组;arange、linspace 等方法;从文件中读入数据返回一个 ndarray 对象;多个 ndarray 对象又能构造生成一个新的 ndarray 对象。

1. 通过构造函数 array 创建一维 array

import numpy as np

In [2]: v = np.array([1,2,3,4])

In [3]: v
Out[3]: array([1, 2, 3, 4])

2. 创建二维 array

In [4]: m = np.array([[1,2],[3,4]])

In [5]: m
Out[5]:
array([[1, 2],
       [3, 4]])

v 和 m 的类型都是 ndarray,NumPy 中最主要的数据结构。

In [6]: type(v),type(m)
Out[6]: (numpy.ndarray, numpy.ndarray)

3. arange 数组

In [94]: ara = np.arange(1,10)

In [95]: ara
Out[95]: array([1, 2, 3, 4, 5, 6, 7, 8, 9])

4. linspace 数组,15 个元素

In [97]: np.linspace(1,10,15)
Out[97]:
array([ 1.        ,  1.64285714,  2.28571429,  2.92857143,  3.57142857,
        4.21428571,  4.85714286,  5.5       ,  6.14285714,  6.78571429,
        7.42857143,  8.07142857,  8.71428571,  9.35714286, 10.        ])

5. 组合 ndarray 对象

如下创建一个 ndarray 对象 a:

In [98]: a = np.arange(10).reshape(2,-1)

In [99]: a
Out[99]:
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])

找出 a 中大于 3 的元素索引,使用 where 方法,返回一个元组,带有 2 个 ndarray 对象,分别表示大于 3 的元素第一维、第二维度中的位置:

In [100]: np.where(a>3)
Out[100]:
(array([0, 1, 1, 1, 1, 1], dtype=int64),
 array([4, 0, 1, 2, 3, 4], dtype=int64))

where 方法返回值可读性不强,我们把它拼接为一个 ndarray 对象:

In [101]: np.array(np.where(a>3))
Out[101]:
array([[0, 1, 1, 1, 1, 1],
       [4, 0, 1, 2, 3, 4]], dtype=int64)

然后,再转置,使用 np.transpose 方法:

tuple_to_array = np.array(np.where(a>3))
np.transpose(tuple_to_array)

结果,这回一看就明白了,[0,4] 表示在原 ndarray 对象 a 上的索引:

Out[102]:
array([[0, 4],
       [1, 0],
       [1, 1],
       [1, 2],
       [1, 3],
       [1, 4]], dtype=int64)

NumPy 数组属性

1. shape 属性

数组的形状信息,非常重要。在深度学习中,构建网络模型,调试多维数组运算代码时,shape 的作用更加凸显。shape 属性返回数组的形状信息,是一个元组对象。

如下,分别创建一维数组 v,二维数组 m:

In [108]: v = np.zeros(10)

In [109]: m = np.ones((3,4))

打印它们的 shape 信息,(10,) 表示为一维数组,且第一维的长度为 10。当元组只有一个元素时,为什么写成这样,我们在前面的 Python 基础部分、进阶部分都提到过。

In [110]: v.shape
Out[110]: (10,)

In [111]: m.shape
Out[111]: (3, 4)

size 属性获取数组内元素个数:

In [112]: m.size
Out[112]: 12

dtype 属性获取数组内元素的类型:

In [113]: m.dtype
Out[113]: dtype('float64')

如果我们尝试用 str 类型赋值给 m,就会报错:

In [10]: m[0,0]='hello'

ValueError: could not convert string to float: 'hello'

创建数组时,还可以通过为 dtype 赋值,指定元素类型:

In [117]: m = np.array([1,2,3],dtype='float')

In [118]: m
Out[118]: array([1., 2., 3.])

dtype 更多取值:int、complex、bool、object,还可以显示的定义数据位数的类型,如:int64、int16、float128、complex128。

个数组函数

1. arange 函数

起始点,终点,步长;不包括终点。前面用到过,不再介绍。

2. li nspace 函数

起始点,终点,分割份数;包括终点。

In [5]: np.linspace(0,10,5)
Out[5]: array([ 0. ,  2.5,  5. ,  7.5, 10. ])

3. logspace 函数

创建以 e 为底,指数为 1,2,…,10 的数组:

In [17]: np.logspace(1, 10, 10, base=e)
Out[17]:
array([2.71828183e+00, 7.38905610e+00, 2.00855369e+01, 5.45981500e+01,
       1.48413159e+02, 4.03428793e+02, 1.09663316e+03, 2.98095799e+03,
       8.10308393e+03, 2.20264658e+04])

4. diag

创建对角数组:

In [22]: np.diag([1,2,3])
Out[22]:
array([[1, 0, 0],
       [0, 2, 0],
       [0, 0, 3]])

主对角线偏移 1 的数组:

In [23]: np.diag([1,2,3],k=1)
Out[23]:
array([[0, 1, 0, 0],
       [0, 0, 2, 0],
       [0, 0, 0, 3],
       [0, 0, 0, 0]])

5. zeros

创建元素全都为 0 的数组:

In [24]: np.zeros((3,3))
Out[24]:
array([[0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.]])

6. ones

创建元素全都为 1 的数组:

In [25]: np.ones((3,3))
Out[25]:
array([[1., 1., 1.],
       [1., 1., 1.],
       [1., 1., 1.]])

7. 随机数组

np.random 模块生成随机数组,更加方便。

生成 0~1、shape 为 (3,5) 的随机数数组:

In [2]: np.random.rand(3,5)
Out[2]:
array([[0.25366147, 0.18996607, 0.01599463, 0.08113353, 0.99794258],
       [0.38813147, 0.33669704, 0.97040282, 0.0836301 , 0.55533133],
       [0.17767781, 0.94982834, 0.52045864, 0.58504198, 0.40904079]])

索引和筛选

NumPy 索引,功能强大,不仅支持切片操作,还支持布尔型按条件筛选操作。

In [2]: m = np.arange(18).reshape(2,3,3)

In [3]: m
Out[3]:
array([[[ 0,  1,  2],
        [ 3,  4,  5],
        [ 6,  7,  8]],

       [[ 9, 10, 11],
        [12, 13, 14],
        [15, 16, 17]]])

: 表示此维度的所有元素全部获取:

In [4]: m[:,1:3,:]
Out[4]:
array([[[ 3,  4,  5],
        [ 6,  7,  8]],

       [[12, 13, 14],
        [15, 16, 17]]])
In [5]: m[:,1:3,:1]
Out[5]:
array([[[ 3],
        [ 6]],

       [[12],
        [15]]])

按照维度赋值:

In [7]: m[:,0,:] = -1

In [8]: m
Out[8]:
array([[[-1, -1, -1],
        [ 3,  4,  5],
        [ 6,  7,  8]],

       [[-1, -1, -1],
        [12, 13, 14],
        [15, 16, 17]]])
In [11]: m[:,:,0]
Out[11]:
array([[-1,  3,  6],
       [-1, 12, 15]])

NumPy 还支持掩码索引,用于元素筛选,非常方便。

判断上面切片 m[:,:,0] 中大于 5 的元素,写法简洁,无需写 for 循环。

In [12]: mt = m[:,:,0]

In [13]: mt[mt>5]
Out[13]: array([ 6, 12, 15])

案例:数据归一化

1. 下载数据

使用 NumPy,下载 iris 数据集。仅提取 iris 数据集的第二列 usecols = [1]

import numpy as np

url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data'
wid = np.genfromtxt(url, delimiter=',', dtype='float', usecols=[1])

展示数据:

array([3.5, 3. , 3.2, 3.1, 3.6, 3.9, 3.4, 3.4, 2.9, 3.1, 3.7, 3.4, 3. ,
       3. , 4. , 4.4, 3.9, 3.5, 3.8, 3.8, 3.4, 3.7, 3.6, 3.3, 3.4, 3. ,
       3.4, 3.5, 3.4, 3.2, 3.1, 3.4, 4.1, 4.2, 3.1, 3.2, 3.5, 3.1, 3. ,
       3.4, 3.5, 2.3, 3.2, 3.5, 3.8, 3. , 3.8, 3.2, 3.7, 3.3, 3.2, 3.2,
       3.1, 2.3, 2.8, 2.8, 3.3, 2.4, 2.9, 2.7, 2. , 3. , 2.2, 2.9, 2.9,
       3.1, 3. , 2.7, 2.2, 2.5, 3.2, 2.8, 2.5, 2.8, 2.9, 3. , 2.8, 3. ,
       2.9, 2.6, 2.4, 2.4, 2.7, 2.7, 3. , 3.4, 3.1, 2.3, 3. , 2.5, 2.6,
       3. , 2.6, 2.3, 2.7, 3. , 2.9, 2.9, 2.5, 2.8, 3.3, 2.7, 3. , 2.9,
       3. , 3. , 2.5, 2.9, 2.5, 3.6, 3.2, 2.7, 3. , 2.5, 2.8, 3.2, 3. ,
       3.8, 2.6, 2.2, 3.2, 2.8, 2.8, 2.7, 3.3, 3.2, 2.8, 3. , 2.8, 3. ,
       2.8, 3.8, 2.8, 2.8, 2.6, 3. , 3.4, 3.1, 3. , 3.1, 3.1, 3.1, 2.7,
       3.2, 3.3, 3. , 2.5, 3. , 3.4, 3. ])

单变量(univariate),长度为 150 的一维 NumPy 数组。

2. 归一化

求出最大值、最小值:

smax = np.max(wid)
smin = np.min(wid)

In [51]: smax,smin
Out[51]: (4.4, 2.0)

归一化公式:

s = (wid - smin) / (smax - smin)

还有一个更简便的方法,使用 ptp 方法,它直接求出最大值与最小值的差:

s = (wid - smin) / wid.ptp()

3. NumPy 的打印设置

只打印小数点后三位的设置方法:

np.set_printoptions(precision=3)  

归一化结果:

array([0.625, 0.417, 0.5  , 0.458, 0.667, 0.792, 0.583, 0.583, 0.375,
       0.458, 0.708, 0.583, 0.417, 0.417, 0.833, 1.   , 0.792, 0.625,
       0.75 , 0.75 , 0.583, 0.708, 0.667, 0.542, 0.583, 0.417, 0.583,
       0.625, 0.583, 0.5  , 0.458, 0.583, 0.875, 0.917, 0.458, 0.5  ,
       0.625, 0.458, 0.417, 0.583, 0.625, 0.125, 0.5  , 0.625, 0.75 ,
       0.417, 0.75 , 0.5  , 0.708, 0.542, 0.5  , 0.5  , 0.458, 0.125,
       0.333, 0.333, 0.542, 0.167, 0.375, 0.292, 0.   , 0.417, 0.083,
       0.375, 0.375, 0.458, 0.417, 0.292, 0.083, 0.208, 0.5  , 0.333,
       0.208, 0.333, 0.375, 0.417, 0.333, 0.417, 0.375, 0.25 , 0.167,
       0.167, 0.292, 0.292, 0.417, 0.583, 0.458, 0.125, 0.417, 0.208,
       0.25 , 0.417, 0.25 , 0.125, 0.292, 0.417, 0.375, 0.375, 0.208,
       0.333, 0.542, 0.292, 0.417, 0.375, 0.417, 0.417, 0.208, 0.375,
       0.208, 0.667, 0.5  , 0.292, 0.417, 0.208, 0.333, 0.5  , 0.417,
       0.75 , 0.25 , 0.083, 0.5  , 0.333, 0.333, 0.292, 0.542, 0.5  ,
       0.333, 0.417, 0.333, 0.417, 0.333, 0.75 , 0.333, 0.333, 0.25 ,
       0.417, 0.583, 0.458, 0.417, 0.458, 0.458, 0.458, 0.292, 0.5  ,
       0.542, 0.417, 0.208, 0.417, 0.583, 0.417])

分布可视化:

import seaborn as sns
sns.distplot(s,kde=False,rug=True)

频率分布直方图:

sns.distplot(s,hist=True,kde=True,rug=True)

带高斯密度核函数的直方图:

10 道 NumPy 基础题

1. 创建一个 [3,5] 所有元素为 True 的数组

In [15]: np.ones((3,5),dtype=bool)
Out[15]:
array([[ True,  True,  True,  True,  True],
       [ True,  True,  True,  True,  True],
       [ True,  True,  True,  True,  True]])

2. 一维数组转二维

In [18]: a =  np.linspace(1,5,10)

In [19]: a.reshape(5,2)
Out[19]:
array([[1.        , 1.44444444],
       [1.88888889, 2.33333333],
       [2.77777778, 3.22222222],
       [3.66666667, 4.11111111],
       [4.55555556, 5.        ]])

3. 数组所有奇数替换为 -1

In [14]: m = np.arange(10).reshape(2,5)

In [16]: m[m%2==1] = -1
In [17]: m
Out[17]:
array([[ 0, -1,  2, -1,  4],
       [-1,  6, -1,  8, -1]])

4. 提取出数组中所有奇数

In [18]: m = np.arange(10).reshape(2,5)

In [19]: m[m%2==1]
Out[19]: array([1, 3, 5, 7, 9])

复制

5. 2 个 NumPy 数组的交集

In [21]: m ,n = np.arange(10), np.arange(1,15,3)

In [22]: np.intersect1d(m,n)
Out[22]: array([1, 4, 7])

6. 2 个 NumPy 数组的差集

In [21]: m ,n = np.arange(10), np.arange(1,15,3)

In [23]: np.setdiff1d(m,n)
Out[23]: array([0, 2, 3, 5, 6, 8, 9])

7. 筛选出指定区间内的所有元素

注意:(m >2),必须要添加一对括号

In [21]: m = np.arange(10).reshape(2,5)
In [34]: m[(m > 2) & (m < 7)]
Out[34]: array([3, 4, 5, 6])  

8. 二维数组交换 2 列

In [21]: m = np.arange(10).reshape(2,5)
In [37]: m
Out[37]:
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])

In [36]: m[:,[1,0,2,3,4]]
Out[36]:
array([[1, 0, 2, 3, 4],
       [6, 5, 7, 8, 9]])

可以一次交换多列:

In [38]: m[:,[1,0,2,4,3]]
Out[38]:
array([[1, 0, 2, 4, 3],
       [6, 5, 7, 9, 8]])

9. 二维数组,反转行

In [39]: m = np.arange(10).reshape(2,5)

In [40]: m
Out[40]:
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])

In [41]: m[::-1]
Out[41]:
array([[5, 6, 7, 8, 9],
       [0, 1, 2, 3, 4]])

10. 生成数值 5~10、shape 为 (3,5) 的随机浮点数

In [9]: np.random.seed(100)

In [42]: np.random.randint(5,10,(3,5)) + np.random.rand(3,5)
Out[42]:
array([[9.31623868, 5.68431289, 9.5974916 , 5.85600452, 9.3478736 ],
       [5.66356114, 7.78257215, 7.81974462, 6.60320117, 7.17326763],
       [7.77318114, 6.81505713, 9.21447171, 5.08486345, 8.47547692]])

小结

今天与大家一起学习了数值计算很有用的 NumPy 包,包括:

  • 数组创建
  • 数组的常用属性
  • 7 个生成数组的函数
  • 强大的索引,掩码索引,数据筛选
  • 一个使用 NumPy 完成数据归一化的案例
  • 10 道 NumPy 基础题目

下一讲,深入探讨 NumPy 包其他常用的方法,及其对应的 NumPy 练习题等。