hossam 0.3.10__py3-none-any.whl → 0.3.12__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 +73 -25
- hossam/{classroom.py → hs_classroom.py} +27 -13
- hossam/{gis.py → hs_gis.py} +35 -25
- hossam/hs_interaction.py +119 -0
- hossam/hs_plot.py +2281 -0
- hossam/hs_prep.py +597 -0
- hossam/hs_stats.py +2704 -0
- hossam/hs_timeserise.py +1104 -0
- hossam/{util.py → hs_util.py} +35 -25
- hossam/leekh.png +0 -0
- {hossam-0.3.10.dist-info → hossam-0.3.12.dist-info}/METADATA +49 -46
- hossam-0.3.12.dist-info/RECORD +17 -0
- hossam/plot.py +0 -1422
- hossam/prep.py +0 -394
- hossam/stats.py +0 -1037
- hossam-0.3.10.dist-info/RECORD +0 -14
- {hossam-0.3.10.dist-info → hossam-0.3.12.dist-info}/WHEEL +0 -0
- {hossam-0.3.10.dist-info → hossam-0.3.12.dist-info}/licenses/LICENSE +0 -0
- {hossam-0.3.10.dist-info → hossam-0.3.12.dist-info}/top_level.txt +0 -0
hossam/__init__.py
CHANGED
|
@@ -1,37 +1,85 @@
|
|
|
1
1
|
from .data_loader import load_data, load_info
|
|
2
|
+
from .hs_stats import oneway_anova
|
|
2
3
|
from matplotlib import pyplot as plt
|
|
3
4
|
from matplotlib import font_manager as fm
|
|
4
5
|
from importlib.resources import files, as_file
|
|
5
6
|
from importlib.metadata import version
|
|
7
|
+
from types import SimpleNamespace
|
|
6
8
|
import warnings
|
|
7
9
|
|
|
8
10
|
try:
|
|
9
|
-
__version__ = version(
|
|
11
|
+
__version__ = version("hossam")
|
|
10
12
|
except Exception:
|
|
11
|
-
__version__ =
|
|
12
|
-
|
|
13
|
+
__version__ = "develop"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
hs_fig = SimpleNamespace(
|
|
17
|
+
dpi=200,
|
|
18
|
+
width=600,
|
|
19
|
+
height=320,
|
|
20
|
+
font_size=6,
|
|
21
|
+
font_weight="light",
|
|
22
|
+
frame_width=0.4,
|
|
23
|
+
line_width=1,
|
|
24
|
+
grid_alpha=0.3,
|
|
25
|
+
grid_width=0.4,
|
|
26
|
+
fill_alpha=0.3
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__all__ = ["load_data", "load_info", "hs_classroom", "hs_gis", "hs_plot", "hs_prep", "hs_stats", "hs_timeserise", "hs_util", "hs_fig"]
|
|
13
30
|
|
|
14
|
-
my_dpi = 200 # 이미지 선명도(100~300)
|
|
15
|
-
default_font_size = 6
|
|
16
31
|
|
|
17
32
|
def _init_korean_font():
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
"""
|
|
34
|
+
패키지에 포함된 한글 폰트를 기본 폰트로 설정합니다.
|
|
35
|
+
"""
|
|
36
|
+
font_file = "NotoSansKR-Regular.ttf"
|
|
37
|
+
try:
|
|
38
|
+
# 패키지 리소스에서 폰트 파일 경로 확보
|
|
39
|
+
with as_file(files("hossam") / font_file) as font_path:
|
|
40
|
+
fm.fontManager.addfont(str(font_path))
|
|
41
|
+
fprop = fm.FontProperties(fname=str(font_path))
|
|
42
|
+
fname = fprop.get_name()
|
|
43
|
+
|
|
44
|
+
plt.rcParams.update({
|
|
45
|
+
"font.family": fname,
|
|
46
|
+
"font.size": hs_fig.font_size,
|
|
47
|
+
"font.weight": hs_fig.font_weight,
|
|
48
|
+
"axes.unicode_minus": False,
|
|
49
|
+
"text.antialiased": True,
|
|
50
|
+
"lines.antialiased": True,
|
|
51
|
+
"patch.antialiased": True,
|
|
52
|
+
"figure.dpi": hs_fig.dpi,
|
|
53
|
+
"savefig.dpi": hs_fig.dpi * 2,
|
|
54
|
+
"text.hinting": "auto",
|
|
55
|
+
"text.hinting_factor": 8,
|
|
56
|
+
"pdf.fonttype": 42,
|
|
57
|
+
"ps.fonttype": 42,
|
|
58
|
+
})
|
|
59
|
+
print(
|
|
60
|
+
"\n✅ 시각화를 위한 한글 글꼴(NotoSansKR-Regular)이 자동 적용되었습니다."
|
|
61
|
+
)
|
|
62
|
+
return
|
|
63
|
+
except Exception as e:
|
|
64
|
+
warnings.warn(f"\n한글 폰트 초기화: 패키지 폰트 사용 실패 ({e}).")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _init():
|
|
68
|
+
|
|
69
|
+
# 안내 메시지 (블릿 리스트)
|
|
70
|
+
messages = [
|
|
71
|
+
"📦 아이티윌 이광호 강사가 제작한 라이브러리를 사용중입니다.",
|
|
72
|
+
"📚 자세한 사용 방법은 https://py.hossam.kr 을 참고하세요.",
|
|
73
|
+
"📧 Email: leekh4232@gmail.com",
|
|
74
|
+
"🎬 Youtube: https://www.youtube.com/@hossam-codingclub",
|
|
75
|
+
"📝 Blog: https://blog.hossam.kr/",
|
|
76
|
+
f"🔖 Version: {__version__}",
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
for msg in messages:
|
|
80
|
+
print(f"{msg}")
|
|
81
|
+
|
|
82
|
+
_init_korean_font()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
_init()
|
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
학생들을 균형잡힌 조로 나누기 위한 기능을 제공합니다.
|
|
6
|
-
관심사 기반 1차 군집과 점수/인원 균형 조정을 통해
|
|
7
|
-
동질성 있고 균형잡힌 조를 구성합니다.
|
|
8
|
-
"""
|
|
9
|
-
|
|
2
|
+
# ===================================================================
|
|
3
|
+
# 패키지 참조
|
|
4
|
+
# ===================================================================
|
|
10
5
|
import math
|
|
11
6
|
from pandas import DataFrame, qcut, concat, to_numeric
|
|
12
7
|
from kmodes.kmodes import KModes
|
|
13
8
|
from matplotlib import pyplot as plt
|
|
14
9
|
import seaborn as sns
|
|
15
|
-
from hossam import
|
|
16
|
-
from hossam.util import hs_load_data, hs_pretty_table
|
|
17
|
-
|
|
10
|
+
from hossam.hs_util import load_data, pretty_table
|
|
18
11
|
|
|
12
|
+
# ===================================================================
|
|
13
|
+
# 학생들을 관심사와 성적으로 균형잡힌 조로 편성한다
|
|
14
|
+
# ===================================================================
|
|
19
15
|
def cluster_students(
|
|
20
16
|
df,
|
|
21
17
|
n_groups: int,
|
|
@@ -63,7 +59,7 @@ def cluster_students(
|
|
|
63
59
|
|
|
64
60
|
# 파일 경로인 경우 데이터프레임으로 로드
|
|
65
61
|
if isinstance(df, str):
|
|
66
|
-
df =
|
|
62
|
+
df = load_data(df, info=False)
|
|
67
63
|
|
|
68
64
|
# 입력 검증
|
|
69
65
|
if df is None or len(df) == 0:
|
|
@@ -201,6 +197,9 @@ def cluster_students(
|
|
|
201
197
|
return result
|
|
202
198
|
|
|
203
199
|
|
|
200
|
+
# ===================================================================
|
|
201
|
+
# 조 내 인원과 성적 균형을 반복 조정하여 최적화한다
|
|
202
|
+
# ===================================================================
|
|
204
203
|
def _balance_groups(
|
|
205
204
|
df: DataFrame,
|
|
206
205
|
n_groups: int,
|
|
@@ -333,6 +332,9 @@ def _balance_groups(
|
|
|
333
332
|
return df
|
|
334
333
|
|
|
335
334
|
|
|
335
|
+
# ===================================================================
|
|
336
|
+
# 성적 데이터가 없을 때 각 조의 인원 수만 균형조정한다
|
|
337
|
+
# ===================================================================
|
|
336
338
|
def _balance_group_sizes_only(
|
|
337
339
|
df: DataFrame,
|
|
338
340
|
n_groups: int,
|
|
@@ -371,6 +373,9 @@ def _balance_group_sizes_only(
|
|
|
371
373
|
return df
|
|
372
374
|
|
|
373
375
|
|
|
376
|
+
# ===================================================================
|
|
377
|
+
# 조 편성 결과의 인원, 관심사, 점수 분포를 시각화한다
|
|
378
|
+
# ===================================================================
|
|
374
379
|
def report_summary(df: DataFrame, figsize: tuple = (20, 4.2), dpi: int = None) -> None:
|
|
375
380
|
"""조 편성 결과의 요약 통계를 시각화합니다.
|
|
376
381
|
|
|
@@ -493,6 +498,9 @@ def report_summary(df: DataFrame, figsize: tuple = (20, 4.2), dpi: int = None) -
|
|
|
493
498
|
plt.show()
|
|
494
499
|
|
|
495
500
|
|
|
501
|
+
# ===================================================================
|
|
502
|
+
# 조별 점수 분포를 커널 밀도 추정(KDE) 그래프로 시각화한다
|
|
503
|
+
# ===================================================================
|
|
496
504
|
def report_kde(df: DataFrame, metric: str = 'average', figsize: tuple = (20, 8), dpi: int = None) -> None:
|
|
497
505
|
"""조별 점수 분포를 KDE(Kernel Density Estimation)로 시각화합니다.
|
|
498
506
|
|
|
@@ -604,6 +612,9 @@ def report_kde(df: DataFrame, metric: str = 'average', figsize: tuple = (20, 8),
|
|
|
604
612
|
plt.show()
|
|
605
613
|
|
|
606
614
|
|
|
615
|
+
# ===================================================================
|
|
616
|
+
# 조별로 학생 목록과 평균 점수를 요약하여 데이터프레임으로 반환한다
|
|
617
|
+
# ===================================================================
|
|
607
618
|
def group_summary(df: DataFrame, name_col: str = '학생번호') -> DataFrame:
|
|
608
619
|
"""조별로 학생 목록과 평균 점수를 요약합니다.
|
|
609
620
|
|
|
@@ -667,6 +678,9 @@ def group_summary(df: DataFrame, name_col: str = '학생번호') -> DataFrame:
|
|
|
667
678
|
return result_df
|
|
668
679
|
|
|
669
680
|
|
|
681
|
+
# ===================================================================
|
|
682
|
+
# 학생 조 편성부터 시각화까지의 전체 분석 프로세스를 일괄 실행한다
|
|
683
|
+
# ===================================================================
|
|
670
684
|
def analyze_classroom(
|
|
671
685
|
df,
|
|
672
686
|
n_groups: int,
|
|
@@ -735,7 +749,7 @@ def analyze_classroom(
|
|
|
735
749
|
# 2. 조별 요약
|
|
736
750
|
summary = group_summary(df_result, name_col=name_col)
|
|
737
751
|
print("\n✓ 조별 요약:")
|
|
738
|
-
|
|
752
|
+
pretty_table(summary, tablefmt="pretty")
|
|
739
753
|
print()
|
|
740
754
|
|
|
741
755
|
# 3. 요약 시각화
|
hossam/{gis.py → hs_gis.py}
RENAMED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
#
|
|
2
|
+
# ===================================================================
|
|
3
|
+
# 패키지 참조
|
|
4
|
+
# ===================================================================
|
|
5
|
+
import os
|
|
6
|
+
import time
|
|
7
|
+
import warnings
|
|
3
8
|
import requests
|
|
4
9
|
import concurrent.futures as futures
|
|
5
|
-
|
|
6
|
-
|
|
10
|
+
|
|
11
|
+
from pandas import DataFrame, to_numeric
|
|
7
12
|
from tqdm.auto import tqdm
|
|
8
|
-
import
|
|
9
|
-
from geopandas import GeoDataFrame, read_file
|
|
10
|
-
import geopandas as gpd
|
|
13
|
+
from geopandas import GeoDataFrame, read_file, points_from_xy
|
|
11
14
|
from pyproj import CRS
|
|
12
|
-
import os
|
|
13
|
-
import warnings
|
|
14
15
|
|
|
15
|
-
from .
|
|
16
|
+
from .hs_util import pretty_table
|
|
16
17
|
|
|
17
|
-
#
|
|
18
|
+
# ===================================================================
|
|
19
|
+
# 단일 주소를 VWorld API로 지오코딩
|
|
20
|
+
# ===================================================================
|
|
18
21
|
def __geocode_item(session: requests.Session, index: int, addr: str, key: str) -> tuple[float, float]:
|
|
19
22
|
"""단일 주소를 VWorld API로 지오코딩합니다.
|
|
20
23
|
|
|
@@ -79,8 +82,11 @@ def __geocode_item(session: requests.Session, index: int, addr: str, key: str) -
|
|
|
79
82
|
#print("%s --> (%s, %s)" % (addr, latitude, longitude))
|
|
80
83
|
return result
|
|
81
84
|
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
|
|
86
|
+
# ===================================================================
|
|
87
|
+
# 주소 컬럼을 일괄 지오코딩하여 위도/경도 컬럼을 추가
|
|
88
|
+
# ===================================================================
|
|
89
|
+
def geocode(df: DataFrame, addr: str, key: str) -> DataFrame:
|
|
84
90
|
"""주소 컬럼을 일괄 지오코딩하여 위도/경도 컬럼을 추가합니다.
|
|
85
91
|
|
|
86
92
|
Args:
|
|
@@ -149,8 +155,10 @@ def hs_geocode(df: DataFrame, addr: str, key: str) -> DataFrame:
|
|
|
149
155
|
|
|
150
156
|
return data
|
|
151
157
|
|
|
152
|
-
#
|
|
153
|
-
|
|
158
|
+
# ===================================================================
|
|
159
|
+
# Shapefile을 읽어 `GeoDataFrame`으로 로드
|
|
160
|
+
# ===================================================================
|
|
161
|
+
def load_shape(path: str, info: bool = True) -> GeoDataFrame:
|
|
154
162
|
"""Shapefile을 읽어 `GeoDataFrame`으로 로드합니다.
|
|
155
163
|
|
|
156
164
|
Args:
|
|
@@ -164,7 +172,7 @@ def hs_load_shape(path: str, info: bool = True) -> GeoDataFrame:
|
|
|
164
172
|
FileNotFoundError: 파일이 존재하지 않는 경우.
|
|
165
173
|
|
|
166
174
|
Examples:
|
|
167
|
-
>>> from hossam.gis import
|
|
175
|
+
>>> from hossam.gis import load_shape
|
|
168
176
|
>>> gdf = hs_load_shape("path/to/file.shp", info=False)
|
|
169
177
|
"""
|
|
170
178
|
if not os.path.exists(path):
|
|
@@ -174,23 +182,25 @@ def hs_load_shape(path: str, info: bool = True) -> GeoDataFrame:
|
|
|
174
182
|
|
|
175
183
|
if info:
|
|
176
184
|
print("\n✅ 테이블 정보")
|
|
177
|
-
|
|
185
|
+
pretty_table(data.info(), tablefmt="pretty")
|
|
178
186
|
|
|
179
187
|
print("\n✅ 상위 5개 행")
|
|
180
|
-
|
|
188
|
+
pretty_table(data.head(), tablefmt="pretty")
|
|
181
189
|
|
|
182
190
|
print("\n✅ 하위 5개 행")
|
|
183
|
-
|
|
191
|
+
pretty_table(data.tail(), tablefmt="pretty")
|
|
184
192
|
|
|
185
193
|
print("\n📊 기술통계")
|
|
186
194
|
desc = data.describe().T
|
|
187
195
|
desc["nan"] = data.isnull().sum()
|
|
188
|
-
|
|
196
|
+
pretty_table(desc, tablefmt="pretty")
|
|
189
197
|
|
|
190
198
|
return data
|
|
191
199
|
|
|
192
|
-
#
|
|
193
|
-
|
|
200
|
+
# ===================================================================
|
|
201
|
+
# 전처리된 데이터(GeoDataFrame 또는 DataFrame)를 Shapefile 또는 GeoPackage로 저장
|
|
202
|
+
# ===================================================================
|
|
203
|
+
def save_shape(
|
|
194
204
|
gdf: GeoDataFrame | DataFrame,
|
|
195
205
|
path: str,
|
|
196
206
|
crs: str | None = None,
|
|
@@ -249,8 +259,8 @@ def hs_save_shape(
|
|
|
249
259
|
|
|
250
260
|
df = gdf.copy()
|
|
251
261
|
# 숫자 변환 및 결측 제거
|
|
252
|
-
df[lat_col] =
|
|
253
|
-
df[lon_col] =
|
|
262
|
+
df[lat_col] = to_numeric(df[lat_col], errors="coerce")
|
|
263
|
+
df[lon_col] = to_numeric(df[lon_col], errors="coerce")
|
|
254
264
|
df = df.dropna(subset=[lat_col, lon_col])
|
|
255
265
|
|
|
256
266
|
if df.empty:
|
|
@@ -258,8 +268,8 @@ def hs_save_shape(
|
|
|
258
268
|
"⚠️[ValueError] 유효한 위경도 값이 없어 Shapefile을 생성할 수 없습니다."
|
|
259
269
|
)
|
|
260
270
|
|
|
261
|
-
geometry =
|
|
262
|
-
gdf =
|
|
271
|
+
geometry = points_from_xy(x=df[lon_col], y=df[lat_col])
|
|
272
|
+
gdf = GeoDataFrame(df, geometry=geometry, crs=target_crs)
|
|
263
273
|
else:
|
|
264
274
|
# GeoDataFrame의 CRS 처리: 존재하면 유지, 없으면만 설정
|
|
265
275
|
if gdf.crs is None:
|
hossam/hs_interaction.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# ===================================================================
|
|
3
|
+
# 상호작용(Interaction) 항 생성 함수
|
|
4
|
+
# ===================================================================
|
|
5
|
+
import numpy as np
|
|
6
|
+
from pandas import DataFrame
|
|
7
|
+
from itertools import combinations
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# ===================================================================
|
|
11
|
+
# 변수 간의 상호작용 항을 추가한 데이터프레임을 반환한다
|
|
12
|
+
# ===================================================================
|
|
13
|
+
def interaction(*fields: str):
|
|
14
|
+
"""변수 간의 상호작용(interaction) 항을 생성하는 데코레이터 함수.
|
|
15
|
+
|
|
16
|
+
사용 방법: 원본 데이터프레임에 대해 호출하면 상호작용 컬럼이 추가된 새 데이터프레임을 반환한다.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
*fields (str): 상호작용할 변수들의 컬럼명. 2개 이상의 컬럼을 지정하면 모든 조합의 상호작용을 생성.
|
|
20
|
+
지정하지 않으면 모든 수치형 컬럼의 모든 2-way 상호작용을 생성.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
function: 데이터프레임을 입력받아 상호작용 항이 추가된 데이터프레임을 반환하는 함수.
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
>>> from hossam.hs_prep import interaction
|
|
27
|
+
>>> import pandas as pd
|
|
28
|
+
>>> df = pd.DataFrame({'x1': [1, 2, 3], 'x2': [4, 5, 6], 'x3': [7, 8, 9]})
|
|
29
|
+
|
|
30
|
+
# 특정 변수들의 상호작용만 생성
|
|
31
|
+
>>> result = interaction('x1', 'x2')(df)
|
|
32
|
+
>>> print(result.columns) # x1, x2, x3, x1*x2
|
|
33
|
+
|
|
34
|
+
# 모든 2-way 상호작용 생성
|
|
35
|
+
>>> result = interaction()(df)
|
|
36
|
+
>>> print(result.columns) # x1, x2, x3, x1*x2, x1*x3, x2*x3
|
|
37
|
+
"""
|
|
38
|
+
def wrapper(data: DataFrame) -> DataFrame:
|
|
39
|
+
df = data.copy()
|
|
40
|
+
|
|
41
|
+
# fields가 비어있으면 모든 수치형 컬럼의 2-way 상호작용 생성
|
|
42
|
+
if not fields:
|
|
43
|
+
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
|
|
44
|
+
cols_to_interact = list(combinations(numeric_cols, 2))
|
|
45
|
+
else:
|
|
46
|
+
# fields가 지정된 경우 모든 가능한 조합 생성
|
|
47
|
+
field_list = [f for f in fields if f in df.columns]
|
|
48
|
+
if len(field_list) < 2:
|
|
49
|
+
return df
|
|
50
|
+
cols_to_interact = list(combinations(field_list, 2))
|
|
51
|
+
|
|
52
|
+
# 상호작용 항 생성
|
|
53
|
+
for col1, col2 in cols_to_interact:
|
|
54
|
+
# 두 컬럼이 모두 수치형인지 확인
|
|
55
|
+
if not (pd.api.types.is_numeric_dtype(df[col1]) and pd.api.types.is_numeric_dtype(df[col2])):
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
interaction_col_name = f"{col1}*{col2}"
|
|
59
|
+
df[interaction_col_name] = df[col1] * df[col2]
|
|
60
|
+
|
|
61
|
+
return df
|
|
62
|
+
|
|
63
|
+
return wrapper
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ===================================================================
|
|
67
|
+
# 직접 상호작용 항을 추가하는 함수 (데코레이터 없이 직접 사용)
|
|
68
|
+
# ===================================================================
|
|
69
|
+
def add_interaction(data: DataFrame, *fields: str) -> DataFrame:
|
|
70
|
+
"""데이터프레임에 상호작용 항을 추가한다.
|
|
71
|
+
|
|
72
|
+
특정 변수 쌍 또는 모든 수치형 변수 간의 상호작용 항을 생성하여 데이터프레임에 추가한다.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
data (DataFrame): 원본 데이터프레임.
|
|
76
|
+
*fields (str): 상호작용할 변수들의 컬럼명 목록.
|
|
77
|
+
지정하지 않으면 모든 수치형 컬럼의 모든 2-way 상호작용을 생성.
|
|
78
|
+
지정된 경우, 지정된 컬럼들의 모든 조합에 대해 상호작용을 생성.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
DataFrame: 상호작용 항이 추가된 새 데이터프레임.
|
|
82
|
+
|
|
83
|
+
Examples:
|
|
84
|
+
>>> from hossam.hs_prep import add_interaction
|
|
85
|
+
>>> import pandas as pd
|
|
86
|
+
>>> df = pd.DataFrame({'x1': [1, 2, 3], 'x2': [4, 5, 6], 'x3': [7, 8, 9]})
|
|
87
|
+
|
|
88
|
+
# 특정 변수들의 상호작용만 추가
|
|
89
|
+
>>> result = add_interaction(df, 'x1', 'x2')
|
|
90
|
+
>>> print(result.columns) # x1, x2, x3, x1*x2
|
|
91
|
+
>>> print(result['x1*x2'].tolist()) # [4, 10, 18]
|
|
92
|
+
|
|
93
|
+
# 모든 2-way 상호작용 추가
|
|
94
|
+
>>> result = add_interaction(df)
|
|
95
|
+
>>> print(result.columns) # x1, x2, x3, x1*x2, x1*x3, x2*x3
|
|
96
|
+
"""
|
|
97
|
+
df = data.copy()
|
|
98
|
+
|
|
99
|
+
# fields가 비어있으면 모든 수치형 컬럼의 2-way 상호작용 생성
|
|
100
|
+
if not fields:
|
|
101
|
+
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
|
|
102
|
+
cols_to_interact = list(combinations(numeric_cols, 2))
|
|
103
|
+
else:
|
|
104
|
+
# fields가 지정된 경우 모든 가능한 조합 생성
|
|
105
|
+
field_list = [f for f in fields if f in df.columns]
|
|
106
|
+
if len(field_list) < 2:
|
|
107
|
+
return df
|
|
108
|
+
cols_to_interact = list(combinations(field_list, 2))
|
|
109
|
+
|
|
110
|
+
# 상호작용 항 생성
|
|
111
|
+
for col1, col2 in cols_to_interact:
|
|
112
|
+
# 두 컬럼이 모두 수치형인지 확인
|
|
113
|
+
if not (pd.api.types.is_numeric_dtype(df[col1]) and pd.api.types.is_numeric_dtype(df[col2])):
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
interaction_col_name = f"{col1}*{col2}"
|
|
117
|
+
df[interaction_col_name] = df[col1] * df[col2]
|
|
118
|
+
|
|
119
|
+
return df
|