hossam 0.4.17__tar.gz → 0.4.19__tar.gz
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-0.4.17/hossam.egg-info → hossam-0.4.19}/PKG-INFO +2 -1
- {hossam-0.4.17 → hossam-0.4.19}/hossam/__init__.py +78 -18
- {hossam-0.4.17 → hossam-0.4.19}/hossam/hs_classroom.py +27 -4
- {hossam-0.4.17 → hossam-0.4.19}/hossam/hs_cluster.py +99 -21
- {hossam-0.4.17 → hossam-0.4.19}/hossam/hs_plot.py +36 -40
- hossam-0.4.19/hossam/hs_reg.py +313 -0
- {hossam-0.4.17 → hossam-0.4.19}/hossam/hs_stats.py +250 -221
- {hossam-0.4.17 → hossam-0.4.19/hossam.egg-info}/PKG-INFO +2 -1
- {hossam-0.4.17 → hossam-0.4.19}/hossam.egg-info/SOURCES.txt +1 -0
- {hossam-0.4.17 → hossam-0.4.19}/hossam.egg-info/requires.txt +1 -0
- {hossam-0.4.17 → hossam-0.4.19}/pyproject.toml +3 -2
- {hossam-0.4.17 → hossam-0.4.19}/LICENSE +0 -0
- {hossam-0.4.17 → hossam-0.4.19}/MANIFEST.in +0 -0
- {hossam-0.4.17 → hossam-0.4.19}/README.md +0 -0
- {hossam-0.4.17 → hossam-0.4.19}/hossam/NotoSansKR-Regular.ttf +0 -0
- {hossam-0.4.17 → hossam-0.4.19}/hossam/hs_gis.py +0 -0
- {hossam-0.4.17 → hossam-0.4.19}/hossam/hs_prep.py +0 -0
- {hossam-0.4.17 → hossam-0.4.19}/hossam/hs_study.py +0 -0
- {hossam-0.4.17 → hossam-0.4.19}/hossam/hs_timeserise.py +0 -0
- {hossam-0.4.17 → hossam-0.4.19}/hossam/hs_util.py +0 -0
- {hossam-0.4.17 → hossam-0.4.19}/hossam/leekh.png +0 -0
- {hossam-0.4.17 → hossam-0.4.19}/hossam.egg-info/dependency_links.txt +0 -0
- {hossam-0.4.17 → hossam-0.4.19}/hossam.egg-info/top_level.txt +0 -0
- {hossam-0.4.17 → hossam-0.4.19}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hossam
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.19
|
|
4
4
|
Summary: Hossam Data Helper
|
|
5
5
|
Author-email: Lee Kwang-Ho <leekh4232@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -40,6 +40,7 @@ Requires-Dist: xgboost
|
|
|
40
40
|
Requires-Dist: lightgbm
|
|
41
41
|
Requires-Dist: catboost
|
|
42
42
|
Requires-Dist: kneed
|
|
43
|
+
Requires-Dist: shap
|
|
43
44
|
Dynamic: license-file
|
|
44
45
|
|
|
45
46
|
---
|
|
@@ -9,11 +9,19 @@ from . import hs_prep
|
|
|
9
9
|
from . import hs_stats
|
|
10
10
|
from . import hs_timeserise
|
|
11
11
|
from . import hs_util
|
|
12
|
+
from . import hs_reg
|
|
12
13
|
from . import hs_cluster
|
|
13
14
|
from . import hs_study
|
|
14
15
|
from .hs_util import load_info
|
|
15
16
|
from .hs_util import _load_data_remote as load_data
|
|
16
17
|
from .hs_plot import visualize_silhouette
|
|
18
|
+
from .hs_stats import ttest_ind as hs_ttest_ind
|
|
19
|
+
from .hs_stats import outlier_table as hs_outlier_table
|
|
20
|
+
from .hs_stats import oneway_anova as hs_oneway_anova
|
|
21
|
+
from .hs_reg import learning_cv as hs_learning_cv
|
|
22
|
+
from .hs_reg import get_scores as hs_get_scores
|
|
23
|
+
from .hs_reg import get_score_cv as hs_get_score_cv
|
|
24
|
+
from .hs_reg import VIFSelector
|
|
17
25
|
|
|
18
26
|
# py-modules
|
|
19
27
|
import sys
|
|
@@ -31,7 +39,29 @@ except Exception:
|
|
|
31
39
|
|
|
32
40
|
my_dpi = hs_plot.config.dpi
|
|
33
41
|
|
|
34
|
-
__all__ = [
|
|
42
|
+
__all__ = [
|
|
43
|
+
"my_dpi",
|
|
44
|
+
"load_data",
|
|
45
|
+
"load_info",
|
|
46
|
+
"hs_classroom",
|
|
47
|
+
"hs_gis",
|
|
48
|
+
"hs_plot",
|
|
49
|
+
"hs_prep",
|
|
50
|
+
"hs_stats",
|
|
51
|
+
"hs_timeserise",
|
|
52
|
+
"hs_util",
|
|
53
|
+
"hs_cluster",
|
|
54
|
+
"hs_reg",
|
|
55
|
+
"hs_study",
|
|
56
|
+
"visualize_silhouette",
|
|
57
|
+
"hs_ttest_ind",
|
|
58
|
+
"hs_outlier_table",
|
|
59
|
+
"hs_oneway_anova",
|
|
60
|
+
"hs_learning_cv",
|
|
61
|
+
"hs_get_scores",
|
|
62
|
+
"hs_get_score_cv",
|
|
63
|
+
"VIFSelector",
|
|
64
|
+
]
|
|
35
65
|
|
|
36
66
|
|
|
37
67
|
def check_pypi_latest(package_name: str):
|
|
@@ -51,7 +81,7 @@ def check_pypi_latest(package_name: str):
|
|
|
51
81
|
"package": package_name,
|
|
52
82
|
"installed": installed,
|
|
53
83
|
"latest": latest,
|
|
54
|
-
"outdated": installed != latest
|
|
84
|
+
"outdated": installed != latest,
|
|
55
85
|
}
|
|
56
86
|
|
|
57
87
|
|
|
@@ -67,21 +97,23 @@ def _init_korean_font():
|
|
|
67
97
|
fprop = fm.FontProperties(fname=str(font_path))
|
|
68
98
|
fname = fprop.get_name()
|
|
69
99
|
|
|
70
|
-
plt.rcParams.update(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
100
|
+
plt.rcParams.update(
|
|
101
|
+
{
|
|
102
|
+
"font.family": fname,
|
|
103
|
+
"font.size": hs_plot.config.font_size,
|
|
104
|
+
"font.weight": hs_plot.config.font_weight,
|
|
105
|
+
"axes.unicode_minus": False,
|
|
106
|
+
"text.antialiased": True,
|
|
107
|
+
"lines.antialiased": True,
|
|
108
|
+
"patch.antialiased": True,
|
|
109
|
+
"figure.dpi": hs_plot.config.dpi,
|
|
110
|
+
"savefig.dpi": hs_plot.config.dpi * 2,
|
|
111
|
+
"text.hinting": "auto",
|
|
112
|
+
"text.hinting_factor": 8,
|
|
113
|
+
"pdf.fonttype": 42,
|
|
114
|
+
"ps.fonttype": 42,
|
|
115
|
+
}
|
|
116
|
+
)
|
|
85
117
|
|
|
86
118
|
print(
|
|
87
119
|
"\n✅ 시각화를 위한 한글 글꼴(NotoSansKR-Regular)이 자동 적용되었습니다."
|
|
@@ -103,6 +135,8 @@ def _init():
|
|
|
103
135
|
f"🔖 Version: {__version__}",
|
|
104
136
|
]
|
|
105
137
|
|
|
138
|
+
|
|
139
|
+
|
|
106
140
|
for msg in messages:
|
|
107
141
|
print(f"{msg}")
|
|
108
142
|
|
|
@@ -119,10 +153,36 @@ def _init():
|
|
|
119
153
|
|
|
120
154
|
_init_korean_font()
|
|
121
155
|
|
|
156
|
+
# 각 열의 넓이 제한 없음
|
|
157
|
+
pd.set_option("display.max_colwidth", None)
|
|
158
|
+
# 출력 너비 제한 없음 (가로 스크롤될 수 있음)
|
|
159
|
+
pd.set_option("display.width", None)
|
|
122
160
|
# 컬럼 생략 금지
|
|
123
161
|
pd.set_option("display.max_columns", None)
|
|
124
162
|
# 행 최대 출력 수 100개로 수정
|
|
125
163
|
pd.set_option("display.max_rows", 100)
|
|
164
|
+
# 소수점 자리수 3자리로 설정
|
|
165
|
+
pd.options.display.float_format = "{:.3f}".format
|
|
166
|
+
|
|
167
|
+
from IPython.display import display, HTML
|
|
168
|
+
|
|
169
|
+
display(
|
|
170
|
+
HTML(
|
|
171
|
+
"""
|
|
172
|
+
<style>
|
|
173
|
+
.dataframe tr:hover {
|
|
174
|
+
background-color: #ffff99 !important;
|
|
175
|
+
border: 1px solid #ffcc00;
|
|
176
|
+
}
|
|
177
|
+
</style>
|
|
178
|
+
"""
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
import multiprocessing as mp
|
|
126
183
|
|
|
184
|
+
def is_parallel_worker():
|
|
185
|
+
return mp.current_process().name != "MainProcess"
|
|
127
186
|
|
|
128
|
-
|
|
187
|
+
if not is_parallel_worker():
|
|
188
|
+
_init()
|
|
@@ -6,6 +6,7 @@ import math
|
|
|
6
6
|
from pandas import DataFrame, qcut, concat, to_numeric
|
|
7
7
|
from kmodes.kmodes import KModes
|
|
8
8
|
from matplotlib import pyplot as plt
|
|
9
|
+
from prompt_toolkit.formatted_text.ansi import i
|
|
9
10
|
import seaborn as sns
|
|
10
11
|
from .hs_util import load_data, pretty_table
|
|
11
12
|
from .hs_plot import config
|
|
@@ -19,6 +20,7 @@ def cluster_students(
|
|
|
19
20
|
n_groups: int,
|
|
20
21
|
score_cols: list | None = None,
|
|
21
22
|
interest_col: str | None = None,
|
|
23
|
+
interest_ignore: str | None = None,
|
|
22
24
|
max_iter: int = 200,
|
|
23
25
|
score_metric: str = 'total'
|
|
24
26
|
) -> DataFrame:
|
|
@@ -39,6 +41,8 @@ def cluster_students(
|
|
|
39
41
|
None일 경우 점수 기반 균형 조정을 하지 않습니다. 기본값: None
|
|
40
42
|
interest_col: 관심사 정보가 있는 컬럼명.
|
|
41
43
|
None일 경우 관심사 기반 군집화를 하지 않습니다. 기본값: None
|
|
44
|
+
interest_ignore: 관심사 군집화에서 제외할 값.
|
|
45
|
+
지정된 값은 별도 군집에서 제외됩니다. 기본값: None
|
|
42
46
|
max_iter: 균형 조정 최대 반복 횟수. 기본값: 200
|
|
43
47
|
score_metric: 점수 기준 선택 ('total' 또는 'average').
|
|
44
48
|
'total'이면 총점, 'average'이면 평균점수 기준. 기본값: 'total'
|
|
@@ -151,8 +155,18 @@ def cluster_students(
|
|
|
151
155
|
if actual_n_groups < 2:
|
|
152
156
|
actual_n_groups = 2
|
|
153
157
|
|
|
158
|
+
df_ignore = None
|
|
159
|
+
|
|
154
160
|
# ===== 3단계: 관심사 기반 1차 군집 =====
|
|
155
161
|
if interest_col is not None:
|
|
162
|
+
df_main[interest_col] = df_main[interest_col].fillna('미정')
|
|
163
|
+
|
|
164
|
+
if interest_ignore is not None:
|
|
165
|
+
df_ignore = df_main[df_main[interest_col] == interest_ignore].copy()
|
|
166
|
+
df_main = df_main[df_main[interest_col] != interest_ignore].copy()
|
|
167
|
+
|
|
168
|
+
print(df_ignore)
|
|
169
|
+
|
|
156
170
|
X_interest = df_main[[interest_col]].to_numpy()
|
|
157
171
|
|
|
158
172
|
kmodes_interest = KModes(
|
|
@@ -184,12 +198,18 @@ def cluster_students(
|
|
|
184
198
|
df_main = _balance_group_sizes_only(df_main, actual_n_groups, min_size, max_size)
|
|
185
199
|
|
|
186
200
|
# ===== 5단계: 극단값 포함 병합 =====
|
|
187
|
-
|
|
201
|
+
result = df_main
|
|
202
|
+
|
|
203
|
+
if (df_outlier is not None and len(df_outlier) > 0):
|
|
188
204
|
# '조'는 숫자형 유지: 극단값은 0으로 표시
|
|
189
205
|
df_outlier['조'] = 0
|
|
190
|
-
result = concat([
|
|
191
|
-
|
|
192
|
-
|
|
206
|
+
result = concat([result, df_outlier], ignore_index=True)
|
|
207
|
+
|
|
208
|
+
if (df_ignore is not None and len(df_ignore) > 0):
|
|
209
|
+
# '조'는 숫자형 유지: 제외된 학생은 -1로 표시
|
|
210
|
+
df_ignore['조'] = -1
|
|
211
|
+
result = concat([result, df_ignore], ignore_index=True)
|
|
212
|
+
|
|
193
213
|
|
|
194
214
|
# 평균점수는 이미 계산됨 (score_cols 있을 때)
|
|
195
215
|
|
|
@@ -694,6 +714,7 @@ def analyze_classroom(
|
|
|
694
714
|
n_groups: int,
|
|
695
715
|
score_cols: list | None = None,
|
|
696
716
|
interest_col: str | None = None,
|
|
717
|
+
interest_ignore: str | None = None,
|
|
697
718
|
max_iter: int = 200,
|
|
698
719
|
score_metric: str = 'average',
|
|
699
720
|
name_col: str = '학생이름',
|
|
@@ -713,6 +734,7 @@ def analyze_classroom(
|
|
|
713
734
|
n_groups: 목표 조의 개수.
|
|
714
735
|
score_cols: 성적 계산에 사용할 점수 컬럼명 리스트. 기본값: None
|
|
715
736
|
interest_col: 관심사 정보가 있는 컬럼명. 기본값: None
|
|
737
|
+
interest_ignore: 관심사 군집화에서 제외할 값. 기본값: None
|
|
716
738
|
max_iter: 균형 조정 최대 반복 횟수. 기본값: 200
|
|
717
739
|
score_metric: 점수 기준 선택 ('total' 또는 'average'). 기본값: 'average'
|
|
718
740
|
name_col: 학생 이름 컬럼명. 기본값: '학생이름'
|
|
@@ -740,6 +762,7 @@ def analyze_classroom(
|
|
|
740
762
|
n_groups=n_groups,
|
|
741
763
|
score_cols=score_cols,
|
|
742
764
|
interest_col=interest_col,
|
|
765
|
+
interest_ignore=interest_ignore,
|
|
743
766
|
max_iter=max_iter,
|
|
744
767
|
score_metric=score_metric
|
|
745
768
|
)
|
|
@@ -23,6 +23,7 @@ from sklearn.metrics import silhouette_score, adjusted_rand_score
|
|
|
23
23
|
# hossam 패키지 참조
|
|
24
24
|
# ===================================================================
|
|
25
25
|
from . import hs_plot
|
|
26
|
+
from .hs_util import is_2d
|
|
26
27
|
|
|
27
28
|
RANDOM_STATE = 52
|
|
28
29
|
|
|
@@ -32,10 +33,11 @@ RANDOM_STATE = 52
|
|
|
32
33
|
# ===================================================================
|
|
33
34
|
def kmeans_fit(
|
|
34
35
|
data: DataFrame,
|
|
35
|
-
n_clusters: int,
|
|
36
|
+
n_clusters: int | None = None,
|
|
37
|
+
k_range: list | tuple = [2, 11],
|
|
36
38
|
random_state: int = RANDOM_STATE,
|
|
37
39
|
plot: bool = False,
|
|
38
|
-
fields: list[list[str]] | None = None,
|
|
40
|
+
fields: list[str] | tuple[str] | tuple[tuple[str]] | list[list[str]] | None = None,
|
|
39
41
|
**params,
|
|
40
42
|
) -> tuple[KMeans, DataFrame, float]:
|
|
41
43
|
"""
|
|
@@ -43,7 +45,7 @@ def kmeans_fit(
|
|
|
43
45
|
|
|
44
46
|
Args:
|
|
45
47
|
data (DataFrame): 군집화할 데이터프레임.
|
|
46
|
-
n_clusters (int): 군집 개수.
|
|
48
|
+
n_clusters (int | None): 군집 개수.
|
|
47
49
|
random_state (int, optional): 랜덤 시드. 기본값은 RANDOM_STATE.
|
|
48
50
|
plot (bool, optional): True면 결과를 시각화함. 기본값 False.
|
|
49
51
|
fields (list[list[str]] | None, optional): 시각화할 필드 쌍 리스트. 기본값 None이면 수치형 컬럼의 모든 조합 사용.
|
|
@@ -55,18 +57,36 @@ def kmeans_fit(
|
|
|
55
57
|
float: 실루엣 점수
|
|
56
58
|
"""
|
|
57
59
|
df = data.copy()
|
|
60
|
+
|
|
61
|
+
if n_clusters is None:
|
|
62
|
+
n_clusters = kmeans_best_k(data=df, k_range=k_range, random_state=random_state, plot=False)
|
|
63
|
+
print(f"Best k found: {n_clusters}")
|
|
64
|
+
|
|
58
65
|
kmeans = KMeans(n_clusters=n_clusters, random_state=random_state, **params)
|
|
59
66
|
kmeans.fit(data)
|
|
60
67
|
df["cluster"] = kmeans.predict(df)
|
|
61
68
|
score = float(silhouette_score(X=data, labels=df["cluster"]))
|
|
62
69
|
|
|
63
70
|
if plot:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
|
|
72
|
+
if not is_2d(fields):
|
|
73
|
+
fields = [fields] # type: ignore
|
|
74
|
+
|
|
75
|
+
# cluster_plot(
|
|
76
|
+
# estimator=kmeans,
|
|
77
|
+
# data=data,
|
|
78
|
+
# fields=fields,
|
|
79
|
+
# title=f"K-Means Clustering (k={n_clusters})",
|
|
80
|
+
# )
|
|
81
|
+
for f in fields: # type: ignore
|
|
82
|
+
hs_plot.visualize_silhouette(
|
|
83
|
+
estimator=kmeans,
|
|
84
|
+
data=data,
|
|
85
|
+
xname=f[0], # type: ignore
|
|
86
|
+
yname=f[1], # type: ignore
|
|
87
|
+
title=f"K-Means Clustering (k={n_clusters})",
|
|
88
|
+
outline=True,
|
|
89
|
+
)
|
|
70
90
|
|
|
71
91
|
return kmeans, df, score
|
|
72
92
|
|
|
@@ -544,6 +564,9 @@ def persona(
|
|
|
544
564
|
persona_dict[("", f"count")] = len(group)
|
|
545
565
|
|
|
546
566
|
for field in fields:
|
|
567
|
+
if field == cluster:
|
|
568
|
+
continue
|
|
569
|
+
|
|
547
570
|
# 명목형일 경우 최빈값 사용
|
|
548
571
|
if df[field].dtype == "object" or df[field].dtype.name == "category":
|
|
549
572
|
persona_dict[(field, "mode")] = group[field].mode()[0]
|
|
@@ -779,7 +802,9 @@ def dbscan_eps(
|
|
|
779
802
|
|
|
780
803
|
return best_eps, eps_grid
|
|
781
804
|
|
|
782
|
-
|
|
805
|
+
# ===================================================================
|
|
806
|
+
# DBSCAN 군집화 모델을 적합하고 최적의 eps 값을 탐지하는 함수.
|
|
807
|
+
# ===================================================================
|
|
783
808
|
def dbscan_fit(
|
|
784
809
|
data: DataFrame,
|
|
785
810
|
eps: float | list | np.ndarray | None = None,
|
|
@@ -789,6 +814,25 @@ def dbscan_fit(
|
|
|
789
814
|
plot: bool = True,
|
|
790
815
|
**params,
|
|
791
816
|
) -> tuple[DBSCAN, DataFrame, DataFrame]:
|
|
817
|
+
"""
|
|
818
|
+
DBSCAN 군집화 모델을 적합하고 최적의 eps 값을 탐지하는 함수.
|
|
819
|
+
|
|
820
|
+
Args:
|
|
821
|
+
data (DataFrame): 군집화할 데이터프레임.
|
|
822
|
+
eps (float | list | np.ndarray | None, optional): eps 값 또는 리스트.
|
|
823
|
+
None이면 최적의 eps 값을 탐지함. 기본값 None.
|
|
824
|
+
min_samples (int, optional): 핵심점이 되기 위한 최소 샘플수. 기본값 5.
|
|
825
|
+
ari_threshold (float, optional): 안정 구간 탐지를 위한 ARI 임계값. 기본값 0.9.
|
|
826
|
+
noise_diff_threshold (float, optional): 안정 구간 탐지를 위한 노이즈 비율 변화 임계값. 기본값 0.05.
|
|
827
|
+
plot (bool, optional): True면 결과를 시각화함. 기본값 True.
|
|
828
|
+
**params: DBSCAN에 전달할 추가 파라미터.
|
|
829
|
+
|
|
830
|
+
Returns:
|
|
831
|
+
tuple: (estimator, cluster_df, result_df)
|
|
832
|
+
- estimator: 적합된 DBSCAN 모델 또는 모델 리스트(최적 eps가 여러 개인 경우).
|
|
833
|
+
- cluster_df: 클러스터 및 벡터 유형이 포함된 데이터 프레임 또는 데이터 프레임 리스트(최적 eps가 여러 개인 경우).
|
|
834
|
+
- result_df: eps 값에 따른 군집화 요약 통계 데이터 프레임.
|
|
835
|
+
"""
|
|
792
836
|
|
|
793
837
|
# eps 값이 지정되지 않은 경우 최적의 eps 탐지
|
|
794
838
|
if eps is None:
|
|
@@ -874,18 +918,52 @@ def dbscan_fit(
|
|
|
874
918
|
# result_dfs에서 recommand가 best에 해당하는 인덱스와 같은 위치의 추정기만 추출
|
|
875
919
|
best_indexes = list(result_dfs[result_dfs["recommand"] == "best"].index) # type: ignore
|
|
876
920
|
|
|
877
|
-
for i in range(len(estimators) - 1, -1, -1):
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
921
|
+
# for i in range(len(estimators) - 1, -1, -1):
|
|
922
|
+
# if i not in best_indexes:
|
|
923
|
+
# del estimators[i]
|
|
924
|
+
# del cluster_dfs[i]
|
|
881
925
|
|
|
882
926
|
pbar.update(1)
|
|
883
927
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
928
|
+
# best 모델 선정: recommand=='best'인 인덱스의 estimator/cluster_df만 반환
|
|
929
|
+
if len(estimators) == 1:
|
|
930
|
+
|
|
931
|
+
if plot:
|
|
932
|
+
hs_plot.scatterplot(
|
|
933
|
+
df=cluster_dfs[0],
|
|
934
|
+
xname=cluster_dfs[0].columns[0],
|
|
935
|
+
yname=cluster_dfs[0].columns[1],
|
|
936
|
+
hue="cluster",
|
|
937
|
+
vector="vector",
|
|
938
|
+
title=f"DBSCAN Clustering (eps={estimators[0].eps}, min_samples={estimators[0].min_samples})",
|
|
939
|
+
outline=True
|
|
940
|
+
)
|
|
941
|
+
|
|
942
|
+
return estimators[0], cluster_dfs[0], result_dfs # type: ignore
|
|
943
|
+
|
|
944
|
+
# recommand=='best'인 인덱스 추출 (여러 개면 첫 번째)
|
|
945
|
+
best_indexes = list(result_dfs[result_dfs["recommand"] == "best"].index) # type: ignore
|
|
946
|
+
if not best_indexes:
|
|
947
|
+
# fallback: 첫 번째
|
|
948
|
+
best_index = 0
|
|
949
|
+
else:
|
|
950
|
+
best_index = best_indexes[0]
|
|
951
|
+
|
|
952
|
+
best_estimator = estimators[best_index]
|
|
953
|
+
best_cluster_df = cluster_dfs[best_index]
|
|
954
|
+
|
|
955
|
+
if plot:
|
|
956
|
+
hs_plot.scatterplot(
|
|
957
|
+
df=best_cluster_df,
|
|
958
|
+
xname=best_cluster_df.columns[0],
|
|
959
|
+
yname=best_cluster_df.columns[1],
|
|
960
|
+
hue="cluster",
|
|
961
|
+
vector="vector",
|
|
962
|
+
title=f"DBSCAN Clustering (eps={best_estimator.eps}, min_samples={best_estimator.min_samples})",
|
|
963
|
+
outline=True
|
|
964
|
+
)
|
|
965
|
+
|
|
966
|
+
return best_estimator, best_cluster_df, result_dfs # type: ignore
|
|
889
967
|
|
|
890
968
|
|
|
891
969
|
# ===================================================================
|
|
@@ -950,8 +1028,8 @@ def agg_fit(
|
|
|
950
1028
|
|
|
951
1029
|
Returns:
|
|
952
1030
|
tuple: (estimator(s), df(s), score_df)
|
|
953
|
-
- estimator(s): 적합된 AgglomerativeClustering 모델 또는 모델
|
|
954
|
-
- df(s): 클러스터 결과가 포함된 데이터 프레임 또는 데이터 프레임
|
|
1031
|
+
- estimator(s): 적합된 AgglomerativeClustering 모델 또는 모델 리스트 (n_clusters가 리스트일 때 리턴도 리스트로 처리됨).
|
|
1032
|
+
- df(s): 클러스터 결과가 포함된 데이터 프레임 또는 데이터 프레임 리스트(n_cluseters가 리스트일 때 리턴되 리스트로 처리됨).
|
|
955
1033
|
- score_df: 각 군집 개수에 대한 실루엣 점수 데이터프레임.
|
|
956
1034
|
|
|
957
1035
|
Examples:
|
|
@@ -8,6 +8,7 @@ from itertools import combinations
|
|
|
8
8
|
import numpy as np
|
|
9
9
|
import seaborn as sb
|
|
10
10
|
import matplotlib.pyplot as plt
|
|
11
|
+
from matplotlib.figure import Figure # type: ignore
|
|
11
12
|
from matplotlib.pyplot import Axes # type: ignore
|
|
12
13
|
from pandas import Series, DataFrame
|
|
13
14
|
from math import sqrt
|
|
@@ -132,7 +133,7 @@ def create_figure(
|
|
|
132
133
|
ws: int | None = None,
|
|
133
134
|
hs: int | None = None,
|
|
134
135
|
title: str | None = None,
|
|
135
|
-
):
|
|
136
|
+
) -> tuple[Figure, Axes]:
|
|
136
137
|
"""기본 크기의 Figure와 Axes를 생성한다. get_default_ax의 래퍼 함수.
|
|
137
138
|
|
|
138
139
|
Args:
|
|
@@ -309,7 +310,7 @@ def lineplot(
|
|
|
309
310
|
# 상자그림(boxplot)을 그린다
|
|
310
311
|
# ===================================================================
|
|
311
312
|
def boxplot(
|
|
312
|
-
df: DataFrame,
|
|
313
|
+
df: DataFrame | None = None,
|
|
313
314
|
xname: str | None = None,
|
|
314
315
|
yname: str | None = None,
|
|
315
316
|
title: str | None = None,
|
|
@@ -331,7 +332,7 @@ def boxplot(
|
|
|
331
332
|
"""상자그림(boxplot)을 그린다.
|
|
332
333
|
|
|
333
334
|
Args:
|
|
334
|
-
df (DataFrame): 시각화할 데이터.
|
|
335
|
+
df (DataFrame|None): 시각화할 데이터.
|
|
335
336
|
xname (str|None): x축 범주 컬럼명.
|
|
336
337
|
yname (str|None): y축 값 컬럼명.
|
|
337
338
|
title (str|None): 그래프 제목.
|
|
@@ -359,13 +360,20 @@ def boxplot(
|
|
|
359
360
|
fig, ax = get_default_ax(width, height, 1, 1, dpi) # type: ignore
|
|
360
361
|
outparams = True
|
|
361
362
|
|
|
362
|
-
if xname is not None
|
|
363
|
+
if xname is not None or yname is not None:
|
|
364
|
+
if xname is not None and yname is None:
|
|
365
|
+
orient = "h"
|
|
366
|
+
elif xname is None and yname is not None:
|
|
367
|
+
orient = "v"
|
|
368
|
+
|
|
369
|
+
|
|
363
370
|
boxplot_kwargs = {
|
|
364
371
|
"data": df,
|
|
365
372
|
"x": xname,
|
|
366
373
|
"y": yname,
|
|
367
374
|
"orient": orient,
|
|
368
375
|
"ax": ax,
|
|
376
|
+
"linewidth": linewidth,
|
|
369
377
|
}
|
|
370
378
|
|
|
371
379
|
# hue 파라미터 확인 (params에 있을 수 있음)
|
|
@@ -377,12 +385,12 @@ def boxplot(
|
|
|
377
385
|
boxplot_kwargs["color"] = sb.color_palette(palette)[0]
|
|
378
386
|
|
|
379
387
|
boxplot_kwargs.update(params)
|
|
380
|
-
sb.boxplot(**boxplot_kwargs
|
|
388
|
+
sb.boxplot(**boxplot_kwargs)
|
|
381
389
|
|
|
382
390
|
# 통계 검정 추가
|
|
383
391
|
if stat_test is not None:
|
|
384
392
|
if stat_pairs is None:
|
|
385
|
-
stat_pairs = [df[xname].dropna().unique().tolist()]
|
|
393
|
+
stat_pairs = [df[xname].dropna().unique().tolist()] # type: ignore
|
|
386
394
|
|
|
387
395
|
annotator = Annotator(
|
|
388
396
|
ax, data=df, x=xname, y=yname, pairs=stat_pairs, orient=orient
|
|
@@ -847,15 +855,15 @@ def scatterplot(
|
|
|
847
855
|
else:
|
|
848
856
|
# 핵심벡터
|
|
849
857
|
scatterplot_kwargs["edgecolor"] = "#ffffff"
|
|
850
|
-
sb.scatterplot(data=df[df[vector] == "core"], **scatterplot_kwargs)
|
|
858
|
+
sb.scatterplot(data=df[df[vector] == "core"], **scatterplot_kwargs) # type: ignore
|
|
851
859
|
|
|
852
860
|
# 외곽백터
|
|
853
861
|
scatterplot_kwargs["edgecolor"] = "#000000"
|
|
854
862
|
scatterplot_kwargs["s"] = 25
|
|
855
863
|
scatterplot_kwargs["marker"] = "^"
|
|
856
864
|
scatterplot_kwargs["linewidth"] = 0.8
|
|
857
|
-
sb.scatterplot(data=df[df[vector] == "border"], **scatterplot_kwargs)
|
|
858
|
-
|
|
865
|
+
sb.scatterplot(data=df[df[vector] == "border"], **scatterplot_kwargs) # type: ignore
|
|
866
|
+
|
|
859
867
|
# 노이즈벡터
|
|
860
868
|
scatterplot_kwargs["edgecolor"] = None
|
|
861
869
|
scatterplot_kwargs["s"] = 25
|
|
@@ -1096,14 +1104,9 @@ def pairplot(
|
|
|
1096
1104
|
g.fig.suptitle(title, fontsize=config.font_size * 1.5, fontweight="bold")
|
|
1097
1105
|
|
|
1098
1106
|
g.map_lower(
|
|
1099
|
-
func=sb.kdeplot, fill=True, alpha=config.fill_alpha
|
|
1107
|
+
func=sb.kdeplot, fill=True, alpha=config.fill_alpha
|
|
1100
1108
|
)
|
|
1101
|
-
g.map_upper(func=sb.scatterplot
|
|
1102
|
-
|
|
1103
|
-
# KDE 대각선에도 linewidth 적용
|
|
1104
|
-
for ax in g.axes.diag: # type: ignore
|
|
1105
|
-
for line in ax.get_lines():
|
|
1106
|
-
line.set_linewidth(linewidth)
|
|
1109
|
+
g.map_upper(func=sb.scatterplot)
|
|
1107
1110
|
|
|
1108
1111
|
plt.tight_layout()
|
|
1109
1112
|
|
|
@@ -1761,25 +1764,14 @@ def ols_residplot(
|
|
|
1761
1764
|
fig, ax = get_default_ax(width + 150 if mse else width, height, 1, 1, dpi) # type: ignore
|
|
1762
1765
|
outparams = True
|
|
1763
1766
|
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
lowess_result = sm_lowess(resid, y_pred, frac=0.6667)
|
|
1773
|
-
ax.plot( # type: ignore
|
|
1774
|
-
lowess_result[:, 0],
|
|
1775
|
-
lowess_result[:, 1], # type: ignore
|
|
1776
|
-
color="red",
|
|
1777
|
-
linewidth=linewidth,
|
|
1778
|
-
label="LOWESS",
|
|
1779
|
-
) # type: ignore
|
|
1780
|
-
|
|
1781
|
-
ax.set_xlabel("Fitted values") # type: ignore
|
|
1782
|
-
ax.set_ylabel("Residuals") # type: ignore
|
|
1767
|
+
sb.residplot(
|
|
1768
|
+
x=y_pred,
|
|
1769
|
+
y=resid,
|
|
1770
|
+
lowess=True, # 잔차의 추세선 표시
|
|
1771
|
+
line_kws={"color": "red", "linewidth": linewidth * 0.7}, # 추세선 스타일
|
|
1772
|
+
scatter_kws={"edgecolor": "white", "alpha": config.alpha},
|
|
1773
|
+
**params
|
|
1774
|
+
)
|
|
1783
1775
|
|
|
1784
1776
|
if mse:
|
|
1785
1777
|
mse_val = mean_squared_error(y, y_pred)
|
|
@@ -1909,8 +1901,7 @@ def ols_qqplot(
|
|
|
1909
1901
|
|
|
1910
1902
|
# 선 굵기 조정
|
|
1911
1903
|
for line in ax.get_lines(): # type: ignore
|
|
1912
|
-
|
|
1913
|
-
line.set_linewidth(linewidth) # type: ignore
|
|
1904
|
+
line.set_linewidth(linewidth) # type: ignore
|
|
1914
1905
|
|
|
1915
1906
|
finalize_plot(ax, callback, outparams, save_path, True, title) # type: ignore
|
|
1916
1907
|
|
|
@@ -3022,10 +3013,15 @@ def pca_plot(
|
|
|
3022
3013
|
if field_group is not None:
|
|
3023
3014
|
title += " - " + ", ".join(field_group)
|
|
3024
3015
|
|
|
3016
|
+
tdf = DataFrame({
|
|
3017
|
+
field_group[0]: xs * scalex,
|
|
3018
|
+
field_group[1]: ys * scaley,
|
|
3019
|
+
})
|
|
3020
|
+
|
|
3025
3021
|
scatterplot(
|
|
3026
|
-
df=
|
|
3027
|
-
xname=
|
|
3028
|
-
yname=
|
|
3022
|
+
df=tdf,
|
|
3023
|
+
xname=field_group[0],
|
|
3024
|
+
yname=field_group[1],
|
|
3029
3025
|
hue=pca_df[hue] if hue is not None else None,
|
|
3030
3026
|
outline=False,
|
|
3031
3027
|
palette=palette,
|