数据预处理是对数据中的特殊数据进行处理,包括重复数据、缺失值和异常值的处理。进行统计分析时,常要求数据满足一定的要求,不满足时对数据进行转换处理,这也是数据预处理的内容。[大谦Excel,dqexcel点com]
数据去重
由于各种原因,数据中可能会出现重复数据。可以使用Excel函数、字典、Power Query和Python等多种方法删除重复数据。
一、使用Excel函数去重
1.4.8小节介绍了使用COUNTIF函数找到数据中的重复行,然后用Range对象的Delete方法删除重复行。请参阅。
二、使用字典去重
字典中的键在整个字典中必须是唯一的。利用字典的这个性质,可以对数据去重。图9-1中为各部门人员的身份证号信息。观察发现,工作表中工号1002和1008的人员信息有重复,现用Excel VBA和Python xlwings使用字典进行去重处理。
图9-1 根据工号对数据去重
【Excel VBA】
Excel VBA中使用字典,需要首先引用相关的库,请参见第8章的介绍。创建字典时,字典中键值对的键由第1列的工号组成,值由它对应的其他各列的数据组成,这样构造4个字典。因为字典中的键是唯一的,所以字典构造完成以后,字典中的键和键对应的这组数据是唯一的,达到了去重的目的。示例文件的存放路径为Samples\ch21\Excel VBA\身份证号-去重.xlsm。
Sub 去重()
Dim intI As Integer
Dim arr
Dim dicT1 As New Dictionary
Dim dicT2 As New Dictionary
Dim dicT3 As New Dictionary
Dim dicT4 As New Dictionary
'获取数据
arr = Range("A1", Cells(Rows.Count, "E").End(xlUp))
For intI = 1 To UBound(arr) '构造字典,去重
dicT1(arr(intI, 1)) = arr(intI, 2)
dicT2(arr(intI, 1)) = arr(intI, 3)
dicT3(arr(intI, 1)) = arr(intI, 4)
dicT4(arr(intI, 1)) = arr(intI, 5)
Next
'输出去重后的数据
[G1].Resize(dicT1.Count) = Application.Transpose(dicT1.Keys) '工号
[H1].Resize(dicT1.Count) = Application.Transpose(dicT1.Items) '部门
[I1].Resize(dicT1.Count) = Application.Transpose(dicT2.Items) '姓名
[J1].Resize(dicT1.Count) = Application.Transpose(dicT3.Items) '身份证号
[K1].Resize(dicT1.Count) = Application.Transpose(dicT4.Items) '性别
End Sub
运行过程,在工作表的G-K列输出去重后的数据。
图9-2 去重后的结果
【Python xlwings】
对图9-1中工作表的数据进行去重处理。创建字典,字典中的键值对,键由第1列的工号组成,值由它对应的行数据组成。使用字典对象的keys方法可以获取当前所有的键。添加键值对时如果键已经存在,则不添加,否则添加。这样,最后得到的所有键值对的值就是去重后的数据。脚本文件的存放路径为Samples\ch21\Python\身份证号-去重.py。
import xlwings as xw
import os
root = os.getcwd()
app = xw.App(visible=True, add_book=False)
wb=app.books.open(root+r'/身份证号-去重.xlsx',read_only=False)
sht=wb.sheets(1)
rng=sht.range('A1', sht.cells(sht.cells(1,'B').end('down').row, 'E'))
dd={} #创建字典dd
for i in range(rng.rows.count): #遍历行数据
if sht[i,0].value not in dd.keys(): #如果dd的键中不包括该行工号
dd[sht[i,0].value]=rng.rows(i+1).value #添加行数据到字典的值
lst=list(dd.values()) #字典的值转成列表
sht.range('G1').options(expand='table').value=lst #列表数据写入工作表
运行脚本,去重后的数据如图9-2所示。
三、使用Power Query和pandas去重
数据量比较大的时候用Power Query或pandas进行处理(处理中小数据也可以)。此处介绍使用pandas DataFrame对象的drop_duplicates方法给数据去重。
下面的Python脚本文件用pandas打开当前路径下的Excel文件“身份证号-去重.xlsx”。使用pandas包的read_excel方法导入该文件中的数据,然后用DataFrame对象的drop_duplicates方法删除重复数据,用keep参数指定保留重复数据中的第1条数据,设置ignore_index参数的值为True,重排行索引编号。脚本文件的存放路径为Samples\ch21\Python\身份证号-去重2.py。
import pandas as pd
import os
root = os.getcwd()
df=pd.read_excel(io=root+r'\身份证号-去重.xlsx',engine='openpyxl')
df2=df.drop_duplicates(subset=['工号'], keep='first', ignore_index=True)
print(df2)
运行脚本,在Python Shell窗口输出查询结果。
>>> = RESTART: …\基础篇\Samples\ch21\Python\身份证号-去重2.py
工号 部门 姓名 身份证号 性别
0 1001 财务部 陈东 5103211978100300** 男
1 1002 财务部 田菊 4128231980052512** 女
2 1008 财务部 夏东 1328011947050583** 男
3 1003 生产部 王伟 4302251980031135** 男
4 1004 生产部 韦龙 4302251985111635** 男
5 1005 销售部 刘洋 4302251980081235** 男
6 1006 生产部 吕川 3203251970010171** 男
7 1007 销售部 杨莉 4201171973021753** 女
得到去重后的结果。默认时生成新的DataFrame对象,设置inplace参数的值为True,不生成新对象,直接修改原数据df。
缺失值处理
数据采集过程中,由于条件受限无法采集到数据,或者采集到的数据遗失了,出现了数据缺失的情况,这就是缺失值。缺失值不是0,而是这个位置没有数据,是空的。数据中存在缺失值,会导致数据处理方法无法进行,所以必须先对缺失值进行处理,要么删除,要么用指定的值进行填充。
【Excel VBA】
1.5.7小节讲到,使用单元格区域对象的SpecialCells方法可以引用区域中的特殊单元格,这其中就包括为空的单元格。空白单元格的引用效果如图1-17所示。这是发现数据中缺失值的一种方式。
使用该方法,还可以将空白单元格用指定的值进行填充,比如指定为数据的均值或中值等。比如下面单元格区域对象的SpecialCells方法找到工作表已用区域中的空单元格,将它们的值指定为10。示例文件的存放路径为Samples\ch21\Excel VBA\缺失值.xlsm。
Sub MissingValues()
Dim sht As Worksheet, rngN As Range
Set sht = ActiveSheet
'找到空单元格
Set rngN = sht.UsedRange.SpecialCells(xlCellTypeBlanks)
If Not rngN Is Nothing Then
rngN.value = 10 '指定空单元格的值为10
End If
End Sub
运行过程,生成图9-3。对比图1-17可以发现,原来为空的单元格现在都填充了数据10。
图9-3 用固定值填充空单元格
如果想将空单元格的值指定为它周围某个单元格的值,需要通过循环结构来实现。判断单元格的值是否等于""可以判断该单元格是否为空。
如果希望删除有区域中有空单元格的行或列,使用下面的代码。示例文件的存放路径为Samples\ch21\Excel VBA\缺失值.xlsm。
Sub MissingValues2()
'删除有空单元格的行
Dim sht As Worksheet, rngN As Range
Set sht = ActiveSheet
'找到空单元格
Set rngN = sht.UsedRange.SpecialCells(xlCellTypeBlanks)
If Not rngN Is Nothing Then '如果是空单元格
rngN.EntireRow.Delete '删除该行
'rngN.EntireColumn.Delete '删除该列
End If
End Sub
【Python xlwings】
编写脚本,用Python xlwings将指定区域内的空单元格用数据10进行填充。脚本文件的存放路径为Samples\ch21\Python\缺失值.py。
import xlwings as xw
import os
root = os.getcwd()
app = xw.App(visible=True, add_book=False)
wb=app.books.open(root+r'/缺失值.xlsx',read_only=False)
sht=wb.sheets(1) #获取工作表
#获取空单元格
rng=sht.api.Range('A1').CurrentRegion.\
SpecialCells(xw.constants.CellType.xlCellTypeBlanks)
if not rng is None:
rng.Value=10 #用10填充
运行脚本,生成图9-3。
如果删除含空单元格的行,使用下面的语句行:
#获取空单元格
rng=sht.api.Range('A1').CurrentRegion.\
SpecialCells(xw.constants.CellType.xlCellTypeBlanks)
if not rng is None:
rng.EntireRow.Delete() #删除包含空单元格的行
#rng.EntireColumn.Delete() #删除包含空单元格的列
【Python pandas】
pandas的DataFrame对象提供了一些方法查找和处理数据中的缺失值。用isnull方法查看是否有缺失值,用dropna方法删除缺失值所在的行或列,用fillna方法对缺失值进行填充。脚本文件的存放路径为Samples\ch21\Python\缺失值2.py。含有缺失值的数据如图9-4所示。
图9-4 有缺失值的数据
用DataFrame对象的isnull方法查找缺失值。
import pandas as pd
import os
root = os.getcwd()
df=pd.read_excel(io=root+r'\缺失值2.xlsx',engine='openpyxl')
df2=df.isnull()
print(df2)
运行脚本,在Python Shell窗口输出结果:
>>> = RESTART: …\基础篇\Samples\ch21\Python\缺失值2.py
A B C D
0 False False False False
1 False False True False
2 False True False False
3 False False False False
4 False False False True
5 False False False False
6 True False False False
7 False False True False
8 False False False True
9 False True False False
10 False False False False
上面结果中,缺失值对应的值是True,非缺失值对应的是False。
用DataFrame对象的dropna方法删除含有缺失值的行。
df3=df.dropna(how='any')
how参数的值为'any',表示只要行中有一个缺失值,就删除整行。
用DataFrame对象的fillna方法填充缺失值。下面的语句用10填充所有缺失值。
df4=df.fillna(10)
下面的语句用每一列的均值填充该列的缺失值。
df5=df.fillna({'A':df['A'].mean(),'B':df['B'].mean(),\
'C':df['C'].mean(),'D':df['D'].mean()})
下面的语句用缺失值下方的值填充缺失值。
df6=df.fillna(method='backfill')
异常值处理
异常值是由于某种原因造成的数据中出现的统计上过大或过小的值,将它们纳入数据分析,会影响分析结果。判断一个值是否异常值,有各种不同的算法。这里介绍比较常用的两种。
第1种方法是使用数据的均值和标准差进行判断,如果数据落在[均值-3*标准差, 均值+3*标准差]范围外,认为数据是异常值,否则不是。第2种方法使用分位数进行判断。0.75分位数减去0.25分位数得到数据的内四分极值,如果数据落在[0.25分位数-1.5*内四分极值, 0.75分位数+1.5*内四分极值]范围外,认为数据是异常值,否则不是。后一种方法也是用箱形图判断异常值的方法。
对于判断为异常值的数据,常常将它作为缺失值进行处理,删除或指定为特殊的值。
下面介绍用Excel函数、Excel VBA, Python xlwings和Python pandas进行异常值查找和处理的方法。对于图9-5工作表中第1列的数据,查找异常值并进行处理。
【Excel】
示例文件的存放路径为Samples\ch21\Excel函数\异常值.xlsx。
图9-5 用Excel函数和箱形图查找异常值
使用第1种方法,即用数据的均值和标准差进行查找,在B1单元格输入下面的公式:
=OR($A1<AVERAGE($A$1:$A$14)-3*STDEV($A$1:$A$14),$A1>AVERAGE($A$1:$A$14)+3*STDEV($A$1:$A$14))
其中,AVERAGE函数计算数据的均值,STDEV函数计算数据的标准差。回车,单元格中显示结果为FALSE,说明A1单元格中的数据不是异常值。双击单元格右下角的圆点,向下复制和填充公式,得到其他数据的判断结果,发现数据326被判断为异常值。
使用第2种方法,即用数据的分位数进行查找,在C1单元格输入下面的公式:
=OR($A1<PERCENTILE.EXC($A$1:$A$14,0.25)-1.5*(PERCENTILE.EXC($A$1:$A$14,0.75)-PERCENTILE.EXC($A$1:$A$14,0.25)),$A1>PERCENTILE.EXC($A$1:$A$14,0.75)+1.5*(PERCENTILE.EXC($A$1:$A$14,0.75)-PERCENTILE.EXC($A$1:$A$14,0.25)))
其中,PERCENTILE.EXC函数计算数据的分位数,参数指定数据范围和分位数的位置。0.75分位数减去0.25分位数得到数据的内四分极值。回车,单元格中显示结果为FALSE,说明A1单元格中的数据不是异常值。双击单元格右下角的圆点,向下复制和填充公式,得到其他数据的判断结果,发现数据3, 104和326被判断为异常值。算法不同,计算结果有差异。
选定A列数据后,在工作表中插入箱形图,如图9-5中所示。箱形图中间箱体的上下界表示数据的0.75分位数和0.25分位数,向外扩展1.5*内四分极值的距离得到上下两个触须。触须之外的点即为异常值点。
【Excel VBA】
在Excel VBA中可以调用Excel函数处理异常值。示例文件的存放路径为Samples\ch21\Excel VBA\异常值.xlsm。
过程Test用均值和标准差查找异常值。
Sub Test()
'用均值和标准差查找异常值
Dim intI As Integer
Dim sngMean As Single
Dim sngSTDEV As Single
'均值
sngMean = Application.WorksheetFunction.Average(Range("A1:A14"))
'标准差
sngSTDEV = Application.WorksheetFunction.StDev(Range("A1:A14"))
'遍历每个数据,如果<(均值-3*标准差)或>(均值+3*标准差),异常
For intI = 1 To 14
If Cells(intI, 1) < sngMean - 3 * sngSTDEV Or Cells(intI, 1) > _
sngMean + 3 * sngSTDEV Then
Cells(intI, 2).Value = True
Else
Cells(intI, 2).Value = False
End If
Next
End Sub
过程Test2用分位数查找异常值。
Sub Test2()
'用分位数查找异常值
Dim intI As Integer
Dim sngP25 As Single
Dim sngP75 As Single
Dim sngIQR As Single
'0.75分位数
sngP75 = Application.WorksheetFunction.Percentile(Range("A1:A14"), 0.75)
'0.25分位数
sngP25 = Application.WorksheetFunction.Percentile(Range("A1:A14"), 0.25)
'内四分极值
sngIQR = sngP75 - sngP25
'遍历每个数据,如果<(0.25分位数-1.5*内四分极值)或
'>(0.75分位数+1.5*内四分极值),异常
For intI = 1 To 14
If Cells(intI, 1) < sngP25 - 1.5 * sngIQR Or Cells(intI, 1) > _
sngP75 + 1.5 * sngIQR Then
Cells(intI, 3).Value = True
Else
Cells(intI, 3).Value = False
End If
Next
End Sub
运行两个过程,分别在工作表中B列和C列输出判断结果。
【Python xlwings】
下面在Python中结合xlwings包调用Excel函数查找数据的异常值。
使用均值和标准差进行判断。脚本文件的存放路径为Samples\ch21\Python\异常值-xlwings-1.py。
import xlwings as xw
import os
root=os.getcwd()
app=xw.App(visible=True, add_book=False)
wb=app.books.open(root+r'/异常值.xlsx',read_only=False)
sht=wb.sheets(1)
#计算均值
mean_v=app.api.WorksheetFunction.Average(sht.api.Range('A1:A14'))
#计算标准差
stdev_v=app.api.WorksheetFunction.StDev(sht.api.Range('A1:A14'))
#遍历每个数据,如果<(均值-3*标准差)或>(均值+3*标准差),异常
for i in range(1,15):
if sht.api.Cells(i,1).Value<mean_v-3*stdev_v or\
sht.api.Cells(i,1).Value>mean_v+3*stdev_v:
sht.api.Cells(i,2).Value=True
else:
sht.api.Cells(i,2).Value=False
使用分位数进行判断。脚本文件的存放路径为Samples\ch21\Python\异常值-xlwings-2.py。
import xlwings as xw
import os
root=os.getcwd()
app=xw.App(visible=True, add_book=False)
wb=app.books.open(root+r'/异常值.xlsx',read_only=False)
sht=wb.sheets(1)
#计算0.75分位数
stp75=app.api.WorksheetFunction.Percentile(sht.api.Range('A1:A14'),0.75)
#计算0.25分位数
stp25=app.api.WorksheetFunction.Percentile(sht.api.Range('A1:A14'),0.25)
#计算内四分极值
iqr=stp75-stp25
#遍历每个数据,如果<(0.25分位数-1.5*内四分极值)或
#>(0.75分位数+1.5*内四分极值),异常
for i in range(1,15):
if sht.api.Cells(i,1).Value<stp25-1.5*iqr or\
sht.api.Cells(i,1).Value>stp75+1.5*iqr:
sht.api.Cells(i,3).Value=True
else:
sht.api.Cells(i,3).Value=False
运行两个脚本,两种方法的判断结果输出到工作表的第2列和第3列。
【Python pandas】
Python的pandas包提供了计算均值、标准差和分位数的函数,下面使用pandas包在数据中查找异常值。
使用均值和标准差进行判断,pandas中用序列对象的mean函数和std函数计算均值和标准差。脚本文件的存放路径为Samples\ch21\Python\异常值-pandas-1.py。
import pandas as pd
import numpy as np
import os
root = os.getcwd()
df=pd.read_excel(io=root+r'\异常值2.xlsx',engine="openpyxl")
#计算均值
mean_v=df['A'].mean()
#计算标准差
stdev_v=df['A'].std()
#数据
data=df['A']
#输出异常值
print(data[(data>mean_v+3*stdev_v) | (data<mean_v-3*stdev_v)])
#处理异常值,换成缺失值
data[(data>mean_v+3*stdev_v) | (data<mean_v-3*stdev_v)]=np.nan
print(data)
使用分位数进行判断,pandas中用序列对象的quantile函数计算分位数。脚本文件的存放路径为Samples\ch21\Python\异常值-pandas-2.py。
import pandas as pd
import numpy as np
import os
root = os.getcwd()
df=pd.read_excel(io=root+r'\异常值2.xlsx',engine="openpyxl")
#计算0.75分位数
stp75=df['A'].quantile(0.75)
#计算0.25分位数
stp25=df['A'].quantile(0.25)
#计算内四分极值
iqr=stp75-stp25
#数据
data=df['A']
#输出异常值
print(data[(data>stp75+1.5*iqr) | (data<stp25-1.5*iqr)])
#处理异常值,换成缺失值
data[(data>stp75+1.5*iqr) | (data<stp25-1.5*iqr)]=np.nan
print(data)
分别运行两个脚本,在Python Shell窗口输出异常值和将异常值处理为缺失值后的结果。
Python中使用Matplotlib包绘制箱形图。编写脚本,脚本文件的存放路径为Samples\ch21\Python\箱形图.py。
import pandas as pd
import matplotlib.pyplot as plt
import os
root = os.getcwd()
df=pd.read_excel(io=root+r'\异常值2.xlsx',engine="openpyxl")
plt.boxplot(df['A'])
plt.show()
运行脚本,生成箱形图如图9-6所示。
图9-6 用Matplotlib生成箱形图
数据转换
对数据进行统计分析时,为了消除量纲和量级的影响,或者为了满足统计方法对数据要求,经常需要在统计分析之前度数据进行转换。常见的数据转换方法有对数转换、平方根转换、反正弦转换、中心化、标准化和归一化等。这里主要介绍后面3种。
中心化是将数据点向中心点平移,算法比较简单,将每个数据减去它们的均值即可。
标准化则使数据变换后服从标准正态分布,其作用是消除量纲和量级的影响,使得表示样本的多个指标具有相同的尺度。标准化的算法是将每个数据减去它们的均值后除以标准差。
归一化是将所有数据转换到0和1之间。算法是将每个数减去数据最小值得到的差除以数据的极差。极差用数据的最大值减去最小值得到。
对于图9-7所示工作表第1列的数据,分别用中心化、归一化和标准化进行转换。
图9-7 数据转换
【Excel】
示例文件的存放路径为Samples\ch21\Excel函数\数据转换.xlsx。
在工作表中,在B2单元格中输入公式:
=$A2-AVERAGE($A$2:$A$85)
回车,双击B2单元格右下角圆点,得到中心化数据的结果如图9-7工作表中第2列所示。
在工作表中C2单元格中输入公式:
=($A2-MIN($A$2:$A$85))/(MAX($A$2:$A$85)-MIN($A$2:$A$85))
回车,双击C2单元格右下角圆点,得到归一化数据的结果如图9-7工作表中第3列所示。
在工作表中D2单元格中输入公式:
=STANDARDIZE($A2,AVERAGE($A$1:$A$85),STDEV($A$1:$A$85))
回车,双击D2单元格右下角圆点,得到标准化化数据的结果如图9-7工作表中第4列所示。
【Excel VBA, Python】
仿照9.3.3小节,可在Excel VBA, Python xlwings和Python pandas环境下实现对应的数据转换方法,不再赘述。[大谦Excel,dqexcel点com]