Jeff Lee http://jeff-lee.name 2015-10-28T02:06:51+00:00 westerly.lzh@gmail.com D3最佳实践:005 贝塞尔曲线的绘制 http://westerly-lzh.github.io/cn/2015/10/D3-005/ 2015-10-09T00:00:00+00:00 Jeff Lee http://westerly-lzh.github.io/cn/2015/10/D3-005

D3学习系列5

本篇用于演示三次贝塞尔曲线的绘制,拖拽不同的点,贝塞尔曲线将会改变形状。

]]>
Python杂记 http://westerly-lzh.github.io/cn/2015/09/Python-Tips/ 2015-09-20T00:00:00+00:00 Jeff Lee http://westerly-lzh.github.io/cn/2015/09/Python-Tips 由于长时间的不用,Python的很多特性都已经开始遗忘了,这里对Python’的一些特性作记录以方便以后的复习使用。

函数的默认值

默认值只计算一次。这使得默认值是列表、字典或大部分类的实例时会有所不同。例如,下面的函数在后续调用过程中会累积传给它的参数:

def f(a, L=[]):
    L.append(a)
    return L

print f(1)
print f(2)
print f(3)

####OUTPUT:
[1]
[1, 2]
[1, 2, 3]

如果你不想默认值在随后的调用中共享,可以像这样编写函数:

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

星号参数

在Python中,函数的参数中单个‘*’表示元组参数,‘**’表示字典参数,其中字典参数必须放在元组参数后面

def ff(kind, *arguments, **keywords):
	pass;
	
## 调用方式:
ff(1,2,3,typ1=4,type2=5)

上面的调用中,2,3将会以元组参数的形式赋值给arguments=(2,3);type1,type2将会以字典参数的形式赋值给keywords={type1=4,type2=4}

列表推导式

列表推导式中每个for循环的计算顺序是从左向右

[x**2 for x in range(10)]
[(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
###############
vec = [[1,2,3], [4,5,6], [7,8,9]]
[num for elem in vec for num in elem]
###############
matrix = [
          [1, 2, 3, 4],
          [5, 6, 7, 8],
          [9, 10, 11, 12],
          ]

print([row[i] for row in matrix for i in range(4)])
print([[row[i] for row in matrix] for i in range(4)])

print([row[i] for i in range(4) for row in matrix])
print([[row[i] for i in range(4)] for row in matrix])

生成器

yield_stmt ::=  yield_expression

yield语句只在定义生成器函数时使用,且只在生成器函数的函数体中使用。虽然生成器是一个函数的,但我们不应当把它当做一个函数来理解,可以要把生成器当成一个具有数据结构的变量,如Java中的对象一样。只是这个变量具有一些特殊的“操作”。

def gen():
    for x in xrange(0,10):
        yield x
        if x%2==0:
            yield "x is even"
        
g = gen();

print(g.next()) ## 0
print(g.next()) ## x is even
print(g.next()) ## 1
print(g.next()) ## 2
print(g.next()) ## x is even

yield 语句在python的文档中给出的解释如下:

当执行一个yield语句时,生成器的状态将冻结起来并且将expression_list的值返回给next()的调用者。“冻结”的意思是所有局部的状态都会被保存起来,包括当前局部变量的绑定、指令指针、内部的计算栈:保存足够的信息以使得下次调用next()时,函数可以准确地继续,就像yield语句只是另外一个外部调用。

在新的yield规范中,增加了对生成器增加了send(),throw(),close()等方法,具体使用参考PEP 0342

迭代器

迭代器让我们避开了在遍历集合(列表)类型元素使用索引的尴尬,但迭代器不是线程安全的,所以不建议在多线程中使用迭代器。若想一个类型可以具有迭代器功能,只需要在该类型中实现iter()方法。

lst = range(4)
it = iter(lst)

it.next()
next(it)

Python提供了工厂方法从一个具有iterable特性的对象中获取迭代器,可以通过迭代器自身的next()来进行迭代,也可以使用内建函数next()来进行迭代。

但在Python的迭代器中没有判断是否已经到了迭代结束的hasNext()方法,所以只能通过异常(StopIteration)来进行迭代结束的判断。

迭代器虽然避开了索引,但是有时我们游需要索引,这时可以通过使用 enumerate()方法,对迭代器进行包装,使得调用迭代器的next()方法后返回的不光有迭代对象,还有该对象的下标。

for idx, element in enumerate(lst):
	print(idx,element)

上下文管理器

“上下文”的概念表明,在某段程序执行之前,我们需要一些准备工作;在该程序执行结束之后,需要一些收尾工作。这里的“准备工作”和“收尾工作”就是该段程序的上限文。所以上下文管理器在Python中实际的作用就是为代码的开始执行做准备,为结束做善后。这个概念在程序使用计算机资源,比如文件时,特别有用,在使用文件前,需要判断文件是否存在,能否打开,然后打开文件;在使用文件结束后进行文件句柄关闭等资源回收操作。

with open("new.txt", "w") as f:
    print(f.closed)
    f.write("Hello World!")
print(f.closed)

在Python中任何对象只需要实现enter()和exit()操作就可以被用于上下文管理器。这两个方法就像它的名字所表示的那样清晰,enter()方法需要返回关键字with后表达式的返回值,并把该值赋给as后的变量。exit()方法在代码执行结束后执行,如果代码执行期间抛出了异常,那么该方法其实是可以捕获到异常的。exit()方法的完整方法签名如下:

def __exit__(self, exc_type, exc_val, exc_tb)

Python中通过contextlib包提供了增强的上下文管理器。

装饰器

要了解装饰器,就需要了解面向切面的编程(经常讨论面向对象和面向过程)。在Python中装饰器承担了这样一种功能,在不改变函数的前提下,为该函数增加额外的功能,当然在Python中能用装饰器的不光是函数,其他的对象也可以。

def decorator(func):
    def wapper():
        print "do some before"
        func()
        print "do some after"
    return wapper


def baseFun():
    print "the base function"

fun = decorator(baseFun)
fun()

上面就是给方法baseFun定义了一个装饰器。采用装饰器装饰过的baseFun不光具有原来的功能,还新增加了其他的功能。下面是Python中通过使用@符号对函数使用装饰器的方法。和Java中的注解(Annotation)很类似。

def decorator(func):
    def wapper():
        print "do some before"
        func()
        print "do some after"
    return wapper

@decorator
def baseFun():
    print "the base function"

baseFun()

上面的装饰器定义,最后通过装饰器返回的函数,并不是我们传入的函数,这在某些编程情境下,比如我想知道该函数的名称时,上面被装饰器作用后的函数是无法获取到原来的函数名的。如果想保留原来的函数基本信息,需要利用functools包中的一个装饰器对来改造之前定义的装饰器。functools包的具体功能,请参考functools

import functools
def decorator(func):
    @functools.wraps(func)
    def wapper():
        print "do some before"
        func()
        print "do some after"
    return wapper

@decorator
def baseFun():
    print "the base function"

baseFun()
print baseFun.__name__

闭包

python中的闭包从表现形式上定义(解释)为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure). 这里的”外部作用域的变量”是只读的。

明白了闭包表现形式上的定义和注意点,就可以很好的理解闭包中的各种匪夷所思的现象了。

#SECTION 1
def foo1():
    a = 1
    def bar():
        a = a +1
        return a
    return bar

c = foo1()
print c()
#SECTION 2
def foo2():
    a = 1
    def bar():
        b = a
        b = b +1
        return b
    return bar

c = foo2()
print c()
#SECTION 3
def foo3():
    a = 1
    def bar():
        b = a
        a = b +1
        return a
    return bar

c = foo3()
print c()
#SECTION 4
def foo4():
    a = 1
    def bar():
        a = 2
        a = a +1
        return a
    return bar

c = foo4()
print c()

闭包中需要注意的就是外层变量是不可变的,所以新版的Python中引入了nonlocal关键字,来指定该变量是外部变量。

def foo5():
    a = 1
    def bar():
        nonlocal a
        a = a +1
        return a
    return bar

c = foo5()
print c()

装饰器是闭包的一种应用,闭包还经常用于函数配置,配置信息不同,函数的行为结果也会不同:

def make_adder(addend):
    def adder(augend):
        return augend + addend
    return adder

p = make_adder(23)
q = make_adder(44)

print p(100)
print q(100)

make_adder 方法传入的参数不同,导致q和p函数虽然是同一个函数,且传入参数相同,但结果却不一样。

]]>
交通数据分析:成渝铁路客运可视化 http://westerly-lzh.github.io/cn/2015/09/Traffic-Data-Analysis-3/ 2015-09-17T00:00:00+00:00 Jeff Lee http://westerly-lzh.github.io/cn/2015/09/Traffic-Data-Analysis-3 最近项目需要一些铁路运输数据的可视化展现方法,于是自己就借助于百度地图,D3等工具,构建了如下几个数据可视化的展现:

不同城市铁路站点间的客流展现

该展现直接使用了百度提供的API

chengyu001

采用模拟数据进行成渝客专的线路客流展现

该展现直接使用了百度提供的API

chengyu002

采用模拟数据进行成渝客专的站点客流展现

该展现结合了百度地图和桑基图的原理实现

chengyu003

]]>
常见排序算法 http://westerly-lzh.github.io/cn/2015/09/Basic-Sort-Algorim/ 2015-09-13T00:00:00+00:00 Jeff Lee http://westerly-lzh.github.io/cn/2015/09/Basic-Sort-Algorim 本文是前一段时间关于排序算法的一些讨论及基本实现思路。需要留意两个概念,算法时间复杂度(一般使用最差条件下,耗时最多的为该算法的时间复杂度),排序算法的稳定性,指同值的数据在原来列表中的顺序和新排好的列表中的顺序是否一致,如果一致,则该算法稳定,否则算法不稳定。

冒泡算法(Bubble)

若初始文件是反序的,需要进行$n-1$趟排序。每趟排序要进行$n-i$次关键字的比较$(1≤i≤n-1)$时间复杂度为 $O(n^2)$,该算法稳定,适合少量数据排序。

冒泡算法的计算步骤如下:

  1. 比较相邻的两个元素,如果第一个比第二个大,就交换他们两个
  2. 对每一对相邻的元素做(1)中的工作,从开始第一对到结尾的最后一对
  3. 针对所有的元素重复以上步骤,除了最后一个
  4. 每次将会越来越少的重复上面的步骤,直到没有任何一对数字需要比较为止

冒泡算法的简单实现:

public static int[] sort(int[] array){
		boolean flag = false;
		int finished = 0;
		do{
			flag = false;
			int tmp;
			for(int i = 0;i<array.length-1-finished;i++){
				if(array[i]>array[i+1]){
					tmp = array[i];
					array[i] = array[i+1];
					array[i+1] = tmp;
					flag = true;
				}
			}
			finished = finished+1;
		}while(flag);
	
		return array;
}

插入排序(Insertion)

在已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,该算法稳定,时间复杂度为$O(n^2)$

  1. 首先建立一个空的列表
  2. 从原列表中取出一个数字,插入新建的列表中,保持新列表有序
  3. 重复2号步骤,直至原来的列表为空

【7,   4,1,8,5,2,3,6,9】 【4,7,   1,8,5,2,3,6,9】 【1,4,7,   8,5,2,3,6,9】 【1,4,7,8,   5,2,3,6,9】

插入排序的基本实现,采用原地插入排序方式实现:

public static int[] sort(int[] array){
	for(int i = 1; i<array.length;i++){
		for(int j = i;j>0;j--){
			if(array[j]<array[j-1]){
				int tmp = array[j-1];
				array[j-1] = array[j];
				array[j] = tmp;
			}else{
				break;
			}
		}
	}
	
	return array;
}

选择排序(Selection)

该算法不稳定,算法时间复杂度$O(n^2)$

  1. 每次从未排序的元素中选出最小的一个,放在已经排序的元素的后面
  2. 开始时,元素都未排序,从所有的元素中选择最小的一个,和下标为0的元素交换位置
  3. 接下俩,从未排序的元素中选择最小的一个,和小标为1的元素交换位置。
  4. 重复步骤3,直到倒数第二个元素

选择排序和上面的插入排序很类似,插入排序是插入是进行排序的操作,选择排序是选择时进行排序的操作,选择排序的基本实现如下:

public static int[] sort(int[] array){
	for(int i = 0; i < array.length-1;i++){
		int tmp = array[i],index = i;
		for(int j = i;j < array.length;j++){
			if(array[j] < tmp){
				tmp = array[j];
				index = j;
			}
		}
		array[index] = array[i];
		array[i] = tmp;
	}
	return array;
}

归并排序(Merge)

该算法稳定,时间复杂度为$O(n /log n)$

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

归并排序的基本实现,采用二路归并:

public static int[] merge(int[] a, int[] b) {
	int[] res = new int[a.length + b.length];
	int i = 0, j = 0, k = 0;
	while (i < a.length && j < b.length) {
		if (a[i] <= b[j]) {
			res[k] = a[i];
			i++;
			k++;
		} else {
			res[k] = b[j];
			j++;
			k++;
		}
	}
	System.arraycopy(a, i, res, k, a.length - i);
	System.arraycopy(b, j, res, k+a.length - i, b.length - j);
	return res;
}

public static int[] sort(int[] array){
	if(array.length<=1){
		return array;
	}
	int middle = array.length/2;
	int[] left = new int[middle];
	int[] right = new int[array.length-middle];
	
	System.arraycopy(array, 0, left, 0, middle);
	System.arraycopy(array, middle, right, 0, array.length - middle);
	return merge(sort(left),sort(right));
}

鸡尾酒排序(Cocktail)

鸡尾酒排序也就是定向冒泡排序,是冒泡排序的一种变形。此演算法与冒泡排序的不同处在于排序时是以双向在序列中进行排序。 数组中的数字本是无规律的排放,先找到最小的数字,把他放到第一位,然后找到最大的数字放到最后一位。然后再找到第二小的数字放到第二位,再找到第二大的数字放到倒数第二位。以此类推,直到完成排序。

public static int[] sort(int[] array){
	int left_index = 0;
	int right_index = array.length-1;
	int tmp ;
	int current_index;
	while(left_index<=right_index){
		//把未排序中最大的移到右侧
		tmp = array[left_index];
		current_index = left_index;
		for(int i = left_index+1;i<=right_index;i++){
			if(array[i]>tmp){
				tmp = array[i];
				current_index= i;
			}
		}
		array[current_index] = array[right_index];
		array[right_index] = tmp;
		right_index--;
		
		//把未排序中最小的移到左侧
		tmp = array[right_index];
		current_index = right_index;
		for(int j = right_index-1;j>=left_index;j--){
			if(array[j]<tmp){
				tmp = array[j];
				current_index = j;
			}
		}
		array[current_index] = array[left_index];
		array[left_index] = tmp;
		left_index++;
		
	}
	return array;
}

快速排序(Quick)

快速排序不稳定,算法时间复杂度为$O(n^2)$,该算法选出一个值,然后一趟排序把比该值小的数据作为该值前面部分,比该值大的数据作为该值的后面部分,然后分别对这两部分进行上述步骤。一趟快速排序的算法是:

  1. 设置两个变量i、j,排序开始的时候:i=0,j=N-1;
  2. 以第一个数组元素作为关键数据,赋值给key,即key=A[0];
  3. 从j开始向前搜索,即由后开始向前搜索(j–),找到第一个小于key的值A[j],将A[j]和A[i]互换;
  4. 从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;
  5. 重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。

快速排序的基本实现:

public static void sort(int[] array,int left,int right){
	if(left>= right){
		return ;
	}
	int l = left,r = right;
	int key = array[left];
	while(l<r){
		while(l<r && key<=array[r]){
			r--;
		}
		array[l] = array[r];
		while(l < r && key>=array[l]){
			l++;
		}
		array[r] = array[l];
	}
	array[l] = key;
	sort(array,left,l-1);
	sort(array,l+1,right);
}

计数排序(Counter)

该算法稳定,时间复杂度为O(n),以空间换时间。首先需要了解计数排序法的适用场景。:待排序元素分布比较集中,没有较大或者较小的异常值

  1. 扫描待排序元素,确定其中的最大值和最小值. 以最大和最小值的间距长度申请一个临时数组。
  2. 扫描待排序元素,利用临时数组对这些元素进行计数,计数方法为:以(元素值 - 最小值)作为临时数组的下标,
  3. 以(元素值 - 最小值)出现的次数为对应的值。这样可以实现把待排序元素的顺序信息压缩到临时数组中。
  4. 这样该临时数组中就存储了按照待排序元素的大小为数组下标,元素出现次数为值的数据。
  5. 对临时数组进行累加:tmp[i] = tmp[i]+tmp[i-1],这样得到的临时数组中就存储了待排序元素的顺序信息
  6. 把利用待排序元素和临时数组,构建出已排序的数组

     public static int[] sort(int[] array){
         int[] res = new int[array.length];
    	
         int max = array[0], min = array[0];
         for(int i : array){
             if(i > max){
                 max = i;
             }
             if(i < min){
                 min = i;
             }
         }
    	
         int k = max - min + 1;
         //初始化的array每个元素默认值为0
         int[] tmp = new int[k];
    	
         for(int i = 0; i < array.length;++i){
             //把array数组的元素压缩到tmp中,
             tmp[array[i] - min] +=1;
         }
         for(int i=1;i<tmp.length;++i){
             //tmp中的值是(下标+min)对应的元素在排序后的顺序值。
             tmp[i]=tmp[i]+tmp[i-1];
         }
    	
         for( int i = 0 ; i < array.length; ++i){
             res[--tmp[array[i] - min]] = array[i];
     //		tmp[array[i] - min] = tmp[array[i] - min]-1;
     //		res[tmp[array[i] - min]] = array[i];
         }
         return res;
     }
    

希尔排序(Shell)

该算法时间复杂度与增量的选择有关,希尔排序(缩小增量排序)算法描述如下:

  1. 选择一个初始增量(一般为数组长度的一半)
  2. 按照增量把数组元素分组,所有距离为d的倍数的数组放在同一个组,然后在各组内进行直接排序。
  3. 取第二个增量(小于第一个增量),重复进行分组和组内直接排序。直到增量值为1.

希尔排序的基本实现:

public static int[] sort(int[] array){
	
	int d = array.length/2;
	while(d>=1){
		for(int i = 0; i< d;i++){
			//分组
			for(int j = i+d;j < array.length;j=j+d){
				//分组内的插入排序
				for(int k = j; k>0&&k-d>=0;k = k-d){
					if(array[k] < array[k-d]){
						int tmp = array[k-d];
						array[k-d] = array[k];
						array[k] = tmp;
					}else{
						break;
					}
				}
			}
		}
		d = d/2;
	}		
	return array;
}

时间复杂度比较:

时间复杂度

完成于2015-9-12,成渝动车D5129.

]]>
数据挖掘系列005 数据分类 决策树 ID3 http://westerly-lzh.github.io/cn/2015/09/DM005-Classification-ID3/ 2015-09-10T00:00:00+00:00 Jeff Lee http://westerly-lzh.github.io/cn/2015/09/DM005-Classification-ID3 数据分类是通过分析输入数据,利用输入数据的部分属性构造一个分类函数或者分类模型,利用该函数或者模型把数据库中的其他数据记录映射到给定的类别中。分类可以用于预测。 在解决分类问题中,决策树方法是一种行之有效的方法。目前已有的决策树算法包括但不限于:ID3,CHAID,CART,FACT,C4.5,SLIQ和SPRINT等。

导言

决策树算法是通过分析输入的数据属性,构造一颗有向无环树,该树的每个叶子节点都对应数据记录的类别标识的一个值,每个内部节点都对应了数据记录的一个属性,父子节点之间的连线都对应了属性的一个分割。决策树算法一般可分为两个阶段:1)决策树生成,2)决策树修剪。本系列主要讨论决策树的生成。

信息论基础

决策树生成算法的关键是求解属性的分割方法。目前的属性分割方法主要包括两类:基于信息论的方法和基于最小Gini指标的方法。前者对应ID3,C4.5;后者对应CART,SLIQ和SPRINT。本文主要讨论ID3算法及其对应的基于信息论的分割方法。

定义1:若给定的概率分布 $P=(p_1,P_2,…,p_n)$ ,则由该分布传递的信息量称为P的熵,即:

定义2:若一个给定的记录集合T根据类别属性,被分成相互独立的类 $C_1,C_2,…,C_k$ ,则识别T的一个记录所属的类别所需要的信息量是$info(T) = I(P)$ ,其中P表示 $(C_1,C_2,…,C_k)$ 的概率分布,即:

定义3:若先根据非类别属性X的值将T分成集合$T_1,T_2,…,T_n$,则确定T中一个记录所属类别的信息量可以通过确定 $T_i$ 的加权平均得到,即:

定义4: 定义信息增益$Gain(X,T)$为:

信息增益表示了两个信息量之间的差值,其中一个信息量的确定需要T,另一个信息量是在已知属性X的值后确定T的一条记录的信息量,所以信息增益与属性X相关。每次分割属性时,选择信息增益最大的属性。

ID3算法

ID3算法构造决策树的过程如下:

image

ID3简单实现

下面的代码是ID3生成决策树算法采用Java语言的简单实现:

package classify;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 简单的ID3算法实现
 * 该实现适用于属性值为离散变量的决策树生成。
 * @author jeff
 *
 */
public class ID3 {

	/**
	 * 决策树的数据结构
	 * @author jeff
	 *
	 */
	public class TreeNode {
		private int index;//该节点在数据集合中所代表的属性的下标
		private String name;//该节点在数据结集合中所代表的属性名称
		private TreeNode parent;//父节点
	
		private Integer leftValue;//当节点为子节点时,用来存储节点的值
	
		private String linkLabel;//在子节点存储link信息
		private List<TreeNode> children;//该节点的子节点

		public int getIndex() {
			return index;
		}

		public void setIndex(int index) {
			this.index = index;
		}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public TreeNode getParent() {
			return parent;
		}

		public void setParent(TreeNode parent) {
			this.parent = parent;
		}

		public List<TreeNode> getChildren() {
			return children;
		}

		public void setChildren(List<TreeNode> children) {
			this.children = children;
		}

		public void addChild(TreeNode child) {
			if (this.children == null) {
				children = new ArrayList<ID3.TreeNode>();
			}
			children.add(child);
		}

		public String getLinkLabel() {
			return linkLabel;
		}

		public void setLinkLabel(String linkLabel) {
			this.linkLabel = linkLabel;
		}

		public Integer getLeftValue() {
			return leftValue;
		}

		public void setLeftValue(Integer leftValue) {
			this.leftValue = leftValue;
		}

	}

	public TreeNode buildTree(RecordSet set) {
		TreeNode root = new TreeNode();
		classify(root, set);

		return root;
	}

	/**
	 * 递归生成决策树
	 * 
	 * @param root
	 * @param current
	 */
	public void classify(TreeNode root, RecordSet current) {
		if (!current.checkValid()) {
			TreeNode node = new TreeNode();
			node.setName("Failure");
			node.setIndex(-1);
			node.setParent(root);
			root.addChild(node);
			return;
		}
		if (current.check()) {
			TreeNode node = new TreeNode();
			node.setName(current.getName(current.getCategoryIndex()));
			node.setIndex(current.getCategoryIndex());
			node.setParent(root);
			node.setLinkLabel(root.getName()+"_"+current.getKeyValue()+"_"+node.getName());
			node.leftValue = current.getValue(0, node.getIndex());
			root.addChild(node);
			return;
		}

		double maxGain = Double.MIN_VALUE;
		Integer maxIndex = -1;
		List<RecordSet> sets = new ArrayList<RecordSet>();

		Integer[] indexs = current.getIndexs();
		for (int i = 0; i < indexs.length; i++) {
			if (indexs[i].intValue() == current.getCategoryIndex()) {
				continue;
			}
			List<RecordSet> tmpSets;
			Map<Integer, List<Integer[]>> maxIndexSplit = new HashMap<Integer, List<Integer[]>>();
			for (Integer[] values : current.getValues()) {
				List<Integer[]> set = maxIndexSplit.putIfAbsent(values[indexs[i]], new ArrayList<Integer[]>());
				if (set != null) {
					set.add(values);
				} else {
					maxIndexSplit.get(values[indexs[i]]).add(values);
				}
			}

			tmpSets = makeRecordSet(current, maxIndexSplit);

			double gain = computeGain(current, tmpSets);
			sets = maxGain < gain ? tmpSets : sets;
			maxIndex = maxGain < gain ? i : maxIndex;
			maxGain = maxGain < gain ? gain : maxGain;
		}

		TreeNode node = new TreeNode();
		node.index = indexs[maxIndex];
		node.name = current.getName(indexs[maxIndex]);
		node.parent = root;
		node.setLinkLabel(root.getName()+"_"+current.getKeyValue()+"_"+node.getName());
		root.addChild(node);

		assert sets != null;
		for (RecordSet set : sets) {
			set.getIndexs()[maxIndex] = -1;
			set = set.filter();
			classify(node, set);
		}

	}

	/**
	 * 根据属性值的不同,对数据集合进行划分,并对每个划分生成新的数据子集
	 * @param current
	 * @param res
	 * @return
	 */
	private List<RecordSet> makeRecordSet(RecordSet current, Map<Integer, List<Integer[]>> res) {
		List<RecordSet> sets = new ArrayList<RecordSet>();
		for (Map.Entry<Integer, List<Integer[]>> entry:res.entrySet()) {
			RecordSet set = new RecordSet();
			set.setIndexs(current.getIndexs());
			set.setIndexName(current.getIndexName());
			set.setValues((Integer[][]) entry.getValue().toArray(new Integer[][] {}));
			set.setCategoryIndex(current.getCategoryIndex());
			set.setRowNum(entry.getValue().size());
			set.setKeyValue(entry.getKey());
			sets.add(set);
		}
		return sets;
	}

	/**
	 * 计算信息熵
	 * @param set
	 * @return
	 */
	private double computeInfo(RecordSet set) {
		Map<Integer, Integer> counter = new HashMap<Integer, Integer>();
		for (Integer[] values : set.getValues()) {
			Integer value = counter.putIfAbsent(values[set.getCategoryIndex()], 1);
			if (value != null) {
				counter.replace(values[set.getCategoryIndex()], value + 1);
			}
		}
		List<Double> pros = new ArrayList<Double>();
		for (Integer value : counter.values()) {
			pros.add(value * 1.0 / set.getRowNum());
		}

		return Util.entropy((Double[]) pros.toArray(new Double[] {}));
	}

	/**
	 * 计算信息增益
	 * @param current
	 * @param subSets
	 * @return
	 */
	private double computeGain(RecordSet current, List<RecordSet> subSets) {

		double infoTbyX = 0.0;
		for (RecordSet recordSet : subSets) {
			infoTbyX = infoTbyX + (recordSet.getRowNum() / current.getRowNum()) * computeInfo(recordSet);
		}
		double infoT = computeInfo(current);
		return infoT - infoTbyX;
	}

	public static void main(String[] args) {
		RecordSet set = new RecordSet();
		set.setCategoryIndex(2);
		set.setIndexName(new String[] { "Age", "Type", "Risk" });
		set.setIndexs(new Integer[] { 0, 1, 2 });
		set.setRowNum(6);
		set.setValues(
				new Integer[][] { { 23, 1, 1 }, { 17, 2, 1 }, { 43, 2, 1 }, { 68, 1, 2 }, { 32, 3, 2 }, { 20, 1, 1 } });
		set.setKeyValue(6);
	
	
	
		RecordSet set1 = new RecordSet();//股票训练数据
		set1.setCategoryIndex(3);
		set1.setIndexName(new String[]{"年龄","竞争力","类型","收益"});
		set1.setIndexs(new Integer[]{0,1,2,3});
		set1.setRowNum(10);
		/**
		 * 年龄			竞争力		类型		收益
		 * 1:老年		1:有		1:软件	1:下降
		 * 2:中年		2:无		2:硬件	2:上升
		 * 3:青年	
		 * 
		 * 
		 * 
		 * 
		 */
	
		set1.setValues(new Integer[][]{
			{1,1,1,1},
			{1,2,1,1},
			{1,2,2,1},
			{2,1,2,1},
			{2,1,2,1},
			{2,2,2,2},
			{2,2,1,2},
			{3,1,1,2},
			{3,2,2,2},
			{3,2,2,2}});
	
		ID3 id3 = new ID3();
		TreeNode tree = id3.buildTree(set1);
		System.out.println(tree);
	}

}

其中使用到的RecordSet类定义如下:

package classify;

import java.util.ArrayList;
import java.util.List;

/**
 * 离散类别 ID3 算法 数据模型
 * 
 * @author jeff
 *
 */
public class RecordSet {

	private Integer keyValue; //RecordSet按属性值进行划分子集时,属性的值

	private Integer[] indexs; // 还未被划分的属性下标
	private String[] indexName; //属性的名称
	private Integer[][] values; //集合值
	private Integer categoryIndex;//分类下标
	private Integer rowNum; //该集合中总的行数

	public boolean removeIndex(Integer index) {
		if (index >= 0 && index < indexs.length) {
			indexs[index] = -1;
			return true;
		}
		return false;
	}

	public String getName(Integer index) {
		for (int i = 0; i < indexs.length; i++) {
			if (index.intValue() == indexs[i]) {
				return indexName[i];
			}
		}
		return null;
	}

	public Integer getValue(Integer rowIndex, Integer colIndex) {
		return values[rowIndex][colIndex];
	}

	public Integer[] getIndexs() {
		return indexs;
	}

	public void setIndexs(Integer[] indexs) {
		this.indexs = indexs;
	}

	public String[] getIndexName() {
		return indexName;
	}

	public void setIndexName(String[] indexName) {
		this.indexName = indexName;
	}

	public Integer[][] getValues() {
		return values;
	}

	public void setValues(Integer[][] values) {
		this.values = values;
	}

	public boolean checkValid() {
		return this.indexs != null && this.indexName != null && this.indexs.length == this.indexName.length
				&& indexs.length > 1 && values.length > 0;
	}

	public boolean check() {
		Integer firstValue = values[0][this.categoryIndex];
		for (Integer[] value : this.values) {
			if (value[this.getCategoryIndex()] != firstValue) {
				return false;
			}
		}
		return true;

	}

	public Integer getCategoryIndex() {
		return categoryIndex;
	}

	public void setCategoryIndex(Integer categoryIndex) {
		this.categoryIndex = categoryIndex;
	}

	public Integer getRowNum() {
		return rowNum;
	}

	public void setRowNum(Integer rowNum) {
		this.rowNum = rowNum;
	}

	public RecordSet filter() {
		List<Integer> new_index = new ArrayList<Integer>();
		List<String> new_names = new ArrayList<String>();
		Integer new_categoryIndex = this.categoryIndex;
		RecordSet set = new RecordSet();

		for (int i = 0; i < indexs.length; i++) {
			if (indexs[i] >= 0) {
				new_index.add(indexs[i]);
				new_names.add(indexName[i]);
			}
			if (indexs[i].intValue() == this.categoryIndex) {
				new_categoryIndex = indexs[i];
			}
		}

		set.keyValue = this.keyValue;
		set.indexs = (Integer[]) new_index.toArray(new Integer[] {});
		set.indexName = (String[]) new_names.toArray(new String[] {});
		set.values = this.values;
		set.categoryIndex = new_categoryIndex;
		set.rowNum = this.rowNum;
		return set;
	}

	public Integer getKeyValue() {
		return keyValue;
	}

	public void setKeyValue(Integer keyValue) {
		this.keyValue = keyValue;
	}

}

熵的计算如下:

package classify;

public class Util {

	public static double entropy(Double[] pros) {
		double sum = 0.0;
		double log10_2 = Math.log10(2);
		for (double d : pros) {
			sum = d * Math.log10(d) / log10_2;
		}
		return -sum;
	}

}

讨论

  • 本实现的ID3算法只适用于离散属性。对于连续属性的决策树构建,需要先对数据集进行离散化处理。
  • 本实现在递归过程中,需要每次递归都需要遍历整个数据集,效率不高。
]]>
数据挖掘系列004 关联规则R语言--arules http://westerly-lzh.github.io/cn/2015/09/DM004-R-arules/ 2015-09-06T00:00:00+00:00 Jeff Lee http://westerly-lzh.github.io/cn/2015/09/DM004-R-arules 关联规则用于发现交易数据中,不同商品之间的关系,这些规则反映了顾客的购买行为模式。如顾客经常在购买A商品的时候也会购买B商品,著名的“啤酒与尿布”的案例就是关联规则的成功应用案例

导语

本篇介绍R语言中的关联规则包“arules”。 在R的cran中,arule包被分在machineLearning的task view下。在该task下,处理关联规则(Association Rules)的包只有arules,提供了Apriori和ECLAT两种进行频繁项集挖掘的算法。很多其他关于频繁项集的挖掘算法还有待R语言的开发实现。

数据结构及使用流程

在进行频繁项集挖掘的过程中,arules包中给出的数据操作流程如下:

image

  1. 获取交易数据源,常见的交易数据源包括数据库、文本文件、R中的list,matrix,data.frame等对象类型。但无论哪种数据源,都需要转换成arules支持的数据格式
  2. arules支持的数据输入格式有两种,transactions类和tidLists类,这两个类型在存储数据时,都是采用的系数矩阵来存储的,稀疏矩阵的存储方式在稀疏矩阵存储列式和行式有介绍。
    • transactions类型

      image

    • tidLists类型

    image

  3. arules提供apriori和eclat算法进行频繁数据集的挖掘
  4. 对于挖掘的结果,我们得到规则类rule。
  5. 对于挖掘结果rule实例的进一步探查。

使用案例

使用list类型创建transactions:

## example 1: creating transactions form a list
a_list <- list(
      c("a","b","c"),
      c("a","b"),
      c("a","b","d"),
      c("c","e"),
      c("a","b","d","e")
      )

## set transaction names
names(a_list) <- paste("Tr",c(1:5), sep = "")
a_list

## coerce into transactions
trans1 <- as(a_list, "transactions")

使用matrix类型创建transactions:

## example 2: creating transactions from a matrix
a_matrix <- matrix(c(
  1,1,1,0,0,
	1,1,0,0,0,
	1,1,0,1,0,
	0,0,1,0,1,
	1,1,0,1,1
  ), ncol = 5)

## set dim names
dimnames(a_matrix) <- list(c("a","b","c","d","e"),
	paste("Tr",c(1:5), sep = ""))

a_matrix

## coerce
trans2 <- as(a_matrix, "transactions")

tidLists类和transactions类在表示的内容上是等价的,所以可以把transactions类型转换为tidLists类型:

tids <- as(trans2, "tidLists")

调用apriori或者eclat算法:

rules <- apriori(trans2, 
             parameter = list(supp = 0.5, conf = 0.9,
                              target = "rules"))
                        
itemsets <- eclat(trans2,
              parameter = list(supp = 0.1, maxlen = 15))

对结果进行分析:

inspect(rules[1000])
]]>
数据挖掘系列003 关联规则ECLAT算法 http://westerly-lzh.github.io/cn/2015/09/DM003-ECLAT/ 2015-09-02T00:00:00+00:00 Jeff Lee http://westerly-lzh.github.io/cn/2015/09/DM003-ECLAT 关联规则用于发现交易数据中,不同商品之间的关系,这些规则反映了顾客的购买行为模式。如顾客经常在购买A商品的时候也会购买B商品,著名的“啤酒与尿布”的案例就是关联规则的成功应用案例

导语

不同于Apriori和FP算法所采用的按照交易事务来水平划分项集的数据挖掘方式,把数据集中的项划归到每个事务下,ECLAT算法采用了另一种思路:把数据集中的事务划归到每个项下。本文采用如下数据:

	A;B;E;
	B;D;
	B;C;
	A;B;D
	A;C;
	B;C;
	A;C;
	A;B;C;E;
	A;B;C;

下表左边为Apriori、FP算法所采用的数据结构,右边是ECLAT算法所采用的数据结构两部分:

事务 项   项 事务
T1 A;B;E   A T1;T4;T5:T7;T8;T9
T2 B;D   B T1;T2;T3;T4;T6;T8;T9
T3 B;C   C T3;T5;T6;T7;T8;T9
T4 A;B;D   D T2;T4
T5 A;C   E T1;T8
T6 B;C      
T7 A;C      
T8 A;B;C;E      
T9 A;B;C      

算法介绍

ECLAT算法把数据库事务划归到每个项下,使得该算法相较于Apriori和FP-Growth算法可以基于集合运算更简便的得到频繁项集,该算法得到频繁项集的基本思路如下:

image

  1. 首先对数据库进行一次遍历,生成项对应的事务集,如上图左上角表格T1。
  2. 然后把所有项作为一个集合I_all,求该集合的子集,设第i个子集为I_i,如上图中橘黄色箭头指示的集合列表。
  3. 对每个子集I_i中的项对应的事务集合求并集为T_i,如上图蓝色箭头指示的集合列表
  4. T_i中元素个数大于阀值的集合,即为频繁项集。

如果设置频繁项的频次阀值为2,则上图中红色的集合为频繁项集。

简单实现

ECLAT 算法只需要一次数据库的遍历,生成以项(item)为key,以出现该item的交易事务Id所组成的集合为value的map。然后频繁项集就可以基于该map获取到。其获取频繁项集的实现代码如下:

package association;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import set.SetUtils;

public class ECLAT {
	public static String SPLIT = ";";
	public static int F = 2;
	public static double C = 0.7;

	public static List<String> transList = new ArrayList<String>();

	static {
		transList.add("T1;A;B;E;");
		transList.add("T2;B;D;");
		transList.add("T3;B;C;");
		transList.add("T4;A;B;D;");
		transList.add("T5;A;C;");
		transList.add("T6;B;C;");
		transList.add("T7;A;C;");
		transList.add("T8;A;B;C;E;");
		transList.add("T9;A;B;C;");
	}


	private Map<String,Set<String>> datas;


	private void etl(){
		datas = new HashMap<String, Set<String>>();
		for (String string : transList) {
			String[] records = string.split(SPLIT);
			for(int i = 1;i<records.length;i++){
				if(!datas.containsKey(records[i])){
					datas.put(records[i],new HashSet<String>());
				}
				datas.get(records[i]).add(records[0]);
			}
		}
	}

	public List<String> getFItems(){
		etl();
		Set<String> keys = datas.keySet();
		Set<String> items = new HashSet<String>();
		items.addAll(keys);
		ArrayList<Set<String>> subsets = SetUtils.getSubset(items);
		Set<String> tmp = new HashSet<String>();
		List<String> fItems = new ArrayList<String>();
		for (Set<String> set : subsets) {
			tmp.clear();
			Iterator<String> it = set.iterator();
			if(it.hasNext()){
				tmp.addAll(datas.get(it.next()));
				while(it.hasNext() && tmp.size()>=F){
					tmp.retainAll(datas.get(it.next()));
				}
			}
			if(tmp.size()>=F){
				fItems.add(set.toString()+":"+tmp.size());
			}
		}
		return fItems;
	}

	public static void main(String[] args) {
		ECLAT eclat = new  ECLAT();
	
		List<String> fItems = eclat.getFItems();
		for (String string : fItems) {
			System.out.println(string);
		}
	}
}
]]>
数据挖掘系列002 关联规则FP-Growth算法 http://westerly-lzh.github.io/cn/2015/08/DM002-FP-Tree/ 2015-08-31T00:00:00+00:00 Jeff Lee http://westerly-lzh.github.io/cn/2015/08/DM002-FP-Tree 关联规则用于发现交易数据中,不同商品之间的关系,这些规则反映了顾客的购买行为模式。如顾客经常在购买A商品的时候也会购买B商品,著名的“啤酒与尿布”的案例就是关联规则的成功应用案例

导语

Apriori算法是关联规则的基本算法,很多用于发现关联规则的算法都是基于Apriori算法,但Apriori算法需要多次访问数据库,具有严重的性能问题。FP-Growth算法只需要两次扫描数据库,相比于Apriori减少了I/O操作,克服了Apriori算法需要多次扫描数据库的问题。本文采用如下的样例数据

	A;B;E;
	B;D;
	B;C;
	A;B;D
	A;C;
	B;C;
	A;C;
	A;B;C;E;
	A;B;C;

FP-Growth生成FP-Tree

FP-Growth算法将数据库中的频繁项集压缩到一颗频繁模式树中,同时保持了频繁项集之间的关联关系。通过对该频繁模式树进行挖掘,可以得到频繁项集。其过程如下:

  1. 第一次扫描数据库,产生频繁1项集,并对产生的频繁项集按照频数降序排列,并剪枝支持数低于阀值的元素。处理后得到L集合,
  2. 第二次扫描数据库,对数据库的每个交易事务中的项按照L集合中项出现的顺序排序,生成FP-Tree。

image

产生fp-tree的步骤可以分解为如下:

image

从FP-Tree挖掘频繁项集

从FP-Tree重可以挖掘出频繁项集,其过程如下:

image

从频繁1项集链表中按照逆序开始,链表可以追溯到每个具有相同项的节点。

  1. 从链表中找到项“E”,追溯出FP-Tree中有两个带“E”的节点,由这两个节点分别向上(parent)追溯,形成两条模式:<E,C,A,B;1>,<E,A,B;1>.
  2. 由这两条模式得到项“E”的条件模式<A,B;2>.
  3. 根据条件模式,得到项“E”的频繁项集(不包含频繁1项集):<E,A;2>,<E,B;2>,<E,A,B;2>
  4. 然后一次得到项“D”,“C”,“A”。

FP-Growth算法简单实现

下面是该算法的一个简单的实现(其中由FP-Tree挖掘频繁项模式实现比较繁琐,计算关联规则时可以通过FP-Tree可以得到,但本文是通过得到的频繁项集得到的)。

package association;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import set.SetUtils;

/**
 * 
 * @author jeff
 * 
 */
public class FPTree {
	public static String SPLIT = ";";
	public static int F = 2;
	public static double C = 0.7;
	public static List<String> transList = new ArrayList<String>();
	Map<String, TreeNode> map = new HashMap<String, FPTree.TreeNode>();// 用来
	TreeNode root = null;
	Map<String, Integer> f1Items;
	List<String> sortedList;

	public Map<String, TreeNode> getMap() {
		return map;
	}

	public TreeNode getRoot() {
		return root;
	}

	static {
		transList.add("A;B;E;");
		transList.add("B;D;");
		transList.add("B;C;");
		transList.add("A;B;D;");
		transList.add("A;C;");
		transList.add("B;C;");
		transList.add("A;C;");
		transList.add("A;B;C;E;");
		transList.add("A;B;C;");
	}

	/**
	 * FP-Tree的节点结构
	 * @author jeff
	 *
	 */
	public class TreeNode {
		public String key;
		public Integer value;

		public TreeNode(String key, Integer value) {
			this.key = key;
			this.value = value;
			this.children = new ArrayList<FPTree.TreeNode>();
		}

		public TreeNode getParent() {
			return this.parent;
		}

		public void setParent(TreeNode parent) {
			this.parent = parent;
		}

		public List<TreeNode> getChildren() {
			return this.children;
		}

		public void setChildren(List<TreeNode> children) {
			this.children = children;
		}

		public void addChild(TreeNode child) {
			if (this.children == null) {
				this.children = new ArrayList<FPTree.TreeNode>();
			}
			this.children.add(child);
			child.setParent(this);
		}

		public TreeNode getLinked() {
			return this.linked;
		}

		public void setLinked(TreeNode linked) {
			this.linked = linked;
		}

		public TreeNode parent;
		public List<TreeNode> children;
		public TreeNode linked;
	}

	/**
	 * 扫描数据库得到频繁1项集
	 * @return
	 */
	private Map<String, Integer> getF1Item() {
		f1Items = new HashMap<String, Integer>();
		for (String tran : transList) {
			String[] items = tran.split(SPLIT);
			for (String item : items) {
				if (f1Items.get(item + SPLIT) == null) {
					f1Items.put(item + SPLIT, 1);
				} else {
					f1Items.put(item + SPLIT, f1Items.get(item + SPLIT) + 1);
				}
			}
		}
		Map<String, Integer> res = new HashMap<String, Integer>();
		for (Map.Entry<String, Integer> entry : f1Items.entrySet()) {
			if (entry.getValue() >= F) {
				res.put(entry.getKey(), entry.getValue());
			}
		}
		return res;
	}

	/**
	 * 对频繁1项集按照频次进行排序
	 * @param oriMap
	 * @return
	 */
	private List<String> sortMapByValue(Map<String, Integer> oriMap) {
		List<String> sortedNodes = new ArrayList<String>();
		if (oriMap != null && !oriMap.isEmpty()) {
			List<Map.Entry<String, Integer>> entryList = new ArrayList<Map.Entry<String, Integer>>(oriMap.entrySet());
			Collections.sort(entryList, new Comparator<Map.Entry<String, Integer>>() {
				public int compare(Entry<String, Integer> entry1, Entry<String, Integer> entry2) {
					int value1 = 0, value2 = 0;
					try {
						value1 = entry1.getValue();
						value2 = entry2.getValue();
					} catch (NumberFormatException e) {
						value1 = 0;
						value2 = 0;
					}
					return value2 - value1;
				}
			});
			Iterator<Map.Entry<String, Integer>> iter = entryList.iterator();
			Map.Entry<String, Integer> tmpEntry = null;
			while (iter.hasNext()) {
				tmpEntry = iter.next();
				sortedNodes.add(tmpEntry.getKey());
			}
		}
		return sortedNodes;
	}

	/**
	 * 根据排序后的频繁1项集,对事务中包含的项集进行排序
	 * @param tran
	 * @param sortedItems
	 * @return
	 */
	private List<String> getSortedTrans(String tran, List<String> sortedItems) {
		List<String> sortedTranItems = new ArrayList<String>();
		String[] items = tran.split(SPLIT);
		for (String item : sortedItems) {
			for (String tItem : items) {
				if (item.equals(tItem + SPLIT)) {
					sortedTranItems.add(tItem + SPLIT);
					break;
				}
			}
		}
		return sortedTranItems;
	}

	/**
	 * 构建FP-Tree
	 * @return
	 */
	public TreeNode generateFPTree() {
		map.clear();
		TreeNode linked;

		root = new TreeNode(null, null);
		root.setChildren(new ArrayList<FPTree.TreeNode>());
		getF1Item();
		sortedList = sortMapByValue(f1Items);
		for (String tran : transList) {
			List<String> sortedTran = getSortedTrans(tran, sortedList);
			TreeNode currentRoot = root;
			for (int i = 0; i < sortedTran.size(); i++) {
				boolean flag = false;
				for (TreeNode node : currentRoot.getChildren()) {
					if (node.key.equals(sortedTran.get(i))) {
						flag = true;
						currentRoot = node;
						break;
					}
				}
				if (flag) {
					currentRoot.value = currentRoot.value + 1;
				} else {
					TreeNode node = new TreeNode(sortedTran.get(i), 1);
					linked = map.get(sortedTran.get(i));
					if (linked != null) {
						node.linked = linked;
					}
					map.put(sortedTran.get(i), node);

					TreeNode child = null;
					currentRoot.addChild(node);
					for (int index = i + 1; index < sortedTran.size(); index++) {
						child = new TreeNode(sortedTran.get(index), 1);
						node.addChild(child);
						node = child;
						linked = map.get(sortedTran.get(index));
						if (linked != null) {
							node.linked = linked;
						}
						map.put(sortedTran.get(index), node);
					}
					break;
				}
			}

		}

		return root;
	}

	/**
	 * 存储由FP-Tree产生频繁项集时的中间结果
	 * @author jeff
	 *
	 */
	public class TMP {
		String item;
		Integer count;
		Set<String> items;

		public TMP(String item) {
			this.item = item;
			this.count = 0;
			this.items = new HashSet<String>();
		}
	}

	/**
	 * 产生频繁项集
	 * @return
	 */
	public Map<String, Integer> getFItems() {
		Map<String, Integer> fItems = new HashMap<String, Integer>();
		generateFPTree();
		fItems.putAll(f1Items);
		for (int i = sortedList.size() - 1; i >= 0; i--) {
			String item = sortedList.get(i);
			TreeNode node = map.get(item);
			TreeNode parent = null;
			List<TMP> cItems = new ArrayList<FPTree.TMP>();
			while (node != null) {
				parent = node;
				TMP tmp = new TMP(item);
				cItems.add(tmp);
				tmp.count = parent.value;
				while (parent != null && parent.key != null) {
					//
					if (!item.equals(parent.key)) {
						tmp.items.add(parent.key);
					}
					parent = parent.parent;
				}
				node = node.linked;
			}
			List<TMP> res = new ArrayList<FPTree.TMP>();
			for (int k = 0; k < cItems.size(); k++) {
				if (cItems.get(k).count >= F) {
					TMP tmp = new TMP(cItems.get(k).item);
					tmp.items.addAll(cItems.get(k).items);
					tmp.count = cItems.get(k).count;
					res.add(tmp);
				}
				for (int l = k + 1; l < cItems.size(); l++) {
					TMP tmp = new TMP(cItems.get(k).item);
					tmp.items.addAll(cItems.get(k).items);
					tmp.items.retainAll(cItems.get(l).items);
					tmp.count = cItems.get(k).count + cItems.get(l).count;
					if (tmp.items.size() > 0) {
						res.add(tmp);
					}
				}
			}

			for (TMP tmp : res) {
				// 需要得到集合的子集
				for (Set<String> set : SetUtils.getSubset(tmp.items)) {
					if (set.size() == 0) {
						continue;
					}
					StringBuilder builder = new StringBuilder();
					builder.append(tmp.item);
					for (String it : set) {
						builder.append(it);
					}
					String fitem = builder.toString();
					if (fItems.containsKey(fitem)) {
						fItems.put(fitem, fItems.get(fitem).intValue() >= tmp.count ? fItems.get(fitem) : tmp.count);
					} else {
						fItems.put(fitem, tmp.count);
					}

				}
			}
		}

		return fItems;
	}

	/**
	 * 有频繁项集生成关联规则
	 * @return
	 */
	public Map<String, Double> getRules() {
		Map<String, Double> rules = new HashMap<String, Double>();
		Map<String, Integer> fItems = getFItems();

		Set<String> result = new HashSet<String>();
		for (String key1 : fItems.keySet()) {
			for (String key2 : fItems.keySet()) {
				Set<String> union1 = new HashSet<String>();
				Set<String> union2 = new HashSet<String>();

				union1.addAll(Arrays.asList(key1.split(SPLIT)));
				union2.addAll(Arrays.asList(key2.split(SPLIT)));

				result.clear();
				result.addAll(union1);
				result.retainAll(union2);
				if (result.size() == 0) {
					result.clear();
					result.addAll(union1);
					result.addAll(union2);
					boolean flag = false;
					for (String key3 : fItems.keySet()) {
						flag = false;
						for (String item : result) {
							if (!key3.contains(item + SPLIT)) {
								flag = true;
								break;
							}
						}
						if (!flag) {
							double conf = fItems.get(key3) / (double) fItems.get(key1);
							if (conf > C) {
								rules.put(key1 + "->" + key2, conf);
							}
						}
					}
				}
			}
		}

		return rules;

	}

	public static void main(String[] args) {
		FPTree tree = new FPTree();
		// tree.generateFPTree();
		// TreeNode root = tree.getRoot();
		// Map<String, TreeNode> map = tree.getMap();
		// System.out.println(root);
		// System.out.println(map);
		Map<String, Integer> fItems = tree.getFItems();
		for (Entry<String, Integer> item : fItems.entrySet()) {
			System.out.println(item.getKey() + ":   " + item.getValue());
		}

		Map<String, Double> rules = tree.getRules();
		for (Entry<String, Double> item : rules.entrySet()) {
			System.out.println(item.getKey() + ":   " + item.getValue());
		}
	}

}
]]>
数据挖掘系列001 关联规则Apriori算法 http://westerly-lzh.github.io/cn/2015/08/DM001-Apriori/ 2015-08-29T00:00:00+00:00 Jeff Lee http://westerly-lzh.github.io/cn/2015/08/DM001-Apriori 关联规则用于发现交易数据中,不同商品之间的关系,这些规则反映了顾客的购买行为模式。如顾客经常在购买A商品的时候也会购买B商品,著名的“啤酒与尿布”的案例就是关联规则的成功应用案例

基本概念

  • 交易数据库(Transaction Database):存储着二维结构的记录集。定义为:D
  • 所有项集(Items):所有项目的集合。定义为:I。
  • 记录 (Transaction ):在资料库里的一笔记录。定义为:T,T ∈ D
  • 项集(Itemset):同时出现的项的集合。定义为:k-itemset(k项集),k-itemset ? T。除非特别说明,否则下文出现的k均表示项数。
  • 支持度(Support):定 义为 supp(X) = occur(X) / count(D) = P(X)。

      1. 解释一:比如选秀比赛,那个支持和这个有点类似,那么多人(资料库),其中有多少人是选择(支持)你的,那个就是支持度;
      2. 解释二:在100个人去超市买东西的,其中买苹果的有9个人,那就是说苹果在这里的支持度是 9,9/100;
      3. 解释三:P(X),意思是事件X出现的概率;
      4. 解释四:关联规则当中是有绝对支持度(个数)和相对支持度(百分比)之分的。
    
  • 置信度(Confidence/Strength): 定义为 conf(X->Y) = supp(X ∪ Y) / supp(X) = P(Y X)。
      在历史数据中,已经买了某某(例如:A、B)的支持度和经过挖掘的某规则(例如:A=>B)中A的支持度的比 例,也就是说买了A和B的人和已经买了 A的人的比例,这就是对A推荐B的置信度(A=>B的置信度)< /span>
    
  • 候选集(Candidate itemset):通过向下合并得出的项集。定义为C[k]。
  • 频繁集(Frequent itemset):支持度大于等于特定的最小支持度(Minimum Support/minsup)的项集。表示为L[k]。注意,频繁集的子集一定是频繁集。
  • 提升比率(提升度Lift):lift(X -> Y) = lift(Y -> X) = conf(X -> Y)/supp(Y) = conf(Y -> X)/supp(X) = P(X and Y)/(P(X)P(Y))

      经过关联规则分析后,针对某些人推销(根据某规则)比盲目推销(一般来说是整个数据)的比率,这个比率越高越好,我们称这个规则为强规则;
    

获取频繁项集

Aprior算法的重点在于寻找频繁项集,其算法过程如下: Apriori算法过程

  1. 由交易数据库产生频繁1项集,产生方式:通过扫描交易数据库,对每个交易事务中的记录进行扫描,对每个记录进行计数,过滤出满足频繁项集支持数的那些记录。
  2. 频繁1项集产生频繁K项集合的过程
  3. 假设存在频繁K-1项集,产生频繁K项集合的候选集,产生方式:频繁K-1项集进行自连接,得到频繁K项集的候选集。
  4. 对候选集中的记录进行筛选,其筛选规则:首先剔除相同元素的自连接,然后扫描数据库,保留出现在数据库中的候选集元素,并对出线次数计数,接着过滤出满足频繁项集支持数的进行,得到频繁K项集
  5. 把得到的频繁K项集并入频繁项集中。

虚线框右下角的N表示执行找到频繁N项集,该虚线框内的内容表示迭代过程。

获取关联规则

由频繁项集,可以产生关联规则。对频繁项集中的任意两个事务,如果这两个事务的商品条目(Item)没有交集,而这两个事务的的条目的并集又包含在该频繁项集合中,那么就可以计算出相应的置信度,如果置信度大于给定的阀值,则这两个事务具有关联关系。

简要算法实现

package association;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

public class Apriori2 {

	public static String SPLIT = ";";
	public static int F = 2;
	public static double C = 0.7;
	
	public static List<String> transList = new ArrayList<String>();
	
	static {
		transList.add("A;B;E;");
		transList.add("B;D;");
		transList.add("B;C;");
		transList.add("A;B;D");
		transList.add("A;C;");
		transList.add("B;C;");
		transList.add("A;C;");
		transList.add("A;B;C;E;");
		transList.add("A;B;C;");
	}

	private Map<String, Integer> getF1Item() {
		Map<String, Integer> F1items = new HashMap<String, Integer>();
		for (String tran : transList) {
			String[] items = tran.split(SPLIT);
			for (String item : items) {
				if (F1items.get(item + SPLIT) == null) {
					F1items.put(item + SPLIT, 1);
				} else {
					F1items.put(item + SPLIT, F1items.get(item + SPLIT) + 1);
				}
			}
		}
		Map<String, Integer> res = new HashMap<String, Integer>();
		for (Map.Entry<String, Integer> entry : F1items.entrySet()) {
			if (entry.getValue() >= F) {
				res.put(entry.getKey(), entry.getValue());
			}
		}
		return res;
	}

	public Map<String, Integer> getFItems() {
		Map<String, Integer> f1items = getF1Item();
		Map<String, Integer> fItems = new HashMap<String, Integer>();
		Map<String, Integer> tFItems = new HashMap<String, Integer>();
		fItems.putAll(f1items);
		tFItems.putAll(f1items);
	
		while (tFItems != null && tFItems.size() != 0) {
			Map<String, Integer> cItems = getCItems(tFItems);
			//过滤候选集
			for (String tran : transList) {
				for (String citems : cItems.keySet()) {
					boolean flag = false;
					String[] items = citems.split(SPLIT);
					for (String item : items) {
						if (tran.indexOf(item + SPLIT) == -1) {
							flag = true;
							break;
						}
					}
					if (!flag) {
						cItems.put(citems, cItems.get(citems) + 1);
					}
				}

			}

			tFItems.clear();
			for (Entry<String, Integer> citem : cItems.entrySet()) {
				if (citem.getValue() >= F) {
					tFItems.put(citem.getKey(), citem.getValue());
				}
			}
			fItems.putAll(tFItems);
		}
		return fItems;
	}

	/**
	 * 产生候选集
 	 * @param fitems
 	 * @return
 	 */
	private Map<String, Integer> getCItems(Map<String, Integer> fitems) {
		Map<String, Integer> citems = new HashMap<String, Integer>();
		Set<String> items = new HashSet<String>();
	
		for (String item1 : fitems.keySet()) {//频繁K项集的自连接,产生候选集
			for (String item2 : fitems.keySet()) {
				if (!item1.equals(item2)) {
					String[] items1 = item1.split(SPLIT);
					String[] items2 = item2.split(SPLIT);
					items.addAll(Arrays.asList(items1));
					items.addAll(Arrays.asList(items2));
					StringBuilder builder = new StringBuilder();
					for (String string : items) {
						builder.append(string).append(SPLIT);
					}
					items.clear();
					citems.put(builder.toString(), 0);
				}
			}

		}
		return citems;
	
	}
	
	
	/**
 	 * 由频繁项集构建关联规则
 	 * @param fItems
 	 * @return
 	 */

	public Map<String,Double> getRule(Map<String,Integer> fItems){
		Map<String,Double> rules = new HashMap<String, Double>();
	
		Set<String> result = new HashSet<String>();
		for(String key1 : fItems.keySet()){
			for(String key2:fItems.keySet()){
				Set<String> union1 = new HashSet<String>();
				Set<String> union2 = new HashSet<String>();
			
				union1.addAll(Arrays.asList(key1.split(SPLIT)));
				union2.addAll(Arrays.asList(key2.split(SPLIT)));
			
				result.clear();
				result.addAll(union1);
				result.retainAll(union2);
				if(result.size()==0){//两个频繁项没有交集
					result.clear();
					result.addAll(union1);
					result.addAll(union2);
					boolean flag = false;
					for(String key3:fItems.keySet()){//两个频繁项的的并集也是频繁项
						flag=false;
						for(String item:result){
							if(!key3.contains(item+SPLIT)){
								flag = true;
								break;
							}
						}
						if(!flag){//计算这两个频繁项关联关系的置信度
							double conf = fItems.get(key3)/(double)fItems.get(key1);
							if(conf>C){
								rules.put(key1+"->"+key2, conf);
							}
						}
					}
				}
			}
		}
	
		return rules;
	}

	public static void main(String[] args) {
		Apriori2 ap2 = new Apriori2();
		Map<String, Integer> fItems = ap2.getFItems();
		for (Entry<String, Integer> item : fItems.entrySet()) {
			System.out.println(item.getKey() + ":   " + item.getValue());
		}
	}

}
]]>
D3最佳实践:004 基本的SVG http://westerly-lzh.github.io/cn/2015/08/D3-004/ 2015-08-24T00:00:00+00:00 Jeff Lee http://westerly-lzh.github.io/cn/2015/08/D3-004

D3学习系列4

本篇讨论D3绘制基本图形。

线条的绘制

直线

在svg中绘制一条直线,只需要指定该之间的开始坐标和结束坐标即可,如:

<line x1="0" y1="0" x2="300" y2="300"/>

直线可以指定的表现属性包括:

stroke:指定线条颜色
stroke-width:指定线条宽度

如下直线绘制代码:

svg.append("line").attr("x1",10).attr("y1",10).attr("x2",40).attr("y2",90).attr("stroke","green").attr("stroke-width",1);
svg.append("line").attr("x1",90).attr("y1",10).attr("x2",40).attr("y2",90).attr("stroke","green").attr("stroke-width",2);
svg.append("line").attr("x1",180).attr("y1",10).attr("x2",40).attr("y2",90).attr("stroke","green").attr("stroke-width",3);
svg.append("line").attr("x1",300).attr("y1",10).attr("x2",40).attr("y2",90).attr("stroke","green").attr("stroke-width",4);
svg.append("line").attr("x1",400).attr("y1",10).attr("x2",40).attr("y2",90).attr("stroke","green").attr("stroke-width",5);

可以产生如下直线:

基本形状的绘制

圆,在svg中绘制圆形需要圆心坐标和半径长度,如:

<circle cx="60" cy="50" r="50" fill="red" stroke="blue" stroke-width="10"  />

椭圆,在svg中绘制需要指定 椭圆的圆心、x轴半径和y轴半径的长度,如:

<ellipse cx="200" cy="50" rx="80" ry="40" fill="yellow" stroke="blue" stroke-width="5"  />

矩形,在svg绘制中需要指定矩形左上角的坐标位置、矩形的长、宽,和矩形角如果为圆角时的半径(非必须参数),如:

<rect x="300" y="20" width="80" height="60" fill="yellow" stroke="navy" stroke-width="10"  />
<rect x="400" y="20" width="80" height="60" rx=“5” ry=“5” fill="yellow" stroke="navy" stroke-width="5"  />

多边形,在svg中绘制其他多边形,可以通过制定一个序列点来完成,svg会按照点的顺序进行绘制,最后把绘制曲线首尾连接,形成封闭图形,如:

<polygon fill="red" stroke="blue" stroke-width="10" 
        points="350,75  379,61 469,61 397,15 423,30
         350,50 277,77 303,15 231,61 321,61" />

基本形状都可以设置如下属性:

fill:形状的填充颜色
stroke:绘制形状的线条颜色
stroke-width:绘制形状的线条的宽度

如下代码:

svg.append("circle").attr("cx",60).attr("cy",50).attr("r",40).attr("fill","red").attr("stroke","blue").attr("stroke-width",10);
svg.append("ellipse").attr("cx",200).attr("cy",50).attr("rx",80).attr("ry",40).attr("fill","yellow").attr("stroke","green").attr("stroke-width",5);
svg.append("rect").attr("x",300).attr("y",20).attr("width",80).attr("height",60).attr("fill","yellow").attr("stroke","green").attr("stroke-width",10);
svg.append("rect").attr("x",400).attr("y",20).attr("width",80).attr("height",60).attr("rx",5).attr("ry",5).attr("fill","yellow").attr("stroke","green").attr("stroke-width",5);
svg.append("polygon").attr("points","350,75  379,61 469,61 397,15 423,30 350,50 277,77 303,15 231,61 321,61").attr("fill","red").attr("stroke","blue").attr("stroke-width",10);

可以产生如下形状:

其他形状绘制

在svg中绘制其他类型的曲线,需要使用path元素.path采用命令的方式进行绘制,命令如下:

命令 名称 参数 解释
M (absolute) Move to (x y)+ 从给定的点开始绘制path,如果M(m)后跟着多个坐标位置,则暗含着这些坐标为lineto命令
m (relative) Move to (x y)+  
Z(z) closepath (none) 用来表示结束path的绘制,结束为止和开始位置相连
L(l) line to (x y)+ 从当前位置绘制直线到给定的坐标位置
H(h) horizontal line to x+ 从当前位置绘制水平直线,长度为x
V(v) vertical line to y+ 从当前位置绘制垂直直线,长度为y
C(c) curve to (x1 y1 x2 y2 x y)+ 从当前位置绘制三次贝塞尔曲线到(x y)点,使用(x1 y1),(x2 y2)作为曲线的控制点
S(s) shorthand/smooth curve to (x2 y2 x y)+  
Q(q) quadratic Bezier curve to (x1 y1 x y)+ 从当前位置绘制二次贝塞尔曲线到(x y)位置,使用(x1 y1)作为控制点
T(t) shorthand/smooth quadratic bezier curve to (x y)+  
A(a) elliptical arc (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+ 从当前点绘制椭圆弧到(x y)点,(rx ry)指定了绘制椭圆弧的轴长,x-axis-rotation 指定了椭圆弧相对当前坐标系统的旋转,椭圆弧所在椭圆的圆心自动计算得到。
<path d="M0,350 l 50,-25 
       a25,25 -30 0,1 50,-25 l 50,-25 
       a25,50 -30 0,1 50,-25 l 50,-25 
       a25,75 -30 0,1 50,-25 l 50,-25 
       a25,100 -30 0,1 50,-25 l 50,-25"
    fill="none" stroke="red" stroke-width="5"  />

如下绘制代码:

svg.append("path").attr("d","M0,350 l 50,-25 a25,25 -30 0,1 50,-25 l 50,-25 a25,50 -30 0,1 50,-25 l 50,-25 a25,75 -30 0,1 50,-25 l 50,-25 a25,100 -30 0,1 50,-25 50,-25").attr("fill","none").attr("stroke","red").attr("stroke-width",5);

可以产生如下形状:

]]>