当你获得一组数据
头发 声音 性别 [['长', '粗', '男'], ['短', '粗', '男'], ['短', '粗', '男'], ['长', '细', '女'], ['短', '细', '女'], ['短', '粗', '女'], ['长', '粗', '女'], ['长', '粗', '女']]
该如何对它按照特征进行分类
当然,最简单的,可以利用groupby进行分组
import pandas as pd
dataset = [['长', '粗', '男'],
['短', '粗', '男'],
['短', '粗', '男'],
['长', '细', '女'],
['短', '细', '女'],
['短', '粗', '女'],
['长', '粗', '女'],
['长', '粗', '女']]
hair_list = []
voice_list = []
sex_list = []
for i in range(len(dataset)):
hair_list.append(dataset[i][0])
voice_list.append(dataset[i][1])
sex_list.append(dataset[i][2])
data = pd.DataFrame({
"hair": hair_list,
"voice": voice_list,
"sex": sex_list
})
group = data.groupby("sex") # 按照sex进行分类
for i in list(group): # 如果不追求美观的话此处循环也可用print(list(group))代替
print(i)
if i == ")":
print("\n")
可以得到这样的输出结果
(‘女’, hair voice sex
3 长 细 女
4 短 细 女
5 短 粗 女
6 长 粗 女
7 长 粗 女)
(‘男’, hair voice sex
0 长 粗 男
1 短 粗 男
2 短 粗 男)
但这毕竟是人为给定的分类结果,存在着数据量过大不好认为分析或分析错误的情况(不一定选择的特征就是最优特征),那么,此处就要根据各个特征所蕴含的信息熵来进行自动化分析
首先是库的导入
from math import log
import operator
再者是创建原始数据(当然也可以采用直接导入的方式 ,但此示例仅以少量数据做示范)
def create_dataset(): # 创造示例数据
dataset = [['长', '粗', '男'],
['短', '粗', '男'],
['短', '粗', '男'],
['长', '细', '女'],
['短', '细', '女'],
['短', '粗', '女'],
['长', '粗', '女'],
['长', '粗', '女']]
labels = ['头发', '声音'] # 两个特征
return dataset, labels # 返回数据集和特征表
然后是一些辅助类的函数
如切片函数(姑且这么叫吧)
def split_dataset(dataset, axis, value): # 按某个特征分类后的数据
ret_dataset = [] # 设置初始返回数据集为空列表
for featVec in dataset: # 遍历数据集
if featVec[axis] == value: # 如果数据集元组第axis列的值等于value,则对其进行切片
reduced_feat_vec = featVec[:axis]
reduced_feat_vec.extend(featVec[axis + 1:])
ret_dataset.append(reduced_feat_vec)
return ret_dataset # 返回切片结果
多数特征选择函数
def majority_cnt(class_list): # 按分类后类别数量排序,比如:最后分类为2男1女,则判定为男;
class_count = {} # 设置类别字典初始值为空
for vote in class_list: # 遍历类别列表
if vote not in class_count.keys(): # 如果该类别不在类别字典中,则添加该类别,并设置该类别初始数量值为0
class_count[vote] = 0
class_count[vote] += 1 # 统计各类别对应的类别数量
sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True) # 按照类别数量对类别字典进行降序排序
return sorted_class_count[0][0] # 返回类别字典第一个类别,即数量最大的类别
然后是一些较为重要的函数了
比如计算信息熵
def calc_shannon_entropy(dataset): # 计算数据的熵(entropy)
num_entries = len(dataset) # 数据条数
label_counts = {} # 创建一个关于性别及其数量的字典
for featVec in dataset: # 遍历数据集
current_label = featVec[-1] # 获取每行数据的最后一个字(类别),即性别
if current_label not in label_counts.keys(): # 如果性别不在label_counts字典的关键字里
label_counts[current_label] = 0 # label_counts字典中current_label(性别)关键字取初始值0
label_counts[current_label] += 1 # 统计有多少个类以及每个类的数量
shannon_ent = 0 # 设置信息熵的初始值为0
for key in label_counts: # 遍历label_counts字典
prob = float(label_counts[key]) / num_entries # 计算单个类的熵值
shannon_ent -= prob * log(prob, 2) # 累加每个类的熵值
return shannon_ent # 返回信息熵的值
再比如最优特征值的选择函数(之所以选择老长串的英文也是方便大家理解嘛(O~O))
def choose_best_feature_to_split(dataset): # 选择最优的分类特征
num_features = len(dataset[0]) - 1 # 计算特征的数量
base_entropy = calc_shannon_entropy(dataset) # 数据集原始的熵
best_info_gain = 0 # 设置最优信息增益初始值为0
best_feature = -1 # 设置最优特征初始值为-1
for i in range(num_features): # 遍历所有特征
feat_list = [example[i] for example in dataset] # 获取第i列特征列表
unique_values = set(feat_list) # 剔除该列重复特征,保留该列所有不重复的特征
new_entropy = 0 # 设置新的熵值为0
for value in unique_values: # 遍历该列所有不重复的特征
sub_dataset = split_dataset(dataset, i, value) # 对数据集第i列按特征进行分类
prob = len(sub_dataset) / float(len(dataset))
new_entropy += prob * calc_shannon_entropy(sub_dataset) # 按特征分类后的熵
info_gain = base_entropy - new_entropy # 原始熵与按特征分类后的熵的差值,即信息增益(info_gain)
if info_gain > best_info_gain: # 获取信息增益最大的特征作为最优特征
best_info_gain = info_gain
best_feature = i
return best_feature # 返回最优特征
最后,合成大法——创建决策树
def create_tree(dataset, labels): # 创造决策树
class_list = [example[-1] for example in dataset] # 类别:男或女,将数据集最后一列单独取出,构成类别列表
if class_list.count(class_list[0]) == len(class_list): # 如果类别列表中类别唯一,则返回该唯一类别
return class_list[0]
if len(dataset[0]) == 1: # 如果数据集中第一个元组只有一个元素,则返回majority_cnt函数结果
return majority_cnt(class_list) # 返回类别列表的主要类别
best_feat = choose_best_feature_to_split(dataset) # 选择最优特征
beat_feat_label = labels[best_feat]
my_tree = {beat_feat_label: {}} # 设置决策树为以最优特征为键的字典,分类结果以字典形式保存
del (labels[best_feat]) # 删除特征列表里的最优特征,保留余下的特征
feat_values = [example[best_feat] for example in dataset] # 获取数据集里的最优特征列,并创建特征值列表feat_values
unique_values = set(feat_values) # 剔除特征值列表的重复数据,获取其中所有的唯一元素,并保存在unique_values中
for value in unique_values: # 遍历唯一特征值列表unique_values
sub_labels = labels[:] # 设置暂存特征列表为余下特征列表labels
# 以数据集dataset中按照最优特征best_feat分类后的数据和暂存特征列表,进行递归调用create_tree函数,不断完善决策树my_tree字典
my_tree[beat_feat_label][value]\
= create_tree(split_dataset(dataset, best_feat, value), sub_labels)
return my_tree # 返回my_tree决策树
再把主函数添上
if __name__ == "__main__":
dataset_, labels_ = create_dataset()
print(create_tree(dataset_, labels_))
来看看输出结果
{‘声音’: {‘粗’: {‘头发’: {‘短’: ‘男’}}}}
或者
{‘声音’: {‘细’: ‘女’}}
或者
{‘声音’: {‘粗’: {‘头发’: {‘长’: ‘女’}}}}
不用太过去纠结结果与现实的正确性(谁能拒绝程序员写出一个声音粗犷的小姐姐呢╰( ̄▽ ̄)╮)
但是为什么同样一个决策树的输出结果会不一样呢???
文章出处登录后可见!
已经登录?立即刷新