前言

第一篇中说好了这个系列要勤更,但是这一篇还是拖得有点久,毕竟是工作之余的学习,各种事情都在耽误吧。上一篇有说到《Python从入门到实战》这本书,这段时间我已经读完了,里面更多的是Python的一些使用基础,如果这一主题的文章继续围绕这本书就不太合适了,我自己也想加快一下学习的速度,做一些更有价值的总结,所以从这一篇开始主要是围绕《利用Python进行数据分析(第二版)》这本书的笔记总结。

关于开发环境,上一篇中我说到了我是使用的PyCharm,不过在学习的过程中我还是不推荐直接使用IDE,所以这之后我的笔记都是使用的IPython或是Jupyter notebook,写起来很方便。我是在MacBook Pro下写的,直接下载的Anaconda包,不需要做什么配置,简单便捷。

废话就不多说了,进入正题。

什么是NumPy?

NumPy(Numerical Python)是目前Python数值计算中最为重要的基础包,主要包含以下内容:

·高效多维数组ndarray,提供了基于数组的便捷算数操作以及灵活的广播功能;

·对所有数据进行快速的矩阵计算,而无需编写循环程序;

·对硬盘中数组数据进行读写的工具,并对内存映射文件进行操作;

·线性代数、随机数生成以及傅里叶变换功能;

·用于连接NumPy到C、C++和FORTRAN语言类库的C语言API。

这一大堆概念可能太过抽象,我们一步一步地来看。

多维数组对象ndarray

ndarray是NumPy的核心特征之一,它是Python中一个快速、灵活的大型数据容器。我们的很多计算都是在它的基础上进行的。那要怎么生成一个ndarray对象呢?最简单的方式就是使用array函数:

In [2]: import numpy as npIn [3]: data1 = [6, 7.5, 8, 0, 1]In [4]: arr1 = np.array(data1)In [5]: arr1
Out[5]: array([6. , 7.5, 8. , 0. , 1. ])

可以看到调用array函数后产生的ndarray对象中的值都变成float类型了,这是因为ndarray是一个通用的多维同类数据容器,也就是说它包含的每一个元素均为相同类型,我们可以通过shape属性和dtype属性来分别查看ndarray对象的每一维度数量以及其数据类型:

In [6]: arr1.shape
Out[6]: (5,)In [7]: arr1.dtype
Out[7]: dtype('float64')

可以看到arr1为一个一维数组,数据类型为float64。

array同样可以将嵌套的序列转换为ndarray:

In [8]: data2 = [[1, 2, 3, 4], [5, 6, 7, 8]]In [9]: arr2 = np.array(data2)In [10]: arr2
Out[10]:
array([[1, 2, 3, 4],[5, 6, 7, 8]])In [11]: arr2.shape
Out[11]: (2, 4)In [12]: arr2.dtype
Out[12]: dtype('int64')

data2中有两个等长的列表,在经过array函数转换后即可生成多维数组。除非我们显示地指定了生成数组的数据类型,否则array函数将会自动推断生成数据的类型。

In [13]: arr3 = np.array(data1, 'int64')In [14]: arr3
Out[14]: array([6, 7, 8, 0, 1])

这样生成的ndarray中的数据类型就成了我们指定的int64了。

当然,你可能还有点好奇如果上面代码中的data2中的元素是两个不等长的列表的话,array函数的执行结果会是什么样子:

In [2]: data3 = [[1, 2, 3], [4, 5, 6, 7]]In [3]: arr3 = np.array(data3)In [4]: arr3
Out[4]: array([list([1, 2, 3]), list([4, 5, 6, 7])], dtype=object)In [5]: arr3.shape
Out[5]: (2,)In [6]: arr3.dtype
Out[6]: dtype('O')

可以看到,arr3会被赋值为一个一维数组,它有两个list类型的元素,这也很好理解。

除了array函数以外,我们还有很多别的方法创建ndarray对象。直接看代码吧:

In [7]: np.zeros(10)
Out[7]: array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])In [8]: np.zeros(10, 'int64')
Out[8]: array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])In [11]: np.zeros((2, 3), 'int64')
Out[11]:
array([[0, 0, 0],[0, 0, 0]])In [12]: np.empty((2, 3), 'int64')
Out[12]:
array([[0, 0, 0],[0, 0, 0]])In [13]: np.arange(10)
Out[13]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

zeros函数将会根据指定的大小和形状创建全0数组,empty函数也能实现,但是在书中特别标注了使用empty函数并不安全,因为有些时候它可能会返回未初始化的垃圾数值(具体是什么现象我也不知道,反正尽量别用这一函数就对了)。zeros函数和empty函数默认生成的数组数据类型都是float64,参数中可以指定数据类型,如以上代码中我指定了数据类型为int64。arange函数是Python内建函数range的数组版,关于range函数上一章我提到过,其返回值是一个可迭代的对象,np.arange其实相当于np.array(range())这样的用法。

当然还有一些其他生成多维数组的函数,在后面实际使用到时我们再聊。

聊聊dtype

上文中已经提到了dtype,可以简单地将其理解为ndarray的数据类型。书上有一段很拗口的解释,它是一个特殊的对象,它包含了ndarray需要为某一种数据类型所申明的内存块信息。这个解释虽然拗口但是并不难理解,之前我提到过,ndarray是一个多维同类数组容器,dtype就包含了它的数据类型的信息。

dtype是NumPy能与其他数据系统灵活交互的原因,其中的原理显得有些抽象了,这里就暂时略过。数据的dtype通常都是按照类型名+元素的位数的方式命名,如上文中出现的float64,其含义就是64位双精度浮点型数据。

我们可以使用astype方法来显示地转换ndarray对象的数据类型:

In [4]: arr = np.array([1, 2, 3, 4, 5])In [5]: arr.dtype
Out[5]: dtype('int64')In [14]: float_arr = arr.astype(np.float64)In [15]: float_arr.dtype
Out[15]: dtype('float64')

NumPy数组算数

ndarray有一个有趣的特性,我们可以称其为向量化,即无需遍历数组即可对数组进行各种算数操作。

In [16]: arr = np.array([[1, 2, 3], [4, 5, 6]])In [17]: arr
Out[17]:
array([[1, 2, 3],[4, 5, 6]])In [18]: In [18]: arr * arr
Out[18]:
array([[ 1,  4,  9],[16, 25, 36]])In [19]: arr - arr
Out[19]:
array([[0, 0, 0],[0, 0, 0]])In [20]: 1 / arr
Out[20]:
array([[1.        , 0.5       , 0.33333333],[0.25      , 0.2       , 0.16666667]])In [21]: arr * 0.5
Out[21]:
array([[0.5, 1. , 1.5],[2. , 2.5, 3. ]])In [22]: arr2 = np.array([[0, 2, 1], [3, 9, 6]])In [24]: arr > arr2
Out[24]:
array([[ True, False,  True],[ True, False, False]])

当然,这里我们讲到的只是对于相同尺寸的数组间的计算,至于不同尺寸的数组以后再聊(感兴趣的朋友可以自己去了解一下广播特性)。

基础索引与切片

一维数组索引和Python的列表类似,都可以通过索引和切片获得所需的元素,不过还是有些许差别:

In [2]: arr = np.arange(10)
In [7]: arr[5:8] = 12In [8]: arr
Out[8]: array([ 0,  1,  2,  3,  4, 12, 12, 12,  8,  9])

看到这段代码是不是有点惊讶?我们可以来对比一下Python的列表:

In [9]: py_list = list(range(10))
In [14]: py_slice = py_list[5:8]In [15]: py_slice
Out[15]: [5, 6, 7]In [16]: py_slice[1] = 1In [17]: py_slice
Out[17]: [5, 1, 7]In [18]: py_list
Out[18]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

我们可以看到,在Python的内建列表中,切片是对于原列表的一段复制,对其值进行改变的话并不会影响到原列表;而在NumPy中,一个数组的切片相当于这个数组的一段视图,修改这段切片的值也会导致原数组的这一段值被修改——这意味着原数组并不是被复制了。

对于这一点,如果你熟悉其他的编程语言的话可能会有所疑惑,在其他语言的使用场景中,切片操作都是希望尽量不影响到原数组的,可是在NumPy中不一样,因为NumPy被设计出来的初衷便是处理非常大的数组,想想看,对于庞大的数组持续复制将会引起多少内存问题。当然了,如果你一定想要一段切片的拷贝的话,可以通过copy函数。

对于多维数组的索引也很好理解,其中的元素需要一级一级地去取,我们以一个2*2*3的数组为例:array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10 ,11, 12]]]),如果我想取出“2”这个值该怎么办?

In [19]: arr3d = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10 ,11, 12]]])In [20]: arr3d
Out[20]:
array([[[ 1,  2,  3],[ 4,  5,  6]],[[ 7,  8,  9],[10, 11, 12]]])In [21]: arr3d[0, 0, 1]
Out[21]: 2

很简单吧?只需要用逗号分隔每一级的索引就OK了。对于多维数组还有一点挺有趣的:

In [20]: arr3d
Out[20]:
array([[[ 1,  2,  3],[ 4,  5,  6]],[[ 7,  8,  9],[10, 11, 12]]])In [21]: arr3d[0, 0, 1]
Out[21]: 2In [22]: arr3d
Out[22]:
array([[[ 1,  2,  3],[ 4,  5,  6]],[[ 7,  8,  9],[10, 11, 12]]])In [23]: old_values = arr3d[0].copy()In [24]: arr3d[0] = 12In [25]: arr3d
Out[25]:
array([[[12, 12, 12],[12, 12, 12]],[[ 7,  8,  9],[10, 11, 12]]])In [26]: arr3d[0] = old_valuesIn [27]: arr3d
Out[27]:
array([[[ 1,  2,  3],[ 4,  5,  6]],[[ 7,  8,  9],[10, 11, 12]]])

无论是传入标量或者数组,都可以为arr3d[0]赋值。

多维数组的切片或许会有点绕,我们还是看arr3d吧:

In [28]: arr3d
Out[28]:
array([[[ 1,  2,  3],[ 4,  5,  6]],[[ 7,  8,  9],[10, 11, 12]]])In [29]: arr3d[: 1]
Out[29]:
array([[[1, 2, 3],[4, 5, 6]]])In [30]: arr3d[:1, 1:3]
Out[30]: array([[[4, 5, 6]]])In [31]: arr3d[:1, 1:2]
Out[31]: array([[[4, 5, 6]]])In [32]: arr3d[:1, 1:2, 0:2]
Out[32]: array([[[4, 5]]])

其实也很容易理解,对多维数组的切片也是一级一级地进行的,以逗号分开即可。

特殊的索引

接下来我们来看看NumPy中两种比较特殊的索引,分别是布尔索引和神奇索引,它们和其他语言中的列表索引操作可能有很大的区别,但是对于数据分析相当有用。

布尔索引

我来改编一下书中的例子,可能更容易理解。我们先假设现在有一组人名names,同时还有一组每个人名对应的信息data,这两个数组中的元素是一一对应的:

In [34]: names
Out[34]: array(['Tom', 'Jack', 'Bob', 'Carl', 'Jack'], dtype='<U4')In [35]: data = np.array([['Tall', 'Shy'], ['Handsome', 'Kind'], ['Popular', 'Cool'], ['Childish', 'Outstanding'], ['Erudite', 'Smart']])In [36]: data
Out[36]:
array([['Tall', 'Shy'],['Handsome', 'Kind'],['Popular', 'Cool'],['Childish', 'Outstanding'],['Erudite', 'Smart']], dtype='<U11')

把两个数组对应起来,Tall和Shy是对Tom的描述,Handsome和Kind是对Jack的描述……同时对于Jack的描述还有Erudite和Smart,我们有没有什么办法可以迅速地找到某一个人对应的描述呢?直接上代码:

In [37]: names == 'Jack'
Out[37]: array([False,  True, False, False,  True])In [38]: data[names == 'Jack']
Out[38]:
array([['Handsome', 'Kind'],['Erudite', 'Smart']], dtype='<U11')

很简单地,我们得到了所有对于Jack的描述。传入data的索引names == 'Jack'比较特殊,它会生成一个布尔类型的数组,names中与'Jack'相等的元素对应的位置的值会为True,否则为False,将这个布尔数组作为索引传入data,即可以筛选出布尔数组中为True的元素的位置在data数组中所对应的值……这个描述挺绕的,其实看看代码就能懂了。

以布尔数组作为索引,这样的索引自然就叫布尔索引。当然,布尔索引还有很多其他的妙用,比如我们现在想找出除了Jack以外其他人的描述:

In [39]: data[names != 'Jack']
Out[39]:
array([['Tall', 'Shy'],['Popular', 'Cool'],['Childish', 'Outstanding']], dtype='<U11')In [40]: data[~(names == 'Jack')]
Out[40]:
array([['Tall', 'Shy'],['Popular', 'Cool'],['Childish', 'Outstanding']], dtype='<U11')

无论是直接使用names != 'Jack'还是对names == 'Jack'取反,都是OK的。同时,其他的条件运算符如|(Or)和&(And)也是可以使用的,大家可以自己敲敲试试。

与切片不同,通过布尔索引产生的数组都是对原数组的一个拷贝,当我们改变它的值时并不会对原数组产生什么影响,这一点注意一下:

In [49]: search_data = data[names != 'Jack']In [50]: search_data
Out[50]:
array([['Tall', 'Shy'],['Popular', 'Cool'],['Childish', 'Outstanding']], dtype='<U11')In [51]: search_data[:] = 'Handsome'In [52]: search_data
Out[52]:
array([['Handsome', 'Handsome'],['Handsome', 'Handsome'],['Handsome', 'Handsome']], dtype='<U11')In [53]: data
Out[53]:
array([['Tall', 'Shy'],['Handsome', 'Kind'],['Popular', 'Cool'],['Childish', 'Outstanding'],['Erudite', 'Smart']], dtype='<U11')

布尔索引的使用相当广泛,相信在后面的章节会有更详细的使用。

神奇索引

这个索引的命名挺中二的,解释起来有点困难,我们直接上代码:

In [57]: arr = np.arange(12).reshape(3, 4)In [58]: arr
Out[58]:
array([[ 0,  1,  2,  3],[ 4,  5,  6,  7],[ 8,  9, 10, 11]])In [59]: arr[[2, 0]]
Out[59]:
array([[ 8,  9, 10, 11],[ 0,  1,  2,  3]])In [60]: arr[[2, 0], [3, 1]]
Out[60]: array([11,  1])

结合代码来看,对于arr这个3*4的二维数组,将一个一维数组[2, 0]作为它的索引,返回的值则为arr[2]和arr[0]组成的数组,如果将[[2, 0], [3, 1]]作为索引,返回值则为arr[2, 3]和arr[0, 1]组成的数组。喏,这就是神奇索引,其实还是蛮抽象的吧……

对于神奇索引,官方一点的解释是:使用整数数组进行数据索引。将这句话结合上面的代码揣摩一下,就容易理解得多了。

数组转置与换轴

只要是学过线代的朋友,对转置都不会陌生,例如有一个矩阵a,矩阵中的元素可以表示为a[x][y],将a转置之后,其中的元素就变成了a[y][z],在NumPy中数组的转置与此类似。我们可以将二维数组看成一个矩阵,在二维数组arr2d中,它的元素可以表示为arr2d[x, y],而在对其进行转置后,其中的元素就变成了arr2d[y, x],和在线代中一样,转置是用字母T表示,ndarray具有一个特殊的属性T,可以获取到其转置后的数组:

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

换轴,正如字面的意思,对于二维数组来说,转置就是一次换轴,调换x轴与y轴的过程,接下来我们拓展到三维数组。ndarray还有两个针对换轴操作的方法,分别是transpose方法和swapaxes方法。

先说transpose方法,我们可以向其传入一个轴编号的元组,它将根据这个轴编号去改变元素在数组中的位置。我们可以把三维数组最外层的一维看做轴0,往内层的维度则依次加1,因此一个三维数组的正常维度序列是(0, 1, 2),可以对应(x, y, z),如果我们往transpose方法中传入(1, 0, 2),则是将原数组中的元素位置变为了(y, x, z)。来看看例子:

In [8]: arr3d = np.arange(30).reshape(2, 3, 5)In [9]: arr3d
Out[9]:
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]]])In [10]: arr3d.transpose((1, 0, 2))
Out[10]:
array([[[ 0,  1,  2,  3,  4],[15, 16, 17, 18, 19]],[[ 5,  6,  7,  8,  9],[20, 21, 22, 23, 24]],[[10, 11, 12, 13, 14],[25, 26, 27, 28, 29]]])

在搞懂了transpose方法之后,理解swapaxes方法也就很容易了。swapaxes方法接收一对轴编号,将传入的这对轴编号位置互换,例如还是这个三维数组arr3d,原本其中每个元素位置都是正常的(x, y, z),如果向swapaxes方法中传入(0, 1),则是意味着将x轴和y轴进行互换,这样一来其中的元素位置就变成了(y, x, z),运行结果跟上面使用transpose方法的代码应该是一样的,我们来验证一下:

In [11]: arr3d.swapaxes(0, 1)
Out[11]:
array([[[ 0,  1,  2,  3,  4],[15, 16, 17, 18, 19]],[[ 5,  6,  7,  8,  9],[20, 21, 22, 23, 24]],[[10, 11, 12, 13, 14],[25, 26, 27, 28, 29]]])

喏,毫无问题。对于更高维度的数组也可以按这样的思想推导,到这里,数组的转置和换轴应该不会有什么不理解的了吧?

小节

这篇文章就先写到这里。这次主要是总结了NumPy中的多维数组(ndarray)的相关基础知识,之前嫌转置和换轴这个总结起来描述麻烦懒得写,现在也补上了。其实大家看完这章会发现数据分析会涉及到很多数学知识,对于理工科出身的朋友来说基本都学过几何与线性代数,这方面的知识都不会有什么大问题,如果是半路出家的程序员的话,我还是建议大家了解一些高等数学的相关概念,这样在学习和应用中会少很多压力。

Python数据分析入门(二)——从NumPy数组说起相关推荐

  1. python数据分析(二)——numpy数组的计算

    系列文章: python数据分析(一)--numpy数组的创建 python数据分析(三)--numpy读取本地数据和索引 python数据分析(四)--numpy中的nan和数据的填充 python ...

  2. python数据分析(四)——numpy中的nan和数据的填充

    系列文章: python数据分析(一)--numpy数组的创建 python数据分析(二)--numpy数组的计算 python数据分析(三)--numpy读取本地数据和索引 python数据分析(五 ...

  3. python数据分析(五)——numpy+matplotlib实例

    系列文章: python数据分析(一)--numpy数组的创建 python数据分析(二)--numpy数组的计算 python数据分析(三)--numpy读取本地数据和索引 python数据分析(四 ...

  4. Python数据分析入门教程(更新中)

    Python数据分析入门教程 你好! 这是一篇适用于初学者的Python数据分析入门教程 1. Numpy关于矩阵的操作 1.1数组与矩阵的基本概念 矩阵:矩阵是一个按照长方阵列排列的实数或复数集合( ...

  5. python数据分析入门【二】 --- 数据处理

    python数据分析入门[二] - 数据处理 上一章内容python数据分析入门[一] - DataFrame & Series 下一章内容python数据分析入门[三] - 数据分析 文章目 ...

  6. python使用np.argsort对一维numpy概率值数据排序获取倒序索引、获取的top索引(例如top2、top5、top10)索引二维numpy数组中对应的原始数据:原始数据概率最大的头部数据

    python使用np.argsort对一维numpy概率值数据排序获取倒序索引.获取的top索引(例如top2.top5.top10)索引二维numpy数组中对应的原始数据:原始数据概率最大的头部数据 ...

  7. python使用np.argsort对一维numpy概率值数据排序获取升序索引、获取的top索引(例如top2、top5、top10)索引二维numpy数组中对应的原始数据:原始数据概率最小的头部数据

    python使用np.argsort对一维numpy概率值数据排序获取升序索引.获取的top索引(例如top2.top5.top10)索引二维numpy数组中对应的原始数据:原始数据概率最小的头部数据 ...

  8. Python使用numpy函数vsplit垂直(行角度)拆分numpy数组(返回拆分后的numpy数组列表)实战:垂直拆分二维numpy数组、split函数垂直拆分二维numpy数组

    Python使用numpy函数vsplit垂直(行角度)拆分numpy数组(返回拆分后的numpy数组列表)实战:垂直拆分二维numpy数组.split函数垂直拆分二维numpy数组 目录

  9. Python使用numpy函数hsplit水平(按列)拆分numpy数组(返回拆分后的numpy数组列表)实战:水平(按列)拆分二维numpy数组、split函数水平(按列)拆分二维numpy数组

    Python使用numpy函数hsplit水平(按列)拆分numpy数组(返回拆分后的numpy数组列表)实战:水平(按列)拆分二维numpy数组.split函数水平(按列)拆分二维numpy数组 目 ...

  10. Python数据分析入门之pandas基础总结

    Pandas--"大熊猫"基础 Series Series: pandas的长枪(数据表中的一列或一行,观测向量,一维数组...) Series1 = pd.Series(np.r ...

最新文章

  1. 如果三十年前有这些AI技术,可可西里的悲剧不会发生
  2. HLSL Texture Object Sample 的一些笔记
  3. 【PC工具】推荐11个在四大使用场景中的常用工具,按场景总结常用电脑工具软件,常用办公工具软件...
  4. MakeFile中文手册(免费下载)-徐海兵整理
  5. 操作系统:程序的编译、链接、装入及地址转换
  6. html 定时刷新 数据,js中,设置定时器 每隔几秒刷新一次页面数据
  7. hibernate--
  8. TypeError: can't pickle _thread.RLock objects
  9. 同前端联调过程中遇到的坑
  10. latex 插入表格_【2020.11.30】IEEE trans英文latex写作心得和学习历程
  11. 03.搭建Spark集群(CentOS7+Spark2.1.1+Hadoop2.8.0)
  12. system.Exception:端口已被占用1080
  13. vue 定位所在地_记录 vue 实现Web端的定位功能 获取经纬度
  14. Actor 模型是什么?Gear 为什么使用它?
  15. mysql8只有ibd文件_只有ibd文件还能恢复数据吗
  16. Web开发过程流程图
  17. 好心情:22个表达好心情的经典句子,送给抑郁焦虑的你
  18. 电气系统中防雷接地保护的综合解决方案
  19. 收购快钱做线下支付,京东数科与蚂蚁终有一战?
  20. 博客搬家到Octopress(Mac)

热门文章

  1. Kyng Alisaunder
  2. python交换机ssh巡检_python自动巡检H3C交换机
  3. 粗读MD-UNET: Multi-input dilated U-shape neural network for segmentation of bladder cancer
  4. 你想要创建一个属于自己的网站吗?十大免费网站
  5. html怎么电视连电脑,电脑怎么投屏到电视?图文讲解电脑投屏具体方法步骤
  6. FPGA verilog基本外设练习(六)- 以太网通信模块
  7. 2021-10-20 推荐一个在线视频格式转换的好网站https://www.zamzar.com/,我测试过mp4转到gif
  8. 【爬虫专栏18】多线程爬笔趣阁遮天
  9. linux下安装ab压力测试工具及ab命令详解
  10. 【C#】WPF实现经典纸牌游戏,适合新手入门