통계학

2017년 대선후보 지지층 군집분석

대선까지 얼마 남지 않은 지금이다. 근 몇 년간 이보다 많은 사람들이 적극적으로 관심을 가진 선거가 과연 있었을까 싶다. 이 때문인지 타 후보에 대한 비방, 지지층간의 갈등과 모함 역시 이전 선거와는 궤를 달리할 정도로 적극적(?)이어 보인다. 특히나 서로 다른 후보의 지지층이 서로를 비난하는 모습은 그 정도가 지나치기도 하여, 소위 말하는 ‘국론 분열’에 대한 우려까지 자아내기도 한다.

후보자의 지지율과 그 추세, 지역, 연령대별 지지율에 대한 정보는 여론조사를 통해서 쉽게 열람할 수 있지만, 각 후보를 지지하는 지지자/지지층이 어떤 성향을 가지고 있는 지에 대해서는 쉽게 파악하기 어렵다. 그저 직관적으로 ‘이 후보를 지지하니 이런이런 사람이겠지’라고 짐작할 뿐이다. 이에 객관적으로 후보의 지지층의 성향을 파악할 수 있다면 서로에 대한 어림짐작으로부터 비롯한 비난(‘OO후보 지지하는 인간은 고령층 적폐세력 옹호자더라!’, ‘OO후보 지지자는 수도권 젊은이 뻘갱이라더라!’ 등등의 원색적인 비난 등)이 줄어들지 않을까 싶어 지지층 군집(cluster) 분석을 기획해 보았다.

 

분석 결과 이전에 알린다

  1. 후보의 성향에 대한 분석이 아니다. 후보를 지지하는 사람들의 성향에 대한 분석이다. 이 둘에는 분명한 차이가 있다.
  2. 또한 이는 후보를 지지하는 사람들의 전반적인 성향에 대한 정보를 제공하는 것이지, 지지자 개개인이 반드시 이러한 성향을 가진다는 의미를 가지지는 않는다.
  3. 모든 데이터는 중앙선거여론조사심의위원회에 결과 등록된 여론조사로, 이는 네이버 제 19대 대선 페이지에서 가져왔다. 결국 각 데이터포인트는 설문에 대한 응답 백분율(%)이다.
  4. 크게 네 가지 종류의 변수를 사용했다. 보다시피 4/7~21에 있었던 여론조사를 바탕으로 만들어진 변수이다.
    1. 정책 성향 관련
      • 일자리 정책 의견 (04.16 조선일보, 칸타퍼블릭) – 민간이 주도해야 한다고 응답한 퍼센티지
      • 사드 배치 찬반 (04.16 조선일보, 칸타퍼블릭) – 반대한다고 응답한 퍼센티지
    2. 연령대 관련
      • 세대별 ‘호감이 간다’ 응답 비율 (04.07 한국갤럽)
    3. 지역 관련
      • 지역별 후보 지지율 (04.21 동아일보, 리서치앤리서치)

    4. 지지 후보 선택 기준
      • 지지후보 결정에 중요한 기준은? (04.18 서울신문, YTN, 엠브레인) – ‘능력과 정치 경험’이라고 응답한 퍼센티지와 ‘도덕성과 청렴성’이라고 응답한 퍼센티지
      • 유승민 후보, 심상정 후보에 대한 조사가 없어서 3자 클러스터링에만 사용되었다.
  5. 데이터포인트를 클러스터링하는 데에는 계층적 클러스터링, 그 중에서도 agglomerative 방식을 사용했다. 클러스터링 옵션을 더 자세히 설명하자면 이렇다:
    • 계층적 클러스터링을 위해서는 각각의 변수값(여기서는 연령대별 지지율 등등) 사이의 유사도를 계산하는 방법인 ‘metric’과 각 샘플(즉, 여기서는 각 대선후보의 지지층) 사이의 클러스터링 방식인 ‘linkage’을 설정해주어야 한다.
    • Metric으로는 코사인 유사도(cosine similarity)를 사용했다. 이는 응답 값의 차이 대신 응답 패턴의 유사성만을 보기 위해서이다. 코사인 유사도를 사용하면 지지율과 같이 패턴이 비슷해도 값에는 차이가 클 수 있는 데이터에서 패턴의 유사도를 계산할 수 있다.
      figure 출처
    • Linkage로는 Ward’s minimum variance method를 사용했다. 그냥 많이들 사용하는 방식이라고 해서 써봤다.
    • 이 외의 metric과 linkage는 도큐멘테이션을 참고하면 좋다.
    • 클러스터링 결과로 그려진 수형도(tree)에서 가지가 짧을수록 서로 유사한 샘플(지지층)이라고 해석할 수 있다.

 

5자 지지층 클러스터링 결과

output_26_0

5자 지지층 비교 – 정책 지향

일자리 정책과 사드 배치 찬반에 대한 의견에서 정책 지향에 따라 지지층의 성향이 극명하게 나뉘는 것을 볼 수 있다. 문재인, 심상정 후보의 지지층이 정책성향이 매우 유사하고, 안철수, 유승민, 홍준표 후보의 지지층 정책성향이 매우 유사하다.

output_28_0

5자 지지층 비교 – 연령대별 호감도

연령대별 호감도로부터 주요 지지층의 연령대를 엿볼 수 있다. 20~40대과 50대 이상의 호감도가 뚜렷하게 구별됨을 확인할 수 있으며, [문재인, 심상정] 후보, [안철수, 유승민] 후보의 지지층의 연령대가 비슷할 것이라 추측할 수 있겠다. 홍준표 후보 지지층이 다른 후보 4명에 비해 유독 튀어보인다.

output_30_0

5자 지지층 비교 – 지역별 지지율

지역별로는 좌와 우 진영이 확실하게 구별되는 듯 하다. (현재는 여야랄게 없지만) 야권과 범여권의 ‘텃밭’에 따라 지지층이 확연히 구별되어 보인다.

 

항상 언론상의 뜨거운 감자인 세 후보의 지지층 정보만으로 다시 클러스터링을 해보았다. 클러스터링 옵션은 위와 같다. 다만 지지후보 선택 기준 변수가 추가되었다.

3자 지지층 클러스터링 결과

output_33_0

3자 지지층 비교 – 정책 지향

굳이 셋 만을 비교한다면 안철수 후보를 지지하는 사람들의 정책 지향은 홍준표 후보 지지자의 것과 유사도가 높게 나타난다.

output_35_0

3자 지지층 비교 – 지지후보 선택 기준

지지후보 선택 기준에 있어서는 문재인 후보 지지층과 안철수 후보 지지층이 매우 유사하다.

output_37_0

3자 지지층 비교 – 연령대별 호감도

연령대별 분류에서도 문재인 후보 지지층과 안철수 후보 지지층이 유사하다. 안철수 후보 지지층은 50대 이상에서도 꽤 나타난다는 것이 차이라면 차이.

output_39_0

3자 지지층 비교 – 지역별 지지율

지역별 분류에서도 문재인 후보 지지층과 안철수 후보 지지층이 유사하다.

 

종합하자면 이렇다

  • 정책 지향성에 있어서는 [홍준표, 유승민, 안철수] 후보군, [문재인, 심상정] 후보군으로 나눌 수 있다. 조사 대상이 두 가지 사안 뿐이었다는 한계가 있으나 사드 배치(외교, 안보)와 일자리 정책(청년, 복지, 경제)이라는 핵심 정책에 대한 조사였으므로 지지층 성향을 파악하는 데에는 충분하지 않았나 싶다.
  • 연령별 분류에 있어서는 [문재인, 심상정] / [안철수, 유승민] / [홍준표] 후보군으로 나눌 수 있다.
  • 지역별 분류에서는 [문재인, 안철수] / [홍준표, 유승민] / [심상정] 후보군으로 나눌 수 있다.
  • 문재인 후보 지지층은 전통적인 민주당계 지지층의 모습을 보여주는 듯 하다. 지역이 그러하고 연령이 그러하며 정책지향성이 그러하다. 마찬가지로 홍준표 후보 지지층은 전통적인 한나라-새누리당계 지지층의 모습을 보여주는 듯 하다.
  • 안철수 후보 지지층은 지역, 연령에 있어서는 진보로 분류되는 그룹이나, 속한 그룹과 별개로 보수적인 가치를 지지하는 모습을 보여준다. 뭇 언론에서 칭하는 것 처럼 이를 ‘중도’라 말할 수도 있겠다.

분석에 사용한 데이터 및 분석 과정과 여론조사 데이터를 긁어모으는데 사용한 스크립트는 각각 여기여기서 볼 수 있다. 실시간으로 계속해서 쌓이는 데이터여서 현재는 당시 분석에 사용한 데이터의 위치가 바뀌었을 수도 있다.

갓 등재한 내 논문이 인용될 수 있을까?

다음 두 분과 함께 한  작은 프로젝트였음을 밝힙니다: wooes23(gmail), hwuis6468(gmail)

서문

데이터 분석의 기초를 배우는 과정에서 그리 어렵지는 않으면서도 조금이나마 흥미가 있는 주제로 프로젝트를 해보고 싶었다. 생물학과 연관이 있으면서 본격적인 생물정보 분석을 요하지는 않는 그런 주제가 없을까? 이런 고민을 하던 와중 불현듯 머릿 속을 스치고 지나간 주제가 있었다.

‘저자, 저널 등 조건변수를 입력하면 지금 갓 출간된 논문의 향후 2년간 피인용수를 대략적으로 예측해주는 모델을 만들어 보면 어떨까?’

그렇게 무모한 생각으로 시작하게 된 프로젝트가 바로 아래의 논문인용예측 프로젝트 되시겠다.

결론부터 이야기하자면 아주 성공적인 결과를 내지는 못했다. 유감스럽게도 논문 피인용수를 예측하는 것 자체가 물리적으로 거의 불가능에 가까운 일이었던데다, 막 분석의 기초를 배우고 있던 그당시엔 큰 데이터를 모으는 데만에도 상당한 시간과 노력이 필요했었기 때문이다. (스택오버플로우가 없었다면 곱절은 더 걸렸을듯..)

난생 처음 데이터 분석 프로젝트를 바닥에서부터 진행하면서 많은 우여곡절을 겪었다. 시작할 때엔 “지금 갓 등재한 논문의 향후 2년간 피인용수를 예측하자!”였던 패기넘치는 주제가, 프로젝트를 끝마칠 즈음엔 “지금 갓 등재한 의/생명과학 분야 논문의 향후 2년간 피인용 여부를 예측하자”로 사뭇 바뀌어있기도 했다.

어쨌든, 객관적으로는 아쉬운 결과일지라도 주관적으로는 꽤나 쓸만한 결론이 도출되었기에 간단하게나마 그 과정과 결과를 공개해본다. 결국 최종 분석 주제는 지금 갓 등재된 의/생명과학 분야 논문의 향후 2년간 피인용 여부 예측이었다.

분석 과정에서 쓰인 모든 정보의 출처는 다음과 같다.

  • Elsevier API에서 논문의 citation overview를,
  • MEDLINE/PubMed에서 2013년 12월에 출간된 거의 모든 생명과학/의학 분야 논문의 abstract와 기본정보를,
  • ScimagoJR에서 주요 저널의 H-index, Cites/Doc., Ref./Doc.를,
  • 한국학술지인용색인에서 제공하는 Scopus list에서 저널의 SJR 정보를 가져왔다.

자료를 구하는 과정에서 많은 도움을 주신 Elsevier API 문의 담당자님께 특히 감사의 말씀을 드리고 싶다.

분석에 사용된 코드 및 더욱 자세한 내용은 여기서 볼 수 있다.

거두절미하고, 결론

논문 피인용 여부 예측 모형

사용한 전체 독립변수는 다음과 같다.

  • SJR: 저널 임팩트 팩터의 여러 문제점을 어느정도 해결하기 위해 등장한 인덱스인 듯 하다. 저널 인용 네트워크에서의 중요도를 기반으로 한다고 한다.
  • H-index: 전체 기간동안 *h*번 이상 인용된 논문이 *h*개 이상 있으면 H-index는 *h*가 된다. 통상적으로 사용되는 H-index가 아닌 **저널의** H-index이다. 주의.
  • cite/docs: 최근 2년간 논문당 평균 피인용수
  • ref/docs: 2015년 기준 논문당 평균 인용 논문수

article type과 journal type도 넣고 돌려볼까 했지만, 그다지 연관이 없어서 제외했다.

크게 다섯 가지의 모델을 사용해봤다: 로지스틱 회귀분석, 커널 서포트 벡터 머신(SVM), 랜덤 포레스트, 그래디언트 부스트, 다수결 분류기.

테스트셋 하나에서 각각의 성능은 다음과 같다.

스크린샷 2016-11-20 오전 12.41.13.png

5-fold cross validation한 결과는 아래와 같다.

스크린샷 2016-11-20 오전 12.57.36.png

굳이 복합 모형을 사용할 필요 없이 잘 나온다.

실제 데이터의 모양새와 비교를 해보면 아래와 같다. 초록색 포인트는 출간 직후 2년간 단 한 번도 인용되지 못한 슬픈 논문들이다. 파란색은 적어도 한 번 이상은 인용된 논문들.

X, Y, Z축은 각각 SJR, H-index, cite/docs이다. 근데 그래프를 이리저리 돌려놓아서 뭐가 X고 뭐가 Y인지 헷갈린다

output_24_0.png

그래디언트 부스트 모형이 가장 원래 데이터와 유사하게 예측한 것으로 생각된다.

0.75의 recall과 0.75의 precision으로 꽤 괜찮은 예측을 할 수 있다!

추가 시도 – 예측 성능에 영향이 큰 독립변수만 사용

랜덤 포레스트를 이용해서 각 독립변수의 중요도를 살펴보았다.

스크린샷 2016-11-20 오전 12.42.38.png

Cite/Doc.과 Ref./Doc.가 모형을 먹여살리는 녀석들임을 확인할 수 있었다.

실제로 SJR의 경우에는 피인용 예측에 아무 도움도 되지 않는 정보임을 아래 플롯으로 확인할 수 있었다.

output_8_0.png

가로축이 SJR, 세로축이 인용수이다. 보시다시피 SJR은 각각의 데이터포인트를 분리하는 데에 유용하지 않다.

더 적은 정보를 입력받아서 같은, 혹은 그보다 좋은 예측을 한다면 더 좋은 모형일게다. 이런 생각에 실제로 예측에 도움이 되는 Cite/Doc., Ref./Doc.만을 사용해서 다시 분류 모형을 학습시켜 보았다.

테스트셋 하나에서 각각의 성능은 다음과 같다.

스크린샷 2016-11-20 오전 12.43.28.png

5-fold cross validation 결과는 다음과 같다.

스크린샷 2016-11-20 오전 1.03.01.png

더 적은 정보로도 거의 똑같은 성능을 낼 수 있었다.

Unknown.png

X, Y축을 각각 Cites/Doc., Ref./Doc.으로 두고 모든 데이터를 그래프로 나타내었다. 여기서도 그레디언트 부스트가 제일 좋아보인다.

결론

  • SJR과 같은 정보는 각 논문의 중요성 혹은 피인용을 판단하는 데에는 아무 쓸모가 없다. (네이처에서도 비슷한 내용을 다룬 바 있다) 극히 소수의 소위 ‘스타 논문’에 의해 임팩트 팩터 등의 수치가 결정된다는 것이다.
  • 신기하게도, 과거의 논문당 피인용수 뿐 아니라 인용해온(피인용이 아니다!) 논문 수 역시 논문의 피인용과 관계가 깊었다.
  • 첫 번째와는 상반되게도, 개개의 논문의 정보가 아닌 저널의 정보만으로도 저널의 피인용 여부는 상당히 정확하게 예측할 수 있었다. -> 개개의 논문만의 정보를 추가한다면(교신저자의 H-index 등) 더 좋은 결과를 얻을 수도 있겠다.

분석 과정에서 느낀 점도 간단히 정리해봤다.

  • 모인 데이터를 다루는 행위 그 자체보다는, 양질의 데이터를 얼마나 많이, 빠르게 얻어낼 수 있느냐가 더 중요하다.
  • 운 좋게 고난이도 스킬의 데이터 가공이 없이도 꽤나 괜찮은 예측 성능을 얻어낼 수 있었지만 날 것의 데이터에서 분석가능한 형태의 데이터로 전처리를 하는 것도 상당히 중요할 수 있겠다는 생각을 했다.
  • 무턱대고 분석을 시작하기 전 전체적인 데이터에 대해 감을 잡고 시작하는게 중요하다. 이때 pairplot이 상당한 도움이 된다.

첫 분석임에도 나쁘지 않은 성능이 나와 희망적이다. 몇 군데 손을 보면 더 향상시킬 수도 있을 듯 하다. 내 논문이 나오면 돌려볼만 하겠다.

본인이 학습시킨 예측 모델이 본인 논문의 피인용 가능성을 없다고 판단하는 슬픈 일이 일어나지는 않기를 바라면서.

 


업데이트:

  • SJR이 이전에 적었던 것처럼 “아무 도움도 되지 않는 정보”이지는 않다. 간단히 로그 변환을 하면 단순선형회귀에서 $$R^2=$$ 0.27정도의 설명력을 얻을 수 있다.
  • 이전에 나왔고 최근 들어 퍼블리시된 논문들을 참고하면, 회귀분석을 사용한 인용수 분석이 “물리적으로 불가능한 일”은 아닐 것이다. 정확도가 아쉬울 수는 있겠지만, 유용한 독립변수에 대한 정보를 더 확보할 수만 있다면 해볼만한 분석으로 보인다.

Regularized linear regression

 

import matplotlib.pyplot as plt
import numpy as np
from sklearn.linear_model import Lasso, Ridge, ElasticNet, LinearRegression 

X = np.random.randn(40)
y = np.cos(X) + np.random.rand(40)/3
plt.scatter(X, y)
plt.show()

output_2_0

from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline
def plot_comparison(degree):
    plt.figure(figsize=(9,7))
    models = [("Linear (non-regularized)", LinearRegression()), ("Lasso", Lasso(alpha=0.01)),
              ("Ridge", Ridge(alpha=0.01)), ("Elastic Net", ElasticNet(alpha=0.01, l1_ratio=0.5))]
    for i, (model_name, model) in enumerate(models):
        plt.subplot(2,2,i+1)
        linear_model = make_pipeline(PolynomialFeatures(degree), model)
        linear_model.fit(X.reshape(-1, 1), y)
        plt.scatter(X, y, color="w", edgecolor="k")

        xx = np.linspace(-3, 4, 10000)
        plt.plot(xx, linear_model.predict(xx.reshape(-1, 1)), color="b", linewidth=1)
        plt.xlim(-4, 4)
        plt.ylim(-5, 2)
        plt.title("{} Regression".format(model_name))
    plt.tight_layout()
    plt.show()

plot_comparison(5)

output_4_0

plot_comparison(15)

output_5_0

plot_comparison(30)

output_6_0

XX = np.c_[np.random.randn(40), np.random.randn(40)**2, np.random.randn(40)**3, np.random.rand(40)*3, np.random.rand(40)]
yy = np.cos(X[0]) + np.random.rand(40)/3
alphas, coefs, dual_gaps = Lasso().path(XX, yy, alphas=np.logspace(-4, 1, 8))
plt.figure(figsize=(7,5))
for i in range(5):
    plt.plot(alphas, coefs[i], label=r"X_{}".format(i+1))
plt.axhline(0, color="k", linestyle="--")
plt.semilogx()
plt.title("Lasso path")
plt.legend()
plt.show();

output_7_0