hossam 0.4.5__tar.gz → 0.4.6__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hossam
3
- Version: 0.4.5
3
+ Version: 0.4.6
4
4
  Summary: Hossam Data Helper
5
5
  Author-email: Lee Kwang-Ho <leekh4232@gmail.com>
6
6
  License-Expression: MIT
@@ -6,7 +6,8 @@ from . import hs_prep
6
6
  from . import hs_stats
7
7
  from . import hs_timeserise
8
8
  from . import hs_util
9
- from .data_loader import load_data, load_info
9
+ from .hs_util import load_info
10
+ from .hs_util import _load_data_remote as load_data
10
11
 
11
12
  # py-modules
12
13
  import sys
@@ -15,10 +15,10 @@ from .hs_plot import config
15
15
  # 학생들을 관심사와 성적으로 균형잡힌 조로 편성한다
16
16
  # ===================================================================
17
17
  def cluster_students(
18
- df,
18
+ df: DataFrame | str,
19
19
  n_groups: int,
20
- score_cols: list = None,
21
- interest_col: str = None,
20
+ score_cols: list | None = None,
21
+ interest_col: str | None = None,
22
22
  max_iter: int = 200,
23
23
  score_metric: str = 'total'
24
24
  ) -> DataFrame:
@@ -173,7 +173,7 @@ def cluster_students(
173
173
  df_main,
174
174
  actual_n_groups,
175
175
  score_cols,
176
- interest_col,
176
+ interest_col, # type: ignore
177
177
  max_iter
178
178
  )
179
179
  else:
@@ -219,8 +219,8 @@ def cluster_students(
219
219
  def _balance_groups(
220
220
  df: DataFrame,
221
221
  n_groups: int,
222
- score_cols: list,
223
- interest_col: str = None,
222
+ score_cols: list | None = None,
223
+ interest_col: str | None = None,
224
224
  max_iter: int = 200
225
225
  ) -> DataFrame:
226
226
  """조 내 인원과 성적 균형을 조정하는 내부 함수.
@@ -281,7 +281,7 @@ def _balance_groups(
281
281
  count = grade_counts.loc[g, grade]
282
282
  min_g, max_g = grade_bounds[grade]
283
283
 
284
- if count <= max_g:
284
+ if count <= max_g: # type: ignore
285
285
  continue
286
286
 
287
287
  donors = group[group['성적그룹'] == grade]
@@ -291,12 +291,12 @@ def _balance_groups(
291
291
  if og == g:
292
292
  continue
293
293
  other_count = grade_counts.loc[og, grade]
294
- if other_count >= min_g:
294
+ if other_count >= min_g: # type: ignore
295
295
  continue
296
296
  other_group = df[df['조'] == og]
297
297
 
298
298
  og_interest = dominant_interest(other_group)
299
- need_groups.append((min_g - other_count, og, og_interest))
299
+ need_groups.append((min_g - other_count, og, og_interest)) # type: ignore
300
300
 
301
301
  need_groups.sort(reverse=True)
302
302
 
@@ -392,14 +392,14 @@ def _balance_group_sizes_only(
392
392
  # ===================================================================
393
393
  # 조 편성 결과의 인원, 관심사, 점수 분포를 시각화한다
394
394
  # ===================================================================
395
- def report_summary(df: DataFrame, interest_col: str = None, width: int = config.width, height: int = config.height, dpi: int = config.dpi) -> None:
395
+ def report_summary(df: DataFrame, interest_col: str | None = None, width: int = config.width, height: int = config.height, dpi: int = config.dpi) -> None:
396
396
  """조 편성 결과의 요약 통계를 시각화합니다.
397
397
 
398
398
  조별 인원 분포, 관심사 분포, 평균점수 분포를 나타냅니다.
399
399
 
400
400
  Args:
401
401
  df (DataFrame): cluster_students 함수의 반환 결과 데이터프레임.
402
- interest_col (str): 관심사 컬럼명
402
+ interest_col (str | None): 관심사 컬럼명
403
403
  width (int): 그래프 넓이. 기본값: config.width
404
404
  height (int): 그래프 높이. 기본값: config.height
405
405
  dpi (int): 그래프 해상도. 기본값: config.dpi
@@ -540,24 +540,24 @@ def report_summary(df: DataFrame, interest_col: str = None, width: int = config.
540
540
  plot_idx += 1
541
541
 
542
542
  # hs_plot.finalize_plot을 사용하여 마무리
543
- hs_plot.finalize_plot(axes, outparams=True, grid=False)
543
+ hs_plot.finalize_plot(axes, outparams=True, grid=False) # type: ignore
544
544
 
545
545
 
546
546
  # ===================================================================
547
547
  # 조별 점수 분포를 커널 밀도 추정(KDE) 그래프로 시각화한다
548
548
  # ===================================================================
549
- def report_kde(df: DataFrame, metric: str = 'average', width: int = config.width, height: int = config.height, dpi: int = config.dpi) -> None:
549
+ def report_kde(df: DataFrame | str, metric: str = 'average', width: int = config.width, height: int = config.height, dpi: int = config.dpi) -> None:
550
550
  """조별 점수 분포를 KDE(Kernel Density Estimation)로 시각화합니다.
551
551
 
552
552
  각 조의 점수 분포를 커널 밀도 추정으로 표시하고 평균 및 95% 신뢰구간을 나타냅니다.
553
553
 
554
554
  Args:
555
- df: cluster_students 함수의 반환 결과 데이터프레임.
556
- metric: 점수 기준 선택 ('total' 또는 'average').
555
+ df (DataFrame | str): cluster_students 함수의 반환 결과 데이터프레임.
556
+ metric (str): 점수 기준 선택 ('total' 또는 'average').
557
557
  'total'이면 총점, 'average'이면 평균점수. 기본값: 'average'
558
- width: 그래프 넓이. 기본값: config.width
559
- height: 그래프 높이. 기본값: config.height
560
- dpi: 그래프 해상도. 기본값: config.dpi
558
+ width (int): 그래프 넓이. 기본값: config.width
559
+ height (int): 그래프 높이. 기본값: config.height
560
+ dpi (int): 그래프 해상도. 기본값: config.dpi
561
561
 
562
562
  Examples:
563
563
  ```python
@@ -570,17 +570,17 @@ def report_kde(df: DataFrame, metric: str = 'average', width: int = config.width
570
570
  print("데이터프레임이 비어있습니다")
571
571
  return
572
572
 
573
- if '조' not in df.columns:
573
+ if '조' not in df.columns: # type: ignore
574
574
  print("데이터프레임에 '조' 컬럼이 없습니다")
575
575
  return
576
576
 
577
- has_score = '총점' in df.columns
578
- has_avg = '평균점수' in df.columns
577
+ has_score = '총점' in df.columns # type: ignore
578
+ has_avg = '평균점수' in df.columns # type: ignore
579
579
  if not has_score:
580
580
  print("점수 데이터가 없습니다")
581
581
  return
582
582
 
583
- labels = df['조'].unique().tolist()
583
+ labels = df['조'].unique().tolist() # type: ignore
584
584
  def _sort_key(v):
585
585
  try:
586
586
  return (0, int(v))
@@ -596,18 +596,18 @@ def report_kde(df: DataFrame, metric: str = 'average', width: int = config.width
596
596
 
597
597
  plot_idx = 0
598
598
  metric_col = '평균점수' if (metric or '').lower() == 'average' else '총점'
599
- if metric_col not in df.columns:
599
+ if metric_col not in df.columns: # type: ignore
600
600
  print(f"'{metric_col}' 컬럼이 없습니다")
601
601
  return
602
602
 
603
603
  for group in ordered_labels:
604
- group_df = df[df['조'] == group]
605
- group_series = group_df[metric_col].dropna()
604
+ group_df = df[df['조'] == group] # type: ignore
605
+ group_series = group_df[metric_col].dropna() # type: ignore
606
606
  n = group_series.size
607
607
  if n == 0:
608
608
  continue
609
609
 
610
- hs_plot.kde_confidence_interval(data=group_df, xnames=metric_col, ax=axes[plot_idx], callback=lambda ax: ax.set_title(f"{group}조"))
610
+ hs_plot.kde_confidence_interval(data=group_df, xnames=metric_col, ax=axes[plot_idx], callback=lambda ax: ax.set_title(f"{group}조")) # type: ignore
611
611
 
612
612
  plot_idx += 1
613
613
 
@@ -615,7 +615,7 @@ def report_kde(df: DataFrame, metric: str = 'average', width: int = config.width
615
615
  for idx in range(plot_idx, len(axes)):
616
616
  fig.delaxes(axes[idx])
617
617
 
618
- hs_plot.finalize_plot(axes)
618
+ hs_plot.finalize_plot(axes) # type: ignore
619
619
 
620
620
 
621
621
  # ===================================================================
@@ -690,10 +690,10 @@ def group_summary(df: DataFrame, name_col: str = '학생이름') -> DataFrame:
690
690
  # 학생 조 편성부터 시각화까지의 전체 분석 프로세스를 일괄 실행한다
691
691
  # ===================================================================
692
692
  def analyze_classroom(
693
- df,
693
+ df: DataFrame | str,
694
694
  n_groups: int,
695
- score_cols: list = None,
696
- interest_col: str = None,
695
+ score_cols: list | None = None,
696
+ interest_col: str | None = None,
697
697
  max_iter: int = 200,
698
698
  score_metric: str = 'average',
699
699
  name_col: str = '학생이름',
@@ -73,7 +73,7 @@ def get_default_ax(width: int = config.width, height: int = config.height, rows:
73
73
  if is_array and (ws != None and hs != None):
74
74
  fig.subplots_adjust(wspace=ws, hspace=hs)
75
75
 
76
- if title and not is_array:
76
+ if title and is_array:
77
77
  fig.suptitle(title, fontsize=config.font_size * 1.5, fontweight='bold')
78
78
 
79
79
  if flatten == True:
@@ -126,7 +126,7 @@ def create_figure(width: int = config.width, height: int = config.height, rows:
126
126
  # ===================================================================
127
127
  # 그래프의 그리드, 레이아웃을 정리하고 필요 시 저장 또는 표시한다
128
128
  # ===================================================================
129
- def finalize_plot(ax: Axes | np.ndarray, callback: Callable | None = None, outparams: bool = False, save_path: str | None = None, grid: bool = True, title: str | None = None) -> None:
129
+ def finalize_plot(ax: Axes | np.ndarray | list, callback: Callable | None = None, outparams: bool = False, save_path: str | None = None, grid: bool = True, title: str | None = None) -> None:
130
130
  """공통 후처리를 수행한다: 콜백 실행, 레이아웃 정리, 필요 시 표시/종료.
131
131
 
132
132
  Args:
@@ -270,6 +270,10 @@ def boxplot(
270
270
  yname: str | None = None,
271
271
  title: str | None = None,
272
272
  orient: str = "v",
273
+ stat_test: str | None = None,
274
+ stat_pairs: list[tuple] | None = None,
275
+ stat_text_format: str = "star",
276
+ stat_loc: str = "inside",
273
277
  palette: str | None = None,
274
278
  width: int = config.width,
275
279
  height: int = config.height,
@@ -288,6 +292,10 @@ def boxplot(
288
292
  yname (str|None): y축 값 컬럼명.
289
293
  title (str|None): 그래프 제목.
290
294
  orient (str): 'v' 또는 'h' 방향.
295
+ stat_test (str|None): 통계 검정 방법. None이면 검정 안함. xname과 yname이 모두 지정되어야 함.
296
+ stat_pairs (list[tuple]|None): 통계 검정할 그룹 쌍 목록.
297
+ stat_text_format (str): 통계 결과 표시 형식.
298
+ stat_loc (str): 통계 결과 위치.
291
299
  palette (str|None): 팔레트 이름.
292
300
  width (int): 캔버스 가로 픽셀.
293
301
  height (int): 캔버스 세로 픽셀.
@@ -326,12 +334,67 @@ def boxplot(
326
334
 
327
335
  boxplot_kwargs.update(params)
328
336
  sb.boxplot(**boxplot_kwargs, linewidth=linewidth)
337
+
338
+ # 통계 검정 추가
339
+ if stat_test is not None:
340
+ if stat_pairs is None:
341
+ stat_pairs = [df[xname].dropna().unique().tolist()]
342
+
343
+ annotator = Annotator(ax, data=df, x=xname, y=yname, pairs=stat_pairs, orient=orient)
344
+ annotator.configure(test=stat_test, text_format=stat_text_format, loc=stat_loc)
345
+ annotator.apply_and_annotate()
329
346
  else:
330
347
  sb.boxplot(data=df, orient=orient, ax=ax, linewidth=linewidth, **params) # type: ignore
331
348
 
332
349
  finalize_plot(ax, callback, outparams, save_path, True, title) # type: ignore
333
350
 
334
351
 
352
+ # ===================================================================
353
+ # 상자그림에 p-value 주석을 추가한다
354
+ # ===================================================================
355
+ def pvalue1_anotation(
356
+ data: DataFrame,
357
+ target: str,
358
+ hue: str,
359
+ title: str | None = None,
360
+ pairs: list | None = None,
361
+ test: str = "t-test_ind",
362
+ text_format: str = "star",
363
+ loc: str = "outside",
364
+ width: int = config.width,
365
+ height: int = config.height,
366
+ linewidth: float = config.line_width,
367
+ dpi: int = config.dpi,
368
+ save_path: str | None = None,
369
+ callback: Callable | None = None,
370
+ ax: Axes | None = None,
371
+ **params
372
+ ) -> None:
373
+ """
374
+ boxplot의 wrapper 함수로, 상자그림에 p-value 주석을 추가한다.
375
+ """
376
+ boxplot(
377
+ data,
378
+ xname=hue,
379
+ yname=target,
380
+ title=title,
381
+ orient="v",
382
+ stat_test=test,
383
+ stat_pairs=pairs,
384
+ stat_text_format=text_format,
385
+ stat_loc=loc,
386
+ palette=None,
387
+ width=width,
388
+ height=height,
389
+ linewidth=linewidth,
390
+ dpi=dpi,
391
+ save_path=save_path,
392
+ callback=callback,
393
+ ax=ax,
394
+ **params
395
+ )
396
+
397
+
335
398
  # ===================================================================
336
399
  # 커널 밀도 추정(KDE) 그래프를 그린다
337
400
  # ===================================================================
@@ -756,7 +819,12 @@ def regplot(
756
819
  "data": df,
757
820
  "x": xname,
758
821
  "y": yname,
759
- "scatter_kws": {"color": scatter_color} if scatter_color else {},
822
+ "scatter_kws": {
823
+ "s": 20,
824
+ "linewidths": 0.5,
825
+ "edgecolor": "w",
826
+ "color": scatter_color
827
+ },
760
828
  "line_kws": {
761
829
  "color": "red",
762
830
  "linestyle": "--",
@@ -1088,7 +1156,7 @@ def barplot(
1088
1156
 
1089
1157
 
1090
1158
  # ===================================================================
1091
- # 바이올린 플롯을 그린다
1159
+ # boxen 플롯을 그린다
1092
1160
  # ===================================================================
1093
1161
  def boxenplot(
1094
1162
  df: DataFrame,
@@ -1598,88 +1666,6 @@ def kde_confidence_interval(
1598
1666
  finalize_plot(axes[0] if isinstance(axes, list) and len(axes) > 0 else ax, callback, outparams, save_path, True, title) # type: ignore
1599
1667
 
1600
1668
 
1601
- # ===================================================================
1602
- # 상자그림에 p-value 주석을 추가한다
1603
- # ===================================================================
1604
- def pvalue1_anotation(
1605
- data: DataFrame,
1606
- target: str,
1607
- hue: str,
1608
- title: str | None = None,
1609
- pairs: list | None = None,
1610
- test: str = "t-test_ind",
1611
- text_format: str = "star",
1612
- loc: str = "outside",
1613
- width: int = config.width,
1614
- height: int = config.height,
1615
- linewidth: float = config.line_width,
1616
- dpi: int = config.dpi,
1617
- save_path: str | None = None,
1618
- callback: Callable | None = None,
1619
- ax: Axes | None = None,
1620
- **params
1621
- ) -> None:
1622
- """statannotations를 이용해 상자그림에 p-value 주석을 추가한다.
1623
-
1624
- Args:
1625
- data (DataFrame): 시각화할 데이터.
1626
- target (str): 값 컬럼명.
1627
- hue (str): 그룹 컬럼명.
1628
- title (str|None): 그래프 제목.
1629
- pairs (list|None): 비교할 (group_a, group_b) 튜플 목록. None이면 hue 컬럼의 모든 고유값 조합을 자동 생성.
1630
- test (str): 적용할 통계 검정 이름.
1631
- text_format (str): 주석 형식('star' 등).
1632
- loc (str): 주석 위치.
1633
- width (int): 캔버스 가로 픽셀.
1634
- height (int): 캔버스 세로 픽셀.
1635
- linewidth (float): 선 굵기.
1636
- dpi (int): 그림 크기 및 해상도.
1637
- callback (Callable|None): Axes 후처리 콜백.
1638
- ax (Axes|None): 외부에서 전달한 Axes.
1639
- **params: seaborn boxplot 추가 인자.
1640
-
1641
- Returns:
1642
- None
1643
- """
1644
- # pairs가 None이면 hue 컬럼의 고유값으로 모든 조합 생성
1645
- if pairs is None:
1646
- from itertools import combinations
1647
- unique_values = sorted(data[hue].unique())
1648
- pairs = list(combinations(unique_values, 2))
1649
-
1650
- outparams = False
1651
-
1652
- if ax is None:
1653
- fig, ax = get_default_ax(width, height, 1, 1, dpi) # type: ignore
1654
- outparams = True
1655
-
1656
- # params에서 palette 추출 (있으면)
1657
- palette_value = params.pop("palette", None)
1658
-
1659
- # boxplot kwargs 구성
1660
- boxplot_kwargs = {
1661
- "data": data,
1662
- "x": hue,
1663
- "y": target,
1664
- "linewidth": linewidth,
1665
- "ax": ax,
1666
- }
1667
-
1668
- # palette가 있으면 추가 (hue는 x에 이미 할당됨)
1669
- if palette_value is not None:
1670
- boxplot_kwargs["palette"] = palette_value
1671
-
1672
- boxplot_kwargs.update(params)
1673
-
1674
- sb.boxplot(**boxplot_kwargs)
1675
- annotator = Annotator(ax, data=data, x=hue, y=target, pairs=pairs)
1676
- annotator.configure(test=test, text_format=text_format, loc=loc)
1677
- annotator.apply_and_annotate()
1678
-
1679
- sb.despine()
1680
- finalize_plot(ax, callback, outparams, save_path, True, title) # type: ignore
1681
-
1682
-
1683
1669
 
1684
1670
  # ===================================================================
1685
1671
  # 잔차도 (선형회귀의 선형성 검정)
@@ -1741,7 +1727,7 @@ def ols_residplot(
1741
1727
  outparams = True
1742
1728
 
1743
1729
  # 산점도 seaborn으로 그리기
1744
- sb.scatterplot(x=y_pred, y=resid, ax=ax, s=0.5, edgecolor="white", alpha=config.fill_alpha, **params)
1730
+ sb.scatterplot(x=y_pred, y=resid, ax=ax, s=20, edgecolor="white", **params)
1745
1731
 
1746
1732
  # 기준선 (잔차 = 0)
1747
1733
  ax.axhline(0, color="gray", linestyle="--", linewidth=linewidth*0.7) # type: ignore
@@ -1795,13 +1781,13 @@ def ols_residplot(
1795
1781
  for i, c in enumerate(["red", "green", "blue"]):
1796
1782
  ax.text( # type: ignore
1797
1783
  s=f"{i+1} sqrt(MSE) = {mse_r[i]:.2f}% ({mse_r[i] - target[i]:.2f}%)",
1798
- x=xmax + 0.2,
1784
+ x=xmax + 0.05,
1799
1785
  y=(i + 1) * mse_sq,
1800
1786
  color=c,
1801
1787
  )
1802
1788
  ax.text( # type: ignore
1803
1789
  s=f"-{i+1} sqrt(MSE) = {mse_r[i]:.2f}% ({mse_r[i] - target[i]:.2f}%)",
1804
- x=xmax + 0.2,
1790
+ x=xmax + 0.05,
1805
1791
  y=-(i + 1) * mse_sq,
1806
1792
  color=c,
1807
1793
  )
@@ -2146,7 +2132,7 @@ def categorical_target_distribution(
2146
2132
  plot_kwargs.update({"x": yname, "hue": col, "palette": palette, "fill": kde_fill, "common_norm": False, "linewidth": linewidth})
2147
2133
  sb.kdeplot(**plot_kwargs)
2148
2134
  else: # box
2149
- plot_kwargs.update({"x": col, "y": yname, "palette": palette})
2135
+ plot_kwargs.update({"x": col, "y": yname, "hue": col, "palette": palette})
2150
2136
  sb.boxplot(**plot_kwargs, linewidth=linewidth)
2151
2137
 
2152
2138
  ax.set_title(f"{col} vs {yname}")
@@ -2419,8 +2405,7 @@ def radarplot(
2419
2405
  # ===================================================================
2420
2406
  def distribution_plot(
2421
2407
  data: DataFrame,
2422
- column: str,
2423
- title: str | None = None,
2408
+ column: str | list[str],
2424
2409
  clevel: float = 0.95,
2425
2410
  orient: str = "h",
2426
2411
  hue: str | None = None,
@@ -2441,7 +2426,6 @@ def distribution_plot(
2441
2426
  Args:
2442
2427
  data (DataFrame): 시각화할 데이터.
2443
2428
  column (str): 분석할 컬럼명.
2444
- title (str|None): 그래프 제목.
2445
2429
  clevel (float): KDE 신뢰수준 (0~1). 기본값 0.95.
2446
2430
  orient (str): Boxplot 방향 ('v' 또는 'h'). 기본값 'h'.
2447
2431
  hue (str|None): 명목형 컬럼명. 지정하면 각 범주별로 행을 늘려 KDE와 boxplot을 그림.
@@ -2456,76 +2440,82 @@ def distribution_plot(
2456
2440
  Returns:
2457
2441
  None
2458
2442
  """
2459
- if hue is None:
2460
- # 1행 2열 서브플롯 생성
2461
- fig, axes = get_default_ax(width, height, rows=1, cols=2, dpi=dpi)
2462
-
2463
- kde_confidence_interval(
2464
- data=data,
2465
- xnames=column,
2466
- clevel=clevel,
2467
- linewidth=linewidth,
2468
- ax=axes[0],
2469
- )
2470
-
2471
- if kind == "hist":
2472
- histplot(
2473
- df=data,
2474
- xname=column,
2475
- linewidth=linewidth,
2476
- ax=axes[1]
2477
- )
2478
- else:
2479
- boxplot(
2480
- df=data[column], # type: ignore
2481
- linewidth=linewidth,
2482
- ax=axes[1]
2483
- )
2484
-
2485
- fig.suptitle(f"Distribution of {column}", fontsize=14, y=1.02)
2486
- else:
2487
- if hue not in data.columns:
2488
- raise ValueError(f"hue column '{hue}' not found in DataFrame")
2489
-
2490
- categories = list(pd.Series(data[hue].dropna().unique()).sort_values())
2491
- n_cat = len(categories) if categories else 1
2443
+ if isinstance(column, str):
2444
+ column = [column]
2492
2445
 
2493
- fig, axes = get_default_ax(width, height, rows=n_cat, cols=2, dpi=dpi)
2494
- axes_2d = np.atleast_2d(axes)
2446
+ for c in column:
2447
+ title = f"Distribution Plot of {c}"
2495
2448
 
2496
- for idx, cat in enumerate(categories):
2497
- subset = data[data[hue] == cat]
2498
- left_ax, right_ax = axes_2d[idx, 0], axes_2d[idx, 1]
2449
+ if hue is None:
2450
+ # 1행 2열 서브플롯 생성
2451
+ fig, axes = get_default_ax(width, height, rows=1, cols=2, dpi=dpi, title=title)
2499
2452
 
2500
2453
  kde_confidence_interval(
2501
- data=subset,
2502
- xnames=column,
2454
+ data=data,
2455
+ xnames=c,
2503
2456
  clevel=clevel,
2504
2457
  linewidth=linewidth,
2505
- ax=left_ax,
2458
+ ax=axes[0],
2506
2459
  )
2507
- left_ax.set_title(f"{hue} = {cat}")
2508
2460
 
2509
2461
  if kind == "hist":
2510
2462
  histplot(
2511
- df=subset,
2512
- xname=column,
2463
+ df=data,
2464
+ xname=c,
2513
2465
  linewidth=linewidth,
2514
- ax=right_ax,
2466
+ ax=axes[1]
2515
2467
  )
2516
2468
  else:
2517
2469
  boxplot(
2518
- df=subset[column], # type: ignore
2470
+ df=data[column], # type: ignore
2519
2471
  linewidth=linewidth,
2520
- ax=right_ax
2472
+ ax=axes[1]
2521
2473
  )
2522
2474
 
2523
- fig.suptitle(f"Distribution of {column} by {hue}", fontsize=14, y=1.02)
2475
+ fig.suptitle(title, fontsize=14, y=1.02)
2476
+ else:
2477
+ if hue not in data.columns:
2478
+ raise ValueError(f"hue column '{hue}' not found in DataFrame")
2524
2479
 
2525
- plt.tight_layout()
2480
+ categories = list(pd.Series(data[hue].dropna().unique()).sort_values())
2481
+ n_cat = len(categories) if categories else 1
2526
2482
 
2527
- if save_path:
2528
- plt.savefig(save_path, bbox_inches='tight', dpi=dpi)
2529
- plt.close()
2530
- else:
2531
- plt.show()
2483
+ fig, axes = get_default_ax(width, height, rows=n_cat, cols=2, dpi=dpi, title=title)
2484
+ axes_2d = np.atleast_2d(axes)
2485
+
2486
+ for idx, cat in enumerate(categories):
2487
+ subset = data[data[hue] == cat]
2488
+ left_ax, right_ax = axes_2d[idx, 0], axes_2d[idx, 1]
2489
+
2490
+ kde_confidence_interval(
2491
+ data=subset,
2492
+ xnames=c,
2493
+ clevel=clevel,
2494
+ linewidth=linewidth,
2495
+ ax=left_ax,
2496
+ )
2497
+ left_ax.set_title(f"{hue} = {cat}")
2498
+
2499
+ if kind == "hist":
2500
+ histplot(
2501
+ df=subset,
2502
+ xname=c,
2503
+ linewidth=linewidth,
2504
+ ax=right_ax,
2505
+ )
2506
+ else:
2507
+ boxplot(
2508
+ df=subset[c], # type: ignore
2509
+ linewidth=linewidth,
2510
+ ax=right_ax
2511
+ )
2512
+
2513
+ fig.suptitle(f"{title} by {hue}", fontsize=14, y=1.02)
2514
+
2515
+ plt.tight_layout()
2516
+
2517
+ if save_path:
2518
+ plt.savefig(save_path, bbox_inches='tight', dpi=dpi)
2519
+ plt.close()
2520
+ else:
2521
+ plt.show()
@@ -764,7 +764,7 @@ def bin_continuous(
764
764
  # ===================================================================
765
765
  # 지정된 변수에 로그 먼저 변환을 적용한다
766
766
  # ===================================================================
767
- def log_transform(data: DataFrame, *fields: str) -> DataFrame:
767
+ def log_transform(data: DataFrame, *fields: str, columns: list | None = None) -> DataFrame:
768
768
  """수치형 변수에 대해 로그 변환을 수행한다.
769
769
 
770
770
  자연로그(ln)를 사용하여 변환하며, 0 또는 음수 값이 있을 경우
@@ -773,6 +773,7 @@ def log_transform(data: DataFrame, *fields: str) -> DataFrame:
773
773
  Args:
774
774
  data (DataFrame): 변환할 데이터프레임.
775
775
  *fields (str): 변환할 컬럼명 목록. 지정하지 않으면 모든 수치형 컬럼을 처리.
776
+ columns (list, optional): 변환할 컬럼명 목록. fields와 중복 사용 불가.
776
777
 
777
778
  Returns:
778
779
  DataFrame: 로그 변환된 데이터프레임.
@@ -799,6 +800,11 @@ def log_transform(data: DataFrame, *fields: str) -> DataFrame:
799
800
  """
800
801
  df = data.copy()
801
802
 
803
+ if columns is not None:
804
+ if fields:
805
+ raise ValueError("fields와 columns 인자는 중복 사용할 수 없습니다.")
806
+ fields = columns # type: ignore
807
+
802
808
  # 대상 컬럼 결정
803
809
  if not fields:
804
810
  # 모든 수치형 컬럼 선택