hossam 0.3.14__py3-none-any.whl → 0.3.16__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 +6 -6
- hossam/hs_plot.py +141 -14
- hossam/hs_prep.py +316 -34
- hossam/hs_stats.py +152 -0
- {hossam-0.3.14.dist-info → hossam-0.3.16.dist-info}/METADATA +1 -1
- {hossam-0.3.14.dist-info → hossam-0.3.16.dist-info}/RECORD +9 -9
- {hossam-0.3.14.dist-info → hossam-0.3.16.dist-info}/WHEEL +0 -0
- {hossam-0.3.14.dist-info → hossam-0.3.16.dist-info}/licenses/LICENSE +0 -0
- {hossam-0.3.14.dist-info → hossam-0.3.16.dist-info}/top_level.txt +0 -0
hossam/__init__.py
CHANGED
|
@@ -15,14 +15,14 @@ except Exception:
|
|
|
15
15
|
|
|
16
16
|
hs_fig = SimpleNamespace(
|
|
17
17
|
dpi=200,
|
|
18
|
-
width=
|
|
19
|
-
height=
|
|
20
|
-
font_size=
|
|
18
|
+
width=800,
|
|
19
|
+
height=480,
|
|
20
|
+
font_size=9.5,
|
|
21
21
|
font_weight="light",
|
|
22
|
-
frame_width=0.
|
|
23
|
-
line_width=1,
|
|
22
|
+
frame_width=0.5,
|
|
23
|
+
line_width=1.5,
|
|
24
24
|
grid_alpha=0.3,
|
|
25
|
-
grid_width=0.
|
|
25
|
+
grid_width=0.5,
|
|
26
26
|
fill_alpha=0.3
|
|
27
27
|
)
|
|
28
28
|
|
hossam/hs_plot.py
CHANGED
|
@@ -56,6 +56,9 @@ def get_default_ax(width: int = hs_fig.width, height: int = hs_fig.height, rows:
|
|
|
56
56
|
rows (int): 서브플롯 행 개수.
|
|
57
57
|
cols (int): 서브플롯 열 개수.
|
|
58
58
|
dpi (int): 해상도(DPI).
|
|
59
|
+
flatten (bool): Axes 배열을 1차원 리스트로 평탄화할지 여부.
|
|
60
|
+
ws (int|None): 서브플롯 가로 간격(`wspace`). rows/cols가 1보다 클 때만 적용.
|
|
61
|
+
hs (int|None): 서브플롯 세로 간격(`hspace`). rows/cols가 1보다 클 때만 적용.
|
|
59
62
|
|
|
60
63
|
Returns:
|
|
61
64
|
tuple[Figure, Axes]: 생성된 matplotlib Figure와 Axes 객체.
|
|
@@ -100,7 +103,7 @@ def finalize_plot(ax: Axes, callback: any = None, outparams: bool = False, save_
|
|
|
100
103
|
callback (Callable|None): 추가 설정을 위한 사용자 콜백.
|
|
101
104
|
outparams (bool): 내부에서 생성한 Figure인 경우 True.
|
|
102
105
|
save_path (str|None): 이미지 저장 경로. None이 아니면 해당 경로로 저장.
|
|
103
|
-
grid (bool): 그리드 표시 여부.
|
|
106
|
+
grid (bool): 그리드 표시 여부. 기본값은 True입니다.
|
|
104
107
|
|
|
105
108
|
Returns:
|
|
106
109
|
None
|
|
@@ -166,6 +169,7 @@ def lineplot(
|
|
|
166
169
|
height (int): 캔버스 세로 픽셀.
|
|
167
170
|
linewidth (float): 선 굵기.
|
|
168
171
|
dpi (int): 해상도.
|
|
172
|
+
save_path (str|None): 이미지 저장 경로. None이면 화면에 표시.
|
|
169
173
|
callback (Callable|None): Axes 후처리 콜백.
|
|
170
174
|
ax (Axes|None): 외부에서 전달한 Axes.
|
|
171
175
|
**params: seaborn lineplot 추가 인자.
|
|
@@ -230,6 +234,7 @@ def boxplot(
|
|
|
230
234
|
height (int): 캔버스 세로 픽셀.
|
|
231
235
|
linewidth (float): 선 굵기.
|
|
232
236
|
dpi (int): 그림 크기 및 해상도.
|
|
237
|
+
save_path (str|None): 이미지 저장 경로. None이면 화면에 표시.
|
|
233
238
|
callback (Callable|None): Axes 후처리 콜백.
|
|
234
239
|
ax (Axes|None): 외부에서 전달한 Axes.
|
|
235
240
|
**params: seaborn boxplot 추가 인자.
|
|
@@ -1847,6 +1852,7 @@ def distribution_by_class(
|
|
|
1847
1852
|
linewidth=linewidth,
|
|
1848
1853
|
dpi=dpi,
|
|
1849
1854
|
callback=callback,
|
|
1855
|
+
save_path=save_path
|
|
1850
1856
|
)
|
|
1851
1857
|
elif type == "hist":
|
|
1852
1858
|
histplot(
|
|
@@ -1861,6 +1867,7 @@ def distribution_by_class(
|
|
|
1861
1867
|
linewidth=linewidth,
|
|
1862
1868
|
dpi=dpi,
|
|
1863
1869
|
callback=callback,
|
|
1870
|
+
save_path=save_path
|
|
1864
1871
|
)
|
|
1865
1872
|
elif type == "histkde":
|
|
1866
1873
|
histplot(
|
|
@@ -1875,6 +1882,7 @@ def distribution_by_class(
|
|
|
1875
1882
|
linewidth=linewidth,
|
|
1876
1883
|
dpi=dpi,
|
|
1877
1884
|
callback=callback,
|
|
1885
|
+
save_path=save_path
|
|
1878
1886
|
)
|
|
1879
1887
|
|
|
1880
1888
|
|
|
@@ -1892,6 +1900,7 @@ def scatter_by_class(
|
|
|
1892
1900
|
height: int = hs_fig.height,
|
|
1893
1901
|
linewidth: float = hs_fig.line_width,
|
|
1894
1902
|
dpi: int = hs_fig.dpi,
|
|
1903
|
+
save_path: str = None,
|
|
1895
1904
|
callback: any = None,
|
|
1896
1905
|
) -> None:
|
|
1897
1906
|
"""종속변수(y)와 각 연속형 독립변수(x) 간 산점도/볼록껍질을 그린다.
|
|
@@ -1939,10 +1948,14 @@ def scatter_by_class(
|
|
|
1939
1948
|
|
|
1940
1949
|
if outline:
|
|
1941
1950
|
for v in group:
|
|
1942
|
-
convex_hull(data, v[0], v[1], hue, palette,
|
|
1951
|
+
convex_hull(data=data, xname=v[0], yname=v[1], hue=hue, palette=palette,
|
|
1952
|
+
width=width, height=height, linewidth=linewidth, dpi=dpi, callback=callback,
|
|
1953
|
+
save_path=save_path)
|
|
1943
1954
|
else:
|
|
1944
1955
|
for v in group:
|
|
1945
|
-
scatterplot(data, v[0], v[1], hue, palette,
|
|
1956
|
+
scatterplot(data=data, xname=v[0], yname=v[1], hue=hue, palette=palette,
|
|
1957
|
+
width=width, height=height, linewidth=linewidth, dpi=dpi, callback=callback,
|
|
1958
|
+
save_path=save_path)
|
|
1946
1959
|
|
|
1947
1960
|
|
|
1948
1961
|
# ===================================================================
|
|
@@ -1951,7 +1964,7 @@ def scatter_by_class(
|
|
|
1951
1964
|
def categorical_target_distribution(
|
|
1952
1965
|
data: DataFrame,
|
|
1953
1966
|
yname: str,
|
|
1954
|
-
|
|
1967
|
+
hue: list | str | None = None,
|
|
1955
1968
|
kind: str = "box",
|
|
1956
1969
|
kde_fill: bool = True,
|
|
1957
1970
|
palette: str | None = None,
|
|
@@ -1960,6 +1973,7 @@ def categorical_target_distribution(
|
|
|
1960
1973
|
linewidth: float = hs_fig.line_width,
|
|
1961
1974
|
dpi: int = hs_fig.dpi,
|
|
1962
1975
|
cols: int = 2,
|
|
1976
|
+
save_path: str = None,
|
|
1963
1977
|
callback: any = None,
|
|
1964
1978
|
) -> None:
|
|
1965
1979
|
"""명목형 변수별로 종속변수 분포 차이를 시각화한다.
|
|
@@ -1967,7 +1981,7 @@ def categorical_target_distribution(
|
|
|
1967
1981
|
Args:
|
|
1968
1982
|
data (DataFrame): 시각화할 데이터.
|
|
1969
1983
|
yname (str): 종속변수 컬럼명(연속형 추천).
|
|
1970
|
-
|
|
1984
|
+
hue (list|str|None): 명목형 독립변수 목록. None이면 자동 탐지.
|
|
1971
1985
|
kind (str): 'box', 'violin', 'kde'.
|
|
1972
1986
|
kde_fill (bool): kind='kde'일 때 영역 채우기 여부.
|
|
1973
1987
|
palette (str|None): 팔레트 이름.
|
|
@@ -1983,13 +1997,13 @@ def categorical_target_distribution(
|
|
|
1983
1997
|
"""
|
|
1984
1998
|
|
|
1985
1999
|
# 명목형 컬럼 후보: object, category, bool
|
|
1986
|
-
if
|
|
2000
|
+
if hue is None:
|
|
1987
2001
|
cat_cols = data.select_dtypes(include=["object", "category", "bool", "boolean"]).columns
|
|
1988
2002
|
target_cols = [c for c in cat_cols if c != yname]
|
|
1989
|
-
elif isinstance(
|
|
1990
|
-
target_cols = [
|
|
2003
|
+
elif isinstance(hue, str):
|
|
2004
|
+
target_cols = [hue]
|
|
1991
2005
|
else:
|
|
1992
|
-
target_cols = list(
|
|
2006
|
+
target_cols = list(hue)
|
|
1993
2007
|
|
|
1994
2008
|
if len(target_cols) == 0:
|
|
1995
2009
|
return
|
|
@@ -2030,7 +2044,7 @@ def categorical_target_distribution(
|
|
|
2030
2044
|
|
|
2031
2045
|
|
|
2032
2046
|
# ===================================================================
|
|
2033
|
-
#
|
|
2047
|
+
# ROC 커브를 시각화 한다.
|
|
2034
2048
|
# ===================================================================
|
|
2035
2049
|
def roc_curve_plot(
|
|
2036
2050
|
fit,
|
|
@@ -2100,14 +2114,13 @@ def roc_curve_plot(
|
|
|
2100
2114
|
|
|
2101
2115
|
|
|
2102
2116
|
# ===================================================================
|
|
2103
|
-
#
|
|
2117
|
+
# 혼동행렬 시각화
|
|
2104
2118
|
# ===================================================================
|
|
2105
2119
|
def confusion_matrix_plot(
|
|
2106
2120
|
fit,
|
|
2107
2121
|
threshold: float = 0.5,
|
|
2108
2122
|
width: int = hs_fig.width,
|
|
2109
2123
|
height: int = hs_fig.height,
|
|
2110
|
-
linewidth: float = hs_fig.line_width,
|
|
2111
2124
|
dpi: int = hs_fig.dpi,
|
|
2112
2125
|
save_path: str = None,
|
|
2113
2126
|
callback: any = None,
|
|
@@ -2120,7 +2133,6 @@ def confusion_matrix_plot(
|
|
|
2120
2133
|
threshold (float): 예측 확률을 이진 분류로 변환할 임계값. 기본값 0.5.
|
|
2121
2134
|
width (int): 캔버스 가로 픽셀.
|
|
2122
2135
|
height (int): 캔버스 세로 픽셀.
|
|
2123
|
-
linewidth (float): 선 굵기 (현재 사용되지 않음).
|
|
2124
2136
|
dpi (int): 해상도.
|
|
2125
2137
|
callback (Callable|None): Axes 후처리 콜백.
|
|
2126
2138
|
ax (Axes|None): 외부에서 전달한 Axes. None이면 새로 생성.
|
|
@@ -2152,7 +2164,7 @@ def confusion_matrix_plot(
|
|
|
2152
2164
|
|
|
2153
2165
|
|
|
2154
2166
|
# ===================================================================
|
|
2155
|
-
#
|
|
2167
|
+
# 레이더 차트(방사형 차트)
|
|
2156
2168
|
# ===================================================================
|
|
2157
2169
|
def radarplot(
|
|
2158
2170
|
df: DataFrame,
|
|
@@ -2279,3 +2291,118 @@ def radarplot(
|
|
|
2279
2291
|
ax.set_title('Radar Chart', pad=20)
|
|
2280
2292
|
|
|
2281
2293
|
finalize_plot(ax, callback, outparams, save_path)
|
|
2294
|
+
|
|
2295
|
+
|
|
2296
|
+
# ===================================================================
|
|
2297
|
+
# 연속형 데이터 분포 시각화 (KDE + Boxplot)
|
|
2298
|
+
# ===================================================================
|
|
2299
|
+
def distribution_plot(
|
|
2300
|
+
data: DataFrame,
|
|
2301
|
+
column: str,
|
|
2302
|
+
clevel: float = 0.95,
|
|
2303
|
+
orient: str = "h",
|
|
2304
|
+
hue: str | None = None,
|
|
2305
|
+
kind: str = "boxplot",
|
|
2306
|
+
width: int = hs_fig.width,
|
|
2307
|
+
height: int = hs_fig.height,
|
|
2308
|
+
linewidth: float = hs_fig.line_width,
|
|
2309
|
+
dpi: int = hs_fig.dpi,
|
|
2310
|
+
save_path: str = None,
|
|
2311
|
+
callback: any = None,
|
|
2312
|
+
) -> None:
|
|
2313
|
+
"""연속형 데이터의 분포를 KDE와 Boxplot으로 시각화한다.
|
|
2314
|
+
|
|
2315
|
+
1행 2열의 서브플롯을 생성하여:
|
|
2316
|
+
- 왼쪽: KDE with 신뢰구간
|
|
2317
|
+
- 오른쪽: Boxplot
|
|
2318
|
+
|
|
2319
|
+
Args:
|
|
2320
|
+
data (DataFrame): 시각화할 데이터.
|
|
2321
|
+
column (str): 분석할 컬럼명.
|
|
2322
|
+
clevel (float): KDE 신뢰수준 (0~1). 기본값 0.95.
|
|
2323
|
+
orient (str): Boxplot 방향 ('v' 또는 'h'). 기본값 'h'.
|
|
2324
|
+
hue (str|None): 명목형 컬럼명. 지정하면 각 범주별로 행을 늘려 KDE와 boxplot을 그림.
|
|
2325
|
+
kind (str): 두 번째 그래프의 유형 (boxplot, hist). 기본값 "boxplot".
|
|
2326
|
+
width (int): 캔버스 가로 픽셀.
|
|
2327
|
+
height (int): 캔버스 세로 픽셀.
|
|
2328
|
+
linewidth (float): 선 굵기.
|
|
2329
|
+
dpi (int): 그림 크기 및 해상도.
|
|
2330
|
+
save_path (str|None): 저장 경로.
|
|
2331
|
+
callback (Callable|None): Axes 후처리 콜백.
|
|
2332
|
+
|
|
2333
|
+
Returns:
|
|
2334
|
+
None
|
|
2335
|
+
"""
|
|
2336
|
+
if hue is None:
|
|
2337
|
+
# 1행 2열 서브플롯 생성
|
|
2338
|
+
fig, axes = get_default_ax(width, height, rows=1, cols=2, dpi=dpi)
|
|
2339
|
+
|
|
2340
|
+
kde_confidence_interval(
|
|
2341
|
+
data=data,
|
|
2342
|
+
xnames=column,
|
|
2343
|
+
clevel=clevel,
|
|
2344
|
+
linewidth=linewidth,
|
|
2345
|
+
ax=axes[0],
|
|
2346
|
+
)
|
|
2347
|
+
|
|
2348
|
+
if kind == "hist":
|
|
2349
|
+
histplot(
|
|
2350
|
+
df=data,
|
|
2351
|
+
xname=column,
|
|
2352
|
+
linewidth=linewidth,
|
|
2353
|
+
ax=axes[1]
|
|
2354
|
+
)
|
|
2355
|
+
else:
|
|
2356
|
+
boxplot(
|
|
2357
|
+
df=data[column],
|
|
2358
|
+
linewidth=linewidth,
|
|
2359
|
+
ax=axes[1]
|
|
2360
|
+
)
|
|
2361
|
+
|
|
2362
|
+
fig.suptitle(f"Distribution of {column}", fontsize=14, y=1.02)
|
|
2363
|
+
else:
|
|
2364
|
+
if hue not in data.columns:
|
|
2365
|
+
raise ValueError(f"hue column '{hue}' not found in DataFrame")
|
|
2366
|
+
|
|
2367
|
+
categories = list(pd.Series(data[hue].dropna().unique()).sort_values())
|
|
2368
|
+
n_cat = len(categories) if categories else 1
|
|
2369
|
+
|
|
2370
|
+
fig, axes = get_default_ax(width, height, rows=n_cat, cols=2, dpi=dpi)
|
|
2371
|
+
axes_2d = np.atleast_2d(axes)
|
|
2372
|
+
|
|
2373
|
+
for idx, cat in enumerate(categories):
|
|
2374
|
+
subset = data[data[hue] == cat]
|
|
2375
|
+
left_ax, right_ax = axes_2d[idx, 0], axes_2d[idx, 1]
|
|
2376
|
+
|
|
2377
|
+
kde_confidence_interval(
|
|
2378
|
+
data=subset,
|
|
2379
|
+
xnames=column,
|
|
2380
|
+
clevel=clevel,
|
|
2381
|
+
linewidth=linewidth,
|
|
2382
|
+
ax=left_ax,
|
|
2383
|
+
)
|
|
2384
|
+
left_ax.set_title(f"{hue} = {cat}")
|
|
2385
|
+
|
|
2386
|
+
if kind == "hist":
|
|
2387
|
+
histplot(
|
|
2388
|
+
df=subset,
|
|
2389
|
+
xname=column,
|
|
2390
|
+
linewidth=linewidth,
|
|
2391
|
+
ax=right_ax,
|
|
2392
|
+
)
|
|
2393
|
+
else:
|
|
2394
|
+
boxplot(
|
|
2395
|
+
df=subset[column],
|
|
2396
|
+
linewidth=linewidth,
|
|
2397
|
+
ax=right_ax
|
|
2398
|
+
)
|
|
2399
|
+
|
|
2400
|
+
fig.suptitle(f"Distribution of {column} by {hue}", fontsize=14, y=1.02)
|
|
2401
|
+
|
|
2402
|
+
plt.tight_layout()
|
|
2403
|
+
|
|
2404
|
+
if save_path:
|
|
2405
|
+
plt.savefig(save_path, bbox_inches='tight', dpi=dpi)
|
|
2406
|
+
plt.close()
|
|
2407
|
+
else:
|
|
2408
|
+
plt.show()
|
hossam/hs_prep.py
CHANGED
|
@@ -10,6 +10,7 @@ from itertools import combinations
|
|
|
10
10
|
#
|
|
11
11
|
# ===================================================================
|
|
12
12
|
import pandas as pd
|
|
13
|
+
import jenkspy
|
|
13
14
|
from pandas import DataFrame
|
|
14
15
|
from sklearn.preprocessing import StandardScaler, MinMaxScaler
|
|
15
16
|
from sklearn.impute import SimpleImputer
|
|
@@ -177,7 +178,7 @@ def set_category(data: DataFrame, *args: str) -> DataFrame:
|
|
|
177
178
|
|
|
178
179
|
|
|
179
180
|
# ===================================================================
|
|
180
|
-
#
|
|
181
|
+
# 명목형 변수의 값 종류에 따른 데이터 분리
|
|
181
182
|
# ===================================================================
|
|
182
183
|
def unmelt(
|
|
183
184
|
data: DataFrame, id_vars: str = "class", value_vars: str = "values"
|
|
@@ -185,49 +186,32 @@ def unmelt(
|
|
|
185
186
|
"""두 개의 컬럼으로 구성된 데이터프레임에서 하나는 명목형, 나머지는 연속형일 경우
|
|
186
187
|
명목형 변수의 값에 따라 고유한 변수를 갖는 데이터프레임으로 변환한다.
|
|
187
188
|
|
|
189
|
+
각 그룹의 데이터 길이가 다를 경우 짧은 쪽에 NaN을 채워 동일한 길이로 맞춥니다.
|
|
190
|
+
이는 독립표본 t-검정(ttest_ind) 등의 분석을 위한 데이터 준비에 유용합니다.
|
|
191
|
+
|
|
188
192
|
Args:
|
|
189
193
|
data (DataFrame): 데이터프레임
|
|
190
194
|
id_vars (str, optional): 명목형 변수의 컬럼명. Defaults to 'class'.
|
|
191
195
|
value_vars (str, optional): 연속형 변수의 컬럼명. Defaults to 'values'.
|
|
192
196
|
|
|
193
197
|
Returns:
|
|
194
|
-
DataFrame: 변환된 데이터프레임
|
|
195
|
-
"""
|
|
196
|
-
result = data.groupby(id_vars)[value_vars].apply(list)
|
|
197
|
-
mydict = {}
|
|
198
|
-
|
|
199
|
-
for i in result.index:
|
|
200
|
-
mydict[i] = result[i]
|
|
201
|
-
|
|
202
|
-
return DataFrame(mydict)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
# ===================================================================
|
|
206
|
-
# 결측치를 평균, 중앙값 등의 전략으로 대체한다
|
|
207
|
-
# ===================================================================
|
|
208
|
-
def replace_missing_value(data: DataFrame, strategy: str = "mean") -> DataFrame:
|
|
209
|
-
"""SimpleImputer로 결측치를 대체한다.
|
|
210
|
-
|
|
211
|
-
Args:
|
|
212
|
-
data (DataFrame): 결측치가 포함된 데이터프레임
|
|
213
|
-
strategy (str, optional): 결측치 대체 방식(mean, median, most_frequent, constant). Defaults to "mean".
|
|
214
|
-
|
|
215
|
-
Returns:
|
|
216
|
-
DataFrame: 결측치가 대체된 데이터프레임
|
|
198
|
+
DataFrame: 변환된 데이터프레임 (각 그룹이 개별 컬럼으로 구성)
|
|
217
199
|
|
|
218
200
|
Examples:
|
|
219
|
-
>>>
|
|
220
|
-
|
|
201
|
+
>>> df = pd.DataFrame({
|
|
202
|
+
... 'group': ['A', 'A', 'B', 'B', 'B'],
|
|
203
|
+
... 'value': [1, 2, 3, 4, 5]
|
|
204
|
+
... })
|
|
205
|
+
>>> result = unmelt(df, id_vars='group', value_vars='value')
|
|
206
|
+
>>> # 결과: A 컬럼에는 [1, 2, NaN], B 컬럼에는 [3, 4, 5]
|
|
221
207
|
"""
|
|
208
|
+
# 그룹별로 값들을 리스트로 모음
|
|
209
|
+
grouped = data.groupby(id_vars, observed=True)[value_vars].apply(lambda x: x.tolist())
|
|
210
|
+
series_dict = {}
|
|
211
|
+
for idx, values in grouped.items():
|
|
212
|
+
series_dict[str(idx)] = pd.Series(values)
|
|
222
213
|
|
|
223
|
-
|
|
224
|
-
if strategy not in allowed:
|
|
225
|
-
raise ValueError(f"strategy는 {allowed} 중 하나여야 합니다.")
|
|
226
|
-
|
|
227
|
-
imr = SimpleImputer(missing_values=np.nan, strategy=strategy)
|
|
228
|
-
df_imr = imr.fit_transform(data.values)
|
|
229
|
-
return DataFrame(df_imr, index=data.index, columns=data.columns)
|
|
230
|
-
|
|
214
|
+
return DataFrame(series_dict)
|
|
231
215
|
|
|
232
216
|
# ===================================================================
|
|
233
217
|
# 지정된 변수의 이상치 테이블로 반환한다
|
|
@@ -439,6 +423,304 @@ def labelling(data: DataFrame, *fields: str) -> DataFrame:
|
|
|
439
423
|
return df
|
|
440
424
|
|
|
441
425
|
|
|
426
|
+
# ===================================================================
|
|
427
|
+
# 연속형 변수를 다양한 기준으로 구간화하여 명목형 변수로 추가한다
|
|
428
|
+
# ===================================================================
|
|
429
|
+
def bin_continuous(
|
|
430
|
+
data: DataFrame,
|
|
431
|
+
field: str,
|
|
432
|
+
method: str = "natural_breaks",
|
|
433
|
+
bins: int | list[float] | None = None,
|
|
434
|
+
labels: list[str] | None = None,
|
|
435
|
+
new_col: str | None = None,
|
|
436
|
+
is_log_transformed: bool = False,
|
|
437
|
+
apply_labels: bool = True,
|
|
438
|
+
) -> DataFrame:
|
|
439
|
+
"""연속형 변수를 다양한 알고리즘으로 구간화해 명목형 파생변수를 추가한다.
|
|
440
|
+
|
|
441
|
+
지원 방법:
|
|
442
|
+
- "natural_breaks"(기본): Jenks 자연 구간화. jenkspy 미사용 시 quantile로 대체
|
|
443
|
+
기본 라벨: "X~Y" 형식 (예: "18~30", "30~40")
|
|
444
|
+
- "quantile"/"qcut"/"equal_freq": 분위수 기반 동빈도
|
|
445
|
+
기본 라벨: "X~Y" 형식
|
|
446
|
+
- "equal_width"/"uniform": 동일 간격
|
|
447
|
+
기본 라벨: "X~Y" 형식
|
|
448
|
+
- "std": 평균±표준편차를 경계로 4구간 생성
|
|
449
|
+
라벨: "low", "mid_low", "mid_high", "high"
|
|
450
|
+
- "lifecourse"/"life_stage": 생애주기 5단계
|
|
451
|
+
라벨: "아동", "청소년", "청년", "중년", "노년" (경계: 0, 13, 19, 40, 65)
|
|
452
|
+
- "age_decade": 10대 단위 연령대
|
|
453
|
+
라벨: "아동", "10대", "20대", "30대", "40대", "50대", "60대 이상"
|
|
454
|
+
- "health_band"/"policy_band": 의료비 위험도 기반 연령대
|
|
455
|
+
라벨: "18-29", "30-39", "40-49", "50-64", "65+"
|
|
456
|
+
- 커스텀 구간: bins에 경계 리스트 전달 (예: [0, 30, 50, 100])
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
data (DataFrame): 입력 데이터프레임
|
|
460
|
+
field (str): 구간화할 연속형 변수명
|
|
461
|
+
method (str): 구간화 알고리즘 키워드 (기본값: "natural_breaks")
|
|
462
|
+
bins (int|list[float]|None):
|
|
463
|
+
- int: 생성할 구간 개수 (quantile, equal_width, natural_breaks에서 사용)
|
|
464
|
+
- list: 경계값 리스트 (커스텀 구간화)
|
|
465
|
+
- None: 기본값 사용 (quantile/equal_width는 4~5, natural_breaks는 5)
|
|
466
|
+
labels (list[str]|None): 구간 레이블 목록
|
|
467
|
+
- None: method별 기본 라벨 자동 생성
|
|
468
|
+
- list: 사용자 정의 라벨 (구간 개수와 일치해야 함)
|
|
469
|
+
new_col (str|None): 생성할 컬럼명
|
|
470
|
+
- None: f"{field}_bin" 사용 (예: "age_bin")
|
|
471
|
+
is_log_transformed (bool): 대상 컬럼이 로그 변환되어 있는지 여부
|
|
472
|
+
- True: 지정된 컬럼을 역변환(exp)한 후 구간화
|
|
473
|
+
- False: 원래 값 그대로 구간화 (기본값)
|
|
474
|
+
apply_labels (bool): 구간에 숫자 인덱스를 적용할지 여부
|
|
475
|
+
- True: 숫자 인덱스 사용 (0, 1, 2, 3, ...) (기본값)
|
|
476
|
+
- False: 문자 라벨 적용 (예: "18~30", "아동")
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
DataFrame: 원본에 구간화된 명목형 컬럼이 추가된 데이터프레임
|
|
480
|
+
|
|
481
|
+
Examples:
|
|
482
|
+
동일 간격으로 5개 구간 생성 (숫자 인덱스):
|
|
483
|
+
>>> df = pd.DataFrame({'age': [20, 35, 50, 65]})
|
|
484
|
+
>>> result = bin_continuous(df, 'age', method='equal_width', bins=5)
|
|
485
|
+
>>> print(result['age_bin']) # 0, 1, 2, ... (숫자 인덱스)
|
|
486
|
+
|
|
487
|
+
문자 레이블 사용:
|
|
488
|
+
>>> result = bin_continuous(df, 'age', method='equal_width', bins=5, apply_labels=False)
|
|
489
|
+
>>> print(result['age_bin']) # 20~30, 30~40, ... (문자 레이블)
|
|
490
|
+
|
|
491
|
+
생애주기 기반 구간화:
|
|
492
|
+
>>> result = bin_continuous(df, 'age', method='lifecourse')
|
|
493
|
+
>>> print(result['age_bin']) # 0, 1, 2, 3, 4 (숫자 인덱스)
|
|
494
|
+
|
|
495
|
+
생애주기 문자 레이블:
|
|
496
|
+
>>> result = bin_continuous(df, 'age', method='lifecourse', apply_labels=False)
|
|
497
|
+
>>> print(result['age_bin']) # 아동, 청소년, 청년, 중년, 노년
|
|
498
|
+
|
|
499
|
+
의료비 위험도 기반 연령대 (health_band):
|
|
500
|
+
>>> result = bin_continuous(df, 'age', method='health_band', apply_labels=False)
|
|
501
|
+
>>> print(result['age_bin']) # 18-29, 30-39, 40-49, 50-64, 65+
|
|
502
|
+
|
|
503
|
+
로그 변환된 컬럼 역변환 후 구간화:
|
|
504
|
+
>>> df_log = pd.DataFrame({'charges_log': [np.log(1000), np.log(5000), np.log(50000)]})
|
|
505
|
+
>>> result = bin_continuous(df_log, 'charges_log', method='equal_width', is_log_transformed=True)
|
|
506
|
+
>>> print(result['charges_log_bin']) # 0, 1, 2 (숫자 인덱스)
|
|
507
|
+
"""
|
|
508
|
+
|
|
509
|
+
if field not in data.columns:
|
|
510
|
+
return data
|
|
511
|
+
|
|
512
|
+
df = data.copy()
|
|
513
|
+
series = df[field].copy()
|
|
514
|
+
|
|
515
|
+
# 로그 변환 역변환
|
|
516
|
+
if is_log_transformed:
|
|
517
|
+
series = np.exp(series)
|
|
518
|
+
|
|
519
|
+
new_col = new_col or f"{field}_bin"
|
|
520
|
+
method_key = (method or "").lower()
|
|
521
|
+
|
|
522
|
+
def _cut(edges: list[float], default_labels: list[str] | None = None, right: bool = False, ordered: bool = True):
|
|
523
|
+
nonlocal labels
|
|
524
|
+
use_labels = None
|
|
525
|
+
|
|
526
|
+
# apply_labels=True일 때 숫자 인덱스, False일 때 문자 레이블
|
|
527
|
+
if apply_labels:
|
|
528
|
+
# 숫자 인덱스 생성
|
|
529
|
+
numeric_labels = list(range(len(edges) - 1))
|
|
530
|
+
use_labels = numeric_labels
|
|
531
|
+
else:
|
|
532
|
+
# 문자 레이블 적용
|
|
533
|
+
use_labels = labels if labels is not None else default_labels
|
|
534
|
+
|
|
535
|
+
df[new_col] = pd.cut(
|
|
536
|
+
series,
|
|
537
|
+
bins=edges,
|
|
538
|
+
labels=use_labels,
|
|
539
|
+
right=right,
|
|
540
|
+
include_lowest=True,
|
|
541
|
+
ordered=False, # 레이블이 있으므로 ordered=False 사용
|
|
542
|
+
)
|
|
543
|
+
df[new_col] = df[new_col].astype("category")
|
|
544
|
+
|
|
545
|
+
# 생애주기 구분
|
|
546
|
+
if method_key in {"lifecourse", "life_stage", "lifecycle", "life"}:
|
|
547
|
+
edges = [0, 13, 19, 40, 65, np.inf]
|
|
548
|
+
# 나이 구간을 함께 표기한 라벨 (apply_labels=False에서 사용)
|
|
549
|
+
default_labels = [
|
|
550
|
+
"아동(0~12)",
|
|
551
|
+
"청소년(13~18)",
|
|
552
|
+
"청년(19~39)",
|
|
553
|
+
"중년(40~64)",
|
|
554
|
+
"노년(65+)",
|
|
555
|
+
]
|
|
556
|
+
_cut(edges, default_labels, right=False)
|
|
557
|
+
return df
|
|
558
|
+
|
|
559
|
+
# 연령대(10단위)
|
|
560
|
+
if method_key in {"age_decade", "age10", "decade"}:
|
|
561
|
+
edges = [0, 13, 20, 30, 40, 50, 60, np.inf]
|
|
562
|
+
default_labels = ["아동", "10대", "20대", "30대", "40대", "50대", "60대 이상"]
|
|
563
|
+
_cut(edges, default_labels, right=False)
|
|
564
|
+
return df
|
|
565
|
+
|
|
566
|
+
# 건강/제도 기준 (의료비 위험군 분류 기준)
|
|
567
|
+
if method_key in {"health_band", "policy_band", "health"}:
|
|
568
|
+
# 연령 데이터 최소값(예: 18세)과 레이블을 일치시킴
|
|
569
|
+
edges = [0, 19, 30, 40, 50, 65, np.inf]
|
|
570
|
+
default_labels = ["0~18", "19-29", "30-39", "40-49", "50-64", "65+"]
|
|
571
|
+
_cut(edges, default_labels, right=False)
|
|
572
|
+
return df
|
|
573
|
+
|
|
574
|
+
# 표준편차 기반
|
|
575
|
+
if method_key == "std":
|
|
576
|
+
mu = series.mean()
|
|
577
|
+
sd = series.std(ddof=0)
|
|
578
|
+
edges = [-np.inf, mu - sd, mu, mu + sd, np.inf]
|
|
579
|
+
default_labels = ["low", "mid_low", "mid_high", "high"]
|
|
580
|
+
_cut(edges, default_labels, right=True)
|
|
581
|
+
return df
|
|
582
|
+
|
|
583
|
+
# 동일 간격
|
|
584
|
+
if method_key in {"equal_width", "uniform"}:
|
|
585
|
+
k = bins if isinstance(bins, int) and bins > 0 else 5
|
|
586
|
+
_, edges = pd.cut(series, bins=k, include_lowest=True, retbins=True)
|
|
587
|
+
|
|
588
|
+
# apply_labels=True: 숫자 인덱스 / False: 문자 레이블
|
|
589
|
+
if apply_labels:
|
|
590
|
+
# 숫자 인덱스 사용 (0, 1, 2, ...)
|
|
591
|
+
numeric_labels = list(range(len(edges) - 1))
|
|
592
|
+
df[new_col] = pd.cut(series, bins=edges, labels=numeric_labels, include_lowest=True, ordered=False)
|
|
593
|
+
else:
|
|
594
|
+
# 문자 레이블 적용
|
|
595
|
+
if labels is None:
|
|
596
|
+
auto_labels = []
|
|
597
|
+
for i in range(len(edges) - 1):
|
|
598
|
+
left = f"{edges[i]:.2f}" if edges[i] != -np.inf else "-∞"
|
|
599
|
+
right = f"{edges[i+1]:.2f}" if edges[i+1] != np.inf else "∞"
|
|
600
|
+
# 정수값인 경우 소수점 제거
|
|
601
|
+
try:
|
|
602
|
+
left = str(int(float(left))) if float(left) == int(float(left)) else left
|
|
603
|
+
right = str(int(float(right))) if float(right) == int(float(right)) else right
|
|
604
|
+
except:
|
|
605
|
+
pass
|
|
606
|
+
auto_labels.append(f"{left}~{right}")
|
|
607
|
+
df[new_col] = pd.cut(series, bins=edges, labels=auto_labels, include_lowest=True, ordered=False)
|
|
608
|
+
else:
|
|
609
|
+
df[new_col] = pd.cut(series, bins=edges, labels=labels, include_lowest=True, ordered=False)
|
|
610
|
+
|
|
611
|
+
df[new_col] = df[new_col].astype("category")
|
|
612
|
+
return df
|
|
613
|
+
|
|
614
|
+
# 분위수 기반 동빈도
|
|
615
|
+
if method_key in {"quantile", "qcut", "equal_freq"}:
|
|
616
|
+
k = bins if isinstance(bins, int) and bins > 0 else 4
|
|
617
|
+
# apply_labels=False일 때 기본 레이블을 사분위수 위치(Q1~)로 설정
|
|
618
|
+
default_q_labels = labels if labels is not None else [f"Q{i+1}" for i in range(k)]
|
|
619
|
+
try:
|
|
620
|
+
if apply_labels:
|
|
621
|
+
# 숫자 인덱스 사용
|
|
622
|
+
numeric_labels = list(range(k))
|
|
623
|
+
df[new_col] = pd.qcut(series, q=k, labels=numeric_labels, duplicates="drop")
|
|
624
|
+
else:
|
|
625
|
+
# 사분위수 위치 기반 문자 레이블(Q1, Q2, ...)
|
|
626
|
+
df[new_col] = pd.qcut(series, q=k, labels=default_q_labels, duplicates="drop")
|
|
627
|
+
except ValueError:
|
|
628
|
+
_, edges = pd.cut(series, bins=k, include_lowest=True, retbins=True)
|
|
629
|
+
# apply_labels=True: 숫자 인덱스 / False: 문자 레이블
|
|
630
|
+
n_bins = len(edges) - 1
|
|
631
|
+
if apply_labels:
|
|
632
|
+
numeric_labels = list(range(n_bins))
|
|
633
|
+
df[new_col] = pd.cut(series, bins=edges, labels=numeric_labels, include_lowest=True, ordered=False)
|
|
634
|
+
else:
|
|
635
|
+
if labels is None:
|
|
636
|
+
position_labels = [f"Q{i+1}" for i in range(n_bins)]
|
|
637
|
+
df[new_col] = pd.cut(
|
|
638
|
+
series, bins=edges, labels=position_labels, include_lowest=True, ordered=False
|
|
639
|
+
)
|
|
640
|
+
else:
|
|
641
|
+
df[new_col] = pd.cut(series, bins=edges, labels=labels, include_lowest=True, ordered=False)
|
|
642
|
+
df[new_col] = df[new_col].astype("category")
|
|
643
|
+
return df
|
|
644
|
+
|
|
645
|
+
# 자연 구간화 (Jenks) - 의존성 없으면 분위수로 폴백
|
|
646
|
+
if method_key in {"natural_breaks", "natural", "jenks"}:
|
|
647
|
+
k = bins if isinstance(bins, int) and bins > 1 else 5
|
|
648
|
+
series_nonnull = series.dropna()
|
|
649
|
+
k = min(k, max(2, series_nonnull.nunique()))
|
|
650
|
+
edges = None
|
|
651
|
+
try:
|
|
652
|
+
edges = jenkspy.jenks_breaks(series_nonnull.to_list(), nb_class=k)
|
|
653
|
+
edges[0] = -np.inf
|
|
654
|
+
edges[-1] = np.inf
|
|
655
|
+
except Exception:
|
|
656
|
+
try:
|
|
657
|
+
use_labels = labels if apply_labels else None
|
|
658
|
+
df[new_col] = pd.qcut(series, q=k, labels=use_labels, duplicates="drop")
|
|
659
|
+
df[new_col] = df[new_col].astype("category")
|
|
660
|
+
return df
|
|
661
|
+
except Exception:
|
|
662
|
+
edges = None
|
|
663
|
+
|
|
664
|
+
if edges:
|
|
665
|
+
# apply_labels=True: 숫자 인덱스 / False: 문자 레이블
|
|
666
|
+
if apply_labels:
|
|
667
|
+
# 숫자 인덱스 사용
|
|
668
|
+
numeric_labels = list(range(len(edges) - 1))
|
|
669
|
+
df[new_col] = pd.cut(series, bins=edges, labels=numeric_labels, include_lowest=True, ordered=False)
|
|
670
|
+
df[new_col] = df[new_col].astype("category")
|
|
671
|
+
else:
|
|
672
|
+
if labels is None:
|
|
673
|
+
auto_labels = []
|
|
674
|
+
for i in range(len(edges) - 1):
|
|
675
|
+
left = f"{edges[i]:.2f}" if edges[i] != -np.inf else "-∞"
|
|
676
|
+
right = f"{edges[i+1]:.2f}" if edges[i+1] != np.inf else "∞"
|
|
677
|
+
# 정수값인 경우 소수점 제거
|
|
678
|
+
try:
|
|
679
|
+
left = str(int(float(left))) if float(left) == int(float(left)) else left
|
|
680
|
+
right = str(int(float(right))) if float(right) == int(float(right)) else right
|
|
681
|
+
except:
|
|
682
|
+
pass
|
|
683
|
+
auto_labels.append(f"{left}~{right}")
|
|
684
|
+
_cut(edges, auto_labels, right=True, ordered=False)
|
|
685
|
+
else:
|
|
686
|
+
_cut(edges, labels, right=True, ordered=False)
|
|
687
|
+
else:
|
|
688
|
+
_, cut_edges = pd.cut(series, bins=k, include_lowest=True, retbins=True)
|
|
689
|
+
if apply_labels:
|
|
690
|
+
# 숫자 인덱스 사용
|
|
691
|
+
numeric_labels = list(range(len(cut_edges) - 1))
|
|
692
|
+
df[new_col] = pd.cut(series, bins=cut_edges, labels=numeric_labels, include_lowest=True, ordered=False)
|
|
693
|
+
else:
|
|
694
|
+
if labels is None:
|
|
695
|
+
auto_labels = []
|
|
696
|
+
for i in range(len(cut_edges) - 1):
|
|
697
|
+
left = f"{cut_edges[i]:.2f}" if cut_edges[i] != -np.inf else "-∞"
|
|
698
|
+
right = f"{cut_edges[i+1]:.2f}" if cut_edges[i+1] != np.inf else "∞"
|
|
699
|
+
# 정수값인 경우 소수점 제거
|
|
700
|
+
try:
|
|
701
|
+
left = str(int(float(left))) if float(left) == int(float(left)) else left
|
|
702
|
+
right = str(int(float(right))) if float(right) == int(float(right)) else right
|
|
703
|
+
except:
|
|
704
|
+
pass
|
|
705
|
+
auto_labels.append(f"{left}~{right}")
|
|
706
|
+
df[new_col] = pd.cut(series, bins=cut_edges, labels=auto_labels, include_lowest=True, ordered=False)
|
|
707
|
+
else:
|
|
708
|
+
df[new_col] = pd.cut(series, bins=cut_edges, labels=labels, include_lowest=True, ordered=False)
|
|
709
|
+
df[new_col] = df[new_col].astype("category")
|
|
710
|
+
return df
|
|
711
|
+
|
|
712
|
+
# 커스텀 경계
|
|
713
|
+
if isinstance(bins, list) and len(bins) >= 2:
|
|
714
|
+
edges = sorted(bins)
|
|
715
|
+
_cut(edges, labels, right=False)
|
|
716
|
+
return df
|
|
717
|
+
|
|
718
|
+
# 기본 폴백: 분위수 4구간
|
|
719
|
+
df[new_col] = pd.qcut(series, q=4, labels=labels, duplicates="drop")
|
|
720
|
+
df[new_col] = df[new_col].astype("category")
|
|
721
|
+
return df
|
|
722
|
+
|
|
723
|
+
|
|
442
724
|
# ===================================================================
|
|
443
725
|
# 지정된 변수에 로그 먼저 변환을 적용한다
|
|
444
726
|
# ===================================================================
|
hossam/hs_stats.py
CHANGED
|
@@ -2702,3 +2702,155 @@ def predict(fit, data: DataFrame | Series) -> DataFrame | Series | float:
|
|
|
2702
2702
|
f"모형 학습 시 사용한 특성과 입력 데이터의 특성이 일치하는지 확인하세요.\n"
|
|
2703
2703
|
f"원본 오류: {str(e)}"
|
|
2704
2704
|
)
|
|
2705
|
+
|
|
2706
|
+
|
|
2707
|
+
# ===================================================================
|
|
2708
|
+
# 확장된 기술통계량 (Extended Descriptive Statistics)
|
|
2709
|
+
# ===================================================================
|
|
2710
|
+
def summary(data: DataFrame, *fields: str, columns: list = None):
|
|
2711
|
+
"""데이터프레임의 연속형 변수에 대한 확장된 기술통계량을 반환한다.
|
|
2712
|
+
|
|
2713
|
+
각 연속형(숫자형) 컬럼의 기술통계량(describe)을 구하고, 이에 사분위수 범위(IQR),
|
|
2714
|
+
이상치 경계값(UP, DOWN), 왜도(skew), 이상치 개수 및 비율, 분포 특성, 로그변환 필요성을
|
|
2715
|
+
추가하여 반환한다.
|
|
2716
|
+
|
|
2717
|
+
Args:
|
|
2718
|
+
data (DataFrame): 분석 대상 데이터프레임.
|
|
2719
|
+
*fields (str): 분석할 컬럼명 목록. 지정하지 않으면 모든 숫자형 컬럼을 처리.
|
|
2720
|
+
columns (list, optional): 반환할 통계량 컬럼 목록. None이면 모든 통계량 반환.
|
|
2721
|
+
|
|
2722
|
+
Returns:
|
|
2723
|
+
DataFrame: 각 필드별 확장된 기술통계량을 포함한 데이터프레임.
|
|
2724
|
+
행은 다음과 같은 통계량을 포함:
|
|
2725
|
+
|
|
2726
|
+
- count (float): 비결측치의 수
|
|
2727
|
+
- mean (float): 평균값
|
|
2728
|
+
- std (float): 표준편차
|
|
2729
|
+
- min (float): 최소값
|
|
2730
|
+
- 25% (float): 제1사분위수 (Q1)
|
|
2731
|
+
- 50% (float): 제2사분위수 (중앙값)
|
|
2732
|
+
- 75% (float): 제3사분위수 (Q3)
|
|
2733
|
+
- max (float): 최대값
|
|
2734
|
+
- iqr (float): 사분위 범위 (Q3 - Q1)
|
|
2735
|
+
- up (float): 이상치 상한 경계값 (Q3 + 1.5 * IQR)
|
|
2736
|
+
- down (float): 이상치 하한 경계값 (Q1 - 1.5 * IQR)
|
|
2737
|
+
- skew (float): 왜도
|
|
2738
|
+
- outlier_count (int): 이상치 개수
|
|
2739
|
+
- outlier_rate (float): 이상치 비율(%)
|
|
2740
|
+
- dist (str): 분포 특성 ("극단 우측 꼬리", "거의 대칭" 등)
|
|
2741
|
+
- log_need (str): 로그변환 필요성 ("높음", "중간", "낮음")
|
|
2742
|
+
|
|
2743
|
+
Examples:
|
|
2744
|
+
전체 숫자형 컬럼에 대한 확장된 기술통계:
|
|
2745
|
+
|
|
2746
|
+
>>> from hossam import summary
|
|
2747
|
+
>>> import pandas as pd
|
|
2748
|
+
>>> df = pd.DataFrame({
|
|
2749
|
+
... 'x': [1, 2, 3, 4, 5, 100],
|
|
2750
|
+
... 'y': [10, 20, 30, 40, 50, 60],
|
|
2751
|
+
... 'z': ['a', 'b', 'c', 'd', 'e', 'f']
|
|
2752
|
+
... })
|
|
2753
|
+
>>> result = summary(df)
|
|
2754
|
+
>>> print(result)
|
|
2755
|
+
|
|
2756
|
+
특정 컬럼만 분석:
|
|
2757
|
+
|
|
2758
|
+
>>> result = summary(df, 'x', 'y')
|
|
2759
|
+
>>> print(result)
|
|
2760
|
+
|
|
2761
|
+
Notes:
|
|
2762
|
+
- 숫자형이 아닌 컬럼은 자동으로 제외됩니다.
|
|
2763
|
+
- 결과는 필드(컬럼)가 행으로, 통계량이 열로 구성됩니다.
|
|
2764
|
+
- Tukey의 1.5 * IQR 규칙을 사용하여 이상치를 판정합니다.
|
|
2765
|
+
- 분포 특성은 왜도 값으로 판정합니다.
|
|
2766
|
+
- 로그변환 필요성은 왜도의 절댓값 크기로 판정합니다.
|
|
2767
|
+
"""
|
|
2768
|
+
if not fields:
|
|
2769
|
+
fields = data.select_dtypes(include=['int', 'int32', 'int64', 'float', 'float32', 'float64']).columns
|
|
2770
|
+
|
|
2771
|
+
# 기술통계량 구하기
|
|
2772
|
+
desc = data[list(fields)].describe().T
|
|
2773
|
+
|
|
2774
|
+
# 추가 통계량 계산
|
|
2775
|
+
additional_stats = []
|
|
2776
|
+
for f in fields:
|
|
2777
|
+
# 숫자 타입이 아니라면 건너뜀
|
|
2778
|
+
if data[f].dtype not in [
|
|
2779
|
+
'int',
|
|
2780
|
+
'int32',
|
|
2781
|
+
'int64',
|
|
2782
|
+
'float',
|
|
2783
|
+
'float32',
|
|
2784
|
+
'float64',
|
|
2785
|
+
'int64',
|
|
2786
|
+
'float64',
|
|
2787
|
+
'float32'
|
|
2788
|
+
]:
|
|
2789
|
+
continue
|
|
2790
|
+
|
|
2791
|
+
# 사분위수
|
|
2792
|
+
q1 = data[f].quantile(q=0.25)
|
|
2793
|
+
q3 = data[f].quantile(q=0.75)
|
|
2794
|
+
|
|
2795
|
+
# 이상치 경계 (Tukey's fences)
|
|
2796
|
+
iqr = q3 - q1
|
|
2797
|
+
down = q1 - 1.5 * iqr
|
|
2798
|
+
up = q3 + 1.5 * iqr
|
|
2799
|
+
|
|
2800
|
+
# 왜도
|
|
2801
|
+
skew = data[f].skew()
|
|
2802
|
+
|
|
2803
|
+
# 이상치 개수 및 비율
|
|
2804
|
+
outlier_count = ((data[f] < down) | (data[f] > up)).sum()
|
|
2805
|
+
outlier_rate = (outlier_count / len(data)) * 100
|
|
2806
|
+
|
|
2807
|
+
# 분포 특성 판정 (왜도 기준)
|
|
2808
|
+
abs_skew = abs(skew)
|
|
2809
|
+
if abs_skew < 0.5:
|
|
2810
|
+
dist = "거의 대칭"
|
|
2811
|
+
elif abs_skew < 1.0:
|
|
2812
|
+
if skew > 0:
|
|
2813
|
+
dist = "약한 우측 꼬리"
|
|
2814
|
+
else:
|
|
2815
|
+
dist = "약한 좌측 꼬리"
|
|
2816
|
+
elif abs_skew < 2.0:
|
|
2817
|
+
if skew > 0:
|
|
2818
|
+
dist = "중간 우측 꼬리"
|
|
2819
|
+
else:
|
|
2820
|
+
dist = "중간 좌측 꼬리"
|
|
2821
|
+
else:
|
|
2822
|
+
if skew > 0:
|
|
2823
|
+
dist = "극단 우측 꼬리"
|
|
2824
|
+
else:
|
|
2825
|
+
dist = "극단 좌측 꼬리"
|
|
2826
|
+
|
|
2827
|
+
# 로그변환 필요성 판정
|
|
2828
|
+
if abs_skew < 0.5:
|
|
2829
|
+
log_need = "낮음"
|
|
2830
|
+
elif abs_skew < 1.0:
|
|
2831
|
+
log_need = "중간"
|
|
2832
|
+
else:
|
|
2833
|
+
log_need = "높음"
|
|
2834
|
+
|
|
2835
|
+
additional_stats.append({
|
|
2836
|
+
'field': f,
|
|
2837
|
+
'iqr': iqr,
|
|
2838
|
+
'up': up,
|
|
2839
|
+
'down': down,
|
|
2840
|
+
'outlier_count': outlier_count,
|
|
2841
|
+
'outlier_rate': outlier_rate,
|
|
2842
|
+
'skew': skew,
|
|
2843
|
+
'dist': dist,
|
|
2844
|
+
'log_need': log_need
|
|
2845
|
+
})
|
|
2846
|
+
|
|
2847
|
+
additional_df = DataFrame(additional_stats).set_index('field')
|
|
2848
|
+
|
|
2849
|
+
# 결과 병합
|
|
2850
|
+
result = concat([desc, additional_df], axis=1)
|
|
2851
|
+
|
|
2852
|
+
# columns 파라미터가 지정된 경우 해당 컬럼만 필터링
|
|
2853
|
+
if columns is not None:
|
|
2854
|
+
result = result[columns]
|
|
2855
|
+
|
|
2856
|
+
return result
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
hossam/NotoSansKR-Regular.ttf,sha256=0SCufUQwcVWrWTu75j4Lt_V2bgBJIBXl1p8iAJJYkVY,6185516
|
|
2
|
-
hossam/__init__.py,sha256=
|
|
2
|
+
hossam/__init__.py,sha256=hJTS1Yl85JqG0iBaVkQcYtguP5f-zO0_wT-ZAouQyzI,2613
|
|
3
3
|
hossam/data_loader.py,sha256=UpC_gn-xUWij0s-MO51qrzJNz3b5-RCz1N6esQMZUJM,6320
|
|
4
4
|
hossam/hs_classroom.py,sha256=b2vzxHapxibnJwcRwWvOfLfczjF-G3ZdT9hIUt4z4oU,27407
|
|
5
5
|
hossam/hs_gis.py,sha256=9ER8gXG2Or0DZ1fpbJR84WsNVPcxu788FsNtR6LsEgo,11379
|
|
6
|
-
hossam/hs_plot.py,sha256=
|
|
7
|
-
hossam/hs_prep.py,sha256=
|
|
8
|
-
hossam/hs_stats.py,sha256=
|
|
6
|
+
hossam/hs_plot.py,sha256=7cngzrEeVeBvANyReI9kd3yHZGMOFZjvgBbBGA7rT2E,78467
|
|
7
|
+
hossam/hs_prep.py,sha256=hTHNJvHMVBkV7xthV6igSz0QIKy-bTyVXHzQKblVfQw,36220
|
|
8
|
+
hossam/hs_stats.py,sha256=nhfdZjo4NeyQ3_Pk-xTAN8OLNBDHH8nH9UDtvBNs5AU,112373
|
|
9
9
|
hossam/hs_timeserise.py,sha256=loRofR-m2NMxHaDEWDhZjo6DwayEf4c7qkSoCErfBWY,42165
|
|
10
10
|
hossam/hs_util.py,sha256=E4LnzPlRdWeqICv7TtTL9DT5PogqBhOuTgYiaav565U,7461
|
|
11
11
|
hossam/leekh.png,sha256=1PB5NQ24SDoHA5KMiBBsWpSa3iniFcwFTuGwuOsTHfI,6395
|
|
12
|
-
hossam-0.3.
|
|
13
|
-
hossam-0.3.
|
|
14
|
-
hossam-0.3.
|
|
15
|
-
hossam-0.3.
|
|
16
|
-
hossam-0.3.
|
|
12
|
+
hossam-0.3.16.dist-info/licenses/LICENSE,sha256=nIqzhlcFY_2D6QtFsYjwU7BWkafo-rUJOQpDZ-DsauI,941
|
|
13
|
+
hossam-0.3.16.dist-info/METADATA,sha256=5Yhqjz7S5BTs_OTGaC_l4YzbhQXL8mssrS73hjjCy5s,13116
|
|
14
|
+
hossam-0.3.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
15
|
+
hossam-0.3.16.dist-info/top_level.txt,sha256=_-7bwjhthHplWhywEaHIJX2yL11CQCaLjCNSBlk6wiQ,7
|
|
16
|
+
hossam-0.3.16.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|