master
aolingwen 6 years ago
parent 8c09fd179a
commit 001d48028b

@ -1,4 +1,4 @@
# 1.1.2数据类型与变量
# 1.1.2 数据类型与变量
## Python内置数据类型

@ -0,0 +1,7 @@
# Chapter1 Python语言
由于 Python 语言的简洁性、易读性以及可扩展性,在国外用 Python 做科学计算的研究机构日益增多,一些知名大学已经采用 Python 来教授程序设计课程。例如卡耐基梅隆大学的编程基础、麻省理工学院的计算机科学及编程导论就使用 Python 语言讲授。众多开源的科学计算软件包都提供了 Python 的调用接口,例如著名的计算机视觉库 OpenCV 、三维可视化库 VTK 、医学图像处理库 ITK 。而 Python 专用的科学计算扩展库就更多了例如如下3个十分经典的科学计算扩展库NumPy、Pandas 和 matplotlib ,它们分别为 Python 提供了快速数组处理、数值运算以及绘图功能。因此Python语言及其众多的扩展库所构成的开发环境十分适合工程技术、科研人员处理实验数据、制作图表甚至开发科学计算应用程序。
Python语言相关实训已在`educoder`平台上提供,若感兴趣可以输入链接进行体验。
链接https://www.educoder.net/paths/85

@ -1,170 +0,0 @@
# 2.1.3:基础操作
## 算术运算
如果想要对 ndarray 对象中的元素做 elementwise (**逐个元素地**)的算术运算非常简单,加减乘除即可。代码如下:
```python
import numpy as np
a = np.array([0, 1, 2, 3])
# a中的所有元素都加2结果为[2, 3, 4, 5]
b = a + 2
# a中的所有元素都减2结果为[-2, -1, 0, 1]
c = a - 2
# a中的所有元素都乘以2结果为[0, 2, 4, 6]
d = a * 2
# a中的所有元素都平方结果为[0, 1, 4, 9]
e = a ** 2
# a中的所有元素都除以2结果为[0, 0.5, 1, 1.5]
f = a / 2
# a中的所有元素都与2比结果为[True, True, False, False]
g = a < 2
```
## 矩阵运算
相同 shape 的矩阵 A 与矩阵 B 之间想要做 elementwise 运算也很简单,加减乘除即可。代码如下:
```python
import numpy as np
a = np.array([[0, 1], [2, 3]])
b = np.array([[1, 1], [3, 2]])
# a与b逐个元素相加结果为[[1, 2], [5, 5]]
c = a + b
# a与b逐个元素相减结果为[[-1, 0], [-1, 1]]
d = a - b
# a与b逐个元素相乘结果为[[0, 1], [6, 6]]
e = a * b
# a的逐个元素除以b的逐个元素结果为[[0., 1.], [0.66666667, 1.5]]
f = a / b
# a与b逐个元素做幂运算结果为[[0, 1], [8, 9]]
g = a ** b
# a与b逐个元素相比较结果为[[True, False], [True, False]]
h = a < b
```
细心的同学应该发现了, * 只能做 elementwise 运算,如果想做真正的矩阵乘法运算显然不能用 * 。 NumPy 提供了 @ 和 dot 函数来实现矩阵乘法。代码如下:
```python
import numpy as np
A = np.array([[1, 1], [0, 1]])
B = np.array([[2, 0], [3, 4]])
# @表示矩阵乘法矩阵A乘以矩阵B结果为[[5, 4], [3, 4]]
print(A @ B)
# 面向对象风格矩阵A乘以矩阵B结果为[[5, 4], [3, 4]]
print(A.dot(B))
# 面向过程风格矩阵A乘以矩阵B结果为[[5, 4], [3, 4]]
print(np.dot(A, B))
```
输出如下:
```python
[[5 4]
[3 4]]
[[5 4]
[3 4]]
[[5 4]
[3 4]]
```
## 简单统计
有的时候想要知道 ndarray 对象中元素的和是多少,最小值是多少,最小值在什么位置,最大值是多少,最大值在什么位置等信息。这个时候可能会想着写一个循环去遍历 ndarray 对象中的所有元素来进行统计。 NumPy 为了解放我们的双手,提供了 sum min max argmin argmax 等函数来实现简单的统计功能,代码如下:
```python
import numpy as np
a = np.array([[-1, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 13]])
# 计算a中所有元素的和结果为67
print(a.sum())
# 找出a中最大的元素结果为13
print(a.max())
# 找出a中最小的元素结果为-1
print(a.min())
# 找出a中最大元素在a中的位置由于a中有12个元素位置从0开始计所以结果为11
print(a.argmax())
# 找出a中最小元素在a中位置结果为0
print(a.argmin())
```
输出如下:
```python
67
13
-1
11
0
```
有的时候,我们在统计时需要根据轴来统计。举个例子,公司员工的基本工资,绩效工资,年终奖的信息如下:
| 工号 | 基本工资 | 绩效工资 | 年终奖 |
| ------------ | ------------ | ------------ | ------------ |
| 1 | 3000 | 4000 | 20000 |
| 2 | 2700 | 5500 | 25000 |
| 3 | 2800 | 3000 | 15000 |
这样一个表格很明显,可以用 ndarray 来存储。代码如下:
```python
import numpy as np
info = np.array([[3000, 4000, 20000], [2700, 5500, 25000], [2800, 3000, 15000]])
```
info 实例化之后就有了维度和轴的概念,很明显 info 是个二维数组,所以它的**维度是 2 **。维度为 2 换句话来说就是 info 有**两个轴0 号轴与 1 号轴(轴的编号从 0 开始算)**。轴所指的方向如下图所示:
![轴示意图](1.jpg)
如果想要统计下这 3 位员工中基本工资、绩效工资与年终奖的最小值与最大值(**也就是说分别统计出每一列中的最小与最大值**)。我们可以沿着 0 号轴来统计。想要实现沿着哪个轴来统计,只需要修改 axis 即可,代码如下:
```python
import numpy as np
info = np.array([[3000, 4000, 20000], [2700, 5500, 25000], [2800, 3000, 15000]])
# 沿着0号轴统计结果为[2700, 3000, 15000]
print(info.min(axis=0))
# 沿着0号轴统计结果为[3000, 5500, 25000]
print(info.max(axis=0))
```
输出如下:
```python
[ 2700 3000 15000]
[ 3000 5500 25000]
```
**PS:当没有修改 axis 时axis 的值默认为 None 。意思是在统计时会把 ndarray 对象中所有的元素都考虑在内。**

@ -1,149 +0,0 @@
# 2.1.4:随机数生成
## 简单随机数生成
NumPy 的 random 模块下提供了许多生成随机数的函数,如果对于随机数的概率分布没有什么要求,则通常可以使用 random_sample 、choice 、randint 等函数来实现生成随机数的功能。
### random_sample
random_sample 用于生成区间为 [0, 1] 的随机数,需要填写的参数 size 表示生成的随机数的形状,比如 size=[2, 3] 那么则会生成一个 2 行 3 列的 ndarray ,并用随机值填充。示例代码如下:
```python
import numpy as np
print(np.random.random_sample(size=[2, 3]))
```
输出可能如下:
```python
[[0.32343809 0.38736262 0.42413616]
[0.86190206 0.27183736 0.12824812]]
```
### choice
如果想模拟像掷骰子、扔硬币等这种随机值是离散值,而且知道范围的可以使用 choice 实现。choice 的主要参数是 a 和 size 。 a 是个一维数组,代表你想从 a 中随机挑选size 是随机数生成后的形状。假如模拟 5 次掷骰子,代码如下:
```python
import numpy as np
'''
掷骰子时可能出现的点数为1, 2, 3, 4, 5, 6所以a=[1,2,3,4,5,6]
模拟5此掷骰子所以size=5
结果可能为 [6, 4, 3, 1, 3]
'''
print(np.random.choice(a=[1, 2, 3, 4, 5, 6], size=5))
```
输出可能如下:
```python
[6 4 3 1 3]
```
### randint
randint 的功能和 choice 差不多,只不过 randint 只能生成整数,而 choice 生成的数与 a 有关,如果 a 中有浮点数,那么 choice 会有概率挑选到浮点数。
randint 的参数有 3 个,分别为 low high 和 size 。其中 low 表示随机数生成时能够生成的最小值, high 表示随机数生成时能够生成的最大值减 1 。也就是说 randint 生成的随机数的区间为 [low, high) 。假如模拟 5 次掷骰子,代码如下:
```python
import numpy as np
'''
掷骰子时可能出现的点数为1, 2, 3, 4, 5, 6所以low=1,high=7
模拟5此掷骰子所以size=5
结果可能为 [6, 4, 3, 1, 3]
'''
print(np.random.randint(low=1, high=7, size=5)
```
输出可能如下:
```python
[6 4 3 1 3]
```
## 概率分布随机数生成
如果对于产生的随机数的概率分布有特别要求NumPy 同样提供了从指定的概率分布中采样得到的随机值的[接口](https://www.numpy.org/devdocs/reference/routines.random.html?highlight=random#module-numpy.random)。在这里主要介绍高斯分布。
高斯分布又称为正态分布,其分布图形如下:
![高斯分布](2.jpg)
上图中横轴为随机变量的值(**在这里可以看成是产生的随机值**),纵轴表示随机变量对应的概率(**在这里可以看成是随机值被挑选到的概率**)。
其实在日常生活中有很多现象或多或少都符合高斯分布。比如某个地方的高考分数,一般来说高考分数非常低和高考分数非常高的学生都比较少,而分数中规中矩的学生比较多,如果所统计的数据足够大,那么高考分数的概率分布也会和上图一样,**中间高,两边低**。
想要实现根据高斯分布来产生随机值,可以使用 normal 函数。示例代码如下:
```python
import numpy as np
'''
根据高斯分布生成5个随机数
结果可能为:[1.2315868, 0.45479902, 0.24923969, 0.42976352, -0.68786445]
从结果可以看出0.4左右得值出现的次数比较高1和-0.7左右的值出现的次数比较低。
'''
print(np.random.normal(size=5)
```
输出可能如下:
```python
[1.2315868 0.45479902 0.24923969 0.42976352 -0.68786445]
```
其中 normal 函数除了 size 参数外,还有两个比较重要的参数就是 loc 和 scale ,它们分别代表高斯分布的**均值**和**方差**。 loc 影响的分布中概率最高的点的位置,假设 loc=2 ,那么分布中概率最高的点的位置就是 2 。下图体现了 loc 对分布的影响其中蓝色f分布的 loc=0 ,红色分布的 loc=5 。
![均值对高斯分布的影响](3.jpg)
scale 影响的是分布图形的胖瘦scale 越小分布就越又高又瘦scale 越大,分布就越又矮又胖。下图体现了 scale 对分布的影响其中蓝色分布的scale=0.5 ,红色分布的 scale=1.0 。
![方差对高斯分布的影响](4.jpg)
所以,想要根据均值为 1 ,方差为 10 的高斯分布来生成 5 个随机值,代码如下:
```python
import numpy as np
print(np.random.normal(loc=1, scale=10, size=5)
```
输出可能如下:
```python
[1.52414964 7.08225325 13.26337651 4.29866004 9.89972241]
```
## 随机种子
前面说了这么多随机数生成的方法,那么随机数是怎样生成的呢?其实计算机产生的随机数是由随机种子根据一定的计算方法计算出来的数值。**所以只要计算方法固定,随机种子固定,那么产生的随机数就不会变!**
如果想要让每次生成的随机数不变,那么就需要设置随机种子(**随机种子其实就是一个0到4294967295的整数**)。设置随机种子很长简单,调用 seed 函数并设置随机种子即可,代码如下:
```python
import numpy as np
# 设置随机种子为233
np.random.seed(seed=233)
data = [1, 2, 3, 4]
# 随机从data中挑选数字结果为4
print(np.random.choice(data))
# 随机从data中挑选数字结果为4
print(np.random.choice(data))
```
输出如下:
```python
4
4
```

@ -1,158 +0,0 @@
# 2.1.5:索引与切片
## 索引
ndarray 的索引其实和 python 的 list 的索引极为相似。元素的索引从 0 开始。代码如下:
```python
import numpy as np
# a中有4个元素那么这些元素的索引分别为0123
a = np.array([2, 15, 3, 7])
# 打印第2个元素
# 索引1表示的是a中的第2个元素
# 结果为15
print(a[1])
# b是个2行3列的二维数组
b = np.array([[1, 2, 3], [4, 5, 6]])
# 打印b中的第1行
# 总共就2行所以行的索引分别为01
# 结果为[1, 2, 3]
print(b[0])
# 打印b中的第2行第2列的元素
# 结果为5
print(b[1][1])
```
输出如下:
```python
15
[1 2 3]
5
```
## 遍历
ndarray 的遍历方式与 python 的 list 的遍历方式也极为相似,示例代码如下:
```python
import numpy as np
a = np.array([2, 15, 3, 7])
# 使用for循环将a中的元素取出来后打印
for element in a:
print(element)
# 根据索引遍历a中的元素并打印
for idx in range(len(a)):
print(a[idx])
# b是个2行3列的二维数组
b = np.array([[1, 2, 3], [4, 5, 6]])
# 将b展成一维数组后遍历并打印
for element in b.flat:
print(element)
# 根据索引遍历b中的元素并打印
for i in range(len(b)):
for j in range(len(b[0])):
print(b[i][j])
```
输出如下:
```python
2
15
3
7
2
15
3
7
1
2
3
4
5
6
1
2
3
4
5
6
```
## 切片
ndarray 的切片方式与 python 的 list 的遍历方式也极为相似,对切片不熟的同学也不用慌,套路很简单,就是用索引。
假设想要将下图中紫色部分切片出来,就需要确定行的范围和列的范围。由于紫色部分行的范围是 0 到 2 ,所以切片时行的索引范围是 0:3 (**索引范围是左闭右开**);又由于紫色部分列的范围也是 0 到 2 ,所以切片时列的索引范围也是 0:3 (**索引范围是左闭右开**)。最后把行和列的索引范围整合起来就是 [0:3, 0:3] (**逗号左边是行的索引范围**)。当然有时为了方便0 可以省略,也就是 [:3, :3] 。
![切片示意图](5.jpg)
切片示例代码如下:
```python
import numpy as np
# a中有4个元素那么这些元素的索引分别为0123
a = np.array([2, 15, 3, 7])
'''
将索引从1开始到最后的所有元素切片出来并打印
结果为[15 3 7]
'''
print(a[1:])
'''
将从倒数第2个开始到最后的所有元素切片出来并打印
结果为[3 7]
'''
print(a[-2:])
'''
将所有元素倒序切片并打印
结果为[ 7 3 15 2]
'''
print(a[::-1])
# b是个2行3列的二维数组
b = np.array([[1, 2, 3], [4, 5, 6]])
'''
将第2行的第2列到第3列的所有元素切片并打印
结果为[[5 6]]
'''
print(b[1:, 1:3])
'''
将第2列到第3列的所有元素切片并打印
结果为[[2 3]
[5 6]]
'''
print(b[:, 1:3])
```
输出如下:
```python
[15 3 7]
[3 7]
[7 3 15 2]
[[5 6]]
[[2 3]
[5 6]]
```

@ -0,0 +1,92 @@
# 2.2.3:广播机制
## 什么是广播
两个`ndarray`对象的相加、相减以及相乘都是对应元素之间的操作。代码如下:
```python
import numpy as np
x = np.array([[2,2,3],[1,2,3]])
y = np.array([[1,1,3],[2,2,4]])
print(x*y)
'''
输出结果如下:
[[ 2 2 9]
[ 2 4 12]]
'''
```
当两个`ndarray`对象的形状并不相同的时候,我们可以通过扩展数组的方法来实现相加、相减、相乘等操作,这种机制叫做广播(`broadcasting`)。
比如,一个二维的`ndarray`对象减去列平均值,来对数组的每一列进行取均值化处理:
```python
import numpy as np
# arr为4行3列的ndarray对象
arr = np.random.randn(4,3)
# arr_mean为有3个元素的一维ndarray对象
arr_mean = arr.mean(axis=0)
# 对arr的每一列进行
demeaned = arr - arr_mean
```
很明显上面代码中的`arr`和`arr_mean`维度并不相同,但是它们可以进行相减操作,这就是通过广播机制来实现的。
## 广播的原则
如果两个数组的后缘维度(trailing dimension即从末尾开始算起的维度)的轴长度相符,或其中的一方的长度为`1`,则认为它们是广播兼容的。广播会在缺失或长度为`1`的维度上进行。
这句话是理解广播的核心。广播主要发生在两种情况,一种是两个数组的维数不相等,但是它们的后缘维度的轴长相符,另外一种是有一方的长度为`1`。
我们来看一个例子:
```python
import numpy as np
arr1 = np.array([[0, 0, 0],[1, 1, 1],[2, 2, 2], [3, 3, 3]])
arr2 = np.array([1, 2, 3])
arr_sum = arr1 + arr2
print(arr_sum)
'''
输入结果如下:
[[1 2 3]
[2 3 4]
[3 4 5]
[4 5 6]]
'''
```
`arr1`的`shape`为`(4,3)``arr2`的`shape`为`(3,)`。可以说前者是二维的,而后者是一维的。但是它们的后缘维度相等,`arr1`的第二维长度为`3`,和`arr2`的维度相同。`arr1`和`arr2`的`shape`并不一样,但是它们可以执行相加操作,这就是通过广播完成的,在这个例子当中是将`arr2`沿着`0`轴进行扩展。
![6](6.jpg)
我们再看一个例子:
```python
import numpy as np
arr1 = np.array([[0, 0, 0],[1, 1, 1],[2, 2, 2], [3, 3, 3]]) #arr1.shape = (4,3)
arr2 = np.array([[1],[2],[3],[4]]) #arr2.shape = (4, 1)
arr_sum = arr1 + arr2
print(arr_sum)
'''
输出结果如下:
[[1 1 1]
[3 3 3]
[5 5 5]
[7 7 7]]
'''
```
`arr1`的`shape`为`(4,3)``arr2`的`shape`为`(4,1)`,它们都是二维的,但是第二个数组在`1`轴上的长度为`1`,所以,可以在`1`轴上面进行广播。
![7](7.jpg)

@ -0,0 +1,117 @@
# 2.1.4:线性代数
## numpy的线性代数
线性代数(如矩阵乘法、矩阵分解、行列式以及其他方阵数学等)是任何数组库的重要组成部分,一般我们使用`*`对两个二维数组相乘得到的是一个元素级的积,而不是一个矩阵点积。因此`numpy`提供了线性代数函数库`linalg`,该库包含了线性代数所需的所有功能。
常用的`numpy.linalg`函数:
| 函数 | 说明 |
| :------------: | :------------: |
| dot |矩阵乘法 |
| vdot | 两个向量的点积 |
| det |计算矩阵的行列式 |
|inv |计算方阵的逆 |
|svd |计算奇异值分解(SVD) |
|solve |解线性方程组Ax=bA是一个方阵 |
|matmul|两个数组的矩阵积|
## 常用函数
**dot()**
该函数返回俩个数组的点积。对于二维向量,效果等于矩阵乘法。对于一维数组,它是向量的内积。对于`N`维数组,它是`a`的最后一个轴上的和与`b`的倒数第二个轴的乘积。
```python
a=np.array([[1,2],[3,4]])
a1=np.array([[5,6],[7,8]])
np.dot(a,a1)
'''
输出array([[19, 22],
[43, 50]])
'''
```
**det()**
该函数用于计算输入矩阵的行列式。
```python
a = np.array([[14, 1], [6, 2]])
a=linalg.det(a)
print(a)
'''
输出21.999999999999996
'''
```
**inv()**
该函数用于计算方阵的逆矩阵。逆矩阵的定义维如果两个方阵A、B使得AB=BA=E则A称为可逆矩阵B为A的逆矩阵E为单位矩阵。
```python
a=np.array([[1,2],[3,4]])
b=linalg.inv(a)
print(np.dot(a,b))
'''
输出array([[1.0000000e+00, 0.0000000e+00],
[8.8817842e-16, 1.0000000e+00]])
'''
```
**solve()**
该函数用于计算线性方程的解。假设有如下方程组:
`3x+2y=7 x+4y=14`
写成矩阵的形式:
`[[3,2][1,4]]`\*`[[x],[y]]`=`[[7],[14]]`
解如上方程组代码如下:
```python
a=np.array([[3,2], [1,4]])
b=np.array([[7],[14]])
linalg.solve(a,b)
'''
输出:array([[0. ],
[3.5]])
最后解出x=0y=3.5
'''
```
**matmul()**
函数返回两个数组的矩阵乘积。如果参数中有一维数组,则通过在其维度上附加`1`来提升为矩阵,并在乘法之后去除。
```python
a=[[3,4],[5,6]]
b=[[7,8],[9,10]]
np.matmul(a,b)
'''
输出array([[ 57, 64],
[ 89, 100]])
'''
b=[7,8]
np.matmul(a,b)
'''
输出array([53, 83])
'''
```
**svd()**
奇异值分解是一种矩阵分解的方法,该函数用来求解`SVD`。
```python
a=[[0,1],[1,1],[1,0]]
linalg.svd(a)
'''
输出:(array([[-4.08248290e-01, 7.07106781e-01, 5.77350269e-01],
[-8.16496581e-01, 2.64811510e-17, -5.77350269e-01],
[-4.08248290e-01, -7.07106781e-01, 5.77350269e-01]]), array([1.73205081, 1. ]), array([[-0.70710678, -0.70710678],
[-0.70710678, 0.70710678]]))
'''
```

@ -0,0 +1,105 @@
# 2.1.5:排序和条件筛选
## numpy中的快速排序
`numpy`中的排序相对 Python 的更加高效,默认情况下`np.sort`的排序算法是 **快速排序**,也可以选择 **归并排序** 和 **堆排序**。
| 类型 |速度 |最坏情况 |工作空间 |稳定性 |
| :------------: | :------------: | :------------: | :------------: | :------------: |
| 快速排序 |`1` | `O(n^2)` | `0` |否 |
| 归并排序 |`2` |`O(n*log(n))` |`~n/2` |是 |
| 堆排序 |`3`|`O(n*log(n))`|`0`|否|
- `np.sort()`函数返回排序后的数组副本,**只能是升序**。
```python
a=np.array([5,9,1,15,3,10])
np.sort(a) #升序排序
'''
输出array([ 1, 3, 5, 9, 10, 15])
'''
```
- `np.argsort()`函数返回排序后数组值从小到大的索引,可以通过这些索引值创建**有序**的**数组**。
```python
a=np.array([4,5,9,1,3])
b=np.argsort(a) #对a使用argsort函数
print(b)
'''
输出array([3, 4, 0, 1, 2], dtype=int64)
第一个数是最小的值的索引,第二个数是第二小的值的索引,以此类推
'''
b1=[]
for i in b: #循环获取索引对应的值
b1.append(a[i])
print(b1)
'''
输出:[1, 3, 4, 5, 9]
'''
```
- 沿行或列进行排序,通过`axis`参数实现对数组的行、列进行排序,这种处理是将行或列当作独立的数组,任何行或列的值之间的关系将会丢失。
```python
a=np.array([[8,1,5,9],[5,4,9,6],[7,1,5,3]])
np.sort(a,axis=1) #沿行排序
'''
输出array([[1, 5, 8, 9],
[4, 5, 6, 9],
[1, 3, 5, 7]])
'''
np.sort(a,axis=0) #沿列排序
'''
输出:array([[5, 1, 5, 3],
[7, 1, 5, 6],
[8, 4, 9, 9]])
'''
```
- `np.partition()`函数为给定一个数,对数组进行分区,区间中的元素任意排序。
```python
a=np.array([8,9,2,3,1,6,4])
np.partition(a,5) #比5小的在左边比5大的在右边
'''
输出:array([1, 3, 2, 4, 6, 8, 9])
'''
```
其他排序函数:
| 函数 |描述 |
| :------------: | :------------: |
| `msort()` |数组按第一个轴排序,返回排序后的数组副本 |
| `sort_complex()` |对复数按先实部后虚部的顺序进行排序 |
| `argpartition()` |通过关键字指定算法沿指定轴进行分区|
## where函数
- `np.where()` 函数返回输入数组中满足给定条件的元素的索引,可以利用该函数进行 **条件筛选**。
```python
a=np.array([19,5,16,22,17])
np.where(a>15) #应用where函数
'''
输出:(array([0, 2, 3, 4], dtype=int64),)
'''
a[np.where(a>15)] #获取满足条件索引的元素
'''
输出:array([19, 16, 22, 17])
'''
```

@ -0,0 +1,94 @@
# 2.1.6:结构化数组
## 结构化数组
结构化数组其实就是`ndarrays`,其数据类型是由组成一系列命名字段的简单数据类型组成的,在定义结构化数据时需要指定数据类型。
构建结构化数组的数据类型有多种制定方式,如字典、元组列表:
```python
a=np.array([(b'Rex', 9, 81.), (b'Fido', 7, 77.)],
dtype=[('name', '<S10'), ('age', '<i4'), ('score', '<f4')]) #
a=np.array([(b'Rex', 9, 81.), (b'Fido', 7, 77.)],dtype={'names':("name","age","score"),"formats":("<S10","<i4","<f4")}) #
print(a)
'''
两种方式结果都是一样的
输出:array([(b'Rex', 9, 81.), (b'Fido', 7, 77.)],
dtype=[('name', '<S10'), ('age', '<i4'), ('score', '<f4')])
'''
```
上面案例中的`S10`表示“长度不超过`10`的字符串”,`i4`表示“`4`个字节整型”,`f4`表示“`4`字节浮点型”。
`numpy`的数据类型如下:
| 数据类型 |描述 |示例 |
| :------------: | :------------: | :------------: |
|`b` |字节型 |`np.dtype('b')` |
|`i` |有符号整型 |`np.dtype('i4')==np.int32` |
|`u` |无符号整型 |`np.dtype('u')==np.uint8` |
|`f` |浮点型 |`np.dtype('f8')==np.int64` |
|`c` |复数浮点型 |`np.dtype('c16')==np.complex128` |
|`S`、`a` |字符串 |`np.dtype('SS')` |
|`U` |`Unicode`字符串 |`np.dtype('U')==np.str\_` |
| `V` |原生数据 |`np.dtype('V')==np.void` |
## 通过文件构造结构化数组
`numpy`通过`loadtxt()`函数读取文件内容,假设有以下文件内容,需要读取文件构造结构化数组:
| name |age |score |
| :------------: | :------------: | :------------: |
|amy |11 |70 |
|kasa |10 | 80|
| baron | 9 | 66|
```python
a=np.loadtxt("data.txt",skiprows=1,dtype=[("name","S10"),("age","int"),("score","float")]) #读取文件并设置数据类型其中skiprows为跳过第一行
print(a)
'''
输出:[(b'amy', 11, 70.) (b'kasa', 10, 80.) (b'baron', 9, 66.)]
'''
```
## 结构化数组常规操作
结构化数组的方便之处在于,你可以通过**索引**或**名称**查看相应的值,并且可以进行快速的数据处理。
```python
a=np.array([(b'Rex', 10, 81.), (b'Fido', 7, 77.),(b'kasr', 9, 55.)],
dtype=[('name', 'S10'), ('age', '<i4'), ('score', '<f4')]) #
print(a[0]) #查看第一行
'''
输出:(b'Rex', 10, 81.)
'''
print(a["age"]>=10) #查看是否有大于等于10岁的
'''
输出:[True False False]
'''
print(a["score"]<60) #60
'''
输出:[False False True]
'''
print(a[a["age"]>=10]) #查看大于等于10岁的信息
'''
输出:[(b'Rex', 10, 81.)]
'''
print(a[a["score"]<60]["name"]) #60
'''
输出:[b'kasr']
'''
```
注意:尽管这里列举的模式对于简单的操作非常有用,但是这些操作场景也可以用`pandas`的`DataFrame`来实现,并且`DataFrame`更加强大。

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

@ -0,0 +1,7 @@
# Chapter2 Numpy
NumPy 是 Python 语言的一个扩充程序库。支持高级大量的维度数组与矩阵运算此外也针对数组运算提供大量的数学函数库。Numpy 内部解除了 Python 的 PIL(全局解释器锁),运算效率极好,是大量机器学习框架的基础库!
Numpy 相关实训已在`educoder`平台上提供,若感兴趣可以输入链接进行体验。
链接https://www.educoder.net/paths/302

@ -0,0 +1,56 @@
# 3.1.1Series对象
## 创建Series对象
创建`Pandas`的`Series`对象的方法:
> pd.Series(data,index=index)
其中,`index`是一个可选参数,默认为`np.arange(n)``data`参数支持多种数据类型。
## Series是通用的Numpy数组
`Series`对象和一维`Numpy`数组基本可以等价交换,但两者间的本质差异其实是索引:`NumPy`数组通过**隐式定义**的整数索引获取数值,而`Pandas`的`Series`对象用一种**显式定义**的索引与数值关联。
显式索引的定义让`Series`对象拥有了更强的能力。例如,索引不再仅仅是整数,还可以是任意想要的类型。
```python
In: data = pd.Series([0.25, 0.5, 0.75, 1.0], index=['a', 'b', 'c', 'd'])
Out: a 0.25
b 0.50
c 0.75
d 1.00
dtype: float64
In: data["b"]
Out: 0.5
```
## Series是特殊的字典
你可以把`Pandas`的`Series`对象看成一种特殊的`Python`字典。字典是一种将任意键映射到一组任意值的数据结构,而`Series`对象其实是一种将类型键映射到一组类型值的数据结构。我们可以直接用`Python`的字典创建一个`Series`对象,让`Series`对象与字典的类比更加清晰。
```python
In: population_dict = {'California': 38332521,'Texas': 26448193, 'New York': 19651127, 'Florida': 19552860, 'Illinois': 12882135}
In: population = pd.Series(population_dict)
In: population
Out: California 38332521
Florida 19552860
Illinois 12882135
New York 19651127
Texas 26448193
dtype: int64
```
用字典创建 `Series`对象时,其索引默认按照顺序排列。典型的字典数值获取方式仍然有效,而且还支持数组形式的切片操作等。
```python
In: population['California']
Out: 38332521
In: population['California':'Illinois']
Out: California 38332521
Florida 19552860
Illinois 12882135
dtype: int64
```

@ -0,0 +1,61 @@
# 3.1.2DateFrame对象
## 创建DataFrame对象
`Pandas`的`DataFrame`对象可以通过许多方式创建,这里举几个常用的例子。
- 通过数组创建
>pd.DateFrame(array, index=list0, columns=list1) #list表示一个列表
- 通过单个Series对象创建
>pd.DateFrame(Series,columns=list)
- 通过字典列表创建
>data = [{'a': i, 'b': 2 * i} for i in range(3)]
pd.DataFrame(data)
## DataFrame是通用的NumPy数组
如果将`Series`类比为带灵活索引的一维数组,那么`DataFrame`就可以看作是一种既有灵活的行索引,又有灵活列名的二维数组,你也可以把`DataFrame`看成是有序排列的若干`Series`对象。这里的“排列”指的是它们拥有共同的索引`index`。
```python
# 创建Series对象
In: area_dict = {'California': 423967, 'Texas': 695662, 'New York': 141297, 'Florida': 170312, 'Illinois': 149995}
In: population_dict = {'California': 38332521,'Texas': 26448193, 'New York': 19651127, 'Florida': 19552860, 'Illinois': 12882135}
In: population = pd.Series(population_dict)
In: area = pd.Series(area_dict)
# 创建DataFrame对象
In: states = pd.DataFrame({'population': population, 'area': area})
In: states
Out: area population
California 423967 38332521
Florida 170312 19552860
Illinois 149995 12882135
New York 141297 19651127
Texas 695662 26448193
```
和`Series`对象一样,`DataFrame`也有一个`index`属性可以获取索引标签。另外,`DataFrame`还有一个`columns`属性,是存放列标签的`Index`对象。
```python
In: states.columns
Out: Index(['area', 'population'], dtype='object')
```
## DataFrame是特殊的字典
与`Series`类似,我们也可以把`DataFrame`看成一种特殊的字典。字典是一个键映射一个值,而`DataFrame`是一列映射一个`Series`的数据。例如,通过`area`的列属性可以返回包含面积数据的`Series`对象。
```python
In: states['area']
Out: California 423967
Florida 170312
Illinois 149995
New York 141297
Texas 695662
Name: area, dtype: int64
```
*注意:在 `NumPy` 的二维数组里,`data[0]` 返回第一行;而在 `DataFrame` 中,`data['列名']`返回与列名相匹配的那一列。*

@ -0,0 +1,41 @@
# 3.1.3Index对象
## 将Index看作不可变数组
`Index`对象得许多操作都像数组。可以通过标准`Python`的取值方法获取数值,也可以通过切片获取数值。
```python
In: ind[1]
Out: 3
In: ind[::2]
Out: Int64Index([2, 5, 11], dtype='int64')
```
`Index`对象还有许多与`NumPy`数组相似的属性。
```python
In: print(ind.size, ind.shape, ind.ndim, ind.dtype)
Out: 5 (5,) 1 int64
```
`Index`对象与`NumPy`数组之间的不同在于,`Index`对象的索引是不可变的,也就是说不能通过通常的方式进行调整。
>ind[1] = 0 # 这种操作是不可取的,会报错
## 将Index看作有序集合
`Pandas`对象被设计用于实现许多操作,如连接(`join`)数据集,其中会涉及许多集合操作。`Index `对象遵循`Python`标准库的集合(`set`)数据结构的许多习惯用法,包括并集、交集、差集等。
```python
In: indA = pd.Index([1, 3, 5, 7, 9])
In: indB = pd.Index([2, 3, 5, 7, 11])
In: indA & indB # 交集
Out: Int64Index([3, 5, 7], dtype='int64')
In: indA | indB # 并集
Out: Int64Index([1, 2, 3, 5, 7, 9, 11], dtype='int64')
In: indA ^ indB # 异或
Out: Int64Index([1, 2, 9, 11], dtype='int64')
```
这些操作还可以通过调用对象方法来实现,例如 `indA.intersection(indB)`等。

@ -0,0 +1,117 @@
# 3.1.4Series数据选择
## Series数据选择方法
与`Python`中的字典一样,`Series`对象提供了键值对的映射。
```python
In: import pandas as pd
In: data = pd.Series([0.25, 0.5, 0.75, 1.0], index=['a', 'b', 'c', 'd'])
In: data["b"]
Out: 0.5
```
还可以用`Python`字典的表达式和方法来检测键/索引和值,也可以像字典一样来修改`Series`对象的值。
```python
In: "a" in data
Out: True
In: data.keys()
Out: Index(['a', 'b', 'c', 'd'], dtype='object')
In: list(data.items())
Out: [('a', 0.25), ('b', 0.5), ('c', 0.75), ('d', 1.0)]
In: data["b"] = 0.05 # 也可以通过此方法来扩展Series
In: data
Out: a 0.25
b 0.05
c 0.75
d 1.00
dtype: float64
```
`Series`对象的可变性是一个非常方便的特性:`Pandas`在底层已经为可能发生的内存布局和数据复制自动决策,用户不需要担心这些问题。
## 将Series看作一堆数组
`Series`对象还具备和`Numpy`数组一样的数组数据选择功能,包括**索引、掩码、花哨索引**等操作,具体示例如下所示:
- 将显示索引作为切片
*(注意显示索引切片结果包含最后一个索引也就是能取到“c”的值)*
```python
In: data['a':'c']
Out: a 0.25
b 0.50
c 0.75
dtype: float64
```
- 将隐式整数索引作为切片
*(注意:隐式索引切片结果不包含最后一个索引)*
```python
In: data[0:2]
Out: a 0.25
b 0.50
dtype: float64
```
- 掩码
```python
In: data[(data > 0.3) & (data < 0.8)]
Out: b 0.50
c 0.75
dtype: float64
```
- 花哨的索引
```python
In: data[["a","e"]]
Out: a 0.25
e 1.25
dtype: float64
```
## 索引器loc和iloc
这些切片和取值操作非常混乱,假如`Series`对象索引序列为整数时,`data[2]`不会取第三行,而是取索引序列为`2`的那一行,也就是说会优先使用显示索引,而`data[1:3]`这样的切片操作会优先使用隐式索引。
```python
In: data = pd.Series(['a', 'b', 'c'], index=[1, 3, 5])
In: data[1]
Out: "a"
In: data[0:2]
Out: 1 a
3 b
dtype: object
```
正是应为这些整数索引很容易造成混淆,所以`Pandas`提供了一些**索引器(indexer)**属性来作为取值的方法。它们不是`Series`对象的函数方法,而是暴露切片接口的属性。
- `loc`属性:表示取值和切片都是显示的
```python
In: data.loc[1]
Out: "a"
In: data.loc[1:3]
Out: 1 a
3 b
dtype: object
```
- `iloc`属性:表示取值和切片都是`Python`形式的隐式索引
-
```python
In: data.iloc[1]
Out: "b"
In: data.iloc[1:3]
Out: 3 b
5 c
dtype: object
```
`Python`代码的设计原则之一式“显示优于隐式”。使用`loc`和`iloc` 可以让代码更容易维护,可读性更高。特别是在处理整数索引的对象时,我强烈推荐使用这两种索引器。它们既可以让代码阅读和理解起来更容易,也能避免因误用索引或者切片而产生的小`bug`。

@ -0,0 +1,98 @@
# 3.1.5DataFrame数据选择方法
## 将DataFrame看作字典
`DataFrame`可以看作一个由若干`Series`对象构成的字典,可以通过对列名进行字典形式的取值获取数据。
```python
In: area = pd.Series({'California': 423967, 'Texas': 695662, 'New York': 141297, 'Florida': 170312, 'Illinois': 149995})
In: pop = pd.Series({'California': 38332521, 'Texas': 26448193, 'New York': 19651127, 'Florida': 19552860, 'Illinois': 12882135})
In: data = pd.DataFrame({'area':area, 'pop':pop})
In: data["area"] # data.area 这种属性形式也可以获取到相同的结果
Out: California 423967
Florida 170312
Illinois 149995
New York 141297
Texas 695662
Name: area, dtype: int64
```
虽然属性形式的数据选择方法很方便,但是它并不是通用的。**如果列名不是纯字符串,或者列名与`DataFrame`的方法同名,那么就不能用属性索引**。例如,`DataFrame`有一个`pop()`方法,如果用`data.pop`就不会获取`'pop'`列,而是显示为方法。
```python
In: data.pop is data['pop']
Out: False
```
**所以,尽量避免用属性形式选择的列直接赋值,即避免`data.pop=z`这种方式赋值。**
和`Series`对象一样,可以用字典形式的语法调整对象,例如增加一列数据。
```python
In: data["density"] = data['pop']/data['area']
In: data
Out: area pop density
California 423967 38332521 90.413926
Florida 170312 19552860 114.806121
Illinois 149995 12882135 85.883763
New York 141297 19651127 139.076746
Texas 695662 26448193 38.018740
```
## 将DataFrame看作二维数组
`DataFrame`可以看成是一个增强版的二维数组,许多数组操作方式都可以用在`DataFrame`对象上,例如,用`value`属性按行查看数组数据,对`DataFrame`进行转置等等。
```python
In: data.value
Out:
array(
[[ 4.23967000e+05, 3.83325210e+07, 9.04139261e+01],
[ 1.70312000e+05, 1.95528600e+07, 1.14806121e+02],
[ 1.49995000e+05, 1.28821350e+07, 8.58837628e+01],
[ 1.41297000e+05, 1.96511270e+07, 1.39076746e+02],
[ 6.95662000e+05, 2.64481930e+07, 3.80187404e+01]])
In: data.T
Out:
```
![](/api/attachments/367560)
---
将`DataFrame`看作数组时,我们可以是使用单个行索引获取一行数据。
```python
In: data.values[0] #取一行数据
Out: array([ 4.23967000e+05, 3.83325210e+07, 9.04139261e+01])
```
而获取一列数据就需要向`DataFrame`传递单个列索引,与字典形式的操作相同,都是用`data["列名"]`获取。
---
因此,在进行数组形式的取值时,我们就需要用另一种方法——前面介绍过的`Pandas`索引器`loc、iloc`和`ix`了。通过`iloc`索引器,我们就可以像对待`NumPy`数组一样索引`Pandas`的底层数组(`Python`的隐式索引),`DataFrame`的行列标签会自动保留在结果中。
```python
In: data.iloc[1:3, :2] # 隐式
Out: area pop
Florida 170312 19552860
Illinois 149995 12882135
In: data.loc[:'Illinois', :'pop'] #显式
Out: area pop
California 423967 38332521
Florida 170312 19552860
Illinois 149995 12882135
```
使用`ix`索引器可以实现一种**混合效果**`Series`中也可以使用这种索引器,但是作用不明显。需要注意的是,`ix`索引器处理整数索引时和`Series`对象一样容易让人混淆。
```python
In: data.ix[:3, :"pop"]
Out: area pop
California 423967 38332521
Florida 170312 19552860
Illinois 149995 12882135
```
任何用于处理`NumPy`形式数据的方法都可以用于这些索引器,例如在`loc`索引器中结合使用掩码与花哨索引方法。
```python
In: data.loc[data.density > 100, ['pop', 'density']]
Out: pop density
Florida 19552860 114.806121
New York 19651127 139.076746
```
## 其他取值方法
还有一些取值方法和前面介绍过的方法不太一样,但是在实际应用中非常实用。
data["列名"]
data["A列":"B列"]
data[0:3] # 取第一到第四行
data[data.density > 100] #取density列值大于100的行

@ -0,0 +1,126 @@
# 3.1.6:数值运算方法
## 通用函数:保留索引
因为`Pandas`是建立在`NumPy`基础之上的,所以`NumPy`的通用函数同样适用于`Pandas`的`Series`和`DataFrame`对象。
```Python
import numpy as np
import pandas as pd
rng = np.random.RandomState(42) #创建随机数种子
ser = pd.Series(rng.randint(0,10,4))
df = pd.DataFrame(rng.randint(0,10,(3,4)), columns=['A','B','C','D'])
# 对Series对象使用Numpy通用函数生成的结果是另一个保留索引的Pands对象
print(np.exp(ser))
Out0 403.428793
1 20.085537
2 1096.633158
3 54.598150
dtype: float64
# 对DataFrame使用Numpy通用函数
print(np.sin(df*np.pi/4))
Out:
A B C D
0 -1.000000 7.071068e-01 1.000000 -1.000000e+00
1 -0.707107 1.224647e-16 0.707107 -7.071068e-01
2 -0.707107 1.000000e+00 -0.707107 1.224647e-16
```
## 通用函数:索引对齐
### Series索引对齐
假如你要整合两个数据源的数据,其中一个是美国面积最大的三个州的面积数据,另一个是美国人口最多的三个州的人口数据:
```python
# 面积
area=pd.Series({'Alaska':1723337,'Texas':695662,'California':423967},name='area')
# 人口
population=pd.Series({'California':38332521,'Texas':26448193,'New York': 19651127}, name='population'})
```
人口除以面积的结果:
```python
print(population/area)
Out:
Alaska NaN
California 90.413926
New York NaN
Texas 38.018740
dtype: float64
```
对于缺失的数据,`Pandas`会用`NaN`填充,表示空值。这是`Pandas`表示缺失值的方法(后面的关卡会介绍)。这种索引对齐方式是通过`Python`内置的集合运算规则实现的,任何缺失值默认都用`NaN`填充。
### DataFrame索引对齐
在计算两个`DataFrame`时,类似的索引对齐规则也同样会出现在共同列中:
```python
A = pd.DataFrame(rng.randint(0, 20, (2, 2)), columns=list('AB'))
"""
A:
A B
0 1 11
1 5 1
"""
B = pd.DataFrame(rng.randint(0, 10, (3, 3)), columns=list('BAC'))
"""
B:
B A C
0 4 0 9
1 5 8 0
2 9 2 6
"""
print(A + B)
Out:: A B C
0 1.0 15.0 NaN
1 13.0 6.0 NaN
2 NaN NaN NaN
```
从上面的例子可以发现,两个对象的行列索引可以是不同顺序的,结果的索引会自动按顺序排列。在`Series`中,我们可以通过运算符方法的` fill_value`参数自定义缺失值;这里我们将用`A`中所有值的均值来填充缺失值。
```python
fill = A.stack().mean() # stack()能将二维数组压缩成多个一维数组
print(A.add(B,fill_value=fill))
Out:
A B C
0 1.0 15.0 13.5 #NaN值都变成了均值
1 13.0 6.0 4.5
2 6.5 13.5 10.5
```
下表中列举了与`Python`运算符相对应的`Pandas`对象方法。
|Python运算符|Pandas方法
|--|--|
|+|add()
|-|sub()、substract()
|\*|mul()、multiply()
|/|truediv()、div()、divide()
|//|floordiv()
|%|mod()
|\*\*|pow()
## 通用函数DataFrame与Series的运算
`DataFrame`和`Series`的运算规则与`Numpy`中二维数组与一维数组的运算规则是一样的。来看一个常见运算,让一个二维数组减去自身的一行数据。
```python
A = rng.randint(10, size=(3, 4))
A - A[0]
Out:
array([[ 0, 0, 0, 0],
[-1, -2, 2, 4],
[ 3, -7, 1, 4]]) # 根据Numpy的广播规则默认是按行运算的
```
在`Pandas`里默认也是按行运算的,如果想按列计算,那么就需要利用前面介绍过的运算符方法,通过设置`axis(轴)`实现
```python
df = pd.DataFrame(A, columns=list('QRST'))
print(df - df.iloc[0])
Out:
Q R S T
0 0 0 0 0
1 -1 -2 2 4
2 3 -7 1 4
print(df.subtract(df['R'],axis=0))
Out:
Q R S T
0 -5 0 -6 -4
1 -4 0 -2 2
2 5 0 2 7
```
`DataFrame/Series`的运算与前面介绍的运算一样,结果的索引都会自动对齐。

@ -0,0 +1,170 @@
# 3.1.6:数值运算与缺失值处理
## 选择处理缺失值的方法
一般情况下可以分为两种:一种方法是通过一个覆盖全局的**掩码**表示缺失值,另一种方法是用一个**标签值`sentinel value`**表示缺失值。
- 掩码方法中掩码可能是一个与原数组维度相同的完整布尔类型数组,也可能是用一个比特(`0`或`1`)表示有缺失值的局部状态。
- 标签方法中,标签值可能是具体的数据(例如用`-9999`表示缺失的整数),也可能是些极少出现的形式。
## Pandas缺失值
综合考虑各种方法的优缺点,`Pandas`最终选择用标签方法表示缺失值,包括两种`Python`原有的缺失值:浮点数据类型的`NaN`值,以及 `Python`的`None`对象。
- **`None``Python`对象类型的缺失值**
`Pandas`可以使用的第一种缺失值标签是`None`,它是一个`Python`单体对象,由于`None`是一个`Python`对象,所以不能作为任何`NumPy / Pandas`数组类型的缺失值,只能用于`'object'`数组类型(即由` Python`对象构成的数组)
```python
np.array([1, None, 3, 4])
Out: array([1, None, 3, 4], dtype=object)
```
- **`NaN`:数值类型的缺失值**
另一种缺失值的标签是`NaN`(全称`Not a Number`),是一种按照`IEEE`浮点数标准设计、在任何系统中都兼容的特殊浮点数:
```python
vals2 = np.array([1, np.nan, 3, 4])
vals2.dtype
Out: dtype('float64')
```
**注意:**`NumPy`会为这个数组选择一个原生浮点类型,这意味着和之前的 `object`类型数组不同,这个数组会被编译成`C`代码从而实现快速操作。你可以把`NaN`看作是一个数据类病毒——它会将与它接触过的数据同化。**无论和`NaN`进行何种操作,最终结果都是`NaN`**
```python
1 + np.nan
0 * np.nan #这两个的结果都为nan
```
虽然这些累计操作的结果定义是合理的(即不会抛出异常),但是并非总是有效的:
```python
vals2 = np.array([1, np.nan, 3, 4])
vals2.sum(), vals2.min(), vals2.max()
Out:(nan, nan, nan)
```
`NumPy`也提供了一些特殊的累计函数,它们可以忽略缺失值的影响:
```python
np.nansum(vals2), np.nanmin(vals2), np.nanmax(vals2)
Out: (8.0, 1.0, 4.0)
```
*谨记,`NaN`是一种特殊的**浮点数**,不是整数、字符串以及其他数据类型。*
- **`Pandas`中`NaN`与`None`的差异**
虽然`NaN`与`None`各有各的用处,但是`Pandas`把它们看成是可以等价交换的:
```python
pd.Series([1, np.nan, 2, None])
Out:
0 1.0
1 NaN
2 2.0
3 NaN
dtype: float64
```
`Pandas`会将没有标签值的数据类型自动转换为`NA`。例如我们将整形数组中的一个值设置为`np.nan`时,这个值就会强制转换成浮点数缺失值`NA`,下表表示`Pandas`对不同类型缺失值的转换规则:
|类型|缺失值转换规则|NA标签值
|--|--|--|
|floating 浮点型|无变化|np.nan
|object 对象类型|无变化|np.nan或None
|integer 整数类型|强制转换为 float64|np.nan
|boolean 布尔类型|强制转换为 object|np.nan或None
## 发现缺失值
`Pandas`有两种方法可以发现缺失值:`isnull()`和`notnull()`,这俩个中方法皆可用于`Series`和`DataFrame`。每种方法都返回布尔类型的掩码数据。
```python
data=pd.Series([1,np.nan,'hello',None])
data.isnull()
Out:
0 False
1 True
2 False
3 True
dtype: bool
```
布尔类型掩码数组可以直接作为`Series`或`DataFrame`的索引使用。
```python
data[data.notnull()]
Out:
0 1
2 hello
dtype: object
```
## 处理缺失值
- dropna()删除缺失值
作用在`Series`对象上时,它的作用和`data[data.notnull()]`一样,而在`DataFrame`上使用它们时需要设置一些参数:
```python
df = pd.DataFrame([[1, np.nan, 2],
[2, 3, 5],
[np.nan, 4, 6]])
# 如果不传任何参数时dropna会删除有缺失值的所有行
df.dropna()
Out:
0 1 2
1 2.0 3.0 5
# 传入axis=1(或者axis='columns')时会删除所有包含缺失值的列
df.dropna(axis=1)
Out:
2
0 2
1 5
2 6
```
但是这么做也会把**非缺失值一并剔除**,因为可能有时候只需要剔除全部是缺失值的行或列,或者绝大多数是缺失值的行或列。这些需求可以通过设置`how`或`thresh`参数来满足,它们可以设置剔除行或列缺失值的数量阈值。
```python
df[3] = np.nan
Out:
0 1 2 3
0 1.0 NaN 2 NaN
1 2.0 3.0 5 NaN
2 NaN 4.0 6 NaN
# 删除值全部为缺失值的列
df.dropna(axis=1,how="all")
Out
0 1 2
0 1.0 NaN 2
1 2.0 3.0 5
2 NaN 4.0 6
#通过 thresh 参数设置行或列中非缺失值的最小数量
df.dropna(axis='rows', thresh=3) #非缺失值至少有3个
Out:
0 1 2 3
1 2.0 3.0 5 NaN
```
- fillna()填充缺失值
有时候你可能并不想移除缺失值,而是想把它们替换成有效的数值。虽然你可以通过`isnull()`方法建立掩码来填充缺失值,但是`Pandas`为此专门提供了一个`fillna()`方法,它将返回填充了缺失值后的数组副本。
```python
data=pd.Series([1, np.nan, 2, None, 3],index=list('abcd')
data.fillna(0) # 将缺失值填充为0
Out
a 1.0
b 0.0
c 2.0
d 0.0
e 3.0
dtype: float64
```
可以用缺失值前面的有效值来从前往后填充`forward-fill`,也可以用缺失值后面的有效值来从后往前填充`back-fill`
```python
data.fillna(method="ffill")
Out
a 1.0
b 1.0
c 2.0
d 2.0
e 3.0
dtype: float64
data.fillna(method='bfill')
Out
a 1.0
b 2.0
c 2.0
d 3.0
e 3.0
dtype: float64
```
`DataFrame`的操作方法与`Series`类似,只是在填充时需要设置坐标轴参数`axis`。
```python
df.fillna(method='ffill', axis=1) # bfill同样适用
Out
0 1 2 3
0 1.0 1.0 2.0 2.0
1 2.0 3.0 5.0 5.0
2 NaN 4.0 6.0 6.0
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

@ -0,0 +1,194 @@
# 3.2.1:多级索引的取值与切片
## 创建多级索引
1. 通过`MultiIndex`构建多级索引
```python
index = [('California', 2000), ('California', 2010), ('New York', 2000), ('New York', 2010), ('Texas', 2000), ('Texas', 2010)]
populations = [33871648, 37253956, 18976457, 19378102, 20851820, 25145561]
pop = pd.Series(populations, index=index)
# 1.基于元组创建
index1 = pd.MultiIndex.from_tuples(index)
index1
Out
MultiIndex(levels=[['California', 'New York', 'Texas'], [2000, 2010]], codes=[[0, 0, 1, 1, 2, 2], [0, 1, 0, 1, 0, 1]])
```
`MultiIndex`里面有一个`levels`属性表示索引的等级——这样做可以将州名和年份作为每个数据点的不同标签。如果将前面创建的`pop`的索引重置`reindex`为`MultiIndex`,就会看到层级索引。其中前两列表示`Series`的多级索引值,第三列式数据。
```python
pop1 = pop.reindex(index1)
pop1
Out
California 2000 33871648
2010 37253956
New York 2000 18976457
2010 19378102
Texas 2000 20851820
2010 25145561
dtype: int64
```
查询`2010`年的数据。
```python
pop[:, 2010] # 得到的是一个单索引数组
Out
California 37253956
New York 19378102
Texas 25145561
dtype: int64
```
以上的例子都是`Series`创建多级行索引,而每个`DataFrame`的行与列都是对称的,也就是说既然有多级行索引,那么同样可以有多级列索引。只需要在创建`DataFrame`时将`columns`的参数传入一个`MultiIndex`。
2. 通过二维索引数组创建多级索引
`Series`或`DataFrame`创建多级索引最直接的办法就是将`index`参数设置为至少二维的索引数组。
```python
df = pd.DataFrame(np.random.rand(4, 2), index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]], columns=['data1', 'data2'])
df
Out:
data1 data2
a 1 0.554233 0.356072
2 0.925244 0.219474
b 1 0.441759 0.610054
2 0.171495 0.886688
```
`MultiIndex`的创建工作将在后台完成。同理,如果你把将元组作为键的字典传递给`Pandas``Pandas`也会默认转换为`MultiIndex`。
3. 显示的创建多级索引
你可以用`pd.MultiIndex`中的类方法更加灵活地构建多级索引。
```python
# 有不同等级的若干简单数组组成的列表来构建
pd.MultiIndex.from_arrays([['a', 'a', 'b', 'b'], [1, 2, 1, 2]])
# 包含多个索引值的元组构成的列表创建
pd.MultiIndex.from_tuples([('a', 1), ('a', 2), ('b', 1), ('b', 2)])
# 由两个索引的笛卡尔积Cartesian product创建
pd.MultiIndex.from_product([['a', 'b'], [1, 2]])
# 三种创建方法的结果都一致
Out:
MultiIndex(levels=[['a', 'b'], [1, 2]],
codes=[[0, 0, 1, 1], [0, 1, 0, 1]])
```
*在创建`Series`或`DataFrame`时,可以将这些对象作为`index`参数,或者通过`reindex`方法更新`Series`或`DataFrame`的索引。*
4. 多级索引的等级名称
你可以在前面任何一个`MultiIndex`构造器中通过`names`参数设置等级名称,也可以在创建之后通过索引的`names`属性来修改名称。
```python
pop.index.names = ['state', 'year']
Out:
state year
California 2000 33871648
2010 37253956
New York 2000 18976457
2010 19378102
Texas 2000 20851820
2010 25145561
dtype: int64
```
5. 多级列索引
每个`DataFrame`的行与列都是对称的,也就是说既然有多级行索引,那么同样可以有多级列索引。
6. 多级索引与`DataFrame`的相互转换
`unstack()`方法可以快速将一个多级索引的`Series`转化为普通索引的`DataFrame`。
```python
pop.unstack() # stack()可以将DataFrame转换为多级索引
Out
2000 2010
California 33871648 37253956
New York 18976457 19378102
Texas 20851820 25145561
```
## 多级索引的取值与切片
1. `Series`多级索引
以各州历年人口数量创建的多级索引`Series`为例:
```python
pop
Out:
state year
California 2000 33871648
2010 37253956
New York 2000 18976457
2010 19378102
Texas 2000 20851820
2010 25145561
dtype: int64
```
获取单个元素:
```python
pop['California',2000]
Out
33871648
```
`MultiIndex`也支持局部取值`partial indexing`,即只取索引的某一个层级。假如只取最高级的索引,获得的结果是一个新的`Series`,未被选中的低层索引值会被保留:
```python
pop['California']
Out
year
2000 33871648
2010 37253956
dtype: int640
```
2. `DataFrame`多级索引
```python
index = pd.MultiIndex.from_product([[2013, 2014], [1, 2]],
names=['year', 'visit'])
columns = pd.MultiIndex.from_product([['Bob', 'Guido', 'Sue'], ['HR', 'Temp']], names=['subject', 'type'])
# 模拟数据
data = np.round(np.random.randn(4, 6), 1)
data[:, ::2] *= 10
data += 37
# 创建一个包含多级列索引的DataFrame
health_data = pd.DataFrame(data, index=index, columns=columns)
health_data
Out
subject Bob Guido Sue
type HR Temp HR Temp HR Temp
year visit
2013 1 31.0 38.7 32.0 36.7 35.0 37.2
2 44. 0 37.7 50.0 35.0 29.0 36.7
2014 1 30.0 37.4 39.0 37.8 61.0 36.9
2 47. 0 37.8 48.0 37.3 51.0 36.5
```
由于`DataFrame`的**基本索引是列索引**,因此`Series`中多级索引的用法到了`DataFrame`中就应用在列上了。
```python
health_data['Guido', 'HR']
Out
year visit
2013 1 32.0
2 50.0
2014 1 39.0
2 48.0
Name: (Guido, HR), dtype: float64
```
`loc`、`iloc`、`ix`三个索引器都可以使用,虽然这些索引器将多维数据当作二维数据处理,但是在`loc`和`iloc`中可以传递多个层级的索引元组。
```python
health_data.loc[:, ('Bob', 'HR')]
Out
year visit
2013 1 31.0
2 44.0
2014 1 30.0
2 47.0
Name: (Bob, HR), dtype: float64
```
这种索引元组的用法不是很方便,如果在**元组中使用切片还会导致语法错误**。
```python
health_data.loc[(:, 1), (:, 'HR')]
# 这种切片方式会报错
```
`Pandas`的`IndexSlice`对象可以解决上述问题。
```python
idx = pd.IndexSlice
health_data.loc[idx[:, 1], idx[:, 'HR']]
Out
subject Bob Guido Sue
type HR HR HR
year visit
2013 1 31.0 32.0 35.0
2014 1 30.0 39.0 61.0
```

@ -0,0 +1,137 @@
# 3.2.2:多级索引的数据转换与累计方法
## 多级索引行列转换
使用多级索引的关键是掌握有效数据转换的方法,`Pandas`提供了许多操作,可以让数据在内容保持不变的同时,按照需要进行行列转换。上一关我们用`stack()`和`unstack()`演示过简单的行列转换,但其实还有许多合理控制层级行列索引的方法,让我们来一探究竟。
1. 有序和无序的索引
如果`MultiIndex`不是有序的索引,那么大多数切片操作都会失败,如下例:
```python
# 首先创建一个不按字典顺序排列的多级索引Series
index = pd.MultiIndex.from_product([['a', 'c', 'b'], [1, 2]])
data = pd.Series(np.random.rand(6), index=index)
data.index.names = ['char', 'int']
data
Out:
char int
a 1 0.003001
2 0.164974
c 1 0.741650
2 0.569264
b 1 0.001693
2 0.526226
dtype: float64
# 如果对该多级索引使用局部切片,就会报错
try:
data['a':'b']
except KeyError as e:
print(type(e))
print(e)
Out
<class 'KeyError'>
'Key length (1) was greater than MultiIndex lexsort depth (0)'
```
问题是出在`MultiIndex`无序排列上,局部切片和许多其他相似的操作都要求`MultiIndex`的各级索引是有序的。为此,`Pandas`提供了许多便捷的操作完成排序,如`sort_index()`和`sortlevel()`方法。
```python
data = data.sort_index()
data["a":"b"]
Out
char int
a 1 0.003001
2 0.164974
b 1 0.001693
2 0.526226
dtype: float64
```
2. 索引stack与unstack
上一关提过,我们可以将一个多级索引数据集转换成简单的二维形式,可以通过`level`参数设置转换的索引层级。
```python
pop
Out:
state year
California 2000 33871648
2010 37253956
New York 2000 18976457
2010 19378102
Texas 2000 20851820
2010 25145561
dtype: int64
# level=0
pop.unstack(level=0)
Out
state California New York Texas
year
2000 33871648 18976457 20851820
2010 37253956 19378102 2514556
# level=1
pop.unstack(level=1)
Out
year 2000 2010
state
California 33871648 37253956
New York 18976457 19378102
Texas 20851820 25145561
```
`unstack()`是`stack()`的逆操作,同时使用这两种方法`pop.unstack().stack()`让数据保持不变。
3. 索引的设置与重置
层级数据维度转换的另一种方法是行列标签转换,可以通过`reset_index`方法实现。也可以用数据的`name`属性为列设置名称:
```python
pop_flat = pop.reset_index(name='population')
pop_flat
Out:
state year population
0 California 2000 33871648
1 California 2010 37253956
2 New York 2000 18976457
3 New York 2010 19378102
4 Texas 2000 20851820
5 Texas 2010 25145561
```
在解决实际问题的时候,可以使用`DataFrame`的`set_index`方法将类似这样的原始输入数据的列直接转换成`MultiIndex`,返回结果就会是一个带多级索引的`DataFrame`。
```python
pop_flat.set_index(['state', 'year'])
Out
population
state year
California 2000 33871648
2010 37253956
New York 2000 18976457
2010 19378102
Texas 2000 20851820
2010 25145561
```
## 多级索引的数据累计方法
`Pandas`有一些自带的数据累计方法,比如`mean()`、`sum()`和`max()`。而对于层级索引数据,可以设置参数`level`实现对数据子集的累计操作。以体检数据为例:
```python
health_data
Out
subject Bob Guido Sue
type HR Temp HR Temp HR Temp
year visit
2013 1 31.0 38.7 32.0 36.7 35.0 37.2
2 44.0 37.7 50.0 35.0 29.0 36.7
2014 1 30.0 37.4 39.0 37.8 61.0 36.9
2 47.0 37.8 48.0 37.3 51.0 36.5
```
如果你需要计算每一年各项指标的平均值,那么可以将参数`level`设置为索引`year`
```python
data_mean = health_data.mean(level='year')
data_mean
Out
subject Bob Guido Sue
type HR Temp HR Temp HR Temp
year
2013 37.5 38.2 41.0 35.85 32.0 36.95
2014 38.5 37.6 43.5 37.55 56.0 36.70
```
如果再设置`axis`参数,就可以对列索引进行类似的累计操作了:
```python
data_mean.mean(axis=1, level='type')
Out
type HR Temp
year
2013 36.833333 37.000000
2014 46.000000 37.283333
```

@ -0,0 +1,126 @@
# 3.2.3Concat与Append操作
## 相关知识
在`Numpy`中,我们介绍过可以用`np.concatenate`、`np.stack`、`np.vstack`和`np.hstack`实现合并功能。`Pandas`中有一个`pd.concat()`函数与`concatenate`语法类似,但是配置参数更多,功能也更强大,主要参数如下。
参数名|说明
--|---
objs|参与连接的对象,必要参数
axis|指定轴默认为0
join|inner或者outer默认为outer指明其他轴的索引按哪种方式进行合并,inner表示取交集outer表示取并集
join_axes|指明用于其他n-1条轴的索引不执行并集/交集运算
keys|与连接对象有关的值,用于形成连接轴向上的层次化索引。可以是任意值的列表或数组
levels|指定用作层次化索引各级别上的索引
names|用于创建分层级别的名称如果设置了keys和levels
verify_integrity|检查结果对象新轴上的重复情况如果发现则引发异常。默认False允许重复
ignore_index|不保留连接轴上的索引,产生一组新索引
---
`pd.concat()`可以简单地合并一维的`Series`或`DataFrame`对象。
```python
# Series合并
ser1 = pd.Series(['A', 'B', 'C'], index=[1, 2, 3])
ser2 = pd.Series(['D', 'E', 'F'], index=[4, 5, 6])
pd.concat([ser1,ser2])
Out
1 A
2 B
3 C
4 D
5 E
6 F
dtype: object
# DataFrame合并将concat的axis参数设置为1即可横向合并
df1 = pd.DataFrame([["A1","B1"],["A2","B2"]],index=[1,2],columns=["A","B"])
df2 = pd.DataFrame([["A3","B3"],["A4","B4"]],index=[3,4],columns=["A","B"])
pd.concat([df1,df2])
Out
A B
1 A1 B1
2 A2 B2
3 A3 B3
4 A4 B4
```
## 合并时索引的处理
`np.concatenate`与`pd.concat`最主要的差异之一就是`Pandas`在合并时会保留索引,即使索引是重复的!
```python
df3 = pd.DataFrame([["A1","B1"],["A2","B2"]],index=[1,2],columns=["A","B"])
df4 = pd.DataFrame([["A1","B1"],["A2","B2"]],index=[1,2],columns=["A","B"])
pd.concat([df3,df4])
Out
A B
1 A1 B1
2 A2 B2
1 A3 B3
2 A4 B4
```
1. 如果你想要检测`pd.concat()`合并的结果中是否出现了重复的索引,可以设置`verify_integrity`参数。将参数设置为`True`,合并时若有索引重复就会触发异常。
```
try:
pd.concat([df3, df4], verify_integrity=True)
except ValueError as e:
print("ValueError:", e)
Out
ValueError: Indexes have overlapping values: [0, 1]
```
2. 有时索引无关紧要,那么合并时就可以忽略它们,可以通过设置 `ignore_index`参数为`True`来实现。
```python
pd.concat([df3,df4],ignore_index=True)
Out
A B
0 A0 B0
1 A1 B1
2 A2 B2
3 A3 B3
```
3. 另一种处理索引重复的方法是通过`keys`参数为数据源设置多级索引标签,这样结果数据就会带上多级索引。
```python
pd.concat([df3, df4], keys=['x', 'y'])
Out
A B
x 0 A0 B0
1 A1 B1
y 0 A2 B2
1 A3 B3
```
## join和join_axes参数
前面介绍的简单示例都有一个共同特点,那就是合并的`DataFrame`都是同样的列名。而在实际工作中,需要合并的数据往往带有不同的列名,而 `pd.concat`提供了一些参数来解决这类合并问题。
```python
df5 = pd.DataFrame([["A1","B1","C1"],["A2","B2","C2"]],index=[1,2],columns=["A","B","C"])
df6 = pd.DataFrame([["B3","C3","D3"],["B4","C4","D4"]],index=[3,4],columns=["B","C","D"])
pd.concat([df5,df6])
Out
A B C D
1 A1 B1 C1 NaN
2 A2 B2 C2 NaN
3 NaN B3 C3 D3
4 NaN B4 C4 D4
```
可以看到,结果中出现了缺失值,如果不想出现缺失值,可以使用`join`和`join_axes`参数。
```python
pd.concat([df5,df6],join="inner") # 合并取交集
Out
B C
1 B1 C1
2 B2 C2
3 B3 C3
4 B4 C4
# join_axes的参数需为一个列表索引对象
pd.concat([df5,df6],join_axes=[pd.Index(["B","C"])])
Out
B C
1 B1 C1
2 B2 C2
3 B3 C3
4 B4 C4
```
## append()方法
因为直接进行数组合并的需求非常普遍,所以`Series`和`DataFrame` 对象都支持`append`方法,让你通过最少的代码实现合并功能。例如,`df1.append(df2)`效果与`pd.concat([df1,df2])`一样。但是它和`Python`中的`append`不一样,**每次使用`Pandas`中的`append()`都需要重新创建索引和数据缓存**。

@ -0,0 +1,130 @@
# 3.2.4:合并与连接
## 相关知识
`merge()`可根据一个或者多个键将不同的`DataFrame`连接在一起,类似于`SQL`数据库中的合并操作。
参数名|说明
--|---
left|拼接左侧DataFrame对象
right|拼接右侧DataFrame对象
on|列(名称)连接必须在左和右DataFrame对象中存在(找到)。
left_on|左侧DataFrame中的列或索引级别用作键可以是列名、索引级名也可以是长度等于DataFrame长度的数组。
right_on|右侧DataFrame中的列或索引级别用作键。可以是列名索引级名称也可以是长度等于DataFrame长度的数组。
left_index|如果为True则使用左侧DataFrame中的索引(行标签)作为其连接键。
right_index|与left_index相似
how|它可以等于left, right, outer, inner. 默认inner。inner是取交集outer取并集。
sort|sort - 按照字典顺序通过连接键对结果DataFrame进行排序。
suffixes|用于追加到重叠列名的末尾。例如左右两个DataFrame都有data则结果中就会出现data_xdata_y.
copy|默认总是复制
---
## 数据连接的类型
- 一对一的连接
```python
df1 = pd.DataFrame({'employee': ['Bob', 'Jake', 'Lisa', 'Sue'], 'group': ['Accounting', 'Engineering', 'Engineering', 'HR']})
df2 = pd.DataFrame({'employee': ['Lisa', 'Bob', 'Jake', 'Sue'], 'hire_date': [2004, 2008, 2012, 2014]})
df3 = pd.merge(df1,df2)
df3
```
输出:
![](/api/attachments/375684)
![](/api/attachments/375685)
- 多对一的连接
```python
df4 = pd.DataFrame({'group': ['Accounting', 'Engineering', 'HR'], 'supervisor': ['Carly', 'Guido', 'Steve']})
pd.merge(df3,df4)
```
输出:
![](/api/attachments/375666)
- 多对多连接
```python
df5 = pd.DataFrame({'group': ['Accounting', 'Accounting', 'Engineering', 'Engineering', 'HR', 'HR'], 'skills': ['math', 'spreadsheets', 'coding', 'linux', 'spreadsheets', 'organization']})
pd.merge(df1,df5)
```
输出:
![](/api/attachments/375667)
## merge()的主要参数
**1. `on`** 可以是列名字符串或者一个包含多列名称的列表
```python
pd.merge(df1, df2, on='employee')
```
输出:
![](/api/attachments/375672)
*这个参数只能在两个`DataFrame`有共同列名的时候才可以使用。*
**2. `left_on`与`right_on`参数**
有时你也需要合并两个列名不同的数据集,例如前面的员工信息表中有一个字段不是`employee`而是`name`。在这种情况下,就可以用`left_on`和`right_on`参数来指定列名。
```python
df3 = pd.DataFrame({'name': ['Bob', 'Jake', 'Lisa', 'Sue'], 'salary': [70000, 80000, 120000, 90000]})
dfx = pd.merge(df1,df3,left_on="employee",right_on="name")
```
输出:
![](/api/attachments/375673)
![](/api/attachments/375676)
**如果出现重复列,但是列名不同时,可以使用`drop`方法将这列去掉**
```python
dfx.drop("name",axis=1)
```
输出:
![](/api/attachments/375837)
**3. left_index与right_index参数** 用于合并索引
```python
df1a = df1.set_index('employee')
df2a = df2.set_index('employee')
pd.merge(df1a,df2a,left_index=True,right_index=True)
```
输出:
![](/api/attachments/375677)
---
用`join()`方法也可以实现该功能:
```python
df1a.join(df2a)
```
输出:
![](/api/attachments/375679)
---
如果想将索引与列混合使用,那么可以通过结合`left_index`与` right_on`,或者结合`left_on`与`right_index`来实现。
```python
pd.merge(df1a, df3, left_index=True, right_on='name')
```
输出:
![](/api/attachments/375681)
---
**4. how参数**
`how`参数默认情况下是`inner`也就是取交集。how参数支持的数据连接方式还有`outer`、`left`和`right`。`outer`表示外连接,取并集。
```python
df6 = pd.DataFrame({'name': ['Peter', 'Paul', 'Mary'], 'food': ['fish', 'beans', 'bread']}, columns=['name', 'food'])
df7 = pd.DataFrame({'name': ['Mary', 'Joseph'], 'drink': ['wine', 'beer']}, columns=['name', 'drink'])
pd.merge(df6, df7, how='outer')
```
输出:
![](/api/attachments/375691)
---
左连接和右连接返回的结果分别只包含左列和右列。
```python
pd.merge(df6, df7, how='left')
```
输出:
![](/api/attachments/375692)
**5. suffixes参数**
如果输出结果中有两个重复的列名,因此`pd.merge()`函数会自动为它们增加后缀 `_x ``_y`,当然也可以通过`suffixes`参数自定义后缀名。
```python
df8 = pd.DataFrame({'name': ['Bob', 'Jake', 'Lisa', 'Sue'], 'rank': [1, 2, 3, 4]})
df9 = pd.DataFrame({'name': ['Bob', 'Jake', 'Lisa', 'Sue'], 'rank': [3, 1, 4, 2]})
pd.merge(df8, df9, on="name", suffixes=["_L", "_R"])
```
输出:
![](/api/attachments/375698)
`suffixes`参数同样适用于任何连接方式,即使有三个及三个以上的重复列名时也同样适用。

@ -0,0 +1,191 @@
# 3.2.5:分组聚合
## 分组
通常我们将数据分成多个集合的操作称之为分组,`Pandas`中使用`groupby()`函数来实现分组操作。
### 单列和多列分组
*对分组后的子集进行数值运算时,不是数值的列会自动过滤*
```Python
import pandas as pd
data = {'A': [1, 2, 2, 3, 2, 4],
'B': [2014, 2015, 2014, 2014, 2015, 2017],
'C': ["a", "b", "c", "d", "e", "f"],
'D': [0.5, 0.9, 2.1, 1.5, 0.5, 0.1]
}
df = pd.DataFrame(data)
df.groupby("B") #单列分组 返回的是一个groupby对象
df.groupby(["B","C"]) #多列分组
```
### `Series`系列分组
选取数据帧中的一列作为`index`进行分组:
```Python
df["A"].groupby(df["B"]) #df的 A 列根据 B 进行分组
```
### 通过数据类型或者字典分组
数据类型分组:
```Python
df.groupby(df.dtypes,axis=1) # axis=1表示按列分组以数据类型为列名
```
传入字典分组:
```Python
dic = {"A": "number", "B": "number", "C": "str", "D": "number"}
df.groupby(dic, axis=1) #按列分组,列名是字典的值
```
### 获取单个分组
使用`get_group()`方法可以选择一个组。
```Python
df.groupby("A").get_group(2)
```
输出:
```Python
A B C D
1 2 2015 b 0.9
2 2 2014 c 2.1
4 2 2015 e 0.5
```
### 对分组进行迭代
`GroupBy`对象支持迭代,可以产生一组二元元组(由分组名和数据块组成)。
```Python
for name,data in df.groupby("A"):
print(name)
print(data)
```
输出:
```Python
1
A B C D
0 1 2014 a 0.5
2
A B C D
1 2 2015 b 0.9
2 2 2014 c 2.1
4 2 2015 e 0.5
3
A B C D
3 3 2014 d 1.5
4
A B C D
5 4 2017 f 0.1
```
## 聚合
聚合函数为每个组返回单个聚合值。当创建了`groupby`对象,就可以对分组数据执行多个聚合操作。比较常用的是通过聚合函数或等效的`agg`方法聚合。常用的聚合函数如下表:
函数名|说明
--|:---:
count|分组中非空值的数量
sum|非空值的和
mean|非空值的平均值
median|非空值的中位数
std、var|无偏标准差和方差
min、max|非空值的最小和最大值
prod|非空值的积
first、last|第一个和最后一个非空值
### 应用单个聚合函数
对分组后的子集进行数值运算时,不是数值的列会自动过滤
```Python
import pandas as pd
import numpy as np
data = {'A': [1, 2, 2, 3, 2, 4],
'B': [2014, 2015, 2014, 2014, 2015, 2017],
'C': ["a", "b", "c", "d", "e", "f"],
'D': [0.5, 0.9, 2.1, 1.5, 0.5, 0.1]
}
df = pd.DataFrame(data)
df.groupby("B").sum() #对分组进行求和
```
输出:
```python
A D
B
2014 6 4.1
2015 4 1.4
2017 4 0.1
```
```python
df.groupby("B").describe()
```
输出:
![](/api/attachments/376362)
### 应用多个聚合函数
```Python
df.groupby("B").agg([np.sum,np.mean,np.std])
```
输出:
```
A D
sum mean std sum mean std
B
2014 6 2 1.0 4.1 1.366667 0.808290
2015 4 2 0.0 1.4 0.700000 0.282843
2017 4 4 NaN 0.1 0.100000 NaN
```
#### 自定义函数传入agg()中
```Python
def result(df):
return df.max() - df.min()
df.groupby("B").agg(result) #求每一组最大值与最小值的差
```
输出:
```
A D
B
2014 2 1.6
2015 0 0.4
2017 0 0.0
```
#### 对不同的列使用不同的聚合函数
```Python
mapping = {"A":np.sum,"D":np.mean}
df.groupby("B").agg(mapping)
```
输出:
```python
A D
B
2014 6 1.366667
2015 4 0.700000
2017 4 0.100000
```

@ -0,0 +1,77 @@
# 3.2.6:创建透视表和交叉表
## 透视表
透视表是各种电子表格程序和其他数据分析软件中一种常见的数据汇总工具。它根据一个或多个键对数据进行聚合,并根据行和列上得分组建将数据分配到各个矩形区域中。在`pandas`中,可以通过`pivot_table`函数创建透视表。
----
`pivot_talbe`函数的参数:
```Python
DataFrame.pivot_table(self, values=None, index=None, columns=None,ggfunc='mean', fill_value=None, .margins=False,dropna=True, margins_name='All')
```
参数名|说明
--|:---:
values|待聚合的列的名称。默认聚合所有数值列
index|用于分组的列名或其他分组键,出现在结果透视表的行
columns|用于分组的列名或其他分组键,出现在结果透视表的列
aggfunc|聚合函数或函数列表,默认为`mean`可以是任何对groupby有效的函数
fill_value|用于替换结果表中的缺失值
dropna|boolean值默认为True是否删除空值
margins|boolean值当需要计算每一组的总数时可以设为True
margins_name|string默认为ALL当参数margins为True时ALL行和列的名字
----
示例:
```Python
data = {'A': [1, 2, 2, 3, 2, 4],
'B': [2014, 2015, 2014, 2014, 2015, 2017],
'C': ["a", "b", "c", "d", "e", "f"],
'D': [0.5, 0.9, 2.1, 1.5, 0.5, 0.1]
}
df = pd.DataFrame(data)
df.pivot_table(index=["B"], columns=["C"], values=["A"], aggfunc=sum, margins=True)
```
输出:
```Python
A
C a b c d e f All
B
2014 1.0 NaN 2.0 3.0 NaN NaN 6
2015 NaN 2.0 NaN NaN 2.0 NaN 4
2017 NaN NaN NaN NaN NaN 4.0 4
All 1.0 2.0 2.0 3.0 2.0 4.0 14
```
## 交叉表
交叉表是一种用于计算分组频率的特殊透视表。通常使用`crosstab`函数来创建交叉表。
----
`crosstab`的参数
```Python
pd.crosstab(index,columns,values=None,rownames=None
,colnames=None,aggfunc=None,margins=False,dropna=True,normalize=False)
```
其中`rownames`可以设置行名,`colnames`可以设置列名,而且前两个参数可以是数组、`Series`或数组列表。
----
示例:
```Python
data = {'A': [1, 2, 2, 3, 2, 4],
'B': [2014, 2015, 2014, 2014, 2015, 2017],
'C': ["a", "b", "c", "d", "e", "f"],
'D': [0.5, 0.9, 2.1, 1.5, 0.5, 0.1]
}
df = pd.DataFrame(data)
pd.crosstab(index=[df["B"],df["A"]], columns=df["C"], values=df["A"], aggfunc=sum, margins=True)
```
输出:
```Python
C a b c d e f All
B A
2014 1 1.0 NaN NaN NaN NaN NaN 1
2 NaN NaN 2.0 NaN NaN NaN 2
3 NaN NaN NaN 3.0 NaN NaN 3
2015 2 NaN 2.0 NaN NaN 2.0 NaN 4
2017 4 NaN NaN NaN NaN NaN 4.0 4
All 1.0 2.0 2.0 3.0 2.0 4.0 14
```

@ -0,0 +1,102 @@
# 3.2.7:字符串操作方法
## 字符串方法
如果你对`Python`字符串方法十分了解,那么下面的知识对你来说如瓮中捉鳖,几乎所有的`Python`内置的字符串方法都被复制到`Pandas`的向量化字符串方法中。下图列举了`Pandas`的`str`方法借鉴`Python`字符串方法的内容:
![](/api/attachments/376455)
---
它们的作用与`Python`字符串的基本一致,但是需要注意这些方法的返回值不同。举两个例子:
```python
monte = pd.Series(['Graham Chapman', 'John Cleese', 'Terry Gilliam', 'Eric Idle', 'Terry Jones', 'Michael Palin'])
monte.str.lower() # 返回字符串
```
输出:
```python
0 graham chapman
1 john cleese
2 terry gilliam
3 eric idle
4 terry jones
5 michael palin
dtype: object
```
```
monte.str.split() # 返回列表
```
输出:
```python
0 [Graham, Chapman]
1 [John, Cleese]
2 [Terry, Gilliam]
3 [Eric, Idle]
4 [Terry, Jones]
5 [Michael, Palin]
dtype: object
```
---
`pandas`中还有一些自带的字符串方法,如下图所示:
![](/api/attachments/376459)
其中`get_dummies()`方法有点难以理解,给大家举个例子,假设有一个包含了某种编码信息的数据集,如 `A`= 出生在美国、`B`= 出生在英国、`C`= 喜欢奶酪、`D`= 喜欢午餐肉:
```python
full_monte = pd.DataFrame({
'name': monte,
'info': ['B|C|D', 'B|D', 'A|C', 'B|D', 'B|C', 'B|C|D']})
print(full_monte)
```
输出:
```python
info name
0 B|C|D Graham Chapman
1 B|D John Cleese
2 A|C Terry Gilliam
3 B|D Eric Idle
4 B|C Terry Jones
5 B|C|D Michael Palin
```
`get_dummies()`方法可以让你快速将这些指标变量分割成一个独热编码的`DataFrame`(每个元素都是`0`或`1`
```python
full_monte['info'].str.get_dummies('|')
```
输出:
```python
A B C D
0 0 1 1 1
1 0 1 0 1
2 1 0 1 0
3 0 1 0 1
4 0 1 1 0
5 0 1 1 1
```
## 正则表达式方法
还有一些支持正则表达式的方法可以用来处理每个字符串元素。如下图所示:
![](/api/attachments/376458)
众所周知,正则表达式“无所不能”,我们可以利用正则实现一些独特的操作,例如提取每个人的`first name`
```python
monte.str.extract('([A-Za-z]+)')
```
输出:
```python
0 Graham
1 John
2 Terry
3 Eric
4 Terry
5 Michael
dtype: object
```
或者找出所有开头和结尾都是辅音字符的名字:
```python
monte.str.findall(r'^[^AEIOU].*[^aeiou]$')
```
输出:
```python
0 [Graham Chapman]
1 []
2 [Terry Gilliam]
3 []
4 [Terry Jones]
5 [Michael Palin]
dtype: object
```

@ -0,0 +1,114 @@
# 3.2.8:日期与时间工具
## 相关知识
`Pandas`是为金融模型而创建的,所以拥有一些功能非常强大的日期、时间、带
时间索引数据的处理工具。本关卡介绍的日期与时间数据主要包含三类:
- **时间戳**:表示某个具体的时间点(例如`2015`年`7`月`4`日上午 7 点)
- **时间间隔与周期**:期表示开始时间点与结束时间点之间的时间长度,例如`2015`年(指的是`2015`年`1`月`1`日至`2015`年`12`月`31`日这段时间间隔)。周期通常是指一种特殊形式的时间间隔,每个间隔长度相同,彼此之间不会重叠(例如,以`24`小时为周期构成每一天)
- **时间增量或持续时间**:表示精确的时间长度(例如,某程序运行持续时间`22.56`秒)
### Python 的日期与时间工具
原生`Python`中也有处理日期与时间的工具,它与`Pandas`中处理时间的工具有着千丝万缕的联系。`Python`的日期与时间功能都在标准库的`datetime`模块和第三方库`dateutil`模块。如果你处理的时间数据量比较大,那么速度就会比较慢,这时就需要使用到`NumPy`中已经被编码的日期类型数组了。
### NumPy中的datetime64类型
`Python`原生日期格式的性能弱点促使`NumPy`团队为`NumPy`增加了自己的时间序列类型。`datetime64`类型将日期编码为`64`位整数,这样可以让日期数组非常紧凑(节省内存)。
```python
In[3]import numpy as np
date = np.array('2015-07-04', dtype=np.datetime64)
date
Out[3]array(datetime.date(2015, 7, 4), dtype='datetime64[D]')
```
有了这个日期格式,既可以进行快速的向量化运算:
```python
In[4]date + np.arange(12) #由于date是datetime类型所以向量化运算也是datetime类型的运算
Out[4]
array(['2015-07-04', '2015-07-05', '2015-07-06', '2015-07-07', '2015-07-08', '2015-07-09', '2015-07-10', '2015-07-11', '2015-07-12', '2015-07-13', '2015-07-14', '2015-07-15'], dtype='datetime64[D]')
```
由于`datetime64`对象是`64`位精度,所以可编码的时间范围可以是基本单元的 `264`倍。也就是说,`datetime64`在**时间精度**与**最大时间跨度**之间达成了一种平衡,也就是说,`NumPy`会自动判断输入时间所需要使用的时间单位。
```python
In[5]: np.datetime64('2015-07-04') # 天为单位
Out[5]: numpy.datetime64('2015-07-04')
In[6]: np.datetime64('2015-07-04 12:00') # 分钟为单位
Out[6]: numpy.datetime64('2015-07-04T12:00')
In[7]: np.datetime64('2015-07-04 12:59:59.50', 'ns') # 手动设置时间单位
Out[7]: numpy.datetime64('2015-07-04T12:59:59.500000000')
```
日期与时间单位格式代码表如下:
代码|含义|时间跨度(相对)|时间跨度(绝对)
-|-|-|-
Y|年year|± 9.2e18 年|[9.2e18 BC, 9.2e18 AD]
M|月month|± 7.6e17 年|[7.6e17 BC, 7.6e17 AD]
W|周week|± 1.7e17 年|[1.7e17 BC, 1.7e17 AD]
D|日day|± 2.5e16 年|[2.5e16 BC, 2.5e16 AD]
h|时hour|± 1.0e15 年|[1.0e15 BC, 1.0e15 AD]
m|分minute|± 1.7e13 年|[1.7e13 BC, 1.7e13 AD]
s|秒second|± 2.9e12 年|[ 2.9e9 BC, 2.9e9 AD]
ms|毫秒millisecond|± 2.9e9 年|[ 2.9e6 BC, 2.9e6 AD]
us|微秒microsecond|± 2.9e6 年|[290301 BC, 294241 AD]
ns|纳秒nanosecond|± 292 年|[ 1678 AD, 2262 AD]
ps|皮秒picosecond|± 106 天|[ 1969 AD, 1970 AD]
fs|飞秒femtosecond|± 2.6 小时|[ 1969 AD, 1970 AD]
as|原秒attosecond|± 9.2 秒|[ 1969 AD, 1970 AD|
### Pandas的日期与时间工具
`Pandas`中的`datetime`是结合了原生`Python`和`NumPy`的`datetime`,用来处理时间序列的基础数据类型如下:
- 针对时间戳数据,`Pandas`提供了`Timestamp`类型。它本质上是`Python`的原生`datetime`类型的替代品,但是在性能更好的`numpy.datetime64`类型的基础上创建。对应的索引数据结构是 **`DatetimeIndex`**。
- 针对时间周期数据,`Pandas`提供了`Period`类型。这是利用`numpy.datetime64`类型将固定频率的时间间隔进行编码。对应的索引数据结构是**`PeriodIndex`**。
- 针对时间增量或持续时间,`Pandas`提供了`Timedelta`类型。`Timedelta`是一种代替`Python`原生`datetime.timedelta`类型的高性能数据结构,同样是基于`numpy.timedelta64`类型。对应的索引数据结构是**`TimedeltaIndex`**。
最基础的日期 / 时间对象是`Timestamp`和`DatetimeIndex`。这两种对象可以直接使用,最常用的方法是`pd.to_datetime()`函数,。对`pd.to_datetime() `传递一个**日期**会返回一个`Timestamp`类型,传递一个**时间序列**会返回一个`DatetimeIndex`类型
- **`DatetimeIndex`**类型
```python
In[8]dates = pd.to_datetime([datetime(2015, 7, 3), '4th of July, 2015', '2015-Jul-6', '07-07-2015', '20150708'])
dates
Out[8]DatetimeIndex(['2015-07-03', '2015-07-04', '2015-07-06', '2015-07-07', '2015-07-08'], dtype='datetime64[ns]', freq=None)
```
- **`PeriodIndex`**类型
任何`DatetimeIndex`类型都可以通过`to_period()`方法和一个频率代码转换成`PeriodIndex`类型,`PeridoIndex`类型可以通过`to_timestamp()`方法转换为`DatetimeIndex`类型。下面用`D`将数据转换成单日的时间序列:
```python
In[10]: dates.to_period('D')
Out[10]: PeriodIndex(['2015-07-03', '2015-07-04', '2015-07-06', '2015-07-07', '2015-07-08'], dtype='int64', freq='D')
```
- **`TimedeltaIndex`**类型
当用一个日期减去另一个日期时,返回的结果是`TimedeltaIndex`类型:
```python
In[11]: dates - dates[0]
Out[11]:
TimedeltaIndex(['0 days', '1 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq=None)
```
---
为了能更简便地创建有规律的时间序列,`Pandas`提供了一些方法:`pd.date_range()`可以处理时间戳、`pd.period_range()`可以处理周期、`pd.timedelta_range()`可以处理时间间隔。
- **`pd.date_range()`**
通过开始日期、结束日期和频率代码(可选的)创建一个有规律的日期序列,默认的频率是天:
```python
In[12]pd.date_range('2015-07-03', '2015-07-10')
Out[12]DatetimeIndex(['2015-07-03', '2015-07-04', '2015-07-05', '2015-07-06', '2015-07-07', '2015-07-08', '2015-07-09', '2015-07-10'], dtype='datetime64[ns]', freq='D')
```
范围不一定非是开始时间和结束时间,也可以设置周期数`periods`来达到改目的:
```python
In[13]pd.date_range('2015-07-03', periods=8)
Out[13]DatetimeIndex(['2015-07-03', '2015-07-04', '2015-07-05', '2015-07-06', '2015-07-07', '2015-07-08', '2015-07-09', '2015-07-10'], dtype='datetime64[ns]', freq='D')
```
`freq`表示时间间隔,默认是`D`,可以通过修改它来`periods`参数的意义:
```python
In[14]pd.date_range('2015-07-03', periods=8, freq='H')
Out[14]
DatetimeIndex(['2015-07-03 00:00:00', '2015-07-03 01:00:00',
'2015-07-03 02:00:00', '2015-07-03 03:00:00',
'2015-07-03 04:00:00', '2015-07-03 05:00:00',
'2015-07-03 06:00:00', '2015-07-03 07:00:00'],
dtype='datetime64[ns]', freq='H')
```
- **`pd.timedelta_range()`**
如果要创建一个有规律的周期或时间间隔序列,`pd.timedelta_range()`可以实现该功能:
```python
In[15]pd.period_range('2015-07', periods=8, freq='M')
Out[15]
PeriodIndex(['2015-07', '2015-08', '2015-09', '2015-10', '2015-11', '2015-12', '2016-01', '2016-02'], dtype='int64', freq='M')
```
也可以通过修改`freq`参数实现各种频率的时间间隔序列。

@ -0,0 +1,155 @@
# 3.2.8:日期与时间工具
## 相关知识
Pandas 时间序列工具的基础是时间频率或偏移量代码。就像之前见过的`Dday`和`Hhour`代码,我们可以用这些代码设置任意需要的时间间隔。
Pandas频率代码表如下
代码|描述
--|--
D|天calendar day按日历算含双休日
W|周weekly M 月末month end
Q|季末quarter end
A|年末year end
H|小时hours
T|分钟minutes
S|秒seconds
L|毫秒milliseonds
U|微秒microseconds
N|纳秒nanoseconds
B|天business day仅含工作日
BM|月末business month end仅含工作日
BQ|季末business quarter end仅含工作日
BA|年末business year end仅含工作日
BH|小时business hours工作时间
MS|月初month start
BMS|月初business month start仅含工作日
QS|季初quarter start
BQS|季初business quarter start仅含工作日
AS|年初year start
BAS|年初business year start仅含工作日
## 时间频率与偏移量
我们可以在频率代码后面加三位月份缩写字母来改变季、年频率的开始时间,也可以再后面加三位星期缩写字母来改变一周的开始时间:
- `Q-JAN、BQ-FEB、QS-MAR、BQS-APR`
- `W-SUN、W-MON、W-TUE、W-WED`
时间频率组合使用:
```
In[0]pd.timedelta_range(0,periods=9,freq="2H30T")
Out[0]TimedeltaIndex(['00:00:00', '02:30:00', '05:00:00', '07:30:00', '10:00:00', '12:30:00', '15:00:00', '17:30:00', '20:00:00'], dtype='timedelta64[ns]', freq='150T')
```
比如直接创建一个工作日偏移序列:
```
In[1]from pandas.tseries.offsets import BDay
pd.date_range('2015-07-01', periods=5, freq=BDay())
Out[1]DatetimeIndex(['2015-07-01', '2015-07-02', '2015-07-03', '2015-07-06', '2015-07-07'], dtype='datetime64[ns]', freq='B')
```
## 重新取样、迁移和窗口
### 重新取样
处理时间序列数据时,经常需要按照新的频率(更高频率、更低频率)对数据进行重新取样。举个例子,首先通过`pandas—datareader`程序包(需要手动安装)导入 Google 的历史股票价格,只获取它的收盘价:
```python
In[2]
from pandas_datareader import data
goog = data.DataReader('GOOG',start="2014",end="2016",data_source="google")['Close'] # Close表示收盘价的列
In[3]
import matplotlib.pyplot as plt
import seaborn; seaborn.set()
goog.plot(); #数据可视化
```
输出:
![](1.jpg)
我们可以通过`resample()`方法和`asfreq()`方法解决这个问题,`resample() `方法是以**数据累计**为基础,而`asfreq()`方法是以**数据选择**为基础。
```python
In[4]
goog.plot(alpha=0.5, style='-')
goog.resample('BA').mean().plot(style=':')
goog.asfreq('BA').plot(style='--');
plt.legend(['input', 'resample', 'asfreq'], loc='upper left');
```
输出:
![](2.jpg)
请注意这两种取样方法的差异:在每个数据点上,`resample`反映的是**上一年的均值**,而`asfreq`反映的是**上一年最后一个工作日的收盘价**。
数据集中经常会出现缺失值,从上面的例子来看,由于周末和节假日股市休市,周末和节假日就会产生缺失值,上面介绍的两种方法默认使用的是向前取样作为缺失值处理。与前面介绍过的`pd.fillna()`函数类似,`asfreq()`有一个`method`参数可以设置填充缺失值的方式。
```python
In[5]fig, ax = plt.subplots(2, sharex=True)
data = goog.iloc[:10]
data.asfreq('D').plot(ax=ax[0], marker='o')
data.asfreq('D', method='bfill').plot(ax=ax[1], style='-o')
data.asfreq('D', method='ffill').plot(ax=ax[1], style='--o')
ax[1].legend(["back-fill", "forward-fill"]);
```
输出:
![](3.jpg)
## 时间迁移
另一种常用的时间序列操作是对数据按时间进行迁移,`Pandas`有两种解决这类问题的方法:`shift()`和`tshift()`。简单来说,`shift()`就是迁移数据,而 `tshift()`就是迁移索引。两种方法都是按照频率代码进行迁移。
下面我们将用`shift()` 和`tshift()`这两种方法让数据迁移`900`天:
```python
In[6]fig, ax = plt.subplots(3, sharey=True)
# 对数据应用时间频率,用向后填充解决缺失值
goog = goog.asfreq('D', method='pad')
goog.plot(ax=ax[0])
goog.shift(900).plot(ax=ax[1])
goog.tshift(900).plot(ax=ax[2])
# 设置图例与标签
local_max = pd.to_datetime('2007-11-05')
offset = pd.Timedelta(900, 'D')
ax[0].legend(['input'], loc=2)
ax[0].get_xticklabels()[4].set(weight='heavy', color='red')
ax[0].axvline(local_max, alpha=0.3, color='red')
ax[1].legend(['shift(900)'], loc=2)
ax[1].get_xticklabels()[4].set(weight='heavy', color='red')
ax[1].axvline(local_max + offset, alpha=0.3, color='red')
ax[2].legend(['tshift(900)'], loc=2)
ax[2].get_xticklabels()[1].set(weight='heavy', color='red')
ax[2].axvline(local_max + offset, alpha=0.3, color='red');
```
输出:
![](4.jpg)
`shift(900)`将数据向前推进了`900`天,这样图形中的一段就消失了(最左侧
就变成了缺失值),而`tshift(900)`方法是将时间索引值向前推进了`900`天。
## 移动时间窗口
`Pandas`处理时间序列数据的第`3`种操作是移动统计值。这些指标可以通过 `Series`和`DataFrame`的`rolling()`属性来实现,它会返回与`groupby`操作类似的结果,移动视图使得许多累计操作成为可能。
```python
In[7] rolling = goog.rolling(365, center=True)
data = pd.DataFrame({'input': goog, 'one-year rolling_mean': rolling.mean(), 'one-year rolling_std': rolling.std()})
ax = data.plot(style=['-', '--', ':'])
ax.lines[0].set_alpha(0.3)
```
输出:
![](5.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

@ -0,0 +1,7 @@
# Chapter3 Pandas
Pandas 是基于 NumPy 的一种工具该工具是为了解决数据分析任务而创建的。Pandas 纳入了大量库和一些标准的数据模型提供了高效地操作大型数据集所需的工具。Pandas 提供了大量能使我们快速便捷地处理数据的函数和方法。你很快就会发现,它是使 Python 成为强大而高效的数据分析环境的重要因素之一。
Pandas 相关实训已在`educoder`平台上提供,若感兴趣可以输入链接进行体验。
链接https://www.educoder.net/paths/302

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@ -0,0 +1,57 @@
# 4.1.1:画图接口
## 导入matplotlib
和`numpy`,`pandas`一样,在导入`matplotlib`时我们也可以用一些常用的简写形式:
```python
import matplotlib as mpl
import matplotlib.pyplot as plt
```
`pyplot`是最常用的画图模块接口,功能非常强大。
## 显示图像
开发环境的不同,显示图像的方式也就不一样,一般有三种开发环境,分别是脚本、`IPython shell`、`IPython Notebook`。
在脚本中使用`matplotlib`进行可视化时显示图像可以使用`plt.show()`。
在`IPython shell`中使用`matplotlib`可视化非常方便,使用`%matplotlib`命令启动`matplotlib`模式。之后的任何`plt`命令都会自动打开一个图像窗口,当有新的命令,图像就会更新。但对已经画好的图像不会自动实时更新。对于这种可以使用`plt.draw()`强制更新。
在`IPython Notebook`中画图和`IPthon shell`类似,也需要使用`%matplotlib`命令。图像的显示是嵌在`IPython Notebook`页面中。有两种展示形式:`%matplotlib notebook`交互式图形;`%matplotlib inline`静态图形。
`matplotlib`还可以直接将图像保存文件,通过`plt.savefig("test.jpg")`命令保存文件。
```python
plt.savefig("test.jpg")
```
## 画图接口
`matplotlib`有两个画图接口:一个是便捷的`matlab`风格接口,另一个是功能更强大的面向对象接口。
`matplotlib`的`matlab`接口许多语法都和`MATLAB`类似,所以使用过`MATLAB`的朋友们想必很快就能上手`matplotlib`。
```python
import matplotlib.pyplot as plt#导入模块
plt.figure(figsize=(10,10))#创建图形,并设置大小为10 x 10
plt.subplot(2,1,1)#创建子图1子图编号
plt.plot([1,2,3,4], [1,2,3,4])
plt.subplot(2,1,2)#创建子图2子图编号
plt.plot([4,3,2,1], [1,2,3,4])
plt.show()
```
![](1.jpg)
面向对象接口可以适应更加复杂的场景,更好地控制图形,在画比较复杂的图形市,面向对象方法会更方便。通过下面的代码,可以用面向对象接口重新创建之前的图形。
```python
fig,ax=plt.subplots(2)#ax是一个包含2个axes对象的数组
ax[0].plot([1,2,3,4], [1,2,3,4])
ax[1].plot([4,3,2,1], [1,2,3,4])
plt.show()
```

@ -0,0 +1,129 @@
# 4.1.2:线形图
## 绘制线形图
在所有图形中,最简单的应该就是线性方程`y = f (x)` 的可视化了。来看看如何创建这个简单的线形图。要画`Matplotlib`图形时,都需要先创建一个图形`fig` 和一个坐标轴`ax`。创建图形与坐标轴的最简单做法是:
```python
import matplotlib.pyplot as plt#导入模块
plt.style.use('seaborn-whitegrid')#设置matplotlib画图样式
fig = plt.figure()
ax = plt.axes()
```
![](2.jpg)
在`Matplotlib`中,`figure`(`plt.Figure`类的一个实例)可以被看成是个能够容纳各种坐标轴、图形、文字和标签的容器。就像你在图中看到的那样,`axes`(`plt.Axes`类的一个实例)是一个带有刻度和标签的矩形,最终会包含所有可视化的图形元素。在这里我们一般使用变量`fig`表示一个图形实例,用变量`ax`表示一个坐标轴实例。
接下来使用`ax.plot`画图,从简单的正弦曲线开始:
```python
fig = plt.figure()
ax = plt.axes()
x = np.linspace(0, 10, 1000)
ax.plot(x, np.sin(x))
```
![](3.jpg)
也可以使用`pylab`接口画图,这时图形与坐标轴都在底层执行,执行结果和上图一样:
```python
plt.plot(x, np.sin(x))
```
试想下如果我们重复调用`plot`命令会发生什么,它会在一张图中创建多条线:
```python
plt.plot(x, np.sin(x))
plt.plot(x, np.cos(x))
```
![](4.jpg)
## 设置颜色和风格
在画图的过程中通常对图形的第一次调整是调整它线条的颜色与风格。`plt.plot()`函数可以通过相应的参数设置颜色和风格,修改颜色使用`color`参数,它支持各种颜色值的字符串,具体使用如下:
```python
plt.plot(x, np.sin(x - 0), color='blue') # 标准颜色名称
plt.plot(x, np.sin(x - 1), color='g') # 缩写颜色代码rgbcmyk
plt.plot(x, np.sin(x - 2), color='0.75') # 范围在0~1的灰度值
plt.plot(x, np.sin(x - 3), color='#FFDD44') # 十六进制RRGGBB00~FF
plt.plot(x, np.sin(x - 4), color=(1.0,0.2,0.3)) # RGB元组范围在0~1
plt.plot(x, np.sin(x - 5), color='chartreuse') # HTML颜色名称
```
![](5.jpg)
常用颜色对应值:
| 取值 |颜色 |
| :------------: | :------------: |
|b|blue|
|g|green|
|r|red|
|c|cyan|
|m|magenta|
|y|yellow|
|k|black|
|w|white|
如果不指定颜色,`matplotlib`会为多条线自动循环使用一组默认的颜色。设置样式使用`linestyle`参数:
```python
plt.plot(x, x + 0, linestyle='solid')
plt.plot(x, x + 1, linestyle='dashed')
plt.plot(x, x + 2, linestyle='dashdot')
plt.plot(x, x + 3, linestyle='dotted')
#也可以用下面的简写形式
plt.plot(x, x + 4, linestyle='-') # 实线
plt.plot(x, x + 5, linestyle='--') # 虚线
plt.plot(x, x + 6, linestyle='-.') # 点划线
plt.plot(x, x + 7, linestyle=':') # 实点线
```
![](6.jpg)
还可以将`linestyle`和`color`编码组合起来,作为`plt.plot()`函数的一个非关键字参数使用:
```python
plt.plot(x, x + 0, '-g') # 绿色实线
plt.plot(x, x + 1, '--c') # 青色虚线
plt.plot(x, x + 2, '-.k') # 黑色点划线
plt.plot(x, x + 3, ':r'); # 红色实点线
```
![](7.jpg)
## 设置坐标轴上下限
虽然`matplotlib`会自动为你的图形选择最合适的坐标轴上下限,但是有时自定义坐标轴上下线可能会更好。调整坐标轴上下限最基础的方式是`plt.xlim()`和`plt.ylim()`:
```python
plt.plot(x, np.sin(x))
plt.xlim(-1, 11)
plt.ylim(-1.5, 1.5)
```
![](8.jpg)
如果你想要让坐标轴逆序显示,那么只需要逆序设置坐标轴刻度值就可以了。`matplotlib`还有一个方法是`plt.axis()`。通过传入[`xmin``xmax``ymin``ymax`]对应的值,这样就可以用一行代码设置`x`和`y`的限值:
```python
plt.plot(x, np.sin(x))
plt.axis([-1, 11, -1.5, 1.5])
```
![](9.jpg)
还支持按照图形的内容自动收紧坐标轴,不留空白区域:
```python
plt.plot(x, np.sin(x))
plt.axis('tight')
```
![](10.jpg)
## 设置图形标签
图形标签与坐标轴标题是最简单的标签,设置方法如下:
```python
plt.plot(x, np.sin(x))
plt.title("A Sine Curve")
plt.xlabel("x")
plt.ylabel("sin(x)");
```
![](11.jpg)

@ -0,0 +1,101 @@
# 4.1.3:线形图
## plot绘制散点图
散点图也是在数据科学中常用图之一,前面我们学习了使用`plt.plot/ax.plot`画线形图的方法。同样的,现在用这些函数来画散点图:
```python
x = np.linspace(0, 10, 30)
y = np.sin(x)
plt.plot(x, y, 'o', color='black')
```
![](12.jpg)
函数的第三个参数是一个字符,表示图形符号的类型。与我们之前用`-`和`--`设置线条属性类似,对应的图形标记也有缩写形式。
```python
rng = np.random.RandomState(0)
for marker in ['o', '.', ',', 'x', '+', 'v', '^', '<', '>', 's', 'd']:
plt.plot(rng.rand(5), rng.rand(5), marker,label="marker='{0}'".format(marker))
plt.legend(numpoints=1)
plt.xlim(0, 1.8);
plt.savefig("T1.png")
plt.show()
```
![](13.jpg)
常用标记如下:
| 取值 | 含义 |
| :------------: | :------------: |
| . |point marker |
| , |pixel marker |
|o|circle marker|
|v|triangle_down marker|
|^|triangle_up marker|
|<|triangle_left marker|
|>|triangle_right marker|
|1|tri_down marker|
|2|tri_up marker|
|3|tri_left marker|
|4|tri_right marker|
|s|square marker|
|p|pentagon marker|
|*|star marker|
|h|hexagon1 marker|
|H|hexagon2 marker|
|+|plus marker|
|x|x marker|
|D|diamond marker|
|d|thin_diamond marker|
|&#124;|vline marker|
|_|hline marker|
`plt.plot`函数非常灵活,可以满足各种不同的可视化配置需求。关于具体配置的完整描述,由于篇幅有限,请参考`plt.plot`文档,这里就不多介绍了,大家多多尝试就好。
## scatter画散点图
另一个可以创建散点图的函数是`plt.scatter`。它的功能非常强大,其用法与`plt.plot`函数类似:
```python
x = np.linspace(0, 10, 30)
y = np.sin(x)
plt.scatter(x, y, marker='o');
```
结果和前面`plt.plot`画的一样。`plt.scatter`和`plt.plot`的主要差别在于,前者在创建散点图时具有更高的灵活性,可以单独控制每个散点与数据匹配,也可以让每个散点具有不同的属性(大小、颜色等)。接下来画一个随机散点图,里面有各种颜色和大小的散点。为了能更好的显示重叠部分,用`alpha`参数来调整透明度:
```python
rng = np.random.RandomState(0)
x = rng.randn(100)
y = rng.randn(100)
colors = rng.rand(100)
sizes = 1000 * rng.rand(100)
plt.scatter(x, y, c=colors, s=sizes, alpha=0.3,
cmap='viridis')
plt.colorbar() # 显示颜色条
```
![](14.jpg)
这里散点的大小以像素为单位。颜色为浮点数,自动映射成颜色条(`color scale`,通过`colorbar`()显示)。当取值为浮点数时,它所对应的颜色则是对应的`colormap`上对应长度的取值。`colormap`就像以下这样的条带:
![15](15.jpg)
这样,散点的颜色与大小就可以在可视化图中显示多维数据的信息了。例如,可以使用`sklearn`程序库中的鸢尾花数据来演示。它里面有三种花,每个样本是一种花,其花瓣与花萼的长度与宽度都经过了测量:
```python
from sklearn.datasets import load_iris
iris = load_iris()
features = iris.data.T#加载数据
plt.scatter(features[0], features[1], alpha=0.2,
s=100*features[3], c=iris.target, cmap='viridis')
plt.xlabel(iris.feature_names[0])
plt.ylabel(iris.feature_names[1]);
```
![16](16.jpg)
散点图可以让我们同时看到不同维度的数据:每个点的坐标值(`x`,` y`) 分别表示花萼的长度和宽度,而点的大小表示花瓣的宽度,三种颜色对应三种不同类型的鸢尾花。这类多颜色与多特征的散点图在探索与演示数据时非常有用。
## plot与scatter效率对比
`plot`与`scatter`除了特征上的差异之外,在数据量较大时,`plot`的效率将大大高于`scatter`。这时由于`scatter`会对每个散点进行单独的大小与颜色的渲染,因此渲染器会消耗更多的资源。而在`plot`中,散点基本都彼此复制,因此整个数据集中的所有点的颜色、大小只需要配置一次。所以面对大型数据集时,`plot`方法比`scatter`方法好。

@ -0,0 +1,44 @@
# 4.1.4:直方图
## 什么是直方图
单从外表上看直方图和条形图非常相似。首先需要区分清楚概念:直方图和条形图。
- 条形图用长条形表示每一个类别,长条形的长度表示类别的频数,宽度表示表示类别。
- 直方图是一种统计报告图,形式上也是一个个的长条形,但是直方图用长条形的面积表示频数,所以长条形的高度表示频数组距,宽度表示组距,其长度和宽度均有意义。当宽度相同时,一般就用长条形长度表示频数。
## 绘制直方图
直方图一般用来描述等距数据。直观上,直方图各个长条形是衔接在一起的,表示数据间的数学关系。
```python
import matplotlib.pyplot as plt
import numpy as np
import matplotlib
# 设置matplotlib正常显示中文和负号
matplotlib.rcParams['font.sans-serif']=['SimHei'] # 用黑体显示中文
matplotlib.rcParams['axes.unicode_minus']=False # 正常显示负号
# 随机生成10000,)服从正态分布的数据
data = np.random.randn(10000)
plt.hist(data,bins=30, normed=0, facecolor="red", alpha=0.7)
# 显示横轴标签
plt.xlabel("区间")
# 显示纵轴标签
plt.ylabel("频数/频率")
# 显示图标题
plt.title("频数/频率分布直方图")
```
![](17.jpg)
| 参数 | 作用 |
| :------------: | :------------: |
| data |必选参数,绘图数据 |
|bins |直方图的长条形数目可选项默认为10 |
|normed|是否将得到的直方图向量归一化可选项默认为0代表不归一化显示频数。normed=1表示归一化显示频率。|
|facecolor|长条形的颜色|
|edgecolor|长条形边框的颜色|
|alpha|透明度|

@ -0,0 +1,51 @@
# 4.1.5:饼图
## 饼图
饼图又称圆饼图、圆形图等,它是利用圆形及圆内扇形面积来表示数值大小的图形。饼图主要用于总体中各组成部分所占比重的研究。
## 绘制饼图
`matplotlib`用来绘制饼图的函数是`pie()`。常用参数如下:
|参数 |作用 |
| :------------: | :------------: |
| x |(每一块的比例如果sum(x) > 1会使用sum(x)归一化|
|labels | 饼图外侧显示的说明文字 |
|explode |(每一块)离开中心距离|
|startangle |起始绘制角度,默认图是从x轴正方向逆时针画起,如设定=90则从y轴正方向画起|
|shadow |在饼图下面画一个阴影。默认值False即不画阴影|
|autopct |控制饼图内百分比设置|
|radius |控制饼图半径默认值为1|
|wedgeprops|字典类型可选参数默认值None。参数字典传递给wedge对象用来画一个饼图。|
```python
labels = 'A','B','C','D'
sizes = [10,20,10,60]
plt.pie(sizes,labels=labels,explode = (0,0.1,0,0),autopct='%1.1f')
plt.show()
```
![](18.jpg)
## 嵌套饼图
嵌套饼图通常被称为空心饼图图表。空心饼图形状的效果是通过`wedgeprops`参数设置馅饼楔形的宽度来实现的:
```python
fig, ax = plt.subplots()
size = 0.3
vals = np.array([[60., 32.], [37., 40.], [29., 10.]])
cmap = plt.get_cmap("tab20c")
outer_colors = cmap(np.arange(3)*4)
inner_colors = cmap(np.array([1, 2, 5, 6, 9, 10]))
ax.pie(vals.sum(axis=1), radius=1, colors=outer_colors,
wedgeprops=dict(width=size, edgecolor='w') )
ax.pie(vals.flatten(), radius=1-size, colors=inner_colors,
wedgeprops=dict(width=size, edgecolor='w'))
ax.set(aspect="equal" )
```
![](19.jpg)

@ -0,0 +1,30 @@
# 4.1.6:手动创建子图
## plt.axes创建子图
前面已经介绍过`plt.axes`函数,这个函数默认配置是创建一个标准的坐标轴,填满整张图。它还有一个可选的参数,由图形坐标系统的四个值构成。这四个值表示为坐标系的[底坐标、左坐标、宽度、高度],数值的取值范围为左下角为`0`,右上角为`1`。
下面演示在右上角创建一个画中画:
```python
x1 = plt.axes() # 默认坐标轴
ax2 = plt.axes([0.65, 0.65, 0.2, 0.2])
```
![](20.jpg)
## fig.add_axes()创建子图
面向对象画图接口中类似的命令由`fig.add_axes()`。用这个命令创建两个竖直排列的坐标轴:
```python
fig = plt.figure()
ax1 = fig.add_axes([0.1, 0.5, 0.8, 0.4],
xticklabels=[], ylim=(-1.2, 1.2))
ax2 = fig.add_axes([0.1, 0.1, 0.8, 0.4],
ylim=(-1.2, 1.2))
x = np.linspace(0, 10)
ax1.plot(np.sin(x))
ax2.plot(np.cos(x));
```
![](21.jpg)
可以看到两个紧挨着的坐标轴:上子图的(起点`y`坐标为`0.5`位置)与下子图`x`轴刻度是对应的(起点 y 坐标为 0.1,高度为 0.4)。

@ -0,0 +1,53 @@
# 4.1.7:网格子图
## plt.subplot()绘制子图
若干彼此对齐的行列子图是常见的可视化任务,`matplotlib`拥有一些可以轻松创建它们的简便方法。最底层且最常用的方法是`plt.subplot()`。这个函数在一个网格中创建一个子图该函数由三个整型参数依次为将要创建的网格子图行数、列数和索引值索引值从1开始从左上到右下递增。
```python
for i in range(1, 7):
plt.subplot(2, 3, i)
plt.text(0.5, 0.5, str((2, 3, i)),
fontsize=18, ha='center')
```
![](22.jpg)
## 调整子图之间的间隔
如上图`y`轴的刻度有的已经和前面的子图重叠,`matplotlib`提供`plt.subplots_adjust`命令调整子图之间的间隔。用面向对象接口的命令` fig.add_subplot() `可以取得同样的效果。
```python
fig = plt.figure()
fig.subplots_adjust(hspace=0.4, wspace=0.4)
for i in range(1, 7):
ax = fig.add_subplot(2, 3, i)
ax.text(0.5, 0.5, str((2, 3, i)),
fontsize=18, ha='center')
```
![](23.jpg)
这里我们通过设置` plt.subplots_adjust`的` hspace `与 `wspace`参数设置与图形高度与宽度一致的子图间距,数值以子图的尺寸为单位。
## plt.subplots创建网格
当我们需要创建一个大型网格子图时,就没办法使用前面那种亦步亦趋的方法了,尤其是当你想隐藏内部子图的`x`轴与`y`轴标题时。`matplotlib`提供了`plt.subplots()`来解决这个问题。这个函数不是用来创建单个子图的,而是用一行代码创建多个子图,并放回一个包含子图的`numpy`数组。关键参数是行数与列数以及可选参数`sharex`与`sharey`。
让我们创建一个` 2 × 3 `的网格子图,同行使用相同的`y`坐标,同列使用相同的`y`轴坐标:
```python
fig, ax = plt.subplots(2, 3, sharex='col', sharey='row')
```
![](24.jpg)
设置`sharex`与`sharey`参数后,我们就可以自动去掉网格内部子图的标签。
坐标轴实例网格的放回结果是一个`numpy`数组,这样就可以通过标准的数组取值方式轻松获取想要的坐标轴了:
```python
fig, ax = plt.subplots(2, 3, sharex='col', sharey='row')
for i in range(2):
for j in range(3):
ax[i, j].text(0.5, 0.5, str((i, j)),
fontsize=18, ha='center')
```
![](25.jpg)

@ -0,0 +1,45 @@
# 4.1.8:更复杂的排列方式
## 不规则子图
`matplotlib`还支持不规则的多行多列子图网格。`plt.GridSpec()`对象本事不能直接创建一个图形,他只是` plt.subplot()`命令可以识别的简易接口。这里创建了一个带行列间距的` 2×3 `网格:
```python
grid = plt.GridSpec(2, 3, wspace=0.4, hspace=0.3)
```
`plt.GridSpec()`支持通过类似`python`切片的语法设置子图的位置和扩展尺寸:
```python
plt.subplot(grid[0, 0])
plt.subplot(grid[0, 1:])
plt.subplot(grid[1, :2])
plt.subplot(grid[1, 2])
```
![](26.jpg)
这种灵活的网格排列方式用途十分广泛,接下来我们用它创建多轴频次直方图:
```python
# 创建一些正态分布数据
mean = [0, 0]
cov = [[1, 1], [1, 2]]
x, y = np.random.multivariate_normal(mean, cov, 3000).T
# 设置坐标轴和网格配置方式
fig = plt.figure(figsize=(6, 6))
grid = plt.GridSpec(4, 4, hspace=0.2, wspace=0.2)
main_ax = fig.add_subplot(grid[:-1, 1:])
y_hist = fig.add_subplot(grid[:-1, 0], xticklabels=[], sharey=main_ax)
x_hist = fig.add_subplot(grid[-1, 1:], yticklabels=[], sharex=main_ax)
# 主坐标轴画散点图
main_ax.plot(x, y, 'ok', markersize=3, alpha=0.2)
# 次坐标轴画频次直方图
x_hist.hist(x, 40, histtype='stepfilled',
orientation='vertical', color='gray')
x_hist.invert_yaxis()
y_hist.hist(y, 40, histtype='stepfilled',
orientation='horizontal', color='gray')
y_hist.invert_xaxis()
```
![](27.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

@ -0,0 +1,184 @@
# 4.2.1:配置颜色条
## 基本颜色演示
`Matplotlib`提供了`8`种指定颜色的方法:
- 在[`0``1`]中的浮点值的`RGB`或`RGBA`元组(例如 (`0.1`, `0.2`, `0.5`) 或 (`0.1` `0.2` `0.5` `0.3`))。`RGBA`是红色,绿色,蓝色,`Alpha`的缩写。
- 十六进制`RGB`或`RGBA`字符串 (例如: `#0F0F0F` 或者 `#0F0F0F0F`)。
- [`0`, `1`]中浮点值的字符串表示,包括灰度级(例如,`0.5`)。
- 单字母字符串,例如这些其中之一:{`b` `g``r` `c``m` `y` `k` `w`}。
- 一个 `X11`/`CSS4` (`html`) 颜色名称,例如:`blue`。
- 来自`xkcd`的颜色调研的名称,前缀为 `xkcd`: (例如:`xkcd:sky blue`)。
- 一个 `Cn` 颜色规范,即`C` 后跟一个数字,这是默认属性循环的索引(`matplotlib.rcParams[axes.prop_cycle]` 索引在艺术家对象创建时发生,如果循环不包括颜色,则默认为黑色。
- 其中一个 {`tab:blue``tab:orange``tab:green``tab:red` `tab:purple``tab:brown``tab:pink``tab:gray``tab:olive` `tab:cyan`},它们是`tab10`分类调色板中的`Tableau`颜色(这是默认的颜色循环)。
示例如下:
```python
t = np.linspace(0.0, 2.0, 201)
s = np.sin(2 * np.pi * t)
fig, ax = plt.subplots(facecolor=(.18, .31, .31))#RGB 元组
ax.set_facecolor('#eafff5')#hex字符串
ax.set_title('Voltage vs. time chart', color='0.7')#灰度字符串
ax.plot(t, s, 'xkcd:crimson')
ax.plot(t, .7*s, color='C4', linestyle='--')#CN颜色选择
ax.tick_params(labelcolor='tab:orange')
```
![](1.jpg)
## 颜色条
颜色的设置应该是在`matploblib`使用最频繁的配置之一了。`matplotlib`通过`cmap`参数为图形设置颜色条的配色方案:
```python
x = np.linspace(0, 10, 1000)
I = np.sin(x) * np.cos(x[:, np.newaxis])
plt.imshow(I, cmap='gray')#采用灰度配色的图形
```
![](2.jpg)
`matplotlib`所有可用的配色方案都在`plt.cm`命名空间中。在`Ipython`里通过`Tab`键就可以查看所有的配置方案:
```python
plt.cm<TAB>
```
## 选择配色方案
有了这么多能够选择的配色方案只是第一步,重要的是如何确定用那种方案。一般情况下我们只需要关注三种不同的配色方案:
- 顺序配色方案,由一组连续的颜色构成的配色方案(例如`binary`或`viridis` )。
- 互逆配色方案,通常由两种互补的颜色构成,表示正反两种含义(例如`RdBu `或 `PuOr` )。
- 定性配色方案,随机顺序的一组颜色(例如 `rainbow``jet` )。
`jet`是一种定性配色方案,定性配色方案在对定性数据进行可视化时的选择空间非常有限。随着图形亮度的提高,经常会出现颜色无法区分的问题。接下来将演示几个常用的配色方案。
这里通过把`jet`转换为黑白的灰度图看看:
```python
from matplotlib.colors import LinearSegmentedColormap
import matplotlib.pyplot as plt
import numpy as np
def grayscale_cmap(cmap):
"""为配色方案显示灰度图"""
cmap = plt.cm.get_cmap(cmap)
colors = cmap(np.arange(cmap.N))
# 将RGBA色转换为不同亮度的灰度值
# 参考链接http://alienryderflex.com/hsp.html
RGB_weight = [0.299, 0.587, 0.114]
luminance = np.sqrt(np.dot(colors[:, :3] ** 2, RGB_weight))
colors[:, :3] = luminance[:, np.newaxis]
return LinearSegmentedColormap.from_list(cmap.name + "_gray", colors, cmap.N)
def view_colormap(cmap):
"""用等价的灰度图表示配色方案"""
cmap = plt.cm.get_cmap(cmap)
colors = cmap(np.arange(cmap.N))
cmap = grayscale_cmap(cmap)
grayscale = cmap(np.arange(cmap.N))
fig, ax = plt.subplots(2, figsize=(6, 2),
subplot_kw=dict(xticks=[], yticks=[]))
ax[0].imshow([colors], extent=[0, 10, 0, 1])
ax[1].imshow([grayscale], extent=[0, 10, 0, 1])
view_colormap('jet')
plt.show()
```
![](3.jpg)
从上图我们观察灰度图里比较亮的那部分条纹。这些亮度变化不均匀的条纹在彩色图中对应某一段彩色区间,由于色彩太接近容易突显出数据集中不重要的部分,导致眼睛无法识别重点。更好的配色方案是`viridis`,它采用了精心设计的亮度渐变方式,这样不仅便于视觉观察,而且更清晰:
```python
view_colormap('viridis')
```
![](4.jpg)
还可以使用`cubehelix`实现彩虹效果,`cubehelix`配色方案可以可视化连续的数值:
```python
view_colormap('cubehelix')
```
![](5.jpg)
还有一种可以用两种颜色表示正反两种含义的方案,实现函数为`RdBu`
```python
view_colormap('RdBu')
```
![](6.jpg)
## 颜色条刻度的限制与扩展功能的设置
`Matplotlib`提供了丰富的颜色条配置功能。由于可以将颜色条本身仅看作是一个`plt.Axes`实例,因此前面所学的所有关于坐标轴和刻度值的格式配置技巧都可以派上用场。颜色条有一些有趣的特性。例如,我们可以缩短颜色取值的上下限,对于超出上下限的数据,通过`extend`参数用三角箭头表示比上限大的数或者比下限小的数。下面展示一张噪点图:
```python
x = np.linspace(0, 10, 1000)
I = np.sin(x) * np.cos(x[:, np.newaxis])
speckles = (np.random.random(I.shape) < 0.01)
I[speckles] = np.random.normal(0, 3, np.count_nonzero(speckles))
plt.figure(figsize=(10, 3.5))
plt.subplot(1, 2, 1)
plt.imshow(I, cmap='RdBu')
plt.colorbar()
plt.subplot(1, 2, 2)
plt.imshow(I, cmap='RdBu')
plt.colorbar(extend='both')
plt.clim(-1, 1)
```
![](7.jpg)
左边的图是用默认的颜色条刻度限制实现的效果,噪点的范围覆盖掉了我们感兴趣的数据。而右边的图形设置了颜色条的刻度上下限,并在上下限之外增加了扩展功能。
## 离散型颜色条
虽然大多数颜色条默认都是连续的,但有的时候你可能也需要表示离散数据。最简单的做法就是使用`plt.cm.get_cmap()`函数,将适当的配色方案的名称以及需要的区间数量传进去即可:
```python
x = np.linspace(0, 10, 1000)
I = np.sin(x) * np.cos(x[:, np.newaxis])
plt.imshow(I, cmap=plt.cm.get_cmap('Blues', 6))
plt.colorbar()
plt.clim(-1, 1);
```
![](8.jpg)
## 手写数字
接下来我们将学习一个比较实用的案例,手写数字可视化图。数据在`sklearn`中,包含近`2000`份`8 x 8`的手写数字缩略图。
先下载数据,然后使用`plt.imshow()`对一些图形进行可视化:
```python
from sklearn.datasets import load_digits
digits = load_digits(n_class=6)
fig, ax = plt.subplots(8, 8, figsize=(6, 6))
for i, axi in enumerate(ax.flat):
axi.imshow(digits.images[i], cmap='binary')
axi.set(xticks=[], yticks=[])
```
![](9.jpg)
由于每个数字都由`64`像素的色相构成,因此可以将每个数字看成是一个位于`64`
维空间的点,即每个维度表示一个像素的亮度。但是想通过可视化来描述如此高维度的空间是非常困难的。一种解决方案是通过降维技术,在尽量保留数据内部重要关联性的同时降低数据的维度,例如流形学习。下面展示如何用流形学习将这些数据投影到二维空间进行可视化:
```python
from sklearn.datasets import load_digits
from sklearn.manifold import Isomap
iso = Isomap(n_components=2)
digits = load_digits(n_class=6)
projection = iso.fit_transform(digits.data)
plt.scatter(projection[:, 0], projection[:, 1], lw=0.1,
c=digits.target, cmap=plt.cm.get_cmap('cubehelix', 6))
plt.colorbar(ticks=range(6), label='digit value')
plt.clim(-0.5, 5.5)
```
![](10.png)
我们使用了离散型颜色条来显示结果,调整`ticks`和`clim`参数来改善颜色条。这个结果向我们展示了一些数据集的有趣特性。比如数字`5`与数字`3`在投影中有大面积重叠,说明一些手写的`5`与`3`难以区分,因此自动分类算法也更容易搞混它们。其它的数字,像数字`0`与数字`1`,隔得特别远,说明两者不太可能出现混淆。

@ -0,0 +1,90 @@
# 4.2.2:设置注释
## 添加注释
为了使我们的可视化图形让人更加容易理解,在一些情况下辅之以少量的文字提示和标签可以更恰当地表达信息。`matplotlib`可以通过`plt.txt`/`ax.txt`命令手动添加注释,它们可以在具体的`x/y`坐标上放文字:
```python
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl
plt.rcParams['font.sans-serif']=['simhei']
plt.rcParams['font.family']='sans-serif'
plt.rcParams['axes.unicode_minus']=False#画图可中文
style = dict(size=10, color='gray')
#绘制图形
plt.plot([5,21,12,53,7,6,43,1,69])
#设置注释
plt.text(0, 5, "第一个点",ha='center', **style)
plt.text(1, 21, "第二个点",ha='center', **style)
plt.text(2, 12, "第三个点",ha='center', **style)
plt.text(3, 53, "第四个点",ha='center', **style)
plt.text(4, 7, "第五个点",ha='center', **style)
plt.text(5, 6, "第六个点",ha='center', **style)
plt.text(6, 43, "第七个点",ha='center', **style)
plt.text(7, 1, "第八个点",ha='center', **style)
plt.text(8, 69, "第九个点",ha='center', **style)
plt.show()
```
![](11.jpg)
`plt.text`方法需要一个`x`轴坐标、一个`y`轴坐标、一个字符串和一些可选参数,比如文字的颜色、字号、风格、对齐方式等其它文字属性。这里用了`ha="center"``ha`是设置水平对齐方式。其他常用参数如下:
1. `fontsize`设置字体大小,默认`12`,可选参数 `xx-smallx-smallsmallmediumlargex-largexx-large`
2. `fontweight`设置字体粗细,可选参数 `lightnormalmediumsemiboldboldheavyblack`
3. `fontstyle`设置字体类型,可选参数`normalitalicoblique]` 。
4. `verticalalignment`设置垂直对齐方式 ,可选参数 :`centertopbottombaseline`。
5. `horizontalalignment`设置水平对齐方式,可选参数:`leftrightcenter`。
6. `rotation`(旋转角度)可选参数为:`verticalhorizontal` 也可以为数字,数字代表旋转的角度。
7. `alpha`透明度参数值0至1之间。
8. `backgroundcolor`标题背景颜色。
还可以通过`plt.annotate()`函数来设置注释,该函数既可以创建文字,也可以创建箭头,而且它创建的箭头能够进行非常灵活的配置。
```python
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(0, 6)
y = x * x
plt.plot(x, y, marker='o')
for xy in zip(x, y):
plt.annotate("(%s,%s)" % xy, xy=xy, xytext=(-20, 10), textcoords='offset points')
plt.annotate('max', xy=(5, 25), xytext=(4.3, 15),arrowprops=dict(facecolor='black', shrink=0.05))
plt.show()
```
![](12.jpg)
这里设置了两个注释,一个显示了点的坐标,另一个显示了最高点描述`max`。`annotate`的第一个参数为注释文本字符串,第二个为被注释的坐标点,第三个为注释文字的坐标位置。
## 文字、坐标变换
上面的例子将文字注释放在目标数据的位置上,有的时候可能需要将文字放在与数据无关的位置上,比如坐标轴或图形,`matplotlib`中通过调整坐标变换`transorm`参数来实现:
```python
fig, ax = plt.subplots()
ax.axis([0, 10, 0, 10])
ax.text(1, 5, ". Data: (1, 5)", transform=ax.transData)
ax.text(0.5, 0.1, ". Axes: (0.5, 0.1)", transform=ax.transAxes)
ax.text(0.2, 0.2, ". Figure: (0.2, 0.2)", transform=fig.transFigure)
plt.show()
```
![](13.jpg)
默认情况下,图中的字符是在其对应的坐标位置。通过设置`transform`参数修改位置,`transData`坐标用`x,y`的标签作为数据坐标;`transAxes`坐标以白色矩阵左下角为原点,按坐标轴尺寸的比例呈现坐标;`transFigure`坐标类似以灰色矩阵左下角为原点,按坐标轴尺寸的比例呈现坐标。

@ -0,0 +1,119 @@
# 4.2.3:自定义坐标刻度
## 主次要刻度
学习前最好先对`matplotlib`图形的对象层级有深入了解。`matplotlib`的`figure`对象是一个盛放图形元素的包围盒。可以将每个`matplotlib`对象都看成是子对象的容器,每个`figure`都包含`axes`对象,每个`axes`对象又包含其他表示图形内容的对象,比如`xaxis/yaxis`,每个属性包含构成坐标轴的线条、刻度和标签的全部属性。
每一个坐标轴都有主次要刻度,主要刻度要比次要刻度更大更显著,而次要刻度往往更小。
```python
import matplotlib.pyplot as plt
import numpy as np
ax = plt.axes(xscale='log', yscale='log')
plt.show()
```
![](14.jpg)
可以看到主要刻度都显示为一个较大的刻度线和标签,而次要刻度都显示为一个较小的可读性,不显示标签。
## 隐藏刻度与标签
最常用的刻度/标签格式化操作可能就是隐藏刻度与标签了,可以通过`plt.NullLocator()`和`plt.NullFormatter()`实现。
示例如下:
```python
ax = plt.axes()
ax.plot(np.random.rand(50))
ax.yaxis.set_major_locator(plt.NullLocator())
ax.xaxis.set_major_formatter(plt.NullFormatter())
plt.show()
```
![](15.jpg)
这里`x`轴的标签隐藏了但是保留了刻度线,`y`轴的刻度和标签都隐藏了。有的图片中都不需要刻度线,比如下面这张包含人脸的图形:
```python
fig, ax = plt.subplots(5, 5, figsize=(5, 5))
fig.subplots_adjust(hspace=0, wspace=0)
# 从scikit-learn获取一些人脸照片数据
from sklearn.datasets import fetch_olivetti_faces
faces = fetch_olivetti_faces().images
for i in range(5):
for j in range(5):
ax[i, j].xaxis.set_major_locator(plt.NullLocator())
ax[i, j].yaxis.set_major_locator(plt.NullLocator())
ax[i, j].imshow(faces[10 * i + j], cmap="bone")
plt.show()
```
![](16.png)
## 花哨的刻度格式
`matplotlib`默认的刻度格式可以满足大部分的需求。虽然默认配置已经很不错了,但是有时候可能需要更多的功能,比如正弦曲线和余弦曲线,默认情况下刻度为整数,如果将刻度与网格线画在`π`的倍数上图形会更加自然,可以通过设置一个`multipleLocator`来实现将刻度放在你提供的数值倍数上:
```python
fig, ax = plt.subplots()
x = np.linspace(0, 3 * np.pi, 1000)
ax.plot(x, np.sin(x), lw=3, label='Sine')
ax.plot(x, np.cos(x), lw=3, label='Cosine')
# 设置网格、图例和坐标轴上下限
ax.grid(True)
ax.legend(frameon=False)
ax.axis('equal')
ax.set_xlim(0, 3 * np.pi)
ax.xaxis.set_major_locator(plt.MultipleLocator(np.pi / 2))
ax.xaxis.set_minor_locator(plt.MultipleLocator(np.pi / 4))
plt.show()
```
![](17.jpg)
`matplotlib`还支持用数学符号来做刻度,在数学表达式两侧加上美元符号`$`,这样就可以方便地显示数学符号和数学公式。可以用`plt.FuncFormatter`来实现,用一个自定义函数设置不同刻度标签的显示:
```python
def format_func(value, tick_number):
# 找到π/2的倍数刻度
N = int(np.round(2 * value / np.pi))
if N == 0:
return "0"
elif N == 1:
return r"$\pi/2$"
elif N == 2:
return r"$\pi$"
elif N % 2 > 0:
return r"${0}\pi/2$".format(N)
else:
return r"${0}\pi$".format(N // 2)
ax.xaxis.set_major_formatter(plt.FuncFormatter(format_func))
```
![](18.jpg)
## 格式生成器与定位器
前面已经介绍了一些格式生成器和定位器,这里再用表格简单总结一些内置的格式生成器和定位器:
| 定位器 |描述 |
| :------------: | :------------: |
|NullLocator |无刻度 |
| FixedLocator | 刻度位置固定 |
| IndexLocator | 用索引作为定位器 |
| LinearLocator | 从min 到max 均匀分布刻度 |
| LogLocator |从min 到max 按对数分布刻度 |
|MultipleLocator |刻度和范围都是基数的倍数 |
|MaxNLocator |为最大刻度找到最优位置 |
|AutoMinorLocator | 次要刻度的定位器 |
|格式生成器 |描述 |
| :------------: | :------------: |
|NullFormatter |刻度上无标签 |
|IndexFormatter | 将一组标签设置为字符串 |
|FixedFormatter |手动为刻度设置标签 |
|FuncFormatter |用自定义函数设置标签 |
|FormatStrFormatter |为每个刻度值设置字符串格式 |
|ScalarFormatter |为标量值设置标签 |
|LogFormatter |对数坐标轴的默认格式生成器 |

@ -0,0 +1,117 @@
# 4.2.4:配置文件与样式表
## 配置图形
我们可以通过修个单个图形配置,使得最终图形比原来的图形更好看。可以为每个单独的图形进行个性化设置。这里我们通过手动调整,将`matplotlib`土到掉渣的默认直方图修改成美图:
```python
import matplotlib.pyplot as plt
plt.style.use('classic')
import numpy as np
x = np.random.randn(1000)
plt.hist(x);
# 用灰色背景
ax = plt.axes(facecolor='#E6E6E6')
ax.set_axisbelow(True)
# 画上白色的网格线
plt.grid(color='w', linestyle='solid')
# 隐藏坐标轴的线条
for spine in ax.spines.values():
spine.set_visible(False)
ax.xaxis.tick_bottom()
ax.yaxis.tick_left()
# 弱化刻度与标签
ax.tick_params(colors='gray', direction='out')
for tick in ax.get_xticklabels():
tick.set_color('gray')
for tick in ax.get_yticklabels():
tick.set_color('gray')
# 设置频次直方图轮廓色与填充色
ax.hist(x, edgecolor='#E6E6E6', color='#EE6666')
plt.show()
```
![](19.jpg)
## 修改默认配置rcParams
通过手动配置确实能达到我们想要的效果,但是如有很多个图形,我们肯定不希望对每一个图都这样手动配置一番。`matplotlib`作为一个强大的工具当然有方法可以让我们只配置一次默认图形,就可以应用到所有图形上。这个方法就是通过修改默认配置`rcParams`。`matplotlib`在每次加载的时候,都会定义一个运行时配置`rc`,其中包含了我们创建的图形元素的默认风格。
```python
from matplotlib import cycler
import matplotlib.pyplot as plt
import numpy as np
colors = cycler('color',['#EE6666', '#3388BB', '#9988DD','#EECC55', '#88BB44', '#FFBBBB'])
plt.rc('axes', facecolor='#E6E6E6', edgecolor='none',axisbelow=True, grid=True, prop_cycle=colors)
plt.rc('grid', color='w', linestyle='solid')
plt.rc('xtick', direction='out', color='gray')
plt.rc('ytick', direction='out', color='gray')
plt.rc('patch', edgecolor='#E6E6E6')
plt.rc('lines', linewidth=2)
x = np.random.randn(1000)
plt.hist(x)#画直方图
plt.show()
for i in range(4):
plt.plot(np.random.rand(10))#折线图
plt.show()
```
![](20.jpg)
![](21.jpg)
所有`rc`设置都存储在一个名为`matplotlib.rcParams`的类字典变量中,可以通过这个变量来查看我们的配置。`rc`的第一个参数是希望自定义的对象,如`figure`、`axes`、`grid`等。其后可以跟上一系列的关键字参数。
## 样式表
`matplotlib`从`1.4`版本中增加了一个非常好用的`style`模块,里面包含了大量的新式默认样式表,还支持创建和打包自己的风格。通过`plt.style.available`命令可以看到所有可用的风格。
```python
plt.style.available[:5]#查看前5个风格样式
'''
输出:['bmh', 'classic', 'dark_background', 'fast', 'fivethirtyeight']
'''
```
使用某种样式表的基本方法为`plt.style.use('stylename')`,这样就改变后面代码的所有风格。支持组合样式,通过传递样式列表可以轻松组合这些样式。如果需要,也可以使用风格上下文管理器临时更换风格:
```python
with plt.style.context('stylename'):
make_a_plot()
```
首先创建一个画两种基本图形的函数:
```python
def hist_and_lines():
np.random.seed(0)
fig, ax = plt.subplots(1, 2, figsize=(11, 4))
ax[0].hist(np.random.randn(1000))
for i in range(3):
ax[1].plot(np.random.rand(10))
ax[1].legend(['a', 'b', 'c'], loc='lower left')
plt.show()
```
![](22.jpg)
再通过修改风格绘制图形:
```python
with plt.style.context('fivethirtyeight'):
hist_and_lines()
```
![](23.jpg)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save