Apriori算法使用频繁项集的先验知识,使用一种称作逐层搜索的迭代方法,k项集用于探索(k+1)项集。首先,通过扫描事务(交易)记录,找出所有的频繁1项集,该集合记做 L 1 L_{1} L1,然后利用 L 1 L_{1} L1找频繁2项集的集合 L 2 L_{2} L2, L 2 L_{2} L2找 L 3 L_{3} L3,如此下去,直到不能再找到任何频繁k项集。最后再在所有的频繁集中找出强规则,即产生用户感兴趣的关联规则。
其中,Apriori算法具有这样一条性质:任一频繁项集的所有非空子集也必须是频繁的。因为假如P(I)< 最小支持度阈值,当有元素A添加到I中时,结果项集(A∩I)不可能比I出现次数更多。因此A∩I也不是频繁的。
在上述的关联规则挖掘过程的两个步骤中,第一步往往是总体性能的瓶颈。Apriori算法采用连接步和剪枝步两种方式来找出所有的频繁项集。
(1) 连接步
为找出 L k L_{k} Lk(所有的频繁k项集的集合),通过将 L k − 1 L_{k-1} Lk−1(所有的频繁k-1项集的集合)与自身连接产生候选k项集的集合。候选集合记作 C k C_{k} Ck。设 l 1 l_{1} l1和 l 2 l_{2} l2是 L k − 1 L_{k-1} Lk−1中的成员。记 l i l_{i} li[j]表示 l i l_{i} li中的第j项。假设Apriori算法对事务或项集中的项按字典次序排序,即对于(k-1)项集 l i l_{i} li, l i l_{i} li[1]< l i l_{i} li[2]<……….< l i l_{i} li[k-1]。将 L k − 1 L_{k-1} Lk−1与自身连接,如果( l 1 l_{1} l1[1]= l 2 l_{2} l2[1])&&( l 1 l_{1} l1[2]= l 2 l_{2} l2[2])&&………&& ( l 1 l_{1} l1[k-2]= l 2 l_{2} l2[k-2])&&( l 1 l_{1} l1[k-1]< l 2 l_{2} l2[k-1]),那认为 l 1 l_{1} l1和 l 2 l_{2} l2是可连接。连接 l 1 l_{1} l1和 l 2 l_{2} l2产生的结果是{ l 1 l_{1} l1[1], l 1 l_{1} l1[2],……, l 1 l_{1} l1[k-1], l 2 l_{2} l2[k-1]}。
(2) 剪枝步
C K C_{K} CK是 L K L_{K} LK的超集,也就是说, C K C_{K} CK的成员可能是也可能不是频繁的。通过扫描所有的事务(交易),确定 C K C_{K} CK中每个候选的计数,判断是否小于最小支持度计数,如果不是,则认为该候选是频繁的。为了压缩 C K C_{K} CK,可以利用Apriori性质:任一频繁项集的所有非空子集也必须是频繁的,反之,如果某个候选的非空子集不是频繁的,那么该候选肯定不是频繁的,从而可以将其从 C K C_{K} CK中删除。
(Tip:为什么要压缩 C K C_{K} CK呢?因为实际情况下事务记录往往是保存在外存储上,比如数据库或者其他格式的文件上,在每次计算候选计数时都需要将候选与所有事务进行比对,众所周知,访问外存的效率往往都比较低,因此Apriori加入了所谓的剪枝步,事先对候选集进行过滤,以减少访问外存的次数。)
// 因为k项集的项不确定它的类型,所以此处没有用字典排序...
const fs = require("fs");
const readline = require('readline');
const fReadName = './data.txt';
const fRead = fs.createReadStream(fReadName);
const rl = readline.createInterface({
input: fRead
});
// 最小支持度
const min_sup = 2;
const dataArr = [];
rl.on('line', (line) => {
let row = line.split(",");
dataArr.push(row);
});
rl.on('close', () => {
let Ck1 = freq1Gen(dataArr);
while (Ck1.length !== 0) {
Ck = Ck1;
Ck1 = freqKGen(Ck);
}
console.log(Ck);
});
//1.计算候选集C1
function freq1Gen(data) {
let buffer = [];
let isShow = false;
for (let i = data.length - 1; i >= 0; i--) {
for (let j = data[i].length - 1; j >= 0; j--) {
isShow = false;
for (let k = buffer.length - 1; k >= 0; k--) {
if (buffer[k].name == data[i][j]) {
buffer[k].count++;
isShow = true;
break;
}
}
if (isShow == false) {
buffer.push({
name: data[i][j],
count: 1
})
}
}
}
let ret = [];
for (let i = buffer.length - 1; i >= 0; i--) {
if (buffer[i].count >= min_sup) {
ret.push([buffer[i].name]);
}
}
return ret;
}
//2.计算候选集C(k+1)
function freqKGen(data) {
// 连接
// 补充:如果k项集的项是数字或字符串,可先将其按字典次序排序。
// 方便后面数组去重复的操作。
let candi = [];
for (let i = 0; i < data.length; i++) {
for (let j = i + 1; j < data.length; j++) {
candi.push(data[i].concat(data[j]).unique());
}
}
candi = rr_unique(candi);
let buffer = [];
for (let i = candi.length - 1; i >= 0; i--) {
buffer.push({
arr: candi[i],
count: 0
});
}
//计算支持数
for (let i = buffer.length - 1; i >= 0; i--) {
for (let j = dataArr.length - 1; j >= 0; j--) {
if (isContain(dataArr[j], buffer[i].arr)) {
buffer[i].count++;
}
}
}
//剪枝
let ret = [];
for (let i = buffer.length - 1; i >= 0; i--) {
if (buffer[i].count >= min_sup) {
ret.push(buffer[i].arr);
}
}
return ret;
}
//数组去重,hash table.
Array.prototype.unique = function() {
let n = {},
r = []; //n为hash表,r为临时数组
for (let i = 0; i < this.length; i++) { //遍历当前数组
if (!n[this[i]]) { //如果hash表中没有当前项
n[this[i]] = true; //存入hash表
r.push(this[i]); //把当前数组的当前项push到临时数组里面
}
}
return r;
}
// 假设k项集里面的项为不确定元素,无法排序。此处只能一一比较。
// arr里面的数组去重复:如何arr里面存在两个相同的数组则找到它,然后删除一个。
function rr_unique(arr) {
let toDel = []
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i].length == arr[j].length) {
let flag = true;
for (let k = 0; k < arr[j].length; k++) {
if (!arr[i].includes(arr[j][k])) {
flag = false;
break;
}
}
if (flag) {
toDel.push(i);
break;
}
}
}
}
for (let i = toDel.length - 1; i >= 0; i--) {
arr.splice(toDel[i], 1);
}
return arr;
}
//判断arr1是否包含arr2
function isContain(arr1, arr2) {
for (let i = arr2.length - 1; i >= 0; i--) {
if (!arr1.includes(arr2[i])) {
return false;
}
}
return true;
}
参考链接:
https://www.cnblogs.com/catkins/p/5270485.html
http://blog.csdn.net/lx583274568/article/details/70739258