最近感觉脑力日渐退化,很多基础的东西都记不清了,于是打算刷一下《利用python进行数据分析》这本书,看了一部分发现有很多东西以前学的时候也被遗漏了,果然还是需要补一下。
于是在这里对书中感觉重要、又记忆不深刻的东西做了下笔记,方便以后查阅。以下涉及到的是书中第四章关于numpy的基本操作,以下笔记只包括了我个人不熟练的内容,如果有朋友想系统地学习相关知识地话建议直接看原书,在这里奉上一份书籍的链接https://www.jianshu.com/p/04d180d90a3f,来自简书的SeanCheney翻译。
使用ipython中内置**%time和%timeit**魔法命令可以很方便地测试代码的运行时间,以方便查看代码运行地效率。在Anaconda下的jupyter notebook和spyder均可使用。
他们地区别在于:
下面给出一个小例子,分别测试numpy数组和列表地计算时间
# 构建包含一百万个整数的数组和列表
import numpy as np
my_arr = np.arange(1000000)
my_list = list(range(1000000))
# 使用%time计算时间
%time my_arr2 = my_arr*2
%time my_list2 = [x * 2 for x in my_list]
# 使用%timeit计算时间
%timeit my_arr3 = my_arr*2
%timeit my_list3 = [x * 2 for x in my_list]
Wall time: 3 ms
Wall time: 113 ms
100 loops, best of 3: 3.6 ms per loop
10 loops, best of 3: 130 ms per loop
通过输出结果可以看出%timeit得出的是循环运行过100次以后而得出的一个平均的时间范围,相对于只运行了1次的%time要客观一些。
当然,为了节省时间,我们也可以使用类似**%time for _ in range(10): + 代码
**的循环来控制运行的次数。
在这里记录下一些记忆不深刻的构建数值的方法,如下:
1.empty可以创建一个没有任何具体值的数组,empty_like可以生成类似empty的一个同维度的数组;
np.empty((3,3))
array([[ 0.00000000e+000, 0.00000000e+000, 0.00000000e+000],
[ 0.00000000e+000, 0.00000000e+000, 5.11852009e-321],
[ 4.28285262e+202, 4.53435245e+202, 4.53435391e+202]])
a = [1, 2, 3]
np.empty_like(a)
array([0, 0, 0])
2.ones和zeros可以生成一个均为1或0的数组,而且他们都有上述的“_like”属性;
print(np.ones((3,3)))
print(np.zeros((3,3)))
[[ 1. 1. 1.]
[ 1. 1. 1.]
[ 1. 1. 1.]]
[[ 0. 0. 0.]
[ 0. 0. 0.]
[ 0. 0. 0.]]
print(np.ones_like(a))
print(np.zeros_like(a))
[1 1 1]
[0 0 0]
3.eye返回一个对角线元素为1,其他元素为0的二维数组;full返回一个由常数填充的数组;identity返回一个n维单位方阵。
print(np.eye(3))
print(np.full((3,3),2))
print(np.identity(3))
[[ 1. 0. 0.]
[ 0. 1. 0.]
[ 0. 0. 1.]]
[[2 2 2]
[2 2 2]
[2 2 2]]
[[ 1. 0. 0.]
[ 0. 1. 0.]
[ 0. 0. 1.]]
b = np.array([[1,2,3],[4,5,6],[7,8,9]])
print(np.full_like(b, 6))
[[6 6 6]
[6 6 6]
[6 6 6]]
通过带入诸如“2.”这样的数据,ndarray会自动识别并把类型识别为最适应数据的float,在建立数组时可以使用“dtype=类型名”的方式来指定类型,也可以通过“.astype”的方法来转换dtype的类型。
arr1 = np.array([1, 2., 3])
arr1.dtype
dtype('float64')
dtype的命名方式为一个类型名(如float或int),后面跟一个用于表示各元素位长的数字。如标准的双精度浮点值(即float对象)需要占用8字节(即64位)。如使用32位计算机时会生成32位的类型。初学者开始不必过多关注数据类型这一块的内容,当需要控制数据在内存和磁盘中的存储方式时(尤其是对大数据集),就需要了解如何控制存储类型。
在这里我们先建立一个用于存储数据的数组以及一个存储姓名的数组(含有重复项)。假设每个名字都对应data数组中的一行,而我们想要选出对应于名字"Bob"的所有行。跟算术运算一样,数组的比较运算(如==)也是矢量化的。
names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
data = np.random.randn(7, 4)
names
array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'],
dtype='
data
array([[-1.17706106, -0.51687289, -1.67710096, 0.78072476],
[-1.66166053, 0.3530725 , 0.82506431, -0.8178901 ],
[-0.05373718, -1.69818667, -2.04325421, -1.1587806 ],
[-1.29072768, -0.0431474 , 0.4765179 , -1.88630547],
[-0.61853764, 1.16299222, 0.19685369, -0.77267238],
[-0.63991631, -0.11371806, -0.02870365, 0.06743721],
[ 0.10020879, -0.66180412, 1.09984563, -0.42427759]])
对names和字符串"Bob"的比较运算将会产生一个布尔型数组,而这个数据也可以用于对数组的索引。
names == 'Bob'
array([ True, False, False, True, False, False, False], dtype=bool)
data[names == 'Bob']
array([[-1.17706106, -0.51687289, -1.67710096, 0.78072476],
[-1.29072768, -0.0431474 , 0.4765179 , -1.88630547]])
上述代码我们选取的“Bob”所对应的行,也可以通过**“~”**对选择进行否定,选取除“Bob”以外的行。
data[~(names == 'Bob')]
array([[-1.66166053, 0.3530725 , 0.82506431, -0.8178901 ],
[-0.05373718, -1.69818667, -2.04325421, -1.1587806 ],
[-0.61853764, 1.16299222, 0.19685369, -0.77267238],
[-0.63991631, -0.11371806, -0.02870365, 0.06743721],
[ 0.10020879, -0.66180412, 1.09984563, -0.42427759]])
同样可以使用如&(和)、|(或)之类的布尔算术运算符来组合多个布尔条件。
data[(names == 'Bob')|(names == 'Will')]
array([[-1.17706106, -0.51687289, -1.67710096, 0.78072476],
[-0.05373718, -1.69818667, -2.04325421, -1.1587806 ],
[-1.29072768, -0.0431474 , 0.4765179 , -1.88630547],
[-0.61853764, 1.16299222, 0.19685369, -0.77267238]])
通用函数(即ufunc)是一种对ndarray中的数据执行元素级运算的函数。你可以将其看做简单函数(接受一个或多个标量值,并产生一个或多个标量值)的矢量化包装器。
在这里列举几个对我有用但又记忆不深刻的通用函数进行介绍。
1.np.modf可以分隔数组的整数部分和小数部分
arr = np.random.randn(4)*8
remainder, whole_part = np.modf(arr)
print(arr)
print(remainder)
print(whole_part)
[ 13.00870719 2.81020693 10.76153185 9.3025193 ]
[ 0.00870719 0.81020693 0.76153185 0.3025193 ]
[ 13. 2. 10. 9.]
2.maximum和fmax能够用于逐个比较两个序列,并得出其中的最大值组成的数组;minimum与fmin则相反.
a = [1,9,3]; b = [2,7,8]
print(np.maximum(a,b))
print(np.fmax(a,b))
print(np.minimum(a,b))
print(np.fmin(a,b))
[2 9 8]
[2 9 8]
[1 7 3]
[1 7 3]
3.abs可以得出传入序列的绝对值,fabs也有同样的效果,但是输出的结果均为浮点数。
print(np.abs([1,-3,-5]))
print(np.fabs([1,-3,-5]))
[1 3 5]
[ 1. 3. 5.]
4.sign用于提取数值的符号,传入值大于0时返回1,等于0时返回0,小于0时返回-1
np.sign([-3,2,6,-8,0])
array([-1, 1, 1, -1, 0])
5.meshgrid用于从一个坐标向量中返回一个坐标矩阵 ,具体见下述案例。
x = np.linspace(0,1,3)
y = np.linspace(0,1,2)
print(x)
print(y)
[ 0. 0.5 1. ]
[ 0. 1.]
xv, yv = np.meshgrid(x,y)
print(xv)
print(yv)
[[ 0. 0.5 1. ]
[ 0. 0.5 1. ]]
[[ 0. 0. 0.]
[ 1. 1. 1.]]
6.cumprod可以实现对不同轴上的元素进行累乘。
a = np.array([[1, 2, 3], [4, 5, 6]])
print(a)
print(np.cumprod(a,))
print(np.cumprod(a, axis=0))
print(np.cumprod(a, axis=1))
[[1 2 3]
[4 5 6]]
[ 1 2 6 24 120 720]
[[ 1 2 3]
[ 4 10 18]]
[[ 1 2 6]
[ 4 20 120]]
面对布尔型数组时,布尔型数组也会被转换位0/1的形式,我们通常可以使用sum来对其中的True进行计算,以得到某种条件下的数值。
另外还有两个方法any和all,它们对布尔型数组非常有用。any用于测试数组中是否存在一个或多个True,而all则检查数组中所有值是否都是True:
bools = np.array([False, False, True, False])
bools.any()
True
bools.all()
False
这两个方法也能用于非布尔型数组,所有非0元素将会被当做True。
这里主要记录一下矩阵的乘法和求逆等操作
矩阵相乘在numpy种一般有两种形式,分别采用dot和@进行相乘。
x = np.array([[1., 2., 3.], [4., 5., 6.]])
y = np.array([[6., 23.], [-1, 7], [8, 9]])
x.dot(y)
array([[ 28., 64.],
[ 67., 181.]])
x@y
array([[ 28., 64.],
[ 67., 181.]])
numpy.linalg中有一组标准的矩阵分解运算以及诸如求逆和行列式之类的东西,比如inv可以实现矩阵求逆.
from numpy.linalg import inv, qr
X = np.random.randn(5, 5)
print(X)
print(inv(X))
[[-0.5036109 -0.97600344 0.03254745 0.16715004 -0.50821894]
[ 1.01192309 1.41291104 0.34649623 -0.64291311 2.88420256]
[ 0.16335004 0.24796311 0.3266961 1.71831367 -0.68582454]
[-0.4730436 -0.85969492 -0.29875557 -0.52413449 0.03625922]
[-0.11117046 -1.05451037 0.73137506 -0.78498822 -0.83989605]]
[[-11.57557774 -0.02819429 5.84182117 11.54711839 2.63584394]
[ 4.53421235 -0.1726351 -2.87290386 -5.89595102 -1.24510859]
[ 5.79910152 0.37071313 -2.50584228 -6.1392223 -0.45486188]
[ -0.2196321 0.12669264 0.85775838 0.82380958 -0.09688417]
[ 1.09443929 0.42488353 -0.14998502 -0.2418298 -0.2817846 ]]
numpy.random模块对Python内置的random进行了补充,增加了一些用于高效生成多种概率分布的样本值的函数。但是这些都是伪随机数,是因为它们都是通过算法基于随机数生成器种子,在确定性的条件下生成的。你可以用NumPy的np.random.seed更改随机数生成种子:
np.random.seed(1234)
samples = np.random.randn(10)
np.random.seed(1234)
samples_2 = np.random.randn(10)
print(samples)
print(samples_2)
[ 0.47143516 -1.19097569 1.43270697 -0.3126519 -0.72058873 0.88716294
0.85958841 -0.6365235 0.01569637 -2.24268495]
[ 0.47143516 -1.19097569 1.43270697 -0.3126519 -0.72058873 0.88716294
0.85958841 -0.6365235 0.01569637 -2.24268495]
可见设置随机种子后生成的随机数是固定的,可以通过控制相同的随机种子的方法来保持输出结果的一致。
但是上述的情况生成的是全局的随机种子。若想避免全局状态,可以使用numpy.random.RandomState,创建一个与其它隔离的随机数生成器:
rng = np.random.RandomState(123)
rng.randn(10)
array([-1.0856306 , 0.99734545, 0.2829785 , -1.50629471, -0.57860025,
1.65143654, -2.42667924, -0.42891263, 1.26593626, -0.8667404 ])
randn是产生正态分布(平均值为0, 标准差为1)随机数的方法,此外还有产生符合其他分布随机数的方法,如:
补充介绍:
1.shuffle可以用于将序列的所有元素随机排序
arr = np.arange(10)
np.random.shuffle(arr)
arr
array([6, 9, 1, 5, 4, 3, 2, 8, 0, 7])
2.permutation可以随机地打乱一个序列,或者生成一个乱寻数组
print(np.random.permutation(10))
print(np.random.permutation([1, 4, 9, 12, 15]))
[9 0 6 5 3 1 2 4 8 7]
[15 1 12 9 4]
关于这一章的笔记大概就记录到这了,后续再过一下pandas的操作numpy的高级应用,争取一周内把这本书刷完吧。。。
2018/9/28
Escher