Python学习笔记(二)
前一篇请参考Python学习笔记(一)
数据分析
同过前一篇的内容,我们建立了Python环境,并且用Qt做了一个GUI界面。接下来,我们要用pandas这个库来实现对数据的分析。
先来说说我们要完成的功能:
1.数据是用Execl的方式来存储的,第一行是数据表头(用英文字母写的名称)
2.从做好的GUI界面上获取相关过滤条件,过滤掉不符合条件的数据
3.按照输出要求,从最后剩下的结果当中抽取出相应的结果,提供给Qt显示
数据表
pandas这个库很强大,可以通过第一行表头的名称来操作相关列,很方便。
pandas提供了Series,DataFrame这连个强大的数据操作类。我这里主要使用DataFrame。
关于pandas,下面这个不知哪个外国大神做的图,挺提纲挈领的。
使用pandas之前,需要像下面一样引用一下相关的库,这里要说明的是pandas使用xlread这个库来读execl所以除了安装pandas以外,还要手动安装xlread.
from pandas import Series,DataFrame
import pandas as pd
import numpy
然后像下面这样的一句话,就可以读入execl表格的内容了。是不是很方便。
self.DF = pd.DataFrame(pd.read_excel(execlFliePath))
or
self.DF = pd.DataFrame(pd.read_excel(execlFliePath,sheetName))
execlFilePath是execl文件的路径和文件名,如果不指定sheet名称,就打开默认active的sheet。
使用某一列的数据就可以用下面的方式
self.DF['Recruitment Time']
想要在DataFrame里面手动加一列可以通过下面这个方式
for payValue in range(4000,30000,500):
self.DF.loc[(self.DF.Pay <= payValue)&(self.DF.Pay > payValue-500),'PayLable']="%04.1fK"%(payValue/1000.0)
这块代码实现了,在DataFrame里面新加入PayLable这一列的功能,PayLable是根据Pay这列的值,按照500为单位,转换成相应的“10.5K”这样字符串。
DataFrame的loc(location定位)函数,第一个参数是行,第二个参数是列。
在上面的这个代码当中,
self.DF.loc[(self.DF.Pay <= payValue)&(self.DF.Pay > payValue-500),'PayLable']
表示Pay这列的值在(payValue-500,payValue]范围内的行,列为’PayLable’的这些Cell单元。
这里再啰嗦几句,DataFrame每一行都有相应的索引,这些索引是在建立DataFrame的时候就指定的。我们这里是直接读的Execl做成的DataFrame,索引就是1,2,3…
条件过滤
DataFrame的另一个强大的地方,是我们不用自己做数据类型的转换。
过滤时间条件
execl数据当中有一列为Recruitment Time,格式是2019/12/12这样的时间
指定时间范围的数据过滤如下
def FilterDataBy_RecruitmentDate(self,sDate,eDate):
self.DF = self.DF[(self.DF['Recruitment Time'] >= pd.Timestamp(sDate)) & (self.DF['Recruitment Time'] <= pd.Timestamp(eDate))]
其中,sDate,eDate是Python当中datetime的数据类型
下面是从Qt的时间控件当中取得时间值,转换成datetime数据类型的代码
import datetime
startDataStringList = self.ui.dateEdit_SearchStart.date().toString(Qt.ISODate).split('-')
endDataStringList = self.ui.dateEdit_SearchEnd.date().toString(Qt.ISODate).split('-')
startData = datetime.date(int(startDataStringList[0]),int(startDataStringList[1]),int(startDataStringList[2]))
endData = datetime.date(int(endDataStringList[0]), int(endDataStringList[1]), int(endDataStringList[2]))
过滤字符串
execl数据当中有一列为Score,格式是3,3.5这样的数值
但是我们从GUI画面上取到是’3’,'3.5’这样的字符串。
但是我们不用自己做相应的转换,直接用下面这样的方法来过滤掉不是’3.5’的相关行
def FilterDataBy_Score(self,scoreStr):
self.DF = self.DF[self.DF['Score'].isin([scoreStr])]
过滤数字
execl数据当中有一列为Experience,格式是6,7,8这样的数字,
我们从GUI获得的是开始数字expStartValue和结束数字expEndValue
def FilterDataBy_Experience(self,expStartValue,expEndValue):
self.DF = self.DF[(self.DF['Experience']>=expStartValue) &(self.DF['Experience']<=expEndValue)]
通过以上方式,就可以对输入的数据进行相应的过滤处理,而且实现起来也很方便快捷。
数据整理
通过上面的过滤,我们得到了想要的数据行。但是还不是我们想要得到的数据样式。
对数据进行排序
截取DataFrame当中的若干列,并且按照其中某一列进行排序
def GetData_NameRankPayList(self):
NameRankPayList = self.DF[['Rank','Name','Pay']].sort_values(by='Rank', axis=0, ascending=True)
return NameRankPayList.values.tolist()
上面这段代码实现了截取Rank,Name,Pay这三列数据,并且按照Rank这列安装从小到大的顺序排列。
要说明的是最后那个Return。DataFrame虽然强大,但是其他库并不认识这个数据结构,这就要我们把DataFrame转换成标准的Python格式。NameRankPayList.values.tolist()的结果长成如下的样子[[‘rank1’,‘name1’,pay1],[‘rank2’,‘name2’,pay2],…]。不是所有的值都是字符串的形式。
统计重复个数
下面这段代码会统计Rank这列当中所有重复的项,并且返回一个不重复的Rank列表和每个Rank下相应人数的列表。
def GetData_Rank2NumOfPeople(self):
# sort it
Rank2NumOfPeople = self.DF[['Rank','Name']].sort_values(by='Rank', axis=0, ascending=True)
Rank2NumOfPeople = Rank2NumOfPeople.groupby(by='Rank').count()
return (Rank2NumOfPeople.index.tolist(),Rank2NumOfPeople['Name'].values.tolist())
要说明的是Rank2NumOfPeople.groupby(by=‘Rank’)之后的结构,并不是DataFrame的。执行完groupby.count之后会形成以Rank为Index的一张新表格。
这里想和大家说的是,如果我们不是特别清楚每一步作为的数据是什么样子,可以利用print函数,把值打印出来。这样有助于我们理解相应的结构
来个复杂一点的
PayLable,Score,Name三列数据,统计每Pay范围下每个得分的人数
并形成以Rank为行,以Score为列,交叉点为人数的新表格
原表格
PayLable | Score | Name |
---|---|---|
06.5K | 3.0 | 小王 |
11.5K | 4.5 | 小黄 |
10.5K | 3.5 | 小张 |
11.5K | 4.5 | 小李 |
转换后的表格
PayLable | 3.0 | 3.5 | 4.5 |
---|---|---|---|
06.5K | 1 | 0 | 0 |
10.5K | 0 | 1 | 0 |
11.5K | 0 | 2 | 0 |
实现的代码如下
def GetData_Score2Pay2NumOfPeope(self):
# Remove duplicated Paylabe and sort it
PayLableDF = self.DF.drop_duplicates(subset=['PayLable'], keep='first')[['PayLable']]
PayLableDF = PayLableDF.sort_values(by='PayLable', axis=0, ascending=True)
# Get Score List
ScoreDF = self.DF.drop_duplicates(subset=['Score'], keep='first')[['Score']]
ScoreDF = ScoreDF.sort_values(by='Score', axis=0, ascending=True)
# Create the new dataframe with new columns
columnsList = ['PayLable']+ ScoreDF['Score'].values.tolist()
Score2Pay2NumOfPeope = PayLableDF.reindex(columns=columnsList,fill_value=0)
# Get the dataframe of num of poeple by 'PayLable', 'Score',
PayScoreNumbertable = self.DF[['PayLable','Score','Name']].groupby(by=['PayLable', 'Score']).count()
# Set the num of people of ('PayLable', 'Score') to the dataframe
for index,data in PayScoreNumbertable.iterrows():
Score2Pay2NumOfPeope.loc[Score2Pay2NumOfPeope.PayLable == index[0],index[1]]=PayScoreNumbertable.xs(index).values[0]
return Score2Pay2NumOfPeope
咱们一点点看
- 首先,我们要获得新表格当中所有行的(也就是PayLable当中不重复的项,并且排序)
self.DF.drop_duplicates(subset=['PayLable'], keep='first')
这个是将原来DF表格当中PayLable这列当中重复的行去掉,保留第一个非重复的项。所以这个函数返回的也是一个DataFrame并且列的名称不变。因此他后面的[[‘PayLable’]]就是将这个不重复的列单独取出来。
之后在通过下面的处理进行排序
PayLableDF = PayLableDF .sort_values(by='PayLable', axis=0, ascending=True)
- 然后,我们要获取新表格当中所有列的名称(也就是Score当中不重复的项,并且排序)
这个处理和上面的一样,不再重复说明
ScoreDF = self.DF.drop_duplicates(subset=['Score'], keep='first')[['Score']]
ScoreDF = ScoreDF.sort_values(by='Score', axis=0, ascending=True)
- 之后,我们要新建这个输出用的新表格
做出新表格所有列的名称
columnsList = ['PayLable']+ ScoreDF['Score'].values.tolist()
这里要说明的是这个columnsList 的结果最终长成这个样子[‘PayLable’,3.0,3.5,4.5],也就是说列的名称其实也可以是非字符串的形式
做成新表格,表格里面的值默认成0
Score2Pay2NumOfPeope = PayLableDF.reindex(columns=columnsList,fill_value=0)
这步作为,表格就变成下面这样
PayLable | 3.0 | 3.5 | 4.5 |
---|---|---|---|
06.5K | 0 | 0 | 0 |
10.5K | 0 | 0 | 0 |
11.5K | 0 | 0 | 0 |
- 表格做好了之后,我们就要开始统计表格当中相同PayLable和Score的个数了
统计方法和之前一样,还是利用Groupby的count函数,只不过这里我们用了两个条件去group.
PayScoreNumbertable = self.DF[['PayLable','Score','Name']].groupby(by=['PayLable', 'Score']).count()
这里要说明一下,PayScoreNumbertable是一个双Index的DataFrame,如下图(忽略数据和上面的不一致的事情)
其实和我们想要的表格很相近了,但是如何直接转换成我们想要的样子而不用像上面一样,先把表格做出来,我没有研究明白,哪位大神有经验麻烦给些Tips
- 最后,把统计好的数据填写的上面建好的表格当中
for index,data in PayScoreNumbertable.iterrows():
Score2Pay2NumOfPeope.loc[Score2Pay2NumOfPeope.PayLable == index[0],index[1]]=PayScoreNumbertable.xs(index).values[0]
这里要说的是index,获得的是每一行的所有Index,所以index[0]就是PayLable,index[1]就是Score。
loc函数我们上面提过了,是用来定位DataFrame的Cell的。
xs(index)函数可以对复数Index来定位相应的行。