系列文章目录
上一篇:【Python Onramp】1. 熟悉Python基本语法、数据结构和方法:2018年中国机场数据处理
下一篇:【Python Onramp】3. Python的文本分析(1)jieba分词:第三方库和基本面向对象编程
本文目录
项目描述
Python杀死Excel的第一步!!(x
github仓库见https://github.com/Honour-Van/CS50/tree/master/Statistics
这个练习中,我们将对一些反汇编指令进行统计分析,以及初步的可视化展示
项目中你将:
- 使用python的统计三件套中的pandas和matplotlib,完成统计分析
- 联合Excel进行对比,进一步实践因地制宜的思想,并初步反思程序设计思维。
文件asmData.txt 是3 个知名软件反汇编数据(基于Mac 平台,芯片非Mac M1),可以理解为是这些软件最终执行的计算机指令(机器指令是二进制编码,汇编语言与之一一对应并用助记符命名,以便于使用)。高级语言(C/C++、Python、Java 等)都要翻译成这种指令的集合。
项目中的数据有多列,列与列之间用\t【Tab】间隔。第1-3 列数据可以忽略【第2、3 列数据也其实很重要】,第4 列是指令名称,其后两列是指令的参数【可以简单地将指令名称理解为函数,其后为其参数】。 特别提示:如数据不符合下图规范,可省略该行数据,不影响总体结论。
任务的可视化示例
Task 1
统计每种指令使用频次,即统计每种指令出现次数,并放入到名为Instruction.xlsx
的Excel 文件中一个名为instructionCount
的sheet 中。第一列为Instruction,第二列为Count。
Task 2
统计每种指令的类别,即无参指令,单参指令,双参指令,多参指令。无参指令指指令后没有其他参数,单参指令指仅有一个参数,以此类推。将数据放到名为Instruction.xlsx
的Excel 文件中一个名为instructionType
的sheet 中。
Task 3
在Instruction.xlsx
文件中增加一个名为Summary 的sheet,有三列数据,第一列是指令名称(Instruction)、频次(Amount)、类别(Type)【每个指令的类别,可能用到vlookup
函数】。按频次从高低排序。
Task 4
在Instruction.xlsx
文件中形成柱状图,并放入到一个名为Chart 的sheet 中。可能要用到数据透视表,计算出每种类别的指令数量。
语法总览
语法点1:词典dict的使用
https://www.runoob.com/python3/python3-dictionary.html
https://www.liaoxuefeng.com/wiki/1016959663602400/1017104324028448
dict是一种键值对的数据结构,比较类似于C++ STL中的map。
在python中,dict使用大括号,list使用中括号,tuple使用小括号。
dict添加键值对非常简单:dict[key]=value
。
if item in dict判断的是键dict.keys()
中是否有item
语法点2:词频统计
https://blog.csdn.net/as604049322/article/details/112486090
由于词频统计是一个累加过程,所以每次找到一个新词都要调取这个词原有的信息。但如果某个键不存在就会报错,所以需要加一个条件判断:
if k in dict:
dict[k] = dict[k] + 1
else:
dict[k] = 0
这样写可以说是非常的impythonic了。
我们可以利用python的get函数进行简化,get可以设定默认值,从而解决了“查无此人”的报错。
dict[k] = dict.get(k, 0) + + 1
语法点3:pandas dataframe的使用
pandas官方网站:https://www.pypandas.cn/
一个简单实用的简易教程:https://www.runoob.com/pandas/pandas-csv-file.html
pandas中文教程:https://www.w3cschool.cn/hyspo/
可以将pandas理解成一个用python直接控制的高性能、大数据的excel工具。
dataframe就是一张工作表。
这里我们注意几个点:
- pandas的基础数据类型是series和dataframe:https://www.w3cschool.cn/hyspo/hyspo-5t1d3725.html
- dataframe可以通过很多序列型变量初始化而来:https://www.jianshu.com/p/b2dcb66595fe
- dataframe的添加:append添加行,insert添加列
- 数字索引iloc,行/列标索引loc。先行后列,注意用方括号!!!!!
- 导出到csv和xlsx都是不错的选择。
语法点4:同excel的协同
to_excel方法可以将dataframe导出到excel表格,但如果想要完成项目中需要的多个工作簿,就需要使用Excelwriter:
https://blog.csdn.net/weixin_43060843/article/details/100766677
https://www.cnblogs.com/xiaoli0520/p/13926544.html
示例:
# excelwriter对象生成,用于操作excel表格
writer = pd.ExcelWriter('Instruction.xlsx', engine='xlsxwriter')
df1 = pd.DataFrame(res1, columns=['Instruction', 'Count'])
df1.to_excel(writer, 'instructionCount', index=False)
语法点5:matplotlib初步
matplotlib是一个python版的matlab式绘图工具。接口和使用方法等都和MATLAB具有高度的相似性。
https://www.runoob.com/w3cnote/matplotlib-tutorial.html
项目中有几个要点需要特别注意:
中文显示
plt.rcParams['font.sans-serif'] = ['KaiTi'] # 指定默认字体
plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题
子图:
fig = plt.figure()
ax1 = fig.add_subplot(2, 1, 1) # 添加子图
ax1.set_title('指令种类数') # 设置子图名称
plt.bar(df4.iloc[:, 0], df4.iloc[:, 1]) # 绘图
显示:show和savefig
# plt.show() # 如果这一句显示,则图片为空白
plt.savefig('./foo.png')
这里直接给出示例代码:
# 绘图
fig = plt.figure()
ax1 = fig.add_subplot(2, 1, 1) # 添加子图
ax1.set_title('指令种类数') # 设置子图名称
plt.bar(df4.iloc[:, 0], df4.iloc[:, 1]) # 绘图
ax2 = fig.add_subplot(2, 1, 2)
ax2.set_title('指令总数')
plt.bar(df4.iloc[:, 0], df4.iloc[:, 2])
plt.title('指令数统计')
plt.tight_layout() # 调整图片元素分布
# plt.show() # 如果这一句显示,则图片为空白
plt.savefig('./foo.png')
具体实现
完整代码见https://github.com/Honour-Van/CS50/blob/master/Statistics/asmdata.py
task1 统计每种指令使用频次
这就是一个典型的词频统计,还不涉及分词,就非常简单了。
关于分行读取文件,我们在上一篇【Python Onramp】1. 熟悉Python基本语法、数据结构和方法:2018年中国机场数据处理中已经讲解过了,词频统计的方法也在这一篇中讲到,代码如下:
inst_dict = {} # 统计指令数
with open('./asmData.txt', 'r', encoding='utf-8') as f:
for line in f.readlines():
content = line.split() # 默认会将tab等空字符分开
if len(content) <= 3:
continue
inst_dict[content[3]] = inst_dict.get(content[3], 0) + 1
注意排除较短的语句。在描述中给出的例图里,即便是无参语句,也应当包括四段,
利用len小于3判断即可。
词典的排序可以对其items成员进行
res1 = sorted(inst_dict.items(), key=lambda x: x[1], reverse=True) # 按指令数量排序
基于lambda函数的排序在上一篇已经使用过,也可以参考这一篇https://www.cnblogs.com/zle1992/p/6271105.html
随后读入
# 生成dataframe对象便于操作、写入
df1 = pd.DataFrame(res1, columns=['Instruction', 'Count'])
df1.to_excel(writer,
'instructionCount', index=False)
task2 指令分类
我们从例图中可以发现,无参指令有4节,直到多参指令至少有7节,故我们先构建如下的映射关系:
param_alia = {4: '无参指令', 5: '单参指令', 6: '双参指令', 7: '多参指令'}
这个任务的一大难点在于,对于一个指令来说,其参数的数量并不完全一致,我们规定其分类按照其使用的最多参数数量计,因此分类过程还伴随着之前分类结果的调整,如下:
type_dict[content[3]] = max([type_dict.get(content[3], 0), l])
总体结构,仍然是故技重施:
type_dict = {} # 用于统计某个指令的最多参数情况,保持更新使得获得最大参数数量,以求准确
with open('asmData.txt', 'r', encoding='utf-8') as f:
for line in f.readlines():
content = line.split()
随后是几个非法判断:
- 某些指令尾部会添加return,要去掉
- 长度小于3的需要排除在统计结果之外
- 大于7的仍然认为是多参
这是简单的条件判断语句:
if content[-1] == 'return;':
content = content[:-1]
l = len(content)
if l <= 3:
continue
elif l > 7:
l = 7
分类部分的代码汇总如下:
param_alia = {4: '无参指令', 5: '单参指令', 6: '双参指令', 7: '多参指令'}
type_dict = {} # 用于统计某个指令的最多参数情况,保持更新使得获得最大参数数量,以求准确
with open('asmData.txt', 'r', encoding='utf-8') as f:
for line in f.readlines():
content = line.split()
if content[-1] == 'return;':
content = content[:-1]
l = len(content)
if l <= 3:
continue
elif l > 7:
l = 7
type_dict[content[3]] = max([type_dict.get(content[3], 0), l])
随后我们将上述的统计结果按照1中的顺序生成一个指令种类统计表,先用list保存:
res2 = []
for item in res1:
res2.append([item[0], param_alia[type_dict[item[0]]]])
这里使用了相对pythonic的写法,每次添加一个list,同时为了展示结果的可读性,嵌套了几重查找表,如果初学,在编写的时候也可以多用几个临时变量。
随后输出到表格:
df2 = pd.DataFrame(res2, columns=['Instruction', 'Type'])
df2.to_excel(writer, 'instructionType', index=False)
task3 任务汇总
由于之前的统计结果都保存在临时变量的dict中,所以我们直接使用dataframe的插入一列操作即可。
对于这种实用性的代码,多用一点空间复杂度,似乎利好编写的简易度。
df3 = df2
# 查找式汇总
df3.insert(1, 'Amount', [inst_dict[x] for x in df2.iloc[:, 0]])
df3.to_excel(writer, 'Summary', index=False)
task4 分类统计
任务2中我们按照参数的多少进行了分类,即无参、单参、双参、多参。
关于这个题目有两种理解方法,即每个类下有多少种指令,和每个类之下有多少个指令。
task1中,我们分指令进行数量统计,task2中我们对指令进行了分类标记,其实这两个任务实质上相同,都是词频统计任务。
不同于常见的词频统计的是,
- 多少种指令:需要对已有的标记进行名称的同义转换等工作。
- 多少个指令:每次叠加的不是1,而是对应的统计数
param_dict = {} # 统计每个类之下有多少个指令
for item in res2:
param_dict[item[1]] = param_dict.get(item[1], 0)
+ inst_dict[item[0]] # task1中找到的指令出现的次数
res4 = sorted(param_dict.items(), key=lambda x: x[1], reverse=True)
df4 = pd.DataFrame(res4, columns=['Type', 'Amount'])
inst_dict4 = {} # 每个类之下有多少种指令
for item in type_dict.keys():
tp = param_alia[type_dict[item]]
inst_dict4[tp] = inst_dict4.get(tp, 0) + 1
df4.insert(1, 'Numbers', [inst_dict4[x] for x in df4.iloc[:, 0]])
Type Numbers Amount
0 双参指令 93 38514
1 单参指令 48 12981
2 多参指令 9 8016
3 无参指令 14 1703
绘图
完整代码
'''
@author: Honour Van, Depart.EE of PKU EECS
@description: 用于统计汇编指令,并使用pandas+plt进行简单统计分析
'''
import pandas as pd
import matplotlib.pyplot as plt
# excelwriter对象生成,用于操作excel表格
writer = pd.ExcelWriter('Instruction.xlsx', engine='xlsxwriter')
# --------任务1:统计指令------------
inst_dict = {} # 统计指令数
with open('./asmData.txt', 'r', encoding='utf-8') as f:
for line in f.readlines():
content = line.split()
if len(content) <= 3:
continue
inst_dict[content[3]] = inst_dict.get(content[3], 0) + 1
res1 = sorted(inst_dict.items(), key=lambda x: x[1], reverse=True) # 按指令数量排序
# 生成dataframe对象便于操作、写入
df1 = pd.DataFrame(res1, columns=['Instruction', 'Count'])
df1.to_excel(writer,
'instructionCount', index=False)
# --------任务2:为任务1添加分类标签------------
param_alia = {4: '无参指令', 5: '单参指令', 6: '双参指令', 7: '多参指令'}
type_dict = {} # 用于统计某个指令的最多参数情况,保持更新使得获得最大参数数量,以求准确
with open('asmData.txt', 'r', encoding='utf-8') as f:
for line in f.readlines():
content = line.split()
if content[-1] == 'return;':
content = content[:-1]
l = len(content)
if l <= 3:
continue
elif l > 7:
l = 7
type_dict[content[3]] = max([type_dict.get(content[3], 0), l])
# 按照1中的顺序生成一个指令种类统计
res2 = []
for item in res1:
res2.append([item[0], param_alia[type_dict[item[0]]]])
df2 = pd.DataFrame(res2, columns=['Instruction', 'Type'])
df2.to_excel(writer,
'instructionType', index=False)
# --------任务3:汇总1和2------------
df3 = df2
# 汇总
df3.insert(1, 'Amount', [inst_dict[x] for x in df2.iloc[:, 0]])
df3.to_excel(writer, 'Summary', index=False)
# -----------任务4:按类统计------------------
param_dict = {} # 统计每个类之下有多少个指令
for item in res2:
param_dict[item[1]] = param_dict.get(item[1], 0) + inst_dict[item[0]]
res4 = sorted(param_dict.items(), key=lambda x: x[1], reverse=True)
df4 = pd.DataFrame(res4, columns=['Type', 'Amount'])
inst_dict4 = {} # 每个类之下有多少种指令
for item in type_dict.keys():
tp = param_alia[type_dict[item]]
inst_dict4[tp] = inst_dict4.get(tp, 0) + 1
df4.insert(1, 'Numbers', [inst_dict4[x] for x in df4.iloc[:, 0]])
print(df4)
# 准备绘图
plt.rcParams['font.sans-serif'] = ['KaiTi'] # 指定默认字体
plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题
# 绘图
fig = plt.figure()
ax1 = fig.add_subplot(2, 1, 1) # 添加子图
ax1.set_title('指令种类数') # 设置子图名称
plt.bar(df4.iloc[:, 0], df4.iloc[:, 1]) # 绘图
ax2 = fig.add_subplot(2, 1, 2)
ax2.set_title('指令总数')
plt.bar(df4.iloc[:, 0], df4.iloc[:, 2])
plt.title('指令数统计')
plt.tight_layout() # 调整图片元素分布
# plt.show() # 如果这一句显示,则图片为空白
plt.savefig('./foo.png')
writer.book.add_worksheet('Chart').insert_image(0, 0, './foo.png') # 保存图片
# -----------保存退出------------------
writer.save() # 写入excel表格
总结
一个思维:重复任务尽量用计算机来解决。在上一节中可能显得不那么迫切,但我相信这一节不是可以人工完成的
语法要点:
- 词典dict的使用和词频统计
- pandas和dataframe的基本使用
- pd.ExcelWriter以及excel软件协同,matplotlib入门
补充:“是的,Python杀死了Excel”
这是CSDN系自媒体常用话术。
但我个人非常不喜欢使用Matplotlib,画的图并不好看而且还很费事,日常实用场景中,只有matplotlib的python在我眼里是杀不死excel的。这是一个因地制宜的情景。
上面的任务完全可以用Excel来替代,按住ctrl,鼠标点两下就出一个表格的事,用写代码来解决有点太过刻意了。
你要是说这不太自动化,我觉得也大可不必用matplotlib,pyecharts明明是更好看更好用的一类可视化工具。
pyecharts后面一定会讲到,这是我非常喜欢的可视化框架。重新整理这个系列的重要原因就是想做一套适合自己的pyecharts模板。可交互的html页给可视化带来无穷可能性。
话说回来,Excel这样的数据处理工具大概率比Python更好用。这是一个工程哲学的问题,之前有人帮你砸过的人力财力,应当不会白砸。