데이터셋 : https://raw.githubusercontent.com/YoungjinBD/dataset/main/HR-Employee-Attrition.csv
주어지는 데이터는 IBM의 근무자에 대한 직무정보와 퇴사여부(Attrition)를 정리한 것이다.
| 주요 컬럼 | 설명 |
|---|---|
| Attrition | Yes : 퇴사, No : 퇴사하지 않음 |
데이터에서 ’Attrition’은 퇴사여부에 대한 종속변수에 해당한다.
종속변수의 값을 다음과 같이 수치로 변환해 새 컬럼으로 추가하고 각
범주별 레코드 수를 계산하시오.
(Yes → 1, No → 0)
데이터셋의 데이터타입별 컬럼 개수를 계산하고, 범주형 변수 중 유일한 값을 1개만 가지고 있는 컬럼(변수)을 찾아내어 그 변수를 데이터에서 제거하시오.
원래 데이터에서 숫자형인 컬럼(변수)만 추출하여 새로운 데이터프레임을 생성하고, 각 변수 간 상관계수(피어슨 상관계수)를 구하고 상관계수가 0.9 이상인 두 개의 컬럼을 찾아내어 그 변수 중 하나를 제거하시오.
# 필요한 파이썬 라이브러리 불러오기
import numpy as np
import pandas as pd
import seaborn as sns
from scipy.stats import pearsonr
from itertools import combinations
from statsmodels.stats.proportion import proportion_confint
# csv 파일이 위치한 디렉토리 입력
df = pd.read_csv('https://raw.githubusercontent.com/YoungjinBD/dataset/main/HR-Employee-Attrition.csv')
df## Age Attrition ... YearsSinceLastPromotion YearsWithCurrManager
## 0 41 Yes ... 0 5
## 1 49 No ... 1 7
## 2 37 Yes ... 0 0
## 3 33 No ... 3 0
## 4 27 No ... 2 2
## ... ... ... ... ... ...
## 1465 36 No ... 0 3
## 1466 39 No ... 1 7
## 1467 27 No ... 0 3
## 1468 49 No ... 0 8
## 1469 34 No ... 1 2
##
## [1470 rows x 35 columns]
# 레이블 인코딩
from sklearn.preprocessing import LabelEncoder
df["Attrition_numerical"] = LabelEncoder().fit_transform(df["Attrition"])
df["Attrition_numerical"].value_counts()## Attrition_numerical
## 0 1233
## 1 237
## Name: count, dtype: int64
## <class 'pandas.core.frame.DataFrame'>
## RangeIndex: 1470 entries, 0 to 1469
## Data columns (total 36 columns):
## # Column Non-Null Count Dtype
## --- ------ -------------- -----
## 0 Age 1470 non-null int64
## 1 Attrition 1470 non-null object
## 2 BusinessTravel 1470 non-null object
## 3 DailyRate 1470 non-null int64
## 4 Department 1470 non-null object
## 5 DistanceFromHome 1470 non-null int64
## 6 Education 1470 non-null int64
## 7 EducationField 1470 non-null object
## 8 EmployeeCount 1470 non-null int64
## 9 EmployeeNumber 1470 non-null int64
## 10 EnvironmentSatisfaction 1470 non-null int64
## 11 Gender 1470 non-null object
## 12 HourlyRate 1470 non-null int64
## 13 JobInvolvement 1470 non-null int64
## 14 JobLevel 1470 non-null int64
## 15 JobRole 1470 non-null object
## 16 JobSatisfaction 1470 non-null int64
## 17 MaritalStatus 1470 non-null object
## 18 MonthlyIncome 1470 non-null int64
## 19 MonthlyRate 1470 non-null int64
## 20 NumCompaniesWorked 1470 non-null int64
## 21 Over18 1470 non-null object
## 22 OverTime 1470 non-null object
## 23 PercentSalaryHike 1470 non-null int64
## 24 PerformanceRating 1470 non-null int64
## 25 RelationshipSatisfaction 1470 non-null int64
## 26 StandardHours 1470 non-null int64
## 27 StockOptionLevel 1470 non-null int64
## 28 TotalWorkingYears 1470 non-null int64
## 29 TrainingTimesLastYear 1470 non-null int64
## 30 WorkLifeBalance 1470 non-null int64
## 31 YearsAtCompany 1470 non-null int64
## 32 YearsInCurrentRole 1470 non-null int64
## 33 YearsSinceLastPromotion 1470 non-null int64
## 34 YearsWithCurrManager 1470 non-null int64
## 35 Attrition_numerical 1470 non-null int32
## dtypes: int32(1), int64(26), object(9)
## memory usage: 407.8+ KB
cat_feat = df.select_dtypes('object','category').columns.values
df_cat = df[cat_feat].copy()
df_cat.nunique().sort_values()## Over18 1
## Attrition 2
## Gender 2
## OverTime 2
## BusinessTravel 3
## Department 3
## MaritalStatus 3
## EducationField 6
## JobRole 9
## dtype: int64
# 'Over18' 컬럼이 데이터프레임에 없을 경우 에러를 발생시키지 않고 무시합니다.
# errors='ignore'은 코드 실행 중 예상치 못한 에러를 방지합니다.
df_cat = df_cat.drop(['Over18'], axis=1, errors='ignore')
df_cat## Attrition BusinessTravel ... MaritalStatus OverTime
## 0 Yes Travel_Rarely ... Single Yes
## 1 No Travel_Frequently ... Married No
## 2 Yes Travel_Rarely ... Single Yes
## 3 No Travel_Frequently ... Married Yes
## 4 No Travel_Rarely ... Married No
## ... ... ... ... ... ...
## 1465 No Travel_Frequently ... Married No
## 1466 No Travel_Rarely ... Married No
## 1467 No Travel_Rarely ... Married Yes
## 1468 No Travel_Frequently ... Married No
## 1469 No Travel_Rarely ... Married No
##
## [1470 rows x 8 columns]
# 'Over18' 컬럼이 데이터프레임에 없을 경우 에러를 발생시키지 않고 무시합니다.
# errors='ignore'은 코드 실행 중 예상치 못한 에러를 방지합니다.
df_cat = df_cat.drop(['Over18'], axis=1, errors='ignore')
df_cat## Attrition BusinessTravel ... MaritalStatus OverTime
## 0 Yes Travel_Rarely ... Single Yes
## 1 No Travel_Frequently ... Married No
## 2 Yes Travel_Rarely ... Single Yes
## 3 No Travel_Frequently ... Married Yes
## 4 No Travel_Rarely ... Married No
## ... ... ... ... ... ...
## 1465 No Travel_Frequently ... Married No
## 1466 No Travel_Rarely ... Married No
## 1467 No Travel_Rarely ... Married Yes
## 1468 No Travel_Frequently ... Married No
## 1469 No Travel_Rarely ... Married No
##
## [1470 rows x 8 columns]
df = pd.read_csv('https://raw.githubusercontent.com/YoungjinBD/dataset/main/HR-Employee-Attrition.csv')
# 수치형 컬럼만으로 구성된 데이터셋 만들기
num_feat = df.select_dtypes('number').columns.values
df_num = df[num_feat].copy()
# 각 컬럼 간 상관계수 구하고 상관계수 차원 계산
corr = df_num.corr(method="pearson")
corr.shape## (26, 26)
# 상관계수가 0.9보다 큰 2개의 변수와 상관계수를 출력
for i in range(0, 24) :
for j in range(i+1, 25) :
if (corr.iloc[i, j] >= 0.9) :
print(i, j, corr.iloc[i, j])## 9 11 0.9502999134798473
## <class 'pandas.core.frame.DataFrame'>
## Index: 26 entries, Age to YearsWithCurrManager
## Data columns (total 26 columns):
## # Column Non-Null Count Dtype
## --- ------ -------------- -----
## 0 Age 24 non-null float64
## 1 DailyRate 24 non-null float64
## 2 DistanceFromHome 24 non-null float64
## 3 Education 24 non-null float64
## 4 EmployeeCount 0 non-null float64
## 5 EmployeeNumber 24 non-null float64
## 6 EnvironmentSatisfaction 24 non-null float64
## 7 HourlyRate 24 non-null float64
## 8 JobInvolvement 24 non-null float64
## 9 JobLevel 24 non-null float64
## 10 JobSatisfaction 24 non-null float64
## 11 MonthlyIncome 24 non-null float64
## 12 MonthlyRate 24 non-null float64
## 13 NumCompaniesWorked 24 non-null float64
## 14 PercentSalaryHike 24 non-null float64
## 15 PerformanceRating 24 non-null float64
## 16 RelationshipSatisfaction 24 non-null float64
## 17 StandardHours 0 non-null float64
## 18 StockOptionLevel 24 non-null float64
## 19 TotalWorkingYears 24 non-null float64
## 20 TrainingTimesLastYear 24 non-null float64
## 21 WorkLifeBalance 24 non-null float64
## 22 YearsAtCompany 24 non-null float64
## 23 YearsInCurrentRole 24 non-null float64
## 24 YearsSinceLastPromotion 24 non-null float64
## 25 YearsWithCurrManager 24 non-null float64
## dtypes: float64(26)
## memory usage: 6.5+ KB
# 0.9 이상인 것 중에서 1개 제거(JobLevel)
df_num = df_num.drop(["JobLevel"], axis=1, errors='ignore')
df_num.info()## <class 'pandas.core.frame.DataFrame'>
## RangeIndex: 1470 entries, 0 to 1469
## Data columns (total 25 columns):
## # Column Non-Null Count Dtype
## --- ------ -------------- -----
## 0 Age 1470 non-null int64
## 1 DailyRate 1470 non-null int64
## 2 DistanceFromHome 1470 non-null int64
## 3 Education 1470 non-null int64
## 4 EmployeeCount 1470 non-null int64
## 5 EmployeeNumber 1470 non-null int64
## 6 EnvironmentSatisfaction 1470 non-null int64
## 7 HourlyRate 1470 non-null int64
## 8 JobInvolvement 1470 non-null int64
## 9 JobSatisfaction 1470 non-null int64
## 10 MonthlyIncome 1470 non-null int64
## 11 MonthlyRate 1470 non-null int64
## 12 NumCompaniesWorked 1470 non-null int64
## 13 PercentSalaryHike 1470 non-null int64
## 14 PerformanceRating 1470 non-null int64
## 15 RelationshipSatisfaction 1470 non-null int64
## 16 StandardHours 1470 non-null int64
## 17 StockOptionLevel 1470 non-null int64
## 18 TotalWorkingYears 1470 non-null int64
## 19 TrainingTimesLastYear 1470 non-null int64
## 20 WorkLifeBalance 1470 non-null int64
## 21 YearsAtCompany 1470 non-null int64
## 22 YearsInCurrentRole 1470 non-null int64
## 23 YearsSinceLastPromotion 1470 non-null int64
## 24 YearsWithCurrManager 1470 non-null int64
## dtypes: int64(25)
## memory usage: 287.2 KB
데이터셋 : https://raw.githubusercontent.com/YoungjinBD/dataset/main/Parkinsons.csv
환자들의 뇌를 촬영한 사진의 상태를 기록한 자료에 각 환자의 상태를 status(1: 파킨슨병 진단, 0: 파킨슨병 아님)로 추가한 테이블이다.
데이터셋을 이용하여 파킨슨병을 예측하는 모델을 로지스틱 회귀모형을 적용하여 생성하고, 이때 파킨슨병을 예측하는데 영향을 미치는 변수를 중요한 순서대로 3개 선정하시오.
이 모델에서 파킨슨병으로 진단하는 기준(threshold, 또는 cutoff)을 0.5로
했을 때와 0.8로 했을 때의 F1-스코어를 비교하고 해석하시오.
(단, 다음 조건을 지켜서 물음에 답하시오.)
분석 조건
# 구글 코랩 환경을 기준으로 하고 있습니다.
# 데이터 처리
import numpy as np
import pandas as pd
# 머신러닝 알고리즘 및 평가
import statsmodels.api as sm
from sklearn.model_selection import train_test_split
import sklearn.preprocessing as preprocessing
from sklearn import metrics
from sklearn.metrics import f1_score
dat = pd.read_csv("https://raw.githubusercontent.com/YoungjinBD/dataset/main/Parkinsons.csv")
dat.head(10)## name MDVP:Fo(Hz) MDVP:Fhi(Hz) ... spread2 D2 PPE
## 0 phon_R01_S01_1 119.992 157.302 ... 0.266482 2.301442 0.284654
## 1 phon_R01_S01_2 122.400 148.650 ... 0.335590 2.486855 0.368674
## 2 phon_R01_S01_3 116.682 131.111 ... 0.311173 2.342259 0.332634
## 3 phon_R01_S01_4 116.676 137.871 ... 0.334147 2.405554 0.368975
## 4 phon_R01_S01_5 116.014 141.781 ... 0.234513 2.332180 0.410335
## 5 phon_R01_S01_6 120.552 131.162 ... 0.299111 2.187560 0.357775
## 6 phon_R01_S02_1 120.267 137.244 ... 0.257682 1.854785 0.211756
## 7 phon_R01_S02_2 107.332 113.840 ... 0.183721 2.064693 0.163755
## 8 phon_R01_S02_3 95.730 132.068 ... 0.327769 2.322511 0.231571
## 9 phon_R01_S02_4 95.056 120.103 ... 0.325996 2.432792 0.271362
##
## [10 rows x 24 columns]
# 데이터프레임의 전처리 및 상수 항 추가 과정을 수행
from sklearn.preprocessing import minmax_scale
import statsmodels.api as sm
import pandas as pd
# 'name' 변수 제거
dat_processing = dat.drop(columns=['name'], errors='ignore')
# 정규화 (Min-Max Scaling) 및 상수 열 추가
dat_processed = pd.DataFrame(
minmax_scale(dat_processing), columns=dat_processing.columns
)
dat_processed = sm.add_constant(dat_processed, has_constant='add')
# 결과 확인
print(dat_processed.head(10))## const MDVP:Fo(Hz) MDVP:Fhi(Hz) ... spread2 D2 PPE
## 0 1.0 0.184308 0.112592 ... 0.585765 0.390661 0.497310
## 1 1.0 0.198327 0.094930 ... 0.741337 0.473145 0.671326
## 2 1.0 0.165039 0.059128 ... 0.686371 0.408819 0.596682
## 3 1.0 0.165004 0.072927 ... 0.738089 0.436977 0.671949
## 4 1.0 0.161150 0.080909 ... 0.513798 0.404336 0.757611
## 5 1.0 0.187568 0.059232 ... 0.659218 0.339999 0.648753
## 6 1.0 0.185909 0.071647 ... 0.565955 0.191959 0.346328
## 7 1.0 0.110606 0.023873 ... 0.399458 0.285340 0.246912
## 8 1.0 0.043063 0.061082 ... 0.723731 0.400034 0.387368
## 9 1.0 0.039139 0.036658 ... 0.719740 0.449094 0.469780
##
## [10 rows x 24 columns]
# 데이터프레임의 모든 컬럼 중 'status' 컬럼을 제외한 나머지 컬럼 이름을 리스트로 저장
feature_columns = list(dat_processed.columns.difference(["status"]))
# 타깃(target) 변수인 'status' 컬럼을 범주형 데이터(category)로 변환하여 y에 저장.
X = dat_processed[feature_columns]
y = dat_processed['status'].astype('category') # 질환여부: 1 or 0
# train_test_split 함수를 이용하여 학습 데이터와 검증 데이터로 9:1로 나누어 데이터를 구분
train_x, test_x, train_y, test_y = train_test_split(X, y, stratify=y, test_size=0.1, random_state=2017010500)
train_x.shape, test_x.shape, train_y.shape, test_y.shape## ((175, 23), (20, 23), (175,), (20,))
import statsmodels.api as sm
# 로지스틱 회귀 모델 생성 및 학습
logit_model = sm.Logit(train_y, train_x)
results = logit_model.fit(method='bfgs', maxiter=1000)## Optimization terminated successfully.
## Current function value: 0.224375
## Iterations: 315
## Function evaluations: 316
## Gradient evaluations: 316
## Logit Regression Results
## ==============================================================================
## Dep. Variable: status No. Observations: 175
## Model: Logit Df Residuals: 152
## Method: MLE Df Model: 22
## Date: 토, 01 2 2025 Pseudo R-squ.: 0.5976
## Time: 13:50:56 Log-Likelihood: -39.266
## converged: True LL-Null: -97.576
## Covariance Type: nonrobust LLR p-value: 7.140e-15
## ====================================================================================
## coef std err z P>|z| [0.025 0.975]
## ------------------------------------------------------------------------------------
## D2 2.8218 3.448 0.818 0.413 -3.936 9.580
## DFA 1.4404 2.320 0.621 0.535 -3.107 5.987
## HNR 3.0901 6.002 0.515 0.607 -8.674 14.854
## Jitter:DDP 45.6740 2956.389 0.015 0.988 -5748.741 5840.089
## MDVP:APQ 31.0449 53.564 0.580 0.562 -73.939 136.029
## MDVP:Fhi(Hz) -1.2380 1.961 -0.631 0.528 -5.082 2.606
## MDVP:Flo(Hz) 1.0740 2.186 0.491 0.623 -3.211 5.359
## MDVP:Fo(Hz) -4.5819 3.936 -1.164 0.244 -12.297 3.133
## MDVP:Jitter(%) -39.0447 40.449 -0.965 0.334 -118.323 40.233
## MDVP:Jitter(Abs) -14.3703 23.721 -0.606 0.545 -60.862 32.122
## MDVP:PPQ -55.8013 38.159 -1.462 0.144 -130.592 18.989
## MDVP:RAP 45.8953 2955.587 0.016 0.988 -5746.948 5838.739
## MDVP:Shimmer -40.6224 119.684 -0.339 0.734 -275.199 193.954
## MDVP:Shimmer(dB) 74.3763 53.297 1.395 0.163 -30.085 178.837
## NHR 9.2313 17.548 0.526 0.599 -25.163 43.625
## PPE 18.3958 13.277 1.386 0.166 -7.627 44.418
## RPDE -1.9728 2.192 -0.900 0.368 -6.270 2.324
## Shimmer:APQ3 -10.7878 6499.355 -0.002 0.999 -1.27e+04 1.27e+04
## Shimmer:APQ5 5.6487 36.853 0.153 0.878 -66.582 77.880
## Shimmer:DDA -12.3804 6500.456 -0.002 0.998 -1.28e+04 1.27e+04
## const -6.7595 5.692 -1.187 0.235 -17.916 4.397
## spread1 0.4163 10.613 0.039 0.969 -20.384 21.217
## spread2 4.8739 2.833 1.721 0.085 -0.678 10.426
## ====================================================================================
##
## Possibly complete quasi-separation: A fraction 0.17 of observations can be
## perfectly predicted. This might indicate that there is complete
## quasi-separation. In this case some parameters will not be identified.
결과 해석
반복 횟수(Iterations: 315), 함수 평가(Function evaluations: 316), 그리고 기울기 평가(Gradient evaluations: 316)를 통해 최적화를 수행했습니다.
Current function value: 0.224375
Pseudo R-squared: 0.5976
모형의 설명력을 나타내는 지표로, 일반적인 결정 계수(R²)의 대안입니다.
값이 1에 가까울수록 종속 변수를 잘 설명한다고 볼 수 있습니다. 여기서는 약 59.76%의 설명력을 가집니다.
Log-Likelihood: -39.266
Covariance Type: nonrobust
회귀 계수 (coef)
의미: 각 독립 변수(feature)가 종속 변수(status)에 미치는 영향을 나타냅니다.
양수: 변수 값이 증가하면 결과(status)가 1일 가능성이 증가.
음수: 변수 값이 증가하면 결과(status)가 0일 가능성이 증가.
P>|z| (p-value)
의미:
각 변수의 계수가 통계적으로 유의미한지 나타냅니다.
일반적으로 P>|z| < 0.05일 때 유의미하다고 판단합니다.
결과 해석:
대부분의 변수에서 P값이 0.05보다 크므로, 통계적으로 유의미하지 않은 변수가 많습니다.
HNR은 경계선에서 유의미할 가능성이 있으므로 주목할 수
있습니다.
“Possibly complete quasi-separation”:
데이터가 특정 변수 조합에 따라 완벽하게 분리(quasi-separation)되었을 가능성을 시사합니다.
이는 일부 관측치가 완벽히 예측 가능하며, 특정 계수를 신뢰할 수 없음을 의미합니다.
이로 인해 일부 계수가 추정되지 않을 수 있습니다.
개선 방안
변수 선택: 유의미하지 않은 변수를 제거하거나 변수 선택 기법(예: Lasso)을 적용해 모델을 단순화할 수 있습니다.
데이터 처리:
변수 간 다중공선성(multicollinearity)을 점검하여 이를 해결.
상관관계가 높은 변수를 제거하거나 결합하여 새로운 변수를 생성.
모델 변경: 로지스틱 회귀 외에 다른 비선형 모델(예: Random Forest, Gradient Boosting)을 고려해 성능을 비교.
결론
현재 모델은 Pseudo R-squared = 0.5976으로 적절한 성능을
보이지만, 다수의 독립 변수가 통계적으로 유의미하지 않아
추가적인 변형이 필요합니다.
특히, 경고 메시지를 고려해 데이터 구조를 분석하고 적절한 전처리를 수행하는 것이 중요합니다.
데이터를 사용해 문제를 풀려고 했는데, 불필요한 정보를 너무 많이 넣어서 방해받는 상황입니다. 불필요한 정보를 정리하거나 더 똑똑한 방법(새로운 모델)을 사용하면 결과가 더 좋아질 겁니다!
# 로지스틱 회귀 결과를 사용해 예측값을 생성하고, 이를 기반으로 모델 성능(F1 점수)을 계산
from sklearn.metrics import f1_score
# cut_off 함수 정의
def cut_off(y, threshold=0.5):
y = y.copy()
y[y >= threshold] = 1
y[y < threshold] = 0
return y.astype(int)
# 테스트 데이터 예측 확률 및 이진화
test_y_pred_prob = results.predict(test_x) # 예측 확률 생성
test_y_pred = cut_off(test_y_pred_prob, threshold=0.8) # 80% 기준 이진화
# F1 점수 계산
f1 = f1_score(test_y, test_y_pred)
print(f"F1 Score: {f1}")## F1 Score: 0.7857142857142857
F1 점수 0.7857은 모델이 정밀도와 재현율 간 균형 잡힌 성능을 보이고 있음을 의미합니다.
Threshold 값 0.8로 설정해 “1”로 예측하는 기준을 높였으며, 이는 정밀도를 증가시키고 재현율은 다소 낮췄을 가능성이 있습니다.
성능을 향상시키기 위해 데이터 전처리, 하이퍼파라미터 튜닝, 또는 Threshold 조정을 고려할 수 있습니다.
데이터셋 : https://raw.githubusercontent.com/YoungjinBD/dataset/main/HR-Employee-Attrition.csv
결혼유무(미혼, 결혼, 이혼)에 따라 초과근로 여부에 차이가 있는지 카이제곱 검정을 이용하여 확인하시오.
주어진 데이터로 결혼유무와 초과근로 간의 분할표를 만들고, 결혼한 집단의 초과근로자 수와 초과근로 하지 않은 자 수의 차이를 정수로 계산하시오.
가설검정을 위한 검정통계량을 소수점 둘째자리까지 반올림하여 구하시오.
통계량에 대한 p-value 값을 소수점 넷째자리까지 반올림하여 구하고, 유의수준 0.05 내에서 결과를 논의하시오. (채택과 기각 중 선택)
import pandas as pd
import scipy.stats as stats
# csv 파일 위치 입력
df = pd.read_csv('https://raw.githubusercontent.com/YoungjinBD/dataset/main/HR-Employee-Attrition.csv')
df[['MaritalStatus','OverTime']]## MaritalStatus OverTime
## 0 Single Yes
## 1 Married No
## 2 Single Yes
## 3 Married Yes
## 4 Married No
## ... ... ...
## 1465 Married No
## 1466 Married No
## 1467 Married Yes
## 1468 Married No
## 1469 Married No
##
## [1470 rows x 2 columns]
## MaritalStatus Divorced Married Single
## OverTime
## No 228 487 339
## Yes 99 186 131
초과근무 여부(OverTime)가 “Yes” 또는 “No”인 그룹의 결혼 상태 데이터를 분리합니다.
초과근무 여부에 따른 “기혼(Married)” 상태의 인원 수를 계산합니다.
# X1: 초과근로자에 대한 결혼 여부
# X2: 초과근로 하지 않은 자의 결혼 여부
X1 = table.loc['Yes', :]
X2 = table.loc['No', :]
print(X1)## MaritalStatus
## Divorced 99
## Married 186
## Single 131
## Name: Yes, dtype: int64
## MaritalStatus
## Divorced 228
## Married 487
## Single 339
## Name: No, dtype: int64
## 301
카이제곱 독립성 검정 (Chi-squared Test for Independence).
귀무가설 (H₀): 두 변수는 독립적이다.
대립가설 (H₁): 두 변수는 독립적이지 않다.
## 0.82
## 0.6647
## 채택