数据分析-pandas数据处理
概述
业务建模流程
- 不是机器学习建模流程
- 将业务抽象为分类or回归问题
- 定义标签,得到y
- 选取合适的样本,并匹配出全部的信息作为特征的来源
- 特征工程 + 模型训练 + 模型评价与调优(相互之间可能会有交互)
- 输出模型报告
- 上线与监控
特征工程
- 基础特征构造(基础特征提取)
- 特征预处理
- 数据清洗/归一化/标准化
- 特征衍生— 用已有的特征构造新的特征
- 多项式 平方 三次方
- 特征交叉
- 数值 x1*x2
- 离散:交叉组合
- 非线性变换— 用非线性函数对原特征进行计算
- 对数/指数/ cos/sin/相除得到比例/ knn中的近邻法/svm中的核方法
- 特征筛选和特征降维— 防止过拟合,减小计算量
数据清洗
- 缺失值,异常值和重复值的处理
缺失值处理
- 缺失值分类
- 行缺失/列缺失
- 处理方法
- 删除
- 原则:根据确实比例和重要性进行衡量
- 删除
- 删除行— 数据量大的时候可以删除空行
- 注意问题
- 删除后数据量小了不能删
- 注意类别平衡问题
- 注意问题
- 删除列
- A:直接删出特征
- B:重新获取数据
- C:删或者简单填充
- D:精准填充
填充
- 统计法 --简单填充
- 数值:均值,中位数
- 类别特征:众数
- 模型法
- 用没有确实的数据作为训练数据,训练模型,对缺失数据进行预测,来补全缺失值
- 数值:用回归模型
- 类别:分类模型
- 用没有确实的数据作为训练数据,训练模型,对缺失数据进行预测,来补全缺失值
- 逻辑推测—比较常用的方法
- 身份证号推测年龄,名字推测性别
- 专家补全
- 随机填充
-
- 真值转换法
- 将缺失值本身也看成是一种值
- 性别:男,女 未知
- 地铁线路未知代表没有地铁
- 注意:距离地铁的距离 0-1 2:代表离地铁站很远
- 注意:要跟业务人员沟通后确定
- 不处理
- 很多算法可以兼容缺失值
- knn,决策树,xgboost等
- 很多算法可以兼容缺失值
- 真值转换法
- 缺失值处理套路
- 发现缺失值
- 统计每个特征中缺失值比例
- 考虑如何额处理缺失值
异常值(极值)处理
如何判断异常:
-
正态分布
-
画箱线图
- boxplot
-
异常检测算法: GMM、孤立森林
-
判断异常值
- 假异常:有业务导致的异常数据,真实存在的
- 真异常:就是数据本身有异常
-
异常值处理
- 删
- 不删:
- 由业务导致的假异常 ,反映的是真实业务
- 做异常检测不用删
- 对异常值不敏感的算法
- 决策树:对数值型异常值不敏感
- 原因:决策树关注的顺序,不是值本身的大小
- 1,2,3,4,5,6,7,8
- 决策树:对数值型异常值不敏感
- 看成缺失值进行填充
重复值处理
- 去重
- 什么时候不去重
- 样本不平衡的时候重复采样的数据
- 真实业务产生的重复数据
python数据清洗案例
import pandas as pd
import numpy as np
from sklearn.impute import SimpleImputer
#缺失值处理
df=pd.DataFrame(np.random.randint(0,10,(6,4)),columns=['col1', 'col2', 'col3', 'col4'])
df.iloc[1:3,1]=np.nan
df.iloc[4,2:]=np.nan
df
df.isnull()
df.isnull().any(0)
df.isnull().sum()
df2=df.dropna()
df2
nan_model=SimpleImputer(missing_values=np.nan,strategy='mean') #使用sklearn填充空值
result=nan_model.fit_transform(df)
result
# pandas填充
# 向上填充
df3=df.fillna(method='backfill')
# 向上填充
df3=df.fillna(method='bfill',limit=1)
# 向下填充
df4=df.fillna(method='pad')
# 指定值填充
df5=df.fillna(0)
# 字典为每个列指定不同的填充
df6=df.fillna({'col2':10,'col3':100})
df7=df.fillna(df.mean())
# 异常值
import pandas as pd
df=pd.DataFrame({
'col1': [1, 120, 3, 5, 2, 12, 13],
'col2': [12, 17, 31, 53, 22, 32, 43]
})
df
z_score=(df-df.mean())/df.std()
z_score
from sklearn.preprocessing import StandardScaler
sc=StandardScaler()
new=sc.fit_transform(df)
pd.DataFrame(new)
z_score.abs()<=2.2
df[(z_score.abs()<=2.2)].dropna()
df[(z_score.abs()<=2.2).all(1)]
# 识别异常值---箱线图
import seaborn as sns
sns.boxplot(df['col1'])
# 重复值
import pandas as pd # 导入pandas库
# 生成重复数据
data1, data2, data3, data4,data5 = ['a', 3], ['b', 2], ['a', 3], ['c', 2],['a', 4]
df = pd.DataFrame([data1, data2, data3, data4,data5], columns=['col1', 'col2'])
df
#判断
df.duplicated()
#去除重复值
df.drop_duplicates() #全部列都重复才算重复
df.drop_duplicates(['col1'])
df.drop_duplicates(['col2'])
df.drop_duplicates(['col1','col2'])
- 缺失值
- 缺失值判断
- pd.isnull(df)
- df.isnull()
- 缺失比例:df.isnull().sum()/df.shape[0]
- df.isnull.any()/all()
- 删除
- df.dropna()
- 填充
- sklearn SimpleImputer
- pandas df.fillna
- sklearn
-
nan_model=SimpleImputer(missing_values=np.nan,strategy='mean')#指定用每一列的均值来填充缺失值 new_df=nan_model.fit_transform(df)#返回array
-
- pandas
-
nan_result_pd1 = df.fillna(method='backfill') # 用后面的值替换缺失值 向上 nan_result_pd2 = df.fillna(method='bfill', limit=1) # 用后面的值替代缺失值,限制每列只能替代一个缺失值 nan_result_pd3 = df.fillna(method='pad') # 用前面的值替换缺失值 向下 nan_result_pd4 = df.fillna(0) # 用0替换缺失值 指定值填充 nan_result_pd5 = df.fillna({'col2': 1.1, 'col4': 1.2}) # 用不同值替换不同列的缺失值 字典填充 nan_result_pd6 = df.fillna(df.mean()['col2':'col4']) # 用平均数代替,选择各自列的均值替换缺失值 series填充
-
- 缺失值判断
- 异常值处理
- 判断异常
- z_score分数判断异常值
- z_score:表示一个值到均值的距离是几个标准差
- 超过多少个标准差就认为是异常值
-
def z_score(s,threshold=2.2): #自定义函数 对一列数据计算z_score并判断是都异常 z_s=(s-s.mean())/s.std() return z_s.abs()>threshold df.apply(z_score,axis=0)
- boxplot盒图形式判断异常
- 结合具体的业务场景
- z_score分数判断异常值
- 删除异常值–pandas逻辑判断
-
#删除异常值 df[df.apply(z_score,axis=0).any(axis=1)==False]
-
- 判断异常
- 数据去重
- 用到的API
dataframe.duplicated() # 判断重复数据记录
dataframe.drop_duplicates() # 删除数据记录中所有列值相同的记录 - 可以指定子集
-
#指定子集 df.drop_duplicates(subset='col2')#只看col2是否重复
- 用到的API
数值型数据的处理
标准化&&归一化
-
标准化(Z-Score)----x’=(x-mean)/std
原转换的数据为x,新数据为x′,
mean和std为x所在列的均值和标准差- 优点:适合大多数类型的数据,也是很多工具的默认标准化方法。标准化之后的数据是以0为均值,方差为1的正态分布
- 缺点:Z-Score方法是一种中心化方法,会改变原有数据的分布结构,不适合对稀疏数据做处理。
-
在原特征轴上进行标准化 ,异常值对总体值几乎没有影响。
-
有异常值,均值标准差受到影响相对较小,可视为忽视异常值影响,整体正常数据进行标准化。
-
归一化(Max-Min)----x’=(x-min)/(max-min)
原转换的数据为x,新数据为x′
min和max为x所在列的最小值和最大值- 优点:应用非常广泛,得到的数据会完全落入[0,1]区间内(Z-Score则没有类似区间)。这种方法能使数据归一化而落到一定的区间内,同时还能较好地保持原有数据结构。
- 受异常值影响 压缩之后的x1和x2方差不一致
- 有异常值,最大值最小值都受影响,总体数据的分布的相对位置没有改变。
preprocessing.StandardScaler()#标准化
preprocessing.MinMaxScaler()#归一化
问题:标准化和归一化只能够解决原特征的方差问题,实际上数据依然存在方差不一样的问题
from sklearn.preprocessing import StandardScaler,MinMaxScaler
#标准化
sc=StandardScaler()
sc_data=sc.fit_transform(data)
sc_data
#归一化
mm=MinMaxScaler()
mm_data=mm.fit_transform(data)
mm_data
pca降维
pca降维=轴旋转(线性变换)+特征选择
- pca轴旋转+标准化 就是将原数据的旋转和拉伸的线性变换复原,再去求欧氏距离==马氏距离
- pca之后再做标准化 :压缩的就是真正的长轴
- 马氏距离
#pca降维
from sklearn.decomposition import PCA
pca=PCA(n_components=2)
pca_data=pca.fit_transform(data)
pca_data
#pca+标准化
sc2=StandardScaler()
sc_pca_data=sc2.fit_transform(pca_data)
sc_pca_data
离散化/分箱/分桶----将数据简化
-
离散化的作用
- 提高计算效率
- 算法需要
- 防止过拟合— 将异常信息淹没在箱子中
- 过拟合—模型过于复杂
- 欠拟合–模型过于简单
- 注意:离散化做的太厉害,有可能导致欠拟合
-
应用场景
- 时间离散化 时间— 周 月 年
- 对多值类别数据进行离散化 原来的类别太多了
- 想象一下一个特征有2000个不同的类别值,做onehot结果是2000列
- 连续数据的离散化
-
离散化技术
- 分位数法 --df.describe()
- 等宽分箱—区间相等pd.cut
- 自定义分箱 — 自己定义区间pd.cut(data,bins)
- 等频分箱 等深分箱 --分好之后每一箱数量相等pd.qcut
- 二值化:Binarizer().fit_predict()
- 聚类法—用kmeans对数据进行聚类,聚类结果就是类别
- km.fit-predict()
- 本质是特征构造方法 机器学习领域经常使用该方法构造新特征
- sklearn中的类接受的数据必须是二维
-
应用场景
- 时间数据的离散化 时间—>周 月 年
- 针对多值离散数据的离散化 :已经离散化的数据再次离散化
- 朝向、 户型
- 针对连续数据的离散化 最常用的场景
- 价格、 打分人数
-
离散化案例
-
- 时间数据离散化:利用dt对象提取周 年 月 日 小时
- 自定义分箱 pd.cut
- 聚类法—本质来讲是一个特征构造方法
- 可以对一列进行、 也可以对多列进行
- sklearn中的模型接收的x一定要是二维数组
- 等深分箱
- pd.qcut(data,bins,labels):指定labels可以显示标签
- 二值化
- Binarizer: 给一个阈值,大于阈值1 小于阈值0
#时间离散化
df.loc[:,'datetime']=pd.to_datetime(df['datetime'])
df.loc[:,'weekday']=df['datetime'].dt.weekday
df.head()
#自定义分箱
bins = [0, 200, 1000, 5000, 10000] # 自定义区间边界
df.loc[:,'amount1']=pd.cut(df['amount'],bins=bins)
df.head()
#等深分箱
df.loc[:,'amount2']=pd.qcut(df['amount'],4,labels=['bad', 'medium', 'good', 'awesome'])
df.head()
#聚类分箱
x=df[['amount']]#实际工作中 这里可以选取多列
#kmeans聚类
km=KMeans(n_clusters=4,random_state=9)
df.loc[:,'amount3']=km.fit_predict(x)
df.head()
#二值化
from sklearn.preprocessing import Binarizer
bn=Binarizer(threshold=df['amount'].mean())
df.loc[:,'amount4']=bn.fit_transform(df[['amount']])#二维
df.head()
分类数据的处理
-
模型类型
- 必须数值计算的模型:
- 线性回归 y=ax+b
- 逻辑回归 y=ax+b
- knn–计算距离
- kmeans–计算距离
- svm-- y=ax+b
- 不需要数值计算的模型(类别型数据可以直接处理的模型):
- 决策树-可以直接处理类别型数据
- 朴素贝叶斯—统计概率
- 必须数值计算的模型:
-
sklearn中,不管什么模型都必须接收数值型数据
- 原因:算法理论和代码实现之间的区别
-
离散类别数据处理思路
- 等距离散:做onehot
- 性别中的男和女 0,1
- 颜色中的红、黄和蓝
- 级别离散:能够分大小,基于统计数值化,直接1,2,3编码
- 用户的价值度分为高、中、低
- 学历分为博士、硕士、学士、大专、高中
更好的编码方式: 高3 中2 低1
更高级的方式
目标数据按照这个特征分组后统计平均值或者频数
高 — y的平均值 yes的频率
中— y的平均值 yes频率
低— y的平均值 yes频率
- 等距离散:做onehot
-
api
-
Onehotencoder 数值型和字符串类别型都会被onehot
-
pd.get_dummies 只对字符串类别型做onehot 数值型数据不做处理
-
DictVectorizer 只对字符串类别型做onehot
-
注意:pd.get_dummies和DictVectorizer区别是:
- pd.get_dummies是一次性的(是一个函数,不保存处理数据的参数) 意味着不能够对后续的数据做同样的处理
- 应用场景:数据挖掘中不需要对后续数据做预测,只是研究目标和特征的关系的时候
- DictVectorizer和Onehotencoder 是类(将处理需要的参数作为类的属性保存了),意味着可以对后续数据做同样的处理
- 在分训练集和测试集的时候
- 而且这些模型在建模完成之后一定要保存,因为将来还要对需要预测的数据进行同样的处理(包括前面学的标准化,归一化等等模型只要建了模,都要保存)
- pd.get_dummies是一次性的(是一个函数,不保存处理数据的参数) 意味着不能够对后续的数据做同样的处理
-
import pandas as pd # 导入pandas库
from sklearn.preprocessing import OneHotEncoder # 导入库
# 生成数据
df = pd.DataFrame({'id': [3566841, 6541227, 3512441],
'sex': ['male', 'Female', 'Female'],
'level': ['high', 'low', 'middle'],
'score': [1, 2, 3]})
df
#onehotencoder
oe=OneHotEncoder()
df2=oe.fit_transform(df[['level','score','sex']])
df2.toarray()
#看类别标签
oe.get_feature_names()
#pd.get_dummies
df3=pd.get_dummies(df[['level','score','sex']])
df3
#dict_vectorizer
from sklearn.feature_extraction import DictVectorizer
dc=DictVectorizer(sparse=False)
df3=dc.fit_transform(df[['level','score','sex']].to_dict(orient='records'))
df3
#获取特征
dc.get_feature_names()
时间类型数据的处理
- pd.to_datetime 转换成日期类型
- pd.date_range 生成日期序列
- series.dt对象
- datetime模块 strftime 格式化日期
- datetime-datetime=timedelta.dt.days 两个日期间隔的天数
##### --------仅供思路,暂无数据
#转换类型
car_sales['date_t']=pd.to_datetime(car_sales['date_t'])
#提取数据
car_sales.loc[:,'day']=car_sales['date_t'].dt.day
car_sales.loc[:,'month']=car_sales['date_t'].dt.month
car_sales.loc[:,'dayofyear']=car_sales['date_t'].dt.dayofyear
car_sales.loc[:,'dayofweek']=car_sales['date_t'].dt.dayofweek
car_sales.head()
样本类别分布不均衡
- 样本类别分布不均衡
- 主要存在与分类问题,(回归问题也存在,不明显)
- Yes:10000样本,no:200个样本
- 超过10倍引起警觉,20倍一定要解决
- 场景:异常检测、客户流失、癌症检测
- 处理方式
- 采样
- 上采样(over-sampling)过采样 把少的一类样本进行复制,变多
- SMOTE算法-线性插值 不是简单复制
- 上采样(over-sampling)过采样 把少的一类样本进行复制,变多
- 采样
smote=SMOTE()
x_smote,y_smote=smote.fit_sample(df[x_cols],df[y_cols])
- 下采样(under-sampling)欠采样 对多类的样本进行抽样,变少
- 损失函数类中给类别加权重 —少类样本损失大 多类样本损失少 等价于复制样本(上采样)
- 很多算法有这个参数 class_weight类别权重
- scikit-learn中的逻辑回归为例,通过在class_weight:{dict,‘balanced’}中针对不同类别来手动指定权重,如果使用其默认的方法balanced,那么逻辑回归会将权重设置为与不同类别样本数量呈反比的权重来进行自动均衡处理,计算公式如下:n_samples/(n_classes*np.bincount(y))
ru=RandomUnderSampler()
x_ru,y_ru=ru.fit_sample(df[x_cols],df[y_cols])
-
损失函数类别权重— 等价于直接复制
-
class_weight:{dict,‘balanced’}
- 自动计算 根据比例不同设置权重
-
集成思想
-
选择对样本不平衡不敏感的算法
- 随机森林、svm
-
当少类样本非常少的时候,将分类问题—>异常检测问题(无监督)
- one-class-svm
- GMM高斯混合模型
- isolation forest 孤立森林
Python处理样本不均衡案例
- pip install imblearn
- 样本不平衡的时候训练模型存下的问题,全猜0
import pandas as pd
from imblearn.over_sampling import SMOTE # 过抽样处理库SMOTE
from imblearn.under_sampling import RandomUnderSampler # 欠抽样处理库RandomUnderSampler
# 导入数据文件
df = pd.read_table('data/data2.txt', sep=' ',names=['col1', 'col2', 'col3', 'col4', 'col5', 'label']) # 读取数据文件
df.head()
smote上采样
smote=SMOTE()#实例化对象
x_sample,y_sample=smote.fit_sample(x,y)
- 一般是只对训练集做上采样,测试集不做
- 复制样本等价于class_weight
- smote不是复制样本,是线性插值
#分离x和y
x_cols=['col1', 'col2', 'col3', 'col4', 'col5']
y_cols='label'
#实例化对象
smote=SMOTE()
x_smote,y_smote=smote.fit_sample(df[x_cols],df[y_cols])
#封装成dataframe
x_smote_df=pd.DataFrame(x_smote,columns=x_cols)
y_smote_df=pd.DataFrame(y_smote,columns=[y_cols])
#拼接
df2=pd.concat([x_smote_df,y_smote_df],axis=1)
df2.head()
#采样后的样本分布
df2['label'].value_counts()
RandomUnderSampler
欠采样
-
- 将样本比较多的一类进行抽样,保留跟少类样本个数一样的样本 把多的变少
- 少类样本的样本数本身并不少
- 0:100000 1:5000 —> 0:5000 1:5000
#实例化对象
ru=RandomUnderSampler()
x_ru,y_ru=ru.fit_sample(df[x_cols],df[y_cols])
#封装成dataframe
x_ru_df=pd.DataFrame(x_ru,columns=x_cols)
y_ru_df=pd.DataFrame(y_ru,columns=[y_cols])
#拼接
df3=pd.concat([x_ru_df,y_ru_df],axis=1)
df3.head()
#采样后的样本分布
df3['label'].value_counts()
数据抽样
- 抽样原因
- 减小数据量、数据采集限制、 时效性
- 数据抽样的现实意义
- 验证概念、解决样本不均衡问题、解决无法覆盖全量样本的情况
- 抽样方法
- 非概率抽样–主观抽样
- 概率抽样–等概率
- 简单随机抽样、等距抽样、分层抽样
- 抽样需要注意的问题
- 反映运营背景
- 数据抽样要能满足数据分析和建模需求— 10052
- 抽样样本在不同类别中的分布问题— 分布保证跟全量样本一致
- 异常检测类数据的处理—异常样本不要抽样 全部拿来
- 抽样api
- 随机抽样
np.random.choice(arr,20)#有放回
np.random.choice(arr,5,replace=False)#无放回
- arr:抽样的集合 一维数组
- size:抽样大小
- replace: True:有放回 False:无放回
-
等距抽样
- data[::width].shape 一行代码搞定等距抽样
-
分层抽样— 男的抽多少,女的抽多少
-
注意问题
- 运营问题
- 时效性/无偏性
- 模型需求
- 时间为维度分布的,至少包含一个能满足预测的完整业务周期
- 数据量要保证: 数据量=100特征数特征的值种类数
- 各类别抽样要平衡
- 抽样分布要和总体分布保持一致
- 均值/ 比例/方差/ 缺失值比例/ 异常值比例
- 异常检测数据抽样问题
- 异常样本不抽样 ,全部保留
- 运营问题
np.random.choice(arr,20)#有放回
np.random.choice(arr,5,replace=False)#无放回
#有放回抽样1000个
sample_index=np.random.choice(np.arange(10000),1000)
data[sample_index].shape
#等距抽样
#一般先要shuffle 打乱
data[::10].shape