hossam 0.3.18__py3-none-any.whl → 0.3.20__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 +3 -3
- hossam/hs_classroom.py +108 -135
- {hossam-0.3.18.dist-info → hossam-0.3.20.dist-info}/METADATA +2 -2
- {hossam-0.3.18.dist-info → hossam-0.3.20.dist-info}/RECORD +8 -8
- {hossam-0.3.18.dist-info → hossam-0.3.20.dist-info}/WHEEL +0 -0
- {hossam-0.3.18.dist-info → hossam-0.3.20.dist-info}/entry_points.txt +0 -0
- {hossam-0.3.18.dist-info → hossam-0.3.20.dist-info}/licenses/LICENSE +0 -0
- {hossam-0.3.18.dist-info → hossam-0.3.20.dist-info}/top_level.txt +0 -0
hossam/__init__.py
CHANGED
|
@@ -17,10 +17,10 @@ except Exception:
|
|
|
17
17
|
hs_fig = SimpleNamespace(
|
|
18
18
|
dpi=200,
|
|
19
19
|
width=800,
|
|
20
|
-
height=
|
|
20
|
+
height=450,
|
|
21
21
|
font_size=9.5,
|
|
22
|
-
font_weight="
|
|
23
|
-
frame_width=0.
|
|
22
|
+
font_weight="normal",
|
|
23
|
+
frame_width=0.7,
|
|
24
24
|
line_width=1.5,
|
|
25
25
|
grid_alpha=0.3,
|
|
26
26
|
grid_width=0.5,
|
hossam/hs_classroom.py
CHANGED
|
@@ -7,7 +7,9 @@ from pandas import DataFrame, qcut, concat, to_numeric
|
|
|
7
7
|
from kmodes.kmodes import KModes
|
|
8
8
|
from matplotlib import pyplot as plt
|
|
9
9
|
import seaborn as sns
|
|
10
|
-
from
|
|
10
|
+
from .hs_util import load_data, pretty_table
|
|
11
|
+
from . import hs_fig
|
|
12
|
+
from . import hs_plot
|
|
11
13
|
|
|
12
14
|
# ===================================================================
|
|
13
15
|
# 학생들을 관심사와 성적으로 균형잡힌 조로 편성한다
|
|
@@ -74,10 +76,6 @@ def cluster_students(
|
|
|
74
76
|
if interest_col is not None and not isinstance(interest_col, str):
|
|
75
77
|
raise ValueError("interest_col은 문자열이어야 합니다")
|
|
76
78
|
|
|
77
|
-
# 필수 컬럼 확인
|
|
78
|
-
if '학생번호' not in df.columns:
|
|
79
|
-
raise ValueError("데이터프레임에 '학생번호' 컬럼이 필요합니다")
|
|
80
|
-
|
|
81
79
|
# 선택적 컬럼 확인
|
|
82
80
|
if score_cols is not None:
|
|
83
81
|
missing_cols = [col for col in score_cols if col not in df.columns]
|
|
@@ -91,6 +89,11 @@ def cluster_students(
|
|
|
91
89
|
|
|
92
90
|
# ===== 1단계: 점수 기반 처리 =====
|
|
93
91
|
if score_cols is not None:
|
|
92
|
+
# 결측치는 0점으로 대체
|
|
93
|
+
for s in score_cols:
|
|
94
|
+
df[s] = df[s].fillna(0)
|
|
95
|
+
print(df)
|
|
96
|
+
|
|
94
97
|
# 총점/평균점수 계산
|
|
95
98
|
df['총점'] = df[score_cols].sum(axis=1)
|
|
96
99
|
df['평균점수'] = df[score_cols].mean(axis=1)
|
|
@@ -99,12 +102,22 @@ def cluster_students(
|
|
|
99
102
|
metric_col = '총점' if (score_metric or '').lower() != 'average' else '평균점수'
|
|
100
103
|
|
|
101
104
|
# 성적사분위 분류 (선택한 기준 사용)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
quantiles = [0, 0.25, 0.50, 0.75, 1.0]
|
|
106
|
+
n_bins = len(quantiles) - 1
|
|
107
|
+
labels = [f"Q{i+1}" for i in range(n_bins)]
|
|
108
|
+
try:
|
|
109
|
+
df['성적사분위'] = qcut(
|
|
110
|
+
df[metric_col],
|
|
111
|
+
q=quantiles,
|
|
112
|
+
labels=labels,
|
|
113
|
+
duplicates='drop' # 중복된 값 처리
|
|
114
|
+
)
|
|
115
|
+
except ValueError:
|
|
116
|
+
# 구간이 줄어든 경우, bins 개수에 맞게 labels 재생성
|
|
117
|
+
import pandas as pd
|
|
118
|
+
bins = pd.qcut(df[metric_col], q=quantiles, duplicates='drop').cat.categories
|
|
119
|
+
labels = [f"Q{i+1}" for i in range(len(bins))]
|
|
120
|
+
df['성적사분위'] = pd.qcut(df[metric_col], q=quantiles, labels=labels, duplicates='drop')
|
|
108
121
|
|
|
109
122
|
# 성적그룹 매핑
|
|
110
123
|
df['성적그룹'] = df['성적사분위'].map({
|
|
@@ -376,15 +389,16 @@ def _balance_group_sizes_only(
|
|
|
376
389
|
# ===================================================================
|
|
377
390
|
# 조 편성 결과의 인원, 관심사, 점수 분포를 시각화한다
|
|
378
391
|
# ===================================================================
|
|
379
|
-
def report_summary(df: DataFrame,
|
|
392
|
+
def report_summary(df: DataFrame, width: int = hs_fig.width, height: int = hs_fig.height, dpi: int = hs_fig.dpi) -> None:
|
|
380
393
|
"""조 편성 결과의 요약 통계를 시각화합니다.
|
|
381
394
|
|
|
382
395
|
조별 인원 분포, 관심사 분포, 평균점수 분포를 나타냅니다.
|
|
383
396
|
|
|
384
397
|
Args:
|
|
385
398
|
df: cluster_students 함수의 반환 결과 데이터프레임.
|
|
386
|
-
|
|
387
|
-
|
|
399
|
+
width: 그래프 넓이. 기본값: hs_fig.width
|
|
400
|
+
height: 그래프 높이. 기본값: hs_fig.height
|
|
401
|
+
dpi: 그래프 해상도. 기본값: hs_fig.dpi
|
|
388
402
|
|
|
389
403
|
Examples:
|
|
390
404
|
>>> from hossam.classroom import cluster_students, report_summary
|
|
@@ -392,9 +406,6 @@ def report_summary(df: DataFrame, figsize: tuple = (20, 4.2), dpi: int = None) -
|
|
|
392
406
|
>>> report_summary(df_result)
|
|
393
407
|
"""
|
|
394
408
|
|
|
395
|
-
if dpi is None:
|
|
396
|
-
dpi = my_dpi
|
|
397
|
-
|
|
398
409
|
if df is None or len(df) == 0:
|
|
399
410
|
print("데이터프레임이 비어있습니다")
|
|
400
411
|
return
|
|
@@ -427,11 +438,15 @@ def report_summary(df: DataFrame, figsize: tuple = (20, 4.2), dpi: int = None) -
|
|
|
427
438
|
if has_score and has_avg:
|
|
428
439
|
n_plots += 1
|
|
429
440
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
441
|
+
# hs_plot.get_default_ax를 사용하여 Figure와 Axes 생성
|
|
442
|
+
fig, axes = hs_plot.get_default_ax(
|
|
443
|
+
width=width,
|
|
444
|
+
height=height,
|
|
445
|
+
rows=n_plots,
|
|
446
|
+
cols=1,
|
|
447
|
+
dpi=dpi,
|
|
448
|
+
flatten=True
|
|
449
|
+
)
|
|
435
450
|
|
|
436
451
|
plot_idx = 0
|
|
437
452
|
|
|
@@ -439,7 +454,16 @@ def report_summary(df: DataFrame, figsize: tuple = (20, 4.2), dpi: int = None) -
|
|
|
439
454
|
group_sizes = df['조'].value_counts()
|
|
440
455
|
group_sizes = group_sizes.reindex(ordered_labels, fill_value=0)
|
|
441
456
|
plot_df = DataFrame({'조': group_sizes.index, '인원': group_sizes.values})
|
|
442
|
-
|
|
457
|
+
|
|
458
|
+
# hs_plot.barplot 사용
|
|
459
|
+
hs_plot.barplot(
|
|
460
|
+
df=plot_df,
|
|
461
|
+
xname='조',
|
|
462
|
+
yname='인원',
|
|
463
|
+
palette='Set2',
|
|
464
|
+
ax=axes[plot_idx]
|
|
465
|
+
)
|
|
466
|
+
|
|
443
467
|
axes[plot_idx].set_title("조별 인원 분포", fontsize=12, fontweight='bold')
|
|
444
468
|
axes[plot_idx].set_xlabel("조")
|
|
445
469
|
axes[plot_idx].set_ylabel("인원")
|
|
@@ -449,59 +473,74 @@ def report_summary(df: DataFrame, figsize: tuple = (20, 4.2), dpi: int = None) -
|
|
|
449
473
|
|
|
450
474
|
# ===== 2. 조별 관심사 분포 =====
|
|
451
475
|
if has_interest:
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
)
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
476
|
+
# hs_plot.stackplot 사용을 위한 데이터 준비
|
|
477
|
+
interest_df = df[['조', '관심사']].copy()
|
|
478
|
+
interest_df['조'] = interest_df['조'].astype(str)
|
|
479
|
+
|
|
480
|
+
def custom_callback(ax):
|
|
481
|
+
ax.set_title("조별 관심사 분포 (%)", fontsize=12, fontweight='bold')
|
|
482
|
+
ax.set_xlabel("조")
|
|
483
|
+
ax.set_ylabel("비율 (%)")
|
|
484
|
+
# x축: 고정된 위치와 라벨 지정
|
|
485
|
+
xticks = ax.get_xticks()
|
|
486
|
+
xticklabels = [tick.get_text() for tick in ax.get_xticklabels()]
|
|
487
|
+
if len(xticklabels) == len(xticks):
|
|
488
|
+
ax.set_xticks(xticks)
|
|
489
|
+
ax.set_xticklabels(xticklabels, rotation=0)
|
|
490
|
+
# y축: 고정된 위치와 라벨 지정
|
|
491
|
+
yticks = ax.get_yticks()
|
|
492
|
+
ax.set_yticks(yticks)
|
|
493
|
+
ax.set_yticklabels([f'{int(y*100)}%' for y in yticks])
|
|
494
|
+
ax.set_ylim(0, 1)
|
|
495
|
+
# legend: 아티스트가 있을 때만
|
|
496
|
+
handles, labels = ax.get_legend_handles_labels()
|
|
497
|
+
if handles and any(l for l in labels if l):
|
|
498
|
+
ax.legend(title='관심사', bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=9)
|
|
499
|
+
|
|
500
|
+
hs_plot.stackplot(
|
|
501
|
+
df=interest_df,
|
|
502
|
+
xname='조',
|
|
503
|
+
hue='관심사',
|
|
504
|
+
palette='Set3',
|
|
466
505
|
ax=axes[plot_idx],
|
|
467
|
-
|
|
468
|
-
width=0.8
|
|
506
|
+
callback=custom_callback
|
|
469
507
|
)
|
|
470
|
-
|
|
471
|
-
axes[plot_idx].set_xlabel("조")
|
|
472
|
-
axes[plot_idx].set_ylabel("비율 (%)")
|
|
473
|
-
axes[plot_idx].set_xticklabels(axes[plot_idx].get_xticklabels(), rotation=0)
|
|
474
|
-
axes[plot_idx].legend(title='관심사', bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=9)
|
|
475
|
-
axes[plot_idx].set_ylim(0, 100)
|
|
508
|
+
|
|
476
509
|
plot_idx += 1
|
|
477
510
|
|
|
478
511
|
# ===== 3. 조별 평균점수 분포 =====
|
|
479
512
|
if has_score and has_avg:
|
|
480
513
|
avg_by_group = df.groupby('조')['평균점수'].agg(['mean', 'std']).reset_index()
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
514
|
+
avg_by_group['조_str'] = avg_by_group['조'].astype(str)
|
|
515
|
+
|
|
516
|
+
# hs_plot.barplot 사용 (errorbar 지원)
|
|
517
|
+
hs_plot.barplot(
|
|
518
|
+
df=avg_by_group,
|
|
519
|
+
xname='조_str',
|
|
520
|
+
yname='mean',
|
|
521
|
+
palette='coolwarm',
|
|
522
|
+
ax=axes[plot_idx],
|
|
523
|
+
errorbar='sd',
|
|
524
|
+
err_kws={'linewidth': 2}
|
|
485
525
|
)
|
|
526
|
+
|
|
486
527
|
axes[plot_idx].set_title("조별 평균점수 분포 (±1 std)", fontsize=12, fontweight='bold')
|
|
487
528
|
axes[plot_idx].set_xlabel("조")
|
|
488
529
|
axes[plot_idx].set_ylabel("평균점수")
|
|
489
|
-
|
|
490
|
-
# ylim 고정: 0~100
|
|
491
530
|
axes[plot_idx].set_ylim(0, 100)
|
|
492
531
|
|
|
493
532
|
for i, row in avg_by_group.iterrows():
|
|
494
533
|
axes[plot_idx].text(i, row['mean'] + 2, f"{row['mean']:.1f}", ha='center', fontsize=10)
|
|
495
534
|
plot_idx += 1
|
|
496
535
|
|
|
497
|
-
|
|
498
|
-
|
|
536
|
+
# hs_plot.finalize_plot을 사용하여 마무리
|
|
537
|
+
hs_plot.finalize_plot(axes, outparams=True, grid=False)
|
|
499
538
|
|
|
500
539
|
|
|
501
540
|
# ===================================================================
|
|
502
541
|
# 조별 점수 분포를 커널 밀도 추정(KDE) 그래프로 시각화한다
|
|
503
542
|
# ===================================================================
|
|
504
|
-
def report_kde(df: DataFrame, metric: str = 'average',
|
|
543
|
+
def report_kde(df: DataFrame, metric: str = 'average', width: int = hs_fig.width, height: int = hs_fig.height, dpi: int = hs_fig.dpi) -> None:
|
|
505
544
|
"""조별 점수 분포를 KDE(Kernel Density Estimation)로 시각화합니다.
|
|
506
545
|
|
|
507
546
|
각 조의 점수 분포를 커널 밀도 추정으로 표시하고 평균 및 95% 신뢰구간을 나타냅니다.
|
|
@@ -510,18 +549,15 @@ def report_kde(df: DataFrame, metric: str = 'average', figsize: tuple = (20, 8),
|
|
|
510
549
|
df: cluster_students 함수의 반환 결과 데이터프레임.
|
|
511
550
|
metric: 점수 기준 선택 ('total' 또는 'average').
|
|
512
551
|
'total'이면 총점, 'average'이면 평균점수. 기본값: 'average'
|
|
513
|
-
|
|
514
|
-
|
|
552
|
+
width: 그래프 넓이. 기본값: hs_fig.width
|
|
553
|
+
height: 그래프 높이. 기본값: hs_fig.height
|
|
554
|
+
dpi: 그래프 해상도. 기본값: hs_fig.dpi
|
|
515
555
|
|
|
516
556
|
Examples:
|
|
517
557
|
>>> from hossam.classroom import cluster_students, report_kde
|
|
518
558
|
>>> df_result = cluster_students(df, n_groups=5, score_cols=['국어', '영어', '수학'])
|
|
519
559
|
>>> report_kde(df_result, metric='average')
|
|
520
560
|
"""
|
|
521
|
-
|
|
522
|
-
if dpi is None:
|
|
523
|
-
dpi = my_dpi
|
|
524
|
-
|
|
525
561
|
if df is None or len(df) == 0:
|
|
526
562
|
print("데이터프레임이 비어있습니다")
|
|
527
563
|
return
|
|
@@ -530,15 +566,12 @@ def report_kde(df: DataFrame, metric: str = 'average', figsize: tuple = (20, 8),
|
|
|
530
566
|
print("데이터프레임에 '조' 컬럼이 없습니다")
|
|
531
567
|
return
|
|
532
568
|
|
|
533
|
-
# 필요한 컬럼 확인
|
|
534
569
|
has_score = '총점' in df.columns
|
|
535
570
|
has_avg = '평균점수' in df.columns
|
|
536
|
-
|
|
537
571
|
if not has_score:
|
|
538
572
|
print("점수 데이터가 없습니다")
|
|
539
573
|
return
|
|
540
574
|
|
|
541
|
-
# 혼합 타입 안전 정렬 라벨 준비
|
|
542
575
|
labels = df['조'].unique().tolist()
|
|
543
576
|
def _sort_key(v):
|
|
544
577
|
try:
|
|
@@ -546,61 +579,27 @@ def report_kde(df: DataFrame, metric: str = 'average', figsize: tuple = (20, 8),
|
|
|
546
579
|
except (ValueError, TypeError):
|
|
547
580
|
return (1, str(v))
|
|
548
581
|
ordered_labels = sorted(labels, key=_sort_key)
|
|
549
|
-
|
|
550
582
|
n_groups = len(ordered_labels)
|
|
551
583
|
|
|
552
|
-
# 레이아웃 결정 (
|
|
553
|
-
cols =
|
|
584
|
+
# 레이아웃 결정 (2열 기준)
|
|
585
|
+
cols = 2
|
|
554
586
|
rows = (n_groups + cols - 1) // cols
|
|
555
|
-
|
|
556
|
-
fig, axes = plt.subplots(rows, cols, figsize=figsize, dpi=dpi)
|
|
557
|
-
|
|
558
|
-
# axes를 1D 배열로 평탄화
|
|
559
|
-
if rows == 1 and cols == 1:
|
|
560
|
-
axes = [axes]
|
|
561
|
-
elif rows == 1 or cols == 1:
|
|
562
|
-
axes = axes.flatten()
|
|
563
|
-
else:
|
|
564
|
-
axes = axes.flatten()
|
|
587
|
+
fig, axes = hs_plot.get_default_ax(width=width, height=height, rows=rows, cols=cols, dpi=dpi, flatten=True)
|
|
565
588
|
|
|
566
589
|
plot_idx = 0
|
|
567
|
-
|
|
568
|
-
# 메트릭 컬럼 결정
|
|
569
590
|
metric_col = '평균점수' if (metric or '').lower() == 'average' else '총점'
|
|
570
591
|
if metric_col not in df.columns:
|
|
571
592
|
print(f"'{metric_col}' 컬럼이 없습니다")
|
|
572
593
|
return
|
|
573
594
|
|
|
574
|
-
# ===== 각 조별 KDE =====
|
|
575
595
|
for group in ordered_labels:
|
|
576
|
-
|
|
596
|
+
group_df = df[df['조'] == group]
|
|
597
|
+
group_series = group_df[metric_col].dropna()
|
|
577
598
|
n = group_series.size
|
|
578
599
|
if n == 0:
|
|
579
600
|
continue
|
|
580
601
|
|
|
581
|
-
|
|
582
|
-
sns.kdeplot(group_series, ax=ax_kde, fill=True)
|
|
583
|
-
|
|
584
|
-
# 평균선 및 95% 신뢰구간(평균에 대한) 표시
|
|
585
|
-
mean_val = float(group_series.mean())
|
|
586
|
-
std_val = float(group_series.std(ddof=1)) if n > 1 else float('nan')
|
|
587
|
-
se = (std_val / (n ** 0.5)) if (n > 1 and std_val == std_val) else None
|
|
588
|
-
if se is not None:
|
|
589
|
-
ci_low = mean_val - 1.96 * se
|
|
590
|
-
ci_high = mean_val + 1.96 * se
|
|
591
|
-
ax_kde.axvspan(ci_low, ci_high, color='red', alpha=0.12, label='95% CI', zorder=9)
|
|
592
|
-
# 평균 세로선 (최상위로)
|
|
593
|
-
ax_kde.axvline(mean_val, color='red', linestyle='--', linewidth=1.5, label='Mean', zorder=10)
|
|
594
|
-
|
|
595
|
-
# 제목/레이블
|
|
596
|
-
ax_kde.set_title(f"{group}조 {metric_col} KDE", fontsize=12, fontweight='bold')
|
|
597
|
-
ax_kde.set_xlabel(metric_col)
|
|
598
|
-
ax_kde.set_ylabel("밀도")
|
|
599
|
-
|
|
600
|
-
# 범례 표시 (Mean/CI가 있으면 노출)
|
|
601
|
-
handles, labels_ = ax_kde.get_legend_handles_labels()
|
|
602
|
-
if handles:
|
|
603
|
-
ax_kde.legend(fontsize=9)
|
|
602
|
+
hs_plot.kde_confidence_interval(data=group_df, xnames=metric_col, ax=axes[plot_idx], callback=lambda ax: ax.set_title(f"{group}조"))
|
|
604
603
|
|
|
605
604
|
plot_idx += 1
|
|
606
605
|
|
|
@@ -608,20 +607,19 @@ def report_kde(df: DataFrame, metric: str = 'average', figsize: tuple = (20, 8),
|
|
|
608
607
|
for idx in range(plot_idx, len(axes)):
|
|
609
608
|
fig.delaxes(axes[idx])
|
|
610
609
|
|
|
611
|
-
|
|
612
|
-
plt.show()
|
|
610
|
+
hs_plot.finalize_plot(axes)
|
|
613
611
|
|
|
614
612
|
|
|
615
613
|
# ===================================================================
|
|
616
614
|
# 조별로 학생 목록과 평균 점수를 요약하여 데이터프레임으로 반환한다
|
|
617
615
|
# ===================================================================
|
|
618
|
-
def group_summary(df: DataFrame, name_col: str = '
|
|
616
|
+
def group_summary(df: DataFrame, name_col: str = '학생이름') -> DataFrame:
|
|
619
617
|
"""조별로 학생 목록과 평균 점수를 요약합니다.
|
|
620
618
|
|
|
621
619
|
Args:
|
|
622
620
|
df: cluster_students 함수의 반환 결과 데이터프레임.
|
|
623
621
|
'조' 컬럼이 필수로 포함되어야 함.
|
|
624
|
-
name_col: 학생 이름이 들어있는 컬럼명. 기본값: '
|
|
622
|
+
name_col: 학생 이름이 들어있는 컬럼명. 기본값: '학생이름'
|
|
625
623
|
|
|
626
624
|
Returns:
|
|
627
625
|
조별 요약 정보가 담긴 데이터프레임.
|
|
@@ -688,7 +686,7 @@ def analyze_classroom(
|
|
|
688
686
|
interest_col: str = None,
|
|
689
687
|
max_iter: int = 200,
|
|
690
688
|
score_metric: str = 'average',
|
|
691
|
-
name_col: str = '
|
|
689
|
+
name_col: str = '학생이름',
|
|
692
690
|
show_summary: bool = True,
|
|
693
691
|
show_kde: bool = True
|
|
694
692
|
) -> DataFrame:
|
|
@@ -707,7 +705,7 @@ def analyze_classroom(
|
|
|
707
705
|
interest_col: 관심사 정보가 있는 컬럼명. 기본값: None
|
|
708
706
|
max_iter: 균형 조정 최대 반복 횟수. 기본값: 200
|
|
709
707
|
score_metric: 점수 기준 선택 ('total' 또는 'average'). 기본값: 'average'
|
|
710
|
-
name_col: 학생 이름 컬럼명. 기본값: '
|
|
708
|
+
name_col: 학생 이름 컬럼명. 기본값: '학생이름'
|
|
711
709
|
show_summary: 요약 시각화 표시 여부. 기본값: True
|
|
712
710
|
show_kde: KDE 시각화 표시 여부. 기본값: True
|
|
713
711
|
|
|
@@ -726,10 +724,6 @@ def analyze_classroom(
|
|
|
726
724
|
>>> print(summary)
|
|
727
725
|
"""
|
|
728
726
|
|
|
729
|
-
print("=" * 60)
|
|
730
|
-
print("1. 학생 조 편성 중...")
|
|
731
|
-
print("=" * 60)
|
|
732
|
-
|
|
733
727
|
# 1. 조 편성
|
|
734
728
|
df_result = cluster_students(
|
|
735
729
|
df=df,
|
|
@@ -742,34 +736,13 @@ def analyze_classroom(
|
|
|
742
736
|
|
|
743
737
|
print(f"\n✓ 조 편성 완료: {len(df_result)}명의 학생을 {n_groups}개 조로 배정\n")
|
|
744
738
|
|
|
745
|
-
print("=" * 60)
|
|
746
|
-
print("2. 조별 요약 생성 중...")
|
|
747
|
-
print("=" * 60)
|
|
748
|
-
|
|
749
|
-
# 2. 조별 요약
|
|
750
|
-
summary = group_summary(df_result, name_col=name_col)
|
|
751
|
-
print("\n✓ 조별 요약:")
|
|
752
|
-
pretty_table(summary, tablefmt="pretty")
|
|
753
|
-
print()
|
|
754
|
-
|
|
755
739
|
# 3. 요약 시각화
|
|
756
740
|
if show_summary:
|
|
757
|
-
print("=" * 60)
|
|
758
|
-
print("3. 조 편성 요약 시각화 중...")
|
|
759
|
-
print("=" * 60)
|
|
760
741
|
report_summary(df_result)
|
|
761
|
-
print("\n✓ 요약 시각화 완료\n")
|
|
762
742
|
|
|
763
743
|
# 4. KDE 시각화
|
|
764
744
|
if show_kde:
|
|
765
|
-
print("=" * 60)
|
|
766
|
-
print(f"4. 조별 {score_metric} KDE 시각화 중...")
|
|
767
|
-
print("=" * 60)
|
|
768
745
|
report_kde(df_result, metric=score_metric)
|
|
769
|
-
print("\n✓ KDE 시각화 완료\n")
|
|
770
|
-
|
|
771
|
-
print("=" * 60)
|
|
772
|
-
print("전체 분석 완료!")
|
|
773
|
-
print("=" * 60)
|
|
774
746
|
|
|
747
|
+
summary = group_summary(df_result, name_col=name_col)
|
|
775
748
|
return summary
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hossam
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.20
|
|
4
4
|
Summary: Hossam Data Helper
|
|
5
5
|
Author-email: Lee Kwang-Ho <leekh4232@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -45,7 +45,7 @@ title: 🎓 Hossam Data Helper
|
|
|
45
45
|
|
|
46
46
|
[](https://www.python.org/downloads/)
|
|
47
47
|
[](https://opensource.org/licenses/MIT)
|
|
48
|
-
[](https://pypi.org/project/hossam/)
|
|
49
49
|
[](https://py.hossam.kr)
|
|
50
50
|
|
|
51
51
|
**Hossam**은 데이터 분석, 시각화, 통계 처리를 위한 종합 헬퍼 라이브러리입니다.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
hossam/NotoSansKR-Regular.ttf,sha256=0SCufUQwcVWrWTu75j4Lt_V2bgBJIBXl1p8iAJJYkVY,6185516
|
|
2
|
-
hossam/__init__.py,sha256=
|
|
2
|
+
hossam/__init__.py,sha256=OHMVqGLMSbqM06zdwND9FITToVDG_fhzMYxreVzWEG8,2801
|
|
3
3
|
hossam/data_loader.py,sha256=UpC_gn-xUWij0s-MO51qrzJNz3b5-RCz1N6esQMZUJM,6320
|
|
4
|
-
hossam/hs_classroom.py,sha256=
|
|
4
|
+
hossam/hs_classroom.py,sha256=GxdrLermlJYcIgZ-6Y6ZE7DmVWyl8xe2mWfmsUbl9kw,26817
|
|
5
5
|
hossam/hs_gis.py,sha256=9ER8gXG2Or0DZ1fpbJR84WsNVPcxu788FsNtR6LsEgo,11379
|
|
6
6
|
hossam/hs_plot.py,sha256=7cngzrEeVeBvANyReI9kd3yHZGMOFZjvgBbBGA7rT2E,78467
|
|
7
7
|
hossam/hs_prep.py,sha256=RMeM6QPR-dsk3X_crJity8Tva8xY-mOXlcG3L7W-qjg,36220
|
|
@@ -19,9 +19,9 @@ hossam/mcp/hs_timeserise.py,sha256=Uc5ZKLv_sBe1tUdSZ0pmBuAFOQ_yX2MH8Me3DVeBp7c,7
|
|
|
19
19
|
hossam/mcp/hs_util.py,sha256=XsjOb9K5PUeAStgne-BluVUGyt0QEzN5wmFX77-yYGk,1237
|
|
20
20
|
hossam/mcp/loader.py,sha256=Ib11QUG8gV1V0TkMUz1g5kGsFJCApiwiwQ9N1cCy7bA,857
|
|
21
21
|
hossam/mcp/server.py,sha256=4B6ri8xwX22sA6OlNOWC4wN7Uwfgtt4gQ_HvROMNINo,21270
|
|
22
|
-
hossam-0.3.
|
|
23
|
-
hossam-0.3.
|
|
24
|
-
hossam-0.3.
|
|
25
|
-
hossam-0.3.
|
|
26
|
-
hossam-0.3.
|
|
27
|
-
hossam-0.3.
|
|
22
|
+
hossam-0.3.20.dist-info/licenses/LICENSE,sha256=nIqzhlcFY_2D6QtFsYjwU7BWkafo-rUJOQpDZ-DsauI,941
|
|
23
|
+
hossam-0.3.20.dist-info/METADATA,sha256=Q8pNH5Ioj9Xw-Z9agrr2ambybQwA-zjy4C5oWdMH0yc,5678
|
|
24
|
+
hossam-0.3.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
25
|
+
hossam-0.3.20.dist-info/entry_points.txt,sha256=0y7c7g_swXFS6See69i-4q_shcwUs-wIalfuvuYiA9I,53
|
|
26
|
+
hossam-0.3.20.dist-info/top_level.txt,sha256=_-7bwjhthHplWhywEaHIJX2yL11CQCaLjCNSBlk6wiQ,7
|
|
27
|
+
hossam-0.3.20.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|