Python数据分析笔记#5.1 Numpy-多维数组

liftword3周前 (12-06)技术文章16


「目录」

  • Numpy介绍
  • Numpy的多维数组
  • 创建多维数组
  • 多维数组的数据类型
  • Numpy数组的运算
  • Numpy数组的索引和切片
  • 数组转置和轴对换



Numpy简介

Numpy(Numerical Python)是Python数值计算最重要的基础包。

Numpy的功能:

  • ndarray,多维数组,具有矢量运算和复杂广播能力,且节省空间
  • 不需要编写循环就可以对整组数据进行快速运算的函数。
  • 读写磁盘的工具。
  • 线性代数,随机数生成,傅里叶变换功能。
  • 集成C,C++,Fortran等语言编写的C API

Numpy对于数值计算特别重要的原因之一是,高效的处理大数组的数据

原因:

  • 比起Python的内置序列,Numpy数组使用的内存更少
  • Numpy可以在整个数组上执行复杂运算,不需要Python的for循环

基于Numpy的算法要比纯Python快10到100倍(或更快),并且使用内存更少。


Numpy的ndaray:多维数组

ndarray是一个快速而灵活的大数据集容器,我们可以利用这种数组对整块数据执行数学运算。

ndarray中所有元素必须相同类型。


创建ndarray

可以使用array函数创建Numpy数组,array函数接收一切序列型对象,例如我们可以把一个列表转化为数组:

In [1]: import numpy as np

在导入numpy库时,惯例给他取个更简短的名字叫np

In [2]: data1 = [6, 7.5, 8, 0, 1]

In [3]: arr1 = np.array(data1)

In [4]: arr1
Out[4]: array([6. , 7.5, 8. , 0. , 1. ])

如果是嵌套列表会被转化为多维数组

In [5]: data2 = [[1, 2, 3, 4], [5, 6, 7, 8]]

In [6]: arr2 = np.array(data2)

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

用属性ndimshape可以查看数组的维度

In [8]: arr2.ndim
Out[8]: 2

数组有2个维度

In [9]: arr2.shape
Out[9]: (2, 4)

这是2*4的数组,有2个维度,第一个维度大小为2,第二个维度大小为4

我们还可以查看数组的数据类型:

In [10]: arr1.dtype
Out[10]: dtype('float64')

In [11]: arr2.dtype
Out[11]: dtype('int32')

数组arr1的数据类型是64位浮点数float,数组arr2的数据类型是32位整数int

可以使用np.zeros创建全是0的数组

In [12]: np.zeros(10)
Out[12]: array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])

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

np.empty创建一个没有具体值的数组

In [14]: np.empty((2, 3, 2))
Out[14]:
array([[[0., 0.],
        [0., 0.],
        [0., 0.]],

       [[0., 0.],
        [0., 0.],
        [0., 0.]]])

empty不代表返回全0数组,而是为初始化的垃圾值

arange是Python内置函数range的numpy版本:

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

下面的表格列出了其他创建数组的函数:


ndarray的数据类型

dtype(data type数据类型),将一块内存解释为特定数据类型:

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

In [16]: arr1 = np.array([1, 2, 3], dtype=np.float64)

In [17]: arr2 = np.array([1, 2, 3], dtype=np.int32)

In [18]: arr1.dtype
Out[18]: dtype('float64')

In [19]: arr2.dtype
Out[19]: dtype('int32')

我们来看看Numpy的数据类型有哪些:


我们可以通过ndarray的astype方法将一个数组从一个dtype转换为另一个dtype,例如我们把整数转换成浮点数:

In [20]: arr = np.array([1, 2, 3, 4, 5])

In [21]: arr.dtype
Out[21]: dtype('int32')

In [22]: float_arr = arr.astype(np.float64)

In [23]: float_arr.dtype
Out[23]: dtype('float64')

使用astype总会创建一个新的数组


Numpy数组的运算

Numpy数组使得我们可以不用编写循环就可对数据进行批量运算。大小相等的数组之间的任何算数运算都会作用到每个元素,看下面的例子就懂了:

In [24]: arr = np.array([[1., 2., 3.], [4., 5., 6.]])

In [25]: arr
Out[25]:
array([[1., 2., 3.],
       [4., 5., 6.]])

In [26]: arr * arr
Out[26]:
array([[ 1.,  4.,  9.],
       [16., 25., 36.]])

In [27]: arr - arr
Out[27]:
array([[0., 0., 0.],
       [0., 0., 0.]])

In [28]: 1 / arr
Out[28]:
array([[1.        , 0.5       , 0.33333333],
       [0.25      , 0.2       , 0.16666667]])

In [29]: arr ** 0.5
Out[29]:
array([[1.        , 1.41421356, 1.73205081],
       [2.        , 2.23606798, 2.44948974]])

两个乘号是次方

大小相同的数组间比较则会生成布尔值:

In [32]: arr2 = np.array([[0., 4., 1.], [7., 2., 12.]])

In [33]: arr2
Out[33]:
array([[ 0.,  4.,  1.],
       [ 7.,  2., 12.]])

In [34]: arr2 > arr
Out[34]:
array([[False,  True, False],
       [ True, False,  True]])


Numpy数组的索引和切片

索引和切片就是选取数据的单个元素或子集。

一维数组

一维数组的索引和切片很简单,直接看例子就会了:

In [35]: arr = np.arange(10)

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

索引

In [37]: arr[5]
Out[37]: 5

切片

In [38]: arr[5:8]
Out[38]: array([5, 6, 7])

当我们将一个标量值赋值给一个切片时,该值会自动传播(就是之前说的广播broadcast)到整个选区。

In [39]: arr[5:8] = 12

In [40]: arr
Out[40]: array([ 0,  1,  2,  3,  4, 12, 12, 12,  8,  9])

和列表切片的区别是,数组切片的数据不会复制,修改数组切片的数据会改变源数组。举个例子,先创建一个切片:

In [41]: arr_slice = arr[5:8]

In [42]: arr_slice
Out[42]: array([12, 12, 12])

修改切片的值:

In [43]: arr_slice[1] = 12345

查看源数组:

In [44]: arr
Out[44]:
array([    0,     1,     2,     3,     4,    12, 12345,    12,     8,
           9])

你可能惊讶的发现源数组也变了,所以切片就是原始数组的视图。

我猜这样的方式节省了内存,而且频繁的复制也降低了性能。

高维数组

高维数组能做的事情更多。我们先来看二维数组,三维,四维以及以上同理。

In [45]: arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

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

我们查看第3行(第1个维度的第3个)的元素:

In [47]: arr2d[2]
Out[47]: array([7, 8, 9])

查看第1行第3列(第一个维度的第1个,第二个维度的第3个)的元素:

In [48]: arr2d[0][2]
Out[48]: 3

也可以用逗号把两个维度隔开:

In [49]: arr2d[0, 2]
Out[49]: 3

下面一张图也可以帮你理解下二维数组的索引方式,轴0是行,轴1是列:


所以总结一下,在多维数组中省略一个索引,就会返回维度低一个的数组

切片索引

ndarray的切片索引和Python列表的差不多:

一维数组切片:

In [50]: arr
Out[50]:
array([0, 1, 2, 3, 4, 12, 12345, 12, 8, 9])

In [51]: arr[1:6]
Out[51]: array([ 1,  2,  3,  4, 12])

二维数组切片:

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

In [53]: arr2d[:2]
Out[53]:
array([[1, 2, 3],
       [4, 5, 6]])

可以看出这是沿着第一个维度切的(选取元素),arr2d[:2]的意思是选取数组的前两行。

也可以传入多个切片:

In [54]: arr2d[:2, 1:]
Out[54]:
array([[2, 3],
       [5, 6]])

这里是选取第一个维度的前2个元素和第二个维度的第2个开始以后的元素。

没看懂的话,再看下面这张二维数组切片的图:


布尔型索引

先看书中的例子,有两个数组,一个存储名字,一个存储对应的数据(随机生成正态分布的数据)。

In [55]: names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])

In [56]: data = np.random.randn(7, 4)

In [57]: names
Out[57]: array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'], dtype='<U4')

In [58]: data
Out[58]:
array([[ 1.21248078, -0.24511897, -0.8038279 , -0.35952465],
       [ 0.1328127 ,  0.42250842,  1.58394523,  0.89735295],
       [ 1.12462729,  0.86692028, -0.12374883, -1.73496697],
       [-1.14994201,  1.4784484 ,  0.24536034, -0.01059576],
       [ 2.47572411,  0.18740736,  0.72617462,  0.91062184],
       [-0.74338633, -0.73775303, -0.5307526 ,  0.34192921],
       [ 0.55185502, -2.79753615, -1.38174436,  0.24901321]])

如果我们要选出Bob的数据,我们先用names数组和字符串"Bob"作比较,产生一个布尔型数组:

In [59]: names == 'Bob'
Out[59]: array([ True, False, False,  True, False, False, False])

这个布尔型数组可以作为索引传入:

In [60]: data[names == 'Bob']
Out[60]:
array([[ 1.21248078, -0.24511897, -0.8038279 , -0.35952465],
       [-1.14994201,  1.4784484 ,  0.24536034, -0.01059576]])

这样就找到了Bob对应的数据。

注意布尔型数组的长度要和被索引的轴长度一致,不然报错。

同样还可以使用不等符号(!=),否定符号(~),与(&),或(|)等布尔类运算符

花式索引

花式索引(Fancy indexing,这竟然是个Numpy术语),指的是利用整数数组进行索引。假设有个8*4的数组:

In [61]: arr = np.empty((8, 4))

In [62]: for i in range(8):^M
    ...:     arr[i] = i
    ...:

In [63]: arr
Out[63]:
array([[0., 0., 0., 0.],
       [1., 1., 1., 1.],
       [2., 2., 2., 2.],
       [3., 3., 3., 3.],
       [4., 4., 4., 4.],
       [5., 5., 5., 5.],
       [6., 6., 6., 6.],
       [7., 7., 7., 7.]])

为了选取特定顺序的行(子集),我们可以传入一个指定顺序的整数列表或ndarray(这里我们选第4,3,0,6行):

In [64]: arr[[4, 3, 0, 6]]
Out[64]:
array([[4., 4., 4., 4.],
       [3., 3., 3., 3.],
       [0., 0., 0., 0.],
       [6., 6., 6., 6.]])

花式索引和切片不一样的地方在于,它会把数据复制到新数组中。


数组转置和轴对换

对数组进行转置有transpose方法T属性

In [65]: arr = np.arange(15).reshape((3, 5))

In [66]: arr
Out[66]:
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])

In [67]: arr.T
Out[67]:
array([[ 0,  5, 10],
       [ 1,  6, 11],
       [ 2,  7, 12],
       [ 3,  8, 13],
       [ 4,  9, 14]])

reshape方法可以重塑一个数组的维度,这里把一维的长度为15的元素改变为2维的3*5数组

高维数组,需要由轴编号组成的元组对这些轴转置:

In [68]: arr = np.arange(16).reshape((2, 2, 4))

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

       [[ 8,  9, 10, 11],
        [12, 13, 14, 15]]])

In [70]: arr.transpose((1, 0, 2))
Out[70]:
array([[[ 0,  1,  2,  3],
        [ 8,  9, 10, 11]],

       [[ 4,  5,  6,  7],
        [12, 13, 14, 15]]])

这里第一个轴变成了第二个,第二个轴变成了第一个,最后一个不变。两种方法都是在进行轴对换。

下篇见!


相关文章

使用python自动化脚本执行shell命令,提前两小时下班(5)

今天我们来学习如何通过python脚本执行linux 操作系统命令,通过前面几期的学习,我们基本已经可以控制操作系统的目录和文件,切换到特定路径下,获取系统环境变量,获取用户命令行变量,跟用户进行入参...