TANE算法代码实现

文章目录

  • TANE算法
    • 导入包
    • 等价类划分:list_duplicates
    • 发现右方集:findCplus
    • 计算函数依赖:compute_dependencies
    • 计算右方集:computeCplus
    • 有效性测试:validfd
    • e(X)计算:computeE
    • 超键检查:check_superkey
    • 修剪属性格:prune
    • 生成下一级:generate_next_level
    • 生成剥离分区:stripped_product
    • 计算剥离分区:computeSingletonPartitions
    • 执行
      • 获取数据属性
      • 计算剥离分区
      • TANE主要算法
      • 测试

TANE算法

导入包

from pandas import *
from collections import defaultdict#字典类
import numpy as NP
import sys
# import pdb
# pdb.set_trace()

等价类划分:list_duplicates

def list_duplicates(seq):
    tally = defaultdict(list)
    for i,item in enumerate(seq):#这两行核心代码实现了合并相同元组:等价类的划分
        tally[item].append(i)#只有item一样才能实现合并
    return ((key,locs) for key,locs in tally.items() 
                            if len(locs)>0)
#测试:对属性集A的划分结果
for element in list_duplicates(data2D['A'].tolist()):
    print(element)

发现右方集:findCplus

Question:计算cplus的过程只用到了借助上层的cplus,那么刚开始符合cplus定义的那一层是如何定义的呢?从第几层开始呢?如何通过调试函数观察变化过程?

def findCplus(x): # this computes the Cplus of x as an intersection of smaller Cplus sets
    global dictCplus
    thesets=[]
    for a in x:
        #计算的过程涉及到了递归调用
        if x.replace(a,'') in dictCplus.keys():
            temp = dictCplus[x.replace(a,'')]
        else:
            temp=findCplus(x.replace(a,'')) # compute C+(X\{A}) for each A at a time
        #dictCplus[x.replace(a,'')] = temp
        thesets.insert(0, set(temp))
    if list(set.intersection(*thesets)) == []:
        cplus = []
    else:
        cplus = list(set.intersection(*thesets))  # compute the intersection in line 2 of pseudocode
    return cplus

计算函数依赖:compute_dependencies

  1. 初始时候,dictCplus只有{‘NULL’: listofcolumns},计算第1层时,由第0层交集,因此,结果为R,真正的计算cplus是从第二层开始
  2. Q 按照代码的逻辑,cplus(x)中应该不包含x以外的元素,为什么输出的结果不合格, 因为未通过FD验证,只有一个属性,肯定没法通过。当调用第二层时,输出的结果自然合格了。
  3. 对L2计算依赖时,AB,BD在代码里面怎么体现这种组合的属性?

    通过lever体现,lever为当前层属性集的列表,L[i+1] = generate_next_level(L[i])

  4. 为什么说A∈X,如果A∈C+(X)并且X\{A}->A成立,则可认为X\{A}->A为最小非平凡函数依赖。
    • A∈X,X\{A}->A依赖成立
    • A∈C+(X),A不依赖X的子集。解释如下:若A不属于Cplus,则存在B,X\B->B,那么X->A显然不是最小,因为有更新的X\B->A。若A属于Cplus,则不存在这样的B,即A不依赖X的子集,于是,X->A是最小的。
def compute_dependencies(level, listofcols):#参数为Li层,即当前层的属性?还是直接A,B,C,D
    global dictCplus#属性-右方集dict
    global finallistofFDs#FD List
    global listofcolumns#属性集List
    #FUN1:计算所有X∈Li的右方集Cplus
    #通过上层结点{A})计算当前层的每个X的Cplus(X)
    #或通过computeCplus
    print(listofcols)
    for x in level:
        thesets=[]
        for a in x:
            if x.replace(a,'') in dictCplus.keys():#如果Cplus(X\A)已经在当前右方集List中
                temp = dictCplus[x.replace(a,'')]#temp存入的是Cplus(X\A)---即X\A的右集合
            else:#否则,计算右方集
                temp=computeCplus(x.replace(a,'')) # compute C+(X\{A}) for each A at a time
                dictCplus[x.replace(a,'')] = temp#存入dictCplus中
            thesets.insert(0, set(temp))#通过set,将temp转换为集合,再将该对象插入到列表的第0个位置
        if list(set.intersection(*thesets)) == []:#set.intersection(set1, set2 ... etc)求并集
            dictCplus[x] = []
        else:
            dictCplus[x] = list(set.intersection(*thesets)) # 即伪代码第二行中的计算交集
    #FUN2:找到最小函数依赖
    #并对Cplus进行剪枝(最小性剪枝):1.删掉已经成立的2.取掉必不可能的 留下的仍然是“有希望的”''' 
    for x in level:
        
        for a in x:
            if a in dictCplus[x]:#即如果A取得X与Cplus的交集
                #if x=='BCJ': print "dictCplus['BCJ'] = ", dictCplus[x]
                if validfd(x.replace(a,''), a): # 即X\{A}->A函数依赖成立
                    finallistofFDs.append([x.replace(a,''), a]) # line 6
                    print ("compute_dependencies:level:%s adding key FD: %s"%(level,[x.replace(a,''),a]))
                    dictCplus[x].remove(a)  # line 7
                    listofcols=listofcolumns[:]#copy listofcolumns 实则为接下来剪枝做准备
                    for j in x: # this loop computes R\X
                        if j in listofcols: listofcols.remove(j)#此时listofcools更新
                         
                    for b in listofcols: # 在 C+(X)删掉所有属于R\X即不属于X的元素,即所留下的Cpuls元素全部属于X
#                         print(b)
#                         print (dictCplus[x])
                        if b in dictCplus[x]:
#                             print(b)
#                             print (dictCplus[x])
                            dictCplus[x].remove(b)
    for x in level:
        print (x)
        print (dictCplus[x])
#测试:但为什么L1层的右方集和理解中不一样呢?---L1是由公式算出来的
compute_dependencies(L[2], listofcolumns[:])
['A', 'B', 'C', 'D']
AB
['A']
BD
['D']

计算右方集:computeCplus

  1. 疑问:NULL和‘’什么区别
def computeCplus(x): 
    # this computes the Cplus from the first definition in section 3.2.2 of TANE paper. 
    #output should be a list of single attributes
    global listofcolumns#始终=[A,B,C,D]
    listofcols = listofcolumns[:]#copy
    if x=='': return listofcols # because C+{phi} = R(φ=Phi)
    cplus = []
    for a in listofcols:#A∈R并且满足如下条件:
        for b in x:
            temp = x.replace(a,'')
            temp = temp.replace(b,'')
            if not validfd(temp, b):
                cplus.append(a)
    return cplus
print(listofcolumns)
computeCplus('C')
['A', 'B', 'C', 'D']
['A', 'B', 'C', 'D']

有效性测试:validfd

def validfd(y,z):#验证Y->是否符合函数依赖
    if y=='' or z=='': return False
    ey = computeE(y)#计算误差e(X)
    eyz = computeE(y+z)#计算误差e(XU{A})
    if ey == eyz :#引理3.5
        return True
    else:
        return False

e(X)计算:computeE

误差e(X)的计算公式如下:
e ( X ) = ( ∥ π X ^ ∥ − ∣ π X ^ ∣ ) / ∣ r ∣ e(X)=\left(\left\|\widehat{\pi_{X}}\right\|-\left|\widehat{\pi_{X}}\right|\right) /|r| e(X)=(πX πX )/r

  • ∥ π X ^ ∥ \left\|\widehat{\pi_{X}}\right\| πX 表示剥离分区 π X ^ \widehat{\pi_{X}} πX 中所有等价类的大小的和,用doublenorm表示
  • ∣ π X ^ ∣ \left|\widehat{\pi_{X}}\right| πX 表示剥离分区的阶:内部有多少个等价类,用len(dictpartitions[''.join(sorted(x))])表示
def computeE(x):#属性集为x
    global totaltuples#元组数
    global dictpartitions#关于每个属性集的剥离分区
    doublenorm = 0
    for i in dictpartitions[''.join(sorted(x))]:#''.join(sorted(x))先将x排序--即BCA to ABC,再转换为字符串,用''隔开
        #测试 print(i) # i为剥离分区中的等价类,对于testABCD-test,x=D,i取[0,3]、[1,2]
        doublenorm = doublenorm + len(i)#doublenorm存储所有等价类的大小的和
    e = (doublenorm-len(dictpartitions[''.join(sorted(x))]))/float(totaltuples)
    return e

#测试 testdataABCD.csv
print(computeE('A'))#4-2 / 4 = 0.5
print(computeE('C'))#0-0/4 = 0
print(dictpartitions)

超键检查:check_superkey

  1. (dictpartitions[x] == [[]]) or (dictpartitions[x] == [])区别在哪
#测试
dictpartitions
def check_superkey(x):
    global dictpartitions#关于每个属性集的剥离分区
    if ((dictpartitions[x] == [[]]) or (dictpartitions[x] == [])):#如果剥离分区为空,则说明π_x只有单例等价类组成
        return True
    else:
        return False

修剪属性格:prune

减掉不满足条件的属性集X,对于某些X,若X->A不成立,则X的超集也不同理,我们从属性包含格中删除X,从而不用考虑超集,节省判断时间

  1. 步骤梳理

    • 第1、2、3行体现的关于右方集的剪枝策略:若Cplus(X)=φ,则删除X----X无用,剪掉是为了不再考虑超集
    • 第4-8行体现的是关于键的剪枝策略:若X为(超)键,则删除X----X有用,但已经用过了,剪掉是为了不再考虑超集
  2. 如果X为超键,如何确定X\{A}->A是否为最小函数依赖呢?
    设X是超键,设A∈X 。依赖项X \ {A}→A是有效且最小的,当且仅当X \ {A}是键且对所有B∈X, A∈C+(X \ {B})时。
    更直观的说是保证:

    • X\{A}->A成立(非平凡):X\A只能是键,X\A->A是才有效的。反之,如果X\A不是键,则X\A->A必然不成立。
    • A不依赖X的真子集(最小性):所有B∈X,所有B∈X, A∈C+(X \ {B})(由cplus定义 or 引理3.1可知)
  3. 疑问:line4~line7

    • 通过compute_dependencies过程C+(X)只保留了X中的元素,那么,C+(X)\X不就是空了吗。

      A:不空只能说明,对于所有属于X与C+(X)交集的A,X\A->A均不成立。否则,若有一项成立,根据line8,C+(X)只保留了X中的元素。因为对不属于X的元素注定没有了希望,必不可能最小。所以对Cplus进行修剪,去掉没有希望的B,以及去掉已经实现愿望的A。

    • Question:修剪过程中,依赖X→A在第7行输出当且仅当如果X是超键,A∈C+ (X)\X 并且对于所有的B∈X而言,A∈C + ((X+A)\ {B})。?这句话如何解释,如何与引理4.2扯上关系。

      Answer:进行到prune这一步时,C+(X)内属于X的属性均已经考虑过(并且均不成立,如果成立的话C+ (X)\X 就是空了),因此需要考虑不属于X的属性,即C+ (X)\X,那么,既然X是超键,则X+A也是超键,(X+A)相当于引理4.2中的((X))那么X相当于引理4.2中的((X\A)),B与4.2中B相同,(显然这里B!=A),因此((X \ {A}))→A为最小,即X->A为最小!

def prune(level):
    global dictCplus#属性集的右方集
    global finallistofFDs#FD
    for x in level: # line 1
        '''Angle1:右方集修剪'''
        if dictCplus[x]==[]: # line 2
            level.remove(x) # line 3:若Cplus(X)=φ,则删除X
        '''Angle2:键修剪'''
        if check_superkey(x): # line 4   ### should this check for a key, instead of super key??? Not sure.
            temp = dictCplus[x][:]# 初始化temp 为 computes cplus(x)
            #1. 求得C+(X) \ X
            for i in x: # this loop computes C+(X) \ X
                if i in temp: temp.remove(i)# temp为C+(X) \ X
            #2. line 5:for each a ∈ Cplus(X)\X do        
            for a in temp: 
                thesets=[]
                #3. 计算Cplus((X+A)\ {B})
                for b in x:
                    if not( ''.join(sorted((x+a).replace(b,''))) in dictCplus.keys()): 
                    # ''.join(sorted((x+a).replace(b,''))表示的就是XU{a}\{b}
                        dictCplus[''.join(sorted((x+a).replace(b,'')))] = findCplus(''.join(sorted((x+a).replace(b,''))))
                    thesets.insert(0,set(dictCplus[''.join(sorted((x+a).replace(b,'')))]))
                #4. 计算Cplus((X+A)\ {B})交集,判断a是否在其中
                if a in list(set.intersection(*thesets)): # line 6 set.intersection(*thesets)为求所有thesets元素的并集
                    finallistofFDs.append([x, a]) # line 7
                    #测试
                    print ("pruning:level:%s adding key FD: %s"%(level,[x,a]))
            # 只要x是超键,就要剪掉x。
            if x in level:level.remove(x)#如果此时在line3中已经删除X,则不执行.

生成下一级:generate_next_level

def generate_next_level(level):
    #首先令 L[i+1] 这一层为空集
    nextlevel=[]
    for i in range(0,len(level)): # 选择一个属性集
        for j in range(i+1, len(level)): # 将其与后面的所有属性集进行比较
            #如果这两个元素属于同一个前缀块,那么就可以合并:只有最后一个属性不同,其余都相同
            if ((not level[i]==level[j]) and level[i][0:-1]==level[j][0:-1]):  # i.e. line 2 and 3
                x = level[i]+level[j][-1]  #line 4  X = Y U Z      
                flag = True
                for a in x: # this entire for loop is for the 'for all' check in line 5
                    if not(x.replace(a, '') in level):
                        flag=False
                if flag==True:
                    nextlevel.append(x)
                    #计算新的属性集X上的剥离分区
                    #=pi_y*pi_z(其中y为level[i],z为level[j])
                    stripped_product(x, level[i] , level[j] ) # compute partition of x as pi_y * pi_z (where y is level[i] and z is level[j])
    return nextlevel
#测试generate_next_level
Ltest=['A','B','C','D']#测试'ABCD'和‘ABD’
nextLtest = generate_next_level(Ltest)
# nextnextLtest = generate_next_level(nextLtest)
# nextnextnextLest = generate_next_level(nextnextLtest)
print(nextLtest)
# print(nextnextLtest)
# print(nextnextnextLest)
y:A partitionY:[[0, 1], [2, 3]],z:B partitionZ[[0, 1, 2]]
x=AB,partitionX=[[0, 1]]
y:A partitionY:[[0, 1], [2, 3]],z:C partitionZ[]
x=AC,partitionX=[]
y:A partitionY:[[0, 1], [2, 3]],z:D partitionZ[[0, 3], [1, 2]]
x=AD,partitionX=[]
y:B partitionY:[[0, 1, 2]],z:C partitionZ[]
x=BC,partitionX=[]
y:B partitionY:[[0, 1, 2]],z:D partitionZ[[0, 3], [1, 2]]
x=BD,partitionX=[[1, 2]]
y:C partitionY:[],z:D partitionZ[[0, 3], [1, 2]]
x=CD,partitionX=[]
['AB', 'AC', 'AD', 'BC', 'BD', 'CD']
# 测试 [-1]表示最后一个位置
ltest = 'ABCDEF'
ltest[-1]
'F'

生成剥离分区:stripped_product

在生成下一级的同时,计算得到新的属性集的剥离分区。除了level=1上的属性集为通过computeSingletonPartitions计算得到的剥离分区,其他level上的属性集的剥离分区均通过stripped_product得到

def stripped_product(x,y,z):
    global dictpartitions#剥离分区
    global tableT
    tableS = ['']*len(tableT)
    #partitionY、partitionZ是属性集Y、Z上的剥离分区,已知!
    #partitionY is a list of lists, each list is an equivalence class
    partitionY = dictpartitions[''.join(sorted(y))] 
    partitionZ = dictpartitions[''.join(sorted(z))]
    print("y:%s partitionY:%s,z:%s partitionZ%s"%(y,partitionY,z,partitionZ))
    partitionofx = [] # line 1
    for i in range(len(partitionY)): # line 2
        for t in partitionY[i]: # line 3
            tableT[t] = i
        tableS[i]='' #line 4
    for i in range(len(partitionZ)): # line 5
        for t in partitionZ[i]: # line 6
            if ( not (tableT[t] == 'NULL')): # line 7
                tableS[tableT[t]] = sorted(list(set(tableS[tableT[t]]) | set([t]))) 
        for t in partitionZ[i]: # line 8
            if (not (tableT[t] == 'NULL')) and len(tableS[tableT[t]])>= 2 : # line 9
                partitionofx.append(tableS[tableT[t]]) 
            if not (tableT[t] == 'NULL'): tableS[tableT[t]]='' # line 10
    for i in range(len(partitionY)): # line 11
        for t in partitionY[i]: # line 12
            tableT[t]='NULL'
    dictpartitions[''.join(sorted(x))] = partitionofx#生成属性集X上的剥离分区
    print('x=%s,partitionX=%s'%(x,partitionofx))
#测试stripped_product

计算剥离分区:computeSingletonPartitions

等价类划分:
list_duplicates(data2D[a].tolist())将每列中,重复的元素合并起来,返回的是一个二元组

  • 元素的值
  • 索引:存放元素的位置,例如对于A列而言,其中的element为(1, [0, 1])、 (5, [2])
def computeSingletonPartitions(listofcols):
    global data2D
    global dictpartitions
    for a in listofcols:
        dictpartitions[a]=[]
        for element in list_duplicates(data2D[a].tolist()): # list_duplicates returns 2-tuples, where 1st is a value, and 2nd is a list of indices where that value occurs
            if len(element[1])>1: # 忽略单例等价类
                dictpartitions[a].append(element[1])
#测试computeSingletonPartitions
#此时考虑的属性集只有A,B,C,D,在单个属性集上面生成剥离分区
'''测试list_duplicates函数的返回值:返回的是每个属性列表中每个属性的剥离分区'''
dictpartitions = {}
dictCplus = {'NULL': listofcolumns}
datatest = read_csv('testdataABCD.csv')
print(datatest)
for a in listofcolumns:#为索引列
    print ("a=",a)
    dictpartitions[a]=[]
    print (a,datatest[a].tolist())
    for element in list_duplicates(datatest[a].tolist()):
        print ("element=",element)
    for element in list_duplicates(datatest[a].tolist()): # list_duplicates returns 2-tuples, where 1st is a value, and 2nd is a list of indices where that value occurs
        if len(element[1])>1: # ignore singleton equivalence classes
            dictpartitions[a].append(element[1])
print(dictpartitions)#存放的是每个属性集上的剥离分区
print(dictCplus)
   A  B  C  D
0  1  1  5  5
1  1  1  1  3
2  5  1  2  3
3  5  2  3  5
a= A
A [1, 1, 5, 5]
element= (1, [0, 1])
element= (5, [2, 3])
a= B
B [1, 1, 1, 2]
element= (1, [0, 1, 2])
element= (2, [3])
a= C
C [5, 1, 2, 3]
element= (5, [0])
element= (1, [1])
element= (2, [2])
element= (3, [3])
a= D
D [5, 3, 3, 5]
element= (5, [0, 3])
element= (3, [1, 2])
{'A': [[0, 1], [2, 3]], 'B': [[0, 1, 2]], 'C': [], 'D': [[0, 3], [1, 2]]}
{'NULL': ['A', 'B', 'C', 'D']}

执行

获取数据属性

data2D = read_csv('testdataABCD.csv')
data2D
A B C D
0 1 1 5 5
1 1 1 1 3
2 5 1 2 3
3 5 2 3 5
#------------------------------------------------------- START ---------------------------------------------------
'''
如果嵌入到项目中,需要这么写代码
if len(sys.argv) > 1:
    infile=str(sys.argv[1]) # this would be e.g. "testdata.csv"
data2D = read_csv(infile)
'''

totaltuples = len(data2D.index) #return num of tuple:4
#listofcolumns为属性集列表:初始时,只有A,B,C,D..下面为初始化过程
listofcolumns = list(data2D.columns.values) # returns ['A', 'B', 'C', 'D', .....]
print(totaltuples)
print(listofcolumns)


tableT = ['NULL']*totaltuples #这是用于函数stripped_product中的表T

L0 = []
dictCplus = {'NULL': listofcolumns}#右方集字典集,初始时,NULL的右方集为R,即A,B,C,D...
#用于存储剥离分区
dictpartitions = {} # maps 'stringslikethis' to a list of lists, each of which contains indices

tableT
4
['A', 'B', 'C', 'D']
['NULL', 'NULL', 'NULL', 'NULL']

计算剥离分区

'''计算每个属性集上的剥离分区'''
computeSingletonPartitions(listofcolumns)
dictpartitions,listofcolumns
({'A': [[0, 1], [2, 3]], 'B': [[0, 1, 2]], 'C': [], 'D': [[0, 3], [1, 2]]},
 ['A', 'B', 'C', 'D'])

TANE主要算法

finallistofFDs=[]
#print dictCplus['NULL']
#初始时,L1层包含的属性集为:A,B,C,D...

L1=listofcolumns[:]  # L1 is a copy of listofcolumns

i=1

L = [L0,L1]
while (not (L[i] == [])):#第i层的包含的属性集不为空
    compute_dependencies(L[i],listofcolumns[:])# 计算该层的函数依赖
    prune(L[i])#剪枝,删除Li中的集合,修剪搜索空间
    temp = generate_next_level(L[i])
    L.append(temp)#将生成的层追加到L集合中
    i=i+1

print ("List of all FDs: " , finallistofFDs)
#  correct result
#  List of all FDs:  [['C', 'D'], ['C', 'A'], ['C', 'B'], ['AD', 'B'], ['AD', 'C']]
# Total number of FDs found:  5
print ("Total number of FDs found: ", len(finallistofFDs))
print(L)
['A', 'B', 'C', 'D']
A
['A', 'C', 'B', 'D']
B
['A', 'C', 'B', 'D']
C
['A', 'C', 'B', 'D']
D
['A', 'C', 'B', 'D']
pruning:level:['A', 'B', 'C', 'D'] adding key FD: ['C', 'A']
pruning:level:['A', 'B', 'C', 'D'] adding key FD: ['C', 'B']
pruning:level:['A', 'B', 'C', 'D'] adding key FD: ['C', 'D']
['A', 'B', 'C', 'D']
AB
['A', 'C', 'B', 'D']
AD
['A', 'C', 'B', 'D']
BD
['A', 'C', 'B', 'D']
pruning:level:['AB', 'AD', 'BD'] adding key FD: ['AD', 'C']
pruning:level:['AB', 'AD', 'BD'] adding key FD: ['AD', 'B']
List of all FDs:  [['C', 'A'], ['C', 'B'], ['C', 'D'], ['AD', 'C'], ['AD', 'B']]
Total number of FDs found:  5
[[], ['A', 'B', 'D'], ['AB', 'BD'], []]

由输出结果可以看出:

测试

#测试 不执行
# print(listofcolumns)
# L1=listofcolumns[:]
# print (L1)
['A', 'B', 'C', 'D']
['A', 'B', 'C', 'D']
#测试lever
L
[[], ['A', 'B', 'D'], ['AB', 'BD'], []]

你可能感兴趣的:(数据质量,数据库)