pandas+PyQt5轻松制作数据处理工具
作者:才哥
由于在工作中需要处理很多日志文件数据,这些数据并不存在于数据库,而是以每日1个单文件的形式存在,为了让我们在日常数据处理中更方便的进行一些基础的数据合并、清洗筛选以及简单的分组或数据透视处理,结合PyQt5
与pandas
库,制作了一个简单的数据处理可视化工具。
执行效果
我们运行脚本打包后的 exe 可执行文件,设定相关参数后点击“数据处理并导出”即可等待处理~
以下是29文件共1400余万行数据的处理结果,差不多用了10分钟合并并处理导出所需结果~
1.窗体可视化设计
采用PyQt5进行可视化界面设计,具体设计过程可以直接在QT designer
中进行操作,然后转化为可视化界面的py文件。
界面效果如下图:
对于我们的操作界面,支持以下功能:
- 选择原始数据所在的文件夹
- 选择需要vlookup的文件所在的文件夹
- 选择处理后结果导出的文件夹
- 输入结果导出的文件名
- 在原始数据中用于过滤筛选的字段
- 在原始数据中用于过滤筛选的条件
- 如果做数据透视的行(index)
- 数据透视的列(column)
- 用于计算的字段
- 用于计算的方法
2.多文件合并
由于我们拿到的原始数据是以日期为文件名的csv文件,如果需要处理多天的数据,需要进行简单的数据合并后再做相关数据处理操作。
这一步其实有4个操作:①获取文件夹下的文件列表 ②根据文件类型进行文件读取 ③对读取的文件进行简单的数据清洗 ④合并清洗后的数据
2.1.获取文件夹下的文件列表
获取文件夹下文件列表可以使用os.walk
方法,产生3-元组 (dirpath, dirnames, filenames)【文件夹路径, 文件夹名字, 文件名】。
根据文件夹路径+文件名即可组成改文件的绝对路径,用于后续文件读取。
In [1]: import os
...:
...: location = r'F:数据处理工具测试数据'
...: filenames = os.walk(location)
...:
...: #获取文件夹下全部文件的绝对路径
...: for fileName in os.walk(location):
...: for table in fileName[2]:
...: path = fileName[0] + '\' + table
...: print(path)
F:数据处理工具测试数据1.csv
F:数据处理工具测试数据2.csv
F:数据处理工具测试数据3.csv
F:数据处理工具测试数据4.csv
F:数据处理工具测试数据5.csv
F:数据处理工具测试数据6.csv
F:数据处理工具测试数据7.csv
F:数据处理工具测试数据8.csv
F:数据处理工具测试数据9.csv
2.2.根据文件类型进行文件读取
由于在实际操作过程中,可能存在原始文件是csv压缩包zip格式,或者xlsx格式。我们需要根据文件名后缀进行判断,然后选择对应的读取文件数据方法。
采用os.path.splitext
(“文件路径”) 分离文件名与扩展名,默认返回(fname,fextension)
元组。
这里我们只考虑两种情况:csv(含zip)以及xlsx(含xls):
if filetype == '.csv'or filetype == '.zip':
Li = pd.read_csv(path, header=0)
elif filetype == '.xlsx' or filetype == '.xls':
Li = pd.read_excel(path, header=0)
else:
log = '<font color="#FF0000">不是支持的文件类型,该工具暂时只支持csv、xlsx和xls文件类型</font>'
print(log)
2.3.对读取的文件夹下简单的数据清洗
对于读取的文件数据,并不是所有的数据都是我们需要用到的,或者说我们需要用到的数据可能是需要满足指定条件的。
比如对于下面这个情况,读取 9.csv
文件后,我们看到 usernum
每个值出现的次数,然后我希望取满足uesrnum为10的数据。
In [2]: df = pd.read_csv(r'F:数据处理工具测试数据9.csv')
In [3]: df.groupby('usernum').count()
Out[3]:
@timestamp appid ... truedmgtohero victory
usernum ...
1 516999 516999 ... 516999 516999
2 33970 33970 ... 33970 33970
3 36819 36819 ... 36819 36819
4 6917 6917 ... 6917 6917
5 7855 7855 ... 7855 7855
6 15416 15416 ... 15416 15416
8 1220 1220 ... 1220 1220
10 75420 75420 ... 75420 75420
[8 rows x 71 columns]
我们用到布尔索引即可df[df['usernum']==10]
In [4]: df_10 = df[df['usernum']==10]
In [5]: df_10.groupby('usernum').count()
Out[5]:
@timestamp appid ... truedmgtohero victory
usernum ...
10 75420 75420 ... 75420 75420
[1 rows x 71 columns]
但是,因为我们的筛选字段及条件都是通过可视化操作界面进行输入的,输入的数据类型在程序中是字符串,所以我们需要将其处理成为可以用于条件筛选的形式。而且,我们在进行清洗的时候字段及条件可能是多个的。
比如我输入的字段为:usernum/victory
;输入的条件为:>=6/==1
。
那我们实际上需要进行的清洗过程是df = df[df['usernum']>=6]
和 df = df[df['victory']==1]
,为实现这个效果,可以用最简单的字符拼接的形式 s = f"Li['{checkli[0]}']{conditionli[0]}",然后进行eval(s)转化。
#获取输入的筛选字段(用‘/’分割),我们用'/'拆分为列表
checkli = self.lineEditcheck.text().split('/')
#获取输入的条件参数(用‘/’分割),我们用'/'拆分为列表
conditionli = self.lineEditcondition.text().split('/')
for inum in range(len(checkli)):
s = f"Li['{checkli[0]}']{conditionli[0]}"
Li = Li[eval(s)]
关于数据清洗处理,我们会在pandas学习笔记中进行详细介绍~
2.4.合并清洗后的数据
这一步就比较简单了,直接将需要合并的数据添加的列表中,然后concat合并即可。不过,需要做个简单的判断,如果原始只有1个文件,直接就取改文件即可;超过1个文件情况下,才需要执行合并操作。
#...读取并清洗数据...
fileList.append(Li)
if len(fileList)>1:
data_result = pd.concat(fileList, ignore_index=True)
else:
data_result = fileList[0]
3.多文件拼接(merge)
这个其实也比较简单,我们事先把需要用于横向拼接的文件放到指定目录后,读取文件列表逐一和第2节中的处理过的原始数据进行merge处理。
基于第2节中介绍过的文件夹下文件列表读取,这里只介绍merge处理。
还是一样的逻辑,先判断是否有需要merge的文件,然后再执行后续操作,我们需要用到左连接方式处理。
#...读取需要用于merge的文件组合成列表...
fileList.append(Li)
for i in range(0,len(fileList)):
data = pd.merge(data,fileList[i],how = 'left')
4.数据处理
数据处理中我们可以用到pivot_table方法或者数据透视分组统计groupby方法,具体根据自己的需求选择。
这一部分我们在后续 pandas学习笔记中也会详细介绍~
4.1.数据透视(pivot_table)
pandas.pivot_table(data, values=None, index=None, columns=None, aggfunc='mean', fill_value=None, margins=False, dropna=True, margins_name='All', observed=False)
以下举例,简单介绍下其使用方式
In [6]: df = pd.DataFrame({"A": ["foo", "foo", "foo", "foo", "foo",
...: "bar", "bar", "bar", "bar"],
...: "B": ["one", "one", "one", "two", "two",
...: "one", "one", "two", "two"],
...: "C": ["small", "large", "large", "small",
...: "small", "large", "small", "small","large"],
...: "D": [1, 2, 2, 3, 3, 4, 5, 6, 7],
...: "E": [2, 4, 5, 5, 6, 6, 8, 9, 9]})
In [7]: df
Out[7]:
A B C D E
0 foo one small 1 2
1 foo one large 2 4
2 foo one large 2 5
3 foo two small 3 5
4 foo two small 3 6
5 bar one large 4 6
6 bar one small 5 8
7 bar two small 6 9
8 bar two large 7 9
# values是需要用于计算的字段,index是索引,columns是列,aggfunc是统计方式
In [8]: table = pd.pivot_table(df, values='D', index=['A', 'B'],
...: columns=['C'], aggfunc='sum')
In [9]: table
Out[9]:
C large small
A B
bar one 4.0 5.0
two 7.0 6.0
foo one 4.0 1.0
two NaN 6.0
#如果aggfunc指定了多个统计方式,其会对计算的字段values中每个字段进行多个统计计算
In [10]: table = pd.pivot_table(df, values=['D', 'E'], index=['A', 'C'],
...: aggfunc={'mean','sum'})
In [11]: table
Out[11]:
D E
mean sum mean sum
A C
bar large 5.500000 11.0 7.500000 15.0
small 5.500000 11.0 8.500000 17.0
foo large 2.000000 4.0 4.500000 9.0
small 2.333333 7.0 4.333333 13.0
#我们可以通过给aggfunc传递字典的形式指定每个用于计算字段的统计方式,这也是我们本次需要用到的
In [12]: table = pd.pivot_table(df, values=['D', 'E'], index=['A', 'C'],
...: aggfunc={'D': 'mean','E': 'sum'})
In [12]: table
Out[12]:
D E
A C
bar large 5.500000 15
small 5.500000 17
foo large 2.000000 9
small 2.333333 13
4.2.分组统计(groupby)
DataFrame.groupby([]).agg(dict)
分组统计是pandas很大的模块,这里也不做过多的介绍,大家可以关注后续 pandas学习笔记系列。
简单举个例子:
In [13]: df
Out[13]:
A B C D E
0 foo one small 1 2
1 foo one large 2 4
2 foo one large 2 5
3 foo two small 3 5
4 foo two small 3 6
5 bar one large 4 6
6 bar one small 5 8
7 bar two small 6 9
8 bar two large 7 9
In [14]: df.groupby('A')['D'].mean()
Out[14]:
A
bar 5.5
foo 2.2
Name: D, dtype: float64
#agg传字段参数更合适,可以和pivot_table统一化
In [15]: df.groupby(['A']).agg({'D':'mean','E':'sum'})
Out[15]:
D E
A
bar 5.5 32
foo 2.2 22
4.3.数据处理函数
由于行列以及计算字段和方法都是在可视化操作界面输入,我们需要对获取参数后进行字符串有关处理,从而组合成为最终的计算方式。
#获取输入的行、列、计算字段和方法
hang = self.lineEditHang.text().split(',')
lie = self.lineEditLie.text().split(',') if len(self.lineEditLie.text())!=0 else []
ziduan = self.lineEditJisuan.text().split(',')
fangfa = self.lineEditJisF.text().split(',')
将计算字段和计算方法进行组合成为字典
dic = {}
for i in range(len(fangfa)):
#需要注意,这里对于非重复计数,其组合形式有点特别,不能用引号
if fangfa[i] == 'pd.Series.nunique':
dic[ziduan[i]] = eval(fangfa[i])
else:
dic[ziduan[i]] = fangfa[i]
判断在可视化操作界面是否选中了数据透视操作,然后执行数据处理
if self.radioButton_toushi.isChecked():
result = pd.pivot_table(df, values=ziduan,
aggfunc=dic,
columns=lie,
index=hang,
).reset_index()
else:
result = df.groupby(hang).agg(dic).reset_index()
5.总结
以上主要三部分:
- 先创建好可视化操作界面,
- 然后编写功能槽函数和可视化操作界面功能进行关联,
- 最后就是打包源代码成可执行文件exe。
在进行每一步的操作时,最好都能加上边界条件处理,避免出现异常报错导致程序崩溃的情况。
每个槽函数其实都是利用到的python基础知识或者pandas基础数据处理知识,熟练掌握后便可很方便理解和实现。
下载代码
在【凹凸数据】公众号回复“0908”,可以领取源代码
- 剑指OFFER之二维数组中的查找(九度OJ1384)
- 剑指OFFER之用两个栈实现队列(九度OJ1512)
- 剑指OFFER之用两个栈实现队列(九度OJ1512)
- 简单的客户机服务器投射模拟
- 使用gcc编译gdb调试
- 剑指OFFER之第一个只出现一次的字符(九度OJ1283)
- c++中类长度解析
- 剑指OFFER之丑数(九度OJ1214)
- 剑指OFFER之把数组排成最小的数(九度OJ1504)
- 剑指OFFER之从1到n中出现1的次数(九度OJ1373)
- 剑指OFFER之最大子向量和(连续子数组的最大和)(九度OJ1372)
- 剑指OFFER之最小的K个数(九度OJ1371)
- 剑指OFFER之数组中出现次数超过一半的数字(九度OJ1370)
- 如何成为一名10x的数据分析师?
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- NLP简报(Issue#9)
- 这些pandas技巧你还不会吗 | Pandas实用手册(PART II)
- 芝麻街跨界NLP,没有一个ERNIE是无辜的
- python实现多层感知机
- 数据分析必备!Pandas实用手册(PART III)
- python实现决策树
- NLP简报(Issue#10)
- Transformer温故知新
- 【作者解读】ERNIE-GEN : 原来你是这样的生成预训练框架!
- Python中那些低调有趣的模块
- 01背包问题讲解(动态规划)
- python实现随机森林
- 在mysql中order by是怎样工作的?
- 多线程应用 - 阻塞队列ArrayBlockingQueue详解
- Java虚拟机 - 超级详细的类加载说明