#读入数据
import pandas as pd
import numpy as np
from sklearn.preprocessing import OrdinalEncoder
from sklearn.ensemble import IsolationForest
raw_data = pd.read_csv('./outlier.txt',sep=',')
print('shape:',raw_data.shape)
raw_data.head()
#out: shape: (10492, 47)
看一下数据的info()和describe()
raw_data.info()
raw_data.describe()
发现有缺失值,和方差为0的项
#全为NaN的列
nanlist1 = raw_data.columns[np.all(raw_data.isna(),axis=0)]
#包括Nan的列
nanlist2 = raw_data.columns[np.any(raw_data.isna(),axis=0)]
#删除全为NaN的列
df1 = raw_data.drop(columns=nanlist1)
#填充包括Nan的列
df2 = df1.fillna({'newVisits': 0, 'pageviews': 0, 'isVideoAd': False, 'isTrueDirect': False})
#删除方差为0的列
#std=0的列
zero_std = df2.describe().columns[df2.describe().loc['std',:]==0]
#Out: Index(['visits'], dtype='object')
df3 = df2.drop(columns=zero_std)
df3.shape
#out: (10492, 44)
删除了全为空和方差为零的特征,同时做了Nan的填充,再填充的时候我们需要查看缺失列的数据分布,再尝试填充。
#查看应该填充什么样的缺失值
raw_data['isVideoAd'].value_counts()
#False 122
# Name: isVideoAd, dtype: int64
为了使用ISOlation Forest检测异常点,首先需要将离散的特征值用OrdinalEncoder替换成数值型。
#取出dtype=='object'的列
str_cols = df3[df3.columns[df3.dtypes=='object']]
##取出dtype!='object'的列
num_cols = df3[df3.columns[df3.dtypes!='object']]
# 分类特征转换为数值型索引
model_oe = OrdinalEncoder()
string_data_con = model_oe.fit_transform(str_cols)
string_data_pd = pd.DataFrame(string_data_con,columns=str_cols.columns)
#删除方差为0的列
#std=0的列
str_zero_std = string_data_pd.describe().columns[string_data_pd.describe().loc['std',:]==0]
# OUt: Index(['socialEngagementType'], dtype='object')
string_data_pd=string_data_pd.drop('socialEngagementType',axis=1)
#合并str和nums 列
df_merge = pd.concat((num_cols,string_data_pd),axis=1).drop('clientId',axis=1)
#使用ISOlation Forest检测异常点
IF_model = IsolationForest(n_estimators=1000,n_jobs=-1)
outlier_lable = IF_model.fit_predict(df_merge)
# IsolationForest(behaviour='deprecated', bootstrap=False, contamination='auto',
# max_features=1.0, max_samples='auto', n_estimators=1000,
# n_jobs=-1, random_state=None, verbose=0, warm_start=False)
再初始DF中添加是否异常特征
#添加是否异常列
df3['isout']=outlier_lable
df3['isout'].value_counts()
#OUT:
# 1 8813
# -1 1679
# Name: isout, dtype: int64
可以看到IF分出的异常点占比还挺大的。
接下来按照渠道对数据分类并聚合得到各个渠道的异常比例
isnormal = df3[df3['isout']==1].groupby('source',as_index=False)[['isout']].count()
notnormal= df3[df3['isout']==-1].groupby('source',as_index=False)[['isout']].count()
nor_df= pd.merge(notnormal,isnormal,how='outer',on='source')
nor_df = nor_df.fillna(0)
#统计每个渠道的异常情况
df_source = df3.groupby('source',as_index=False)[['isout']].count().sort_values('isout',ascending=False)
df_source= pd.merge(nor_df,df_source,how='outer',on='source')
df_source['outlier_rate'] = df_source['isout_x']/df_source['isout']
df_source = df_source.sort_values('isout',ascending=False)
df_source

#计算总体平均异常点击概率
mean_outrate = df_source.sum()['isout_x']/df_source.sum()['isout']
# 0.1598360655737705
#点击大于10,异常小于平均(good)
df_source.query('isout>=10 & outlier_rate<0.1598360655737705')

#点击大于10,异常大于平均(bad)
df_source.query('isout>=10 & outlier_rate>=0.1598360655737705')

由于样本没有明确的标签值,所以我们通过算法判断出来的结果不一定可靠,但可以作为一个参考。