hossam 0.4.16__py3-none-any.whl → 0.4.17__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/hs_cluster.py +283 -120
- hossam/hs_plot.py +224 -5
- hossam/hs_prep.py +241 -56
- hossam/hs_util.py +20 -0
- {hossam-0.4.16.dist-info → hossam-0.4.17.dist-info}/METADATA +1 -1
- {hossam-0.4.16.dist-info → hossam-0.4.17.dist-info}/RECORD +9 -9
- {hossam-0.4.16.dist-info → hossam-0.4.17.dist-info}/WHEEL +0 -0
- {hossam-0.4.16.dist-info → hossam-0.4.17.dist-info}/licenses/LICENSE +0 -0
- {hossam-0.4.16.dist-info → hossam-0.4.17.dist-info}/top_level.txt +0 -0
hossam/hs_plot.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
from types import SimpleNamespace
|
|
4
|
-
from typing import Callable
|
|
4
|
+
from typing import Callable, Literal
|
|
5
|
+
from itertools import combinations
|
|
5
6
|
|
|
6
7
|
# ===================================================================
|
|
7
8
|
import numpy as np
|
|
@@ -15,6 +16,7 @@ from pandas import DataFrame
|
|
|
15
16
|
# ===================================================================
|
|
16
17
|
from scipy.stats import t
|
|
17
18
|
from scipy.spatial import ConvexHull
|
|
19
|
+
from scipy.cluster.hierarchy import dendrogram as sch_dendrogram
|
|
18
20
|
from statsmodels.graphics.gofplots import qqplot as sm_qqplot
|
|
19
21
|
from statsmodels.nonparametric.smoothers_lowess import lowess as sm_lowess
|
|
20
22
|
|
|
@@ -23,6 +25,7 @@ from statannotations.Annotator import Annotator
|
|
|
23
25
|
|
|
24
26
|
# ===================================================================
|
|
25
27
|
from sklearn.cluster import AgglomerativeClustering, KMeans
|
|
28
|
+
from sklearn.decomposition import PCA
|
|
26
29
|
|
|
27
30
|
from sklearn.metrics import (
|
|
28
31
|
mean_squared_error,
|
|
@@ -34,6 +37,8 @@ from sklearn.metrics import (
|
|
|
34
37
|
silhouette_samples,
|
|
35
38
|
)
|
|
36
39
|
|
|
40
|
+
from .hs_util import is_2d
|
|
41
|
+
|
|
37
42
|
# ===================================================================
|
|
38
43
|
config = SimpleNamespace(
|
|
39
44
|
dpi=200,
|
|
@@ -749,7 +754,7 @@ def stackplot(
|
|
|
749
754
|
# 산점도를 그린다
|
|
750
755
|
# ===================================================================
|
|
751
756
|
def scatterplot(
|
|
752
|
-
df: DataFrame,
|
|
757
|
+
df: DataFrame | None,
|
|
753
758
|
xname: str,
|
|
754
759
|
yname: str,
|
|
755
760
|
hue=None,
|
|
@@ -769,7 +774,7 @@ def scatterplot(
|
|
|
769
774
|
"""산점도를 그린다.
|
|
770
775
|
|
|
771
776
|
Args:
|
|
772
|
-
df (DataFrame): 시각화할 데이터.
|
|
777
|
+
df (DataFrame | None): 시각화할 데이터.
|
|
773
778
|
xname (str): x축 컬럼.
|
|
774
779
|
yname (str): y축 컬럼.
|
|
775
780
|
hue (str|None): 범주 컬럼.
|
|
@@ -796,12 +801,12 @@ def scatterplot(
|
|
|
796
801
|
|
|
797
802
|
if outline and hue is not None:
|
|
798
803
|
# 군집별 값의 종류별로 반복 수행
|
|
799
|
-
for c in df[hue].unique():
|
|
804
|
+
for c in df[hue].unique(): # type: ignore
|
|
800
805
|
if c == -1:
|
|
801
806
|
continue
|
|
802
807
|
|
|
803
808
|
# 한 종류만 필터링한 결과에서 두 변수만 선택
|
|
804
|
-
df_c = df.loc[df[hue] == c, [xname, yname]]
|
|
809
|
+
df_c = df.loc[df[hue] == c, [xname, yname]] # type: ignore
|
|
805
810
|
|
|
806
811
|
try:
|
|
807
812
|
# 외각선 좌표 계산
|
|
@@ -2644,6 +2649,9 @@ def silhouette_plot(
|
|
|
2644
2649
|
finalize_plot(ax, callback, outparams, save_path, True, title) # type: ignore
|
|
2645
2650
|
|
|
2646
2651
|
|
|
2652
|
+
# ===================================================================
|
|
2653
|
+
# 군집분석 결과 시각화
|
|
2654
|
+
# ===================================================================
|
|
2647
2655
|
def cluster_plot(
|
|
2648
2656
|
estimator: KMeans | AgglomerativeClustering | None = None,
|
|
2649
2657
|
data: DataFrame | None = None,
|
|
@@ -2755,6 +2763,9 @@ def cluster_plot(
|
|
|
2755
2763
|
)
|
|
2756
2764
|
|
|
2757
2765
|
|
|
2766
|
+
# ===================================================================
|
|
2767
|
+
# 군집분석 결과의 실루엣 플롯과 군집 산점도를 한 화면에 함께 시각화
|
|
2768
|
+
# ===================================================================
|
|
2758
2769
|
def visualize_silhouette(
|
|
2759
2770
|
estimator: KMeans | AgglomerativeClustering,
|
|
2760
2771
|
data: DataFrame,
|
|
@@ -2819,3 +2830,211 @@ def visualize_silhouette(
|
|
|
2819
2830
|
)
|
|
2820
2831
|
|
|
2821
2832
|
finalize_plot(ax)
|
|
2833
|
+
|
|
2834
|
+
|
|
2835
|
+
|
|
2836
|
+
# ===================================================================
|
|
2837
|
+
# 덴드로그램 시각화
|
|
2838
|
+
# ===================================================================
|
|
2839
|
+
def dandrogram(
|
|
2840
|
+
estimator: AgglomerativeClustering,
|
|
2841
|
+
p: int = 30,
|
|
2842
|
+
count_sort: Literal["ascending", "descending", False] = "ascending",
|
|
2843
|
+
title: str | None = None,
|
|
2844
|
+
width: int = config.width,
|
|
2845
|
+
height: int = config.height,
|
|
2846
|
+
dpi: int = config.dpi,
|
|
2847
|
+
save_path: str | None = None,
|
|
2848
|
+
callback: Callable | None = None,
|
|
2849
|
+
ax: Axes | None = None
|
|
2850
|
+
) -> None:
|
|
2851
|
+
"""덴드로그램 시각화
|
|
2852
|
+
|
|
2853
|
+
Args:
|
|
2854
|
+
estimator (AgglomerativeClustering): 학습된 AgglomerativeClustering 군집 모델 객체.
|
|
2855
|
+
p (int): 덴드로그램에서 표시할 마지막 병합된 군집 수. 기본값 30.
|
|
2856
|
+
count_sort (str): 'ascending' 또는 'descending'으로 병합 순서 정렬.
|
|
2857
|
+
title (str|None): 그래프 제목.
|
|
2858
|
+
palette (str|None): 팔레트 이름.
|
|
2859
|
+
width (int): 캔버스 가로 픽셀.
|
|
2860
|
+
height (int): 캔버스 세로 픽셀.
|
|
2861
|
+
dpi (int): 그림 크기 및 해상도.
|
|
2862
|
+
save_path (str|None): 저장 경로.
|
|
2863
|
+
callback (Callable|None): Axes 후처리 콜백.
|
|
2864
|
+
ax (Axes|None): 외부에서 전달한 Axes. None이면 새로 생성.
|
|
2865
|
+
|
|
2866
|
+
Returns:
|
|
2867
|
+
None
|
|
2868
|
+
"""
|
|
2869
|
+
# 덴드로그램을 그리기 위해 linkage 행렬 생성
|
|
2870
|
+
counts = np.zeros(estimator.children_.shape[0]) # type: ignore
|
|
2871
|
+
n_samples = len(estimator.labels_)
|
|
2872
|
+
|
|
2873
|
+
for i, merge in enumerate(estimator.children_): # type: ignore
|
|
2874
|
+
current_count = 0
|
|
2875
|
+
for child_idx in merge:
|
|
2876
|
+
if child_idx < n_samples:
|
|
2877
|
+
current_count += 1 # leaf node
|
|
2878
|
+
else:
|
|
2879
|
+
current_count += counts[child_idx - n_samples]
|
|
2880
|
+
counts[i] = current_count
|
|
2881
|
+
|
|
2882
|
+
linkage_matrix = np.column_stack(
|
|
2883
|
+
[estimator.children_, estimator.distances_, counts]
|
|
2884
|
+
).astype(float)
|
|
2885
|
+
|
|
2886
|
+
outparams = False
|
|
2887
|
+
|
|
2888
|
+
if ax is None:
|
|
2889
|
+
fig, ax = get_default_ax(width, height, 1, 1, dpi) # type: ignore
|
|
2890
|
+
outparams = True
|
|
2891
|
+
|
|
2892
|
+
|
|
2893
|
+
sch_dendrogram(
|
|
2894
|
+
linkage_matrix,
|
|
2895
|
+
ax=ax,
|
|
2896
|
+
p=p,
|
|
2897
|
+
truncate_mode="lastp" if p > 0 else None,
|
|
2898
|
+
leaf_rotation=0,
|
|
2899
|
+
leaf_font_size=8,
|
|
2900
|
+
count_sort=count_sort,
|
|
2901
|
+
color_threshold=None,
|
|
2902
|
+
above_threshold_color="grey",
|
|
2903
|
+
)
|
|
2904
|
+
|
|
2905
|
+
finalize_plot(ax, callback, outparams, save_path, True, title) # type: ignore
|
|
2906
|
+
|
|
2907
|
+
|
|
2908
|
+
# ===================================================================
|
|
2909
|
+
# PCA 분석 결과에 대한 biplot 시각화
|
|
2910
|
+
# ===================================================================
|
|
2911
|
+
def pca_plot(
|
|
2912
|
+
estimator: PCA,
|
|
2913
|
+
data: DataFrame,
|
|
2914
|
+
yname: str | None = None,
|
|
2915
|
+
fields: list | tuple | list[list] | tuple[list] | list[tuple] | tuple[tuple] | None = None,
|
|
2916
|
+
hue: str | None = None,
|
|
2917
|
+
palette: str | None = None,
|
|
2918
|
+
width: int = config.width,
|
|
2919
|
+
height: int = config.height,
|
|
2920
|
+
linewidth: float = config.line_width,
|
|
2921
|
+
dpi: int = config.dpi,
|
|
2922
|
+
save_path: str | None = None,
|
|
2923
|
+
callback: Callable | None = None,
|
|
2924
|
+
) -> None:
|
|
2925
|
+
"""
|
|
2926
|
+
PCA 분석 결과에 대한 biplot 시각화
|
|
2927
|
+
|
|
2928
|
+
Args:
|
|
2929
|
+
estimator (PCA): 학습된 PCA 객체.
|
|
2930
|
+
data (DataFrame): PCA에 사용된 원본 데이터.
|
|
2931
|
+
yname (str | None): 종속변수 컬럼명.
|
|
2932
|
+
fields (list | tuple | list[list] | tuple[list] | list[tuple] | tuple[tuple] | None): 시각화할 독립변수 목록. None이면 자동 탐지.
|
|
2933
|
+
hue (str|None): 집단 구분 컬럼명.
|
|
2934
|
+
palette (str|None): 팔레트 이름.
|
|
2935
|
+
width (int): 캔버스 가로 픽셀.
|
|
2936
|
+
height (int): 캔버스 세로 픽셀.
|
|
2937
|
+
linewidth (float): 선 굵기.
|
|
2938
|
+
dpi (int): 그림 크기 및 해상도.
|
|
2939
|
+
save_path (str|None): 저장 경로.
|
|
2940
|
+
callback (Callable|None): Axes 후처리 콜백.
|
|
2941
|
+
|
|
2942
|
+
Returns:
|
|
2943
|
+
None
|
|
2944
|
+
"""
|
|
2945
|
+
df = data.copy()
|
|
2946
|
+
df_columns = df.columns.tolist()
|
|
2947
|
+
|
|
2948
|
+
# 종속변수가 지정되었다면 해당 컬럼 추출
|
|
2949
|
+
yfield = None
|
|
2950
|
+
if yname is not None and yname in data.columns:
|
|
2951
|
+
yfield = df[[yname]].copy()
|
|
2952
|
+
df = df.drop(columns=[yname])
|
|
2953
|
+
|
|
2954
|
+
# PCA 변환 수행
|
|
2955
|
+
#display(df)
|
|
2956
|
+
score = estimator.transform(df)
|
|
2957
|
+
#print(score)
|
|
2958
|
+
|
|
2959
|
+
# 추정기로부터 PCA 결과 데이터 프레임 생성
|
|
2960
|
+
pca_df = DataFrame(
|
|
2961
|
+
data=score,
|
|
2962
|
+
columns=[f"PC{i+1}" for i in range(estimator.n_components_)],
|
|
2963
|
+
)
|
|
2964
|
+
#display(pca_df)
|
|
2965
|
+
|
|
2966
|
+
# 종속변수 컬럼 추가
|
|
2967
|
+
if yfield is not None:
|
|
2968
|
+
pca_df[yname] = yfield
|
|
2969
|
+
|
|
2970
|
+
# 모든 컬럼명에 대한 조합 생성
|
|
2971
|
+
if fields is None:
|
|
2972
|
+
feature_cols = pca_df.columns.tolist()
|
|
2973
|
+
if yname is not None and yname in feature_cols:
|
|
2974
|
+
feature_cols.remove(yname)
|
|
2975
|
+
fields = list(combinations(feature_cols, 2))
|
|
2976
|
+
|
|
2977
|
+
if not is_2d(fields):
|
|
2978
|
+
fields = [fields] # type: ignore
|
|
2979
|
+
|
|
2980
|
+
components = estimator.components_
|
|
2981
|
+
|
|
2982
|
+
x_index: int = 0
|
|
2983
|
+
y_index: int = 0
|
|
2984
|
+
|
|
2985
|
+
def __callable(ax) -> None:
|
|
2986
|
+
for i in range(n):
|
|
2987
|
+
ax.arrow(
|
|
2988
|
+
0,
|
|
2989
|
+
0,
|
|
2990
|
+
components[x_index, i],
|
|
2991
|
+
components[y_index, i],
|
|
2992
|
+
color="r",
|
|
2993
|
+
head_width=0.007,
|
|
2994
|
+
head_length=0.007,
|
|
2995
|
+
linewidth=linewidth * 0.75,
|
|
2996
|
+
alpha=0.75,
|
|
2997
|
+
)
|
|
2998
|
+
ax.text(
|
|
2999
|
+
components[x_index, i] * 1.15,
|
|
3000
|
+
components[y_index, i] * 1.15,
|
|
3001
|
+
f"{df_columns[i]} ({components[x_index, i]:.2f})",
|
|
3002
|
+
color="b",
|
|
3003
|
+
ha="center",
|
|
3004
|
+
va="center",
|
|
3005
|
+
)
|
|
3006
|
+
|
|
3007
|
+
if callback is not None:
|
|
3008
|
+
callback(ax)
|
|
3009
|
+
|
|
3010
|
+
for field_group in fields: # type: ignore
|
|
3011
|
+
x_index = int(pca_df.columns.get_loc(field_group[0])) # type: ignore
|
|
3012
|
+
y_index = int(pca_df.columns.get_loc(field_group[1])) # type: ignore
|
|
3013
|
+
|
|
3014
|
+
xs = score[:, x_index]
|
|
3015
|
+
ys = score[:, y_index]
|
|
3016
|
+
n = score.shape[1]
|
|
3017
|
+
|
|
3018
|
+
scalex = 1.0 / (xs.max() - xs.min())
|
|
3019
|
+
scaley = 1.0 / (ys.max() - ys.min())
|
|
3020
|
+
|
|
3021
|
+
title = "PCA Biplot"
|
|
3022
|
+
if field_group is not None:
|
|
3023
|
+
title += " - " + ", ".join(field_group)
|
|
3024
|
+
|
|
3025
|
+
scatterplot(
|
|
3026
|
+
df=None,
|
|
3027
|
+
xname=xs * scalex,
|
|
3028
|
+
yname=ys * scaley,
|
|
3029
|
+
hue=pca_df[hue] if hue is not None else None,
|
|
3030
|
+
outline=False,
|
|
3031
|
+
palette=palette,
|
|
3032
|
+
width=width,
|
|
3033
|
+
height=height,
|
|
3034
|
+
linewidth=linewidth,
|
|
3035
|
+
dpi=dpi,
|
|
3036
|
+
save_path=save_path,
|
|
3037
|
+
title=title,
|
|
3038
|
+
callback=__callable,
|
|
3039
|
+
)
|
|
3040
|
+
|