天天看点

爱数课实验 | 第七期-基于随机森林的金融危机分析

爱数课实验 | 第七期-基于随机森林的金融危机分析

爱数课:idatacourse.cn

领域:其他

简介:上世纪60年代之后,非洲掀起了摆脱殖民主义的独立浪潮。由于几百年的历史原因,非洲大陆多数国家经济发展较为落后,经济体系较脆弱,各种危机时常发生。该案例对近百年来非洲13个国家的金融危机进行了探索性分析,并构建了随机森林模型进行预测。

数据:

./dataset/african_crises.csv

./dataset/SimHei.ttf

目录

爱数课实验 | 第七期-基于随机森林的金融危机分析

数据共计1059条,各数据字段含义如下表所示:

字段 含义
case 国家编号,代表特定国家的数字
cc3 国家代码,三个字母的国家/地区代码
country 国家名称
year 观测年份
systemic_crisis 系统性危机,“ 0”表示当年未发生系统性危机,“ 1”表示当年有发生系统性危机
exch_usd 该国货币兑美元的汇率
domestic_debt_in_default 国内债务违约,“0”表示当年未发生国内债务违约,“1”表示当年有发生国内债务违约
sovereign_external_debt_default 主权外债违约,“0”表示当年未发生主权外债违约,“1”表示当年有发生主权外债违约
gdp_weighted_default 违约债务总额与GDP之比
inflation_annual_cpi 年度CPI通货膨胀率
independence 独立性,“ 0”表示“无独立性”,“ 1”表示“独立性”
currency_crises 货币危机,“ 0”表示当年未发生“货币危机”,“ 1”表示当年有发生“货币危机”
inflation_crises 通胀危机,“ 0”表示当年未发生“通胀危机”,“ 1”表示当年有发生“通胀危机”
banking_crisis 银行业危机,“ no_crisis”表示当年没有发生银行业危机,而“ crisis”表示当年有发生银行业危机

1. 数据读取与预处理

1.1 读取数据

# 导入相应模块
import numpy as np 
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
# 设置字体
font = FontProperties(fname = "./dataset/SimHei.ttf", size=14)

import seaborn as sns
import random

# 设置绘图风格
%matplotlib inline
sns.set(style='whitegrid')

# 忽略所有警告
import warnings
warnings.filterwarnings('ignore')

# 读取数据
data = pd.read_csv('./dataset/african_crises.csv')
data.sample(5)      

1.3 查看数据基本信息

首先我们查看一共有哪些国家。

unique_countries = data.country.unique()
unique_countries      

可以看到数据中一共包含13个非洲国家,按顺序分别为阿尔及利亚,安哥拉,中非共和国,象牙海岸,埃及,肯尼亚,毛里求斯,摩洛哥, 尼日利亚,南非,突尼斯,赞比亚和津巴布韦。接下来我们使用描述性统计函数查看数据的基本情况以及数据中是否存在缺失和异常。

# 数据集的基本信息
data.______()      

可以看到除了国家代码​

​cc3​

​​、国家名称​

​country​

​​和银行危机​

​banking_crisis​

​这三个字段为字符型之外,其余均为数值类型,且数据中不存在缺失值。

# 查看数据统计性指标
data.____________(include = 'all')      

通过观察统计性指标,我们看到年份​

​year​

​​的最大最小值分别为2014年和1860年,埃及的统计记录最多,有155条数据。同时我们也发现一个异常,货币危机​

​currency_crises​

​的取值范围为0、1,但数据中出现了取值2,我们需要单独进行处理,且其余指标无明显异常。

1.3 数据预处理

#查看货币危机currency_crises的取值为2的数据
data[data['currency_crises'] == 2]       

可以看到存在异常的数据只有4条,我们直接进行删除。

data = data[data['currency_crises'] != 2]# 得到生成删除货币危机currency_crises的取值为2的数据集
data.______ # 查看新生成的数据集大小      

2. 经济指标探索性分析

在二战之后,当今世界的格局初步形成,在60年代之后,非洲掀起了摆脱殖民主义的独立浪潮。由于几百年的历史原因,非洲大陆是地球上发展最落后的地区,多数国家经济政治发展较为落后,人口素质较低,经济体系较为脆弱,各种危机时常发生。接下来我们通过该数据集分析一下在独立前后的各个国家的经济发展以及面临经济危机的情况。首先我们绘制13个国家的货币兑美元的汇率变化情况的折线图。

2.1 货币兑美元的汇率变化情况

plt.figure(figsize=(12,20))

for i in range(13):
    
    plt.subplot(7,2,i+1)
    country = unique_countries[i]
    
    # 随机生成一种颜色 random.choice():从一个序列中随机的抽取一个元素,抽取6次组成6位代表随机颜色
    col="#"+''.join([random.choice('0123456789ABCDEF') for j in range(6)])
    
    # 绘制折线图
    sns.____________(data[data.country == country]['year'],data[data.country == country]['exch_usd'],label = country,color = col)
    
    # np.logical_and()逻辑与 两个条件均成立时返回True
    plt.plot([np.min(data[np.logical_and(data.country == country,data.independence == 1)]['year']),
              np.min(data[np.logical_and(data.country == country,data.independence == 1)]['year'])],
             [0,np.max(data[data.country == country]['exch_usd'])],color = 'black',linestyle = 'dotted',alpha = 0.8)
    

    plt.______(country) # 添加图像标题
    
plt.tight_layout() # 自动调整子图参数以提供指定的填充
plt.show() # 输出13个国家的货币兑美元的汇率变化情况的折线图      

可以看到,大部分国家在在独立前和独立之后的短期阶段并没有自己的货币体系,仍沿用殖民统治国家的货币,如法郎或英镑。安哥拉(Angola)、津巴布韦(Zimbabwe)、赞比亚(Zambia)、尼日利亚(Nigeria)等国汇率长期维持在0,在21世纪左右开始有了本国货币。突尼斯(Tunisia)在独立后汇率骤然下降,长期稳定在1:1左右。另外还能发现大部分非洲国家随着时间的发展,以美元为基准,货币呈现逐步贬值的状态。

2.2 通货膨胀率变化情况

通货膨胀率也称为物价变化率,是货币超发部分与实际需要的货币量之比,用以反映通货膨胀、货币贬值的程度。通过价格指数的增长率来计算通货膨胀率,在本数据中采用的是消费者价格指数(CPI)来进行表示的。

按照价格上升的速度加以区分:

  • 温和的通货膨胀(每年物价上升比例在1%~6%之内)
  • 严重的通货膨胀(每年物价上升比例在6%~9%)
  • 飞奔的通货膨胀(每年物价上升比例在10%~50%以下)
  • 恶性的通货膨胀(每年物价上升比例在50%以上)

接下来我们分析一下各国的通货膨胀率变化情况。

plt.figure(figsize=(12,20))

for i in range(13):
    
    plt.subplot(7,2,i+1)
    country = unique_countries[i]
    
    # 随机生成一种颜色
    col="#"+''.join([random.choice('0123456789ABCDEF') for j in range(6)])
    
    # 绘制折线图
    sns.lineplot(data[data.country == country]['year'],data[data.country == country]['inflation_annual_cpi'],label = country,color = col)
    
    # 加入散点图
    plt.______(data[data.country == country]['year'],data[data.country == country]['inflation_annual_cpi'],color = col,s = 28) # s指散点的面积
    
    plt.plot([np.min(data[np.logical_and(data.country == country,data.independence==1)]['year']),
              np.min(data[np.logical_and(data.country == country,data.independence==1)]['year'])],
             [np.min(data[data.country == country]['inflation_annual_cpi']),np.max(data[data.country == country]['inflation_annual_cpi'])],
             color = 'black',linestyle = 'dotted',alpha = 0.8) # alpha指颜色透明度

    plt.title(country)
    
plt.tight_layout() # 自动调整子图参数以提供指定的填充
plt.show() # 输出13个国家的通货膨胀率变化情况      

可以看到这些非洲国家中大部分均出现了不同程度的通货膨胀,如南非(South Africa)在1970-1990之间由于种族隔离政策不断被西方经济体制裁导致经济受到大幅度影响;安哥拉(Angola)在20世纪90年代发生多次内战,战争导致通货膨胀率疯狂飙升,在最高一年达到4000以上。也有部分国家经济较为稳定,如突尼斯(Tunisa)独立后,通货膨胀率经过短暂上升又逐渐下降至较低水平并保持稳定。

2.3 其他危机分布情况

接下来我们分析一下数据的其他字段:不同国家的系统性危机​

​systemic_crisis​

​​、国内债务违约​

​domestic_debt_in_default​

​​、主权外债违约​

​sovereign_external_debt_default​

​​、货币危机​

​currency_crises​

​​、通胀危机​

​inflation_crises​

​​、银行危机​

​banking_crisis​

​等的分布规律。

sns.set(style='darkgrid')
columns = ['systemic_crisis','domestic_debt_in_default','sovereign_external_debt_default','currency_crises','inflation_crises','banking_crisis']

# 绘制其他特征的分布规律图
plt.figure(figsize=(16,16))

for i in range(6):
    plt.subplot('32'+str(i+1))
    sns.countplot(y = data.country,hue = data[columns[i]],palette = 'rocket') # palette为调色板
    plt.______(loc = 0) # 选择最优的图例位置
    plt.title(columns[i])
    
plt.tight_layout()
plt.show()      
  • 主权外债违约​

    ​sovereign_external_debt_default​

    ​是指一国政府无法按时对其向外担保借来的债务还本付息的情况,观察上图可以发现中非共和国(Central African Republic)、津巴布韦(Zimbabwe)和象牙海岸(Ivory Coast)出现大量的主权外债违约,这导致主权信用评级极低。
  • 同时我们还能发现,除了安哥拉(Angola)、津巴布韦(Zimbabwe),其余大部分国家均没有国内债务违约​

    ​domestic_debt_in_default​

    ​。这是因为当一国政府掌握有货币的发行权时,政府可以通过发行新的货币,以投放过量的货币来偿还本币债务(即内债),此时政府不会发生对国内债务的主权违约,这也是采用本币标记的主权债券在其国内享有最高信用评级的原因。但现实中由于政府超发货币会带来通货膨胀、本币币值波动等现象,因此其面向全部债权人债务总额是有上限的。
  • 系统性金融危机可以称为“全面金融危机”,是指主要的金融领域都出现严重混乱,如货币危机、银行业危机、外债危机的同时或相继发生。它往往发生在金融经济、金融系统、金融资产比较繁荣的市场化国家和地区以及赤字和外债较为严重的国家,对世界经济的发展具有巨大的破坏作用。

发生系统性危机​

​systemic_crisis​

​​最多的国家是中非共和国(Central African Republic),其次是津巴布韦(Zimbabwe)和肯尼亚(Kenya)。按照系统性危机的定义,系统性危机与银行业危机之间应存在联系。让我们检查这些国家在发生系统性危机的时候是否同时发生银行危机​

​banking_crisis​

​。

2.4 系统性危机与银行危机间的相关性

# 创建包含年份,国家,系统性危机,银行危机的数据集
systemic = data[['year','country', 'systemic_crisis', 'banking_crisis']]

# 绘制观察系统性危机与银行危机发生的重叠性
systemic = systemic[(systemic['country'] == 'Central African Republic') | (systemic['country']=='Kenya') | (systemic['country']=='Zimbabwe') ]
plt.figure(figsize=(12,12))
count = 1

for country in systemic.country.unique():
    plt.subplot(len(systemic.country.unique()),1,count)
    subset = systemic[(systemic['country'] == country)]
    sns.lineplot(subset['year'],subset['systemic_crisis'],ci=None) # ci参数可用于指定线段区间的大小
    plt.scatter(subset['year'],subset["banking_crisis"], color='coral', label='Banking Crisis')
    plt.subplots_adjust(hspace=0.6) # hspace用来设置子图上下间的距离
    plt.______('Years') # 给x轴命名
    plt.______('Systemic Crisis/Banking Crisis') # 给y轴命名
    plt.title(country)
    count+=1      

蓝色折线的取值代表系统性危机是否发生,红色散点代表银行危机是否发生,上图表明了危机是如何重叠的,从而证实了我们关于系统性危机对银行业危机有影响的假设。

计算所有特征间的相关性

# 将银行危机banking_crisis列进行特征编码
# 将银行危机banking_crisis中未发生危机的数据标为0,发生危机的数据标为1
data['banking_crisis'] = data['banking_crisis'].map({"no_crisis":0,"crisis":1})

# 选出所有特征
selected_features = ['systemic_crisis', 'exch_usd', 'domestic_debt_in_default','sovereign_external_debt_default', 'gdp_weighted_default',
       'inflation_annual_cpi', 'independence', 'currency_crises','inflation_crises','banking_crisis']

corr = data[selected_features].______() # 得到各特征间的相关性大小生成相关性矩阵

fig = plt.figure(figsize = (12,8))

cmap = sns.diverging_palette(220, 10, as_cmap=True) # 生成蓝-白-红的颜色列表
mask = np.zeros_like(corr, dtype=np.bool) # 返回与相关性矩阵具有相同形状和类型的零数组作为掩码
mask[np.triu_indices_from(mask)] = True # 给相关性矩阵的上三角阵生成掩码

# 绘制热力图
sns.______(corr, mask=mask, cmap=cmap,vmin=-0.5,vmax=0.7, center=0,annot = True,
            square=True, linewidths=.5,cbar_kws={"shrink": .5});

plt.title("特征间的相关性",fontproperties = font)
plt.show()      

除了看到系统性危机和银行危机的相关性极高之外,我们还了解到货币兑美元的汇率及国内债务违约与主权外债违约相关性较大。

接下来我们尝试构建一个随机森林分类模型,预测影响银行危机发生的特征有哪些。

3. 构建银行危机预测模型

  • 特征编码
  • 数据集划分与分层采样
  • 建立随机森林预测模型
  • 模型效果的评估
  • 使用SMOTE进行过采样优化模型
  • 特征重要性排序

3.1 特征编码

data.drop(['case','cc3'],axis = 1,inplace = True) # 在原数据集上删掉case列和cc3列
data.head()      
# 对国家country进行labelencoder
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
le.______(data['country'].values) # 将country的值塞入空字典
data['country']=le.____________(data['country'].values) # 将字典中的country的值转变为索引值      
print(data['country']) # 查看特征编码后的country名称      
# 绘制未发生银行危机no_crisis与发生银行危机crisis的柱状图
fig = plt.figure(figsize = (8,6))

data['banking_crisis'].value_counts().plot(kind='______',rot = 360,color = 'lightseagreen')

plt.xticks([0,1],["no_crisis","crisis"])
plt.show()      

可以看到,数据集中发生银行危机的情况远少于未发生银行危机的情况,比例约为1:10,对于此类不平衡数据,应该尽量使得训练集和测试集中样本的比例相一致。需要使用分层采样的方法来划分训练集和测试集。

3.2 数据集划分与分层采样

下面我们开始对数据进行训练集与测试集的划分。在Sklearn中的​

​model_selection​

​​模块,存在​

​train_test_split()​

​​函数,用作训练集和测试集划分,函数语法为:​

​train_test_split(x,y,test_size = None,random_state = None,stratify = y)​

​,其中:

  • ​x,y​

    ​: 分别为预测所需的所有特征,以及需要预测的特征。
  • ​test_size​

    ​​: 测试集比例,例如​

    ​test_size=0.2​

    ​则表示划分​

    ​20%​

    ​的数据作为测试集。
  • ​random_state​

    ​​: 随机种子,因为划分过程是随机的,为了进行可重复的训练,需要固定一个​

    ​random_state​

    ​,结果重现。
  • ​stratify​

    ​: 使用分层采样,保证从发生银行危机样本和未发生银行危机样本中抽取了同样比例的训练集和测试集。

函数最终将返回四个变量,分别为​

​x​

​​的训练集和测试集,以及​

​y​

​的训练集和测试集。

# 训练集与测试集的划分
from sklearn import model_selection

x = data.drop('banking_crisis',axis = 1) # 将删除banking_crisis列的数据集作为x
y = data['banking_crisis'] # banking_crisis列作为y

x_train,x_test,y_train,y_test = model_selection.__________________(x, y,test_size=0.2,random_state = 33,stratify=y)      

3.3 建立随机森林预测模型

随机森林是一种集成学习方法,通过使用随机的方式从数据中抽取样本和特征,训练多个不同的决策树,形成“森林”。每个树都给出自己的分类意见,称“投票”。在分类问题下,森林选择选票最多的分类;在回归问题下则使用平均值。在Python中使用​

​sklearn.ensemble​

​​的​

​RandomForestClassifier​

​构建分类模型,其主要参数包括:

  • ​n_estimators​

    ​ : 训练分类器的数量(默认为100);
  • ​max_depth​

    ​ : 每棵树的最大深度(默认为3);
  • ​max_features​

    ​: 划分的最大特征数(默认为 'auto')
  • ​random_state​

    ​ : 随机种子。
from sklearn.ensemble import RandomForestClassifier

# 训练随机森林分类模型
rf = RandomForestClassifier(n_estimators = 100, max_depth = 20,max_features = 10, random_state = 20)
rf.fit(x_train, y_train) 
y_pred = rf.______(x_test) # 对y进行预测      

3.4 模型评估

在评价模型好坏时,我们分别使用函数​

​classification_report()​

​​、​

​confusion_matrix()​

​​和​

​accuracy_score()​

​,用于输出模型的预测报告、混淆矩阵和分类正确率。

from sklearn.metrics import classification_report,confusion_matrix

print(classification_report(y_test, y_pred)) # 输出模型的预测报告
confusion_matrix = __________________(y_test, y_pred) 
print(confusion_matrix) # 输出混淆矩阵

# 绘制混淆矩阵热力图
fig,ax = plt.subplots(figsize=(8,6)) 
sns._________(confusion_matrix,ax=ax,annot=True,annot_kws={'size':15}, fmt='d',cmap = 'YlGnBu_r')
ax.set_ylabel('真实值',fontproperties = font)
ax.set_xlabel('预测值',fontproperties = font)
ax.set_title('混淆矩阵热力图',fontproperties = font)
plt.show() # 输出混淆矩阵热力图      

从模型预测报告中可以看出,对发生银行危机(少数类)的召回率达到了89%,通过混淆矩阵及混淆矩阵热力图可以看出分类正确的占比较高,说明随机森林模型效果较好,接下来我们画出二分类的ROC_AUC曲线,进行进一步评估。

ROC曲线

from sklearn.metrics import roc_auc_score
from sklearn.metrics import roc_curve

roc_auc = ____________(y_test, rf.predict(x_test)) #计算auc的值
fpr, tpr, thresholds = ____________(y_test, rf.predict_proba(x_test)[:,1]) #计算不同阈值下的TPR和FPR

# 绘制ROC曲线
plt.figure(figsize = (8,6))

plt.plot(fpr, tpr, label='Random Forest (area = %0.2f)' % roc_auc)
plt.plot([0, 1], [0, 1],'r--')# 绘制随机猜测线
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC curve')

plt.legend(loc="lower right") # 图例位置位于右下方
plt.show()      

可以看到随机森林ROC曲线表现较好,接近左上角,AUC的值达到了0.94。

考虑到训练集里面样本数量较少,样本类别不平衡,我们对少数类使用SMOTE进行过采样操作,扩充少数类样本,对模型进行优化。

3.5 使用SMOTE进行过采样优化模型

SMOTE算法的基本思想是对少数类样本进行分析并根据少数类样本人工合成新样本添加到数据集中。对于少数类样本a, 随机选择一个最近邻的样本b, 然后从a与b的连线上随机选取一个点c作为新的少数类样本。

在对数据集进行划分,接着对训练集进行过采样,将少数类进行扩充。在Python中使用imblearn.over_sampling的SMOTE类构建SMOTE过采样模型。

# 对x_train,y_train进行SMOTE过采样
from imblearn.over_sampling import SMOTE
x_train_resampled, y_train_resampled = SMOTE(random_state=4).fit_resample(x_train, y_train)

print(x_train_resampled.shape, y_train_resampled.shape) #查看采样后的数据集大小      
# 通过网格搜索选择最优参数
from sklearn.model_selection import GridSearchCV
param_grid = [{
    'n_estimators':[10,20,30,40,50],
    'max_depth':[5,8,10,15,20,25]
}]
grid_search = GridSearchCV(rf, param_grid, scoring = 'recall')      
# 输出最佳参数组合以及分数
grid_search.fit(x_train_resampled, y_train_resampled)

print("best params:", grid_search.best_params_)

print("best score:", grid_search.best_score_)      

可以发现对模型进行SMOTE过采样以及进行网格搜索找到最优参数后,得到最优的参数选择为max_depth: 10, n_estimators: 10;recall得分也最高可达到0.97,与优化模型前的0.89相比也有提升。

最后,我们使用随机森林筛选出影响银行危机发生的重要的特征,并画出特征重要性排序图。

3.6 特征重要性排序

fig = plt.figure(figsize=(16,12))

# 得到随机森林特征重要性评分
rf_importance = rf.__________________
index = data.drop(['banking_crisis'], axis=1).columns # 删掉银行危机banking_crisis列特征

# 对得到的特征重要性评分进行降序排序
rf_feature_importance = pd.DataFrame(rf_importance.T, index=index,columns=['score']).sort_values(by='score', ascending=True)

# 水平条形图绘制
rf_feature_importance.plot(kind='______',legend=False,color = 'deepskyblue')

plt.title('随机森林特征重要性',fontproperties = font)

plt.show()      

可以看到,系统危机​

​systemic_crisis​

​​的重要性最高、年度cpi通货膨胀率​

​inflation_annual_cpi​

​​、年份​

​year​

​​、该国货币兑美元汇率​

​exch_used​

​重要性也较高,说明这些特征对影响银行危机是否发生较为重要,这也进一步验证了我们在之前特征相关性分析的结论。

4. 总结