以下内容主要学习自《利用Python进行数据分析》
第4章 NumPy基础(2)
NumPy是Numerical Python的简称,它是目前Python数值计算中最为重要的基础包。大多数计算包都提供了基于Numpy的科学函数功能,将NumPy的数组对象作为数据交换的通用语言。
本章将介绍NumPy数组的基础操作。虽然深入理解NumPy对于大部分数据分析应用并不是必需的,但是精通基于数组的编程和思维是成为Python科学计算专家的第一步。
ndarray索引与切片
Numpy.ndarray的索引与切片是一个值得学习的话题。有很多方式可以让你选中数据的子集或某个单一元素。
所谓“索引”,就是通过单一的索引号,得到ndarray的部分数据;所谓“切片”,就是通过索引号段,得到ndarray的部分数据。索引可以取得原数组降维的部分数据,而切片不会降低维度,而是得到一个维度相同的子集。
如果熟悉Python的列表操作,那么很容易掌握ndarray的索引与切片,因为语法是相似的。
索引
通过索引获得单一的元素
NumPy的索引语法与Python列表的索引语法是一样的。如下是对一维数组进行索引:
In[1]: arr = np.arange(10)
In[2]: arr
Out[2]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In[3]: arr[5] #一维数组的索引
Out[3]: 5
如果要从二维数组(或多维数组)中取得单一元素,那么就要提供多个索引值。如下是对二维数组进行索引:
In [4]: arr = arr.reshape(2, 5)
In [5]: arr
Out[5]:
array([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]])
In [6]: arr[1][3] # 二维数组的索引
Out[6]: 8
In [7]: arr[1, 3] # 尽管索引语法不同,但索引效果一样
Out[7]: 8
通过索引获得ndarray的子集
可以从二维(或多维)数组中,获得ndarray的自己,方法是在多维数组中,如果省略后续索引值,返回的对象将是降低一个维度的数组。
# 生成一个维度为(2,3,4)的三维数组
In [1]: arr = np.arange(24).reshape(2, 3, 4)
In [2]: arr
Out[2]:
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]]])
In [3]: arr[0] # 返回第1维、索引号为0的子集,返回的将是二维数组
Out[3]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
In [4]: arr[0][1] # 返回一维数组
Out[4]: array([4, 5, 6, 7])
切片
一维数组的切片
与Python列表类似,NumPy数组可以通过相似的语法对数组进行切片。
In[1]: arr = np.arange(10)
In[2]: arr
Out[2]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In[3]: sub = arr[5:8] # 通过切片获取部分数据
In[4]: sub
Out[4]: array([5, 6, 7])
In [5]: sub.shape # sub是一个有三个元素的一维数组
Out[5]: (3,)
In[6]: sub[0:2] = 12 # 对切片赋值,就是对切片中的每一个元素赋值
In[7]: arr
Out[7]: array([ 0, 1, 2, 3, 4, 12, 12, 7, 8, 9])
注意,区别于Python的内建列表,ndarray的切片是原数组的视图,这意味着数据并不是被复制了,任何对于视图的修改都会反映到原数组上。这是因为NumPy被设计成适合处理非常大的数组,你可以想象如果NumPy持续复制数据会引起内存和性能问题。
如果你是NumPy新手,你可能对此感到惊讶,因为其他的编程语言都是急切地复制数据。如果你坚持要得到一份数组切片的拷贝,而不是一份视图的话,可以试用ndarray.copy()方法
显式地复制数据。
In [8]: sub = arr[0:3].copy() # 显式地复制数据
In [9]: sub
Out[9]: array([0, 1, 2])
In [10]: sub[0:3] = 99
In [11]: sub
Out[11]: array([99, 99, 99])
# 通过复制的子集,不会影响原数组
In [12]: arr
Out[12]: array([ 0, 1, 2, 3, 4, 13, 13, 12, 8, 9])
多维数组的切片
In [1]: arr = np.arange(12).reshape(3,4)
In [2]: arr # 一个3X4的二维数组
Out[2]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
# 对二维数组进行切片,得到一个子集
# 1:3表示从第2行开始,取3-1=2行
# 2:4表示从第3列开始,取4-2=2列
In [3]: arr[1:3, 2:4]
Out[3]:
array([[ 6, 7],
[10, 11]])
注意:索引可以取得原数组降维的部分数据,而切片不会降低维度,而是得到一个维度相同的子集。妥善地混合索引和切片,就可以得到低维度的切片。
In [4]: arr[1, 1:3] # 获取第2行,2、3列数据
Out[4]: array([5, 6])
单独一个冒号表示选择整个轴上的数组,并且对原数组的切片赋值,那么原数组也会被赋值。这也是因为NumPy被设计成适合处理非常大的数组,因此缺省是按地址引用。
In [1]: arr = np.arange(12).reshape(3,4)
In [2: arr
Out[2]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
# 获取所有行、第1列的切片
In [3]: sub = arr[:, :1]
In [4]: sub
Out[4]:
array([[0],
[4],
[8]])
# 把切片中的所有元素赋值为0
In [5]: sub[:,:] = 0
# 对切片的赋值,会影响到原数组
In [6]: arr
Out[6]:
array([[ 0, 1, 2, 3],
[ 0, 5, 6, 7],
[ 0, 9, 10, 11]])
神奇索引
神奇索引这个词很容易让人误解,它是NumPy的一个术语。简单来讲,神奇索引的目的是用整数数组对ndarray数据进行索引。
假设我们有一个8X4的数组:
In [1]: arr = np.empty((8,4))
In [2]: for i in range(8):
...: arr[i] = i+1
In [3]: arr
Out[3]:
array([[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.],
[8., 8., 8., 8.]])
如果我们要从如上的数组中,选择第5、4、1、7行组成的子集,那么可以传递[4,3,0,6]来得到(注意数组下标从0开始)
In [4]: arr[[4, 3, 0, 6]]
Out[4]:
array([[5., 5., 5., 5.],
[4., 4., 4., 4.],
[1., 1., 1., 1.],
[7., 7., 7., 7.]])
如果使用负数索引,将从尾部进行选择:
In [5]: arr[[-3, -5, -7]]
Out[5]:
array([[6., 6., 6., 6.],
[4., 4., 4., 4.],
[2., 2., 2., 2.]])
注意上面的例子,原数组是二维的,但传入了一个索引维度,所以是得到了降维后的子集。如果传入两个索引,将得到一维数组。
In [1]: arr = np.arange(12).reshape(3,4)
In [2]: arr
Out[2]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
# 选去第1行第2个元素、以及第3行第4个元素。
In [3]: arr[[0,2], [1, 3]]
Out[3]: array([ 1, 11])
请牢记:神奇索引与切片不同,它总是将数据复制到一个新的数组中。
布尔索引
假设我们有7X3的数据,这7行数据数据属于3个人,如下示例:
In [1]: names = np.array(['张三', '王五', '李四', '张三', '王五', '王五', '李四'])
# 生成一个7X3的数组,数组中的每个元素是0~10的随机数
In [2]: data = np.random.randint(low=0, high=10, size=(7,3))
In [3]: data
Out[3]:
array([[4, 0, 2],
[8, 1, 5],
[3, 3, 1],
[4, 9, 3],
[3, 7, 3],
[7, 8, 4],
[5, 4, 7]])
用names数组与字符串'张三'比较会产生一个布尔值数组:
In [4]: names == '张三'
Out[4]: array([ True, False, False, True, False, False, False])
如果把这个布尔值数组当作“神奇索引”,传递给data数组,会得到如下结果
In [5]: data[names == '张三']
Out[5]:
array([[4, 0, 2],
[4, 9, 3]])
如上,其效果等同于选择出了所有“张三”的行数据。
ndarray排序
一维数组的排序
数组实例可以使用sort方法
进行排序。
In [1]: arr = np.random.randn(6)
In [2]: arr
Out[2]:
array([-1.51683294, 0.71207739, -1.91733282, -1.07901923, -0.35628366,
-0.52888226])
In [3]: arr.sort()
In [4]: arr
Out[4]:
array([-1.91733282, -1.51683294, -1.07901923, -0.52888226, -0.35628366,
0.71207739])
多维数组的排序
对于多维数组的排序,可以使用NumPy的顶层sort函数
,该函数可以接送axis参数,以便按照某一个维度排序。
In [1]: arr = np.random.randint(-5, 5, (3,4))
In [2]: arr
Out[2]:
array([[ 2, 4, -4, -3],
[-1, -5, 2, 0],
[-3, -4, -3, -3]])
In [3]: np.sort(arr, axis=0)
Out[3]:
array([[-3, -5, -4, -3],
[-1, -4, -3, -3],
[ 2, 4, 2, 0]])
In [4]: np.sort(arr, axis=1)
Out[4]:
array([[-4, -3, 2, 4],
[-5, -1, 0, 2],
[-4, -3, -3, -3]])