hossam 0.4.8__py3-none-any.whl → 0.4.11__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 CHANGED
@@ -6,8 +6,10 @@ from . import hs_prep
6
6
  from . import hs_stats
7
7
  from . import hs_timeserise
8
8
  from . import hs_util
9
+ from . import hs_cluster
9
10
  from .hs_util import load_info
10
11
  from .hs_util import _load_data_remote as load_data
12
+ from .hs_plot import visualize_silhouette
11
13
 
12
14
  # py-modules
13
15
  import sys
@@ -24,7 +26,7 @@ except Exception:
24
26
 
25
27
  my_dpi = hs_plot.config.dpi
26
28
 
27
- __all__ = ["my_dpi", "load_data", "load_info", "hs_classroom", "hs_gis", "hs_plot", "hs_prep", "hs_stats", "hs_timeserise", "hs_util"]
29
+ __all__ = ["my_dpi", "load_data", "load_info", "hs_classroom", "hs_gis", "hs_plot", "hs_prep", "hs_stats", "hs_timeserise", "hs_util", "hs_cluster", "visualize_silhouette"]
28
30
 
29
31
  # 내부 모듈에서 hs_fig를 사용할 때는 아래와 같이 import 하세요.
30
32
  # from hossam import hs_fig
hossam/hs_classroom.py CHANGED
@@ -8,8 +8,8 @@ from kmodes.kmodes import KModes
8
8
  from matplotlib import pyplot as plt
9
9
  import seaborn as sns
10
10
  from .hs_util import load_data, pretty_table
11
- from . import hs_plot
12
11
  from .hs_plot import config
12
+ from . import hs_plot
13
13
 
14
14
  # ===================================================================
15
15
  # 학생들을 관심사와 성적으로 균형잡힌 조로 편성한다
hossam/hs_cluster.py ADDED
@@ -0,0 +1,119 @@
1
+ from typing import Literal
2
+ from kneed import KneeLocator
3
+ from pandas import Series
4
+ from matplotlib.pyplot import Axes # type: ignore
5
+
6
+ from . import hs_plot
7
+
8
+ import numpy as np
9
+
10
+ def elbow_point(
11
+ x: Series | np.ndarray | list,
12
+ y: Series | np.ndarray | list,
13
+ dir: Literal["left,down", "left,up", "right,down", "right,up"] = "left,down",
14
+ S: float = 0.1,
15
+ plot: bool = True,
16
+ title: str = None,
17
+ marker: str = None,
18
+ width: int = hs_plot.config.width,
19
+ height: int = hs_plot.config.height,
20
+ dpi: int = hs_plot.config.dpi,
21
+ linewidth: int = hs_plot.config.line_width,
22
+ save_path: str | None = None,
23
+ ax: Axes | None = None,
24
+ **params,
25
+ ) -> tuple:
26
+
27
+ """
28
+ 엘보우(Elbow) 포인트를 자동으로 탐지하는 함수.
29
+
30
+ 주어진 x, y 값의 곡선에서 KneeLocator를 활용해 엘보우(혹은 니) 포인트를 탐지하고, 필요시 시각화까지 지원함.
31
+
32
+ Args:
33
+ x (Series | np.ndarray | list): x축 값(일반적으로 K값 등).
34
+ y (Series | np.ndarray | list): y축 값(일반적으로 inertia, SSE 등).
35
+ dir (Literal["left,down", "left,up", "right,down", "right,up"], optional):
36
+ 곡선의 방향 및 형태 지정. 기본값은 "left,down".
37
+ - "left,down": 왼쪽에서 오른쪽으로 감소(볼록)
38
+ - "left,up": 왼쪽에서 오른쪽으로 증가(오목)
39
+ - "right,down": 오른쪽에서 왼쪽으로 감소(볼록)
40
+ - "right,up": 오른쪽에서 왼쪽으로 증가(오목)
41
+ S (float, optional): KneeLocator의 민감도 파라미터. 기본값 0.1.
42
+ plot (bool, optional): True면 결과를 시각화함. 기본값 True.
43
+ title (str, optional): 플롯 제목.
44
+ marker (str, optional): 마커 스타일.
45
+ width (int, optional): 플롯 가로 크기.
46
+ height (int, optional): 플롯 세로 크기.
47
+ dpi (int, optional): 플롯 해상도.
48
+ linewidth (int, optional): 선 두께.
49
+ save_path (str | None, optional): 저장 경로 지정시 파일로 저장.
50
+ ax (Axes | None, optional): 기존 matplotlib Axes 객체. None이면 새로 생성.
51
+ **params: lineplot에 전달할 추가 파라미터.
52
+
53
+ Returns:
54
+ tuple: (best_x, best_y)
55
+ - best_x: 엘보우 포인트의 x값(예: 최적 K)
56
+ - best_y: 엘보우 포인트의 y값
57
+
58
+ Examples:
59
+ ```python
60
+ x = [1, 2, 3, 4, 5, 6]
61
+ y = [100, 80, 60, 45, 44, 43]
62
+ elbow_point(x, y)
63
+ ```
64
+
65
+ Note:
66
+ - KneeLocator는 kneed 패키지의 클래스로, 곡선의 형태(curve)와 방향(direction)에 따라 엘보우 포인트를 탐지함.
67
+ - dir 파라미터에 따라 curve/direction이 자동 지정됨.
68
+ - plot=True일 때, 엘보우 포인트에 수직/수평선과 텍스트가 표시됨.
69
+ """
70
+
71
+ if dir == "left,down":
72
+ curve = "convex"
73
+ direction = "decreasing"
74
+ elif dir == "left,up":
75
+ curve = "concave"
76
+ direction = "increasing"
77
+ elif dir == "right,down":
78
+ curve = "convex"
79
+ direction = "increasing"
80
+ else:
81
+ curve = "concave"
82
+ direction = "decreasing"
83
+
84
+ kn = KneeLocator(x=x, y=y, curve=curve, direction=direction, S=S)
85
+
86
+ best_x = kn.elbow
87
+ best_y = kn.elbow_y
88
+
89
+ if plot:
90
+ def hvline(ax):
91
+ ax.axvline(best_x, color="red", linestyle="--", linewidth=0.7)
92
+ ax.axhline(best_y, color="red", linestyle="--", linewidth=0.7)
93
+ ax.text(
94
+ best_x + 0.1,
95
+ best_y + 0.1,
96
+ "Best K=%d" % best_x,
97
+ fontsize=8,
98
+ ha="left",
99
+ va="bottom",
100
+ color="r",
101
+ )
102
+
103
+ hs_plot.lineplot(
104
+ df = None,
105
+ xname = x,
106
+ yname = y,
107
+ title = title,
108
+ marker = marker,
109
+ width = width,
110
+ height = height,
111
+ linewidth = linewidth,
112
+ dpi = dpi,
113
+ save_path = save_path,
114
+ callback = hvline,
115
+ ax = ax,
116
+ **params
117
+ )
118
+
119
+ return best_x, best_y
hossam/hs_plot.py CHANGED
@@ -5,10 +5,10 @@ from typing import Callable
5
5
 
6
6
  # ===================================================================
7
7
  import numpy as np
8
- import pandas as pd
9
8
  import seaborn as sb
10
9
  import matplotlib.pyplot as plt
11
10
  from matplotlib.pyplot import Axes # type: ignore
11
+ from pandas import Series, DataFrame
12
12
  from math import sqrt
13
13
  from pandas import DataFrame
14
14
 
@@ -22,12 +22,16 @@ from statsmodels.nonparametric.smoothers_lowess import lowess as sm_lowess
22
22
  from statannotations.Annotator import Annotator
23
23
 
24
24
  # ===================================================================
25
+ from sklearn.cluster._kmeans import KMeans
26
+
25
27
  from sklearn.metrics import (
26
28
  mean_squared_error,
27
29
  ConfusionMatrixDisplay,
28
30
  roc_curve,
29
31
  auc,
30
- confusion_matrix
32
+ confusion_matrix,
33
+ silhouette_score,
34
+ silhouette_samples
31
35
  )
32
36
 
33
37
  # ===================================================================
@@ -196,9 +200,9 @@ def show_figure(ax: Axes | np.ndarray, callback: Callable | None = None, outpara
196
200
  # 선 그래프를 그린다
197
201
  # ===================================================================
198
202
  def lineplot(
199
- df: DataFrame,
200
- xname: str | None = None,
201
- yname: str | None = None,
203
+ df: DataFrame | None = None,
204
+ xname: str | Series | np.ndarray | list | None = None,
205
+ yname: str | Series | np.ndarray | list | None = None,
202
206
  hue: str | None = None,
203
207
  title: str | None = None,
204
208
  marker: str | None = None,
@@ -215,13 +219,13 @@ def lineplot(
215
219
  """선 그래프를 그린다.
216
220
 
217
221
  Args:
218
- df (DataFrame): 시각화할 데이터.
219
- xname (str|None): x축 컬럼명.
220
- yname (str|None): y축 컬럼명.
221
- hue (str|None): 범주 구분 컬럼명.
222
- title (str|None): 그래프 제목.
223
- marker (str|None): 마커 모양.
224
- palette (str|None): 팔레트 이름.
222
+ df (DataFrame | None): 시각화할 데이터.
223
+ xname (str | Series | np.ndarray | list | None): x축 컬럼명 혹은 x축 값 시퀀스.
224
+ yname (str | Series | np.ndarray | list | None): y축 컬럼명 혹은 y축 값 시퀀스.
225
+ hue (str | None): 범주 구분 컬럼명.
226
+ title (str | None): 그래프 제목.
227
+ marker (str | None): 마커 모양.
228
+ palette (str | None): 팔레트 이름.
225
229
  width (int): 캔버스 가로 픽셀.
226
230
  height (int): 캔버스 세로 픽셀.
227
231
  linewidth (float): 선 굵기.
@@ -708,6 +712,8 @@ def scatterplot(
708
712
  xname: str,
709
713
  yname: str,
710
714
  hue=None,
715
+ vector: str | None = None,
716
+ outline: bool = False,
711
717
  title: str | None = None,
712
718
  palette: str | None = None,
713
719
  width: int = config.width,
@@ -726,6 +732,8 @@ def scatterplot(
726
732
  xname (str): x축 컬럼.
727
733
  yname (str): y축 컬럼.
728
734
  hue (str|None): 범주 컬럼.
735
+ vector (str|None): 벡터 종류 컬럼.
736
+ outline (bool): 점 외곽선 표시 여부.
729
737
  title (str|None): 그래프 제목.
730
738
  palette (str|None): 팔레트 이름.
731
739
  width (int): 캔버스 가로 픽셀.
@@ -745,9 +753,32 @@ def scatterplot(
745
753
  fig, ax = get_default_ax(width, height, 1, 1, dpi) # type: ignore
746
754
  outparams = True
747
755
 
756
+
757
+ if outline and hue is not None:
758
+ # 군집별 값의 종류별로 반복 수행
759
+ for c in df[hue].unique():
760
+ if c == -1:
761
+ continue
762
+
763
+ # 한 종류만 필터링한 결과에서 두 변수만 선택
764
+ df_c = df.loc[df[hue] == c, [xname, yname]]
765
+
766
+ try:
767
+ # 외각선 좌표 계산
768
+ hull = ConvexHull(df_c)
769
+
770
+ # 마지막 좌표 이후에 첫 번째 좌표를 연결
771
+ points = np.append(hull.vertices, hull.vertices[0])
772
+
773
+ ax.plot( # type: ignore
774
+ df_c.iloc[points, 0], df_c.iloc[points, 1], linewidth=linewidth, linestyle=":"
775
+ )
776
+ ax.fill(df_c.iloc[points, 0], df_c.iloc[points, 1], alpha=0.1) # type: ignore
777
+ except:
778
+ pass
779
+
748
780
  # hue가 있을 때만 palette 사용, 없으면 color 사용
749
781
  scatterplot_kwargs = {
750
- "data": df,
751
782
  "x": xname,
752
783
  "y": yname,
753
784
  "hue": hue,
@@ -762,7 +793,30 @@ def scatterplot(
762
793
 
763
794
  scatterplot_kwargs.update(params)
764
795
 
765
- sb.scatterplot(**scatterplot_kwargs)
796
+ # 백터 종류 구분 필드가 전달되지 않은 경우에는 원본 데이터를 그대로 사용
797
+ if vector is None:
798
+ sb.scatterplot(data=df, **scatterplot_kwargs)
799
+ else:
800
+ # 핵심벡터
801
+ scatterplot_kwargs['edgecolor'] = '#ffffff'
802
+ sb.scatterplot(data=df[df[vector] == "core"], **scatterplot_kwargs)
803
+
804
+ # 외곽백터
805
+ scatterplot_kwargs['edgecolor'] = '#000000'
806
+ scatterplot_kwargs['s'] = 25
807
+ scatterplot_kwargs['marker'] = '^'
808
+ scatterplot_kwargs['linewidth'] = 0.8
809
+ sb.scatterplot(data=df[df[vector] == "border"], **scatterplot_kwargs)
810
+
811
+ # 노이즈벡터
812
+ scatterplot_kwargs['edgecolor'] = None
813
+ scatterplot_kwargs['s'] = 25
814
+ scatterplot_kwargs['marker'] = 'x'
815
+ scatterplot_kwargs['linewidth'] = 2
816
+ scatterplot_kwargs['color'] = '#ff0000'
817
+ scatterplot_kwargs['hue'] = None
818
+ sb.scatterplot(data=df[df[vector] == "noise"], **scatterplot_kwargs)
819
+
766
820
 
767
821
  finalize_plot(ax, callback, outparams, save_path, True, title) # type: ignore
768
822
 
@@ -1479,79 +1533,6 @@ def heatmap(
1479
1533
  finalize_plot(ax, callback, outparams, save_path, True, title) # type: ignore
1480
1534
 
1481
1535
 
1482
- # ===================================================================
1483
- # 클러스터별 볼록 경계막(convex hull)을 그린다
1484
- # ===================================================================
1485
- def convex_hull(
1486
- data: DataFrame,
1487
- xname: str,
1488
- yname: str,
1489
- hue: str | None = None,
1490
- title: str | None = None,
1491
- palette: str | None = None,
1492
- width: int = config.width,
1493
- height: int = config.height,
1494
- linewidth: float = config.line_width,
1495
- dpi: int = config.dpi,
1496
- save_path: str | None = None,
1497
- callback: Callable | None = None,
1498
- ax: Axes | None = None,
1499
- **params,
1500
- ):
1501
- """클러스터별 볼록 껍질(convex hull)과 산점도를 그린다.
1502
-
1503
- Args:
1504
- data (DataFrame): 시각화할 데이터.
1505
- xname (str): x축 컬럼.
1506
- yname (str): y축 컬럼.
1507
- hue (str): 클러스터/범주 컬럼.
1508
- title (str|None): 그래프 제목.
1509
- palette (str|None): 팔레트 이름.
1510
- width (int): 캔버스 가로 픽셀.
1511
- height (int): 캔버스 세로 픽셀.
1512
- linewidth (float): 선 굵기.
1513
- dpi (int): 그림 크기 및 해상도.
1514
- callback (Callable|None): Axes 후처리 콜백.
1515
- ax (Axes|None): 외부에서 전달한 Axes.
1516
- **params: seaborn scatterplot 추가 인자.
1517
-
1518
- Returns:
1519
- None
1520
- """
1521
- outparams = False
1522
-
1523
- if ax is None:
1524
- fig, ax = get_default_ax(width, height, 1, 1, dpi) # type: ignore
1525
- outparams = True
1526
-
1527
- # 군집별 값의 종류별로 반복 수행
1528
- for c in data[hue].unique():
1529
- if c == -1:
1530
- continue
1531
-
1532
- # 한 종류만 필터링한 결과에서 두 변수만 선택
1533
- df_c = data.loc[data[hue] == c, [xname, yname]]
1534
-
1535
- try:
1536
- # 외각선 좌표 계산
1537
- hull = ConvexHull(df_c)
1538
-
1539
- # 마지막 좌표 이후에 첫 번째 좌표를 연결
1540
- points = np.append(hull.vertices, hull.vertices[0])
1541
-
1542
- ax.plot( # type: ignore
1543
- df_c.iloc[points, 0], df_c.iloc[points, 1], linewidth=linewidth, linestyle=":"
1544
- )
1545
- ax.fill(df_c.iloc[points, 0], df_c.iloc[points, 1], alpha=0.1) # type: ignore
1546
- except:
1547
- pass
1548
-
1549
- # convex_hull은 hue가 필수이므로 palette를 그대로 사용
1550
- sb.scatterplot(
1551
- data=data, x=xname, y=yname, hue=hue, palette=palette, ax=ax, **params
1552
- )
1553
- finalize_plot(ax, callback, outparams, save_path, True, title) # type: ignore
1554
-
1555
1536
 
1556
1537
  # ===================================================================
1557
1538
  # KDE와 신뢰구간을 나타낸 그래프를 그린다
@@ -2045,16 +2026,8 @@ def scatter_by_class(
2045
2026
  processed.append([item, yname])
2046
2027
  group = processed
2047
2028
 
2048
- if outline:
2049
- for v in group:
2050
- convex_hull(data=data, xname=v[0], yname=v[1], hue=hue, palette=palette,
2051
- width=width, height=height, linewidth=linewidth, dpi=dpi, callback=callback,
2052
- save_path=save_path)
2053
- else:
2054
- for v in group:
2055
- scatterplot(data=data, xname=v[0], yname=v[1], hue=hue, palette=palette,
2056
- width=width, height=height, linewidth=linewidth, dpi=dpi, callback=callback,
2057
- save_path=save_path) # type: ignore
2029
+ for v in group:
2030
+ scatterplot(data=data, xname=v[0], yname=v[1], outline=outline, hue=hue, palette=palette, width=width, height=height, linewidth=linewidth, dpi=dpi, callback=callback, save_path=save_path) # type: ignore
2058
2031
 
2059
2032
 
2060
2033
  # ===================================================================
@@ -2149,8 +2122,8 @@ def categorical_target_distribution(
2149
2122
  # ===================================================================
2150
2123
  def roc_curve_plot(
2151
2124
  fit,
2152
- y: np.ndarray | pd.Series | None = None,
2153
- X: pd.DataFrame | np.ndarray | None = None,
2125
+ y: np.ndarray | Series | None = None,
2126
+ X: DataFrame | np.ndarray | None = None,
2154
2127
  title: str | None = None,
2155
2128
  width: int = config.height,
2156
2129
  height: int = config.height,
@@ -2477,7 +2450,7 @@ def distribution_plot(
2477
2450
  if hue not in data.columns:
2478
2451
  raise ValueError(f"hue column '{hue}' not found in DataFrame")
2479
2452
 
2480
- categories = list(pd.Series(data[hue].dropna().unique()).sort_values())
2453
+ categories = list(Series(data[hue].dropna().unique()).sort_values())
2481
2454
  n_cat = len(categories) if categories else 1
2482
2455
 
2483
2456
  fig, axes = get_default_ax(width, height, rows=n_cat, cols=2, dpi=dpi, title=title)
@@ -2519,3 +2492,218 @@ def distribution_plot(
2519
2492
  plt.close()
2520
2493
  else:
2521
2494
  plt.show()
2495
+
2496
+
2497
+ def silhouette_plot(
2498
+ estimator: KMeans,
2499
+ data: DataFrame,
2500
+ title: str | None = None,
2501
+ width: int = config.width,
2502
+ height: int = config.height,
2503
+ linewidth: float = config.line_width,
2504
+ dpi: int = config.dpi,
2505
+ save_path: str | None = None,
2506
+ callback: Callable | None = None,
2507
+ ax: Axes | None = None,
2508
+ ) -> None:
2509
+ """
2510
+ 군집분석 결과의 실루엣 플롯을 시각화함.
2511
+
2512
+ Args:
2513
+ estimator (KMeans): 학습된 KMeans 군집 모델 객체.
2514
+ data (DataFrame): 군집분석에 사용된 입력 데이터 (n_samples, n_features).
2515
+ title (str, optional): 플롯 제목. None이면 자동 생성.
2516
+ width (int, optional): 플롯 가로 크기 (inch 단위).
2517
+ height (int, optional): 플롯 세로 크기 (inch 단위).
2518
+ linewidth (float, optional): 기준선 등 선 두께.
2519
+ dpi (int, optional): 플롯 해상도(DPI).
2520
+ save_path (str, optional): 저장 경로 지정 시 파일로 저장.
2521
+ callback (Callable, optional): 추가 커스텀 콜백 함수.
2522
+ ax (Axes, optional): 기존 matplotlib Axes 객체. None이면 새로 생성.
2523
+
2524
+ Returns:
2525
+ None
2526
+
2527
+ Note:
2528
+ - 각 군집별 실루엣 계수 분포를 막대그래프로 시각화
2529
+ - 군집 품질(응집도/분리도) 평가에 활용
2530
+ - 붉은색 세로선은 전체 평균 실루엣 스코어를 의미
2531
+ """
2532
+
2533
+ outparams = False
2534
+ if ax is None:
2535
+ fig, ax = get_default_ax(width, height, 1, 1, dpi) # type: ignore
2536
+ outparams = True
2537
+
2538
+ sil_avg = silhouette_score(X=data, labels=estimator.labels_)
2539
+ sil_values = silhouette_samples(X=data, labels=estimator.labels_)
2540
+
2541
+ y_lower = 10
2542
+
2543
+ # 클러스터링 갯수별로 fill_betweenx( )형태의 막대 그래프 표현.
2544
+ for i in range(estimator.n_clusters): # type: ignore
2545
+ ith_cluster_sil_values = sil_values[estimator.labels_ == i] # type: ignore
2546
+ ith_cluster_sil_values.sort() # type: ignore
2547
+
2548
+ size_cluster_i = ith_cluster_sil_values.shape[0] # type: ignore
2549
+ y_upper = y_lower + size_cluster_i
2550
+
2551
+ ax.fill_betweenx( # type: ignore
2552
+ np.arange(y_lower, y_upper),
2553
+ 0,
2554
+ ith_cluster_sil_values,
2555
+ alpha=0.7,
2556
+ )
2557
+ ax.text(-0.05, y_lower + 0.5 * size_cluster_i, str(i)) # type: ignore
2558
+ y_lower = y_upper + 10
2559
+
2560
+ ax.axvline(x=sil_avg, color="red", linestyle="--", linewidth=linewidth) # type: ignore
2561
+
2562
+ ax.set_xlabel("The silhouette coefficient values") # type: ignore
2563
+ ax.set_ylabel("Cluster label") # type: ignore
2564
+ ax.set_xlim([-0.1, 1]) # type: ignore
2565
+ ax.set_ylim([0, len(data) + (estimator.n_clusters + 1) * 10]) # type: ignore
2566
+ ax.set_yticks([]) # type: ignore
2567
+ ax.set_xticks([0, 0.2, 0.4, 0.6, 0.8, 1]) # type: ignore
2568
+
2569
+ if title is None:
2570
+ title = "Number of Cluster : " + str(estimator.n_clusters) + ", Silhouette Score :" + str(round(sil_avg, 3)) # type: ignore
2571
+
2572
+ finalize_plot(ax, callback, outparams, save_path, True, title) # type: ignore
2573
+
2574
+
2575
+ def cluster_plot(
2576
+ estimator: KMeans,
2577
+ data: DataFrame,
2578
+ xname: str | None = None,
2579
+ yname: str | None = None,
2580
+ hue: str | None = None,
2581
+ title: str | None = None,
2582
+ palette: str | None = None,
2583
+ outline: bool = False,
2584
+ width: int = config.width,
2585
+ height: int = config.height,
2586
+ linewidth: float = config.line_width,
2587
+ dpi: int = config.dpi,
2588
+ save_path: str | None = None,
2589
+ ax: Axes | None = None,
2590
+ ) -> None:
2591
+
2592
+ """
2593
+ 2차원 공간에서 군집분석 결과를 산점도로 시각화함.
2594
+
2595
+ Args:
2596
+ estimator (KMeans): 학습된 KMeans 군집 모델 객체.
2597
+ data (DataFrame): 군집분석에 사용된 입력 데이터 (n_samples, n_features).
2598
+ xname (str, optional): x축에 사용할 컬럼명. None이면 첫 번째 컬럼 사용.
2599
+ yname (str, optional): y축에 사용할 컬럼명. None이면 두 번째 컬럼 사용.
2600
+ hue (str, optional): 군집 구분에 사용할 컬럼명. None이면 'cluster' 자동 생성.
2601
+ title (str, optional): 플롯 제목. None이면 기본값 사용.
2602
+ palette (str, optional): 색상 팔레트.
2603
+ outline (bool, optional): 외곽선 표시 여부.
2604
+ width (int, optional): 플롯 가로 크기 (inch 단위).
2605
+ height (int, optional): 플롯 세로 크기 (inch 단위).
2606
+ linewidth (float, optional): 중심점 등 선 두께.
2607
+ dpi (int, optional): 플롯 해상도(DPI).
2608
+ save_path (str, optional): 저장 경로 지정 시 파일로 저장.
2609
+ ax (Axes, optional): 기존 matplotlib Axes 객체. None이면 새로 생성.
2610
+
2611
+ Returns:
2612
+ None
2613
+
2614
+ Example:
2615
+ ```python
2616
+ cluster_plot(estimator, data, xname='Sepal.Length', yname='Sepal.Width')
2617
+ ```
2618
+
2619
+ Note:
2620
+ - 각 군집별 산점도와 중심점(빨간색 원/숫자) 표시
2621
+ - 2차원 특성 공간에서 군집 분포와 분리도 시각화
2622
+ """
2623
+ outparams = False
2624
+ if ax is None:
2625
+ fig, ax = get_default_ax(width, height, 1, 1, dpi) # type: ignore
2626
+ outparams = True
2627
+
2628
+ df = data.copy()
2629
+
2630
+ if not hue:
2631
+ df['cluster'] = estimator.labels_ # type: ignore
2632
+ hue = 'cluster'
2633
+
2634
+ if xname is None:
2635
+ xname = df.columns[0] # type: ignore
2636
+
2637
+ if yname is None:
2638
+ yname = df.columns[1] # type: ignore
2639
+
2640
+ xindex = df.columns.get_loc(xname) # type: ignore
2641
+ yindex = df.columns.get_loc(yname) # type: ignore
2642
+
2643
+ def callback(ax: Axes) -> None:
2644
+ # 클러스터 중심점 표시
2645
+ centers = estimator.cluster_centers_ # type: ignore
2646
+ ax.scatter( # type: ignore
2647
+ centers[:, xindex],
2648
+ centers[:, yindex],
2649
+ marker="o",
2650
+ color="white",
2651
+ alpha=1,
2652
+ s=200,
2653
+ edgecolor="r",
2654
+ linewidth=linewidth
2655
+ )
2656
+
2657
+ for i, c in enumerate(centers):
2658
+ ax.scatter(c[xindex], c[yindex], marker="$%d$" % i, alpha=1, s=50, edgecolor="k")
2659
+
2660
+ ax.set_xlabel("Feature space for the " + xname)
2661
+ ax.set_ylabel("Feature space for the " + yname)
2662
+
2663
+ scatterplot(
2664
+ df=df,
2665
+ xname=xname,
2666
+ yname=yname,
2667
+ hue=hue,
2668
+ title="The visualization of the clustered data." if title is None else title,
2669
+ outline=outline,
2670
+ palette=palette,
2671
+ width=width,
2672
+ height=height,
2673
+ linewidth=linewidth,
2674
+ dpi=dpi,
2675
+ save_path=save_path,
2676
+ callback=callback,
2677
+ ax=ax
2678
+ )
2679
+
2680
+ def visualize_silhouette(estimator: KMeans, data: DataFrame) -> None:
2681
+ """
2682
+ 군집분석 결과의 실루엣 플롯과 군집 산점도를 한 화면에 함께 시각화함.
2683
+
2684
+ Args:
2685
+ estimator (KMeans): 학습된 KMeans 군집 모델 객체.
2686
+ data (DataFrame): 군집분석에 사용된 입력 데이터 (n_samples, n_features).
2687
+
2688
+ Returns:
2689
+ None
2690
+
2691
+ Note:
2692
+ - 실루엣 플롯(왼쪽)과 2차원 군집 산점도(오른쪽)를 동시에 확인 가능
2693
+ - 군집 품질과 분포를 한눈에 비교·분석할 때 유용
2694
+ """
2695
+ fig, ax = get_default_ax(rows=1, cols=2)
2696
+
2697
+ silhouette_plot(
2698
+ estimator=estimator,
2699
+ data=data,
2700
+ ax=ax[0],
2701
+ )
2702
+
2703
+ cluster_plot(
2704
+ estimator=estimator,
2705
+ data=data,
2706
+ ax=ax[1],
2707
+ )
2708
+
2709
+ finalize_plot(ax)
hossam/hs_timeserise.py CHANGED
@@ -123,7 +123,7 @@ def diff(
123
123
  ardict["기각값(Critical Values) %s" % key] = value
124
124
 
125
125
  stationarity = ar[1] <= 0.05
126
- ardict["데이터 정상성 여부"] = "정상" if stationarity else "비정상"
126
+ ardict["데이터 정상성 여부"] = "정상" if stationarity else "비정상" # type: ignore
127
127
 
128
128
  ardf = DataFrame(ardict, index=["ADF Test"]).T
129
129
  pretty_table(ardf)
hossam/hs_util.py CHANGED
@@ -2,14 +2,21 @@
2
2
  # -------------------------------------------------------------
3
3
  import requests
4
4
  import json
5
+ import tempfile
6
+ import zipfile
7
+ import shutil
8
+ from pathlib import Path
5
9
  from typing import TYPE_CHECKING
6
10
  from importlib.metadata import distributions
7
11
  import pandas as pd
8
12
  import numpy as np
13
+ import glob as gl
14
+ # -------------------------------------------------------------
9
15
  from pandas import DataFrame, DatetimeIndex, read_csv, read_excel
10
16
  from scipy.stats import normaltest
11
17
  from tabulate import tabulate
12
18
  from os.path import join, exists
19
+ import os
13
20
  from io import BytesIO
14
21
  from pandas import DataFrame, read_csv, read_excel
15
22
  from typing import Optional, Tuple, Any
@@ -20,6 +27,40 @@ BASE_URL = "https://data.hossam.kr"
20
27
  def __get_df(path: str, index_col=None) -> DataFrame:
21
28
  p = path.rfind(".")
22
29
  exec = path[p+1:].lower()
30
+ tmp_dir = None
31
+
32
+ # 파일 확장자가 압축파일인 경우 로컬에 파일을 다운로드 후 압축 해제
33
+ if exec == "zip":
34
+ tmp_dir = os.getcwd() + "/.hossam_tmp"
35
+ os.makedirs(tmp_dir, exist_ok=True)
36
+ zip_path = join(tmp_dir, "data.zip")
37
+
38
+ # 원격 URL인 경우 파일 다운로드
39
+ if path.lower().startswith(('http://', 'https://')):
40
+ path = path.replace("\\", "/")
41
+ with requests.Session() as session:
42
+ r = session.get(path)
43
+
44
+ if r.status_code != 200:
45
+ raise Exception(f"HTTP {r.status_code} Error - {r.reason} > {path}")
46
+
47
+ with open(zip_path, "wb") as f:
48
+ f.write(r.content)
49
+ else:
50
+ zip_path = Path(path)
51
+
52
+ # 압축 해제
53
+ with zipfile.ZipFile(zip_path, 'r') as zip_ref:
54
+ zip_ref.extractall(tmp_dir)
55
+
56
+ # 압축 해제된 파일 중 첫 번째 파일을 데이터로 로드
57
+ extracted_files = list(gl.glob(join(tmp_dir, '*')))
58
+ if not extracted_files:
59
+ raise FileNotFoundError("압축 파일 내에 데이터 파일이 없습니다.")
60
+
61
+ path = str(extracted_files[0])
62
+ p = path.rfind(".")
63
+ exec = path[p+1:].lower()
23
64
 
24
65
  if exec == 'xlsx':
25
66
  # If path is a remote URL, fetch the file once and reuse the bytes
@@ -60,6 +101,9 @@ def __get_df(path: str, index_col=None) -> DataFrame:
60
101
  else:
61
102
  df = read_csv(path, index_col=index_col)
62
103
 
104
+ if tmp_dir:
105
+ shutil.rmtree(tmp_dir)
106
+
63
107
  return df
64
108
 
65
109
  # -------------------------------------------------------------
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hossam
3
- Version: 0.4.8
3
+ Version: 0.4.11
4
4
  Summary: Hossam Data Helper
5
5
  Author-email: Lee Kwang-Ho <leekh4232@gmail.com>
6
6
  License-Expression: MIT
@@ -61,9 +61,9 @@ title: 🎓 Hossam Data Helper
61
61
 
62
62
  - 📊 **풍부한 시각화**: 25+ 시각화 함수 (Seaborn/Matplotlib 기반)
63
63
  - 🎯 **통계 분석**: 회귀, 분류, 시계열 분석 도구
64
+ - 🤖 **머신 러닝**: 예측, 분류, 군집 학습 모델 구축 및 성능 평가
64
65
  - 📦 **샘플 데이터**: 학습용 데이터셋 즉시 로드
65
66
  - 🔧 **데이터 전처리**: 결측치 처리, 이상치 탐지, 스케일링
66
- - 🤖 **MCP 서버**: VSCode/Copilot과 통합 가능한 Model Context Protocol 지원
67
67
  - 📈 **교육용 최적화**: 데이터 분석 교육에 특화된 설계
68
68
 
69
69
 
@@ -84,9 +84,11 @@ pip install hossam
84
84
  - **hs_plot**: 25+ 시각화 함수 (선 그래프, 산점도, 히스토그램, 박스플롯, 히트맵 등)
85
85
  - **hs_stats**: 회귀/분류 분석, 교차검증, 정규성 검정, 상관분석 등
86
86
  - **hs_prep**: 결측치 처리, 이상치 탐지, 스케일링, 인코딩 등의 데이터 전처리 기능
87
- - **hs_timeseries**: 시계열 분석 기능 지원
87
+ - **hs_timeserise**: 시계열 분석 기능 지원
88
88
  - **hs_gis**: GIS 데이터 로드 및 시각화 (대한민국 지도 지원)
89
89
  - **hs_util**: 예쁜 테이블 출력, 그리드 서치 등
90
+ - **hs_cluster**: 군집분석, PCA 등 (작업중)
91
+ - **hs_ml**: 예측, 분류 분석 (예정)
90
92
 
91
93
  자세한 사용법은 [API 문서](https://py.hossam.kr/api/hossam/)를 참고하세요.
92
94
 
@@ -0,0 +1,16 @@
1
+ hossam/NotoSansKR-Regular.ttf,sha256=0SCufUQwcVWrWTu75j4Lt_V2bgBJIBXl1p8iAJJYkVY,6185516
2
+ hossam/__init__.py,sha256=4cGvavSotmQKhkHS4UCANhzszrMNwXNESAhh0RuFF-w,2893
3
+ hossam/hs_classroom.py,sha256=Sb1thy49LKn2zU90aiOVwHWhyWSMHLZbZX7eXmQlquc,27523
4
+ hossam/hs_cluster.py,sha256=anjoZ12JsIDWoGhm6agd0IF4N_md5czutyW4rbXnDEM,4255
5
+ hossam/hs_gis.py,sha256=DVmndBK-_7GMK3J1_on3ieEQk1S0MfUZ8_wlX-cDdZQ,11581
6
+ hossam/hs_plot.py,sha256=5j498vga_1wZBRlMUZ047LswNSvfTEpGP6uL7yzl3-g,92744
7
+ hossam/hs_prep.py,sha256=ypuX97mCxpo7CLoI_S79bUw7th0ok5LCZjt4vzRaGiI,38326
8
+ hossam/hs_stats.py,sha256=MDS3rvaXDP8aYwcE36JTetWiZgE4fkXnNo0vwlXu-pA,119890
9
+ hossam/hs_timeserise.py,sha256=NzGV4bJmVQr3qUFySOP25qENItmloYjgh3VgwSbSmXc,43163
10
+ hossam/hs_util.py,sha256=ptl-2W7-0Ad_BemZMR8cFnDt6-SHCRRCk1Gh7giFjSs,16149
11
+ hossam/leekh.png,sha256=1PB5NQ24SDoHA5KMiBBsWpSa3iniFcwFTuGwuOsTHfI,6395
12
+ hossam-0.4.11.dist-info/licenses/LICENSE,sha256=nIqzhlcFY_2D6QtFsYjwU7BWkafo-rUJOQpDZ-DsauI,941
13
+ hossam-0.4.11.dist-info/METADATA,sha256=POoVivyFd3rFLoQny6kigfNmsDbHdyvndbZ71Sz2NjY,3803
14
+ hossam-0.4.11.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
15
+ hossam-0.4.11.dist-info/top_level.txt,sha256=_-7bwjhthHplWhywEaHIJX2yL11CQCaLjCNSBlk6wiQ,7
16
+ hossam-0.4.11.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,15 +0,0 @@
1
- hossam/NotoSansKR-Regular.ttf,sha256=0SCufUQwcVWrWTu75j4Lt_V2bgBJIBXl1p8iAJJYkVY,6185516
2
- hossam/__init__.py,sha256=kiE_u23uXygPmpukEP-n-szHnM2AE5kWHQICByn3qhA,2788
3
- hossam/hs_classroom.py,sha256=oNRnHPXOu0-YqtPY7EJeS1qteH0CtKxNk5Lt7opti_w,27523
4
- hossam/hs_gis.py,sha256=DVmndBK-_7GMK3J1_on3ieEQk1S0MfUZ8_wlX-cDdZQ,11581
5
- hossam/hs_plot.py,sha256=83B7fjEDaXnpwg8GhDGsVX6lAd81rYqoqvMGzovn3qc,85900
6
- hossam/hs_prep.py,sha256=ypuX97mCxpo7CLoI_S79bUw7th0ok5LCZjt4vzRaGiI,38326
7
- hossam/hs_stats.py,sha256=MDS3rvaXDP8aYwcE36JTetWiZgE4fkXnNo0vwlXu-pA,119890
8
- hossam/hs_timeserise.py,sha256=XB8DKJBFb-892ACNCATcyBliSJVtbn-dpzfKi-grRAo,43148
9
- hossam/hs_util.py,sha256=i5thXDt4VVWbju3y6Q7PAdEay62b-5PJNX9TjQhFZCM,14663
10
- hossam/leekh.png,sha256=1PB5NQ24SDoHA5KMiBBsWpSa3iniFcwFTuGwuOsTHfI,6395
11
- hossam-0.4.8.dist-info/licenses/LICENSE,sha256=nIqzhlcFY_2D6QtFsYjwU7BWkafo-rUJOQpDZ-DsauI,941
12
- hossam-0.4.8.dist-info/METADATA,sha256=G29Fmy2WwAUSrqWEWhzntFhICYB3fXge-s8ZFF52riY,3706
13
- hossam-0.4.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- hossam-0.4.8.dist-info/top_level.txt,sha256=_-7bwjhthHplWhywEaHIJX2yL11CQCaLjCNSBlk6wiQ,7
15
- hossam-0.4.8.dist-info/RECORD,,