第四章 简介

NumPy之于数值计算特别重要的原因之一,是因为它可以高效处理大数组的数据。这是因为:

  • NumPy是在一个连续的内存块中存储数据,独立于其他Python内置对象。NumPy的C语言编写的算法库可以操作内存,而不必进行类型检查或其它前期工作。比起Python的内置序列,NumPy数组使用的内存更少。
  • NumPy可以在整个数组上执行复杂的计算,而不需要Python的for循环。
In [4]:
import numpy as np
my_arr = np.arange(1000000)
my_list =  list(range(1000000))
%time for n in range(10):my_arr2 = my_arr * 2
Wall time: 24.9 ms
In [5]:
my_arr2[:10]
Out[5]:
array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])
In [6]:
%time for m in range(10): my_list2 = [x * 2 for x in my_list]
Wall time: 1.2 s
In [8]:
my_list2[:10]
Out[8]:
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
  • 通过上述实验对比可知,numpy在大型数组计算时会快很多

4.1ndarray:多维数组对象

In [10]:
import numpy as np
# 产生2*3的数组
data = np.random.randn(2,3)
data
Out[10]:
array([[ 1.46500606,  0.38171158,  0.20194511],
       [-1.21663118, -0.65103605,  2.03941173]])
In [11]:
#数组的每个元素乘以一个实数
data * 10
Out[11]:
array([[ 14.65006062,   3.81711579,   2.01945106],
       [-12.1663118 ,  -6.51036045,  20.39411734]])
In [12]:
data + data
Out[12]:
array([[ 2.93001212,  0.76342316,  0.40389021],
       [-2.43326236, -1.30207209,  4.07882347]])
  • ndarray是一个通用的同构数据多维容器,即其所有元素必须是相同类型的。每个数组都有一个shape(一个表示各维度大小的元组)和一个dtype(一个用于说明数组数据类型的对象)
In [13]:
data.shape
Out[13]:
(2, 3)
In [14]:
data.dtype
Out[14]:
dtype('float64')
  • 创建数组:最简单的办法就是使用array函数。它接受一切序列型的对象(包括其他数组),然后产生一个新的含有传入数据的NumPy数组
In [17]:
data1 = [1,2,3,6.5,0]
arr1 = np.array(data1)
arr1
Out[17]:
array([1. , 2. , 3. , 6.5, 0. ])
  • 嵌套序列:有一组等长的列表组成的列表
In [19]:
data2 = [[1,2,3],[4,5,6]]
arr2 = np.array(data2)
arr2
Out[19]:
array([[1, 2, 3],
       [4, 5, 6]])
In [20]:
arr2.ndim    #维度
Out[20]:
2
In [21]:
arr2.shape
Out[21]:
(2, 3)
In [22]:
arr2.dtype
Out[22]:
dtype('int32')
  • zeros和ones分别可以创建指定长度或形状的全0或全1数组。empty可以创建一个没有任何具体值的数组。要用这些方法创建多维数组,只需传入一个表示形状的元组即可
In [24]:
np.zeros((2,3))
Out[24]:
array([[0., 0., 0.],
       [0., 0., 0.]])
In [26]:
arr3 = np.ones((2,2,2))
arr3
Out[26]:
array([[[1., 1.],
        [1., 1.]],

       [[1., 1.],
        [1., 1.]]])
In [27]:
arr3.ndim
Out[27]:
3
In [28]:
arr3.shape
Out[28]:
(2, 2, 2)
  • arange()是Python内置函数range()函数的数组版
In [29]:
np.arange(10)
Out[29]:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

数组创建函数

  • 也可以在创建数组时指定数据类型
In [33]:
arr4 = np.array([1,2,3],dtype = np.float)
arr4
Out[33]:
array([1., 2., 3.])
In [34]:
arr4.dtype
Out[34]:
dtype('float64')
In [36]:
arr5 = np.array([1,2,3],dtype=np.int32)
arr5
Out[36]:
array([1, 2, 3])
In [37]:
arr5.dtype
Out[37]:
dtype('int32')
  • 可以使用astype(type)将一个类型的数组转换为指定类型
In [38]:
float_arr5 = arr5.astype(np.float64)
In [39]:
float_arr5.dtype
Out[39]:
dtype('float64')
  • 上例中将整数转换为浮点数;如果将浮点数转换为整数,则小数部分将被截取删除
In [42]:
f_arr = np.array([1.22, 33.211, 20.01])
f_arr
Out[42]:
array([ 1.22 , 33.211, 20.01 ])
In [44]:
f_arr.dtype
Out[44]:
dtype('float64')
In [45]:
i_arr = f_arr.astype(np.int32)
In [46]:
i_arr
Out[46]:
array([ 1, 33, 20])
In [47]:
i_arr.dtype
Out[47]:
dtype('int32')
  • 如果某个数组元素全是数字字符串,也可以使用astype进行转换
In [50]:
s_arr = np.array(['1','2.22','3.01'])
s_arr.dtype
Out[50]:
dtype('<U4')
In [51]:
s_arr.astype(np.float64)
Out[51]:
array([1.  , 2.22, 3.01])

numpy 数组的运算

In [52]:
arr = np.array([[1,2,3.0],[4.,5.,6.]])
arr
Out[52]:
array([[1., 2., 3.],
       [4., 5., 6.]])
In [58]:
arr **2
Out[58]:
array([[ 1.,  4.,  9.],
       [16., 25., 36.]])
In [59]:
arr * arr
Out[59]:
array([[ 1.,  4.,  9.],
       [16., 25., 36.]])
In [63]:
#数组与标量的算术运算会将标量值传播到各个元素
#不同大小的数组之间的运算叫做广播(broadcasting)
arr / 2
Out[63]:
array([[0.5, 1. , 1.5],
       [2. , 2.5, 3. ]])
In [56]:
arr // 2
Out[56]:
array([[0., 1., 1.],
       [2., 2., 3.]])
In [60]:
arr - 1
Out[60]:
array([[0., 1., 2.],
       [3., 4., 5.]])
In [62]:
# shape相同的数组比较会产生一个布尔数组
arr2 = np.array([[4,1,1],[7,5,5]])
arr > arr2
Out[62]:
array([[False,  True,  True],
       [False, False,  True]])

基本的索引和切片

In [66]:
arr = np.arange(10)
arr
Out[66]:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [67]:
arr[5]
Out[67]:
5
In [68]:
arr[:6]
Out[68]:
array([0, 1, 2, 3, 4, 5])
In [70]:
arr[2:5] = 10
arr
Out[70]:
array([ 0,  1, 10, 10, 10,  5,  6,  7,  8,  9])
  • 数组切片跟列表最重要的区别在于,数组切片是原始数组的视图。这意味着数据不会被复制,视图上的任何修改都会直接反映到源数组上。
In [76]:
arr_slice = arr[2:5]
arr_slice
Out[76]:
array([10, 10, 10])
In [78]:
#当修改arr_slice时,arr数组也会被修改
arr_slice[1] = 100
arr_slice
Out[78]:
array([ 10, 100,  10])
In [79]:
arr
Out[79]:
array([  0,   1,  10, 100,  10,   5,   6,   7,   8,   9])
  • 如果想要得到数组的一个副本,而不是原数组的一个视图,可以使用copy()方法
In [109]:
arr_copy = arr[:3].copy()
arr_copy
Out[109]:
array([ 0,  1, 10])
In [110]:
arr_copy[0] = 100
arr_copy
Out[110]:
array([100,   1,  10])
In [111]:
arr
Out[111]:
array([  0,   1,  10, 100,  10,   5,   6,   7,   8,   9])
In [112]:
arr2d = np.array([[1,2,3],[4,5,6],[7,8,9]])
arr2d[1]
Out[112]:
array([4, 5, 6])
In [113]:
arr2d[1,1]
Out[113]:
5
In [114]:
arr2d[1][1]
Out[114]:
5
In [115]:
arr2d.ndim
Out[115]:
2
In [116]:
# 三维数组
arr3d = np.array([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]])
arr3d
Out[116]:
array([[[ 1,  2,  3],
        [ 4,  5,  6]],

       [[ 7,  8,  9],
        [10, 11, 12]]])
In [117]:
arr3d.shape
Out[117]:
(2, 2, 3)
In [118]:
arr3d[0]
Out[118]:
array([[1, 2, 3],
       [4, 5, 6]])
In [119]:
arr3d[0,1]
Out[119]:
array([4, 5, 6])
In [120]:
arr3d[0,1,2]
Out[120]:
6
In [125]:
# 标量值可以直接赋值给数组得某个元素,具有广播
arr3d[0] = 0
arr3d
Out[125]:
array([[[ 0,  0,  0],
        [ 0,  0,  0]],

       [[ 7,  8,  9],
        [10, 11, 12]]])

切片索引

In [126]:
arr2d = np.array([[1,2,3],[4,5,6],[7,8,9]])
arr2d
Out[126]:
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])
In [127]:
arr2d[:2]
Out[127]:
array([[1, 2, 3],
       [4, 5, 6]])
In [128]:
arr2d[:2,:2]
Out[128]:
array([[1, 2],
       [4, 5]])
In [129]:
arr2d[1,:2]
Out[129]:
array([4, 5])
  • 对切片表达式的赋值也会扩散到整个选区
In [131]:
arr2d[:2,:2] = 1
arr2d
Out[131]:
array([[1, 1, 3],
       [1, 1, 6],
       [7, 8, 9]])

布尔型索引

In [134]:
names = np.array(['Bob','Joe','Will','Bob','will','Joe','Joe'])
data = np.random.randn(7,4)
names
Out[134]:
array(['Bob', 'Joe', 'Will', 'Bob', 'will', 'Joe', 'Joe'], dtype='<U4')
In [135]:
data
Out[135]:
array([[-0.22069039, -0.3274228 , -1.16742577, -0.73831674],
       [-0.17419963, -1.86709013,  1.07920976, -1.10985636],
       [ 1.55001149, -2.08476377, -1.14224182, -1.15043735],
       [ 0.98140083,  0.96417792,  3.68246684, -1.21193783],
       [-0.49201841,  0.93379201, -2.06124303,  1.73694261],
       [ 0.76176287,  0.59960082, -0.72839576, -1.22476123],
       [ 0.03756529,  2.76069372,  1.33604503, -1.47566927]])
In [136]:
names == 'Bob'
Out[136]:
array([ True, False, False,  True, False, False, False])
In [137]:
data[names == 'Bob']
Out[137]:
array([[-0.22069039, -0.3274228 , -1.16742577, -0.73831674],
       [ 0.98140083,  0.96417792,  3.68246684, -1.21193783]])
In [138]:
data[names == 'Bob',2:]
Out[138]:
array([[-1.16742577, -0.73831674],
       [ 3.68246684, -1.21193783]])
In [140]:
data[names == 'Bob',2] #索引列
Out[140]:
array([-1.16742577,  3.68246684])
  • 如果要获取除了‘Bob’以外的值,既可以使用 != ,也可以使用~取反
In [141]:
names != 'Bob'
Out[141]:
array([False,  True,  True, False,  True,  True,  True])
In [142]:
data[~(names == 'Bob')]
Out[142]:
array([[-0.17419963, -1.86709013,  1.07920976, -1.10985636],
       [ 1.55001149, -2.08476377, -1.14224182, -1.15043735],
       [-0.49201841,  0.93379201, -2.06124303,  1.73694261],
       [ 0.76176287,  0.59960082, -0.72839576, -1.22476123],
       [ 0.03756529,  2.76069372,  1.33604503, -1.47566927]])
  • 也可以使用& 、 | 、等不二算术运算
In [143]:
mask = (names == 'Bob') |(names == 'Will')
mask
Out[143]:
array([ True, False,  True,  True, False, False, False])
In [144]:
data[mask]
Out[144]:
array([[-0.22069039, -0.3274228 , -1.16742577, -0.73831674],
       [ 1.55001149, -2.08476377, -1.14224182, -1.15043735],
       [ 0.98140083,  0.96417792,  3.68246684, -1.21193783]])

通过布尔型索引选取数组元素,都将创建数组的副本。而数字切片索引将获得数组的视图

In [145]:
# 将data中小于零的元素设置为0
data[data < 0] = 0
data
Out[145]:
array([[0.        , 0.        , 0.        , 0.        ],
       [0.        , 0.        , 1.07920976, 0.        ],
       [1.55001149, 0.        , 0.        , 0.        ],
       [0.98140083, 0.96417792, 3.68246684, 0.        ],
       [0.        , 0.93379201, 0.        , 1.73694261],
       [0.76176287, 0.59960082, 0.        , 0.        ],
       [0.03756529, 2.76069372, 1.33604503, 0.        ]])
In [147]:
# 通过一维布尔数组设置整行或列的值
data[names != 'Joe'] = 7
data
Out[147]:
array([[7.        , 7.        , 7.        , 7.        ],
       [0.        , 0.        , 1.07920976, 0.        ],
       [7.        , 7.        , 7.        , 7.        ],
       [7.        , 7.        , 7.        , 7.        ],
       [7.        , 7.        , 7.        , 7.        ],
       [0.76176287, 0.59960082, 0.        , 0.        ],
       [0.03756529, 2.76069372, 1.33604503, 0.        ]])

花式索引

  • 花式索引(Fancy indexing)是一个NumPy术语,它指的是利用整数数组进行索引。
In [151]:
arr = np.empty((8,4))
for i in range(8):
    arr[i] = i
arr
Out[151]:
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.]])
In [152]:
# 为了以特定的顺序选取行子集,只需要传入一个用于指定顺序的整数列表或数组即可
arr[[4,3,0,6]]
Out[152]:
array([[4., 4., 4., 4.],
       [3., 3., 3., 3.],
       [0., 0., 0., 0.],
       [6., 6., 6., 6.]])
In [153]:
# 也可以使用负数索引,会从末尾开始选取行
arr[[-1,-3,-5]]
Out[153]:
array([[7., 7., 7., 7.],
       [5., 5., 5., 5.],
       [3., 3., 3., 3.]])
  • 一次传入多个索引数组会有一点特别。它返回的是一个一维数组,其中的元素对应各个索引元组:
In [154]:
arr = np.arange(32).reshape((8,4))
arr
Out[154]:
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, 30, 31]])
In [156]:
#最终选出的是元素(1,0)、(5,3)、(7,1)和(2,2)。无论数组是多少维的,花式索引总是一维的。
arr[[1,5,7,2],[0,3,1,2]]
Out[156]:
array([ 4, 23, 29, 10])
In [160]:
arr[[1,5,7,2]]
Out[160]:
array([[ 4,  5,  6,  7],
       [20, 21, 22, 23],
       [28, 29, 30, 31],
       [ 8,  9, 10, 11]])
In [161]:
arr[[1,5,7,2]][:,[0,3,1,2]]
Out[161]:
array([[ 4,  7,  5,  6],
       [20, 23, 21, 22],
       [28, 31, 29, 30],
       [ 8, 11,  9, 10]])
  • 花式索引跟切片不一样,它总是将数据复制到新数组中。