数据库与数据挖掘_实验三_Python代码与数据预处理
下面举例详细讲述如何用python编程实现数据预处理,包含数据清理、数据集成、数据变换以及数据规约等操作.
数据清理是数据预处理中关键的一步,其目的在于剔除原有数据中的“脏” 数据,提高数据的质量,使数据具有完整性、唯一性、权威性、合法性和一致性等特点。 常见的数据清理的问题有数据缺失、数据重复、数据异常等,下面结合Python中的常用库pandas、numpy等库展开练习。
1.2 缺省值的检测与处理
缺失值是指样本数据中某个或某些属性的值是不全的, 主要是由于机械故障、人为原因导致部分数据未能收集.
1.2.1 缺失值的检测
pandas库中None或NaN代表缺失值,检测缺失值的常用方法包括isnull()、notnull()、isna()和notna().
import pandas as pd
import numpy as np
na_df = pd.DataFrame({'A':[1, 2, np.NaN, 4],
'B':[3, 4, 4, 5],
'C':[5, 6, 7, 8],
'D':[7, 5, np.NaN, np.NaN]})
na_df
#使用isna()方法检测na_df中是否存在缺失值
na_df.isna()
na_df.notna()
注意: isnull()、notnull()、isna()和notna()方法均会返回一个由布尔值组成、与原对象形状相同的新对象,
其中isnull()和isna()方法的用法相同,它们会在检测到缺失值的位置标记True;
notnull()和notna()方法的用法相同,它们会在检测到缺失值的位置标记False.
1.2.2 缺失值的处理
缺失值的常见处理方式有三种: 删除缺失值、填充缺失值和插补缺失值,pandas中为每种处理方式均提供了相应的方法.
删除缺失值: pandas中dropna()方法用于删除缺失值所在的一行或一列数据,并返回一个删除缺失值后的新对象.
#删除缺失值所在的一行数据
na_df.dropna()
# 保留至少有3个非NaN值的行
na_df.dropna(thresh=3)
填充缺失值: pandas中fillna()方法既可以使用指定的数据填充, 也可以使用缺失值前面或后面的数据填充.
# 计算A列的平均数,并保留一位小数
col_a = np.around(np.mean(na_df['A']), 1)
# 计算D列的平均数,并保留一位小数
col_d = np.around(np.mean(na_df['D']), 1)
# 将计算的平均数填充到指定的列
na_df.fillna({'A':col_a, 'D':col_d})
na_df.fillna(method='ffill')
插补缺失值:pandas中提供了插补缺失值的方法interpolate(),interpolate() 会根据相应的插值方法求得的值进行填充.
na_df.interpolate(method='linear')
1.3 重复值的检测与处理
重复值是指样本数据中某个或某些数据记录完全相同, 主要是由于人工录入、机械故障导致部分数据重复录入.
1.3.1 重复值的检测
pandas库中使用duplicated()方法来检测数据中的重复值.
person_info = pd.DataFrame({'name': ['刘婷婷', '王淼', '彭岩', '刘华', '刘华', '周华'],
'age': [24, 23, 29, 22, 22, 27],
'height': [162, 165, 175, 175, 175, 178],
'gender': ['女', '女', '男', '男', '男', '男']})
person_info
# 检测person_info对象中的重复值
person_info.duplicated()
1.3.2 重复值的处理
重复值的一般处理方式是删除,pandas中使用drop_duplicates()方法删除重复值.
# 删除person_info对象中的重复值
person_info.drop_duplicates()
1.4 异常值的检测与处理
异常值是指样本数据中处于特定范围之外的个别值,这些值明显偏离它们所属样本的其余观测值,其产生的原因有很多,包括人为疏忽、失误或仪器异常等.
处理异常值之前,需要先辨别哪些值是"真异常"和"伪异常",再根据实际情况正确地处理异常值.
1.4.1 异常值的检测
3σ原则和箱形图检测
大多数数值集中在(μ-3σ,μ+3σ)区间的概率最大,数值超出这个区间的概率仅占不到0.3%. 所以,凡是误差超过(μ-3σ,μ+3σ)区间的数值均属于异常值.
import numpy as np
import pandas as pd
def three_sigma(ser):
"""
:param ser: 被检测的数据,接收DataFrame的一列数据
:return: 异常值及其对应的行索引
"""
# 计算平均值
mean_data = ser.mean()
# 计算标准差
std_data = ser.std()
#小于μ-3σ或大于μ+3σ的数值均为异常值
rule = (mean_data-3*std_data>ser) | (mean_data+3*std_data<ser)
# 返回异常值的行索引
index = np.arange(ser.shape[0])[rule]
# 获取异常值
outliers = ser.iloc[index]
return outliers
# 读取data.xlsx文件
excel_data = pd.read_excel('example_data.xlsx')
# 对value列进行异常值检测
three_sigma(excel_data['value'])
箱形图是一种用于显示一组数据分散情况的统计图,它通常由上边缘、上四分位数、中位数、下四分位数、下边缘和异常值组成。
箱形图能直观地反映出一组数据的分散情况,一旦图中出现离群点(远离大多数值的点),就认为该离群点可能为异常值。
Q3表示上四分位数,说明全部检测值中有四分之一的值比它大;Q1表示下四分位数,说明全部检测值中有四分之一的值比它小;
IQR表示四分位数间距,即上四分位数Q3与下四分位数Q1之差,其中包含了一半检测值;空心圆点表示异常值,
该值的范围通常为小于Q1 – 1.5IQR或大于Q3 + 1.5IQR
import pandas as pd
excel_data = pd.read_excel('example_data.xlsx')
excel_data.boxplot(column='value')
如果需要从箱形图中获取异常值及其对应的索引, 那么可以根据箱形图中异常值的范围计算, 具体计算方式为:
首先对数据集进行排序, 然后根据排序后的数据分别计算Q1、Q3和IQR的值, 最后根据异常值的范围(Q1 – 1.5IQR或大于Q3 + 1.5IQR)得出异常值.
import pandas as pd
import numpy as np
def box_outliers(ser):
# 对待检测的数据集进行排序
new_ser = ser.sort_values()
# 判断数据的总数量是奇数还是偶数
if new_ser.count() % 2 == 0:
# 计算Q3、Q1、IQR
Q3 = new_ser[int(len(new_ser) / 2):].median()
Q1 = new_ser[:int(len(new_ser) / 2)].median()
elif new_ser.count() % 2 != 0:
Q3 = new_ser[int((len(new_ser)-1) / 2):].median()
Q1 = new_ser[:int((len(new_ser)-1) / 2)].median()
IQR = round(Q3 - Q1, 1)
rule = (round(Q3+1.5*IQR, 1) < ser)|(round(Q1-1.5*IQR, 1) > ser)
index = np.arange(ser.shape[0])[rule]
# 获取异常值及其索引
outliers = ser.iloc[index]
return outliers
excel_data = pd.read_excel('example_data.xlsx')
box_outliers(excel_data['value'])
1.4.2 异常值的处理
异常值有3种处理方式, 分别为保留异常值、删除异常值和替换异常值
删除异常值: pandas中提供了删除数据的drop()方法,使用该方法可以根据指定的行标签索引或列标签索引来删除异常值.
excel_data = pd.read_excel('example_data.xlsx')
excel_data.drop([121, 710])
clean_data = excel_data.drop([121, 710])
# 再次检测数据中是否还有异常值
three_sigma(clean_data['value'])
替换异常值: pandas中提供了替换值的replace()方法, replace()方法可以对单个或多个值进行替换.
replace_data = excel_data.replace({13.2:10.2, 13.1:10.5})
# 根据行索引获取替换后的值
print(replace_data.loc[121])
print(replace_data.loc[710])
为提高数据分析的效率,多个数据源的数据需要合并到一个数据源,形成一致的数据存储,这一过程就是数据集成。 在数据集成期间可能会面临很多问题,包括实体识别、冗余属性识别、元组重复、数据值冲突等问题,其中实体识别、冗余属性识别、元组重复较难理解。
2.2 合并数据
主键合并数据; 主键合并数据类似于关系型数据库的连接操作,主要通过指定一个或多个键将两组数据进行连接,通常以两组数据中重复的列索引为合并键.
参数的取值'inner'代表基于left与right的共有的键合并,类似于数据库的内连接操作;
'left'代表基于left的键合并,类似于数据库的左外连接操作;
'right'代表基于right的键合并,类似于数据库的右外连接操作;
'outer'代表基于所有left与right的键合并,类似于数据库的全外连接操作.
import pandas as pd
df_left = pd.DataFrame({'key':['K0','K1','K2'],
'A':['A0','A1','A2'],
'B':['B0','B1','B2']})
df_right = pd.DataFrame({'key':['K0','K1','K2','K3'],
'C':['C0','C1','C2','C3'],
'D':['D0','D1','D2','D3']})
# 以key为主键,采用内连接的方式合并数据
result = pd.merge(df_left, df_right, on='key')
print(result)
# 以key为主键,采用左外连接的方式合并数据
result = pd.merge(df_left, df_right, on='key', how='left')
print(result)
# 以key为主键,采用右外连接的方式合并数据
result = pd.merge(df_left, df_right, on='key', how='right')
print(result)
# 以key为主键,采用全外连接的方式合并数据
result = pd.merge(df_left, df_right, on='key', how='outer')
print(result)
堆叠合并数据
堆叠合并数据类似于数据库中合并数据表的操作,主要沿着某个轴将多个对象进行拼接.
# 采用外连接方式,沿行方向合并数据
result = pd.concat([df_left, df_right], axis=0)
print(result)
# 采用外连接方式,沿列方向合并数据
result = pd.concat([df_left, df_right], axis=1)
print(result)
重叠合并数据
当两组数据的索引完全重合或部分重合,且数据中存在缺失值时,可以采用重叠合并的方式组合数据.
重叠合并数据是一种并不常见的操作,它主要将一组数据的空值填充为另一组数据中对应位置的值.
pandas中可使用combine_first()方法实现重叠合并数据的操作.
import numpy as np
from numpy import NAN
import pandas as pd
df_left = pd.DataFrame({'A': [np.nan, 'A1', 'A2', 'A3'],
'B': [np.nan, 'B1', np.nan, 'B3'],
'C': ['C0', 'C1', 'C2', 'C3']})
df_right = pd.DataFrame({'A': ['A1', 'A0','A2'],
'B': ['B1', 'B0','B2']}, index=[1,0,2])
# 采用重叠合并的方式组合数据
result = df_left.combine_first(df_right)
print(result)
数据变换主要是从数据中找到特征表示,通过一些转换方法减少有效变量的数目或找到数据的不变式, 常见的操作可以分为数据标准化处理、数据离散化处理和数据泛化处理三类。
数据标准化处理 数据标准化处理是将数据按照一定的比例缩放,使之投射到一个比较小的特定区间。 最小-最大标准化:主要对数据进行线性变换,使之范围变为[0,1]。 均值标准化:通过该方法处理的新数据中均值为0,标准差为1。 小数定标标准化:移动数据的小数点,使数据映射到[-1,1]。
数据离散化处理 数据离散化处理一般是在数据的取值范围内设定若干个离散的划分点,将取值范围划分为若干离散化的区间,分别用不同的符号或整数值代表落在每个子区间的数值。 等宽法:等宽法将属性的值域从最小值到最大值划分成具有相同宽度的区间 等频法:等频法将相同数量的值划分到每个区间,保证每个区间的数量基本一致。
数据泛化处理 数据泛化处理指用高层次概念取代低层次概念的数据。例如,年龄是一个低层次的概念,它经过泛化处理后会变成诸如青年、中年等高层次的概念。
Pandas中有关数据变换的基本操作包括轴向旋转、分组与聚合、哑变量处理和面元划分.
3.2 轴向旋转
pivot()方法用于将DataFrame类对象的某一列数据转换为列索引.
import pandas as pd
df_obj = pd.DataFrame({'商品名称': ['荣耀9X','小米6x','OPPO A1',
'荣耀9X','小米6x','OPPO A1'],
'出售日期': ['5月25日', '5月25日','5月25日',
'6月18日','6月18日', '6月18日'],
'价格(元)': [999, 1399, 1399, 800, 1200, 1250]})
df_obj
# 将出售日期一列的唯一数据变换为行索引,商品一列的唯一数据变换为列索引
new_df = df_obj.pivot(index='出售日期', columns='商品名称',
values='价格(元)')
new_df
melt()是pivot()的逆操作方法,用于将DataFrame类对象的列索引转换为一行数据.
# 将列索引转换为一行数据
new_df.melt(value_name='价格(元)', ignore_index=False)
3.3 分组与聚合
分组与聚合是常见的数据变换操作,其中分组指根据分组条件(一个或多个键)将原数据拆分为若干个组;聚合指任何能从分组数据生成标量值的变换过程.
分组操作: pandas中使用groupby()方法根据键将原数据拆分为若干个分组.
import pandas as pd
df_obj = pd.DataFrame({"key":["C", "B", "C", "A", "B", "B", "A", "C", "A"],
"data":[2, 4, 6, 8, 10, 1, 3, 5, 7]})
# 根据key列对df_obj进行分组
groupby_obj = df_obj.groupby(by="key")
print(groupby_obj)
for group in groupby_obj: # 遍历DataFrameGroupBy类的对象
print(group)
result = dict([x for x in groupby_obj])['A']
print(result)
# print(groupby_obj.max()) # 使用max()方法聚合分组数据
from pandas import DataFrame
df_obj = DataFrame({'a': [0, 6, 12, 18, 24, 30],
'b': [1, 7, 13, 19, 25, 31],
'c': [2, 8, 14, 20, 26, 32],
'd': [3, 9, 15, 21, 27, 33],
'e': [4, 10, 16, 22, 28, 34],
'f': [5, 11, 17, 23, 29, 35]})
print(df_obj)
聚合操作: pandas中可通过多种方式实现聚合操作,除内置统计方法之外,还包括agg()、transfrom()和apply()方法.
# 根据列表对df_obj进行分组,列表中相同元素对应的行会归为一组
groupby_obj = df_obj.groupby(by=['A', 'A', 'B', 'B', 'A', 'B'])
# 定义求极差的函数
def my_range(arr):
return arr.max()-arr.min()
groupby_obj.agg(my_range) # 使用agg()方法聚合分组数据
# 使用agg()方法聚合分组中指定列的数据
groupby_obj.agg({'a':'max', 'c':'sum', 'e': my_range})
# 使用transform()方法聚合分组数据
print(groupby_obj.transform('max'))
# 自定义函数,用于计算每个数据除以100的结果
def div_hun(df):
return df.iloc[:, :] / 100
print(groupby_obj.apply(div_hun))
3.4 哑变量
哑变量又称虚拟变量、名义变量等,它是人为虚设的变量,用来反映某个变量的不同类别,常用的取值为0和1.
需要说明的是,0和1并不代表数量的多少,而代表不同的类别.
pandas中使用get_dummies()函数对类别数据进行哑变量处理,并在处理后返回一个哑变量矩阵.
import pandas as pd
position_df = pd.DataFrame({'职业': ['工人', '学生', '司机', '教师', '导游']})
# 哑变量处理, 并给哑变量添加前缀
result = pd.get_dummies(position_df, prefix=['col'])
print(result)
3.5 面元划分
面元划分是指数据被离散化处理,按一定的映射关系划分为相应的面元(可以理解为区间),只适用于连续数据.
pandas中使用cut()函数能够实现面元划分操作, cut()函数会采用等宽法对连续型数据进行离散化处理.
import pandas as pd
ages = pd.Series([19, 21, 25, 55, 30, 45, 52, 46, 20])
bins = [0, 18, 30, 40, 50, 100]
# 使用cut函数划分年龄区间
cuts = pd.cut(ages, bins)
print(cuts)
数据规约类似数据集的压缩,它的作用主要是从原有数据集中获得一个精简的数据集,这样可以在降低数据规模的基础上,保留了原有数据集的完整特性。 多种手段,包括维度规约、数量规约和数据压缩。
4.2 维度规约
维度规约是指减少所需属性的数目.
重塑分层索引是pandas中简单的维度规约操作,该操作主要会将DataFrame类对象的列索引转换为行索引,生成一个具有分层索引的结果对象.
pandas中可以使用stack()方法实现重塑分层索引操作.
import pandas as pd
df = pd.DataFrame({'A':['A0','A1','A2'],
'B':['B0','B1','B2']})
# 重塑df,使之具有两层行索引
result = df.stack()
result
4.3 数量规约
数量规约是指用较小规模的数据替换或估计原数据,主要包括回归与线性对数模型、直方图、聚类、采样和数据立方体这几种方法.
降采样是一种简单的数据规约操作,它主要是将高频率采集数据规约到低频率采集数据.
pandas中可以使用resample()方法实现降采样操作.
import numpy as np
import pandas as pd
time_ser = pd.date_range('2020/06/01', periods=30)
stock_data = np.random.randint(40, 60, size=30)
time_obj = pd.Series(stock_data, index=time_ser)
print(time_obj)
# 每7天采集一次数据,实现降采样操作
result = time_obj.resample('7D').mean()
result.astype("int64")