典型关联分析(Canonical Correlation Analysis)

1. 问题

      在线性回归中,我们使用直线来拟合样本点,寻找n维特征向量X和输出结果(或者叫做label)Y之间的线性关系。其中clip_image002clip_image004。然而当Y也是多维时,或者说Y也有多个特征时,我们希望分析出X和Y的关系。

      当然我们仍然可以使用回归的方法来分析,做法如下:

      假设clip_image002[1]clip_image006,那么可以建立等式Y=AX如下

      clip_image008

      其中clip_image010,形式和线性回归一样,需要训练m次得到m个clip_image012

      这样做的一个缺点是,Y中的每个特征都与X的所有特征关联,Y中的特征之间没有什么联系。

      我们想换一种思路来看这个问题,如果将X和Y都看成整体,考察这两个整体之间的关系。我们将整体表示成X和Y各自特征间的线性组合,也就是考察clip_image014clip_image016之间的关系。

      这样的应用其实很多,举个简单的例子。我们想考察一个人解题能力X(解题速度clip_image018,解题正确率clip_image020)与他/她的阅读能力Y(阅读速度clip_image022,理解程度clip_image024)之间的关系,那么形式化为:

      clip_image026clip_image028

      然后使用Pearson相关系数

      clip_image030

      来度量u和v的关系,我们期望寻求一组最优的解a和b,使得Corr(u, v)最大,这样得到的a和b就是使得u和v就有最大关联的权重。

      到这里,基本上介绍了典型相关分析的目的。

2. CCA表示与求解

      给定两组向量clip_image032clip_image034(替换之前的x为clip_image032[1],y为clip_image034[1]),clip_image032[2]维度为clip_image036clip_image034[2]维度为clip_image038,默认clip_image040。形式化表示如下:

      clip_image042

      clip_image044是x的协方差矩阵;左上角是clip_image032[3]自己的协方差矩阵;右上角是clip_image046;左下角是clip_image048,也是clip_image050的转置;右下角是clip_image034[3]的协方差矩阵。

      与之前一样,我们从clip_image032[4]clip_image034[4]的整体入手,定义

      clip_image052clip_image054

      我们可以算出u和v的方差和协方差:

      clip_image056clip_image058clip_image060

      上面的结果其实很好算,推导一下第一个吧:

      clip_image062

      最后,我们需要算Corr(u,v)了

      clip_image064

      我们期望Corr(u,v)越大越好,关于Pearson相关系数,《数据挖掘导论》给出了一个很好的图来说明:

      典型关联分析(Canonical Correlation Analysis)_第1张图片

      横轴是u,纵轴是v,这里我们期望通过调整a和b使得u和v的关系越像最后一个图越好。其实第一个图和最后一个图有联系的,我们可以调整a和b的符号,使得从第一个图变为最后一个。

      接下来我们求解a和b。

      回想在LDA中,也得到了类似Corr(u,v)的公式,我们在求解时固定了分母,来求分子(避免a和b同时扩大n倍仍然符号解条件的情况出现)。这里我们同样这么做。

      这个优化问题的条件是:

      Maximize clip_image068

      Subject to: clip_image070

      求解方法是构造Lagrangian等式,这里我简单推导如下:

      clip_image072

      求导,得

      clip_image074

      clip_image076

      令导数为0后,得到方程组:

      clip_image078

      clip_image080

      第一个等式左乘clip_image082,第二个左乘clip_image084,再根据clip_image086,得到

      clip_image088

      也就是说求出的clip_image090即是Corr(u,v),只需找最大clip_image090[1]即可。

      让我们把上面的方程组进一步简化,并写成矩阵形式,得到

      clip_image092

      clip_image094

      写成矩阵形式

      clip_image096

      令

      clip_image098

      那么上式可以写作:

      clip_image100

      显然,又回到了求特征值的老路上了,只要求得clip_image102的最大特征值clip_image104,那么Corr(u,v)和a和b都可以求出。

      在上面的推导过程中,我们假设了clip_image106clip_image108均可逆。一般情况下都是可逆的,只有存在特征间线性相关时会出现不可逆的情况,在本文最后会提到不可逆的处理办法。

      再次审视一下,如果直接去计算clip_image102[1]的特征值,复杂度有点高。我们将第二个式子代入第一个,得

      clip_image110

      这样先对clip_image112求特征值clip_image114和特征向量clip_image116,然后根据第二个式子求得b。

待会举个例子说明求解过程。

      假设按照上述过程,得到了clip_image090[2]最大时的clip_image118clip_image120。那么clip_image118[1]clip_image120[1]称为典型变量(canonical variates),clip_image090[3]即是u和v的相关系数。

      最后,我们得到u和v的等式为:

      clip_image122clip_image124

      我们也可以接着去寻找第二组典型变量对,其最优化条件是

      Maximize       clip_image126

      Subject to:   clip_image128

                        clip_image130

      其实第二组约束条件就是clip_image132

      计算步骤同第一组计算方法,只不过是clip_image090[4]clip_image112[1]的第二大特征值。

      得到的clip_image134clip_image136其实也满足

      clip_image138clip_image140

      总结一下,i和j分别表示clip_image142clip_image144得到结果

      clip_image146

      clip_image148

3. CCA计算例子

      我们回到之前的评价一个人解题和其阅读能力的关系的例子。假设我们通过对样本计算协方差矩阵得到如下结果:

      clip_image150

      clip_image152

      然后求clip_image112[2],得

      clip_image154

      这里的A和前面的clip_image156中的A不是一回事(这里符号有点乱,不好意思)。

      然后对A求特征值和特征向量,得到

      clip_image158

      然后求b,之前我们说的方法是根据clip_image160求b,这里,我们也可以采用类似求a的方法来求b。

      回想之前的等式

      clip_image092[1]

      clip_image094[1]

      我们将上面的式子代入下面的,得

      clip_image162

      然后直接对clip_image164求特征向量即可,注意clip_image164[1]clip_image112[3]的特征值相同,这个可以自己证明下。

      不管使用哪种方法,

      clip_image166

      clip_image168

      这里我们得到a和b的两组向量,到这还没完,我们需要让它们满足之前的约束条件

      clip_image170

      这里的clip_image172应该是我们之前得到的VecA中的列向量的m倍,我们只需要求得m,然后将VecA中的列向量乘以m即可。

      clip_image174

      这里的clip_image176是VecA的列向量。

      clip_image178

      因此最后的a和b为:

      clip_image180

      第一组典型变量为

      clip_image182

      相关系数

      clip_image184

      第二组典型变量为

      clip_image186

      相关系数

      clip_image188

      这里的clip_image190(解题速度),clip_image192(解题正确率),clip_image194(阅读速度),clip_image196(阅读理解程度)。他们前面的系数意思不是特征对单个u或v的贡献比重,而是从u和v整体关系看,当两者关系最密切时,特征计算时的权重。

4. Kernel Canonical Correlation Analysis(KCCA)

      通常当我们发现特征的线性组合效果不够好或者两组集合关系是非线性的时候,我们会尝试核函数方法,这里我们继续介绍Kernel CCA。

      在《支持向量机-核函数》那一篇中,大致介绍了一下核函数,这里再简单提一下:

      当我们对两个向量作内积的时候

      clip_image198

      我们可以使用clip_image200clip_image202来替代clip_image204clip_image206,比如原来的clip_image204[1]特征向量为clip_image208,那么

      我们可以定义

      典型关联分析(Canonical Correlation Analysis)_第2张图片

      如果clip_image202[1]clip_image200[1]的构造一样,那么

      clip_image212

                        clip_image214

      这样,仅通过计算x和y的内积的平方就可以达到在高维空间(这里为clip_image216)中计算clip_image218clip_image220内积的效果。

      由核函数,我们可以得到核矩阵K,其中

      clip_image222

      即第clip_image224行第clip_image226列的元素是第clip_image224[1]个和第clip_image226[1]个样例在核函数下的内积。

      一个很好的核函数定义:

      clip_image228

      其中样例x有n个特征,经过clip_image218[1]变换后,从n维特征上升到了N维特征,其中每一个特征是clip_image230

      回到CCA,我们在使用核函数之前

      clip_image232clip_image234

      这里假设x和y都是n维的,引入核函数后,clip_image236clip_image238变为了N维。

      使用核函数后,u和v的公式为:

      clip_image240

      clip_image242

      这里的c和d都是N维向量。

      现在我们有样本clip_image244,这里的clip_image246表示样本x的第i个样例,是n维向量。

根据前面说过的相关系数,构造拉格朗日公式如下:

      典型关联分析(Canonical Correlation Analysis)_第3张图片

      其中

      clip_image250

      clip_image252

      然后让L对a求导,令导数等于0,得到(这一步我没有验证,待会从宏观上解释一下)

      clip_image254

      同样对b求导,令导数等于0,得到

      clip_image256

      求出c和d干嘛呢?c和d只是clip_image258的系数而已,按照原始的CCA做法去做就行了呗,为了再引入clip_image260clip_image262

      回答这个问题要从核函数的意义上来说明。核函数初衷是希望在式子中有clip_image264,然后用K替换之,根本没有打算去计算出实际的clip_image258[1]。因此即是按照原始CCA的方式计算出了c和d,也是没用的,因为根本有没有实际的clip_image258[2]让我们去做clip_image266。另一个原因是核函数比如高斯径向基核函数可以上升到无限维,N是无穷的,因此c和d也是无穷维的,根本没办法直接计算出来。我们的思路是在原始的空间中构造出权重clip_image260[1]clip_image262[1],然后利用clip_image258[3]clip_image260[2]clip_image262[2]上升到高维,他们在高维对应的权重就是c和d。

      虽然clip_image260[3]clip_image262[3]是在原始空间中(维度为样例个数M),但其作用点不是在原始特征上,而是原始样例上。看上面得出的c和d的公式就知道。clip_image260[4]通过控制每个高维样例的权重,来控制c。

      好了,接下来我们看看使用clip_image260[5]clip_image262[4]后,u和v的变化

      clip_image268

      clip_image270

      clip_image272表示可以将第i个样例上升到的N维向量,clip_image274意义可以类比原始CCA的x。

      鉴于这样表示接下来会越来越复杂,改用矩阵形式表示。

      clip_image276

      简写为

      clip_image278

      其中X(M×N)为

            clip_image280

      我们发现

      clip_image282

      我们可以算出u和v的方差和协方差(这里实际上事先对样本clip_image204[2]clip_image284做了均值归0处理):

      clip_image286

      clip_image288

      clip_image290

      这里clip_image274[1]clip_image292维度可以不一样。

      最后,我们得到Corr(u,v)

      clip_image294

      可以看到,在将clip_image018[1]clip_image020[1]处理成clip_image296clip_image298后,得到的结果和之前形式基本一样,只是将clip_image044[1]替换成了两个K乘积。

      因此,得到的结果也是一样的,之前是

      clip_image100[1]

      其中

      clip_image098[1]

      引入核函数后,得到

      clip_image100[2]

      其中

      clip_image300

      注意这里的两个w有点区别,前面的clip_image116[1]维度和x的特征数相同,clip_image302维度和y的特征数相同。后面的clip_image304维度和x的样例数相同,clip_image306维度和y的样例数相同,严格来说“clip_image304[1]维度=clip_image306[1]维度”。

5. 其他话题

      1、当协方差矩阵不可逆时,怎么办?

      要进行regularization。

      一种方法是将前面的KCCA中的拉格朗日等式加上二次正则化项,即:

      clip_image308

      这样求导后得到的等式中,等式右边的矩阵一定是正定矩阵。

      第二种方法是在Pearson系数的分母上加入正则化项,同样结果也一定可逆。

      典型关联分析(Canonical Correlation Analysis)_第4张图片

      2、求Kernel矩阵效率不高怎么办?

      使用Cholesky decomposition压缩法或者部分Gram-Schmidt正交化法,。

      3、怎么使用CCA用来做预测?

典型关联分析(Canonical Correlation Analysis)_第5张图片

  

     典型关联分析(Canonical Correlation Analysis)_第6张图片

      4、如果有多个集合怎么办?X、Y、Z…?怎么衡量多个样本集的关系?

      这个称为Generalization of the Canonical Correlation。方法是使得两两集合的距离差之和最小。可以参考文献2。

6. 参考文献

      1、 http://www.stat.tamu.edu/~rrhocking/stat636/LEC-9.636.pdf

      2、 Canonical correlation analysis: An overview with application to learning methods. David R. Hardoon , Sandor Szedmak and John Shawe-Taylor

      3、 A kernel method for canonical correlation analysis. Shotaro Akaho

      4、 Canonical Correlation a Tutorial. Magnus Borga

      5、 Kernel Canonical Correlation Analysis. Max Welling


关联性分析源码如下:

#coding=utf-8

'''
Created on Mar 24, 2011
Ch 11 code
@author: Peter


使用Apriori算法进行关联分析

关联分析
Apriori算法
优点:易编码实现。
缺点:在大数据集上可能较慢。
适用数据类型:数值型或者标称型数据。
    关联分析是一种在大规模数据集中寻找有趣关系的任务。这些关系可以有两种形式:频繁项
集或者关联规则。频繁项集(frequent item sets)是经常出现在一块的物品的集合,关联规则
( association rules)暗示两种物品之间可能存在很强的关系。

    一个项集的支持度(support)被定义为数据集中包含该项集的记录所占的比例。从图11-1中
可以得到,{豆奶}的支持度为4/5。而在5条交易记录中有3条包含{豆奶,尿布},因此王豆奶,尿
布}的支持度为3/5。支持度是针对项集来说的,因此可以定义一个最小支持度,而只保留满足最
小支持度的项集。
    可信度或置信度(confidence)是针对一条诸如{尿布}‘{葡萄酒}的关联规则来定义的。这
条规则的可信度被定义为“支持度({尿布,葡萄酒”伎持度({尿布””。从图11-1中可以看到,由
于{尿布,葡萄酒}的支持度为3/5,尿布的支持度为4/5,所以“尿布‘葡萄酒”的可信度为3/4=0.75 0
这意味着对于包含“尿布”的所有记录,我们的规则对其中75%的记录都适用。


总结:
    关联分析是用于发现大数据集中元素间有趣关系的一个工具集,可以采用两种方式来量化这
些有趣的关系。第一种方式是使用频繁项集,它会给出经常在一起出现的元素项。第二种方式是
关联规则,每条关联规则意味着元素项之间的“如果……那么”关系。
    发现元素项间不同的组合是个十分耗时的任务,不可避免需要大量昂贵的计算资源,这就需
要一些更智能的方法在合理的时间范围内找到频繁项集。能够实现这一目标的一个方法是Apriori算法
它使用Apriori原理来减少在数据库上进行检查的集合的数目。Apriori原理是说如果一个元
素项是不频繁的
过组合满足最小
现的频率。
,那么那些包含该元素的超集也是不频繁的。Apriori算法从单元素项集开始,通
支持度要求的项集来形成更大的集合。支持度用来度量一个集合在原始数据中出
关联分析可以用在许多不同物品上。
商店中的商品以及网站的访问页面是其中比较常见的例
子。关联分析也曾用于查看选举人及法官的投票历史。
    每次增加频繁项集的大小,Apriori算法都会重新扫描整个数据集。当数据集很大时,这会显
著降低频繁项集发现的速度。下一章会介绍FPgrowth算法①,和Apriori算法相比,该算法只需要
对数据库进行两次遍历,能够显著加快发现繁项集的速度。


'''
from numpy import *
'''
Apriori算法的一般流程
(1)收集数据:使用任意方法。
(2)准备数据:任何数据类型都可以,因为我们只保存集合。
(3)分析数据:使用任意方法。
(4)训练算法:使用Apriori算法来找到频繁项集。
(5)刚试算法:不需要测试过程。
(6)使用算法:用于发现频繁项集以及物品之间的关联规则。

Apriori原理是说如果某个项集是频繁的,那么它的所有子集也是频繁的。
对于图11-2给出的例子,这意味着如果{{0,1 }是频繁的,那么{0}, {1}也一定是频繁的。这个原理
直观上并没有什么帮助,但是如果反过来看就有用了,也就是说如果一个项集是非频繁集,那么
它的所有超集也是非频繁的

    11.1节提到,关联分析的目标包括两项:发现频繁项集和发现关联规则。首先需要找到频繁
项集,然后才能获得关联规则。本节将只关注于发现频繁项集。
    Apriori算法是发现频繁项集的一种方法。Apriori}法的两个输人参数分别是最小支持度和数
据集。该算法首先会生成所有单个物品的项集列表。接着扫描交易记录来查看哪些项集满足最小
支持度要求,那些不满足最小支持度的集合会被去掉。然后,对剩下来的集合进行组合以生成包
含两个元素的项集。接下来,再重新扫描交易记录,去掉不满足最小支持度的项集。该过程重复
进行直到所有项集都被去掉。


生成候选集
对数据集中的每条交易记录tran
对每个候选项集can:
    检查一下can是否是tran的子集:
    如果是,则增加can的计数值
    对每个候选项集:
如果其支持度不低于最小值,则保留该项集
返回所有频繁项集列表


上述程序包含三个函数。第一个函数loadDataSet()创建了一个用于测试的简单数据集,
另外两个函数分别是createCl()和scanD ( )。
    不言自名,函数createCl()将构建集合C1o C1是大小为1的所有候选项集的集合。Apriori
算法首先构建集合C1,然后扫描数据集来判断这些只有一个元素的项集是否满足最小支持度的要
求。那些满足最低要求的项集构成集合L1。而L1中的元素相互组合构成C2, C2再进一步过滤变
为L2。到这里,我想读者应该明白了该算法的主要思路。
    因此算法需要一个函数createCl()来构建第一个候选项集的列表C1。由于算法一开始是从输
人数据中提取候选项集列表,所以这里需要一个特殊的函数来处理,而后续的项集列表则是按一定
的格式存放的。这里使用的格式就是Python中的frozense淡型。frozense提指被“冰冻”的集合,就
是说它们是不可改变的,即用户不能修改它们。这里必须要使用frozensetf}TJ不是se璞型,因为之后
必须要将这些集合作为字典键值使用,使用frozense呵以实现这一点,而set}[1做不到。
    首先创建一个空列表c1,它用来存储所有不重复的项值。接下来遍历数据集中的所有交易记
录。对每一条记录,遍历记录中的每一个项。如果某个物品项没有在c1中出现,则将其添加到
C1中。这里并不是简单地添加每个物品项,而是添加只包含该物品项的一个列表①。这样做的目
的是为每个物品项构建一个集合。因为在Apriori算法的后续处理中,需要做集合操作。Python不
能创建只有一个整数的集合,因此这里实现必须使用列表(有兴趣的读者可以试一下)。这就是
我们使用一个由单物品列表组成的大列表的原因。最后,对大列表进行排序并将其中的每个单元
素列表映射到frozenset(),最后返回frozenset的列表Do
    程序清单11-1中的第二个函数是scanD ( ),它有三个参数,分别是数据集Ck、包含候选集合
的列表以及感兴趣项集的最小支持度minSupport。该函数用于从C1生成L1。另外,该函数会返
回一个包含支持度值的字典以备后用。scanD ( )函数首先创建一个空字典ssCn七,然后遍历数据
集中的所有交易记录以及C1中的所有候选集。如果C1中的集合是记录的一部分,那么增加字典
中对应的计数值。这里字典的键就是集合。当扫描完数据集中的所有项以及所有候选集时,就需
要计算支持度。不满足最小支持度要求的集合不会输出。函数也会先构建一个空列表,该列表包
含满足最小支持度要求的集合。下一个循环遍历字典中的每个元素并且计算支持度.。如果支持
度满足最小支持度要求,则将字典元素添加到retList中。可以使用语句retL乒st.
insert (0 , key)在列表的首部插人任意新的集合。当然也不一定非要在首部插人,这只是为了
让列表看起来有组织。函数最后返回最频繁项集的支持度supportData
'''
def loadDataSet():
    return [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]

def createC1(dataSet):
    C1 = []
    for transaction in dataSet:
        for item in transaction:
            if not [item] in C1:
                C1.append([item])
                
    C1.sort()
    return map(frozenset, C1)#use frozen set so we
                            #can use it as a key in a dict    

def scanD(D, Ck, minSupport):
    ssCnt = {}
    for tid in D:
        for can in Ck:
            if can.issubset(tid):
                if not ssCnt.has_key(can): ssCnt[can]=1
                else: ssCnt[can] += 1
    numItems = float(len(D))
    retList = []
    supportData = {}
    for key in ssCnt:
        support = ssCnt[key]/numItems
        if support >= minSupport:
            retList.insert(0,key)
        supportData[key] = support
    return retList, supportData
'''
整个Aprior滇法的伪代码如下:
当集合中项的个数大于0时
    构建一个k个项组成的候选项集的列衣
检查数据以确认每个项集都是频繁的
保留频繁项集并构建k+l项组成的候选项集的列农

程序清单11-2包含两个函数aprioriGen()与apriori(),其中主函数是apriori(),它会
调用aprioriGen()来创建候选项集Ck,函数aprioriGen()
的输人参数为频繁项集列表Lk与项集元素个数k,输出为Ck,首先创建一个
空列表,然后计算Lk中的元素数目。接下来,’比较Lk中的每一个元素与其他元素,
这可以通过两个for循环来实现。紧接着,取列表中的两个集合进行比较。如果这两个集合的前面k-2个元
素都相等,那么就将这两个集合合成一个大小为k的集合.。这里使用集合的并操作来完成,在
Python中对应操作符|。


    上面所有的操作都被封装在apriori()函数中。给该函数传递一个数据集以及一个支持度,
函数会生成候选项集的列表,这通过首先创建C1然后读人数据集将其转化为D(集合列表)来完
成。程序中使用map函数将set()映射到da七aSet列表中的每一项。接下来,使用程序清单11-1
中的scanD ( )函数来创建L1,并将L1放人列表L中。L会包含L1 } L2 , L3 ...。现在有了L1,后面
会继续找L2, L3...,这可以通过 while循环来完成,它创建包含更大项集的更大列表,直到下一
个大的项集为空。如果这听起来让人有点困惑的话,那么等一下你会看到它的工作流程。首先使
用aprioriGen()来创建Ck,然后使用scanD()基于Ck来创建Lko Ck是一个候选项集列表,然
后scanD()会遍历Ck,丢掉不满足最小支持度要求的项集)a Lk列表被添加到L,同时增加k的
值,重复上述过程。最后,当Lk为空时,程序返回L并退出。



'''
def aprioriGen(Lk, k): #creates Ck
    retList = []
    lenLk = len(Lk)
    for i in range(lenLk):
        for j in range(i+1, lenLk): 
            L1 = list(Lk[i])[:k-2]; L2 = list(Lk[j])[:k-2]
            L1.sort(); L2.sort()
            if L1==L2: #if first k-2 elements are equal
                retList.append(Lk[i] | Lk[j]) #set union
    return retList

def apriori(dataSet, minSupport = 0.5):
    C1 = createC1(dataSet)
    D = map(set, dataSet)
    L1, supportData = scanD(D, C1, minSupport)
    L = [L1]
    k = 2
    while (len(L[k-2]) > 0):
        Ck = aprioriGen(L[k-2], k)
        Lk, supK = scanD(D, Ck, minSupport)#scan DB to get Lk
        supportData.update(supK)
        L.append(Lk)
        k += 1
    return L, supportData
'''
关联规则生成函数

可以首先从一个频繁项集开始,接着创建一个规则列表,其中规则右部只包含一个
元素,然后对这些规则进行测试。
接下来合并所有剩余规则来创建一个新的规则列表,其中规则
右部包含两个元素。这种方法也被称作分级法。

    上述程序中包含三个函数。第一个函数generateRules()是主函数,它调用其他两个函数。
其他两个函数是rulesFromConseq()和calcConf(),分别用于生成候选规则集合以及对规则
进行评估。
    函数generateRules()有3个参数:频繁项集列表、包含那些频繁项集支持数据的字典、最
小可信度阑值。函数最后要生成一个包含可信度的规则列表,后面可以基于可信度对它们进行排
序。这些规则存放在bigRuleList中。如果事先没有给定最小可信度的闽值,那么默认值设为
0.7o generateRules)的另两个输入参数正好是程序清单11-2中函数apriori()的输出结果。
该函数遍历L中的每一个频繁项集并对每个频繁项集创建只包含单个元素集合的列表H1。因为无
法从单元素项集中构建关联规则,所以要从包含两个或者更多元素的项集开始规则构建过程Oo
如果从集合{{0,1,2}开始,那么H1应该是〔{0},{1},{2}]。如果频繁项集的元素数目超过2,那么会考
虑对它做进一步的合并。具体合并可以通过函数rulesFromConseq()来完成,后面会详细讨论
合并过程。如果项集中只有两个元素,那么使用函数calcConf()来计算可信度值。
    我们的目标是计算规则的可信度以及找到满足最小可信度要求的规则。所有这些可以使用函
数calcConf()来完成,而程序清单11-3中的其余代码都用来准备规则。函数会返回一个满足最
小可信度要求的规则列表,为了保存这些规则,需要创建一个空列表prunedH。接下来,遍历H
中的所有项集并计算它们的可信度值。可信度计算时使用。upportData中的支持度数据。通过
导人这些支持度数据,可以节省大量计算时间。如果某条规则满足最小可信度值,那么将这些规
则输出到屏幕显示。通过检查的规则也会被返回,并被用在下一个函数rulesFromConseq()中。
同时也需要对列表brl进行填充,而brl是前面通过检查的bigRuleListo

    为从最初的项集中生成更多的关联规则,可以使用rulesFromConseq()函数。该函数有2
个参数:一个是频繁项集,另一个是可以出现在规则右部的元素列表H。函数先计算H中的频繁集
大小m)。接下来查看该频繁项集是否大到可以移除大小为m的子集。如果可以的话,则将其移
除。可以使用程序清单11-2中的函数aprioriGen()来生成H中元素的无重复组合O。该结果会
存储在Hmp1中,这也是下一次迭代的H列表。Hmp1包含所有可能的规则。可以利用calcConf ( )
来测试它们的可信度以确定规则是否满足要求。如果不止一条规则满足要求,那么使用Hmp1迭
代调用函数rulesFromConseq(,来判断是否可以进一步组合这些规则

'''
def generateRules(L, supportData, minConf=0.7):  #supportData is a dict coming from scanD
    bigRuleList = []
    for i in range(1, len(L)):#only get the sets with two or more items
        for freqSet in L[i]:
            H1 = [frozenset([item]) for item in freqSet]
            if (i > 1):
                rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf)
            else:
                calcConf(freqSet, H1, supportData, bigRuleList, minConf)
    return bigRuleList         

def calcConf(freqSet, H, supportData, brl, minConf=0.7):
    prunedH = [] #create new list to return
    for conseq in H:
        conf = supportData[freqSet]/supportData[freqSet-conseq] #calc confidence
        if conf >= minConf: 
            print freqSet-conseq,'-->',conseq,'conf:',conf
            brl.append((freqSet-conseq, conseq, conf))
            prunedH.append(conseq)
    return prunedH

def rulesFromConseq(freqSet, H, supportData, brl, minConf=0.7):
    m = len(H[0])
    if (len(freqSet) > (m + 1)): #try further merging
        Hmp1 = aprioriGen(H, m+1)#create Hm+1 new candidates
        Hmp1 = calcConf(freqSet, Hmp1, supportData, brl, minConf)
        if (len(Hmp1) > 1):    #need at least two sets to merge
            rulesFromConseq(freqSet, Hmp1, supportData, brl, minConf)
            
def pntRules(ruleList, itemMeaning):
    for ruleTup in ruleList:
        for item in ruleTup[0]:
            print itemMeaning[item]
        print "           -------->"
        for item in ruleTup[1]:
            print itemMeaning[item]
        print "confidence: %f" % ruleTup[2]
        print       #print a blank line
        
'''
收集数据:构建美国国会投票记录的事务数据集
(1)收集数据:使用votesmart模块来访问投票记录。
(2)准备数据:构造一个函数来将投票转化为一串交易记录。
(3)分析数据:在Python提示符下查看准备的数据以确保其正确性。
(#)训练算法:使用本章早先的apriori()和generateRules()函数来发现投票记录中
  的有趣信息。
(5)测试算法:不适用,即没有刚试过程。
(6)使用算法:这里只是出于娱乐的目的,不过也可以使用分析结果来为政治竞选活动服
  务,或者预测选举官员会如何投票。


    上述程序中导人了votesmart模块并通过引人sleep函数来延迟API调用。getActions工ds()
函数会返回存储在recent20bills.txt文件中议案的action工d。程序先导人API key,然后创建两个
空列表。这两个列表分别用来返回actionsId和标题。首先打开recent20bills.tx改件,对每一行内不
同元素使用tab进行分隔,之后进人try-excep七模块。由于在使用外部”I时可能会遇到错误,
并且也不想让错误占用数据获取的时间,上述try-except模块调用是一种非常可行的做法。所
以,首先尝试使用getBill()方法来获得一个billDetail对象。接下来遍历议案中的所有行为,
来寻找有投票数据的行为.。在Passage阶段与Amendment Vote(修正案投票)阶段都会有投票数
据,要找的就是它们。现在,在行政级别上也有一个Passage阶段,但那个阶段并不包含任何投票
数据,所以要确保这个阶段是发生在众议院.。如果确实如此,程序就会将actionId打印出来
并将它添加到action工dList中。同时,也会将议案的标题添加到billTitleList中。如果在
API调用时发生错误,就不会执行actionIdList的添加操作。一旦有错误就会执行except模块
并将错误信息输出。最后,程序会休眠1秒钟,以避免对Votesmart.org网站的过度频繁访问。程序
活行结束时,action工dList与billTitleLis七会被返回用于进一步的处理。

    可以看到action工d显示了出来,它同时也被添加到action工dList中输出,以后我们可以使
用这些action工d了。如果程序运行错误,则尝试使用try..excep七代码来捕获错误。我自己就曾
经在获取所有actiondId时遇到一个错误。接下里可以继续来获取这些action工d的投票信息。
    选举人可以投是或否的表决票,也可以弃权。需要一种方法来将这些上述信息转化为类似于
项集或者交易数据库之类的东西。前面提到过,一条交易记录数据只包含一个项的出现或不出现
信息,并不包含项出现的次数。基于上述投票数据,可以将投票是或否看成一个元素。
    美国有两个主要政党:共和党与民主党。下面也会对这些信息进行编码并写到事务数据库中。
幸运的是,这些信息在投票数据中已经包括。下面给出构建事务数据库的流程:首先创建一个字
典,字典中使用政客的名字作为键值。当某政客首次出现时,将他及其所属政党(民主党或者共
和党)添加到字典中,这里使用。来代表民主党,1来代表共和党。下面介绍如何对投票进行编码。
对每条议案创建两个条目:bill+'Yea'以及bill十·Nay'。该方法允许在某个政客根本没有投
票时也能合理编码

'''            
from time import sleep
from votesmart import votesmart
votesmart.apikey = 'a7fa40adec6f4a77178799fae4441030'
#votesmart.apikey = 'get your api key first'
def getActionIds():
    actionIdList = []; billTitleList = []
    fr = open('recent20bills.txt') 
    for line in fr.readlines():
        billNum = int(line.split('\t')[0])
        try:
            billDetail = votesmart.votes.getBill(billNum) #api call
            for action in billDetail.actions:
                if action.level == 'House' and \
                (action.stage == 'Passage' or action.stage == 'Amendment Vote'):
                    actionId = int(action.actionId)
                    print 'bill: %d has actionId: %d' % (billNum, actionId)
                    actionIdList.append(actionId)
                    billTitleList.append(line.strip().split('\t')[1])
        except:
            print "problem getting bill %d" % billNum
        sleep(1)                                      #delay to be polite
    return actionIdList, billTitleList
'''
函数getTransList()会创建一个事务数据库.于是在此基础上可以使用前面的Apriori代码
来生成频繁项集与关联规则。该函数也会创建一个标题列表,所以很容易了解每个元素项的含义。
一开始使用前两个元素“Repbulican”和“Democratic" 创建一个含义列表itemMeaning。当想
知道某些元素项的具体含义时,需要做的是以元素项的编号作为索引访问itemMeaning即可。
接下来遍历所有议案,然后在议案标题后添加Nay(反对) 或者Yea(同意)并将它们放人
itemMeaning列表中,接下来创建一个空字典用于加人元素项,然后遍历函数getActionIds()
返回的每一个action工do遍历时要做的第一件事是休眠,即在for循环中一开始调用sleep ( )
函数来延迟访问,这样做可以避免过于频繁的API调用。接着将运行结果打印出来,以便知道程
序县否在正常工作。再接着通过try..except块来使用VotesmartAPI获取某个特定action工d
相关的所有投票信息。然后,遍历所有的投票信息(通常voteList会超过400个投票)。在遍历
时,使用政客的名字作为字典的键值来填充transDicto,如果之前没有遇到该政客,那么就要获
取他的政党信息。字典中的每个政客都有一个列表来存储他投票的元素项或者他的政党信息。接
下来会看到该政客是否对当前议案投了赞成(Yea )或反对(Nay)票。如果他们之前有投票,
那么不管是投赞成票还是反对票,
这些信息都将添加到列表中。如果API调用中发生了什么错误,
except模块中的程序就会被调用并将错误信息孰输出到屏幕上,之后函数仍然继续执行。最后,
程序返回事务字典transDict及元素项含义类表itemMeaningo






'''        
def getTransList(actionIdList, billTitleList): #this will return a list of lists containing ints
    itemMeaning = ['Republican', 'Democratic']#list of what each item stands for
    for billTitle in billTitleList:#fill up itemMeaning list
        itemMeaning.append('%s -- Nay' % billTitle)
        itemMeaning.append('%s -- Yea' % billTitle)
    transDict = {}#list of items in each transaction (politician) 
    voteCount = 2
    for actionId in actionIdList:
        sleep(3)
        print 'getting votes for actionId: %d' % actionId
        try:
            voteList = votesmart.votes.getBillActionVotes(actionId)
            for vote in voteList:
                if not transDict.has_key(vote.candidateName): 
                    transDict[vote.candidateName] = []
                    if vote.officeParties == 'Democratic':
                        transDict[vote.candidateName].append(1)
                    elif vote.officeParties == 'Republican':
                        transDict[vote.candidateName].append(0)
                if vote.action == 'Nay':
                    transDict[vote.candidateName].append(voteCount)
                elif vote.action == 'Yea':
                    transDict[vote.candidateName].append(voteCount + 1)
        except: 
            print "problem getting actionId: %d" % actionId
        voteCount += 2
    return transDict, itemMeaning


你可能感兴趣的:(典型关联分析(Canonical Correlation Analysis))