以下内容主要学习自《利用Python进行数据分析》
第4章 NumPy基础(1)
NumPy是Numerical Python的简称,它是目前Python数值计算中最为重要的基础包。大多数计算包都提供了基于Numpy的科学函数功能,将NumPy的数组对象作为数据交换的通用语言。
本章将介绍NumPy数组的基础操作。虽然深入理解NumPy对于大部分数据分析应用并不是必需的,但是精通基于数组的编程和思维是成为Python科学计算专家的第一步。
NumPy之所以如此重要,其中一个原因就是它的设计对于含有大量数组的数据非常有效,此外还有如下原因:
- NumPy是用C语言写的,因此是强类型的,运算时省却了类型检查等管理操作;
- NumPy将数据存储在连续的内存块上,对内存的消耗小于Python内建的序列;
- NumPy可以针对全量数组进行复杂计算而不需要写Python循环。
In [1]: import numpy as np
In [2]: my_arr = np.arange(1000000)
In [3]: my_list = list(range(1000000))
In [4]: %time for _ in range(10): my_arr2 = my_arr * 2
Wall time: 19 ms
In [5]: %time for _ in range(10): my_list2 = [x*2 for x in my_list]
Wall time: 823 ms
上面的示例中,对100万个整数乘2,并循环10次,我们看到Numpy比传统的Python快了40多倍。
ndarray多维数组对象
NumPy的核心特征之一就是N维数据对象:ndarray,它是一个快速、灵活的大型数据集容器,允许你在整块数据上进行计算。
数据类型
一个ndarray对象中包含的每一个元素均为相同类型的。NumPy支持的数据类型有:
类型 | 类型代码 | 描述 |
---|---|---|
int8, uint8 | i1, u1 | 有符号和无符号的8位整数 |
int16, uint16 | i2, u2 | 有符号和无符号的16位整数 |
int32, uint32 | i4, u4 | 有符号和无符号的32位整数 |
int64, uint64 | i8, u8 | 有符号和无符号的64位整数 |
float16 | f2 | 半精度浮点数 |
float32 | f4或f | 单精度浮点数 |
float64 | f8或d | 双精度浮点数 |
float128 | f16或g | 拓展精度浮点数 |
complex64 | c8 | 基于32位浮点数的复数 |
complex128 | c16 | 基于64位浮点数的复数 |
complex256 | c32 | 基于128位浮点数的复数 |
bool | ? | 布尔值,True或Flase |
object | O | Python object类型 |
string_ | S | ASCII字符串,‘S10’表示长度为10的字符串 |
unicode_ | U | Unicode字符串,‘U10’表示长度为10的字符串 |
NumPy创建ndarray对象时,若不明确指定数据类型,会设置默认的数据类型:整数默认为int32,小数默认为float64(双精度浮点数)。
In [1]: arr = np.array([1, 2, 3])
In [2]: arr.dtype
Out[3]: dtype('int32')
In [3]: arr = np.array([1., 2., 3.])
In [4]: arr.dtype
Out[4]: dtype('float64')
使用dtype参数
显式指定数据类型:
In [5]: arr = np.array([1, 2, 3], dtype=np.float64)
In [6]: arr.dtype
Out[6]: dtype('float64')
In [7]: arr = np.array([1, 2, 3], dtype='f8')
In [8]: arr.dtype
Out[8]: dtype('float64')
可以用astype方法
显式的转换数据的数据类型:
In [9]: arr = np.array([3.7, -1.2, -2.6, 0.5, 12.9, 10.1])
In [10]: arr.dtype
Out[10]: dtype('float64')
In [11]: arr.astype('i2') # 转换为 int16 类型
Out[12]: array([ 3, -1, -2, 0, 12, 10], dtype=int16)
从上例可以看出:从浮点数转换为整数,将丢失小数部分的内容。另外,还可以把数字组成的字符串转换为数值,但如果转换失败会报告ValueError异常
。
ndarray属性
ndarray对象的常用属性如下:
属性 | 说明 |
---|---|
ndarray.ndim | 秩,即轴的数量或维度的数量。 |
ndarray.shape | 数组的维度(m, n),相当于 m行 n列。 |
ndarray.size | 数组元素的总个数,相当于 .shape 中 m*n 的值。 |
ndarray.dtype | ndarray 对象的元素类型。 |
In [1]: import numpy as np
In [2]: arr = np.array([[1, 2, 3], [4 ,5 ,6 ]]) # 创建了一个二维的ndarray对象
In [3]: arr.ndim # 数组是2维的
Out[3]: 2
In [4]: arr.shape # 数组是2X3的
Out[4]: (2, 3)
In [5]: arr.size # 数组共有6个元素
Out[5]: 6
In [6]: arr.dtype
Out[6]: dtype('int32')
创建ndarray
从Python序列创建
使用nd.array()
可以从已有的Python序列(列表或元组)生成ndarray对象:
In [1]: arr = np.array(((1, 2, 3), (4, 5, 6)), dtype=np.float64)
In [2]: arr
Out[2]:
array([[1., 2., 3.],
[4., 5., 6.]])
In [3]: arr = np.array([['a', 'b', 'c'],['d', 'e', 'f']])
In [4]: arr
Out[4]:
array([['a', 'b', 'c'],
['d', 'e', 'f']], dtype='
用数值范围生成
使用nd.arange(start, stop, step, dtype)
可以用数值范围生成ndarray对象:
In [5]: arr = np.arange(0, 10, 3, dtype=np.float32)
In [6]: arr
Out[6]: array([0., 3., 6., 9.], dtype=float32)
用函数生成
NumPy还提供了如下的函数,用于生成给定值的数组:
函数名 | 描述 |
---|---|
ones | 根据给定维度和数据类型生成全1数组 |
ones_like | 根据给定数组生成一个维度一样的全1数组 |
zeros | 根据给定维度和数据类型生成全0数组 |
zeros_like | 根据给定数组生成一个维度一样的全0数组 |
empty | 根据给定维度生成没有初始化值的空数组 |
empty_like | 根据给定数组生成一个维度一样的空数组 |
full | 根据给定维度和数据类型生成指定值的数组 |
full_like | 根据给定数组生成一个维度一样的指定值的数组 |
eye, identity | 生成一个N*N的特征矩阵(对角线是1,其余是0) |
In [7]: arr1 = np.ones((2,3))
In [8]: arr1
Out[8]:
array([[1., 1., 1.],
[1., 1., 1.]])
In [9]: arr2 = np.zeros_like(arr1, dtype=np.int32)
In [10]: arr2
Out[10]:
array([[0, 0, 0],
[0, 0, 0]])
In [11]: arr3 = np.full((2,3), 'a')
In [12]: arr3
Out[12]:
array([['a', 'a', 'a'],
['a', 'a', 'a']], dtype='
用随机数生成
下面这段代码,生成了100万个正态分布的数值数组,但分别用了Python内置的random对象、以及NumPy的random对象。再次看出NumPy的高效,在性能上比Python快了20多倍:
In [1]: import numpy as np
In [2]: from random import normalvariate
In [3]: N = 1000000
In [4]: %timeit samples = [normalvariate(0, 1) for _ in range(N)]
820 ms ± 4.93 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [5]: %timeit np.random.normal(size=N)
32.6 ms ± 321 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
出于科学计算经常要构建一些样本数据的考虑,NumPy提供了多种生成特定概率分布下样本数值的函数:
函数 | 描述 |
---|---|
rand | 均匀分布样本 |
randint | 均匀分布整数样本 |
binomial | 从二项分布中抽取样本 |
normal | 正态分布样本 |
beta | beta分布样本 |
chisquare | 卡方分布样本 |
gamma | 伽马分布样本 |
In [6]: np.random.rand(3, 2) # 生成一个3X2的随机样本
Out[6]:
array([[0.11786151, 0.08833357],
[0.22276668, 0.80334031],
[0.8968275 , 0.85327754]])
# 生成一个2X4的随机整数样本,每个元素大于等于20,、且小于50
In [86]: np.random.randint(20, 50, size=(2, 4))
Out[86]:
array([[33, 48, 42, 36],
[28, 36, 46, 43]])
# 生成10个正态分布的数值,以0为分布的中心值,5为标准偏差(扩散值)。
In [96]: np.random.normal(0, 5, size=(10))
Out[96]:
array([ 0.51643973, -2.83238426, 4.52948333, 3.56189259, 3.9595988 ,
2.12536774, 2.96357585, 4.07909436, -2.13527795, -2.62903347])
正态分布(Normal distribution),又名高斯分布(Gaussian distribution),是一个在数学、物理及工程等领域都非常重要的概率分布,在统计学的许多方面有着重大的影响力。
符合正态分布的数据,如果用曲线表达,具有如下特征:
- 集中性:正态曲线的高峰位于正中央,即均数所在的位置。
- 对称性:正态曲线以均数为中心,左右对称,曲线两端永远不与横轴相交。
- 均匀变动性:正态曲线由均数所在处开始,分别向左右两侧逐渐均匀下降。
从文件输入/输出
NumPy可以将数据以文本或二进制格式存入磁盘或由磁盘载入。
In [97]: arr = np.arange(10)
In [99]: np.save('c:\SomeArray', arr) # 如果存放路径中没有后缀名,将自动加上.npy
In [100]: np.load('c:\SomeArray.npy')
Out[100]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
变换ndarray
修改数组形状
numpy.ndarray的reshape方法
可以在不改变数据的前提下,修改数组的形状。
In [1]: arr = np.arange(8)
In [2]: arr
Out [2]: array([0, 1, 2, 3, 4, 5, 6, 7])
In[3]: arr.shape()
Out[3]: (8,)
In[4]: arr = arr.reshape(2, 4)
In[5]: arr
Out[5]: array([[0, 1, 2, 3],
[4, 5, 6, 7]])
In[6]: arr = arr.reshape(4, 2)
Out[6]: array([[0, 1],
[2, 3],
[4, 5],
[6, 7]])
行列转置
使用ndarray.T属性
可以得到转置的数组。在下面的例子演示的是对一个矩阵(二维数组)进行行列转置。
In[1]: arr = np.arange(8).reshape(2, 4)
In[2]: arr
Out[2]: array([[0, 1, 2, 3],
[4, 5, 6, 7]])
In[3]: arr.T
Out[3]: array([[0, 4],
[1, 5],
[2, 6],
[3, 7]])
如果数组是多维度的,如下三维数组,那么效果如下
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 = arr.T
Out[3]: array([[[ 0, 12],
[ 4, 16],
[ 8, 20]],
[[ 1, 13],
[ 5, 17],
[ 9, 21]],
[[ 2, 14],
[ 6, 18],
[10, 22]],
[[ 3, 15],
[ 7, 19],
[11, 23]]])
In[4]: arr.shape
Out[4]: (4, 3, 2)
在上面的例子中,数组还是三维的,不同的是每个维度的数量发生了变化,即形状从(2,3,4)变为了(4,3,2)。
高维度数组换轴
实际上,使用.T
进行转置是一个特例。如果要对指定的轴进行置换,那么可以使用transpose方法
,该方法接收包含轴编号的元组。
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.transpose(1, 0, 2)
Out[3]: array([[[ 0, 1, 2, 3],
[12, 13, 14, 15]],
[[ 4, 5, 6, 7],
[16, 17, 18, 19]],
[[ 8, 9, 10, 11],
[20, 21, 22, 23]]])
对于多维度数组的换轴,很容易引起误解。在上面的案例中,我们构建了一个三维的数组,每个轴的索引号分别是0,1,2。
接着我们调用了transpose(1, 0, 2)方法,这里面传输的参数,就是原轴的索引号。实际上就是把原来的1轴变为0轴、0轴变为1轴(0、1轴对换),而2轴没有改变。
swapaxes()
Numpy的transpose方法
可以置换多个轴的数据。但如果只想对两个轴的数据进行交换,那么可以使用swapaxes方法
,如下例的效果与上面是一致的。
In[4]: np.swapaxes(arr, 0, 1)
Out[4]: array([[[ 0, 1, 2, 3],
[12, 13, 14, 15]],
[[ 4, 5, 6, 7],
[16, 17, 18, 19]],
[[ 8, 9, 10, 11],
[20, 21, 22, 23]]])