最近博主在作个 kaggle 竞赛,有个 Kernel 的数据探索分析很是值得借鉴,博主也学习了一波操做,搬运过来借鉴,原连接以下:程序员
https://www.kaggle.com/willkoehrsen/start-here-a-gentle-introduction安全
数据由Home Credit提供,该服务致力于向无银行帐户的人群提供信贷(贷款)。预测客户是否偿还贷款或遇到困难是一项重要的业务需求,Home Credit将在Kaggle上举办此类竞赛,以了解机器学习社区能够开展哪些模式以帮助他们完成此任务。app
有7种不一样的数据来源:机器学习
application_train / application_test:函数
主要的培训和测试数据以及关于Home Credit每一个贷款申请的信息。每笔贷款都有本身的行,并由功能SK_ID_CURR标识。培训申请数据附带TARGET表示0:贷款已还清或1:贷款未还清。学习
bureau:测试
有关客户以前来自其余金融机构的信贷的数据。之前的每一笔信贷都有本身的分行,但申请数据中的一笔贷款可能有多笔先前信贷。编码
bureau_balance:spa
关于主管局之前信用的月度数据。每一行都是上一个信用的一个月,而且一个先前的信用能够有多个行,每一个信用额度的每月有一个行。code
previous_application:
以前在申请数据中拥有贷款的客户的Home Credit贷款申请。申请数据中的每一个当前贷款均可以有多个之前的贷款。每一个之前的应用程序都有一行,并由功能SK_ID_PREV标识。
POS_CASH_BALANCE:关于客户之前的销售点或现金贷款与住房贷款有关的每个月数据。每一行都是前一个销售点或现金贷款的一个月,之前的一笔贷款能够有多行。
credit_card_balance:
有关以前的信用卡客户与Home Credit有关的每个月数据。每行都是信用卡余额的一个月,一张信用卡能够有多行。
installments_payment:
Home Credit之前贷款的付款记录。每笔付款都有一行,每笔未付款都有一行。
下图显示了全部数据是如何相关的:
# numpy and pandas for data manipulation
import numpy as np
import pandas as pd
# sklearn preprocessing for dealing with categorical variables
from sklearn.preprocessing import LabelEncoder
# File system manangement
import os
# Suppress warnings
import warnings
warnings.filterwarnings('ignore')
# matplotlib and seaborn for plotting
import matplotlib.pyplot as plt
import seaborn as sns
app_train = pd.read_csv('../input/application_train.csv')
app_test = pd.read_csv('../input/application_test.csv')
app_train['TARGET'].value_counts()
app_train['TARGET'].astype(int).plot.hist();
根据这些信息,咱们发现这是一个不平衡的课堂问题。 按时还款的贷款远远多于未还款的贷款。 一旦咱们进入更复杂的机器学习模型,咱们能够经过它们在数据中的表示来对类进行加权以反映这种不平衡。
# Function to calculate missing values by column# Funct
def missing_values_table(df):
# Total missing values
mis_val = df.isnull().sum()
# Percentage of missing values
mis_val_percent = 100 * df.isnull().sum() / len(df)
# Make a table with the results
mis_val_table = pd.concat([mis_val, mis_val_percent], axis=1)
# Rename the columns
mis_val_table_ren_columns = mis_val_table.rename(
columns = {0 : 'Missing Values', 1 : '% of Total Values'})
# Sort the table by percentage of missing descending
mis_val_table_ren_columns = mis_val_table_ren_columns[
mis_val_table_ren_columns.iloc[:,1] != 0].sort_values(
'% of Total Values', ascending=False).round(1)
# Print some summary information
print ("Your selected dataframe has " + str(df.shape[1]) + " columns.\n"
"There are " + str(mis_val_table_ren_columns.shape[0]) +
" columns that have missing values.")
# Return the dataframe with missing information
return mis_val_table_ren_columns
# Missing values statistics
missing_values = missing_values_table(app_train)
missing_values.head(20)
当须要创建咱们的机器学习模型时,咱们将不得不填写这些缺失值(称为插补)。 在之后的工做中,咱们将使用XGBoost等模型,能够处理缺失值而不须要插补。 另外一种选择是删除缺失值比例较高的列,但若是这些列对咱们的模型有帮助,则不可能提早知道。 所以,咱们如今将保留全部列。
# Number of each type of column
app_train.dtypes.value_counts()
如今咱们来看看每一个对象(分类)列中惟一条目的数量。
app_train.select_dtypes('object').apply(pd.Series.nunique, axis = 0)
大多数分类变量的惟一条目数量相对较少。 咱们须要找到一种方法来处理这些分类变量!
在咱们继续前进以前,咱们须要处理讨厌的分类变量。 不幸的是,机器学习模型不能处理分类变量(除了LightGBM等一些模型)。 所以,咱们必须找到一种将这些变量编码(表示)为数字的方式,而后再将它们交给模型。 有两种主要的方法来执行这个过程:
标签编码的问题在于它给了类别一个任意的顺序。 分配给每一个类别的值是随机的,并不反映该类别的任何固有方面。 在上面的例子中,程序员收到一个4和数据科学家a 1,但若是咱们再次执行相同的过程,标签可能会颠倒或彻底不一样。 整数的实际赋值是任意的。 所以,当咱们执行标签编码时,模型可能会使用特征的相对值(例如程序员= 4和数据科学家= 1)来分配不是咱们想要的权重。 若是咱们对于分类变量(例如男性/女性)只有两个惟一值,那么标签编码很好,但对于超过2个独特类别,单热编码是安全的选择。
关于这些方法的相对优势存在一些争议,而且一些模型能够处理标签编码的分类变量而没有问题。 这是一个很好的Stack Overflow讨论。 我认为(这只是一种我的观点)对于有不少类的分类变量,单热编码是最安全的方法,由于它不会对类别强加任意值。 单热编码惟一的缺点是特征的数量(数据的维数)会随着分类变量的分类而爆炸。 为了解决这个问题,咱们能够执行一个热门的编码,而后是 PCA 或其余降维方法,以减小维数(同时仍试图保留信息)。
咱们将使用标签编码来处理任何仅有2个类别的分类变量,对于超过2个类别的任何分类变量使用单向编码。
让咱们实现上面描述的策略:对于具备2个惟一类别的任何分类变量(dtype ==对象),咱们将使用标签编码,对于具备多于2个惟一类别的任何分类变量,咱们将使用单热编码。
对于标签编码,咱们使用Scikit-Learn LabelEncoder和一个热门编码,即熊猫get_dummies(df)函数。
# Create a label encoder object
le = LabelEncoder()
le_count = 0
# Iterate through the columns
for col in app_train:
if app_train[col].dtype == 'object':
# If 2 or fewer unique categories
if len(list(app_train[col].unique())) <= 2:
# Train on the training data
le.fit(app_train[col])
# Transform both training and testing data
app_train[col] = le.transform(app_train[col])
app_test[col] = le.transform(app_test[col])
# Keep track of how many columns were label encoded
le_count += 1
print('%d columns were label encoded.' % le_count)
app_train = pd.get_dummies(app_train)
app_test = pd.get_dummies(app_test)
print('Training Features shape: ', app_train.shape)
print('Testing Features shape: ', app_test.shape)
训练和测试数据中都须要具备相同的特征(列)。 一次性编码在训练数据中建立了更多列,由于有一些分类变量的类别未在测试数据中表示。 要删除训练数据中不在测试数据中的列,咱们须要对齐数据框。 首先,咱们从训练数据中提取目标列(由于这不在测试数据中,但咱们须要保留这些信息)。 当咱们进行对齐时,咱们必须确保将axis = 1设置为基于列而不是在行上对齐数据框!
train_labels = app_train['TARGET']
# Align the training and testing data, keep only columns present in both dataframes
app_train, app_test = app_train.align(app_test, join = 'inner', axis = 1)
# Add the target back in
app_train['TARGET'] = train_labels
print('Training Features shape: ', app_train.shape)
print('Testing Features shape: ', app_test.shape)
训练和测试数据集如今具备机器学习所需的相同特征。 因为单热编码,功能数量显着增长。 在某些状况下,咱们可能会尝试降维(删除不相关的功能)以减少数据集的大小。
在探索数据的时候须要注意的一个问题是数据中的异常状况。 这多是因为错误的数字,测量设备的错误,或者它们多是有效的,但极端的测量。 定量支持异常的一种方法是使用描述方法查看列的统计数据。 DAYS_BIRTH列中的数字是负数,由于它们是相对于当前贷款申请记录的。 要查看这些年份的统计数据,咱们能够用-1来多项式除以一年中的天数:
(app_train['DAYS_BIRTH'] / -365).describe()
那些年龄看起来合理。 高端或低端的年龄没有异常值。 就业日期如何?
app_train['DAYS_EMPLOYED'].describe()
这看起来不正确! 最大值(除了正值)大约是1万年!
app_train['DAYS_EMPLOYED'].plot.hist(title = 'Days Employment Histogram');
plt.xlabel('Days Employment');
出于好奇,让咱们对异常客户进行分类,看看他们是否倾向于比其余客户的违约率更高或更低。
anom = app_train[app_train['DAYS_EMPLOYED'] == 365243]
non_anom = app_train[app_train['DAYS_EMPLOYED'] != 365243]
print('The non-anomalies default on %0.2f%% of loans' % (100 * non_anom['TARGET'].mean()))
print('The anomalies default on %0.2f%% of loans' % (100 * anom['TARGET'].mean()))
print('There are %d anomalous days of employment' % len(anom))
很是有趣! 事实证实,异常状况的违约率较低。
处理异常状况取决于具体状况,没有设置规则。 最安全的方法之一就是将异常设置为缺失值,而后在机器学习以前填充(使用插补)。 在这种状况下,因为全部的异常都具备彻底相同的价值,咱们但愿用相同的价值填充它们以防全部这些贷款共享相同的东西。 异常值彷佛有一些重要性,因此咱们想告诉机器学习模型,若是咱们确实填写了这些值。 做为一个解决方案,咱们将用不是一个数字(np.nan)填充异常值,而后建立一个新的布尔列来指示值是否异常。
# Create an anomalous flag column
app_train['DAYS_EMPLOYED_ANOM'] = app_train["DAYS_EMPLOYED"] == 365243
# Replace the anomalous values with nan
app_train['DAYS_EMPLOYED'].replace({365243: np.nan}, inplace = True)
app_train['DAYS_EMPLOYED'].plot.hist(title = 'Days Employment Histogram');
plt.xlabel('Days Employment');
这个分布看起来更符合咱们的预期,并且咱们还建立了一个新的列,告诉模型这些值最初是异常的(由于咱们将不得不用一些值填补nans,多是中值的列)。 数据框中DAYS的其余列看起来与咱们所指望的没有明显的异常值有关。
做为一个很是重要的笔记,咱们对训练数据所作的任何事情,咱们也必须对测试数据进行处理。 让咱们确保建立新列,并在测试数据中用np.nan填充现有列。
app_test['DAYS_EMPLOYED_ANOM'] = app_test["DAYS_EMPLOYED"] == 365243
app_test["DAYS_EMPLOYED"].replace({365243: np.nan}, inplace = True)
print('There are %d anomalies in the test data out of %d entries' % (app_test["DAYS_EMPLOYED_ANOM"].sum(), len(app_test)))
如今咱们已经处理了分类变量和异常值, 尝试和理解数据的一种方法是经过查找特征和目标之间的相关性。 咱们可使用.corr数据框方法计算每一个变量和目标之间的 Pearson 相关系数。
相关系数并非表示某个要素“相关性”的最佳方法,但它确实给了咱们一个关于数据内可能关系的想法。 对相关系数绝对值的一些通常解释是:
# Find correlations with the target and sort
correlations = app_train.corr()['TARGET'].sort_values()
# Display correlations
print('Most Positive Correlations:\n', correlations.tail(15))
print('\nMost Negative Correlations:\n', correlations.head(15))
咱们来看看更重要的一些相关性:DAYS_BIRTH是最正相关的。 (TARGET除外,由于变量与其自己的相关性始终为1!)查看文档,DAYS_BIRTH是负很多天期内客户在贷款时间的天数(不管出于何种缘由!)。 这种相关性是正向的,可是这个特征的价值其实是负的,这意味着随着客户年龄的增加,他们不太可能违约(即目标== 0)。 这有点使人困惑,所以咱们将采用该特征的绝对值,而后相关性将为负值。
# Find the correlation of the positive days since birth and target
app_train['DAYS_BIRTH'] = abs(app_train['DAYS_BIRTH'])
app_train['DAYS_BIRTH'].corr(app_train['TARGET'])
随着客户年龄的增加,与目标意义呈负相关关系,即随着客户年龄的增加,他们每每会更常常地按时偿还贷款。咱们开始看这个变量。 首先,咱们能够制做年龄的直方图。 咱们将把x轴放在几年,使情节更容易理解。
# Set the style of plots
plt.style.use('fivethirtyeight')
# Plot the distribution of ages in years
plt.hist(app_train['DAYS_BIRTH'] / 365, edgecolor = 'k', bins = 25)
plt.title('Age of Client'); plt.xlabel('Age (years)'); plt.ylabel('Count');
就其自己而言,年龄的分布并无告诉咱们不少,除了全部年龄都是合理的,没有异常值。 为了观察年龄对目标的影响,咱们接下来将制做一个核心密度估计图(KDE),该图由目标值着色。 核密度估计图显示单个变量的分布,能够认为是平滑直方图(它是经过在每一个数据点上计算内核(一般为高斯)建立的,而后对全部单个内核进行平均以造成单个平滑 曲线)。 咱们将为此图使用 seaborn kdeplot。
plt.figure(figsize = (10, 8))
# KDE plot of loans that were repaid on time
sns.kdeplot(app_train.loc[app_train['TARGET'] == 0, 'DAYS_BIRTH'] / 365, label = 'target == 0')
# KDE plot of loans which were not repaid on time
sns.kdeplot(app_train.loc[app_train['TARGET'] == 1, 'DAYS_BIRTH'] / 365, label = 'target == 1')
# Labeling of plot
plt.xlabel('Age (years)'); plt.ylabel('Density'); plt.title('Distribution of Ages');
目标== 1曲线朝向范围的年轻端倾斜。 虽然这不是显著的相关性(-0.07相关系数),但这个变量在机器学习模型中极可能会有用,由于它确实会影响目标。 让咱们从另外一个角度来看待这种关系:平均未按年龄段偿还贷款。
为了制做这个图表,咱们首先将年龄类别分为5年的垃圾箱。 而后,对于每一个垃圾箱,咱们计算目标的平均值,它告诉咱们在每一个年龄段中没有偿还的贷款的比例。
# Age information into a separate dataframe
age_data = app_train[['TARGET', 'DAYS_BIRTH']]
age_data['YEARS_BIRTH'] = age_data['DAYS_BIRTH'] / 365
# Bin the age data
age_data['YEARS_BINNED'] = pd.cut(age_data['YEARS_BIRTH'], bins = np.linspace(20, 70, num = 11))
age_data.head(10)
age_groups = age_data.groupby('YEARS_BINNED').mean()
age_groups
plt.figure(figsize = (8, 8))
# Graph the age bins and the average of the target as a bar plot
plt.bar(age_groups.index.astype(str), 100 * age_groups['TARGET'])
# Plot labeling
plt.xticks(rotation = 75); plt.xlabel('Age Group (years)'); plt.ylabel('Failure to Repay (%)')
plt.title('Failure to Repay by Age Group');
有一个明显的趋势:年轻的申请人更有可能不偿还贷款! 年龄最小的三个年龄组的失败率在10%以上,最老的年龄组为5%。这是银行能够直接使用的信息:由于年轻的客户不太可能偿还贷款,可能应该为他们提供更多的指导或财务规划提示。 这并不意味着银行应该歧视年轻的客户,但采起预防措施帮助年轻客户按时付款是明智之举。
咱们来看看这些变量。
首先,咱们能够显示“EXT_SOURCE”特征与目标和相互之间的相关性。
# Extract the EXT_SOURCE variables and show correlations
ext_data = app_train[['TARGET', 'EXT_SOURCE_1', 'EXT_SOURCE_2', 'EXT_SOURCE_3', 'DAYS_BIRTH']]
ext_data_corrs = ext_data.corr()
ext_data_corrs
# Extract the EXT_SOURCE variables and show correlations
ext_data = app_train[['TARGET', 'EXT_SOURCE_1', 'EXT_SOURCE_2', 'EXT_SOURCE_3', 'DAYS_BIRTH']]
ext_data_corrs = ext_data.corr()
ext_data_corrs
全部三个EXT_SOURCE特征与目标都有负相关,代表随着EXT_SOURCE的价值增长,客户更有可能偿还贷款。 咱们还能够看到,DAYS_BIRTH与EXT_SOURCE_1正相关,代表多是此分数中的一个因素是客户年龄。
接下来咱们能够看一下这些目标值的颜色的分布。 这将让咱们可视化这个变量对目标的影响。
plt.figure(figsize = (10, 12))
# iterate through the sources
for i, source in enumerate(['EXT_SOURCE_1', 'EXT_SOURCE_2', 'EXT_SOURCE_3']):
# create a new subplot for each source
plt.subplot(3, 1, i + 1)
# plot repaid loans
sns.kdeplot(app_train.loc[app_train['TARGET'] == 0, source], label = 'target == 0')
# plot loans that were not repaid
sns.kdeplot(app_train.loc[app_train['TARGET'] == 1, source], label = 'target == 1')
# Label the plots
plt.title('Distribution of %s by Target Value' % source)
plt.xlabel('%s' % source); plt.ylabel('Density');
plt.tight_layout(h_pad = 2.5)
EXT_SOURCE_3显示目标值之间的最大差别。 咱们能够清楚地看到,这一特征与申请人偿还贷款的可能性有必定关系。 这种关系不是很强(事实上它们都被认为是很是弱的,可是这些变量对于机器学习模型来讲仍然是有用的,能够预测申请人是否按时偿还贷款。