应对期末考试,对本学期《Python 数据分析与应用》课程学习的内容进行复习。


# 第四章、pandas 进行数据预处理

1719038031036

# 一、堆叠合并数据

image-20240622143713236

# 1. 轴向堆叠合并数据

函数:pandas.concat (参数),concat 函数主要参数如下,axis 是关键,它用于指定合并的轴是行还是列:

参数名称参数说明
objs接收多个 Series、DataFrame、Panel 的组合。表示参与连接的 pandas 对象的列表的组合。无默认值
axis接收 int。表示连接的轴向,可选 0 和 1。默认为 0
join接收 str。表示其他轴向上的索引是按交集(inner)还是并集(outer)进行合并。默认为 outer
ignore_index接收 bool。表示是否不保留连接轴上的索引,产生一组新索引 range (total_length)。默认为 False
keys接收 sequence。表示与连接对象有关的值,用于形成连接轴向上的层次化索引。默认为 None
levels接收包含多个 sequence 的 list。表示在指定 keys 参数后,指定用作层次化索引各级别上的索引。默认为 None
names接收 list。表示在设置了 keys 和 levels 参数后,用于创建分层级别的名称。默认为 None
verify_integrity接收 bool。表示检查新连接的轴是否包含重复项,如果发现重复项,那么引发异常。默认为 False
sort接收 bool。表示对非连接轴进行排序。默认为 False
  • 横向堆叠:行对齐,然后将不同列名称的两张或多张表合并。索引不同,join 参数用于设置连接可选 inner 或 outer,索引完全相同,join 设置任何值都是直接将两个表在 x 轴向拼接在一起。

    image-20240622143940387

  • 纵向堆叠:列对齐,将不同行索引的两张或多张表纵向合并。列名并不完全相同,当 join 参数取值为 inner 时,返回的仅仅是列名的交集所代表的列。当 join 参数取值为 outer 时,返回的是两者列名的并集所代表的列。当两张表的列名完全相同时,不论 join 参数的取值是 inner 还是 outer,结果都是将两个表完全按照 y 轴拼接起来。

  • image-20240622143946855

函数:append () 方法,方法主要使用格式及参数如下:

pandas.DataFrame.append (参数)

参数名称参数说明
other接收 DataFrame 或 Series。表示要添加的新数据。无默认值
ignore_index接收 bool。如果输入 True,那么就会对新生成的 DataFrame 使用新的索引(自动产生),而忽略原来数据的索引。默认为 False
verify_integrity接收 bool。如果输入 True,那么当 ignore_index 为 False 时,会检查添加的数据索引是否冲突,若冲突,则会添加失败。默认为 False
sort接收 bool。如果输入 True,那么会对合并的两个表的列进行排序。默认为 False

用于纵向合并两张表。但是使用 append () 方法实现纵向表堆叠的前提条件是两张表的列名需要完全一致。append () 方法的基本使用格式如下。

案例:

import pandas as pd
import numpy as np
df1 = pd.DataFrame(np.random.randn(3,4),columns=['A','B','C','D'])
df2 = pd.DataFrame(np.random.randn(2,3),columns=['A','B','D'])
print(df1)
print(df2)
print(pd.concat([df1,df2]))  #默认 axis=0
print(pd.concat([df1,df2],axis=1))#默认 join='ourter'
print(pd.concat([df1,df2],axis=1,join='inner')
#默认 join='ourter'
print(pd.concat([df1,df2],axis=0,join='inner',ignore_index=True))
#axis=0 (纵向),join=inner(内连接交集),ignore_index=True (重新设置索引)

# 2、主键合并数据

通过一个或多个键将两个数据集的行连接起来,类似于关系型数据库的连接方式中的 join。针对两张包含不同特征的表,将根据某几个特征一一对应拼接起来,合并后数据的列数为两个原数据的列数和减去连接键的数量。

image-20240622144151012

image-20240622144155297

函数:pandas.merge (参数),函数主要参数如下:

参数名称参数说明
left接收 DataFrame 或 Series。表示要添加的新数据 1。无默认值
right接收 DataFrame 或 Series。表示要添加的新数据 2。无默认值
how接收 “inner”“outer”“left” 或 “right”。表示数据的连接方式。默认为 inner
on接收 str 或 sequence。表示两个数据合并的主键(必须一致)。默认为 None
left_on接收 str 或 sequence。表示 left 参数接收数据用于合并的主键。默认为 None
right_on接收 str 或 sequence。表示 right 参数接收数据用于合并的主键。默认为 None
left_index接收 bool。表示是否将 left 参数接收数据的 index 作为连接主键。默认为 False
right_index接收 bool。表示是否将 right 参数接收数据的 index 作为连接主键。默认为 False
sort接收 bool。表示是否根据连接键对合并后的数据进行排序。默认为 False
suffixes接收 tuple。表示用于追加到 left 和 right 参数接收数据列名相同时的后缀。默认为 ('_x', '_y')

案例:

df1 = pd.DataFrame({'id':[1,2,3,4,5],'age':[34,28,22,29,17],'ctg':list('AABCB')})
df2 = pd.DataFrame({'id':[3,4,5,6,7],'ticket':['1001','1002','1003','1004','1005'],'amount':[24.1,32.5,34.8,19.5,26.2]})
new = pd.merge(df1,df2,how='inner',on='id')
print(new)

总结(综上所述):

1、concat 实现的只是将两个 df 按行或者列简单进行拼接,并没有实现 sql 中的 join 功能。

2、要想实现 sql 中的 join,需要使用 merge 方法。

# 二、Pandas 清洗数据

# 1、检测与处理重复值

image-20240622144416065

记录重复,同一列不同数据是否相同:pd.dataframe.drop_duplicates (参数)

参数名称参数说明
subset接收 str 或 sequence。表示进行去重的列。默认为 None
keep接收特定 str。表示重复时保留第几个数据,“first” 表示保留第一个;“last” 表示保留最后一个;False 表示只要有重复都不保留。默认为 first
inplace接收 bool。表示是否在原表上进行操作。默认为 False
ignore_index接收 bool。表示是否忽略索引。默认为 False

特征重复(不同列之间数据是否相同):使用 corr 方法计算相似度。

dataframe.corr (method),method 方法有:pearson(皮尔森,两组变量中任何一组中的值不能都是相同、同一组中的值不能差距太大,即如果有异常值相关性会干扰)、spearman(斯皮尔曼,根据原始数据的排序位置进行求解,异常值不会干扰)、kendall(肯德尔)秩相关系数,它也是一种秩相关系数,不过它所计算的对象主要是分类变量,比如无序的 --- 性别(男、女)、血型(A、B、O、AB),有序的 --- 比如肥胖等级(重度肥胖,中度肥胖、轻度肥胖、不肥胖)。

但要注意:只能针对数值型特征计算相似度。无法实现对类别型特征计算相似度矩阵,如果遇到特征值可转换。

案例:

core = pd.read_excel(r'data\core.xlsx')
print(core)
print("以下是pearson(皮尔森)")
print(core[['E','F']].corr(method='pearson'))  #pearson(皮尔森)
print(core[['G','F']].corr(method='pearson'))  #pearson(皮尔森), 有一个列值相同
print(core[['D','F']].corr(method='pearson'))  #pearson(皮尔森),有异常值
print("以下是spearman(斯皮尔曼)")
print(core[['E','G']].corr(method='spearman'))   #spearman(斯皮尔曼)
print(core[['D','F']].corr(method='spearman'))   #spearman(斯皮尔曼)
print("以下是kendall(肯德尔)")
print(core[['D','F']].corr(method='kendall'))  #kendall (肯德尔)

# 2、检测与处理缺失值

image-20240622144658241

(1)缺失(空)值判断:pd.isnull (data)、pd.isna (data) 或 data.isnull ()、data.isna ()

(2)缺失(空)值处理:

删除:data.dropna (axis),axis 参数用于设置移除行或列

替换:data.fillna (value,method)

methodà’bfill’: 后一个值填充,methodà’ffill’: 前一个值填充

插值:线性插值,多项式插值,样条插值

案例:

#线性插值
from scipy.interpolate import interp1d
x = pd.DataFrame({'x':[1,2,3,4,5,8,9,10]})
y1 = pd.DataFrame({'y1':[2,8,18,32,50,128,162,200]})
y2 = pd.DataFrame({'y2':[3,5,7,9,11,17,19,21]})
data = pd.concat([x,y1,y2],axis=1)
print(data)
data1 = interp1d(data['x'],data['y1'],kind='linear')
data2 = interp1d(data['x'],data['y2'],kind='linear')
print(data1([6,7]))
print(data2([6,7]))

# 3、检测与处理异常值

image-20240622144949435

符合正态分布的值:3σ(大写 Σ) 原则 --- 数据量较大、格拉布斯准则 --- 检验较少数据

3σ 原则又称为拉依达准则,其原则就是先假设一组检测数据只含有随机误差,对原始数据进行计算处理得到标准差,然后按一定的概率确定一个区间,认为误差超过这个区间就属于异常。

3σ 原则仅适用于对正态或近似正态分布的样本数据进行处理。正态分布数据的 3σ 原则如下表所示,其中 σ 代表标准差 (std),μ 代表均值 (mean)。数据的数值分布几乎全部集中在区间 (μ-3σ,μ+3σ) 内,超出这个范围的数据仅占不到 0.3%。故根据小概率原理,可以认为超出 3σ 的部分为异常数据。

image-20240622145008150

image-20240622145011041

案例:

#读取 data\core.xlsx 文件中的数据,使用 3σ 原则分析 D 列是否存在异常值。
import pandas as pd 
data = pd.read_excel(r'data\core.xlsx')
def out_range(ser1):
    bool_ind = (ser1.mean()-3*ser1.std() >ser1) |(ser1.mean() +3*ser1.std()< ser1)
    index = np.arange((ser1.shape[0]))[bool_ind]
    outrange = ser1.iloc[index]
    return outrange
print(data)
out = out_range(data['D'])
print(out)

离散值:箱线图分析

箱线图提供了识别异常值的一个标准,即异常值通常被定义为小于 QL-1.5IQR 或大于 QU+1.5IQR 的值。

・QL 称为下四分位数,表示全部观察值中有四分之一的数据取值比它小;

・QU 称为上四分位数,表示全部观察值中有四分之一的数据取值比它大;

・IQR 称为四分位数间距,是上四分位数 QU 与下四分位数 QL 之差,其间包含了全部观察值的一半。

案例:

#读取 data\core.xlsx 文件中的数据,使用箱线图分析年龄异常值。
import pandas as pd
import  matplotlib.pyplot as plt
data = pd.read_excel(r'data\core.xlsx')
plt.figure(figsize=(10,8),dpi=160)
p = plt.boxplot(list(data['年龄'].values))
outlier1 = p['fliers'][0].get_ydata()
plt.savefig(r'data/异常.jpg')
plt.show()

# 三、Pandas 量纲化数据

不同特征往往具有不同的量纲,涉及空间距离计算或使用梯度下降法时,不对数据进行处理可能会影响数据分析结果。为了消除因特征之间的量纲和取值范围差异对数据分析可能造成的影响,保证数据的一致性,需要进行规范化(量纲化)处理。量纲化有很多种方式,但具体应该使用那一种方式,并没有固定的标准,而应该结合数据情况或者研究算法,选择最适合的量纲化处理方式,量纲化处理方法常见的有如下图所示:

image-20240622145117940

# 1、 归一化数据

离差标准化是对原始数据的一种线性变换,结果是将原始数据的数值映射到 [0,1] 区间,转换公式如下:

image-20240622145123830

max 为样本数据的最大值,min 为样本数据的最小值,max-min 为极差。

离差标准化的缺点:若数值集中某个数值很大,则规范化后各其余值会接近于 0,并且将会相差不大。

# 2、 标准差标准化数据

标准差标准化也叫零均值标准化或分数标准化,是当前使用较为广泛的数据标准化方法,经过该方法处理的数据均值为 0,标准差为 1,转化公式如下式:

image-20240622145129617

其中, 为原始数据的均值,d 为原始数据的标准差。标准差标准化后的值区间不局限于 [0,1],并且存在负值。

import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import MinMaxScaler
data = pd.DataFrame([['北京','-5','25','18'],
                     ['上海','-2','27','20'],
                     ['广州','3','33','25'],
                     ['深圳','5','35','28']],
                    columns=['地区','最低气温','最高气温','最常气温'])
#标准差(Z-score) 标准化数据
s_scaler = StandardScaler()
df = data.iloc[:,1:]
print(df)
print("标准差(Z-score)标准化数据:")
print(s_scaler.fit_transform(df))
#归一化 (离差)标准化数据
print("归一化(离差)标准化数据:")
max_min_scaler = MinMaxScaler()
print(max_min_scaler.fit_transform(df))
import pandas as pd
pay = pd.read_csv(r'data/user_pay_info.csv', index_col=0)
# 自定义离差标准化函数
def min_max_scale(data):
    data = (data - data.min()) / (data.max() - data.min())
    return data
# 对用户每月支出信息表的每月支出数据做离差标准化
pay_min_max = min_max_scale(pay['每月支出'])
print('离差标准化之前每月支出数据为:\n', pay['每月支出'].head(20))
print('离差标准化之后每月支出数据为\n', pay_min_max.head(20))
# 自定义标准差标准化函数
def standard_scaler(data):
    data = (data - data.mean()) / data.std()
    return data
# 对用户每月支出信息表的每月支出数据做标准差标准化
pay_standard = standard_scaler(pay['每月支出'])
print('标准差标准化之前每月支出数据为:\n', pay['每月支出'].head(20))
print('标准差标准化之后每月支出数据为:\n', pay_standard.head(20))

两种标准化数据的比较:

离差标准化方法简单,便于理解,标准化后的数据限定在 [0,1] 区间内。

标准差标准化受数据分布的影响较小,大于 0 说明高于平均水平,小于 0 说明低于平均水平。

# 四、Pandas 变换数据

# 1、哑变量处理类别型数据

分析模型中有相当一部分的算法模型都要求输入的特征为数值型,但实际数据中,特征的类型不一定只有数值型,还会存在相当一部分的类别型,这部分的特征需要经过哑变量处理才可以放入模型之中。

使用 pandas 库的 get_dummies 函数对类别型变量作哑变量处理。

对于一个类别型特征,若其取值有 m 个,则经过哑变量处理后就变成了 m 个二元特征,并且这些特征互斥,每次只有一个激活,这使得数据变得稀疏。

# 将特定的数据 转化为 可以使用的数据 --- 数据变换
# 两种情况:
# 第一种:哑变量转化 -- 利用 pandas 库中的 get_dummies () 函数进行哑变量处理
# 通常与 PCA(主成分分析)一起使用,即构造哑变量产生高维数据后采用 PCA 进行降维。
# pd.get_dummmies(data,prefix,prefix_sep)
# data : 需要转化的数据
# prefix: 转化之后列名称的前缀
# 1、将年龄列(非数值型)数据 -----> 数值型
data = pd.read_excel(r'data\core.xlsx')
print(data['性别'])
newagedata = pd.get_dummies(data['性别'],prefix='性别_')
# 哑变量转化为数值型数据,维度两例,可降维(删除其中一列)
newagedata = newagedata.drop(columns='性别__男')
newagedata = newagedata.rename(columns={'性别__女':'性别'})
# 2、将城市列(非数值型)数据 -----> 数值型
newcitydata = pd.get_dummies(data['城市'],prefix='城市_')
print(newcitydata)
#哑变量转化为数值型数据,维度太多,降维不好处理。
#(1)Label Encoding 给对应进行编号处理,使用 LabelEncoder () 函数将文本类型的数据转换成数字(随机)
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
label = le.fit_transform(data['城市'])
print(label)
data['城市'] = label
print(data)
#(2)pandas 库中的 replace () 函数,按特定内容进行替换
# 先利用 value_counts () 函数查看 “城市” 列有哪些内容需要替换
print(data['城市'].value_counts())
data['城市'] = data['城市'].replace({'北京':11,'上海':12,'广州':13,'深圳':14,'佛山':15})
print(data)

# 2、离散化连续型数据

某些模型算法,特别是某些分类算法,如 ID3 决策树算法和 Apriori 算法等,要求数据是离散的。

此时就需要将连续型特征(数值型)变换成离散型特征(类别型),即连续特征离散化。

离散化涉及两个子任务,即确定分类数以及如何将连续型数据映射到这些类别型数据上。

等宽法:利用 pandas 的 cut 函数(将数据的值域分成具有相同宽度的区间,区间的个数由数据本身特点或者用户指定。)

等频法:cut 函数虽然不能够直接实现等频离散化,但是可以通过定义将相同数量的记录放进每个区间。

聚类分析法:将连续型数据使用聚类算法(k-means)进行聚类,再处理聚类得到的簇,为合并到一个簇的连续型数据做同一种标记。

①等宽法缺点:若数据分布不均匀,则等宽 cut 之后各个类的数目也会变得不均匀。

②等频法缺点:频法离散化,相较于等宽法离散化,避免了类分布不均匀的问题,但同时也有可能将数值非常接近的两个值分到不同的区间以满足每个区间对数据个数的要求。

③聚类分析法(连续型数据用聚类算法,如 k-means),使用频率高。

all_info = pd.read_csv(r'data\user_all_info.csv')
all_info['年龄'].fillna(30)
#等宽离散化 -- 将该列中所有的年龄值按设置的等份平均划分
age_cut = pd.cut(all_info['年龄'],5)
print('等宽离散化后记录的年龄分布为:\n',age_cut.value_counts())
#等频离散化 -- 将相同数量的记录放置在每个区间
import  numpy as np
def same_rate(data,k):
    w = data.quantile(np.arange(0,1+1.0/k,1.0/k))
    data = pd.cut(data,w)
    return data
age_same_rate = same_rate(all_info['年龄'],5).value_counts()
print('等频离散化后记录的年龄分布为:\n',age_same_rate)
#聚类离散化 --- 使用 Kmeans 进行聚类,得到簇,用户指定簇的个数,用于决定生成的区间数
def kmean_cut(data,k):
    from sklearn.cluster import  KMeans
    kmodel = KMeans(n_clusters=k)  #用户指定的簇的个数调用 KMeans 算法标记数据
    kmodel.fit(data.values.reshape((len(data) ,1)))  #训练数据模型
    c = pd.DataFrame(kmodel.cluster_centers_).sort_values(0)  #输出聚类中心并排序
    w = c.rolling(2).mean().iloc[1:]  #对相邻两项求中点,作为边界点
    w = [0] + list(w[0]) + [data.max()]  #把首末边界点加上
    data = pd.cut(data, w)  #调用 cut 函数将标记后的数据离散化处理
    return  data
all_info['年龄'].dropna(inplace = True)  #将表内年龄列为空的数据删除(inplace = True 表示原表操作)
age_kmeans = kmean_cut(all_info['年龄'],5).value_counts()
print('聚类离散化后记录的年龄分布为:\n',age_kmeans)