一、预处理
1.打开csv打分表,并指出数据存在的问题,给出相应的解决方案;
打分表中,前五行数据为空白或不在统计范围内的数据,第六行数据为列名,因此需要从第七行开始进行存储。在列名中,存在许多同学所处分组、得分、提交标题和提交内容为空,还有一名同学专业和班级为空,因此在进行数据统计值分析前,需要把统计依照的列为空的项以及得分为空的项删除。
2.使用python从csv文件中提取数据,使用合适的数据结构进行保存,随后进行预处理;
# 将xlsx文件转化为csv文件
def xlsx_to_csv():
workbook = xlrd.open_workbook('C:/Users/admin/Desktop/数据挖掘/实验一/数据挖掘与分析-你说对就.xlsx')
table = workbook.sheet_by_index(0)
with codecs.open('数据挖掘与分析-你说对就.csv', 'w', encoding='gbk') as f:
write = csv.writer(f)
for row_num in range(table.nrows):
row_value = table.row_values(row_num)
write.writerow(row_value)
def loadDataSet():
xlsx_to_csv()
fp = open('数据挖掘与分析-你说对就.csv', 'r')
df = [line.strip().split(',') for line in fp.readlines()[6:]]
dataset = []
for d in df:
if len(d) > 9: # 不能出现过多空数据项
if d[8] != '' and d[9] != '':
dataset.append([d[8], d[9]])
return dataset
由于课程资料给的是xlsx文件,为了方便后续操作,编写xlsx_to_csv函数,将xlsx文件转为csv文件。随后编写loadDataset函数,通过判断分隔符“,”对数据进行分割,并从第七行数据开始获取,存放入dataframe,并创建事务集dataset,通过if语句判断每一行数据是否符合数据统计分析的要求,即相关数据是否为空,再将符合条件的数据append给dataset,最后得到的dataset就是参与数据统计的数据行的集合。
在将xlsx文件转为csv文件时,一开始使用的encoding是utf-8,转为csv之后用写字板打开,发现出现了大量乱码,网上查阅资料后将encoding换成了gbk,随后就没有乱码了。而在对数据进行选择时,一开始混淆了dataframe和list,并且没有理解如何判断dataframe的某一列是否为空,导致频频出现报错,最后通过for循环找到了每一行,再依次判断数据的可用性,符合要求则存入dataset。
3.针对各列评分属性,进行数据统计值分析,并说明和解释分析结果。
if __name__=='__main__':
dataSet = loadDataSet()
df = pd.DataFrame(dataSet, columns=["所处分组", "得分"])
df[['得分']] = df[['得分']].apply(pd.to_numeric)
df = df.groupby('所处分组')['得分'].mean().sort_values()
df.to_csv('你说对就队.csv')
print(df)
为了有效地进行数据统计值分析,通过调用loadDataset函数获取想要的列存入事务集dataSet,随后将dataSet存入dataframe便于之后的操作,同时根据loadDataset中获取的列为数据设置列名。然后,调用groupby函数来为数据根据设置的列名进行分组,再调用mean函数根据设置的列名对每一组取平均值。此处根据“所处分组”进行分组,求出每一组的得分均值。最后根据求出的均值进行排序,导出为csv文件并进行结果的打印。
调用groupby函数与mean函数时,是根据列名来进行的,而先前导出的dataSet并没有设置列名,因此需要在转为dataframe时设置列名,方便数据统计值分析。在尝试运行后发现存在报错,和同学交流探讨后发现是“得分”的数据类型为文本而非数值,无法对其求平均值,因此补充了一条df[['得分']].apply(pd.to_numeric)语句,将“得分”的数据类型转为数值,再运行就成功求出平均值了。
我分别根据所处分组、专业、院系这三项进行分组并求得分的平均值,并进行升序排列。从按“所处分组”进行分组的结果图中可以看出,第六组打分最高,为96分,第20组打分最低,为79分,分数跨度较大,但普遍打分在90分及以上。从按“专业”进行分组的结果图中可以看出,交通运输(汽车运用工程)(中美合作)这一专业的同学打分最高,为96分,而数据科学与大数据技术的同学打分最低,平均分约为90分,平均分都在90分以上。从按“院系”进行分组的结果图中可以看出,每个院系打分的平均分都在90分至94分之间。通过以上三组数据可以看出,大部分同学对该组的汇报认可度较高,仅有第20组和第3组同学较为不满意,可以询问该两组同学相关改善建议。
二、关联规则算法
1.调试并理解Apriori算法的源代码实现apriori.py。
apriori.py中,主程序中先调用了loadDataSet函数,将统计数据返回给dataSet,随后调用apriori函数。在apriori函数中,先调用createC1函数,从事务集中获取候选1项集,再事务集的每个元素转化为集合,然后调用scanD函数获取频繁1项集和对应的支持度并存入L。之后进入循环,调用aprioriGen函数找到k阶候选项集,再调用scanD函数获取对应的k阶频繁项集和支持度并append到L,更新supportData。这样就获得了所有的频繁项集和支持度。
createC1函数通过循环嵌套定位到每个元素,随后判断该元素是否已存在于候选1项集C1中,若不存在则append至C1,查找完所有元素后排序。返回数据为候选1项集。
scanD函数利用之前获得的k阶候选项集和事务集元素的集合,通过循环查找事务集元素的每个集合中,每个候选项集出现的次数,存入ssCnt。随后通过循环计算每个候选项集的支持度,再判断支持度是否大于预先设置的最小支持度,若大于,则为频繁项集,插入retList的首部,并将支持度存入supportData。返回数据为在Ck中找出的频繁项集以及各频繁项集的支持度。
aprioriGen函数接收的第一个参数是多个数据组,将Lk中各组元素按照一定原则每两组之间合并成一组。 其中规则与k值相关,k=2时,如果第一组数据和第二组数据前两个数据排序后相同,则合并这两个数据组。最后返回数据为合并数据组后的retList。
2.将数据集改为:
TID | 商品列表 | |||
101 | 面包 | 可乐 | 麦片 | |
102 | 牛奶 | 可乐 | ||
103 | 牛奶 | 面包 | 麦片 | |
104 | 牛奶 | 可乐 | ||
105 | 面包 | 鸡蛋 | 麦片 | |
106 | 牛奶 | 面包 | 可乐 | |
107 | 牛奶 | 面包 | 鸡蛋 | 麦片 |
108 | 牛奶 | 面包 | 可乐 | |
109 | 面包 | 可乐 |
def loadDataSet():
return [['面包', '可乐', '麦片'], ['牛奶', '可乐'], ['牛奶', '面包', '麦片'], ['牛奶', '可乐'], ['面包', '鸡蛋', '麦片'], ['牛奶', '面包', '可乐'], ['牛奶', '面包', '鸡蛋', '麦片'], ['牛奶', '面包', '可乐'],['面包', '可乐']]
数据集的导入仅与loadDataset的返回值有关,故将返回值改为商品列表的事务集即可。
3.改进代码,从频繁项集结果中,提取2阶频繁项集。
L, suppData = apriori(dataSet, minSupport=0.3)
print(L[1])
k阶频繁项集由scanD函数得出后存放于L[k-1]中,因此提取2阶频繁项集只需要打印L[k-1]即可。但因最小支持度设置过高的话,则不存在2阶频繁项集,打印结果为空集合。故将最小支持度minSupport设置为0.3来进行实验,得出相应2阶频繁项集。
4.使用seaborn工具包,实现2阶频繁项集的热力图表示。
def draw_seaborn(suppData, n):
list1 = []
list2 = []
for i in range(n):
list3 = []
list1.append(i + 1) # 存放已查找的2阶项集
for j in range(n):
a = list(map(frozenset, [[i + 1, j + 1]]))[0]
if a in suppData:
list3.append(suppData[a]) # 存放当前元素的所有2阶项集的支持度
else:
list3.append(0.00)
list2.append(list3) # 存放每一个元素对应的所有2阶项集的支持度
data = {}
for num in range(n):
data[list1[num]] = list2[num] # 将list2中存放的每一个元素对应的所有2阶项集的支持度存放到对应list1的data数据集中
df = pd.DataFrame(data, index=list1, columns=list1)
return sns.heatmap(df, linewidths=.5, annot=True, yticklabels=List_Dataset, xticklabels=List_Dataset)
draw_seaborn函数先创建了list1和list2两个列表,list1用于存放当前正在获取2阶项集支持度的元素,list2用于存放每一个元素对应的所有2阶项集的支持度。
然后进入循环,依次获取每个元素对应的所有的2阶项集支持度,同时创建list3,用于存放当前元素的所有2阶项集支持度。
随后嵌套循环,依次获取当前元素的每一个2阶项集的支持度。实现方法是判断该2阶项集是否存在于之前apriori函数得出的支持度数据集suppData中。若不存在,则说明该2阶项集支持度为0;若存在,则为suppData中对应的值。将结果append给list3,结束内循环后,得到当前元素的完整的list3,再将list3依次append给list2,合成总支持度列表,结束外循环。
下一步依旧运用循环,将list2中的数据,一一对应地赋给data列表,key为list1中对应的元素,从而将支持度与元素绑定,这才能在之后将data转为dataframe型,并为其index和column的命名直接赋list1的值。此处由于index与column名称相同,都为list1,所以不对二者进行区分,统一使用list1。若index与column名称不同,则需另外创建一个list2用于存放index的名称。
最后返回的值调用了seaborn工具包中的heatmap函数,即热力图。df为热力图提供矩形数据集,其index和column信息分别对应到热力图的的columns和rows。linewidths定义了热力图中的每个方格之间的间隔大小。annot默认为False,当annot为True时,表明在热力图中的每个方格写入数据。xticklabels控制每列标签名的输出,yticklabels控制每行标签名的输出,此处应当设置为list1,但由于list1指代物品与List_Dataset一致,为了图像信息更加明确清晰,此处设置为List_Dataset。
5.改进代码,计算2阶频繁项集的置信度(Confidence)和提升度(Lift)。定义最小置信度阈值,并生成和输出2阶关联规则。
def data_deal(L, suppData):
min = -1
DataList = []
for i in range(len(L[1])):
for j in L[1][i]:
if i > min:
min = i
List = []
List.append(suppData[L[1][i]])
List.append(j)
DataList.append(List)
Datanum = {}
for m in range(len(L[0])):
Datanum[len(L[0]) - m] = L[0][m]
return DataList, Datanum
# 计算置信度
def confidence(DataList, Datanum, suppData):
confidence_data = {}
for num in range(len(DataList)):
str1 = List_Dataset[DataList[num][1]-1] + '->' + List_Dataset[DataList[num][2]-1]
confidence_data[str1] = DataList[num][0] / suppData[Datanum[DataList[num][1]]]
str2 = List_Dataset[DataList[num][2]-1] + '->' + List_Dataset[DataList[num][1]-1]
confidence_data[str2] = DataList[num][0] / suppData[Datanum[DataList[num][2]]]
return confidence_data
# 计算提升度
def Lift(DataList, confidence_data, Datanum, suppData):
Lift_data = {}
for num in range(len(DataList)):
str1 = List_Dataset[DataList[num][1]-1] + '->' + List_Dataset[DataList[num][2]-1]
Lift_data[str1] = confidence_data[str1] / suppData[Datanum[DataList[num][2]]]
str2 = List_Dataset[DataList[num][2]-1] + '->' + List_Dataset[DataList[num][1]-1]
Lift_data[str2] = confidence_data[str2] / suppData[Datanum[DataList[num][1]]]
return Lift_data
# 定义最小置信度阈值
def limit(confidence_data, Lift_data, minConfidence=0.5):
limit_data = []
for k in confidence_data.keys():
data = []
if confidence_data[k] >= minConfidence:
data.append(k)
data.append(confidence_data[k])
data.append(Lift_data[k])
limit_data.append(data)
return limit_data
if __name__ == '__main__':
dataSet = loadDataSet()
List_Dataset = ['面包', '可乐', '麦片', '牛奶', '鸡蛋']
L, suppData = apriori(dataSet, minSupport=0.2)
DataList, Datanum = data_deal(L, suppData)
confidence_data = confidence(DataList, Datanum, suppData)
Lift_data = Lift(DataList, confidence_data, Datanum, suppData)
limit_data = limit(confidence_data, Lift_data, minConfidence=0.5)
for i in limit_data:
print(i)
draw_seaborn(suppData, len(L[0]))
plt.show()
为了计算2阶频繁项集的置信度与提升度,需要先获取所有2阶频繁项集及其支持度的列表集合DataList和涉及到的1阶频繁项集的集合Datanum。data_deal函数满足了这一需求,函数体中利用循环嵌套的方式,内循环找到每一个1阶频繁项集对应2阶频繁项集的支持度,随后将支持度与2阶项集append至List,外循环将每个List逐一append给DataList。另外,data_deal又通过循环,将1阶频繁项集依次赋给Datanum。最后返回DataList和Datanum。
confidence函数用于计算置信度。通过循环逐一将DataList中的每个List在List_Dataset中对应的物品名称以不同的顺序用“->”连接来表示关联规则,并存入str1与str2。随后计算相应的置信度,并以str1和str2作为confidence_data的key,将置信度存入confidence_data[str1]和confidence_data[str2]。最后返回的confidence_data中包含所有2阶频繁项集的置信度。
Lift函数用于计算提升度,函数体大致与confidence函数一致,仅在公式上有所区别。计算置信度时是2阶频繁项集的支持度除以1阶频繁项集的支持度,而计算提升度时是2阶频繁项集的置信度除以1阶频繁项集的支持度。
limit函数定义最小置信度阈值,通过循环与判断选出大于等于最小置信度的confidence_data依次将相应的key、confidence_data、Lift_data逐一append给data,再将每一个data逐一append给limit_data,最后返回的limit_data就是所有符合最小置信度阈值的事务集,共有三个column,依次为关联规则、置信度、提升度,这就是最终输出的结果。
为了便于编写代码,在输入数据时,物品名称用数字代替,这导致最终打印出的结果中,物品名称无法显示,不好理解。因此在confidence函数与lift函数中,对str1与str2进行了修改,用List_Dataset来进行编号与物品名称的转换。一开始没有注意到list下标与物品名称的标号相差了1,导致越界报错,在下标中又减去了1,就成功输出了结果。
根据最终得出的结果进行分析,可以得知“鸡蛋->面包”、“鸡蛋->麦片”、“麦片->面包”的置信度都为1.0,这说明购买了前者的顾客有极大概率购买后者。“麦片->鸡蛋”、“鸡蛋->麦片”的提升度高达2.25,这说明顾客常常捆绑购买这两件商品,在计划促销活动时可以优先考虑鸡蛋和麦片一同销售。
三、实验心得
通过第一题,我学会了如何将xlsx文件转为csv文件,利用split()函数对csv文件进行元素分割后提取需要的数据至dataframe,存为数据矩阵。随后通过循环定位到dataframe中的每一个list,再如何通过if语句来判断是否是自己需要的元素,进行数据预处理。此外,我还学会了dataframe的apply.(pd.to_numeric)、groupby()、mean()等函数,以及将dataframe数据转为csv文件需要用到的dataframe的to_csv()函数的用法。
第一题中学到的知识可以应用于一些基础的数据统计值分析,例如生活中的分析成绩、账单流水、人群喜好分析等,这些数据分析需要分组、求均值、排序这些功能,而这一题中学到的知识就和这些需求十分契合。至于将xlsx文件转为csv文件这一功能,先前用Navicate实现过,这次学会了用python来实验,拓宽了知识。
通过第二题,我实际地了解到apriori算法可以发现频繁项集,而其具体实现用到了createC1、scanD、aprioriGen、apriori这四个函数,其中apriori函数内包含了对前三个函数的调用。三个函数各司其职,createC1负责获取候选1项集,scanD负责找出候选集中的频繁项集,aprioriGen通过频繁项集生成候选项集。
在了解apriori算法的基础上,我还学会了用seaborn工具包中的heatmap函数实现了热力图的显示,以及将数据整理为所有2阶频繁项集及其支持度的列表集合DataList和相关的1阶频繁项集的集合Datanum,之后根据公式编写置信度计算函数confidence和提升度计算函数Lift,其中要注意的是,由于涉及list过多,因此key值需要多加思考,不然容易造成越界。最后编写limit函数来定义最小置信度阈值,k本身的意义就是2阶关联规则中频繁项集的关系,而confidence_data[k]和Lift_data[k]又分别代表了置信度与提升度,因此将这三个数据存入一个list作为一组数据,又接到limit_data的尾部,所以最后limit_data内存储的就是所有符合最小置信度阈值的关联规则。
第二题中学到的知识可以用于挖掘数据关联规则,找出频繁项集,因此在实际生活中更运用于商业,例如分析顾客会更加倾向于购买哪一种商品组合,如何分配商品能够使其更加热销,什么种类的商品配以什么样的广告会更吸引顾客购买,这些问题都可以通过apriori算法来解决。