NumPy - 入门

NumPy , 是Numerical Python 的简称,它是目前Python 数值计算中最为重要的基础包。大多数计算包都提供了基于NumPy 的科学函数功能,将NumPy的数组对象作为数据交换的通用语

以下内容会出现在NumPy中:

  • ndarray,一种高效多维数组,提供了基于数组的便捷算术操作以及灵活的广播功能。
  • 对所有数据进行快速的矩阵计算,而无须编写循环程序。
  • 对硬盘中数组数据进行读写的工具,并对内存映射文件进行操作
  • 线性代数、随机数生成以及傅里叶变换功能
  • 用于连接NumPy到C、C++ 和 FORTRAN 语言类库的C语言 API

由于NumPy 提供了一个非常易用的C语言 API,这使得,将数据传递给用底层语言编写的外部类库,再由外部类库将计算结果按照NumPy 数组的方式返回,变得非常简单。这个特征使得Python可以对存量C/C++/Fortran代码库进行封装,并为这些代码提供动态、易用的接口.NumPy本身并不提供建模和科学函数,理解Numpy 的数组以及基于数组的计算将帮助你更高效地使用基于数组的工具,比如pandas

虽然NumPy提供了数值数据操作的计算基础,但大多数渎者还是想把pandas作为统计、分析的基石,尤其是针对表格数据。pandas提供了更多针对特定场景的函数功能,例如时间序列操作等NumPy并不包含的功能

NumPy之所以如此重要,其中一个原因就是它的设计对含有大量数组的数据非常有效,此外还有如下原因:

  • NumPy在内部将数据存储在连续的内存块上,这与其他Python内建数据结构是不同的。NumPy算法库使用C语言写的,所以在操作数据内存时,不需要任何类型检查或其他管理操作。NumPy数组使用的内存量也小于其他Python内建序列
  • NumPy 可以针对全量数组进行复杂计算而不需要写Python循环

下面例子将展现NumPy的不同,假设分别有一个包含100万个整数的NumPy数组和list:

In [89]: my_arr = np.arange(1000000)

In [90]: my_list = list(range(1000000))

In [91]: %time for _ in range(10): my_arr2 = my_arr * 2
CPU times: total: 0 ns
Wall time: 10 ms

In [93]: %time  for _ in range(10): my_list2 = [x * 2 for x in my_list]
CPU times: total: 422 ms
Wall time: 426 ms

NumPy 的方法比Python 方法要快10到100倍,并且使用的内存也更少

ndarray 多维数组对象

NumPy 的核心特征之就是 N-维 数组对象——ndarray。ndarray 是Python 中一个快速灵活的大型数据集容器。数组允许你使用类似标量的操作语法在整块数据上进行数学计算

# 标量计算
In [1]: import numpy as np

In [5]: data  = np.random.randn(2,3)

In [6]: data
Out[6]:
array([[ 1.51218044, -2.26613906,  1.08783148],
       [ 1.08847228,  0.25755131,  0.53060664]])

In [7]: data * 10
Out[7]:
array([[ 15.12180439, -22.66139063,  10.87831484],
       [ 10.88472279,   2.57551306,   5.30606639]])

# 数组相加
In [8]: data + data
Out[8]:
array([[ 3.02436088, -4.53227813,  2.17566297],
       [ 2.17694456,  0.51510261,  1.06121328]])

一个ndarray是一个通用的多维同类数据容器,也就是说,它包含的每一个元素均为相同类型。每一个数组都有一个shape属性,用来表征数组每一维度的数量;每一个数组都有一个dtype属性,用来描述数组的数据类型:

In [9]: data.shape
Out[9]: (2, 3)

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

生成adarray

生成数组最简单的方式就是使用array函数。array函数接收任意的序列型对象。生成一个新的包含传递数据的NumPy数组。例如,列表的转换:

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

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

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

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

# 嵌套序列,如同等长度的列表,将会自动转换成多维数组:
In [15]: data2 = [[1,2,3,4],[5,6,7,8]]

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

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


# 查看数组的维度
In [22]: arr2.ndim
Out[22]: 2

# 查询的数组的维度和二维数组中一维数组的大小
In [23]: arr2.shape
Out[23]: (2, 4)

#三维数组由1个二维数组组成,二维数组由4个一维数组组成,一维数组由3个元素组成,所以arr3.shape为(1, 4, 3)
In [24]: arr11 = np.array([[[1,3,5],[2,4,6],[-2,-7,-9],[6,6,6]]])
In [26]: arr11
Out[26]:
array([[[ 1,  3,  5],
        [ 2,  4,  6],
        [-2, -7, -9],
        [ 6,  6,  6]]])
In [25]: arr11.shape
Out[25]: (1, 4, 3)

# 除非显示地指定,否则np.array 会自动推断生成数组的数据类型。数据类型被存储在一个特殊的元数据dtype中。
In [28]: arr1.dtype
Out[28]: dtype('float64')

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

arrange 是Python 内建函数range的数组版:

In [31]: np.arange(5)
Out[31]: array([0, 1, 2, 3, 4])

ndarray 的数据类型

数据类型,即dtype 是一个特殊的对象,它包含了ndarray 需要为某一种数据类型所申明的内存块信息

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

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

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

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


# 可以使用astype 方法显示地转换数组的数据类型
In [36]: arr = np.array([1,2,3,4,5,6])

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

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

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

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

# 将表达数字含义的字符串转化为数字:
In [41]: arr4 = np.array(['1.25','-9.6','42'],dtype=np.string_)
In [43]: arr4
Out[43]: array([b'1.25', b'-9.6', b'42'], dtype='|S4')
In [44]: arr4.astype(np.float64)
Out[44]: array([ 1.25, -9.6 , 42.  ])

In [45]: arr4.dtype
Out[45]: dtype('S4')

tips:

  • 使用astype 时总是生成一个新的数组,即使你传入的dtype 与之前的一样
  • 在NumPy中,当使用numpy.string_类型作字符串数据要小心,因为NumPy会修正它的大小或删除输入且不发出警告。pandas在处理非数值数据时有更直观的开箱型操作

NumPy 数组算术

数组之所以重要是因为它允许你进行批量操作而无须任何for循环。NumPy用户称这种特性为向量化。任何两个等尺寸数组之间的算术操作都应用了逐元素操作的方式:

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

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

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

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

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

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


# 同尺寸数组之间的比较,会产生一个布尔值数组:
In [56]: arr
Out[56]:
array([[1., 2., 3.],
       [4., 5., 6.]])

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

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

基础索引与切片

一维数组比较简单,看起来和Python 的列表很类似:

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

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

In [61]: arr[5]
Out[61]: 5

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

# 对切片进行赋值操作,注意list 不支持该操作
In [63]: arr[5:8] = 12

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

python 内建的list 不支持切换的赋值修改:

>>> a = [0,1,2,3]
>>> a[:2] = 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only assign an iterable
>>>

如果对切片进行赋值操作,例如arr[5:8] = 12,数值被传递给了整个切片。区别于Python的内建列表,数组的切片是原数组的视图。这意味着数据并不是被赋值了。任何对于视图的修改
都会反映到原数组上。

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

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

# 改变arr_slice 后,变化也会体现在原数组上
In [67]: arr_slice[1] = 12345

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

# 改变所有的值
In [69]: arr_slice[:] = 64

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


# 多维数组中,可以省略后续索引值,返回的对象是降低了一个维度的数组。
In [71]: arr3d = np.array([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]])

In [72]: arr3d
Out[72]:
array([[[ 1,  2,  3],
        [ 4,  5,  6]],

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

# arr3d[0] 是一个 2 X 3 的数组:
In [73]: arr3d[0]
Out[73]:
array([[1, 2, 3],
       [4, 5, 6]])

In [74]: arr3d[1]
Out[74]:
array([[ 7,  8,  9],
       [10, 11, 12]])

# 标量和数组都可以传递给arr3d[0]
In [75]: old_values = arr3d[0].copy()

In [76]: old_values
Out[76]:
array([[1, 2, 3],
       [4, 5, 6]])

# 标量
In [77]: arr3d[0] = 42

In [78]: arr3d
Out[78]:
array([[[42, 42, 42],
        [42, 42, 42]],

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

# 数组
In [79]: arr3d[0] = old_values

In [80]: arr3d
Out[80]:
array([[[ 1,  2,  3],
        [ 4,  5,  6]],

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

# 返回一维数组
In [81]: arr3d[1,0]
Out[81]: array([7, 8, 9])

In [82]: arr3d[1][0]
Out[82]: array([7, 8, 9])


# arr3d[1,0] 和 arr3d[1][0] 都可以理解为以下两个步骤:
In [84]: x = arr3d[1]

In [85]: x[0]
Out[85]: array([7, 8, 9])

可以进行多组切片,与多组索引类似:


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

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

# 取前两行
In [7]: arr2d[:2]
Out[7]:
array([[1, 2, 3],
       [4, 5, 6]])

# 取前两行,第二、三列
In [6]: arr2d[:2,1:]
Out[6]:
array([[2, 3],
       [5, 6]])

# 如果将索引和切片混合,就可以得到低维度的切片
# 例如,只选择第二行但是只选择前两列
In [9]: arr2d[1,:2]
Out[9]: array([4, 5])

# 类似的,选择前2行,第三列:
In [11]: arr2d[:2,2]
Out[11]: array([3, 6])

# 全部行的第一列:
In [12]: arr2d[:, :2]
Out[12]:
array([[1, 2],
       [4, 5],
       [7, 8]])

# 修改操作
In [13]: arr2d[:2, 1:] = 0
In [15]: arr2d
Out[15]:
array([[1, 0, 0],
       [4, 0, 0],
       [7, 8, 9]]

布尔索引

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

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

In [4]: data
Out[4]:
array([[ 0.88945304,  1.20702223, -1.80396476,  1.73600343],
       [-0.83062009,  0.80114758, -0.05341618,  2.06891105],
       [-0.9367139 ,  0.70309517, -0.60219465, -0.43911795],
       [ 0.19394595, -1.0654216 ,  0.0600268 ,  0.65027297],
       [-1.18194796, -1.389811  ,  0.83965045,  1.4783589 ],
       [ 0.689694  , -0.61443106,  0.92243982, -0.59251905],
       [ 1.39081109, -1.57327467,  1.16744237,  2.15674934]])

# 产生布尔数组
In [5]: names == 'Bob'
Out[5]: array([ True, False, False,  True, False, False, False])

# 提取数据
In [6]: data[ names == 'Bob']
Out[6]:
array([[ 0.88945304,  1.20702223, -1.80396476,  1.73600343],
       [ 0.19394595, -1.0654216 ,  0.0600268 ,  0.65027297]])

刚开始看不太明白什么意思,后来理解如下:

  • 将data 看作一个矩阵(二维表),names 数组作为行索引
    将判断条件为True的数据给筛选出来了

需要注意的是:
布尔值数组的长度必须和数组轴索引长度一致,当布尔值数组的长度不正确的时候,布尔值选数据的方法并不会报错,所以在使用该特性的时候要小心

# 布尔索引和切换混合使用,选择第3,4列数据
In [13]: data[ names == 'Bob', 2:]
Out[13]:
array([[-1.80396476,  1.73600343],
       [ 0.0600268 ,  0.65027297]])

# 布尔索引 和 整数索引混合使用
In [14]: data[ names == 'Bob', 3]
Out[14]: array([1.73600343, 0.65027297])

# 也可以使用 != 或者条件表达式前使用~对条件取反
In [3]: datas = np.random.randn(7,4)

In [4]: datas
Out[4]:
array([[ 1.69463393, -0.13282557,  0.97685929,  0.47726206],
       [ 1.4151519 ,  0.03530559, -1.81394627, -0.94818625],
       [-0.01785297, -1.71707401, -1.45711343, -0.6270112 ],
       [-0.72410849,  1.54598123,  1.02553296,  0.26675473],
       [ 0.44388758, -0.94716734,  0.86788661,  0.29255579],
       [-0.18685197, -1.71200296,  0.18037524, -0.4429684 ],
       [ 1.61793481, -0.47413065,  0.50914338,  0.38576835]])

In [5]: datas[names != 'Bob']
Out[5]:
array([[ 1.4151519 ,  0.03530559, -1.81394627, -0.94818625],
       [-0.01785297, -1.71707401, -1.45711343, -0.6270112 ],
       [ 0.44388758, -0.94716734,  0.86788661,  0.29255579],
       [-0.18685197, -1.71200296,  0.18037524, -0.4429684 ],
       [ 1.61793481, -0.47413065,  0.50914338,  0.38576835]])
In [7]: datas[~(names == 'Bob')]
Out[7]:
array([[ 1.4151519 ,  0.03530559, -1.81394627, -0.94818625],
       [-0.01785297, -1.71707401, -1.45711343, -0.6270112 ],
       [ 0.44388758, -0.94716734,  0.86788661,  0.29255579],
       [-0.18685197, -1.71200296,  0.18037524, -0.4429684 ],
       [ 1.61793481, -0.47413065,  0.50914338,  0.38576835]])
In [8]: cond = names == 'Bob'
In [11]: datas[~cond]
Out[11]:
array([[ 1.4151519 ,  0.03530559, -1.81394627, -0.94818625],
       [-0.01785297, -1.71707401, -1.45711343, -0.6270112 ],
       [ 0.44388758, -0.94716734,  0.86788661,  0.29255579],
       [-0.18685197, -1.71200296,  0.18037524, -0.4429684 ],
       [ 1.61793481, -0.47413065,  0.50914338,  0.38576835]])


# 组合多个布尔值条件,需要使用算术运算符:如& 和 | 
# tpis:Python的关键字and 和 or 对布尔数组并没有用
In [16]: names
Out[16]: array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'], dtype='<U4')

In [12]: mask = (names =='Bob') | (names == 'Will')

In [13]: mask
Out[13]: array([ True, False,  True,  True,  True, False, False])

# 取为True的数据
In [18]: datas[mask]
Out[18]:
array([[ 1.69463393, -0.13282557,  0.97685929,  0.47726206],
       [-0.01785297, -1.71707401, -1.45711343, -0.6270112 ],
       [-0.72410849,  1.54598123,  1.02553296,  0.26675473],
       [ 0.44388758, -0.94716734,  0.86788661,  0.29255579]])

# 将datas 中所有负值设置为0
In [19]: datas
Out[19]:
array([[ 1.69463393, -0.13282557,  0.97685929,  0.47726206],
       [ 1.4151519 ,  0.03530559, -1.81394627, -0.94818625],
       [-0.01785297, -1.71707401, -1.45711343, -0.6270112 ],
       [-0.72410849,  1.54598123,  1.02553296,  0.26675473],
       [ 0.44388758, -0.94716734,  0.86788661,  0.29255579],
       [-0.18685197, -1.71200296,  0.18037524, -0.4429684 ],
       [ 1.61793481, -0.47413065,  0.50914338,  0.38576835]])

In [20]: datas[datas < 0] = 0

In [21]: datas
Out[21]:
array([[1.69463393, 0.        , 0.97685929, 0.47726206],
       [1.4151519 , 0.03530559, 0.        , 0.        ],
       [0.        , 0.        , 0.        , 0.        ],
       [0.        , 1.54598123, 1.02553296, 0.26675473],
       [0.44388758, 0.        , 0.86788661, 0.29255579],
       [0.        , 0.        , 0.18037524, 0.        ],
       [1.61793481, 0.        , 0.50914338, 0.38576835]])


# 利用布尔数组设置值
In [23]: datas[names != 'Joe'] = 7

In [24]: datas
Out[24]:
array([[7.        , 7.        , 7.        , 7.        ],
       [1.4151519 , 0.03530559, 0.        , 0.        ],
       [7.        , 7.        , 7.        , 7.        ],
       [7.        , 7.        , 7.        , 7.        ],
       [7.        , 7.        , 7.        , 7.        ],
       [0.        , 0.        , 0.18037524, 0.        ],
       [1.61793481, 0.        , 0.50914338, 0.38576835]])

神奇索引

神奇索引是NumPy 中的术语,用于描述使用整数数组进行数据索引

# 假设有一个 8 X 4 的数组
In [25]: arr =  np.empty((8,4))

In [26]: for i in range(8):
    ...:     arr[i] = i
    ...:

# 为了选出一个符合特定顺序的子集,可以简单地通过一个包含指明所需顺序的列表或数组来完成
In [27]: arr
Out[27]:
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 [29]: arr[[-3,-5,-7]]
Out[29]:
array([[5., 5., 5., 5.],
       [3., 3., 3., 3.],
       [1., 1., 1., 1.]])


# 生成一个 8 X  4的 数组
In [30]: arr = np.arange(32).reshape((8,4))

In [31]: arr
Out[31]:
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 [32]: arr[[1,5,7,2],[0,3,1,2]]
# (1,0),(5,3),(7,1),(2,2) 被选中
Out[32]: array([ 4, 23, 29, 10])


# 神奇索引的行为和一些用户所设想的并不相同,通常情况下我们所设想的结果是通过选择矩阵中行列的子集所形成的矩阵区域。下面是实现我们想法的一种方式:
In [39]: arr[[1,5,7,2]][:,[0,3,1,2]]
Out[39]:
array([[ 4,  7,  5,  6],
       [20, 23, 21, 22],
       [28, 31, 29, 30],
       [ 8, 11,  9, 10]])
# 请牢记神奇索引和切片不同,它总是将数据复制到一个新的数组中。
posted @ 2022-12-31 21:14  chuangzhou  阅读(28)  评论(0编辑  收藏  举报