实战|用pandas+PyQt5制作一款数据分组透视处理工具
早起导读:pandas是Python数据处理的利器,如果每天都要使用pandas执行同样的操作,如何制作一个有界面的软件更高效的完成?本文提供了一种基于PyQt5的实现思路。
关键词:pandas PyQt5 数据透视 文件合并
前言
由于在工作中需要处理很多日志文件数据,这些数据并不存在于数据库,而是以每日1个单文件的形式存在,为了让我们在日常数据处理中更方便的进行一些基础的数据合并、清洗筛选以及简单的分组或数据透视处理,结合PyQt5
与pandas
库,制作了一个简单的数据处理可视化工具。
执行效果
我们运行脚本打包后的 exe 可执行文件,设定相关参数后点击“数据处理并导出”即可等待处理~
以下是29文件共1400余万行数据的处理结果,差不多用了10分钟合并并处理导出所需结果~
1.窗体可视化设计
采用PyQt5进行可视化界面设计,具体设计过程可以直接在QT designer
中进行操作,然后转化为可视化界面的py文件。
界面效果如下图:
对于我们的操作界面,支持以下功能:
选择原始数据所在的文件夹 选择需要vlookup的文件所在的文件夹 选择处理后结果导出的文件夹 输入结果导出的文件名 在原始数据中用于过滤筛选的字段 在原始数据中用于过滤筛选的条件 如果做数据透视的行(index) 数据透视的列(column) 用于计算的字段 用于计算的方法
2.多文件合并(concat)
由于我们拿到的原始数据是以日期为文件名的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 = '不是支持的文件类型,该工具暂时只支持csv、xlsx和xls文件类型'
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)
数据处理中我们可以用到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基础数据处理知识,熟练掌握后便可很方便理解和实现。
注:后台回复「0906」获取本文涉及的完整源码!
-END-