Python 통계 - T검정 예제

Page content

강의 홍보

통계분석을 활용한 문제해결 과정

  • 비즈니스에서 통계는 그저 툴이다.
    • 통계를 몰라도 물건을 파는데 전혀 문제가 없다.
  • 통계는 객관적인 근거를 확보하여 유효한 의사결정을 내리기 위한 그저 도구 (Tool) 이다.
  • 따라서, 마케팅이나 CRM과 같은 경영이슈에서도 통계는 문제해결을 이한 체계적인 절차를 제공한다.
    • 문제정의
    • 가설수립 및 분석방법 설정
    • 유의수준 및 임계치 설정
    • 분석 및 검정 통계량 산출
    • 결과 해석 및 가설 검증

예제를 활용한 통계분석 예제

  • 데이터 분석에서 가장 자주 사용되는 통계 기법 중의 하나는 t-검정이다.
  • 처음 t-검정을 배울 때는 비슷한 용어들이 많아서 혼동이 오기도 했다.
    • z-검정, t-검정, 분산분석
    • 이 중 2개 이하의 집단에서 평균을 비교하는 것은 z-검정, t-검정은 사실 동일한 분석방법이다.
    • 그러나, 실무에서는 t-검정을 자주 사용한다 (모집단의 분산을 알 수 있는 방법이 없다, 이는 자세히 한번 얘기하도록 하겠다)

(1) 문제 정의

  • 이제 부터 상상의 나래를 펼쳐보자.
    • 이제 온라인 쇼핑몰을 운영하는 사장이다.
    • 마케팅 부서에서는 콜센터를 통해 접수된 클레임 고객에 대한 타겟마케팅을 기획한다.
    • 클레임 고객은 상대적으로 매장을 찾는 횟수가 적어져 이탈위험도가 높을 것이라고 예상되기 때문이다.

(2) 가설설정 및 분석방법

  • 이제 가설 설정을 한다.
  • t검정을 실시할 때는 보통의 경우 평균의 차이는 없는 것으로 정한다.
    • $H_{0}$(귀무가설) = A쇼핑 클레임 고객들과 비클레임 고객들의 방문 횟수 차이는 없다.
    • $H_{1}$(연구가설) = A쇼핑 클레임 고객들과 비클레임 고객들의 방문횟수 차이는 있다.
  • 즉, 두 그룹간의 평균 비교이다.

(3) 데이터 수집 및 분석방법

  • 독립표본 t-검정을 수행하기 위해서는 평균과 등분산 여부, 그리고 t-value(검정 통계량)p-value(유의확률)을 출력한다.
    • 가장 중요한 것은 두 그룹간의 분산이 동일한지 확인할 필요가 있다.
  • 먼저 데이터를 불러온다.
# Mount Google Drive
from google.colab import drive # import drive from google colab

ROOT = "/content/drive"     # default location for the drive
print(ROOT)                 # print content of ROOT (Optional)
drive.mount(ROOT)           # we mount the google drive at /content/drive
/content/drive
Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount("/content/drive", force_remount=True).
# import join used to join ROOT path and MY_GOOGLE_DRIVE_PATH
from os.path import join  

# path to your project on Google Drive
MY_GOOGLE_DRIVE_PATH = 'My Drive/Colab Notebooks/inflearn/Python/Kaggle_Edu/05_statistics'

PROJECT_PATH = join(ROOT, MY_GOOGLE_DRIVE_PATH)
print(PROJECT_PATH)
/content/drive/My Drive/Colab Notebooks/inflearn/Python/Kaggle_Edu/05_statistics
%cd "{PROJECT_PATH}"
/content/drive/My Drive/Colab Notebooks/inflearn/Python/Kaggle_Edu/05_statistics
import pandas as pd
data = pd.read_csv('python_stat/Ashopping.csv', sep=",", encoding='CP949')
data.head()
고객ID 이탈여부 총_매출액 방문빈도 1회_평균매출액 할인권_사용 횟수 총_할인_금액 고객등급 구매유형 클레임접수여부 구매_카테고리_수 거주지역 성별 고객_나이대 거래기간 할인민감여부 멤버쉽_프로그램_가입전_만족도 멤버쉽_프로그램_가입후_만족도 Recency Frequency Monetary 상품_만족도 매장_만족도 서비스_만족도 상품_품질 상품_다양성 가격_적절성 상품_진열_위치 상품_설명_표시 매장_청결성 공간_편의성 시야_확보성 음향_적절성 안내_표지판_설명 친절성 신속성 책임성 정확성 전문성
0 1 0 4007080 17 235711 1 5445 1 4 0 6 6 1 4 1079 0 5 7 7 3 4 6 5 6 7 7 6 7.0 6.0 6 7 6 6 6 6 6 6 6 6
1 2 1 3168400 14 226314 22 350995 2 4 0 4 4 1 1 537 0 2 3 2 3 3 2 5 4 6 7 6 6.0 NaN 7 7 6 6 6 5 3 6 6 6
2 3 0 2680780 18 148932 6 186045 1 4 1 6 6 1 6 1080 0 6 6 7 3 2 4 6 7 6 7 6 7.0 NaN 6 6 6 6 6 7 7 6 6 7
3 4 0 5946600 17 349800 1 5195 1 4 1 5 5 1 6 1019 0 3 5 7 3 5 3 5 5 6 6 6 5.0 6.0 6 6 5 6 6 6 6 6 5 6
4 5 0 13745950 73 188301 9 246350 1 2 0 6 6 0 6 1086 0 5 6 7 6 7 5 6 6 5 6 6 5.0 6.0 5 6 6 6 5 5 6 6 5 6
  • 데이터를 확인했다면, 이제, 클레임 접수여부에 따라 클레임이 없는 (0)고객과, 클레임이 있는 고객(1) 객체에 저장후 두 그룹의 방문빈도를 추출한다.
  • 우선 클레임이 없는 고객을 뽑자.
import numpy as np
no_claim = data[data.클레임접수여부 == 0]
no_claim_array = np.array(no_claim.방문빈도)
no_claim_array
array([ 17,  14,  73,  26,   6,  17,  19,  88,  39,  12,  27,  21,  56,
        16,  21,  28,  17,  15,  48,  42,  33,   9,  32,  15,  23,  24,
        18,  18,  38,  15,  16,  36,   8,  33,  41,  34,  12,  56,  60,
        27,  10,  39,  48,  24,   8,   7,  25,  19,  33,  22,  30,   9,
        80,  18,  15,  50,  71,  15,  66,  41,  18,   5,  78,  30,  14,
        10,  54,  21,  43,  33,  24,  21,  13,  25,  37,  25,  47,  60,
        21,  23,  18,  26,  28,  31,  58,  88,  19,  38,  37,  20,  33,
        18,  15,   7,  29,  30,   6,  16,  10,  23,  10,  14,  18,  11,
        15,  14,  90,   9,   9,   5,  15,  11,  33,   9,  20,   6,  42,
        17,  78,   9,  20,  37,  14,  17,  25,  52,  19,   9,  26,  16,
        49,  32,  24,  40,  44,  13,  16,  25,  16,  47,  27,   9,  15,
         5,  26,  34,  39,  18,  28,  16,  32,  81,  15,  41,   9,  10,
         9,   8,  56,  12,  65,  10,  45,  60,  22,  52,  15,  61,  18,
        12,  23,  93,  28,  12,  39, 103,  11,  28,   6,  35,   6,  86,
        17, 185,  16,  37,  21,  22,  50,  37,  14,  15,  28,  14,  11,
        31,  31,  59,  26,  20,  29,  23,  11,  29,  11, 108,   6,  37,
        29,  12,  11,  10,  18,  36,  73,  18,  22,  14,  22,  13,  31,
        15,  31,  25,  52,  10,  35,   9,   9,   5,  27,  12,  79,  21,
         8,  11,  64,  22,  60,  31,  15,  48,  18,   8,  31,  14,  46,
       102,  10,  34,   4,  23,  43,  45,   9,  18,  26,  12,   8,  81,
        51,  14,  28,  18,  24,  28,  15,  29,   8,   5,  17,  16,  28,
        36,  27,  11,  10,  20,  13,  54,  23,  14,  27,  13,  38,  38,
        79,  27,  97,   8,  10,  41,   6,  19,  16,  21,  46,  23,  39,
        38,  19,   7,   6,  33,  61,  15,  10, 114,  71,  33,  25,  79,
        13,  29,  34,  30,  19,  23,  40,  15,  17,   8,   3,   7,  21,
         9,  40,  46,  24,  73,  38,  56,  13,  13,  30,  46,   6,  30,
        19,   5,  20,  23,   7,  20,  26,  15,  58,  15,  11,  31,  17,
        10,  14,  20,  10,  50,  21,  37,  30,   6,  16,  23,  21, 102,
        26,  43,   8,  15,  10,  14,  71,  60,  45,  25,  49,  50,   9,
        18,  23,  29, 106,  35,  22,  12, 203,  12,  17,  14,  39,  11,
        19,  29,  22,  21,   9,  22,  11,  21,  58,  10,   5,  16,  39,
        13,  33,  13,  14,  13,  18,  42,  11,  29,  28,  35,   9,  21,
        26,  17,  24,   5,  23,  71,  22,  20,  20,   7,  14,  12,  10,
        16,  18,  30,  25,  22,  15,  18,  43,  33,  46,  14,  12,  24,
        23,  18,  23,   9,  13,  17,  22,   7,  18,  15,  39,  22,  22,
        22,   9,  15,  36,  24,  32,  38, 109,  28,  23,  20,  76,  43,
        25,   5,  46,  10,  22,  18,  30,  75,  34,  11,  17,  43,  39,
        84,  42,  41,  26,  32,  31,  37,  15,  50,  22,  19,   6,  19,
        11,   7,  25,  63,  21,  16,  67,  27,   5,  32,  31, 114,  20,
        21,  14,  80,  19,  14,  32,   4,  15,  37,  24,  14,  11,  10,
        20,  30,  10,  19,   6,  26,  26,   9,   4,  66,  16,  24,  29,
        33,  20,  19,  20,  49,  10,  15,  23])
  • 두번째, 클레임이 있는 고객을 뽑아본다.
claim = data[data.클레임접수여부 == 1]
claim_array = np.array(claim.방문빈도)
claim_array
array([ 18,  17, 109,  15,  21,   9,  12,  28,   5,  12,  12,  13,   8,
        18,  27,  34,  23,  19,  29,  10,  19,  28,  23,  30,  14,  20,
         7,  14,  32,  32,  18,  17,  10,  23,  12,   9,  43,  42,   7,
        12,  73,  26,  27,  25,  29,  37,  32,  45,  34,   7,  29,  26,
        27,  33,  27,  28,   8,  45,  66,  14,  73,  24,  13,  54,  24,
        17,  29,  29,  57,  18,  17,  27,  15,  23,  11,   4,  15,  31,
        21,  21,  11,  19,  34,  41,  18,  45,  10,  72,  18,  82,  10,
        26,  17,  37,  17,   8,  26,  11,  28,  17,  12,  23,  37,  19,
         7,  20,   9,  14,  13,  36,  26,  23,  23,  16,  30,  13,  45,
        30,   6,  44,  32,  19,  20,  24,   7,  14,   7,  65,  31,   6,
        37,  91,  17,  12,  23,  61, 162,  23,  21,  12,  14,  10,  18,
        14,   4,   6,  26,  19,  27,  14,   7,  32,  11,  11,  20,   7,
         7,   7,  27,  16,  26,  10,  12,   8,  12,  25,  14,  18,  30,
        33,  86,  20,  22,  10,  34,  36,   9,  68,  28,  14,  61,   9,
        17,   6,  30,  14,  25,  32,  19,  34,  26,  17,  24,  18,  30,
        15,  15,  96,  17,  18,  19,  24,  16,   6,  27,  31,  15,  38,
        11,   7,  65,  15,   7,  23,   7,  15,  34,  17,  20,  19,  10,
        14,  19,  69,   4,  21,  13,  20,  45,  50,  58,   6,  15,  10,
        10,  19,  32,   6,  38,  36,   6,  39,  16,  20,  48,  26,  21,
        22,   7,   8,  28,   8,  31,  13,  46,  20,   8,  49,   9,  48,
        20,  25,  54,  21,   6,  30,  40,  11,  12,  28,  15,  24,  90,
        22,  15,  14,   7,  77,  39,  11,  18,  16,  55,  15,  12,  17,
        23,  15,  33,  15,  18,  28,   7,  17,  34,  27,  44,  35,  67,
        12,  54,  18,  10,  21,  28,  67,   9, 126,  23,  10,  41,  15,
        21,  21,  42,  18,  31,  13,  20,  19,  39,  26,  11,  22,  64,
         4,  27,  21,  14,  30,  25,  18,  11,  10,  70,  24,  18,  19,
        30,  30,  19, 104,  39,  92,  52,  48,  30,  79,  15,  24,  23,
        14,   8,   9,  21,  18,  11,   7,   8,  13,  21,  23,   2,  12,
        12,  52,  22,  20,   6,  22,   6,  24,  12,  18,  20,  11,  12,
        33,   8,  24,  79,  34,   8,  36,  13,  19,   5,  12,  25,  31,
        27,  17,  11,  65,  29,  18,  10,  28,  22,  12,  18,  20,  27,
        17,  20,  17,   8,  38,  18,  25,  10,   7,  18,  11,  55,   5,
        20,  11,  41,  12,  11,  26,  55,  25,   6,  35,  38,  32,  27,
        11,  10,  11,  55,  22,  12,  18,  10,  15,  32,  13,  29,   8,
        17,  83,  15,   7,   8,  11,  27,  21,  18,  15,  19,   8,  16,
        35,  15,  40,   8])
  • 그 후, stats.bartlett() 함수를 이용하여 등분산 검정을 실시한다.
  • 이 때, 주의해야 하는 것이 있다.
  • 등분산 검정의 귀무가설과 대립가설을 다음과 같다.
    • H0 : 등분산이다.
    • H1 : 이분산이다(등분산이 아니다).
  • 즉, 등분산 가정의 귀무가설을 채택하려면 유의확률인 0.05 > p 나와야 한다.
from scipy import stats
stats.bartlett(no_claim_array, claim_array)
BartlettResult(statistic=13.626177910965525, pvalue=0.00022305349806448475)
  • 여기에서 두 고객 그룹간의 등분산 검정 결과 F값은 13.626, 유의확률은 0.05미만으로 귀무가설이 기각된다.
    • 즉, 두 집단의 분산은 동일하지 않은 것으로 나타났다.
  • 등분산성이 나와야 하는데, 나오지 않아서 입문자들이 조금 당혹스러워 할 수 있다.
    • 굳이 당황할 필요는 없다. 분산이 동일하지 않으면 동일하지 않다고 표시만 해두면 된다. (check: equal_var)
  • 이제 ttest_ind를 활용해서 구하도록 하자.
print(stats.ttest_ind(no_claim_array, claim_array, equal_var=False))
Ttest_indResult(statistic=2.595726838875684, pvalue=0.009577734932789503)
  • 독립표본 t-value2.59이며, p-value 0.0095로 나왔다.
    • 이 의미는 두 그룹간의 평균 방문 빈도에 차이가 있음을 의미한다.
  • 조금 더 구체적으로 구해보자.

(4) 시각화

  • 실제 두 그룹간의 차이를 matplotlib을 활용하여 시각화로 구현해본다.
  • 이 때, 각 그룹의 평균(Mean)표준편차(Standard Deviation)를 구한다.
# 평균 계산하기
no_claim_mean = np.mean(no_claim_array)
claim_mean = np.mean(claim_array)
print("클레임이 없는 고객의 평균 방문빈도:", no_claim_mean)
print("클레임이 있는 고객의 평균 방문빈도:", claim_mean)

# 표준편차 구하기
no_claim_std = np.std(no_claim_array)
claim_std = np.std(claim_array)
print("클레임이 없는 고객의 표준편차:", no_claim_std)
print("클레임이 있는 고객의 표준편차:", claim_std)

# 라벨 정리
viz_labels = ['No Claim', 'Claim']
x_pos = np.arange(len(viz_labels))
avg = (no_claim_mean, claim_mean)
error = [no_claim_std, claim_std]
클레임이 없는 고객의 평균 방문빈도: 28.184842883548985
클레임이 있는 고객의 평균 방문빈도: 24.736383442265794
클레임이 없는 고객의 표준편차: 22.7348095052013
클레임이 있는 고객의 표준편차: 19.234427104778828
import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.bar(x_pos, avg,
       yerr = error,
       align='center',
       alpha=0.5,
       ecolor='black',
       capsize=10)
ax.set_ylabel('Avg. Customer Visitation')
ax.set_xticks(x_pos)
ax.set_xticklabels(viz_labels)
ax.set_title('The Customer difference between No Claim and Claim')
ax.yaxis.grid(True)

plt.tight_layout()
plt.show()

png

  • 막대그래프는 평균값을 의미하고, Error bar표준편차를 의미한다.

(5) T검정 해석

  • 먼저 두 고객그룹간의 등분산성 검정 결과 F값은 13.626, p-value0.00022로 도출되었다.
    • 즉, 두 표본의 분산은 동일하지 않다.
  • 따라서, 등분산이 가정되지 않은 t 통계량을 확인하여 가설검증을 수행한다.
    • 분석결과 클레임 접수 경험이 있는 고객(클레임 접수 여부=0)의 평균 방문 횟수는 24.73회, 클레임 접수 경험이 없는 고객(클레임 접수 여부=1)평균 방문 횟수는 28.18회이며, 이는 t-값은 2.60, 유의확률은 0.0096으로 도출되어 클레임 접수 여부에 따라 방문 빈도는 차이가 있다. (유의수준 0.05 이내)
  • 위 결과를 토대로, 클레임 접수 고객이 많으면 많을수록 매장 방문 빈도가 줄어드는 것을 추론할 수 있으므로 클레임 접수 고객이 더 많이 유입되는 마케팅 지원책을 강구해야 한다.

결론

  • 위 내용은 Reference 교재에서 내용을 요약 및 보충하였다.
    • 조금 더 자세한 내용은 교재를 구입하자.
  • 위 과정은 매우 간단한 코드로 누구나 실행해서 참고할 수 있다.
    • 데이터 전처리와 그에 맞는 적절한 기초통계만으로 굉장히 의미있는 인사이트를 도출할 수 있다.
  • 기초통계는 마케팅에서 즉시적으로 사용할 수 있는 기법이다. 또한 어렵지 않음을 기억하자.

Reference

  • 김형수(2020). Step by Step 파이썬 비즈니스 통계분석. 서울: 프레딕스.