Numpy 数组及其索引

先导入numpy:

In [1]:

  1. from numpy import *

产生数组

从列表产生数组:

In [2]:

  1. lst = [0, 1, 2, 3]
  2. a = array(lst)
  3. a

Out[2]:

  1. array([0, 1, 2, 3])

或者直接将列表传入:

In [3]:

  1. a = array([1, 2, 3, 4])
  2. a

Out[3]:

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

数组属性

查看类型:

In [4]:

  1. type(a)

Out[4]:

  1. numpy.ndarray

查看数组中的数据类型:

In [5]:

  1. # 32比特的整数
  2. a.dtype

Out[5]:

  1. dtype('int32')

查看每个元素所占的字节:

In [6]:

  1. a.itemsize

Out[6]:

  1. 4

查看形状,会返回一个元组,每个元素代表这一维的元素数目:

In [7]:

  1. # 1维数组,返回一个元组
  2. a.shape

Out[7]:

  1. (4L,)

或者使用:

In [8]:

  1. shape(a)

Out[8]:

  1. (4L,)

shape 的使用历史要比 a.shape 久,而且还可以作用于别的类型:

In [9]:

  1. lst = [1,2,3,4]
  2. shape(lst)

Out[9]:

  1. (4L,)

查看元素数目:

In [10]:

  1. a.size

Out[10]:

  1. 4

In [11]:

  1. size(a)

Out[11]:

  1. 4

查看所有元素所占的空间:

In [12]:

  1. a.nbytes

Out[12]:

  1. 16

但事实上,数组所占的存储空间要比这个数字大,因为要用一个header来保存shape,dtype这样的信息。

查看数组维数:

In [13]:

  1. a.ndim

Out[13]:

  1. 1

使用fill方法设定初始值

可以使用 fill 方法将数组设为指定值:

In [14]:

  1. a.fill(-4.8)
  2. a

Out[14]:

  1. array([-4, -4, -4, -4])

但是与列表不同,数组中要求所有元素的 dtype 是一样的,如果传入参数的类型与数组类型不一样,需要按照已有的类型进行转换。

索引与切片

和列表相似,数组也支持索引和切片操作。

索引第一个元素:

In [15]:

  1. a = array([0, 1, 2, 3])
  2. a[0]

Out[15]:

  1. 0

修改第一个元素的值:

In [16]:

  1. a[0] = 10
  2. a

Out[16]:

  1. array([10, 1, 2, 3])

切片,支持负索引:

In [17]:

  1. a = array([11,12,13,14,15])
  2. a[1:3]

Out[17]:

  1. array([12, 13])

In [18]:

  1. a[1:-2]

Out[18]:

  1. array([12, 13])

In [19]:

  1. a[-4:3]

Out[19]:

  1. array([12, 13])

省略参数:

In [20]:

  1. a[::2]

Out[20]:

  1. array([11, 13, 15])

In [21]:

  1. a[-2:]

Out[21]:

  1. array([14, 15])

假设我们记录一辆汽车表盘上每天显示的里程数:

In [22]:

  1. od = array([21000, 21180, 21240, 22100, 22400])

可以这样计算每天的旅程:

In [23]:

  1. dist = od[1:] - od[:-1]
  2. dist

Out[23]:

  1. array([180, 60, 860, 300])

在本质上,Python会将array的各种计算转换为类似这样的C代码:

  1. int compute_sum(int *arr, int N) {
  2. int sum = 0;
  3. int i;
  4. for (i = 0; i < N; i++) {
  5. sum += arr[i];
  6. }
  7. return sum;
  8. }

多维数组及其属性

array 还可以用来生成多维数组:

In [24]:

  1. a = array([[ 0, 1, 2, 3],
  2. [10,11,12,13]])
  3. a

Out[24]:

  1. array([[ 0, 1, 2, 3],
  2. [10, 11, 12, 13]])

事实上我们传入的是一个以列表为元素的列表,最终得到一个二维数组。

甚至可以扩展到3D或者4D的情景。

查看形状:

In [25]:

  1. a.shape

Out[25]:

  1. (2L, 4L)

这里2代表行数,4代表列数。

查看总的元素个数:

In [26]:

  1. # 2 * 4 = 8
  2. a.size

Out[26]:

  1. 8

查看维数:

In [27]:

  1. a.ndim

Out[27]:

  1. 2

多维数组索引

对于二维数组,可以传入两个数字来索引:

In [28]:

  1. a[1, 3]

Out[28]:

  1. 13

其中,1是行索引,3是列索引,中间用逗号隔开,事实上,Python会将它们看成一个元组(1,3),然后按照顺序进行对应。

可以利用索引给它赋值:

In [29]:

  1. a[1, 3] = -1
  2. a

Out[29]:

  1. array([[ 0, 1, 2, 3],
  2. [10, 11, 12, -1]])

事实上,我们还可以使用单个索引来索引一整行内容:

In [30]:

  1. # 返回第二行元组组成的array
  2. a[1]

Out[30]:

  1. array([10, 11, 12, -1])

Python会将这单个元组当成对第一维的索引,然后返回对应的内容。

多维数组切片

多维数组,也支持切片操作:

In [31]:

  1. a = array([[ 0, 1, 2, 3, 4, 5],
  2. [10,11,12,13,14,15],
  3. [20,21,22,23,24,25],
  4. [30,31,32,33,34,35],
  5. [40,41,42,43,44,45],
  6. [50,51,52,53,54,55]])
  7. a

Out[31]:

  1. array([[ 0, 1, 2, 3, 4, 5],
  2. [10, 11, 12, 13, 14, 15],
  3. [20, 21, 22, 23, 24, 25],
  4. [30, 31, 32, 33, 34, 35],
  5. [40, 41, 42, 43, 44, 45],
  6. [50, 51, 52, 53, 54, 55]])

想得到第一行的第 4 和第 5 两个元素:

In [32]:

  1. a[0, 3:5]

Out[32]:

  1. array([3, 4])

得到最后两行的最后两列:

In [33]:

  1. a[4:, 4:]

Out[33]:

  1. array([[44, 45],
  2. [54, 55]])

得到第三列:

In [34]:

  1. a[:, 2]

Out[34]:

  1. array([ 2, 12, 22, 32, 42, 52])

每一维都支持切片的规则,包括负索引,省略:

  1. [lower:upper:step]

例如,取出3,5行的奇数列:

In [35]:

  1. a[2::2, ::2]

Out[35]:

  1. array([[20, 22, 24],
  2. [40, 42, 44]])

切片是引用

切片在内存中使用的是引用机制。

In [36]:

  1. a = array([0,1,2,3,4])
  2. b = a[2:4]
  3. print b
  1. [2 3]

引用机制意味着,Python并没有为 b 分配新的空间来存储它的值,而是让 b 指向了 a 所分配的内存空间,因此,改变 b 会改变 a 的值:

In [37]:

  1. b[0] = 10
  2. a

Out[37]:

  1. array([ 0, 1, 10, 3, 4])

而这种现象在列表中并不会出现:

In [38]:

  1. a = [1,2,3,4,5]
  2. b = a[2:3]
  3. b[0] = 13234
  4. print a
  1. [1, 2, 3, 4, 5]

这样做的好处在于,对于很大的数组,不用大量复制多余的值,节约了空间。

缺点在于,可能出现改变一个值改变另一个值的情况。

一个解决方法是使用copy()方法产生一个复制,这个复制会申请新的内存:

In [39]:

  1. a = array([0,1,2,3,4])
  2. b = a[2:4].copy()
  3. b[0] = 10
  4. a

Out[39]:

  1. array([0, 1, 2, 3, 4])

花式索引

切片只能支持连续或者等间隔的切片操作,要想实现任意位置的操作,需要使用花式索引 fancy slicing

一维花式索引

与 range 函数类似,我们可以使用 arange 函数来产生等差数组。

In [40]:

  1. a = arange(0, 80, 10)
  2. a

Out[40]:

  1. array([ 0, 10, 20, 30, 40, 50, 60, 70])

花式索引需要指定索引位置:

In [41]:

  1. indices = [1, 2, -3]
  2. y = a[indices]
  3. print y
  1. [10 20 50]

还可以使用布尔数组来花式索引:

In [42]:

  1. mask = array([0,1,1,0,0,1,0,0],
  2. dtype=bool)

In [43]:

  1. a[mask]

Out[43]:

  1. array([10, 20, 50])

或者用布尔表达式生成 mask,选出了所有大于0.5的值:

In [44]:

  1. from numpy.random import rand
  2. a = rand(10)
  3. a

Out[44]:

  1. array([ 0.37214708, 0.48594733, 0.73365131, 0.15769295, 0.30786017,
  2. 0.62068734, 0.36940654, 0.09424167, 0.53085308, 0.12248951])

In [45]:

  1. mask = a > 0.5
  2. a[mask]

Out[45]:

  1. array([ 0.73365131, 0.62068734, 0.53085308])

mask 必须是布尔数组。

二维花式索引

In [46]:

  1. a = array([[ 0, 1, 2, 3, 4, 5],
  2. [10,11,12,13,14,15],
  3. [20,21,22,23,24,25],
  4. [30,31,32,33,34,35],
  5. [40,41,42,43,44,45],
  6. [50,51,52,53,54,55]])
  7. a

Out[46]:

  1. array([[ 0, 1, 2, 3, 4, 5],
  2. [10, 11, 12, 13, 14, 15],
  3. [20, 21, 22, 23, 24, 25],
  4. [30, 31, 32, 33, 34, 35],
  5. [40, 41, 42, 43, 44, 45],
  6. [50, 51, 52, 53, 54, 55]])

对于二维花式索引,我们需要给定 rowcol 的值:

In [47]:

  1. a[(0,1,2,3,4), (1,2,3,4,5)]

Out[47]:

  1. array([ 1, 12, 23, 34, 45])

返回的是一条次对角线上的5个值。

In [48]:

  1. a[3:, [0,2,5]]

Out[48]:

  1. array([[30, 32, 35],
  2. [40, 42, 45],
  3. [50, 52, 55]])

返回的是最后三行的第1,3,5列。

也可以使用mask进行索引:

In [49]:

  1. mask = array([1,0,1,0,0,1],
  2. dtype=bool)
  3. a[mask, 2]

Out[49]:

  1. array([ 2, 22, 52])

与切片不同,花式索引返回的是原对象的一个复制而不是引用。

“不完全”索引

只给定行索引的时候,返回整行:

In [50]:

  1. y = a[:3]
  2. y

Out[50]:

  1. array([[ 0, 1, 2, 3, 4, 5],
  2. [10, 11, 12, 13, 14, 15],
  3. [20, 21, 22, 23, 24, 25]])

这时候也可以使用花式索引取出第2,3,5行:

In [51]:

  1. condition = array([0,1,1,0,1],
  2. dtype=bool)
  3. a[condition]

Out[51]:

  1. array([[10, 11, 12, 13, 14, 15],
  2. [20, 21, 22, 23, 24, 25],
  3. [40, 41, 42, 43, 44, 45]])

三维花式索引

In [52]:

  1. a = arange(64)
  2. a.shape = 4,4,4
  3. a

Out[52]:

  1. array([[[ 0, 1, 2, 3],
  2. [ 4, 5, 6, 7],
  3. [ 8, 9, 10, 11],
  4. [12, 13, 14, 15]],
  5.  
  6. [[16, 17, 18, 19],
  7. [20, 21, 22, 23],
  8. [24, 25, 26, 27],
  9. [28, 29, 30, 31]],
  10.  
  11. [[32, 33, 34, 35],
  12. [36, 37, 38, 39],
  13. [40, 41, 42, 43],
  14. [44, 45, 46, 47]],
  15.  
  16. [[48, 49, 50, 51],
  17. [52, 53, 54, 55],
  18. [56, 57, 58, 59],
  19. [60, 61, 62, 63]]])

In [53]:

  1. y = a[:,:,[2, -1]]
  2. y

Out[53]:

  1. array([[[ 2, 3],
  2. [ 6, 7],
  3. [10, 11],
  4. [14, 15]],
  5.  
  6. [[18, 19],
  7. [22, 23],
  8. [26, 27],
  9. [30, 31]],
  10.  
  11. [[34, 35],
  12. [38, 39],
  13. [42, 43],
  14. [46, 47]],
  15.  
  16. [[50, 51],
  17. [54, 55],
  18. [58, 59],
  19. [62, 63]]])

where语句

  1. where(array)

where 函数会返回所有非零元素的索引。

一维数组

先看一维的例子:

In [54]:

  1. a = array([0, 12, 5, 20])

判断数组中的元素是不是大于10:

In [55]:

  1. a > 10

Out[55]:

  1. array([False, True, False, True], dtype=bool)

数组中所有大于10的元素的索引位置:

In [56]:

  1. where(a > 10)

Out[56]:

  1. (array([1, 3], dtype=int64),)

注意到 where 的返回值是一个元组。

使用元组是由于 where 可以对多维数组使用,此时返回值就是多维的。

在使用的时候,我们可以这样:

In [57]:

  1. indices = where(a > 10)
  2. indices = indices[0]
  3. indices

Out[57]:

  1. array([1, 3], dtype=int64)

或者:

In [58]:

  1. indices = where(a>10)[0]
  2. indices

Out[58]:

  1. array([1, 3], dtype=int64)

可以直接用 where 的返回值进行索引:

In [59]:

  1. loc = where(a > 10)
  2. a[loc]

Out[59]:

  1. array([12, 20])

多维数组

考虑二维数组:

In [60]:

  1. a = array([[0, 12, 5, 20],
  2. [1, 2, 11, 15]])
  3. loc = where(a > 10)

返回结果是一个二维的元组,每一维代表这一维的索引值:

In [61]:

  1. loc

Out[61]:

  1. (array([0, 0, 1, 1], dtype=int64), array([1, 3, 2, 3], dtype=int64))

也可以直接用来索引a:

In [62]:

  1. a[loc]

Out[62]:

  1. array([12, 20, 11, 15])

或者可以这样:

In [63]:

  1. rows, cols = where(a>10)

In [64]:

  1. rows

Out[64]:

  1. array([0, 0, 1, 1], dtype=int64)

In [65]:

  1. cols

Out[65]:

  1. array([1, 3, 2, 3], dtype=int64)

In [66]:

  1. a[rows, cols]

Out[66]:

  1. array([12, 20, 11, 15])

再看另一个例子:

In [67]:

  1. a = arange(25)
  2. a.shape = 5,5
  3. a

Out[67]:

  1. array([[ 0, 1, 2, 3, 4],
  2. [ 5, 6, 7, 8, 9],
  3. [10, 11, 12, 13, 14],
  4. [15, 16, 17, 18, 19],
  5. [20, 21, 22, 23, 24]])

In [68]:

  1. a > 12

Out[68]:

  1. array([[False, False, False, False, False],
  2. [False, False, False, False, False],
  3. [False, False, False, True, True],
  4. [ True, True, True, True, True],
  5. [ True, True, True, True, True]], dtype=bool)

In [69]:

  1. where(a > 12)

Out[69]:

  1. (array([2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4], dtype=int64),
  2. array([3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4], dtype=int64))

原文: https://nbviewer.jupyter.org/github/lijin-THU/notes-python/blob/master/03-numpy/03.03-numpy-arrays.ipynb