算法思想

Apriori算法是第一个关联规则挖掘算法,也是最经典的算法。

首先找出所有的频繁项集,这些项集出现的频繁性至少和预定义的最小支持度一样。然后由频繁项集产生强关联规则,这些规则必须满足最小支持度和最小置信度。然后使用第1步找到的频繁项集产生期望的规则,产生只包含集合的项的所有规则,其中每一条规则的右部只有一项,这里采用的是中规则的定义。一旦这些规则被生成,那么只有那些大于用户给定的最小置信度的规则才被留下来。为了生成所有频繁项集,使用了递归的方法。

相关术语

支持度(support):support(A=>B) = P(A∪B),表示A和B同时出现的概率。

置信度(confidence):confidence(A=>B)=support(A∪B) / support(A),表示A和B同时出现的概率占A出现概率的比值。

频繁项集:数据集中经常一起出现的变量。

强关联规则:满足最小支持度和最小置信度的关联规则。

数据集及预处理

环境:python3.6

工具:jupyter notebook

部分原始数据集如下所示:


apriori算法简介 python apriori算法python包_apriori算法简介 python

原始数据集

部分属性字段太长,所以先做了一个映射表,用数字字符去替换掉这些长文字字符,并保证每个数字字符替换的长文字字符是唯一的。处理后如下所示:


apriori算法简介 python apriori算法python包_apriori算法简介 python_02

映射后数据集

用pandas将数据读入进来:

import pandas as pd
import numpy as np
import sys
# 加载数据集 成列表[],[]
dataSets=pd.read_csv('sheet1.csv',encoding='utf-8')
dataset2=dataSets.convert_objects(convert_numeric=True)  # 转换数据类型
dataset1=dataset2.fillna(0)  #缺失值填充
dataset1['婚姻状况']=dataset1['婚姻状况'].astype(np.int64)
dataSet=dataset1.values.tolist()
dataSet

convert_objects(convert_numeric=True) 表示转换数据类型,如object对象可将其转为float,fillna()是对一些缺失值用0填充,再将数据中“婚姻状况”这一列的数据类型转成int64。

pandas读取的数据是dataframe格式,对于挖掘频繁项方法,需要先将数据转成列表形式,调用了values.tolist()方法。处理后数据集如下所示:

[[52, 800, 100, 0, 300, 1002],
 [49, 800, 100, 0, 200, 1019],
 [29, 800, 100, 0, 200, 1007],
 [31, 600, 113, 1, 200, 1004],
 [56, 600, 113, 1, 200, 1004],
 ...]

可调用pandas里面很好用的describe()和info()方法查看一下整体数据集的基本情况:


apriori算法简介 python apriori算法python包_apriori_03

describe方法

describe()可以输出数据对每个属性的计数、均值、标准差、最小值、最大值、前25%、前50%等等,很直观。


apriori算法简介 python apriori算法python包_python_04

info方法

info()可以输出数据各个属性的类型,以及数据大小,每一列的计数,通过这个信息我们可以对缺失的数据项做一些填充,对不一致的数据类型可以做一些转换,达到我们想要的形式。

实现apriori算法

# 构建候选项集C1
def createC1(dataSet):
    C1 = [ ]
    for transaction in dataSet:
        for item in transaction:
            if not [item] in C1:
                C1.append( [item] ) #store all the item unrepeatly
    C1.sort()
    #return map(frozenset, C1)#frozen set, user can't change it.
    return list(map(frozenset,C1))

先构建C1候选集,使用的格式frozenset类型。frozenset是指被“冰冻”的集合,即用户不能修改它们,列出所有的候选项:

测试:

C1=createC1(dataSet) 
C1

输出: 

[frozenset({0}),
 frozenset({1}),
 frozenset({2}),
 frozenset({3}),
 frozenset({4}),
 frozenset({5}),
 frozenset({6}),
 frozenset({7}),
 frozenset({21}),
 ...
 frozenset({1059}),
 frozenset({1060})]
# 生成K1项集的频繁项集
def scanD(D,Ck,minSupport):
#参数:数据集、候选项集列表 Ck以及感兴趣项集的最小支持度 minSupport
    ssCnt={}
    for tid in D:#遍历数据集
        for can in Ck:#遍历候选项
            if can.issubset(tid):#判断候选项中是否含数据集的各项
                #if not ssCnt.has_key(can): 
                if not can in ssCnt:
                    ssCnt[can]=1 #不含设为1
                else: ssCnt[can]+=1#有则计数加1
    numItems=float(len(D)) #数据集大小
    retList = []#L1初始化
    supportData = {}#记录候选项中各个数据的支持度
    for key in ssCnt:
        support = ssCnt[key]/numItems#计算支持度
        if support >= minSupport:
            retList.insert(0,key)#满足条件加入L1中
        supportData[key] = support
    return retList, supportData

测试:

D=list(map(set,dataSet))
L1,supportdata0=scanD(D,C1,0.3)
L1

输出:

[frozenset({136}),
 frozenset({1}),
 frozenset({1000}),
 frozenset({200}),
 frozenset({800}),
 frozenset({0})]

在最小支持度(minsupport)为0.3的情况下,构建出的L1频繁项集。

接下来实现完整的apriori:

# 创建候选项集CK
def aprioriGen(Lk, k): #组合,向上合并
    #creates Ck 参数:频繁项集列表 Lk 与项集元素个数 k
    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: #若两个集合的前k-2个项相同时,则将两个集合合并
                retList.append(Lk[i] | Lk[j]) #set union
    return retList

def apriori(dataSet, minSupport = 0.3):
    C1 = createC1(dataSet)
    D = list(map(set, dataSet)) #python3
    L1, supportData = scanD(D, C1, minSupport)#单项最小支持度判断 0.5,生成L1
    L = [L1]
    k = 2
    while (len(L[k-2]) > 0):#创建包含更大项集的更大列表,直到下一个大的项集为空
        Ck = aprioriGen(L[k-2], k)#Ck
        Lk, supK = scanD(D, Ck, minSupport)#get Lk
        supportData.update(supK)
        L.append(Lk)
        k += 1
    return L, supportData

测试:

L,supportdata=apriori(dataSet,minSupport = 0.3)
L

在最小支持度为0.3的情况下,输出所有频繁项集

输出:

[[frozenset({136}),
  frozenset({1}),
  frozenset({1000}),
  frozenset({200}),
  frozenset({800}),
  frozenset({0})],
 [frozenset({136, 800}),
  frozenset({136, 200}),
  frozenset({200, 1000}),
  frozenset({1, 800}),
  frozenset({1, 200}),
  frozenset({0, 200}),
  frozenset({200, 800})],
 [frozenset({136, 200, 800}), frozenset({1, 200, 800})],
 []]

可以看到,最多只生成了3频繁项,4频繁项为空。

接下来引入置信度获取规则:

def generateRules(L, supportData, minConf=0.7):
    #频繁项集列表、包含那些频繁项集支持数据的字典、最小可信度阈值
    bigRuleList = [] #存储所有的关联规则
    for i in range(1, len(L)):  #只获取有两个或者更多集合的项目,从1,即第二个元素开始,L[0]是单个元素的
        # 两个及以上的才可能有关联一说,单个元素的项集不存在关联问题
        for freqSet in L[i]:
            H1 = [frozenset([item]) for item in freqSet]
            #该函数遍历L中的每一个频繁项集并对每个频繁项集创建只包含单个元素集合的列表H1
            if (i > 1):
            #如果频繁项集元素数目超过2,那么会考虑对它做进一步的合并
                rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf)
            else:#第一层时,后件数为1
                calcConf(freqSet, H1, supportData, bigRuleList, minConf)# 调用函数2
    return bigRuleList

def calcConf(freqSet, H, supportData, brl, minConf=0.7):
    #针对项集中只有两个元素时,计算可信度
    prunedH = []#返回一个满足最小可信度要求的规则列表
    for conseq in H:#后件,遍历 H中的所有项集并计算它们的可信度值
        conf = supportData[freqSet]/supportData[freqSet-conseq] #可信度计算,结合支持度数据
        if conf >= minConf:
            print (freqSet-conseq,'-->',conseq,'conf:',conf)
            #如果某条规则满足最小可信度值,那么将这些规则输出到屏幕显示
            brl.append((freqSet-conseq, conseq, conf))#添加到规则里,brl 是前面通过检查的 bigRuleList
            prunedH.append(conseq)#同样需要放入列表到后面检查
    return prunedH


def rulesFromConseq(freqSet, H, supportData, brl, minConf=0.7):
    #参数:一个是频繁项集,另一个是可以出现在规则右部的元素列表 H
    m = len(H[0])
    if (len(freqSet) > (m + 1)): #频繁项集元素数目大于单个集合的元素数
        Hmp1 = aprioriGen(H, m+1)#存在不同顺序、元素相同的集合,合并具有相同部分的集合
        Hmp1 = calcConf(freqSet, Hmp1, supportData, brl, minConf)#计算可信度
        if (len(Hmp1) > 1):    
        #满足最小可信度要求的规则列表多于1,则递归来判断是否可以进一步组合这些规则
            rulesFromConseq(freqSet, Hmp1, supportData, brl, minConf)

测试:

rules=generateRules(L,supportdata,0.7)
rules

输出:

frozenset({136}) --> frozenset({800}) conf: 0.7766210196337238
frozenset({136}) --> frozenset({200}) conf: 0.9901913875598086
frozenset({1000}) --> frozenset({200}) conf: 0.9627698830179791
frozenset({1}) --> frozenset({800}) conf: 0.7442194695110624
frozenset({1}) --> frozenset({200}) conf: 0.990477400936617
frozenset({0}) --> frozenset({200}) conf: 0.9202183740377532
frozenset({800}) --> frozenset({200}) conf: 0.9738033721332872
frozenset({200}) --> frozenset({800}) conf: 0.7641836281451676
frozenset({136}) --> frozenset({800, 200}) conf: 0.7721250618709783
frozenset({1}) --> frozenset({200, 800}) conf: 0.7381285257569685

以上为置信度取0.7时获取到的所有规则。

相对来说,apriori算法只要明白了算法原理,实现代码大都千篇一律,网上一找一大堆,重要的是怎么将数据集处理成你需要的格式并挖掘出价值,在我处理的过程中,数据预处理占了绝大一部分的时间,最后调节参数跑模型代码这些花不了多久,以及最后挖掘出的关联规则是不是你预期的, 可能还需要结合其他例如统计分析方法共同发掘潜在价值。