老实说,看懂这一篇我还真走了不少弯路,即使所有思路理通了,也担心有些差错,希望读者能找出我理解的一些不当并评论告知我,提前说一声谢谢!!
接下来我就对kaggle上的这篇文章的数据预处理的每一步进行较为详细的解说:
1.在加载数据前,先构造一个优化运行内存的函数
def reduce_mem_usage(df, verbose=True):
numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']
start_mem = df.memory_usage().sum() / 1024**2
for col in df.columns:
col_type = df[col].dtypes
if col_type in numerics:
c_min = df[col].min()
c_max = df[col].max()
if str(col_type)[:3] == 'int':
if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
df[col] = df[col].astype(np.int8)
elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
df[col] = df[col].astype(np.int16)
elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
df[col] = df[col].astype(np.int32)
elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:
df[col] = df[col].astype(np.int64)
else:
if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
df[col] = df[col].astype(np.float16)
elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
df[col] = df[col].astype(np.float32)
else:
df[col] = df[col].astype(np.float64)
end_mem = df.memory_usage().sum() / 1024**2
if verbose: print('Mem. usage decreased to {:5.2f} Mb ({:.1f}% reduction)'.format(end_mem, 100 * (start_mem - end_mem) / start_mem))
return df
这一个函数我们可以看到它对数据进行多步的判断,主要是从小到大地去确定它的值,为啥呢?我们知道,存储数据是以8位或16位或32以及64位去存储,那怎样才能降低运行开销呢?那就是每一个数据单元或每一组数据的存储位数最小,而我们要确定数值在哪个范围内,尽量用最小的位数去存储数据,这就是该函数的原理。
2.加载数据
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import lightgbm as lgb
from sklearn.model_selection import KFold
import warnings
import gc
import time
import sys
import datetime
import matplotlib.pyplot as plt
import seaborn as sns
from tqdm import tqdm
from sklearn.metrics import mean_squared_error
warnings.simplefilter(action='ignore', category=FutureWarning)
from sklearn import metrics
# Plotly library
from plotly.offline import init_notebook_mode, iplot
import plotly.graph_objs as go
from plotly import tools
init_notebook_mode(connected=True)
pd.set_option('display.max_columns', 500)
以上是所需包的导入,和提前的定义,warnings.simplefilter(action=‘ignore’, category=FutureWarning)是数据过滤的出错警告的设置,init_notebook_mode(connected=True)的意思是说进行离线加载
pd.set_option(‘display.max_columns’, 500)这一句只是对显示的设置,我们不做太多解释。
dtypes = {
'MachineIdentifier': 'category',
'ProductName': 'category',
'EngineVersion': 'category',
'AppVersion': 'category',
'AvSigVersion': 'category',
'IsBeta': 'int8',
'RtpStateBitfield': 'float16',
'IsSxsPassiveMode': 'int8',
'DefaultBrowsersIdentifier': 'float16',
'AVProductStatesIdentifier': 'float32',
'AVProductsInstalled': 'float16',
'AVProductsEnabled': 'float16',
'HasTpm': 'int8',
'CountryIdentifier': 'int16',
'CityIdentifier': 'float32',
'OrganizationIdentifier': 'float16',
'GeoNameIdentifier': 'float16',
'LocaleEnglishNameIdentifier': 'int8',
'Platform': 'category',
'Processor': 'category',
'OsVer': 'category',
'OsBuild': 'int16',
'OsSuite': 'int16',
'OsPlatformSubRelease': 'category',
'OsBuildLab': 'category',
'SkuEdition': 'category',
'IsProtected': 'float16',
'AutoSampleOptIn': 'int8',
'PuaMode': 'category',
'SMode': 'float16',
'IeVerIdentifier': 'float16',
'SmartScreen': 'category',
'Firewall': 'float16',
'UacLuaenable': 'float32',
'Census_MDC2FormFactor': 'category',
'Census_DeviceFamily': 'category',
'Census_OEMNameIdentifier': 'float16',
'Census_OEMModelIdentifier': 'float32',
'Census_ProcessorCoreCount': 'float16',
'Census_ProcessorManufacturerIdentifier': 'float16',
'Census_ProcessorModelIdentifier': 'float16',
'Census_ProcessorClass': 'category',
'Census_PrimaryDiskTotalCapacity': 'float32',
'Census_PrimaryDiskTypeName': 'category',
'Census_SystemVolumeTotalCapacity': 'float32',
'Census_HasOpticalDiskDrive': 'int8',
'Census_TotalPhysicalRAM': 'float32',
'Census_ChassisTypeName': 'category',
'Census_InternalPrimaryDiagonalDisplaySizeInInches': 'float16',
'Census_InternalPrimaryDisplayResolutionHorizontal': 'float16',
'Census_InternalPrimaryDisplayResolutionVertical': 'float16',
'Census_PowerPlatformRoleName': 'category',
'Census_InternalBatteryType': 'category',
'Census_InternalBatteryNumberOfCharges': 'float32',
'Census_OSVersion': 'category',
'Census_OSArchitecture': 'category',
'Census_OSBranch': 'category',
'Census_OSBuildNumber': 'int16',
'Census_OSBuildRevision': 'int32',
'Census_OSEdition': 'category',
'Census_OSSkuName': 'category',
'Census_OSInstallTypeName': 'category',
'Census_OSInstallLanguageIdentifier': 'float16',
'Census_OSUILocaleIdentifier': 'int16',
'Census_OSWUAutoUpdateOptionsName': 'category',
'Census_IsPortableOperatingSystem': 'int8',
'Census_GenuineStateName': 'category',
'Census_ActivationChannel': 'category',
'Census_IsFlightingInternal': 'float16',
'Census_IsFlightsDisabled': 'float16',
'Census_FlightRing': 'category',
'Census_ThresholdOptIn': 'float16',
'Census_FirmwareManufacturerIdentifier': 'float16',
'Census_FirmwareVersionIdentifier': 'float32',
'Census_IsSecureBootEnabled': 'int8',
'Census_IsWIMBootEnabled': 'float16',
'Census_IsVirtualDevice': 'float16',
'Census_IsTouchEnabled': 'int8',
'Census_IsPenCapable': 'int8',
'Census_IsAlwaysOnAlwaysConnectedCapable': 'float16',
'Wdft_IsGamer': 'float16',
'Wdft_RegionIdentifier': 'float16',
'HasDetections': 'int8'
}
这一步是文件数据提取的参数之一,我们在数据文件(样本文件)中已经排好了整理好了数据,是一个行列矩阵,行是样本,而列则是特征,列首是特征的名称。而这一步的有两列,第一列就对应着数据文件中的所有列的列首名称,也就是特征名称,第二列则是每一列下的值的类型。
numerics = ['int8', 'int16', 'int32', 'int64', 'float16', 'float32', 'float64']
numerical_columns = [c for c,v in dtypes.items() if v in numerics]
categorical_columns = [c for c,v in dtypes.items() if v not in numerics]
读取文件前先对数据类型进行一个分类(numerical_columns和categorical_columns)。
nrows = 4000000
#_______________________________________________________________________________
retained_columns = numerical_columns + categorical_columns
train = pd.read_csv('../input/train.csv',
nrows = nrows,
usecols = retained_columns,
dtype = dtypes)
#_______________________________________________________________
retained_columns += ['MachineIdentifier']
retained_columns.remove('HasDetections')
test = pd.read_csv('../input/test.csv',
usecols = retained_columns,
dtype = dtypes)
这一步便是读取数据,nrows的定义是为了限制读取行数,因为样本太多,所以显示4000000个就足够了。上面的retained_columns.remove(‘HasDetections’)是剔除其中的HasDetections这个特征,为什么呢?因为这是测试集,既然用来测试,那便不需要答案嘛。
虽说我们前面提前对数据类型进行了分类,但其实这不够准确,因为许多数值型的数据大多对应着标识符,也就是说,数值型这块分的不好,里面其实有大多是category的类型,所以得进行更精确的分析。
true_numerical_columns = [
'Census_ProcessorCoreCount',
'Census_PrimaryDiskTotalCapacity',
'Census_SystemVolumeTotalCapacity',
'Census_TotalPhysicalRAM',
'Census_InternalPrimaryDiagonalDisplaySizeInInches',
'Census_InternalPrimaryDisplayResolutionHorizontal',
'Census_InternalPrimaryDisplayResolutionVertical',
'Census_InternalBatteryNumberOfCharges'
]
首先上面的便是真正完全的数值类型(为原代码作者手动分析出来)。
binary_variables = [c for c in train.columns if train[c].nunique() == 2]
其次我们再分出一个二进制型,实际上就是判断类型,为什么分出这个呢,原文的解说是这种类型可直接被识别。(那就说这一个类型是从真数值类型和category类型中找出符合的样本组成的一个样本集)
categorical_columns = [c for c in train.columns
if (c not in true_numerical_columns) & (c not in binary_variables)]
之后便把剩下的全部定义位category类型。
下面这幅图便是三种类型的占比:
cardinality = []
for c in categorical_columns:
if c == 'MachineIdentifier': continue
cardinality.append([c, train[c].nunique()])
cardinality.sort(key = lambda x:x[1], reverse=False)
trace = go.Bar(y=[x[0] for x in cardinality],
x=[x[1] for x in cardinality],
orientation='h', marker=dict(color='rgb(49,130,189)'), name='train')
layout = go.Layout(
title='Categorical cardinality', height=1600, width=800,
xaxis=dict(
title='Number of categories',
titlefont=dict(size=16, color='rgb(107, 107, 107)'),
domain=[0.25, 1]
),
barmode='group',
bargap=0.1,
bargroupgap=0.1
)
fig = go.Figure(data=[trace], layout=layout)
iplot(fig)
以上是为了用可视化方式显示数据的基数的,因为基数的大小不同有不同的适应方法。
下面这幅图是结果:
进行了数据的基本分析后,接下来就是进入特征工程,也就是对数据的各个特征进行删减和休整,毕竟加载出来的数据集一般都会有缺失值,输入错误,含有无关特征和相关特征等问题。故而进行数据集特征优化是必经的过程。上文说过特征基数的大小有不同的特征优化方式
一下这部分是对基数较大的特征的优化(基数:这里可以理解为是特征值的多样性,就是说,特征值不同的个数较多的特征,其中‘个数’便是所谓的基数,泛化的讲就是集合的元素个数)
def frequency_encoding(variable):
t = pd.concat([train[variable], test[variable]]).value_counts().reset_index()
t = t.reset_index()
t.loc[t[variable] == 1, 'level_0'] = np.nan
t.set_index('index', inplace=True)
max_label = t['level_0'].max() + 1
t.fillna(max_label, inplace=True)
return t.to_dict()['level_0']
frequency_encoded_variables = [
'Census_OEMModelIdentifier',
'CityIdentifier',
'Census_FirmwareVersionIdentifier',
'AvSigVersion',
'Census_ProcessorModelIdentifier',
'Census_OEMNameIdentifier',
'DefaultBrowsersIdentifier'
]
for variable in tqdm(frequency_encoded_variables):
freq_enc_dict = frequency_encoding(variable)
train[variable] = train[variable].map(lambda x: freq_enc_dict.get(x, np.nan))
test[variable] = test[variable].map(lambda x: freq_enc_dict.get(x, np.nan))
categorical_columns.remove(variable)
定义frequency_encoding()函数的作用就是编码,当然完整的编码完成是配合第三步的,至于这代码的实现的还需我们自己去操作一下,才能较好的明白其中的原理(自己随便造一个矩阵,用这其中的代码进行操作,相似的操作,每步都输出一次,就可以很清楚的了解其中的编码原理)。
indexer = {}
for col in tqdm(categorical_columns):
if col == 'MachineIdentifier': continue
_, indexer[col] = pd.factorize(train[col])
for col in tqdm(categorical_columns):
if col == 'MachineIdentifier': continue
train[col] = indexer[col].get_indexer(train[col])
test[col] = indexer[col].get_indexer(test[col])
前面已经处理了基数较大的特征,那么上面这一步就是处理基数较小的特征。
然后就用一开始的减存函数进行减存就行啦
train = reduce_mem_usage(train)
test = reduce_mem_usage(test)
之后就是进行模型训练啦!
对于这篇的数据预处理:基本情况就是把电脑能直接识别类型分类出来,然后把其中需要进行编码的category类型进行编码,但这些说多的对理解的帮助还不如自己用代码操作一遍。
以上就是这一篇的数据预处理全部啦!