hossam 0.4.18__py3-none-any.whl → 0.4.19__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hossam/__init__.py +68 -27
- hossam/hs_classroom.py +27 -4
- hossam/hs_plot.py +13 -29
- hossam/hs_reg.py +313 -0
- hossam/hs_stats.py +211 -219
- {hossam-0.4.18.dist-info → hossam-0.4.19.dist-info}/METADATA +2 -1
- hossam-0.4.19.dist-info/RECORD +18 -0
- hossam/hs_cluster copy.py +0 -1060
- hossam-0.4.18.dist-info/RECORD +0 -18
- {hossam-0.4.18.dist-info → hossam-0.4.19.dist-info}/WHEEL +0 -0
- {hossam-0.4.18.dist-info → hossam-0.4.19.dist-info}/licenses/LICENSE +0 -0
- {hossam-0.4.18.dist-info → hossam-0.4.19.dist-info}/top_level.txt +0 -0
hossam/hs_stats.py
CHANGED
|
@@ -30,7 +30,8 @@ from scipy.stats import (
|
|
|
30
30
|
wilcoxon,
|
|
31
31
|
pearsonr,
|
|
32
32
|
spearmanr,
|
|
33
|
-
chi2
|
|
33
|
+
chi2,
|
|
34
|
+
jarque_bera
|
|
34
35
|
)
|
|
35
36
|
|
|
36
37
|
import statsmodels.api as sm
|
|
@@ -46,6 +47,7 @@ from pingouin import anova, pairwise_tukey, welch_anova, pairwise_gameshowell
|
|
|
46
47
|
|
|
47
48
|
from .hs_plot import ols_residplot, ols_qqplot, get_default_ax, finalize_plot
|
|
48
49
|
from .hs_prep import unmelt
|
|
50
|
+
from .hs_util import pretty_table
|
|
49
51
|
|
|
50
52
|
# ===================================================================
|
|
51
53
|
# MCAR(결측치 무작위성) 검정
|
|
@@ -212,17 +214,12 @@ def outlier_table(data: DataFrame, *fields: str):
|
|
|
212
214
|
if not fields:
|
|
213
215
|
fields = tuple(data.columns)
|
|
214
216
|
|
|
217
|
+
num_columns = data.select_dtypes(include=np.number).columns
|
|
218
|
+
|
|
215
219
|
result = []
|
|
216
220
|
for f in fields:
|
|
217
221
|
# 숫자 타입이 아니라면 건너뜀
|
|
218
|
-
if
|
|
219
|
-
"int",
|
|
220
|
-
"int32",
|
|
221
|
-
"int64",
|
|
222
|
-
"float",
|
|
223
|
-
"float32",
|
|
224
|
-
"float64",
|
|
225
|
-
]:
|
|
222
|
+
if f not in num_columns:
|
|
226
223
|
continue
|
|
227
224
|
|
|
228
225
|
# 사분위수
|
|
@@ -366,8 +363,10 @@ def describe(data: DataFrame, *fields: str, columns: list | None = None):
|
|
|
366
363
|
- 분포 특성은 왜도 값으로 판정합니다.
|
|
367
364
|
- 로그변환 필요성은 왜도의 절댓값 크기로 판정합니다.
|
|
368
365
|
"""
|
|
366
|
+
num_columns = data.select_dtypes(include=np.number).columns
|
|
367
|
+
|
|
369
368
|
if not fields:
|
|
370
|
-
fields = tuple(
|
|
369
|
+
fields = tuple(num_columns)
|
|
371
370
|
|
|
372
371
|
# 기술통계량 구하기
|
|
373
372
|
desc = data[list(fields)].describe().T
|
|
@@ -383,17 +382,7 @@ def describe(data: DataFrame, *fields: str, columns: list | None = None):
|
|
|
383
382
|
additional_stats = []
|
|
384
383
|
for f in fields:
|
|
385
384
|
# 숫자 타입이 아니라면 건너뜀
|
|
386
|
-
if
|
|
387
|
-
'int',
|
|
388
|
-
'int32',
|
|
389
|
-
'int64',
|
|
390
|
-
'float',
|
|
391
|
-
'float32',
|
|
392
|
-
'float64',
|
|
393
|
-
'int64',
|
|
394
|
-
'float64',
|
|
395
|
-
'float32'
|
|
396
|
-
]:
|
|
385
|
+
if f not in num_columns:
|
|
397
386
|
continue
|
|
398
387
|
|
|
399
388
|
# 사분위수
|
|
@@ -509,6 +498,8 @@ def category_describe(data: DataFrame, *fields: str):
|
|
|
509
498
|
- 숫자형 컬럼은 자동으로 제외됩니다.
|
|
510
499
|
- NaN 값도 하나의 범주로 포함됩니다.
|
|
511
500
|
"""
|
|
501
|
+
num_columns = data.select_dtypes(include=np.number).columns
|
|
502
|
+
|
|
512
503
|
if not fields:
|
|
513
504
|
# 명목형(범주형) 컬럼 선택: object, category, bool 타입
|
|
514
505
|
fields = data.select_dtypes(include=['object', 'category', 'bool']).columns # type: ignore
|
|
@@ -517,14 +508,7 @@ def category_describe(data: DataFrame, *fields: str):
|
|
|
517
508
|
summary = []
|
|
518
509
|
for f in fields:
|
|
519
510
|
# 숫자형 컬럼은 건너뜀
|
|
520
|
-
if
|
|
521
|
-
"int",
|
|
522
|
-
"int32",
|
|
523
|
-
"int64",
|
|
524
|
-
"float",
|
|
525
|
-
"float32",
|
|
526
|
-
"float64",
|
|
527
|
-
]:
|
|
511
|
+
if f in num_columns:
|
|
528
512
|
continue
|
|
529
513
|
|
|
530
514
|
# 각 범주값의 빈도수 계산 (NaN 포함)
|
|
@@ -768,6 +752,7 @@ def equal_var_test(data: DataFrame, columns: list | str | None = None, normal_di
|
|
|
768
752
|
normality_result = normal_test(data[numeric_cols], method="n")
|
|
769
753
|
# 모든 컬럼이 정규분포를 따르는지 확인
|
|
770
754
|
all_normal = normality_result["is_normal"].all()
|
|
755
|
+
normality_method = normality_result["method"].iloc[0]
|
|
771
756
|
normal_dist = all_normal # type: ignore
|
|
772
757
|
|
|
773
758
|
try:
|
|
@@ -779,13 +764,14 @@ def equal_var_test(data: DataFrame, columns: list | str | None = None, normal_di
|
|
|
779
764
|
s, p = levene(*fields)
|
|
780
765
|
|
|
781
766
|
result_df = DataFrame([{
|
|
767
|
+
"normality_method": normality_method,
|
|
768
|
+
"normality_checked": normal_dist,
|
|
782
769
|
"method": method_name,
|
|
783
770
|
"statistic": s,
|
|
784
771
|
"p-value": p,
|
|
785
772
|
"is_equal_var": p > 0.05,
|
|
786
773
|
"n_columns": len(fields),
|
|
787
|
-
"columns": ", ".join(numeric_cols[:len(fields)])
|
|
788
|
-
"normality_checked": normality_checked
|
|
774
|
+
"columns": ", ".join(numeric_cols[:len(fields)])
|
|
789
775
|
}])
|
|
790
776
|
|
|
791
777
|
return result_df
|
|
@@ -853,52 +839,40 @@ def ttest_1samp(data, mean_value: float = 0.0) -> DataFrame:
|
|
|
853
839
|
alternative: list = ["two-sided", "less", "greater"]
|
|
854
840
|
result: list = []
|
|
855
841
|
|
|
856
|
-
#
|
|
857
|
-
|
|
858
|
-
|
|
842
|
+
# 각 대립가설 방향에 대해 t-검정 수행
|
|
843
|
+
for a in alternative:
|
|
844
|
+
try:
|
|
845
|
+
s, p = ttest_1samp(col_data, mean_value, alternative=a) # type: ignore
|
|
846
|
+
|
|
847
|
+
itp = None
|
|
848
|
+
|
|
849
|
+
if a == "two-sided":
|
|
850
|
+
itp = "μ {0} {1}".format("==" if p > 0.05 else "!=", mean_value)
|
|
851
|
+
elif a == "less":
|
|
852
|
+
itp = "μ {0} {1}".format(">=" if p > 0.05 else "<", mean_value)
|
|
853
|
+
else:
|
|
854
|
+
itp = "μ {0} {1}".format("<=" if p > 0.05 else ">", mean_value)
|
|
855
|
+
|
|
856
|
+
result.append({
|
|
857
|
+
"alternative": a,
|
|
858
|
+
"statistic": round(s, 3),
|
|
859
|
+
"p-value": round(p, 4),
|
|
860
|
+
"H0": p > 0.05,
|
|
861
|
+
"H1": p <= 0.05,
|
|
862
|
+
"interpretation": itp,
|
|
863
|
+
})
|
|
864
|
+
except Exception as e:
|
|
859
865
|
result.append({
|
|
860
866
|
"alternative": a,
|
|
861
867
|
"statistic": np.nan,
|
|
862
868
|
"p-value": np.nan,
|
|
863
869
|
"H0": False,
|
|
864
870
|
"H1": False,
|
|
865
|
-
"interpretation": f"검정
|
|
871
|
+
"interpretation": f"검정 실패: {str(e)}"
|
|
866
872
|
})
|
|
867
|
-
else:
|
|
868
|
-
for a in alternative:
|
|
869
|
-
try:
|
|
870
|
-
s, p = ttest_1samp(col_data, mean_value, alternative=a) # type: ignore
|
|
871
|
-
|
|
872
|
-
itp = None
|
|
873
|
-
|
|
874
|
-
if a == "two-sided":
|
|
875
|
-
itp = "μ {0} {1}".format("==" if p > 0.05 else "!=", mean_value)
|
|
876
|
-
elif a == "less":
|
|
877
|
-
itp = "μ {0} {1}".format(">=" if p > 0.05 else "<", mean_value)
|
|
878
|
-
else:
|
|
879
|
-
itp = "μ {0} {1}".format("<=" if p > 0.05 else ">", mean_value)
|
|
880
|
-
|
|
881
|
-
result.append({
|
|
882
|
-
"alternative": a,
|
|
883
|
-
"statistic": round(s, 3),
|
|
884
|
-
"p-value": round(p, 4),
|
|
885
|
-
"H0": p > 0.05,
|
|
886
|
-
"H1": p <= 0.05,
|
|
887
|
-
"interpretation": itp,
|
|
888
|
-
})
|
|
889
|
-
except Exception as e:
|
|
890
|
-
result.append({
|
|
891
|
-
"alternative": a,
|
|
892
|
-
"statistic": np.nan,
|
|
893
|
-
"p-value": np.nan,
|
|
894
|
-
"H0": False,
|
|
895
|
-
"H1": False,
|
|
896
|
-
"interpretation": f"검정 실패: {str(e)}"
|
|
897
|
-
})
|
|
898
873
|
|
|
899
874
|
rdf = DataFrame(result)
|
|
900
875
|
rdf.set_index(["field", "alternative"], inplace=True)
|
|
901
|
-
|
|
902
876
|
return rdf
|
|
903
877
|
|
|
904
878
|
|
|
@@ -984,6 +958,9 @@ def ttest_ind(
|
|
|
984
958
|
# 두 데이터를 DataFrame으로 구성하여 등분산성 검정
|
|
985
959
|
temp_df = DataFrame({'x': x_data, 'y': y_data})
|
|
986
960
|
var_result = equal_var_test(temp_df)
|
|
961
|
+
normality_method = var_result["normality_method"].iloc[0]
|
|
962
|
+
normality_checked = var_result["normality_checked"].iloc[0]
|
|
963
|
+
equal_var_method = var_result["method"].iloc[0]
|
|
987
964
|
equal_var = var_result["is_equal_var"].iloc[0]
|
|
988
965
|
|
|
989
966
|
alternative: list = ["two-sided", "less", "greater"]
|
|
@@ -1009,8 +986,9 @@ def ttest_ind(
|
|
|
1009
986
|
"test": n,
|
|
1010
987
|
"alternative": a,
|
|
1011
988
|
"interpretation": itp,
|
|
1012
|
-
|
|
1013
|
-
|
|
989
|
+
normality_method: normality_checked,
|
|
990
|
+
equal_var_method: equal_var,
|
|
991
|
+
n: round(s, 3), # type: ignore
|
|
1014
992
|
"p-value": round(p, 4), # type: ignore
|
|
1015
993
|
"H0": p > 0.05, # type: ignore
|
|
1016
994
|
"H1": p <= 0.05, # type: ignore
|
|
@@ -1019,12 +997,13 @@ def ttest_ind(
|
|
|
1019
997
|
result.append({
|
|
1020
998
|
"test": "t-test_ind" if equal_var else "Welch's t-test",
|
|
1021
999
|
"alternative": a,
|
|
1022
|
-
"
|
|
1000
|
+
"interpretation": f"검정 실패: {str(e)}",
|
|
1001
|
+
normality_method: normality_checked,
|
|
1002
|
+
equal_var_method: equal_var,
|
|
1003
|
+
n: np.nan,
|
|
1023
1004
|
"p-value": np.nan,
|
|
1024
1005
|
"H0": False,
|
|
1025
|
-
"H1": False
|
|
1026
|
-
"interpretation": f"검정 실패: {str(e)}",
|
|
1027
|
-
"equal_var_checked": var_checked
|
|
1006
|
+
"H1": False
|
|
1028
1007
|
})
|
|
1029
1008
|
|
|
1030
1009
|
rdf = DataFrame(result)
|
|
@@ -1035,7 +1014,7 @@ def ttest_ind(
|
|
|
1035
1014
|
# ===================================================================
|
|
1036
1015
|
# 대응표본 t-검정 또는 Wilcoxon test
|
|
1037
1016
|
# ===================================================================
|
|
1038
|
-
def ttest_rel(x, y,
|
|
1017
|
+
def ttest_rel(x, y, normality: bool | None = None) -> DataFrame:
|
|
1039
1018
|
"""대응표본 t-검정 또는 Wilcoxon signed-rank test를 수행한다.
|
|
1040
1019
|
|
|
1041
1020
|
대응표본 t-검정은 동일 개체에서 측정된 두 시점의 평균 차이를 검정한다.
|
|
@@ -1044,7 +1023,7 @@ def ttest_rel(x, y, parametric: bool | None = None) -> DataFrame:
|
|
|
1044
1023
|
Args:
|
|
1045
1024
|
x (array-like): 첫 번째 측정값의 연속형 데이터 (리스트, Series, ndarray 등).
|
|
1046
1025
|
y (array-like): 두 번째 측정값의 연속형 데이터 (리스트, Series, ndarray 등).
|
|
1047
|
-
|
|
1026
|
+
normality (bool | None, optional): 정규성 가정 여부.
|
|
1048
1027
|
- True: 대응표본 t-검정 (차이의 정규분포 가정)
|
|
1049
1028
|
- False: Wilcoxon signed-rank test (비모수 검정, 더 강건함)
|
|
1050
1029
|
- None: 차이의 정규성을 자동으로 검정하여 판별
|
|
@@ -1097,36 +1076,31 @@ def ttest_rel(x, y, parametric: bool | None = None) -> DataFrame:
|
|
|
1097
1076
|
raise ValueError(f"최소 2개 이상의 대응 데이터가 필요합니다. 현재: {len(x_data)}")
|
|
1098
1077
|
|
|
1099
1078
|
# parametric이 None이면 차이의 정규성을 자동으로 검정
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
#
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
parametric = p_normal > 0.05 # p > 0.05면 정규분포 따름
|
|
1108
|
-
except Exception:
|
|
1109
|
-
# shapiro 실패 시 normaltest 사용
|
|
1110
|
-
try:
|
|
1111
|
-
_, p_normal = normaltest(diff)
|
|
1112
|
-
parametric = p_normal > 0.05
|
|
1113
|
-
except Exception:
|
|
1114
|
-
# 둘 다 실패하면 기본값으로 비모수 검정 사용
|
|
1115
|
-
parametric = False
|
|
1079
|
+
if normality is None:
|
|
1080
|
+
tmp_df = DataFrame({'x': x_data, 'y': y_data})
|
|
1081
|
+
normality_result = normal_test(tmp_df, method="n")
|
|
1082
|
+
# 모든 컬럼이 정규분포를 따르는지 확인
|
|
1083
|
+
all_normal = normality_result["is_normal"].all()
|
|
1084
|
+
normality_method = normality_result["method"].iloc[0]
|
|
1085
|
+
normality = all_normal # type: ignore
|
|
1116
1086
|
|
|
1117
1087
|
alternative: list = ["two-sided", "less", "greater"]
|
|
1118
1088
|
result: list = []
|
|
1119
1089
|
fmt: str = "μ(x) {0} μ(y)"
|
|
1120
1090
|
|
|
1091
|
+
if normality:
|
|
1092
|
+
s, p = ttest_rel(x_data, y_data, alternative=a) # type: ignore
|
|
1093
|
+
else:
|
|
1094
|
+
# Wilcoxon signed-rank test (대응표본용 비모수 검정)
|
|
1095
|
+
n = "Wilcoxon signed-rank"
|
|
1096
|
+
|
|
1121
1097
|
for a in alternative:
|
|
1122
1098
|
try:
|
|
1123
|
-
if
|
|
1099
|
+
if normality:
|
|
1124
1100
|
s, p = ttest_rel(x_data, y_data, alternative=a) # type: ignore
|
|
1125
|
-
n = "t-test_paired"
|
|
1126
1101
|
else:
|
|
1127
1102
|
# Wilcoxon signed-rank test (대응표본용 비모수 검정)
|
|
1128
1103
|
s, p = wilcoxon(x_data, y_data, alternative=a)
|
|
1129
|
-
n = "Wilcoxon signed-rank"
|
|
1130
1104
|
|
|
1131
1105
|
itp = None
|
|
1132
1106
|
|
|
@@ -1140,28 +1114,27 @@ def ttest_rel(x, y, parametric: bool | None = None) -> DataFrame:
|
|
|
1140
1114
|
result.append({
|
|
1141
1115
|
"test": n,
|
|
1142
1116
|
"alternative": a,
|
|
1117
|
+
normality_method: normality,
|
|
1118
|
+
"interpretation": itp,
|
|
1143
1119
|
"statistic": round(s, 3) if not np.isnan(s) else s, # type: ignore
|
|
1144
1120
|
"p-value": round(p, 4) if not np.isnan(p) else p, # type: ignore
|
|
1145
1121
|
"H0": p > 0.05, # type: ignore
|
|
1146
1122
|
"H1": p <= 0.05, # type: ignore
|
|
1147
|
-
"interpretation": itp,
|
|
1148
|
-
"normality_checked": var_checked
|
|
1149
1123
|
})
|
|
1150
1124
|
except Exception as e:
|
|
1151
1125
|
result.append({
|
|
1152
|
-
"test":
|
|
1126
|
+
"test": n,
|
|
1153
1127
|
"alternative": a,
|
|
1128
|
+
normality_method: normality,
|
|
1129
|
+
"interpretation": f"검정 실패: {str(e)}",
|
|
1154
1130
|
"statistic": np.nan,
|
|
1155
1131
|
"p-value": np.nan,
|
|
1156
1132
|
"H0": False,
|
|
1157
|
-
"H1": False
|
|
1158
|
-
"interpretation": f"검정 실패: {str(e)}",
|
|
1159
|
-
"normality_checked": var_checked
|
|
1133
|
+
"H1": False
|
|
1160
1134
|
})
|
|
1161
1135
|
|
|
1162
1136
|
rdf = DataFrame(result)
|
|
1163
1137
|
rdf.set_index(["test", "alternative"], inplace=True)
|
|
1164
|
-
|
|
1165
1138
|
return rdf
|
|
1166
1139
|
|
|
1167
1140
|
|
|
@@ -1170,7 +1143,8 @@ def ttest_rel(x, y, parametric: bool | None = None) -> DataFrame:
|
|
|
1170
1143
|
# ===================================================================
|
|
1171
1144
|
# 일원 분산분석 (One-way ANOVA)
|
|
1172
1145
|
# ===================================================================
|
|
1173
|
-
def oneway_anova(data: DataFrame, dv: str, between: str, alpha: float = 0.05) -> tuple[DataFrame, str, DataFrame | None, str]:
|
|
1146
|
+
#def oneway_anova(data: DataFrame, dv: str, between: str, alpha: float = 0.05) -> tuple[DataFrame, str, DataFrame | None, str]:
|
|
1147
|
+
def oneway_anova(data: DataFrame, dv: str, between: str, alpha: float = 0.05, posthoc: bool = False) -> DataFrame | tuple[DataFrame, DataFrame] :
|
|
1174
1148
|
"""일원분산분석(One-way ANOVA)을 일괄 처리한다.
|
|
1175
1149
|
|
|
1176
1150
|
정규성 및 등분산성 검정을 자동으로 수행한 후,
|
|
@@ -1188,13 +1162,12 @@ def oneway_anova(data: DataFrame, dv: str, between: str, alpha: float = 0.05) ->
|
|
|
1188
1162
|
dv (str): 종속변수(Dependent Variable) 컬럼명.
|
|
1189
1163
|
between (str): 그룹 구분 변수 컬럼명.
|
|
1190
1164
|
alpha (float, optional): 유의수준. 기본값 0.05.
|
|
1165
|
+
posthoc (bool, optional): 사후검정 수행 여부. 기본값 False.
|
|
1191
1166
|
|
|
1192
1167
|
Returns:
|
|
1193
1168
|
tuple:
|
|
1194
1169
|
- anova_df (DataFrame): ANOVA 또는 Welch 결과 테이블(Source, ddof1, ddof2, F, p-unc, np2 등 포함).
|
|
1195
|
-
- anova_report (str): 정규성/등분산 여부와 F, p값, 효과크기를 요약한 보고 문장.
|
|
1196
1170
|
- posthoc_df (DataFrame|None): 사후검정 결과(Tukey HSD 또는 Games-Howell). ANOVA가 유의할 때만 생성.
|
|
1197
|
-
- posthoc_report (str): 사후검정 유무와 유의한 쌍 정보를 요약한 보고 문장.
|
|
1198
1171
|
|
|
1199
1172
|
Examples:
|
|
1200
1173
|
```python
|
|
@@ -1206,7 +1179,7 @@ def oneway_anova(data: DataFrame, dv: str, between: str, alpha: float = 0.05) ->
|
|
|
1206
1179
|
'group': ['A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B']
|
|
1207
1180
|
})
|
|
1208
1181
|
|
|
1209
|
-
anova_df,
|
|
1182
|
+
anova_df, posthoc_df = hs_stats.oneway_anova(df, dv='score', between='group')
|
|
1210
1183
|
|
|
1211
1184
|
# 사후검정결과는 ANOVA가 유의할 때만 생성됨
|
|
1212
1185
|
if posthoc_df is not None:
|
|
@@ -1263,57 +1236,61 @@ def oneway_anova(data: DataFrame, dv: str, between: str, alpha: float = 0.05) ->
|
|
|
1263
1236
|
# ============================================
|
|
1264
1237
|
# 3. ANOVA 수행
|
|
1265
1238
|
# ============================================
|
|
1239
|
+
anova_df: DataFrame
|
|
1240
|
+
anova_method: str
|
|
1241
|
+
|
|
1266
1242
|
if equal_var_satisfied:
|
|
1267
1243
|
# 등분산을 만족할 때 일반적인 ANOVA 사용
|
|
1268
1244
|
anova_method = "ANOVA"
|
|
1269
1245
|
anova_df = anova(data=df_filtered, dv=dv, between=between)
|
|
1246
|
+
en = "Bartlett"
|
|
1270
1247
|
else:
|
|
1271
1248
|
# 등분산을 만족하지 않을 때 Welch's ANOVA 사용
|
|
1272
1249
|
anova_method = "Welch"
|
|
1273
1250
|
anova_df = welch_anova(data=df_filtered, dv=dv, between=between)
|
|
1251
|
+
en = "Levene"
|
|
1274
1252
|
|
|
1275
1253
|
# ANOVA 결과에 메타정보 추가
|
|
1276
1254
|
anova_df.insert(1, 'normality', normality_satisfied)
|
|
1277
|
-
anova_df.insert(2,
|
|
1278
|
-
anova_df
|
|
1255
|
+
anova_df.insert(2, en, equal_var_satisfied)
|
|
1256
|
+
anova_df[anova_method] = anova_df['p-unc'] <= alpha if 'p-unc' in anova_df.columns else False # type: ignore
|
|
1279
1257
|
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
anova_df['significant'] = anova_df['p-unc'] <= alpha
|
|
1258
|
+
if posthoc == False:
|
|
1259
|
+
return anova_df
|
|
1283
1260
|
|
|
1284
1261
|
# ANOVA 결과가 유의한지 확인
|
|
1285
1262
|
p_unc = float(anova_df.loc[0, 'p-unc']) # type: ignore
|
|
1286
1263
|
anova_significant = p_unc <= alpha
|
|
1287
1264
|
|
|
1288
1265
|
# ANOVA 보고 문장 생성
|
|
1289
|
-
def _safe_get(col: str, default: float = np.nan) -> float:
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1266
|
+
# def _safe_get(col: str, default: float = np.nan) -> float:
|
|
1267
|
+
# try:
|
|
1268
|
+
# return float(anova_df.loc[0, col]) if col in anova_df.columns else default # type: ignore
|
|
1269
|
+
# except Exception:
|
|
1270
|
+
# return default
|
|
1294
1271
|
|
|
1295
|
-
df1 = _safe_get('ddof1')
|
|
1296
|
-
df2 = _safe_get('ddof2')
|
|
1297
|
-
fval = _safe_get('F')
|
|
1298
|
-
eta2 = _safe_get('np2')
|
|
1272
|
+
# df1 = _safe_get('ddof1')
|
|
1273
|
+
# df2 = _safe_get('ddof2')
|
|
1274
|
+
# fval = _safe_get('F')
|
|
1275
|
+
# eta2 = _safe_get('np2')
|
|
1299
1276
|
|
|
1300
|
-
anova_sig_text = "그룹별 평균이 다를 가능성이 높습니다." if anova_significant else "그룹별 평균 차이에 대한 근거가 부족합니다."
|
|
1301
|
-
assumption_text = f"정규성은 {'대체로 만족' if normality_satisfied else '충족되지 않았고'}, 등분산성은 {'
|
|
1277
|
+
# anova_sig_text = "그룹별 평균이 다를 가능성이 높습니다." if anova_significant else "그룹별 평균 차이에 대한 근거가 부족합니다."
|
|
1278
|
+
# assumption_text = f"정규성은 {'대체로 만족' if normality_satisfied else '충족되지 않았고'}, 등분산성은 {'충족되었다' if equal_var_satisfied else '충족되지 않았다'}고 판단됩니다."
|
|
1302
1279
|
|
|
1303
|
-
anova_report = (
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
)
|
|
1280
|
+
# anova_report = (
|
|
1281
|
+
# f"{between}별로 {dv} 평균을 비교한 {anova_method} 결과: F({df1:.3f}, {df2:.3f}) = {fval:.3f}, p = {p_unc:.4f}. "
|
|
1282
|
+
# f"해석: {anova_sig_text} {assumption_text}"
|
|
1283
|
+
# )
|
|
1307
1284
|
|
|
1308
|
-
if not np.isnan(eta2):
|
|
1309
|
-
|
|
1285
|
+
# if not np.isnan(eta2):
|
|
1286
|
+
# anova_report += f" 효과 크기(η²p) ≈ {eta2:.3f}, 값이 클수록 그룹 차이가 뚜렷함을 의미합니다."
|
|
1310
1287
|
|
|
1311
1288
|
# ============================================
|
|
1312
1289
|
# 4. 사후검정 (ANOVA 유의할 때만)
|
|
1313
1290
|
# ============================================
|
|
1314
|
-
posthoc_df
|
|
1315
|
-
posthoc_method
|
|
1316
|
-
posthoc_report = "ANOVA 결과가 유의하지 않아 사후검정을 진행하지 않았습니다."
|
|
1291
|
+
posthoc_df: DataFrame
|
|
1292
|
+
posthoc_method: str
|
|
1293
|
+
#posthoc_report = "ANOVA 결과가 유의하지 않아 사후검정을 진행하지 않았습니다."
|
|
1317
1294
|
|
|
1318
1295
|
if anova_significant:
|
|
1319
1296
|
if equal_var_satisfied:
|
|
@@ -1328,38 +1305,39 @@ def oneway_anova(data: DataFrame, dv: str, between: str, alpha: float = 0.05) ->
|
|
|
1328
1305
|
# 사후검정 결과에 메타정보 추가
|
|
1329
1306
|
# posthoc_df.insert(0, 'normality', normality_satisfied)
|
|
1330
1307
|
# posthoc_df.insert(1, 'equal_var', equal_var_satisfied)
|
|
1331
|
-
posthoc_df.insert(0, 'method', posthoc_method)
|
|
1308
|
+
posthoc_df.insert(0, 'method', posthoc_method) # type: ignore
|
|
1332
1309
|
|
|
1333
1310
|
# p-value 컬럼 탐색
|
|
1334
|
-
p_cols = [c for c in ["p-tukey", "pval", "p-adjust", "p_adj", "p-corr", "p", "p-unc", "pvalue", "p_value"] if c in posthoc_df.columns]
|
|
1311
|
+
p_cols = [c for c in ["p-tukey", "pval", "p-adjust", "p_adj", "p-corr", "p", "p-unc", "pvalue", "p_value"] if c in posthoc_df.columns] # type: ignore
|
|
1335
1312
|
p_col = p_cols[0] if p_cols else None
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
else:
|
|
1357
|
-
|
|
1313
|
+
|
|
1314
|
+
# 유의성 여부 컬럼 추가
|
|
1315
|
+
posthoc_df['significant'] = posthoc_df[p_col] <= alpha if p_col else False # type: ignore
|
|
1316
|
+
|
|
1317
|
+
# if p_col:
|
|
1318
|
+
# sig_pairs_df = posthoc_df[posthoc_df[p_col] <= alpha]
|
|
1319
|
+
# sig_count = len(sig_pairs_df)
|
|
1320
|
+
# total_count = len(posthoc_df)
|
|
1321
|
+
# pair_samples = []
|
|
1322
|
+
# if not sig_pairs_df.empty and {'A', 'B'}.issubset(sig_pairs_df.columns):
|
|
1323
|
+
# pair_samples = [f"{row['A']} vs {row['B']}" for _, row in sig_pairs_df.head(3).iterrows()]
|
|
1324
|
+
|
|
1325
|
+
# if sig_count > 0:
|
|
1326
|
+
# posthoc_report = (
|
|
1327
|
+
# f"{posthoc_method} 사후검정에서 {sig_count}/{total_count}쌍이 의미 있는 차이를 보였습니다 (alpha={alpha})."
|
|
1328
|
+
# )
|
|
1329
|
+
# if pair_samples:
|
|
1330
|
+
# posthoc_report += " 예: " + ", ".join(pair_samples) + " 등."
|
|
1331
|
+
# else:
|
|
1332
|
+
# posthoc_report = f"{posthoc_method} 사후검정에서 추가로 유의한 쌍은 발견되지 않았습니다."
|
|
1333
|
+
# else:
|
|
1334
|
+
# posthoc_report = f"{posthoc_method} 결과는 생성했지만 p-value 정보를 찾지 못해 유의성을 확인할 수 없습니다."
|
|
1358
1335
|
|
|
1359
1336
|
# ============================================
|
|
1360
1337
|
# 5. 결과 반환
|
|
1361
1338
|
# ============================================
|
|
1362
|
-
return anova_df, anova_report, posthoc_df, posthoc_report
|
|
1339
|
+
#return anova_df, anova_report, posthoc_df, posthoc_report
|
|
1340
|
+
return anova_df, posthoc_df
|
|
1363
1341
|
|
|
1364
1342
|
|
|
1365
1343
|
# ===================================================================
|
|
@@ -1640,8 +1618,7 @@ def corr_pairwise(
|
|
|
1640
1618
|
alpha: float = 0.05,
|
|
1641
1619
|
z_thresh: float = 3.0,
|
|
1642
1620
|
min_n: int = 8,
|
|
1643
|
-
linearity_power: tuple[int, ...] = (2,)
|
|
1644
|
-
p_adjust: str = "none",
|
|
1621
|
+
#linearity_power: tuple[int, ...] = (2,)
|
|
1645
1622
|
) -> tuple[DataFrame, DataFrame]:
|
|
1646
1623
|
"""각 변수 쌍에 대해 선형성·이상치 여부를 점검한 뒤 Pearson/Spearman을 자동 선택해 상관을 요약한다.
|
|
1647
1624
|
|
|
@@ -1650,7 +1627,6 @@ def corr_pairwise(
|
|
|
1650
1627
|
2) 단순회귀 y~x에 대해 Ramsey RESET(linearity_power)로 선형성 검정 (모든 p>alpha → 선형성 충족)
|
|
1651
1628
|
3) 선형성 충족이고 양쪽 변수에서 |z|>z_thresh 이상치가 없으면 Pearson, 그 외엔 Spearman 선택
|
|
1652
1629
|
4) 상관계수/유의확률, 유의성 여부, 강도(strong/medium/weak/no correlation) 기록
|
|
1653
|
-
5) 선택적으로 다중비교 보정(p_adjust="fdr_bh" 등) 적용하여 pval_adj와 significant_adj 추가
|
|
1654
1630
|
|
|
1655
1631
|
Args:
|
|
1656
1632
|
data (DataFrame): 분석 대상 데이터프레임.
|
|
@@ -1658,14 +1634,13 @@ def corr_pairwise(
|
|
|
1658
1634
|
alpha (float, optional): 유의수준. 기본 0.05.
|
|
1659
1635
|
z_thresh (float, optional): 이상치 판단 임계값(|z| 기준). 기본 3.0.
|
|
1660
1636
|
min_n (int, optional): 쌍별 최소 표본 크기. 미만이면 계산 생략. 기본 8.
|
|
1661
|
-
linearity_power (tuple[int,...], optional): RESET 검정에서 포함할 차수 집합. 기본 (2,).
|
|
1662
|
-
p_adjust (str, optional): 다중비교 보정 방법. "none" 또는 statsmodels.multipletests 지원값 중 하나(e.g., "fdr_bh"). 기본 "none".
|
|
1637
|
+
#linearity_power (tuple[int,...], optional): RESET 검정에서 포함할 차수 집합. 기본 (2,).
|
|
1663
1638
|
|
|
1664
1639
|
Returns:
|
|
1665
1640
|
tuple[DataFrame, DataFrame]: 두 개의 데이터프레임을 반환.
|
|
1666
1641
|
[0] result_df: 각 변수쌍별 결과 테이블. 컬럼:
|
|
1667
1642
|
var_a, var_b, n, linearity(bool), outlier_flag(bool), chosen('pearson'|'spearman'),
|
|
1668
|
-
corr, pval, significant(bool), strength(str)
|
|
1643
|
+
corr, pval, significant(bool), strength(str)
|
|
1669
1644
|
[1] corr_matrix: 상관계수 행렬 (행과 열에 변수명, 값에 상관계수)
|
|
1670
1645
|
|
|
1671
1646
|
Examples:
|
|
@@ -1709,7 +1684,7 @@ def corr_pairwise(
|
|
|
1709
1684
|
for a, b in combinations(cols, 2):
|
|
1710
1685
|
# 공통 관측치 사용
|
|
1711
1686
|
pair_df = data[[a, b]].dropna()
|
|
1712
|
-
if len(pair_df) <
|
|
1687
|
+
if len(pair_df) < min_n:
|
|
1713
1688
|
# 표본이 너무 적으면 계산하지 않음
|
|
1714
1689
|
rows.append(
|
|
1715
1690
|
{
|
|
@@ -1753,13 +1728,16 @@ def corr_pairwise(
|
|
|
1753
1728
|
try:
|
|
1754
1729
|
X_const = sm.add_constant(x)
|
|
1755
1730
|
model = sm.OLS(y, X_const).fit()
|
|
1756
|
-
pvals = []
|
|
1757
|
-
for pwr in linearity_power:
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
# 모든 차수에서 유의하지 않을 때 선형성 충족으로 간주
|
|
1761
|
-
if len(pvals) > 0:
|
|
1762
|
-
|
|
1731
|
+
# pvals = []
|
|
1732
|
+
# for pwr in linearity_power:
|
|
1733
|
+
# reset = linear_reset(model, power=pwr, use_f=True)
|
|
1734
|
+
# pvals.append(reset.pvalue)
|
|
1735
|
+
# # 모든 차수에서 유의하지 않을 때 선형성 충족으로 간주
|
|
1736
|
+
# if len(pvals) > 0:
|
|
1737
|
+
# linearity_ok = all([pv > alpha for pv in pvals])
|
|
1738
|
+
|
|
1739
|
+
reset = linear_reset(model)
|
|
1740
|
+
linearity_ok = reset.pvalue > alpha
|
|
1763
1741
|
except Exception:
|
|
1764
1742
|
linearity_ok = False
|
|
1765
1743
|
|
|
@@ -1807,16 +1785,8 @@ def corr_pairwise(
|
|
|
1807
1785
|
|
|
1808
1786
|
result_df = DataFrame(rows)
|
|
1809
1787
|
|
|
1810
|
-
# 5) 다중비교 보정 (선택)
|
|
1811
|
-
if p_adjust.lower() != "none" and not result_df.empty:
|
|
1812
|
-
# 유효한 p만 보정
|
|
1813
|
-
mask = result_df["pval"].notna()
|
|
1814
|
-
if mask.any():
|
|
1815
|
-
_, p_adj, _, _ = multipletests(result_df.loc[mask, "pval"], alpha=alpha, method=p_adjust)
|
|
1816
|
-
result_df.loc[mask, "pval_adj"] = p_adj
|
|
1817
|
-
result_df["significant_adj"] = result_df["pval_adj"] <= alpha
|
|
1818
1788
|
|
|
1819
|
-
#
|
|
1789
|
+
# 5) 상관행렬 생성 (result_df 기반)
|
|
1820
1790
|
# 모든 변수를 행과 열로 하는 대칭 행렬 생성
|
|
1821
1791
|
corr_matrix = DataFrame(np.nan, index=cols, columns=cols)
|
|
1822
1792
|
# 대각선: 1 (자기상관)
|
|
@@ -1889,17 +1859,21 @@ def vif_filter(
|
|
|
1889
1859
|
result = data.copy()
|
|
1890
1860
|
return result
|
|
1891
1861
|
|
|
1892
|
-
def _compute_vifs(X_: DataFrame) ->
|
|
1862
|
+
def _compute_vifs(X_: DataFrame, verbose: bool = False) -> DataFrame:
|
|
1893
1863
|
# NA 제거 후 상수항 추가
|
|
1894
1864
|
X_clean = X_.dropna()
|
|
1865
|
+
|
|
1895
1866
|
if X_clean.shape[0] == 0:
|
|
1896
1867
|
# 데이터가 모두 NA인 경우 VIF 계산 불가: NaN 반환
|
|
1897
|
-
return {col: np.nan for col in X_.columns}
|
|
1868
|
+
return DataFrame({col: [np.nan] for col in X_.columns})
|
|
1869
|
+
|
|
1898
1870
|
if X_clean.shape[1] == 1:
|
|
1899
1871
|
# 단일 예측변수의 경우 다른 설명변수가 없으므로 VIF는 1로 간주
|
|
1900
|
-
return {col: 1.0 for col in X_clean.columns}
|
|
1872
|
+
return DataFrame({col: [1.0] for col in X_clean.columns})
|
|
1873
|
+
|
|
1901
1874
|
exog = sm.add_constant(X_clean, prepend=True)
|
|
1902
1875
|
vifs = {}
|
|
1876
|
+
|
|
1903
1877
|
for i, col in enumerate(X_clean.columns, start=0):
|
|
1904
1878
|
# exog의 첫 열은 상수항이므로 변수 인덱스는 +1
|
|
1905
1879
|
try:
|
|
@@ -1907,27 +1881,40 @@ def vif_filter(
|
|
|
1907
1881
|
except Exception:
|
|
1908
1882
|
# 계산 실패 시 무한대로 처리하여 우선 제거 대상으로
|
|
1909
1883
|
vifs[col] = float("inf")
|
|
1910
|
-
|
|
1884
|
+
|
|
1885
|
+
vdf = DataFrame(list(vifs.items()), columns=["Variable", "VIF"])
|
|
1886
|
+
vdf.sort_values("VIF", ascending=False, inplace=True)
|
|
1887
|
+
|
|
1888
|
+
if verbose:
|
|
1889
|
+
pretty_table(vdf) # type: ignore
|
|
1890
|
+
print()
|
|
1891
|
+
|
|
1892
|
+
return vdf
|
|
1911
1893
|
|
|
1912
1894
|
# 반복 제거 루프
|
|
1895
|
+
i = 0
|
|
1913
1896
|
while True:
|
|
1914
1897
|
if X.shape[1] == 0:
|
|
1915
1898
|
break
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1899
|
+
|
|
1900
|
+
print(f"📇 VIF 제거 반복 {i+1}회차\n")
|
|
1901
|
+
vifs = _compute_vifs(X, verbose=verbose)
|
|
1902
|
+
|
|
1919
1903
|
# 모든 변수가 임계값 이하이면 종료
|
|
1920
|
-
|
|
1921
|
-
|
|
1904
|
+
max_vif = vifs.iloc[0]["VIF"]
|
|
1905
|
+
max_key = vifs.iloc[0]["Variable"]
|
|
1906
|
+
|
|
1922
1907
|
if np.isnan(max_vif) or max_vif <= threshold:
|
|
1908
|
+
if i == 0:
|
|
1909
|
+
print("▶ 모든 변수의 VIF가 임계값 이하입니다. 제거할 변수가 없습니다.\n")
|
|
1910
|
+
else:
|
|
1911
|
+
print("▶ 모든 변수의 VIF가 임계값 이하가 되어 종료합니다. 제거된 변수 {0}개\n".format(i))
|
|
1923
1912
|
break
|
|
1913
|
+
|
|
1924
1914
|
# 가장 큰 VIF 변수 제거
|
|
1925
1915
|
X = X.drop(columns=[max_key])
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
if not verbose:
|
|
1929
|
-
final_vifs = _compute_vifs(X) if X.shape[1] > 0 else {}
|
|
1930
|
-
print(final_vifs)
|
|
1916
|
+
print(f"제거된 변수: {max_key} (VIF={max_vif:.2f})")
|
|
1917
|
+
i += 1
|
|
1931
1918
|
|
|
1932
1919
|
# 원본 컬럼 순서 유지하며 제거된 수치형 컬럼만 제외
|
|
1933
1920
|
kept_numeric_cols = list(X.columns)
|
|
@@ -2104,19 +2091,20 @@ def ols_report(
|
|
|
2104
2091
|
var_row = {
|
|
2105
2092
|
"종속변수": yname, # 종속변수 이름
|
|
2106
2093
|
"독립변수": name, # 독립변수 이름
|
|
2107
|
-
"B":
|
|
2094
|
+
"B(비표준화 계수)": np.round(b, 4), # 비표준화 회귀계수(B)
|
|
2108
2095
|
}
|
|
2109
2096
|
# logvar가 True면 exp(B) 컬럼 추가
|
|
2110
2097
|
if 'logvar' in locals() and logvar:
|
|
2111
|
-
var_row["exp(B)"] =
|
|
2098
|
+
var_row["exp(B)"] = np.round(np.exp(b), 4)
|
|
2099
|
+
|
|
2112
2100
|
var_row.update({
|
|
2113
|
-
"표준오차":
|
|
2114
|
-
"
|
|
2115
|
-
"t": f"{t
|
|
2116
|
-
"
|
|
2117
|
-
"significant": p <= alpha, # 유의성 여부 (boolean)
|
|
2118
|
-
"공차": 1 / vif, # 공차(Tolerance = 1/VIF)
|
|
2119
|
-
"vif": vif, # 분산팽창계수
|
|
2101
|
+
"표준오차": np.round(se, 4), # 계수 표준오차
|
|
2102
|
+
"β(표준화 계수)": np.round(beta, 4), # 표준화 회귀계수(β)
|
|
2103
|
+
"t": f"{np.round(t, 4)}{stars}", # t-통계량(+별표)
|
|
2104
|
+
"유의확률": np.round(p, 4), # 계수 유의확률
|
|
2105
|
+
#"significant": p <= alpha, # 유의성 여부 (boolean)
|
|
2106
|
+
#"공차": 1 / vif, # 공차(Tolerance = 1/VIF)
|
|
2107
|
+
"vif": np.round(vif, 4), # 분산팽창계수
|
|
2120
2108
|
})
|
|
2121
2109
|
variables.append(var_row)
|
|
2122
2110
|
|
|
@@ -2135,11 +2123,15 @@ def ols_report(
|
|
|
2135
2123
|
continue
|
|
2136
2124
|
result_dict[key] = value
|
|
2137
2125
|
|
|
2126
|
+
r2 = float(result_dict.get('R-squared', np.nan))
|
|
2127
|
+
adj_r2 = float(result_dict.get('Adj. R-squared', np.nan))
|
|
2128
|
+
r = np.sqrt(r2) if r2 >= 0 else np.nan
|
|
2129
|
+
|
|
2138
2130
|
# 적합도 보고 문자열 구성
|
|
2139
|
-
result_report = f"𝑅({
|
|
2131
|
+
result_report = f"𝑅({r:.3f}), 𝑅^2({r2:.3f}), Adj 𝑅^2({adj_r2:.3f}), 𝐹({float(result_dict['F-statistic']):.3f}), 유의확률({float(result_dict['Prob (F-statistic)']):.3f}), Durbin-Watson({float(result_dict['Durbin-Watson']):.3f})"
|
|
2140
2132
|
|
|
2141
2133
|
# 모형 보고 문장 구성
|
|
2142
|
-
tpl = "%s에 대하여 %s로 예측하는 회귀분석을 실시한 결과, 이 회귀모형은 통계적으로 %s(F(%s,%s) = %
|
|
2134
|
+
tpl = "%s에 대하여 %s로 예측하는 회귀분석을 실시한 결과, 이 회귀모형은 통계적으로 %s(F(%s,%s) = %0.3f, p %s 0.05)."
|
|
2143
2135
|
model_report = tpl % (
|
|
2144
2136
|
rdf["종속변수"][0],
|
|
2145
2137
|
",".join(list(rdf["독립변수"])),
|
|
@@ -2150,27 +2142,27 @@ def ols_report(
|
|
|
2150
2142
|
),
|
|
2151
2143
|
result_dict["Df Model"],
|
|
2152
2144
|
result_dict["Df Residuals"],
|
|
2153
|
-
result_dict["F-statistic"],
|
|
2145
|
+
float(result_dict["F-statistic"]),
|
|
2154
2146
|
"<=" if float(result_dict["Prob (F-statistic)"]) <= 0.05 else ">",
|
|
2155
2147
|
)
|
|
2156
2148
|
|
|
2157
2149
|
# 변수별 보고 문장 리스트 구성
|
|
2158
2150
|
variable_reports = []
|
|
2159
|
-
s_normal = "%s가 1 증가하면 %s가 %.
|
|
2160
|
-
s_log = "%s가 1 증가하면 %s가 약 %.
|
|
2151
|
+
s_normal = "%s가 1 증가하면 %s(이)가 %.3f만큼 변하는 것으로 나타남. (p %s 0.05, %s)"
|
|
2152
|
+
s_log = "%s가 1 증가하면 %s(이)가 약 %.3f배 변하는 것으로 나타남. (p %s 0.05, %s)"
|
|
2161
2153
|
|
|
2162
2154
|
for i in rdf.index:
|
|
2163
2155
|
row = rdf.iloc[i]
|
|
2164
2156
|
if logvar:
|
|
2165
|
-
effect = np.exp(float(row["B"]))
|
|
2157
|
+
effect = np.exp(float(row["B(비표준화 계수)"]))
|
|
2166
2158
|
variable_reports.append(
|
|
2167
2159
|
s_log
|
|
2168
2160
|
% (
|
|
2169
2161
|
row["독립변수"],
|
|
2170
2162
|
row["종속변수"],
|
|
2171
2163
|
effect,
|
|
2172
|
-
"<=" if float(row["
|
|
2173
|
-
"유의함" if float(row["
|
|
2164
|
+
"<=" if float(row["유의확률"]) < 0.05 else ">",
|
|
2165
|
+
"유의함" if float(row["유의확률"]) < 0.05 else "유의하지 않음",
|
|
2174
2166
|
)
|
|
2175
2167
|
)
|
|
2176
2168
|
else:
|
|
@@ -2179,9 +2171,9 @@ def ols_report(
|
|
|
2179
2171
|
% (
|
|
2180
2172
|
row["독립변수"],
|
|
2181
2173
|
row["종속변수"],
|
|
2182
|
-
float(row["B"]),
|
|
2183
|
-
"<=" if float(row["
|
|
2184
|
-
"유의함" if float(row["
|
|
2174
|
+
float(row["B(비표준화 계수)"]),
|
|
2175
|
+
"<=" if float(row["유의확률"]) < 0.05 else ">",
|
|
2176
|
+
"유의함" if float(row["유의확률"]) < 0.05 else "유의하지 않음",
|
|
2185
2177
|
)
|
|
2186
2178
|
)
|
|
2187
2179
|
|
|
@@ -2201,8 +2193,9 @@ def ols_report(
|
|
|
2201
2193
|
# 성능 지표 표 생성 (pdf)
|
|
2202
2194
|
pdf = DataFrame(
|
|
2203
2195
|
{
|
|
2204
|
-
"R": [
|
|
2205
|
-
"R²": [
|
|
2196
|
+
"R": [r],
|
|
2197
|
+
"R²": [r2],
|
|
2198
|
+
"Adj. R²": [adj_r2],
|
|
2206
2199
|
"F": [float(result_dict.get('F-statistic', np.nan))],
|
|
2207
2200
|
"p-value": [float(result_dict.get('Prob (F-statistic)', np.nan))],
|
|
2208
2201
|
"Durbin-Watson": [float(result_dict.get('Durbin-Watson', np.nan))],
|
|
@@ -2337,7 +2330,7 @@ def ols(df: DataFrame, yname: str, report: Literal[False, "summary", "full"] = "
|
|
|
2337
2330
|
# ===================================================================
|
|
2338
2331
|
# 선형성 검정 (Linearity Test)
|
|
2339
2332
|
# ===================================================================
|
|
2340
|
-
def ols_linearity_test(fit: RegressionResultsWrapper, power: int = 2, alpha: float = 0.05) -> DataFrame:
|
|
2333
|
+
def ols_linearity_test(fit: RegressionResultsWrapper, power: int = 2, alpha: float = 0.05, plot: bool = False, title: str | None = None, save_path: str | None = None) -> DataFrame:
|
|
2341
2334
|
"""회귀모형의 선형성을 Ramsey RESET 검정으로 평가한다.
|
|
2342
2335
|
|
|
2343
2336
|
적합된 회귀모형에 대해 Ramsey RESET(Regression Specification Error Test) 검정을 수행하여
|
|
@@ -2432,6 +2425,9 @@ def ols_linearity_test(fit: RegressionResultsWrapper, power: int = 2, alpha: flo
|
|
|
2432
2425
|
"해석": [interpretation]
|
|
2433
2426
|
})
|
|
2434
2427
|
|
|
2428
|
+
if plot:
|
|
2429
|
+
ols_residplot(fit, lowess=True, mse=True, title=title, save_path=save_path)
|
|
2430
|
+
|
|
2435
2431
|
return result_df
|
|
2436
2432
|
|
|
2437
2433
|
|
|
@@ -2473,8 +2469,6 @@ def ols_normality_test(fit: RegressionResultsWrapper, alpha: float = 0.05, plot:
|
|
|
2473
2469
|
- p-value > alpha: 정규성 가정 만족 (귀무가설 채택)
|
|
2474
2470
|
- p-value <= alpha: 정규성 가정 위반 (귀무가설 기각)
|
|
2475
2471
|
"""
|
|
2476
|
-
from scipy.stats import jarque_bera
|
|
2477
|
-
|
|
2478
2472
|
# fit 객체에서 잔차 추출
|
|
2479
2473
|
residuals = fit.resid
|
|
2480
2474
|
n = len(residuals)
|
|
@@ -2669,8 +2663,6 @@ def ols_independence_test(fit: RegressionResultsWrapper, alpha: float = 0.05) ->
|
|
|
2669
2663
|
- 일반적으로 1.5~2.5 범위를 자기상관 없음으로 판단
|
|
2670
2664
|
- 시계열 데이터나 관측치에 순서가 있는 경우 중요한 검정
|
|
2671
2665
|
"""
|
|
2672
|
-
from pandas import DataFrame
|
|
2673
|
-
|
|
2674
2666
|
# Durbin-Watson 통계량 계산
|
|
2675
2667
|
dw_stat = durbin_watson(fit.resid)
|
|
2676
2668
|
|