본문 바로가기

Python Programming/Projects

기업의 ESG 요소와 수익률 간의 상관관계 분석(2) - Clustering Analysis

Data Load

from google.colab import drive
drive.mount('/gdrive')
Mounted at /gdrive
import pandas as pd
import numpy as np
from sklearn.metrics import silhouette_score as sil
from sklearn.cluster import KMeans
import seaborn as sns
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
  Unnamed: 0 company code esg e s g AV ret sector sector_class AV_log ret_group sector_num sector_count sector_2 sector_3 sector_4 sector_5 sector_6 sector_7 sector_8 sector_9
0 0 BNK금융지주 138930 6 4 6 6 1.851312e+12 -0.000826 기타 금융업 금융 및 보험업 28.246916 1 2 83 1 0 0 0 0 0 0 0
1 1 DGB금융지주 139130 6 5 6 6 1.148500e+12 0.000187 기타 금융업 금융 및 보험업 27.769478 2 2 83 1 0 0 0 0 0 0 0
2 2 JB금융지주 175330 6 5 5 6 1.110984e+12 0.000460 기타 금융업 금융 및 보험업 27.736267 2 2 83 1 0 0 0 0 0 0 0
3 3 KB금융 105560 6 6 6 6 1.804606e+13 0.000152 기타 금융업 금융 및 보험업 30.523949 2 2 83 1 0 0 0 0 0 0 0
4 4 S-Oil 10950 6 5 6 6 7.790729e+12 -0.000677 석유 정제품 제조업 제조업 29.683956 1 9 314 0 0 0 0 0 0 0 1

Sector의 경우, 섹터의 번호는 수치적인 의미를 갖는 것을 방지하기 위해 각 Sector에 대한 더미변수를 생성하여 총 9개의 column으로 구분하였다. 

또한, ESG의 등급 score이 1~6으로 지정될 경우, A+이 D에 비해 6배 높게 수치화되어 왜곡된 결과를 불러올 수 있기 때문에 검토가 필요하였다. 따라서 박은진, <ESG전략이 기업의 재무 성과에 미치는 영향(2018)>, KAIST 논문을 참고하여 score를 재부여하였다. 

grade_mapper = {6 : 120, 5 : 110, 4 : 100, 3 : 90, 2 : 80, 1 : 70}
esg['e'] = esg['e'].map(grade_mapper)
esg['s'] = esg['s'].map(grade_mapper)
esg['g'] = esg['g'].map(grade_mapper)
esg['esg'] = esg['esg'].map(grade_mapper)
esg.head()
  Unnamed: 0 company code esg e s g AV ret sector sector_class AV_log ret_group sector_num sector_count sector_2 sector_3 sector_4 sector_5 sector_6 sector_7 sector_8 sector_9
0 0 BNK금융지주 138930 120 100 120 120 1.851312e+12 -0.000826 기타 금융업 금융 및 보험업 28.246916 1 2 83 1 0 0 0 0 0 0 0
1 1 DGB금융지주 139130 120 110 120 120 1.148500e+12 0.000187 기타 금융업 금융 및 보험업 27.769478 2 2 83 1 0 0 0 0 0 0 0
2 2 JB금융지주 175330 120 110 110 120 1.110984e+12 0.000460 기타 금융업 금융 및 보험업 27.736267 2 2 83 1 0 0 0 0 0 0 0
3 3 KB금융 105560 120 120 120 120 1.804606e+13 0.000152 기타 금융업 금융 및 보험업 30.523949 2 2 83 1 0 0 0 0 0 0 0
4 4 S-Oil 10950 120 110 120 120 7.790729e+12 -0.000677 석유 정제품 제조업 제조업 29.683956 1 9 314 0 0 0 0 0 0 0 1

Model 1

Sector, 시가총액, e, s, g 포함 Clustering

Prep

먼저 Elbow Score를 구한다. 아래의 그래프에서 가장 급격하게 기울기가 변경되는 지점은 3이므로 클러스터의 개수를 3으로 설정하였다. 

all_scores1 = []
features1 = ['e', 's', 'g', 'AV_log', 'sector_2', 'sector_3', 'sector_4', 'sector_5',
       'sector_6', 'sector_7', 'sector_8', 'sector_9']
for i in range(10):
    i = i + 2
    model1 = KMeans(n_clusters=i)
    model1.fit(esg[features1])
    sil_score = sil(esg[features1], model1.labels_)
    # 새로운 모델 만들 때마다 fit(학습) 하고 실루엣 방식으로 점수를 뽑아냄
    elbow_score = model1.inertia_
    score_dict = {'cluster_num' : i, 'sil_score' : sil_score, 'elbow' : elbow_score}
    all_scores1.append(score_dict)

score_df1 = pd.DataFrame(all_scores1)
sns.lineplot(data=score_df1, x='cluster_num', y='elbow')
# 적정 cluster_num = 3
<matplotlib.axes._subplots.AxesSubplot at 0x7f19ae98ec50>

 

# Scaling

from sklearn.preprocessing import StandardScaler

esg_revise = esg[features1]
scaler = StandardScaler()
scaler.fit(esg_revise.values)
esg_scaled = scaler.transform(esg_revise.values)

esg_scaled1 = pd.DataFrame(esg_scaled)
esg_scaled1.columns = features1
esg_scaled1.head()
  e s g AV_log sector_2 sector_3 sector_4 sector_5 sector_6 sector_7 sector_8 sector_9
0 0.876859 1.867814 2.663795 0.775274 2.394773 -0.104163 -0.281337 -0.207148 -0.306486 -0.192629 -0.182405 -1.115787
1 1.641545 1.867814 2.663795 0.465823 2.394773 -0.104163 -0.281337 -0.207148 -0.306486 -0.192629 -0.182405 -1.115787
2 1.641545 0.981474 2.663795 0.444297 2.394773 -0.104163 -0.281337 -0.207148 -0.306486 -0.192629 -0.182405 -1.115787
3 2.406232 1.867814 2.663795 2.251129 2.394773 -0.104163 -0.281337 -0.207148 -0.306486 -0.192629 -0.182405 -1.115787
4 1.641545 1.867814 2.663795 1.706689 -0.417576 -0.104163 -0.281337 -0.207148 -0.306486 -0.192629 -0.182405 0.896229
  • Scale을 통일하기 위해 StandardScaler 적용
model1 = KMeans(n_clusters = 3)
model1.fit(esg_scaled1)
esg_scaled1['cluster'] = model1.labels_ k
  • Statistics of each Cluster
esg_scaled1['ret'] = esg['ret']
esg_scaled1['sector_class'] = esg['sector_class']

cluster_df = esg_scaled1.groupby('cluster').agg({'e' : np.mean, 's' : np.mean, 'g' : np.mean, 'AV_log' : np.mean})
cluster_0 = esg_scaled1.loc[esg_scaled1['cluster'] == 0]
cluster_1 = esg_scaled1.loc[esg_scaled1['cluster'] == 1]
cluster_2 = esg_scaled1.loc[esg_scaled1['cluster'] == 2]

cluster_df['ret'] = [cluster_0['ret'].mean(), cluster_1['ret'].mean(), cluster_2['ret'].mean()]
cluster_df['ret_sd'] = [cluster_0['ret'].std(), cluster_1['ret'].std(), cluster_2['ret'].std()]
cluster_df.sort_values(by = 'ret', ascending = False)
  e s g AV_log ret ret_sd
cluster            
1 -0.078999 -0.532689 -0.460467 -0.469280 0.001446 0.001764
2 1.139037 1.215721 1.096823 1.056544 0.001122 0.001650
0 -0.894254 -0.190781 -0.206399 -0.155007 0.000975 0.001550

Cluster 2의 e, s, g, 시가총액이 가장 크지만 ret 값은 가장 높지 않다. 

변수 개수가 많은 관계로 다차원 변수들을 2차원으로 축소하여 시각화하기 위해 TSNE 알고리즘을 활용하였다. 시각화를 해본 결과, 클러스터가 중구난방으로 퍼져 있고 일정하게 모여 있지 않은 모습이다. 따라서 이 모델을 구성하는 변수들 중 수정 및 제거해야 하는 변수가 포함되어 있다고 예측하였다. 

feature_df1 = pd.DataFrame(esg_scaled1[features1])

transformed1 = TSNE(n_components=2).fit_transform(feature_df1)
xs = transformed1[:,0]
ys = transformed1[:,1]
plt.scatter(xs,ys, c=esg_scaled1['cluster'])  #라벨은 색상으로 분류됨

plt.show()

Result

fig, ax = plt.subplots(nrows=3, figsize = (7, 10))
x1 = 'e'
x2 = 's'
x3 = 'g'
y = 'sector_class'
hue = 'cluster'

scatter_df1 = esg_scaled1.groupby([y, hue]).agg({x1 : np.mean}).reset_index()
scatter_df2 = esg_scaled1.groupby([y, hue]).agg({x2 : np.mean}).reset_index()
scatter_df3 = esg_scaled1.groupby([y, hue]).agg({x3 : np.mean}).reset_index()

sns.scatterplot(data = scatter_df1, x = x1, y = y, hue = hue, ax = ax[0])
sns.scatterplot(data = scatter_df2, x = x2, y = y, hue = hue, ax = ax[1])
sns.scatterplot(data = scatter_df3, x = x3, y = y, hue = hue, ax = ax[2])

  • 3개의 seaborn 그래프를 통해 알 수 있는 것
    • sector를 기준으로 그래프를 나누었을 때에는 큰 관련성을 찾을 수 없다, 
    • e, s, g(x축들)을 기준으로 그래프를 나누면 같은 색깔(cluster)끼리 모여있는 경향성이 있다. 
    • 이 경향성은 e에서 더욱 두드러진다. 
  • Sector보다 e, s, g에 의해 cluster이 나누어지는 경향이 강하다. 
  • 따라서, Model2에서는 Sector 더미변수들을 제외하고 Clustering 시도하였다. 

Model 2

Sector 제외 Clustering

Prep

all_scores = []
features2 = ['e', 's', 'g', 'AV_log']
for i in range(10):
    i = i + 2
    model2 = KMeans(n_clusters=i)
    model2.fit(esg[features2])
    sil_score = sil(esg[features2], model2.labels_)
    elbow_score = model2.inertia_
    score_dict = {'cluster_num' : i, 'sil_score' : sil_score, 'elbow' : elbow_score}
    all_scores.append(score_dict)

score_df = pd.DataFrame(all_scores)
sns.lineplot(data=score_df, x='cluster_num', y='elbow')
# 적정 cluster_num = 3 or 4

esg_scaled2 = pd.DataFrame(esg_scaled)
esg_scaled2.columns = features2

esg_scaled2['ret'] = esg['ret']
esg_scaled2['sector_class'] = esg['sector_class']
model2 = KMeans(n_clusters = 3)
model2.fit(esg_scaled2[features2])
esg_scaled2['cluster'] = model2.labels_

cluster_df = esg_scaled2.groupby('cluster').agg({'e' : np.mean, 's' : np.mean, 'g' : np.mean, 'AV_log' : np.mean})
cluster_0 = esg_scaled2.loc[esg_scaled2['cluster'] == 0]
cluster_1 = esg_scaled2.loc[esg_scaled2['cluster'] == 1]
cluster_2 = esg_scaled2.loc[esg_scaled2['cluster'] == 2]

cluster_df['ret'] = [cluster_0['ret'].mean(), cluster_1['ret'].mean(), cluster_2['ret'].mean()]
cluster_df['ret_sd'] = [cluster_0['ret'].std(), cluster_1['ret'].std(), cluster_2['ret'].std()]
cluster_df.sort_values(by = 'ret', ascending = False)
  e s g AV_log ret ret_sd
cluster            
0 0.203935 -0.649390 -0.688125 -0.645514 0.001424 0.001768
2 -1.031072 -0.260279 -0.072491 -0.159439 0.001175 0.001644
1 1.066813 1.162130 0.969861 1.027448 0.001070 0.001629
  • Model 1과 비슷하게 ESG 등급이 가장 높은 특정 클러스터가 시가총액도 가장 높게 나타난다. 그리고, ret 값은 가장 낮게 나타났다.
  • Cluster 별로 ret 값의 차이의 간격이 크지 않기 때문에 유의미한 차이는 아닐 것이라고 예측할 수 있다. 
feature_df2 = pd.DataFrame(esg_scaled2[features2])

transformed2 = TSNE(n_components=2).fit_transform(feature_df2)
xs = transformed2[:,0]
ys = transformed2[:,1]
plt.scatter(xs,ys, c=esg_scaled2['cluster'])  #라벨은 색상으로 분류됨

plt.show()

TSNE로 차원축소를 해본 결과, 첫 번째 모델보다는 비교적 같은 Cluster끼리 모여 있는 모습이다. 

Result

plt.scatter(esg_scaled2['AV_log'], esg_scaled2['ret'], c=esg_scaled2['cluster'])
plt.title('AV - Return (by Cluster)')
plt.xlabel("AV_log")
plt.ylabel("Return")
Text(0, 0.5, 'Return')



/usr/local/lib/python3.7/dist-packages/matplotlib/backends/backend_agg.py:214: RuntimeWarning: Glyph 8722 missing from current font.
  font.set_text(s, 0.0, flags=flags)
/usr/local/lib/python3.7/dist-packages/matplotlib/backends/backend_agg.py:183: RuntimeWarning: Glyph 8722 missing from current font.
  font.set_text(s, 0, flags=flags)

  • Return 값에 따라 Cluster이 분류되는 정도는 매우 낮다. 
  • 즉, 앞선 ret 값의 mean 차이는 매우 미미하며 영향력이 거의 없다고 봐도 무방하다. 
  • 전체적으로는 시가총액과 ret 값이 우상향하는 관계는 아니지만, 특정 몇몇 구간에서는 시가총액에 따라 ret 값이 상승하는 양상을 어느 정도 확인 가능하다. 
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15,5))
fig.suptitle('E, S, G - Return (by Cluster)')
ax1.scatter(esg_scaled2['e'], esg_scaled2['ret'], c=esg_scaled2['cluster'])
ax1.set_xlabel('E')
ax1.set_ylabel('Return')
ax2.scatter(esg_scaled2['s'], esg_scaled2['ret'], c=esg_scaled2['cluster'])
ax2.set_xlabel('S')
ax3.scatter(esg_scaled2['g'], esg_scaled2['ret'], c=esg_scaled2['cluster'])
ax3.set_xlabel('G')

  • S, G 보다 E에 의해 Cluster이 분류되는 정도가 더 강하게 나타난다. 
  • S, G를 x축으로 설정한 그래프에서는 초록색, 노란색 Cluster이 중간 중간 많이 섞여 있다. 
  • E, G를 x축으로 설정한 그래프의 경우, 등급이 최고점인 경우 오히려 ret 값이 낮게 나타나는 경향이 있다. 
  • E가 가장 낮은 그룹이 S, G에서는 중간 등급 그룹에 속한다. (roughly)

Conclusion

  • 섹터 변수는 ESG와 시가총액이 수익률에 미치는 영향과는 큰 관련성이 없다.
  • 2020년 기준으로는 ESG 등급이 높은 그룹이 수익률이 특별히 높게 나타나지 않았다.
  • 다만, ESG 등급이 높은 그룹은 시가총액이 높은 기업들이 주로 포함된 그룹이었기 때문에, 기업의 수익률이 ESG보다는 시가총액에 의해 결정될 확률이 높을 것 같다고 판단하였다.
  • 한국기업지배구조원에서 원점수로 연구했던 기존 연구결과에서는 E를 제외한 S, G, ESG통합점수가 토빈q 같은 기업가치 지표에 유의미한 영향을 준다는 내용을 발표하였었는데, 해당 연구결과에서 나타내는 기업가치들이 수익률에 아직은 반영되지 않았다는 결론을 내릴 수 있다. 

한계점 & 보완점

    • 수익률 데이터 수집 기간이 1년으로 한정되었으므로, ESG에 대한 평가 요소가 수익률에 반영되는 데 시간이 소요될 수 있다는 점을 고려한다면, 오차가 발생하였을 수 있다. 
    • 2020년 코로나 이슈로 인해 수익률 데이터의 변동성이 기존과는 다른 양상을 보일 수 있다. 즉, 앞으로 2025년까지의 데이터 수집 후 더욱 안정적인 분석이 가능할 것으로 예측된다. 
    • 확보할 수 있는 ESG 평가 등급 자료가 한국기업지배구조원으로 한정되었다. 다른 기관들의 등급을 함께 활용한다면 더욱 다각적인 분석이 가능할 것이다. 
    • E, S, G 의 지표를 단순히 등급으로 서열화하기 보다 각 부문에 대한 원점수를 공개한다면 정확성이 높아질 것으로 기대할 수 있다.