콘텐츠로 이동

hossam.hs_prep

hossam.hs_prep

standard_scaler

standard_scaler(
    data, yname=None, save_path=None, load_path=None
)

연속형 변수에 대해 Standard Scaling을 수행한다.

  • DataFrame 입력 시: 비수치형/종속변수를 분리한 후 스케일링하고 다시 합칩니다.
  • 배열 입력 시: 그대로 스케일링된 ndarray를 반환합니다.
  • load_path가 주어지면 기존 스케일러를 재사용하고, save_path가 주어지면 학습된 스케일러를 저장합니다.

Parameters:

Name Type Description Default
data DataFrame | ndarray

스케일링할 데이터.

required
yname str | None

종속변수 컬럼명. 분리하지 않으려면 None.

None
save_path str | None

학습된 스케일러 저장 경로.

None
load_path str | None

기존 스케일러 로드 경로.

None

Returns:

Type Description
DataFrame

DataFrame | ndarray: 스케일링된 데이터(입력 타입과 동일).

Examples:

from hossam import *
std_df = hs_prep.standard_scaler(df, yname="y", save_path="std.pkl")
Source code in hossam/hs_prep.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def standard_scaler(
    data: Any,
    yname: str | None = None,
    save_path: str | None = None,
    load_path: str | None = None,
) -> DataFrame:
    """연속형 변수에 대해 Standard Scaling을 수행한다.

    - DataFrame 입력 시: 비수치형/종속변수를 분리한 후 스케일링하고 다시 합칩니다.
    - 배열 입력 시: 그대로 스케일링된 ndarray를 반환합니다.
    - `load_path`가 주어지면 기존 스케일러를 재사용하고, `save_path`가 주어지면 학습된 스케일러를 저장합니다.

    Args:
        data (DataFrame | ndarray): 스케일링할 데이터.
        yname (str | None): 종속변수 컬럼명. 분리하지 않으려면 None.
        save_path (str | None): 학습된 스케일러 저장 경로.
        load_path (str | None): 기존 스케일러 로드 경로.

    Returns:
        DataFrame | ndarray: 스케일링된 데이터(입력 타입과 동일).

    Examples:
        ```python
        from hossam import *
        std_df = hs_prep.standard_scaler(df, yname="y", save_path="std.pkl")
        ```
    """

    is_df = isinstance(data, DataFrame)

    # ndarray 처리 분기
    if not is_df:
        arr = np.asarray(data)
        if arr.ndim == 1:
            arr = arr.reshape(-1, 1)
        scaler = joblib.load(load_path) if load_path else StandardScaler()
        sdata = scaler.transform(arr) if load_path else scaler.fit_transform(arr)
        if save_path:
            joblib.dump(value=scaler, filename=save_path)
        return sdata  # type: ignore

    df = data.copy()

    y = None
    if yname and yname in df.columns:
        y = df[yname]
        df = df.drop(columns=[yname])

    category_cols = df.select_dtypes(exclude=[np.number]).columns.tolist()
    cate = df[category_cols] if category_cols else DataFrame(index=df.index)
    X = df.drop(columns=category_cols)

    if X.shape[1] == 0:
        return data

    scaler = joblib.load(load_path) if load_path else StandardScaler()
    sdata = scaler.transform(X) if load_path else scaler.fit_transform(X)

    if save_path:
        joblib.dump(value=scaler, filename=save_path)

    std_df = DataFrame(data=sdata, index=data.index, columns=X.columns)

    if category_cols:
        std_df[category_cols] = cate
    if yname and y is not None:
        std_df[yname] = y

    return std_df

minmax_scaler

minmax_scaler(
    data, yname=None, save_path=None, load_path=None
)

연속형 변수에 대해 MinMax Scaling을 수행한다.

DataFrame은 비수치/종속변수를 분리 후 스케일링하고 재결합하며, 배열 입력은 그대로 ndarray를 반환한다. load_path 제공 시 기존 스케일러를 사용하고, save_path 제공 시 학습 스케일러를 저장한다.

Parameters:

Name Type Description Default
data DataFrame | ndarray

스케일링할 데이터.

required
yname str | None

종속변수 컬럼명. 분리하지 않으려면 None.

None
save_path str | None

학습된 스케일러 저장 경로.

None
load_path str | None

기존 스케일러 로드 경로.

None

Returns:

Type Description
DataFrame

DataFrame | ndarray: 스케일링된 데이터(입력 타입과 동일).

Examples:

from hossam import *
mm_df = hs_prep.minmax_scaler(df, yname="y")
Source code in hossam/hs_prep.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def minmax_scaler(
    data: Any,
    yname: str | None = None,
    save_path: str | None = None,
    load_path: str | None = None,
) -> DataFrame:
    """연속형 변수에 대해 MinMax Scaling을 수행한다.

    DataFrame은 비수치/종속변수를 분리 후 스케일링하고 재결합하며, 배열 입력은 그대로 ndarray를 반환한다.
    `load_path` 제공 시 기존 스케일러를 사용하고, `save_path` 제공 시 학습 스케일러를 저장한다.

    Args:
        data (DataFrame | ndarray): 스케일링할 데이터.
        yname (str | None): 종속변수 컬럼명. 분리하지 않으려면 None.
        save_path (str | None): 학습된 스케일러 저장 경로.
        load_path (str | None): 기존 스케일러 로드 경로.

    Returns:
        DataFrame | ndarray: 스케일링된 데이터(입력 타입과 동일).

    Examples:
        ```python
        from hossam import *
        mm_df = hs_prep.minmax_scaler(df, yname="y")
        ```
    """

    is_df = isinstance(data, DataFrame)

    if not is_df:
        arr = np.asarray(data)
        if arr.ndim == 1:
            arr = arr.reshape(-1, 1)
        scaler = joblib.load(load_path) if load_path else MinMaxScaler()
        sdata = scaler.transform(arr) if load_path else scaler.fit_transform(arr)
        if save_path:
            joblib.dump(scaler, save_path)
        return sdata  # type: ignore

    df = data.copy()

    y = None
    if yname and yname in df.columns:
        y = df[yname]
        df = df.drop(columns=[yname])

    category_cols = df.select_dtypes(exclude=[np.number]).columns.tolist()
    cate = df[category_cols] if category_cols else DataFrame(index=df.index)
    X = df.drop(columns=category_cols)

    if X.shape[1] == 0:
        return data

    scaler = joblib.load(load_path) if load_path else MinMaxScaler()
    sdata = scaler.transform(X) if load_path else scaler.fit_transform(X)

    if save_path:
        joblib.dump(scaler, save_path)

    std_df = DataFrame(data=sdata, index=data.index, columns=X.columns)

    if category_cols:
        std_df[category_cols] = cate
    if yname and y is not None:
        std_df[yname] = y

    return std_df

set_category

set_category(data, *args, columns=None)

카테고리 데이터를 설정한다.

Parameters:

Name Type Description Default
data DataFrame

데이터프레임 객체

required
*args str

컬럼명 목록

()
columns list

변환할 컬럼명 목록. args와 중복 사용 불가.

None

Returns:

Name Type Description
DataFrame DataFrame

카테고리 설정된 데이터프레임

Source code in hossam/hs_prep.py
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
def set_category(data: DataFrame, *args: str, columns: list | None = None) -> DataFrame:
    """카테고리 데이터를 설정한다.

    Args:
        data (DataFrame): 데이터프레임 객체
        *args (str): 컬럼명 목록
        columns (list, optional): 변환할 컬럼명 목록. args와 중복 사용 불가.

    Returns:
        DataFrame: 카테고리 설정된 데이터프레임
    """
    # columns 인자가 있으면 args보다 우선한다.
    if columns is not None:
        if args:
            raise ValueError("args와 columns 인자는 중복 사용할 수 없습니다.")
        args = columns  # type: ignore

    df = data.copy()

    for k in args:
        df[k] = df[k].astype("category")

    return df

unmelt

unmelt(data, id_vars='class', value_vars='values')

두 개의 컬럼으로 구성된 데이터프레임에서 하나는 명목형, 나머지는 연속형일 경우 명목형 변수의 값에 따라 고유한 변수를 갖는 데이터프레임으로 변환한다.

각 그룹의 데이터 길이가 다를 경우 짧은 쪽에 NaN을 채워 동일한 길이로 맞춥니다. 이는 독립표본 t-검정(ttest_ind) 등의 분석을 위한 데이터 준비에 유용합니다.

Parameters:

Name Type Description Default
data DataFrame

데이터프레임

required
id_vars str

명목형 변수의 컬럼명. Defaults to 'class'.

'class'
value_vars str

연속형 변수의 컬럼명. Defaults to 'values'.

'values'

Returns:

Name Type Description
DataFrame DataFrame

변환된 데이터프레임 (각 그룹이 개별 컬럼으로 구성)

Examples:

df = pd.DataFrame({
    'group': ['A', 'A', 'B', 'B', 'B'],
    'value': [1, 2, 3, 4, 5]
})

from hossam import *
result = hs_prep.unmelt(df, id_vars='group', value_vars='value')
# 결과: A 컬럼에는 [1, 2, NaN], B 컬럼에는 [3, 4, 5]
Source code in hossam/hs_prep.py
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
def unmelt(
    data: DataFrame, id_vars: str = "class", value_vars: str = "values"
) -> DataFrame:
    """두 개의 컬럼으로 구성된 데이터프레임에서 하나는 명목형, 나머지는 연속형일 경우
    명목형 변수의 값에 따라 고유한 변수를 갖는 데이터프레임으로 변환한다.

    각 그룹의 데이터 길이가 다를 경우 짧은 쪽에 NaN을 채워 동일한 길이로 맞춥니다.
    이는 독립표본 t-검정(ttest_ind) 등의 분석을 위한 데이터 준비에 유용합니다.

    Args:
        data (DataFrame): 데이터프레임
        id_vars (str, optional): 명목형 변수의 컬럼명. Defaults to 'class'.
        value_vars (str, optional): 연속형 변수의 컬럼명. Defaults to 'values'.

    Returns:
        DataFrame: 변환된 데이터프레임 (각 그룹이 개별 컬럼으로 구성)

    Examples:
        ```python
        df = pd.DataFrame({
            'group': ['A', 'A', 'B', 'B', 'B'],
            'value': [1, 2, 3, 4, 5]
        })

        from hossam import *
        result = hs_prep.unmelt(df, id_vars='group', value_vars='value')
        # 결과: A 컬럼에는 [1, 2, NaN], B 컬럼에는 [3, 4, 5]
        ```
    """
    # 그룹별로 값들을 리스트로 모음
    grouped = data.groupby(id_vars, observed=True)[value_vars].apply(
        lambda x: x.tolist()
    )
    series_dict = {}
    for idx, values in grouped.items():
        series_dict[str(idx)] = pd.Series(values)

    return DataFrame(series_dict)

outlier_table

outlier_table(data, *fields, columns=None)

수치형 컬럼에 대한 사분위수 및 IQR 기반 이상치 경계를 계산한다.

전달된 fields가 없으면 데이터프레임의 모든 수치형 컬럼을 대상으로 한다. 결측치는 제외하고 사분위수를 계산한다.

Parameters:

Name Type Description Default
data DataFrame

분석할 데이터프레임.

required
*fields str

대상 컬럼명(들). 생략 시 모든 수치형 컬럼 대상.

()
columns list

변환할 컬럼명 목록. args와 중복 사용 불가.

None

Returns:

Name Type Description
DataFrame DataFrame

Q1, Q2(중앙값), Q3, IQR, 하한, 상한을 포함한 통계표.

Examples:

from hossam import * hs_prep.outlier_table(df, "value")

Source code in hossam/hs_prep.py
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
def outlier_table(
    data: DataFrame, *fields: str, columns: list | None = None
) -> DataFrame:
    """수치형 컬럼에 대한 사분위수 및 IQR 기반 이상치 경계를 계산한다.

    전달된 `fields`가 없으면 데이터프레임의 모든 수치형 컬럼을 대상으로 한다.
    결측치는 제외하고 사분위수를 계산한다.

    Args:
        data (DataFrame): 분석할 데이터프레임.
        *fields (str): 대상 컬럼명(들). 생략 시 모든 수치형 컬럼 대상.
        columns (list, optional): 변환할 컬럼명 목록. args와 중복 사용 불가.

    Returns:
        DataFrame: Q1, Q2(중앙값), Q3, IQR, 하한, 상한을 포함한 통계표.

    Examples:
        from hossam import *
        hs_prep.outlier_table(df, "value")
    """
    # columns 인자가 있으면 fields보다 우선한다.
    if columns is not None:
        if fields:  # type: ignore
            raise ValueError("fields와 columns 인자는 중복 사용할 수 없습니다.")
        fields = columns # type: ignore

    target_fields = list(fields) if fields else columns

    if not target_fields:
        target_fields = list(data.select_dtypes(include=[np.number]).columns)

    result = []
    for f in target_fields:
        if f not in data.columns:
            continue

        series = data[f].dropna()
        if series.empty:
            continue

        q1 = series.quantile(q=0.25)
        q2 = series.quantile(q=0.5)
        q3 = series.quantile(q=0.75)

        iqr = q3 - q1
        down = q1 - 1.5 * iqr
        up = q3 + 1.5 * iqr

        result.append(
            {
                "FIELD": f,
                "Q1": q1,
                "Q2": q2,
                "Q3": q3,
                "IQR": iqr,
                "UP": up,
                "DOWN": down,
            }
        )

    return DataFrame(result).set_index("FIELD") if result else DataFrame()

replace_outliner

replace_outliner(data, method='nan', *fields, columns=None)

이상치 경계값을 넘어가는 데이터를 경계값으로 대체한다.

Parameters:

Name Type Description Default
data DataFrame

데이터프레임

required
method str

대체 방법 - nan: 결측치 대체 - outline: 경계값 대체 - mean: 평균 대체 - most: 최빈값 대체 - median: 중앙값 대체

'nan'
*fields str

컬럼명 목록

()
columns list

변환할 컬럼명 목록. args와 중복 사용 불가.

None

Returns:

Name Type Description
DataFrame DataFrame

이상치가 경계값으로 대체된 데이터 프레임

Source code in hossam/hs_prep.py
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
def replace_outliner(
    data: DataFrame, method: str = "nan", *fields: str, columns: list | None = None
) -> DataFrame:
    """이상치 경계값을 넘어가는 데이터를 경계값으로 대체한다.

    Args:
        data (DataFrame): 데이터프레임
        method (str): 대체 방법
            - nan: 결측치 대체
            - outline: 경계값 대체
            - mean: 평균 대체
            - most: 최빈값 대체
            - median: 중앙값 대체
        *fields (str): 컬럼명 목록
        columns (list, optional): 변환할 컬럼명 목록. args와 중복 사용 불가.

    Returns:
        DataFrame: 이상치가 경계값으로 대체된 데이터 프레임
    """
    # columns 인자가 있으면 args보다 우선한다.
    if columns is not None:
        if args:  # type: ignore
            raise ValueError("args와 columns 인자는 중복 사용할 수 없습니다.")
        args = columns

    # 원본 데이터 프레임 복사
    df = data.copy()

    # 카테고리 타입만 골라냄
    category_fields = []
    for f in df.columns:
        if df[f].dtypes not in ["int", "int32", "int64", "float", "float32", "float64"]:
            category_fields.append(f)

    cate = df[category_fields]
    df = df.drop(category_fields, axis=1)

    # 이상치 경계값을 구한다.
    outliner_table = outlier_table(df, *fields)

    if outliner_table.empty:
        return data.copy()

    # 이상치가 발견된 필드에 대해서만 처리
    for f in outliner_table.index:
        if method == "outline":
            df.loc[df[f] < outliner_table.loc[f, "DOWN"], f] = outliner_table.loc[
                f, "DOWN"
            ]
            df.loc[df[f] > outliner_table.loc[f, "UP"], f] = outliner_table.loc[f, "UP"]
        else:
            df.loc[df[f] < outliner_table.loc[f, "DOWN"], f] = np.nan
            df.loc[df[f] > outliner_table.loc[f, "UP"], f] = np.nan

    # NaN으로 표시된 이상치를 지정한 방법으로 대체
    if method in {"mean", "median", "most"}:
        strategy_map = {"mean": "mean", "median": "median", "most": "most_frequent"}
        imr = SimpleImputer(missing_values=np.nan, strategy=strategy_map[method])
        df_imr = imr.fit_transform(df.values)
        df = DataFrame(df_imr, index=data.index, columns=df.columns)
    elif method not in {"nan", "outline"}:
        raise ValueError(
            "method는 'nan', 'outline', 'mean', 'median', 'most' 중 하나여야 합니다."
        )

    # 분리했던 카테고리 타입을 다시 병합
    if category_fields:
        df[category_fields] = cate

    return df

drop_outliner

drop_outliner(data, *fields, columns=None)

이상치를 결측치로 변환한 후 모두 삭제한다.

Parameters:

Name Type Description Default
data DataFrame

데이터프레임

required
*fields str

컬럼명 목록

()
columns list

변환할 컬럼명 목록. args와 중복 사용 불가.

None

Returns:

Name Type Description
DataFrame DataFrame

이상치가 삭제된 데이터프레임

Source code in hossam/hs_prep.py
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
def drop_outliner(
    data: DataFrame, *fields: str, columns: list | None = None
) -> DataFrame:
    """이상치를 결측치로 변환한 후 모두 삭제한다.

    Args:
        data (DataFrame): 데이터프레임
        *fields (str): 컬럼명 목록
        columns (list, optional): 변환할 컬럼명 목록. args와 중복 사용 불가.

    Returns:
        DataFrame: 이상치가 삭제된 데이터프레임
    """
    # columns 인자가 있으면 args보다 우선한다.
    if columns is not None:
        if args:  # type: ignore
            raise ValueError("args와 columns 인자는 중복 사용할 수 없습니다.")
        args = columns

    df = replace_outliner(data, "nan", *fields)
    return df.dropna()

get_dummies

get_dummies(
    data, *args, columns=None, drop_first=True, dtype="int"
)

명목형 변수를 더미 변수로 변환한다.

컬럼명을 지정하면 그 컬럼들만 더미 변수로 변환하고, 지정하지 않으면 숫자 타입이 아닌 모든 컬럼(문자열/명목형)을 자동으로 더미 변수로 변환한다.

Parameters:

Name Type Description Default
data DataFrame

데이터프레임

required
*args str

변환할 컬럼명 목록. 지정하지 않으면 숫자형이 아닌 모든 컬럼 자동 선택.

()
columns list

변환할 컬럼명 목록. args와 중복 사용 불가.

None
drop_first bool

첫 번째 더미 변수 제거 여부. 기본값 True.

True
dtype str

더미 변수 데이터 타입. 기본값 "int".

'int'

Returns:

Name Type Description
DataFrame DataFrame

더미 변수로 변환된 데이터프레임

Examples:

from hossam import *
# 전체 비숫자 컬럼 자동 변환
result = hs_prep.get_dummies(df)
# 특정 컬럼만 변환
result = hs_prep.get_dummies(df, 'cut', 'color', 'clarity')
# 옵션 지정
result = hs_prep.get_dummies(df, 'col1', drop_first=False, dtype='bool')
Source code in hossam/hs_prep.py
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
def get_dummies(
    data: DataFrame,
    *args: str,
    columns: list | None = None,
    drop_first: bool = True,
    dtype: str = "int",
) -> DataFrame:
    """명목형 변수를 더미 변수로 변환한다.

    컬럼명을 지정하면 그 컬럼들만 더미 변수로 변환하고,
    지정하지 않으면 숫자 타입이 아닌 모든 컬럼(문자열/명목형)을 자동으로 더미 변수로 변환한다.

    Args:
        data (DataFrame): 데이터프레임
        *args (str): 변환할 컬럼명 목록. 지정하지 않으면 숫자형이 아닌 모든 컬럼 자동 선택.
        columns (list, optional): 변환할 컬럼명 목록. args와 중복 사용 불가.
        drop_first (bool, optional): 첫 번째 더미 변수 제거 여부. 기본값 True.
        dtype (str, optional): 더미 변수 데이터 타입. 기본값 "int".

    Returns:
        DataFrame: 더미 변수로 변환된 데이터프레임

    Examples:
        ```python
        from hossam import *
        # 전체 비숫자 컬럼 자동 변환
        result = hs_prep.get_dummies(df)
        # 특정 컬럼만 변환
        result = hs_prep.get_dummies(df, 'cut', 'color', 'clarity')
        # 옵션 지정
        result = hs_prep.get_dummies(df, 'col1', drop_first=False, dtype='bool')
        ```
    """
    # columns 인자가 있으면 args보다 우선한다.
    if columns is not None:
        if args:
            raise ValueError("args와 columns 인자는 중복 사용할 수 없습니다.")
        args = columns  # type: ignore

    if not args:
        # args가 없으면 숫자 타입이 아닌 모든 컬럼 자동 선택
        cols_to_convert = []
        for f in data.columns:
            if not pd.api.types.is_numeric_dtype(data[f]):
                cols_to_convert.append(f)
        args = cols_to_convert  # type: ignore
    else:
        # args가 있으면 그 컬럼들만 사용 (존재 여부 확인)
        args = [c for c in args if c in data.columns]  # type: ignore

    # pandas.get_dummies 사용 (재귀 문제 없음)
    return pd.get_dummies(data, columns=args, drop_first=drop_first, dtype=dtype) if args else data.copy()  # type: ignore

labelling

labelling(data, *fields)

명목형 변수를 라벨링한다.

Parameters:

Name Type Description Default
data DataFrame

데이터프레임

required
*fields str

명목형 컬럼 목록

()

Returns:

Name Type Description
DataFrame DataFrame

라벨링된 데이터프레임

Source code in hossam/hs_prep.py
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
def labelling(data: DataFrame, *fields: str) -> DataFrame:
    """명목형 변수를 라벨링한다.

    Args:
        data (DataFrame): 데이터프레임
        *fields (str): 명목형 컬럼 목록

    Returns:
        DataFrame: 라벨링된 데이터프레임
    """
    df = data.copy()

    for f in fields:
        vc = sorted(list(df[f].unique()))
        label = {v: i for i, v in enumerate(vc)}
        df[f] = df[f].map(label).astype("int")

        # 라벨링 상황을 출력한다.
        i = []
        v = []
        for k in label:
            i.append(k)
            v.append(label[k])

        label_df = DataFrame({"label": v}, index=i)
        label_df.index.name = f
        pretty_table(label_df)

    return df

bin_continuous

bin_continuous(
    data,
    field,
    method="natural_breaks",
    bins=None,
    labels=None,
    new_col=None,
    is_log_transformed=False,
    apply_labels=True,
)

연속형 변수를 다양한 알고리즘으로 구간화해 명목형 파생변수를 추가한다.

지원 방법: - "natural_breaks"(기본): Jenks 자연 구간화. jenkspy 미사용 시 quantile로 대체 기본 라벨: "X-Y" 형식 (예: "18-30", "30-40") - "quantile"/"qcut"/"equal_freq": 분위수 기반 동빈도 기본 라벨: "X-Y" 형식 - "equal_width"/"uniform": 동일 간격 기본 라벨: "X-Y" 형식 - "std": 평균±표준편차를 경계로 4구간 생성 라벨: "low", "mid_low", "mid_high", "high" - "lifecourse"/"life_stage": 생애주기 5단계 라벨: "아동", "청소년", "청년", "중년", "노년" (경계: 0, 13, 19, 40, 65) - "age_decade": 10대 단위 연령대 라벨: "아동", "10대", "20대", "30대", "40대", "50대", "60대 이상" - "health_band"/"policy_band": 의료비 위험도 기반 연령대 라벨: "18-29", "30-39", "40-49", "50-64", "65+" - 커스텀 구간: bins에 경계 리스트 전달 (예: [0, 30, 50, 100])

Parameters:

Name Type Description Default
data DataFrame

입력 데이터프레임

required
field str

구간화할 연속형 변수명

required
method str

구간화 알고리즘 키워드 (기본값: "natural_breaks")

'natural_breaks'
bins int | list[float] | None
  • int: 생성할 구간 개수 (quantile, equal_width, natural_breaks에서 사용)
  • list: 경계값 리스트 (커스텀 구간화)
  • None: 기본값 사용 (quantile/equal_width는 4~5, natural_breaks는 5)
None
labels list[str] | None

구간 레이블 목록 - None: method별 기본 라벨 자동 생성 - list: 사용자 정의 라벨 (구간 개수와 일치해야 함)

None
new_col str | None

생성할 컬럼명 - None: f"{field}_bin" 사용 (예: "age_bin")

None
is_log_transformed bool

대상 컬럼이 로그 변환되어 있는지 여부 - True: 지정된 컬럼을 역변환(exp)한 후 구간화 - False: 원래 값 그대로 구간화 (기본값)

False
apply_labels bool

구간에 숫자 인덱스를 적용할지 여부 - True: 숫자 인덱스 사용 (0, 1, 2, 3, ...) (기본값) - False: 문자 라벨 적용 (예: "18~30", "아동")

True

Returns:

Name Type Description
DataFrame DataFrame

원본에 구간화된 명목형 컬럼이 추가된 데이터프레임

Examples:

from hossam import *

# 동일 간격으로 5개 구간 생성 (숫자 인덱스):
df = pd.DataFrame({'age': [20, 35, 50, 65]})
result = hs_prep.bin_continuous(df, 'age', method='equal_width', bins=5)
print(result['age_bin'])  # 0, 1, 2, ... (숫자 인덱스)

# 문자 레이블 사용:
result = hs_prep.bin_continuous(df, 'age', method='equal_width', bins=5, apply_labels=False)
print(result['age_bin'])  # 20~30, 30~40, ... (문자 레이블)

# 생애주기 기반 구간화:
result = hs_prep.bin_continuous(df, 'age', method='lifecourse')
print(result['age_bin'])  # 0, 1, 2, 3, 4 (숫자 인덱스)

# 생애주기 문자 레이블:
result = hs_prep.bin_continuous(df, 'age', method='lifecourse', apply_labels=False)
print(result['age_bin'])  # 아동, 청소년, 청년, 중년, 노년

# 의료비 위험도 기반 연령대 (health_band):
result = hs_prep.bin_continuous(df, 'age', method='health_band', apply_labels=False)
print(result['age_bin'])  # 18-29, 30-39, 40-49, 50-64, 65+

# 로그 변환된 컬럼 역변환 후 구간화:
df_log = pd.DataFrame({'charges_log': [np.log(1000), np.log(5000), np.log(50000)]})
result = hs_prep.bin_continuous(df_log, 'charges_log', method='equal_width', is_log_transformed=True)
print(result['charges_log_bin'])  # 0, 1, 2 (숫자 인덱스)
Source code in hossam/hs_prep.py
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
def bin_continuous(
    data: DataFrame,
    field: str,
    method: str = "natural_breaks",
    bins: int | list[float] | None = None,
    labels: list[str] | None = None,
    new_col: str | None = None,
    is_log_transformed: bool = False,
    apply_labels: bool = True,
) -> DataFrame:
    """연속형 변수를 다양한 알고리즘으로 구간화해 명목형 파생변수를 추가한다.

    지원 방법:
    - "natural_breaks"(기본): Jenks 자연 구간화. jenkspy 미사용 시 quantile로 대체
      기본 라벨: "X-Y" 형식 (예: "18-30", "30-40")
    - "quantile"/"qcut"/"equal_freq": 분위수 기반 동빈도
      기본 라벨: "X-Y" 형식
    - "equal_width"/"uniform": 동일 간격
      기본 라벨: "X-Y" 형식
    - "std": 평균±표준편차를 경계로 4구간 생성
      라벨: "low", "mid_low", "mid_high", "high"
    - "lifecourse"/"life_stage": 생애주기 5단계
      라벨: "아동", "청소년", "청년", "중년", "노년" (경계: 0, 13, 19, 40, 65)
    - "age_decade": 10대 단위 연령대
      라벨: "아동", "10대", "20대", "30대", "40대", "50대", "60대 이상"
    - "health_band"/"policy_band": 의료비 위험도 기반 연령대
      라벨: "18-29", "30-39", "40-49", "50-64", "65+"
    - 커스텀 구간: bins에 경계 리스트 전달 (예: [0, 30, 50, 100])

    Args:
        data (DataFrame): 입력 데이터프레임
        field (str): 구간화할 연속형 변수명
        method (str): 구간화 알고리즘 키워드 (기본값: "natural_breaks")
        bins (int|list[float]|None):
            - int: 생성할 구간 개수 (quantile, equal_width, natural_breaks에서 사용)
            - list: 경계값 리스트 (커스텀 구간화)
            - None: 기본값 사용 (quantile/equal_width는 4~5, natural_breaks는 5)
        labels (list[str]|None): 구간 레이블 목록
            - None: method별 기본 라벨 자동 생성
            - list: 사용자 정의 라벨 (구간 개수와 일치해야 함)
        new_col (str|None): 생성할 컬럼명
            - None: f"{field}_bin" 사용 (예: "age_bin")
        is_log_transformed (bool): 대상 컬럼이 로그 변환되어 있는지 여부
            - True: 지정된 컬럼을 역변환(exp)한 후 구간화
            - False: 원래 값 그대로 구간화 (기본값)
        apply_labels (bool): 구간에 숫자 인덱스를 적용할지 여부
            - True: 숫자 인덱스 사용 (0, 1, 2, 3, ...) (기본값)
            - False: 문자 라벨 적용 (예: "18~30", "아동")

    Returns:
        DataFrame: 원본에 구간화된 명목형 컬럼이 추가된 데이터프레임

    Examples:
        ```python
        from hossam import *

        # 동일 간격으로 5개 구간 생성 (숫자 인덱스):
        df = pd.DataFrame({'age': [20, 35, 50, 65]})
        result = hs_prep.bin_continuous(df, 'age', method='equal_width', bins=5)
        print(result['age_bin'])  # 0, 1, 2, ... (숫자 인덱스)

        # 문자 레이블 사용:
        result = hs_prep.bin_continuous(df, 'age', method='equal_width', bins=5, apply_labels=False)
        print(result['age_bin'])  # 20~30, 30~40, ... (문자 레이블)

        # 생애주기 기반 구간화:
        result = hs_prep.bin_continuous(df, 'age', method='lifecourse')
        print(result['age_bin'])  # 0, 1, 2, 3, 4 (숫자 인덱스)

        # 생애주기 문자 레이블:
        result = hs_prep.bin_continuous(df, 'age', method='lifecourse', apply_labels=False)
        print(result['age_bin'])  # 아동, 청소년, 청년, 중년, 노년

        # 의료비 위험도 기반 연령대 (health_band):
        result = hs_prep.bin_continuous(df, 'age', method='health_band', apply_labels=False)
        print(result['age_bin'])  # 18-29, 30-39, 40-49, 50-64, 65+

        # 로그 변환된 컬럼 역변환 후 구간화:
        df_log = pd.DataFrame({'charges_log': [np.log(1000), np.log(5000), np.log(50000)]})
        result = hs_prep.bin_continuous(df_log, 'charges_log', method='equal_width', is_log_transformed=True)
        print(result['charges_log_bin'])  # 0, 1, 2 (숫자 인덱스)
        ```
    """

    if field not in data.columns:
        return data

    df = data.copy()
    series = df[field].copy()

    # 로그 변환 역변환
    if is_log_transformed:
        series = np.exp(series)

    new_col = new_col or f"{field}_bin"
    method_key = (method or "").lower()

    def _cut(
        edges: list[float],
        default_labels: list[str] | None = None,
        right: bool = False,
        ordered: bool = True,
    ):
        nonlocal labels
        use_labels = None

        # apply_labels=True일 때 숫자 인덱스, False일 때 문자 레이블
        if apply_labels:
            # 숫자 인덱스 생성
            numeric_labels = list(range(len(edges) - 1))
            use_labels = numeric_labels
        else:
            # 문자 레이블 적용
            use_labels = labels if labels is not None else default_labels

        df[new_col] = pd.cut(
            series,
            bins=edges,
            labels=use_labels,
            right=right,
            include_lowest=True,
            ordered=False,  # 레이블이 있으므로 ordered=False 사용
        )
        df[new_col] = df[new_col].astype("category")

    # 생애주기 구분
    if method_key in {"lifecourse", "life_stage", "lifecycle", "life"}:
        edges = [0, 13, 19, 40, 65, np.inf]
        # 나이 구간을 함께 표기한 라벨 (apply_labels=False에서 사용)
        default_labels = [
            "아동(0~12)",
            "청소년(13~18)",
            "청년(19~39)",
            "중년(40~64)",
            "노년(65+)",
        ]
        _cut(edges, default_labels, right=False)
        return df

    # 연령대(10단위)
    if method_key in {"age_decade", "age10", "decade"}:
        edges = [0, 13, 20, 30, 40, 50, 60, np.inf]
        default_labels = ["아동", "10대", "20대", "30대", "40대", "50대", "60대 이상"]
        _cut(edges, default_labels, right=False)
        return df

    # 건강/제도 기준 (의료비 위험군 분류 기준)
    if method_key in {"health_band", "policy_band", "health"}:
        # 연령 데이터 최소값(예: 18세)과 레이블을 일치시킴
        edges = [0, 19, 30, 40, 50, 65, np.inf]
        default_labels = ["0~18", "19-29", "30-39", "40-49", "50-64", "65+"]
        _cut(edges, default_labels, right=False)
        return df

    # 표준편차 기반
    if method_key == "std":
        mu = series.mean()
        sd = series.std(ddof=0)
        edges = [-np.inf, mu - sd, mu, mu + sd, np.inf]
        default_labels = ["low", "mid_low", "mid_high", "high"]
        _cut(edges, default_labels, right=True)
        return df

    # 동일 간격
    if method_key in {"equal_width", "uniform"}:
        k = bins if isinstance(bins, int) and bins > 0 else 5
        _, edges = pd.cut(series, bins=k, include_lowest=True, retbins=True)

        # apply_labels=True: 숫자 인덱스 / False: 문자 레이블
        if apply_labels:
            # 숫자 인덱스 사용 (0, 1, 2, ...)
            numeric_labels = list(range(len(edges) - 1))
            df[new_col] = pd.cut(series, bins=edges, labels=numeric_labels, include_lowest=True, ordered=False)  # type: ignore
        else:
            # 문자 레이블 적용
            if labels is None:
                auto_labels = []
                for i in range(len(edges) - 1):
                    left = f"{edges[i]:.2f}" if edges[i] != -np.inf else "-∞"
                    right = f"{edges[i+1]:.2f}" if edges[i + 1] != np.inf else "∞"
                    # 정수값인 경우 소수점 제거
                    try:
                        left = (
                            str(int(float(left)))
                            if float(left) == int(float(left))
                            else left
                        )
                        right = (
                            str(int(float(right)))
                            if float(right) == int(float(right))
                            else right
                        )
                    except:
                        pass
                    auto_labels.append(f"{left}~{right}")
                df[new_col] = pd.cut(series, bins=edges, labels=auto_labels, include_lowest=True, ordered=False)  # type: ignore
            else:
                df[new_col] = pd.cut(series, bins=edges, labels=labels, include_lowest=True, ordered=False)  # type: ignore

        df[new_col] = df[new_col].astype("category")
        return df

    # 분위수 기반 동빈도
    if method_key in {"quantile", "qcut", "equal_freq"}:
        k = bins if isinstance(bins, int) and bins > 0 else 4
        # apply_labels=False일 때 기본 레이블을 사분위수 위치(Q1~)로 설정
        default_q_labels = (
            labels if labels is not None else [f"Q{i+1}" for i in range(k)]
        )
        try:
            if apply_labels:
                # 숫자 인덱스 사용
                numeric_labels = list(range(k))
                df[new_col] = pd.qcut(
                    series, q=k, labels=numeric_labels, duplicates="drop"
                )
            else:
                # 사분위수 위치 기반 문자 레이블(Q1, Q2, ...)
                df[new_col] = pd.qcut(
                    series, q=k, labels=default_q_labels, duplicates="drop"
                )
        except ValueError:
            _, edges = pd.cut(series, bins=k, include_lowest=True, retbins=True)
            # apply_labels=True: 숫자 인덱스 / False: 문자 레이블
            n_bins = len(edges) - 1
            if apply_labels:
                numeric_labels = list(range(n_bins))
                df[new_col] = pd.cut(series, bins=edges, labels=numeric_labels, include_lowest=True, ordered=False)  # type: ignore
            else:
                if labels is None:
                    position_labels = [f"Q{i+1}" for i in range(n_bins)]
                    df[new_col] = pd.cut(series, bins=edges, labels=position_labels, include_lowest=True, ordered=False)  # type: ignore
                else:
                    df[new_col] = pd.cut(series, bins=edges, labels=labels, include_lowest=True, ordered=False)  # type: ignore
        df[new_col] = df[new_col].astype("category")
        return df

    # 자연 구간화 (Jenks) - 의존성 없으면 분위수로 폴백
    if method_key in {"natural_breaks", "natural", "jenks"}:
        k = bins if isinstance(bins, int) and bins > 1 else 5
        series_nonnull = series.dropna()  # type: ignore
        k = min(k, max(2, series_nonnull.nunique()))
        edges = None
        try:
            edges = jenkspy.jenks_breaks(series_nonnull.to_list(), nb_class=k)  # type: ignore
            edges[0] = -np.inf
            edges[-1] = np.inf
        except Exception:
            try:
                use_labels = labels if apply_labels else None
                df[new_col] = pd.qcut(series, q=k, labels=use_labels, duplicates="drop")
                df[new_col] = df[new_col].astype("category")
                return df
            except Exception:
                edges = None

        if edges:
            # apply_labels=True: 숫자 인덱스 / False: 문자 레이블
            if apply_labels:
                # 숫자 인덱스 사용
                numeric_labels = list(range(len(edges) - 1))
                df[new_col] = pd.cut(
                    series,
                    bins=edges,
                    labels=numeric_labels,
                    include_lowest=True,
                    ordered=False,
                )
                df[new_col] = df[new_col].astype("category")
            else:
                if labels is None:
                    auto_labels = []
                    for i in range(len(edges) - 1):
                        left = f"{edges[i]:.2f}" if edges[i] != -np.inf else "-∞"
                        right = f"{edges[i+1]:.2f}" if edges[i + 1] != np.inf else "∞"
                        # 정수값인 경우 소수점 제거
                        try:
                            left = (
                                str(int(float(left)))
                                if float(left) == int(float(left))
                                else left
                            )
                            right = (
                                str(int(float(right)))
                                if float(right) == int(float(right))
                                else right
                            )
                        except:
                            pass
                        auto_labels.append(f"{left}~{right}")
                    _cut(edges, auto_labels, right=True, ordered=False)
                else:
                    _cut(edges, labels, right=True, ordered=False)
        else:
            _, cut_edges = pd.cut(series, bins=k, include_lowest=True, retbins=True)
            if apply_labels:
                # 숫자 인덱스 사용
                numeric_labels = list(range(len(cut_edges) - 1))
                df[new_col] = pd.cut(series, bins=cut_edges, labels=numeric_labels, include_lowest=True, ordered=False)  # type: ignore
            else:
                if labels is None:
                    auto_labels = []
                    for i in range(len(cut_edges) - 1):
                        left = (
                            f"{cut_edges[i]:.2f}" if cut_edges[i] != -np.inf else "-∞"
                        )
                        right = (
                            f"{cut_edges[i+1]:.2f}"
                            if cut_edges[i + 1] != np.inf
                            else "∞"
                        )
                        # 정수값인 경우 소수점 제거
                        try:
                            left = (
                                str(int(float(left)))
                                if float(left) == int(float(left))
                                else left
                            )
                            right = (
                                str(int(float(right)))
                                if float(right) == int(float(right))
                                else right
                            )
                        except:
                            pass
                        auto_labels.append(f"{left}~{right}")
                    df[new_col] = pd.cut(series, bins=cut_edges, labels=auto_labels, include_lowest=True, ordered=False)  # type: ignore
                else:
                    df[new_col] = pd.cut(series, bins=cut_edges, labels=labels, include_lowest=True, ordered=False)  # type: ignore
            df[new_col] = df[new_col].astype("category")
        return df

    # 커스텀 경계
    if isinstance(bins, list) and len(bins) >= 2:
        edges = sorted(bins)
        _cut(edges, labels, right=False)
        return df

    # 기본 폴백: 분위수 4구간
    df[new_col] = pd.qcut(series, q=4, labels=labels, duplicates="drop")
    df[new_col] = df[new_col].astype("category")
    return df

log_transform

log_transform(data, *fields, columns=None)

수치형 변수에 대해 로그 변환을 수행한다.

자연로그(ln)를 사용하여 변환하며, 0 또는 음수 값이 있을 경우 최소값을 기준으로 보정(shift)을 적용한다.

Parameters:

Name Type Description Default
data DataFrame

변환할 데이터프레임.

required
*fields str

변환할 컬럼명 목록. 지정하지 않으면 모든 수치형 컬럼을 처리.

()
columns list

변환할 컬럼명 목록. fields와 중복 사용 불가.

None

Returns:

Name Type Description
DataFrame DataFrame

로그 변환된 데이터프레임.

Examples:

from hossam import *
from pandas import DataFrame
df = DataFrame({'x': [1, 10, 100], 'y': [2, 20, 200], 'z': ['a', 'b', 'c']})

# 전체 수치형 컬럼에 대한 로그 변환:
result = hs_prep.log_transform(df)
print(result)

# 특정 컬럼만 변환:
result = hs_prep.log_transform(df, 'x', 'y')
print(result)
Notes
  • 수치형이 아닌 컬럼은 자동으로 제외됩니다.
  • 0 또는 음수 값이 있는 경우 자동으로 보정됩니다.
  • 변환 공식: log(x + shift), 여기서 shift = 1 - min(x) (min(x) <= 0인 경우)
Source code in hossam/hs_prep.py
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
def log_transform(
    data: DataFrame, *fields: str, columns: list | None = None
) -> DataFrame:
    """수치형 변수에 대해 로그 변환을 수행한다.

    자연로그(ln)를 사용하여 변환하며, 0 또는 음수 값이 있을 경우
    최소값을 기준으로 보정(shift)을 적용한다.

    Args:
        data (DataFrame): 변환할 데이터프레임.
        *fields (str): 변환할 컬럼명 목록. 지정하지 않으면 모든 수치형 컬럼을 처리.
        columns (list, optional): 변환할 컬럼명 목록. fields와 중복 사용 불가.

    Returns:
        DataFrame: 로그 변환된 데이터프레임.

    Examples:
        ```python
        from hossam import *
        from pandas import DataFrame
        df = DataFrame({'x': [1, 10, 100], 'y': [2, 20, 200], 'z': ['a', 'b', 'c']})

        # 전체 수치형 컬럼에 대한 로그 변환:
        result = hs_prep.log_transform(df)
        print(result)

        # 특정 컬럼만 변환:
        result = hs_prep.log_transform(df, 'x', 'y')
        print(result)
        ```

    Notes:
        - 수치형이 아닌 컬럼은 자동으로 제외됩니다.
        - 0 또는 음수 값이 있는 경우 자동으로 보정됩니다.
        - 변환 공식: log(x + shift), 여기서 shift = 1 - min(x) (min(x) <= 0인 경우)
    """
    df = data.copy()

    if columns is not None:
        if fields:
            raise ValueError("fields와 columns 인자는 중복 사용할 수 없습니다.")
        fields = columns  # type: ignore

    # 대상 컬럼 결정
    if not fields:
        # 모든 수치형 컬럼 선택
        target_cols = df.select_dtypes(include=[np.number]).columns.tolist()
    else:
        target_cols = list(fields)

    # 각 컬럼에 대해 로그 변환 수행
    for col in target_cols:
        # 컬럼이 존재하고 수치형인지 확인
        if col not in df.columns:
            continue

        if df[col].dtype not in [
            "int",
            "int32",
            "int64",
            "float",
            "float32",
            "float64",
        ]:
            continue

        # 최소값 확인
        min_val = df[col].min()

        # 0 또는 음수가 있으면 shift 적용
        if min_val <= 0:
            shift = 1 - min_val
            df[col] = np.log(df[col] + shift)
        else:
            df[col] = np.log(df[col])

    return df

add_interaction

add_interaction(data, pairs=None)

데이터프레임에 상호작용(interaction) 항을 추가한다.

수치형 및 명목형 변수 간의 상호작용 항을 생성하여 데이터프레임에 추가한다. - 수치형 * 수치형: 두 변수의 곱셈 (col1col2) - 수치형 * 명목형: 명목형의 각 카테고리별 수치형 변수 생성 (col1col2_category) - 명목형 * 명목형: 두 명목형을 결합한 새 명목형 변수 생성 (col1_col2)

Parameters:

Name Type Description Default
data DataFrame

원본 데이터프레임.

required
pairs list[tuple[str, str]]

직접 지정할 교호작용 쌍의 리스트. 예: [("age", "gender"), ("color", "cut")] None이면 모든 수치형 컬럼의 2-way 상호작용을 생성.

None

Returns:

Name Type Description
DataFrame DataFrame

상호작용 항이 추가된 새 데이터프레임.

Examples:

from hossam import *
from padas import DataFrame

# 수치형 변수들의 상호작용:
df = DataFrame({'x1': [1, 2, 3], 'x2': [4, 5, 6]})
result = hs_prep.add_interaction(df)
print(result.columns)  # x1, x2, x1*x2

# 수치형과 명목형의 상호작용:
df = DataFrame({'age': [20, 30, 40], 'gender': ['M', 'F', 'M']})
result = hs_prep.add_interaction(df, pairs=[('age', 'gender')])
print(result.columns)  # age, gender, age*gender_M, age*gender_F

# 명목형끼리의 상호작용:
df = DataFrame({'color': ['R', 'G', 'B'], 'cut': ['A', 'B', 'A']})
result = hs_prep.add_interaction(df, pairs=[('color', 'cut')])
print(result.columns)  # color, cut, color_cut
Source code in hossam/hs_prep.py
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
def add_interaction(
    data: DataFrame, pairs: list[tuple[str, str]] | None = None
) -> DataFrame:
    """데이터프레임에 상호작용(interaction) 항을 추가한다.

    수치형 및 명목형 변수 간의 상호작용 항을 생성하여 데이터프레임에 추가한다.
    - `수치형 * 수치형`: 두 변수의 곱셈 (col1*col2)
    - `수치형 * 명목형`: 명목형의 각 카테고리별 수치형 변수 생성 (col1*col2_category)
    - `명목형 * 명목형`: 두 명목형을 결합한 새 명목형 변수 생성 (col1_col2)

    Args:
        data (DataFrame): 원본 데이터프레임.
        pairs (list[tuple[str, str]], optional): 직접 지정할 교호작용 쌍의 리스트.
            예: [("age", "gender"), ("color", "cut")]
            None이면 모든 수치형 컬럼의 2-way 상호작용을 생성.

    Returns:
        DataFrame: 상호작용 항이 추가된 새 데이터프레임.

    Examples:
        ```python
        from hossam import *
        from padas import DataFrame

        # 수치형 변수들의 상호작용:
        df = DataFrame({'x1': [1, 2, 3], 'x2': [4, 5, 6]})
        result = hs_prep.add_interaction(df)
        print(result.columns)  # x1, x2, x1*x2

        # 수치형과 명목형의 상호작용:
        df = DataFrame({'age': [20, 30, 40], 'gender': ['M', 'F', 'M']})
        result = hs_prep.add_interaction(df, pairs=[('age', 'gender')])
        print(result.columns)  # age, gender, age*gender_M, age*gender_F

        # 명목형끼리의 상호작용:
        df = DataFrame({'color': ['R', 'G', 'B'], 'cut': ['A', 'B', 'A']})
        result = hs_prep.add_interaction(df, pairs=[('color', 'cut')])
        print(result.columns)  # color, cut, color_cut
        ```
    """
    df = data.copy()

    # pairs가 제공되면 그것을 사용, 아니면 모든 수치형 컬럼의 2-way 상호작용 생성
    if pairs is not None:
        cols_to_interact = [
            (col1, col2)
            for col1, col2 in pairs
            if col1 in df.columns and col2 in df.columns
        ]
    else:
        numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
        cols_to_interact = list(combinations(numeric_cols, 2))

    # 상호작용 항 생성
    for col1, col2 in cols_to_interact:
        is_col1_numeric = pd.api.types.is_numeric_dtype(df[col1])
        is_col2_numeric = pd.api.types.is_numeric_dtype(df[col2])

        # Case 1: 둘 다 수치형 -> 곱셈
        if is_col1_numeric and is_col2_numeric:
            interaction_col_name = f"{col1}*{col2}"
            df[interaction_col_name] = df[col1] * df[col2]

        # Case 2: 하나는 수치형, 하나는 명목형 -> 명목형의 각 카테고리별로 수치형 변수 생성
        elif is_col1_numeric and not is_col2_numeric:
            # col1은 수치형, col2는 명목형
            categories = df[col2].unique()
            for cat in categories:
                # 결측치 처리
                cat_str = str(cat) if pd.notna(cat) else "nan"
                interaction_col_name = f"{col1}*{col2}_{cat_str}"
                # 해당 카테고리인 경우 수치형 값, 아니면 0
                df[interaction_col_name] = df[col1] * (df[col2] == cat).astype(int)

        elif not is_col1_numeric and is_col2_numeric:
            # col1은 명목형, col2는 수치형
            categories = df[col1].unique()
            for cat in categories:
                cat_str = str(cat) if pd.notna(cat) else "nan"
                interaction_col_name = f"{col2}*{col1}_{cat_str}"
                df[interaction_col_name] = df[col2] * (df[col1] == cat).astype(int)

        # Case 3: 둘 다 명목형 -> 두 변수를 결합한 새로운 명목형 변수
        else:
            interaction_col_name = f"{col1}_{col2}"
            # 문자열로 변환 후 결합 (결측치 처리 포함)
            df[interaction_col_name] = (
                df[col1].astype(str).fillna("nan")
                + "_"
                + df[col2].astype(str).fillna("nan")
            )

    return df

pca

pca(
    data,
    n_components=0.8,
    yname=None,
    random_state=RANDOM_STATE,
    plot=False,
    fields=None,
    palette=None,
    width=config.width,
    height=config.height,
    linewidth=config.line_width,
    save_path=None,
    callback=None,
)

주성분 분석(PCA)을 수행하고, 주성분 데이터프레임과 설명된 분산 비율 데이터프레임을 반환합니다.

Parameters:

Name Type Description Default
data DataFrame

입력 데이터프레임.

required
n_components int | float | str

주성분의 수 또는 설명할 분산 비율. (기본값 0.8)

0.8
yname str | None

종속 변수 이름. 종속변수 이름이 주어진 경우 해당 컬럼은 제외하고 처리합니다. (기본값 None)

None
random_state int

랜덤 시드. (기본값 RANDOM_STATE)

RANDOM_STATE
plot bool

주성분 설명력 그래프를 출력할지 여부. (기본값 False)

False
fields list | tuple | list[list] | tuple[list] | list[tuple] | tuple[tuple] | None

산점도에 표시할 변수 목록.

None
palette str | None

팔레트 이름.

None
width int

캔버스 가로 픽셀.

width
height int

캔버스 세로 픽셀.

height
linewidth float

선 굵기.

line_width
save_path str | None

저장 경로.

None
callback Callable | None

Axes 후처리 콜백.

None

Returns:

Type Description
tuple[PCA, DataFrame, DataFrame]

tuple[DataFrame, DataFrame]: 주성분 데이터프레임과 설명된 분산 비율 데이터프레임.

Source code in hossam/hs_prep.py
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
def pca(
    data: DataFrame,
    n_components: int | float | str = 0.8,
    yname: str | None = None,
    random_state: int = RANDOM_STATE,
    plot: bool = False,
    fields: list | tuple | list[list] | tuple[list] | list[tuple] | tuple[tuple] | None = None,
    palette: str | None = None,
    width: int = config.width,
    height: int = config.height,
    linewidth: float = config.line_width,
    save_path: str | None = None,
    callback: Callable | None = None,
) -> tuple[PCA, DataFrame, DataFrame]:
    """
    주성분 분석(PCA)을 수행하고, 주성분 데이터프레임과 설명된 분산 비율 데이터프레임을 반환합니다.

    Args:
        data (DataFrame): 입력 데이터프레임.
        n_components (int|float|str): 주성분의 수 또는 설명할 분산 비율. (기본값 0.8)
        yname (str|None): 종속 변수 이름. 종속변수 이름이 주어진 경우 해당 컬럼은 제외하고 처리합니다. (기본값 None)
        random_state (int): 랜덤 시드. (기본값 RANDOM_STATE)
        plot (bool): 주성분 설명력 그래프를 출력할지 여부. (기본값 False)
        fields (list|tuple|list[list]|tuple[list]|list[tuple]|tuple[tuple]|None): 산점도에 표시할 변수 목록.
        palette (str|None): 팔레트 이름.
        width (int): 캔버스 가로 픽셀.
        height (int): 캔버스 세로 픽셀.
        linewidth (float): 선 굵기.
        save_path (str|None): 저장 경로.
        callback (Callable|None): Axes 후처리 콜백.

    Returns:
        tuple[DataFrame, DataFrame]: 주성분 데이터프레임과 설명된 분산 비율 데이터프레임.
    """

    df = data.copy()
    yfield = None

    if yname is not None:
        if yname not in df.columns:
            raise ValueError(f"yname '{yname}'이(가) 데이터프레임에 존재하지 않습니다.")

        yfield = df[[yname]].copy()
        df = df.drop(columns=[yname])

    estimator = PCA(n_components=n_components, random_state=random_state)
    pca = estimator.fit_transform(df)

    n = pca.shape[1]
    cols = [f"PC{i+1}" for i in range(n)]

    pca_df = DataFrame(pca, columns=cols)

    if yfield is not None:
        pca_df[yname] = yfield

    # 주성분 로딩 행렬 구성
    loadings = pd.DataFrame(estimator.components_, columns=df.columns.tolist(), index=cols)
    loadings.T.index.name = "Features"

    # 주성분별 설명력과 누적합을 데이터프레임에 추가 구성
    loadings[f"[Explained Variance]"] = estimator.explained_variance_ratio_
    loadings[f"[Cumulative Variance]"] = estimator.explained_variance_ratio_.cumsum()
    loadings = loadings.T

    if plot:
        pca_plot(
            estimator=estimator,
            data = data,
            yname = yname,
            fields = fields,
            hue = yname,
            palette = palette,
            width = width,
            height = height,
            linewidth = linewidth,
            save_path = save_path,
            callback = callback,
        )

    return estimator, pca_df, loadings