diff --git "a/ADP_\355\225\265\354\213\254\354\240\225\353\246\254.md" "b/ADP_\355\225\265\354\213\254\354\240\225\353\246\254.md" new file mode 100644 index 0000000..c78e1bc --- /dev/null +++ "b/ADP_\355\225\265\354\213\254\354\240\225\353\246\254.md" @@ -0,0 +1,1456 @@ +# ADP 데이터분석 핵심 정리 + +--- + +## 1. 환경 설정 + +```python +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt +import seaborn as sns +%matplotlib inline + +# 출력 옵션 +from IPython.core.interactiveshell import InteractiveShell +InteractiveShell.ast_node_interactivity = "all" +pd.options.display.max_columns = None + +# 한글 폰트 (Linux) +from matplotlib import font_manager, rc +font_name = font_manager.FontProperties(fname="malgun.ttf").get_name() +rc('font', family=font_name) +plt.rcParams['axes.unicode_minus'] = False + +# 경고 무시 +import warnings +warnings.filterwarnings('ignore') +``` + +--- + +## 2. 데이터 입출력 + +### CSV / TXT +```python +df = pd.read_csv('file.csv', encoding='cp949') +# 주요 파라미터 +# sep=',' 구분자 +# header=0 컬럼명 행 번호 +# names=[...] 컬럼명 직접 지정 +# engine='python' 한글 경로 오류 시 + +df.to_csv('out.csv', index=False, encoding='cp949') +``` + +### Excel +```python +df = pd.read_excel('file.xlsx', sheet_name='Sheet1') +df.to_excel('out.xlsx', sheet_name='Sheet1', index=False) +``` + +### 일반 텍스트 직접 파싱 (구분자가 특이한 경우) +```python +with open('file.txt', 'r', encoding='cp949') as f: + raw = f.read() + +rows = [] +for line in raw.split('\n'): # 줄바꿈으로 행 분리 + rows.append(line.split(',')) # 콤마로 열 분리 + +df = pd.DataFrame(rows[1:], columns=rows[0]) +``` + +### Pickle +```python +import pickle +# 저장 +with open('data.pkl', 'wb') as f: + pickle.dump(obj, f) +# 불러오기 +with open('data.pkl', 'rb') as f: + obj = pickle.load(f) +``` + +### 날짜 형식 문자열 포맷 +| 코드 | 의미 | 예시 | +|------|------|------| +| `%Y` | 4자리 연도 | 2024 | +| `%m` | 2자리 월 | 03 | +| `%d` | 2자리 일 | 15 | +| `%H` | 24시간 시 | 14 | +| `%M` | 분 | 30 | +| `%S` | 초 | 05 | + +```python +# 문자열 → datetime +df['date'] = pd.to_datetime(df['date']) +df['date'] = pd.to_datetime(df['date'], format='%d.%m.%Y') + +# timestamp → datetime +from datetime import datetime +datetime.fromtimestamp(1599989445) + +# 연·월·일 추출 +df['year'] = df['date'].dt.year +df['month'] = df['date'].dt.month +df['day'] = df['date'].dt.day +df['weekday'] = df['date'].dt.day_name() +``` + +--- + +## 3. NumPy 핵심 + +```python +# 배열 생성 +a = np.array([1, 2, 3]) +np.zeros((3, 3)) # 0으로 채운 3×3 +np.ones((3, 4)) # 1로 채운 3×4 +np.eye(3) # 3×3 단위행렬 +np.arange(5) # [0,1,2,3,4] +np.random.random((2, 2)) # 0~1 랜덤 + +# reshape: -1은 자동 계산 +a = np.arange(6).reshape(2, 3) # 2행 3열 +a = np.arange(6).reshape(-1, 2) # 열 2개, 행 자동 + +# 수학 함수 +np.abs(a) # 절댓값 +np.sqrt(a) # 제곱근 +np.log(a) # 자연로그 +np.log1p(a) # log(1+a) — 0 포함 데이터에 안전 +np.expm1(a) # log1p의 역함수 +``` + +--- + +## 4. Pandas 핵심 + +### DataFrame 생성 & 기본 확인 +```python +df = pd.DataFrame({'name': ['Alice', 'Bob'], 'age': [25, 30]}) + +df.shape # (행, 열) +df.dtypes # 컬럼별 타입 +df.info() # 결측치 포함 요약 +df.describe() # 수치형 기술통계 +df.describe(include='object') # 범주형 기술통계 +df['col'].unique() # 고유값 +df['col'].value_counts() # 빈도 +``` + +### 조회 +```python +df['col'] # 열 선택 +df[['col1', 'col2']] # 복수 열 +df.iloc[0:3, 1:4] # 행·열 번호로 +df.loc[df['age'] > 25, 'name'] # 조건 + 열 이름 +df.query('age > 25 and name == "Alice"') +``` + +### 정렬 & 인덱스 +```python +df.sort_values(by='age', ascending=False) +df.sort_values(by=['col1', 'col2'], ascending=[True, False]) +df.set_index('date') # 컬럼 → 인덱스 +df.reset_index() # 인덱스 → 컬럼 +``` + +### 컬럼 조작 +```python +df.rename(columns={'old': 'new'}) +df.columns = ['a', 'b', 'c'] # 전체 교체 +df['new_col'] = df['a'] + df['b'] # 파생변수 +df.drop('col', axis=1, inplace=True) # 열 삭제 +``` + +### 타입 변환 +```python +df['col'].astype('int') +df['col'].astype('float') +df['col'].astype('str') +pd.to_numeric(df['col'], errors='coerce') # 변환 불가 → NaN +pd.Categorical(df['col']) # 범주형 +pd.to_datetime(df['col']) # 날짜형 +``` + +### apply / map +```python +# map: Series의 값 하나하나에 함수 적용 +df['B'] = df['A'].map(lambda x: x * 2) + +# apply: 행 단위로 여러 컬럼 접근 +df['C'] = df.apply(lambda row: row['A'] + row['B'], axis=1) +``` + +### 문자열 처리 +```python +df['col'].str.contains('keyword') # 포함 여부 +df['col'].str.replace('old', 'new') +df['col'].str.strip() # 앞뒤 공백 제거 +df['col'].str.split(',') +``` + +### GroupBy & 집계 +```python +df.groupby('key')['value'].mean() +df.groupby('key').agg({'col1': 'sum', 'col2': 'mean'}) +df.groupby('key').agg(['min', 'max', 'mean']) + +# 피벗테이블 +df.pivot_table(index='sex', columns='class', values='fare', aggfunc='mean') +``` + +### 병합 +```python +# 열 기준 합치기 (SQL JOIN) +pd.merge(df1, df2, on='key', how='left') # left join +pd.merge(df1, df2, on='key', how='outer') # full outer join + +# 행/열 단순 이어붙이기 +pd.concat([df1, df2], axis=0) # 행 방향 (아래로) +pd.concat([df1, df2], axis=1) # 열 방향 (옆으로) +``` + +### MultiIndex +```python +# 다중 컬럼 인덱스를 단일로 변환 +lv0 = df.columns.get_level_values(0) +lv1 = df.columns.get_level_values(1) +df.columns = lv0 + '_' + lv1 + +# 다중 행 인덱스 → 단일 +df.reset_index() +``` + +### Dummy 변수 (원-핫 인코딩) +```python +dummies = pd.get_dummies(df['category']) +df = pd.concat([df.drop('category', axis=1), dummies], axis=1) +``` + +### Rolling & Shift (시계열용) +```python +df['ma3'] = df['value'].rolling(3).mean() # 3기간 이동평균 +df['lag1'] = df['value'].shift(1) # 1기간 lag +``` +--- + +## 5. 데이터 전처리 + +### 결측치 +```python +df.isnull().sum() # 컬럼별 결측 수 +df.dropna() # 결측 행 제거 +df.dropna(how='all') # 모든 값이 결측인 행만 제거 +df.dropna(subset=['col']) # 특정 컬럼 기준 + +df.fillna(0) # 상수로 대체 +df.fillna(df['col'].mean()) # 평균으로 대체 +df.fillna(method='ffill') # 앞 값으로 채우기 +df.fillna(method='bfill') # 뒤 값으로 채우기 +``` + +### 중복 제거 +```python +df.duplicated().sum() # 중복 행 개수 +df.drop_duplicates() # 중복 행 제거 +df.drop_duplicates(subset=['col'], keep='last') # 기준 컬럼 지정 +``` + +### 이상치 처리 (IQR 방식) +> IQR(사분위범위) = Q3 − Q1. 이상치 기준: Q1 − 1.5×IQR 미만 또는 Q3 + 1.5×IQR 초과. + +```python +Q1 = df['col'].quantile(0.25) +Q3 = df['col'].quantile(0.75) +IQR = Q3 - Q1 + +lower = max(Q1 - 1.5 * IQR, df['col'].min()) +upper = min(Q3 + 1.5 * IQR, df['col'].max()) + +# 이상치 확인 +df[(df['col'] < lower) | (df['col'] > upper)] + +# 윈저라이징(Winsorizing): 이상치를 경계값으로 대체 +df.loc[df['col'] < lower, 'col'] = lower +df.loc[df['col'] > upper, 'col'] = upper +``` + +### 기초통계량 종합 함수 +```python +def describe_num(data): + """연속형 변수에 대한 기술통계 + 결측, 분산, 왜도, 첨도""" + da = data.describe(percentiles=[.01,.05,.1,.25,.5,.75,.9,.95,.99]).T + da['missing'] = data.isnull().sum() + da['variance'] = data.var() + da['skewness'] = data.skew() # 양수: 오른쪽 꼬리, 음수: 왼쪽 꼬리 + da['kurtosis'] = data.kurtosis() # 0보다 클수록 뾰족한 분포 + return da.round(2) +``` + +### 범주형 변수 빈도표 +```python +def freq_table(df, col): + cnt = df[col].value_counts(dropna=False).rename('count') + ratio = df[col].value_counts(dropna=False, normalize=True).rename('ratio') + return pd.concat([cnt, ratio], axis=1) +``` + +--- + +## 6. EDA & 시각화 + +### 분포 확인 +```python +# 히스토그램 +df['col'].hist(bins=20) + +# 박스플롯 — 이상치, 분포 동시에 확인 +sns.boxplot(x='category', y='value', data=df) + +# KDE (밀도 추정) +sns.kdeplot(df['col']) + +# 히스토그램 + KDE +sns.histplot(df['col'], kde=True) +``` + +### 관계 확인 +```python +# 산점도 +sns.scatterplot(x='col1', y='col2', hue='category', data=df) + +# 상관 히트맵 +sns.heatmap(df.corr(), annot=True, fmt='.2f', cmap='Blues') + +# 산점도 행렬 (변수 4개 이하 권장) +sns.pairplot(df, hue='target') +``` + +### 범주형 변수 +```python +# 막대 그래프 (빈도) +sns.countplot(x='category', hue='target', data=df) + +# 막대 그래프 (평균값) +sns.barplot(x='category', y='value', data=df) + +# 파이 차트 +df['category'].value_counts().plot(kind='pie', autopct='%.1f%%') +``` + +### 그룹별 비교 +```python +# 그룹별 평균 막대 그래프 +df.groupby('group')['value'].mean().plot(kind='bar') + +# 박스플롯으로 그룹 비교 +df.boxplot(column='value', by='group') +``` + +### 시각화 저장 +```python +fig, ax = plt.subplots(figsize=(10, 6)) +# ... 그래프 코드 ... +fig.savefig('output.png', dpi=150, bbox_inches='tight') +plt.show() +``` + +### 변수 선택 기준 (EDA 결과 활용) +- **연속형**: 데이터 발생 구간의 90% 이내 범위를 사용하는 변수 채택 +- **범주형**: 특정 클래스에 95% 이상 집중된 변수는 정보량이 낮아 제거 검토 +- **상관계수 ≥ 0.7** 인 변수쌍은 다중공선성 위험 → VIF로 추가 검증 +--- + +## 7. 통계 분석 + +### 7.1 가설 검정 기본 개념 +- **귀무가설(H₀)**: 차이가 없다 / 독립이다 +- **대립가설(H₁)**: 차이가 있다 / 관련이 있다 +- **p-value < 0.05** → 귀무가설 기각 (통계적으로 유의) + +--- + +### 7.2 정규성 검정 +> 가설: 정규분포를 따른다(H₀) vs 따르지 않는다(H₁) +> **샘플 수 ≤ 5000**: Shapiro-Wilk 권장 + +```python +from scipy import stats + +stat, p = stats.shapiro(df['col']) +# p < 0.05 → 정규성 없음 +``` + +--- + +### 7.3 등분산 검정 +> 두 집단의 분산이 같은지 검정. t-test 전에 확인. + +```python +stat, p = stats.levene(group1, group2) +# p < 0.05 → 등분산 아님 → t-test에서 equal_var=False +``` + +--- + +### 7.4 평균 비교 검정 (t-test, ANOVA) + +| 상황 | 검정 방법 | +|------|----------| +| 1표본 평균 vs 기준값 | One-sample t-test | +| 2 독립 집단 평균 비교 | Two-sample t-test | +| 동일 집단 전·후 비교 | Paired t-test | +| 3집단 이상 평균 비교 | ANOVA (F-test) | + +```python +# 1표본 t-test: 표본 평균이 mu=5인가? +stats.ttest_1samp(sample, popmean=5) + +# 2표본 t-test: A, B 평균이 같은가? +stats.ttest_ind(A, B, equal_var=False) # 등분산 모르면 False + +# 대응 t-test: before, after 차이가 없는가? +stats.ttest_rel(before, after) + +# ANOVA: A, B, C 중 하나라도 다른가? +stats.f_oneway(A, B, C) +# ANOVA는 '어떤 집단이 다른지'는 알려주지 않음 → 사후검정 필요 (Tukey HSD 등) +``` + +--- + +### 7.5 범주형 독립성 검정 + +**카이제곱 검정**: 두 범주형 변수가 독립인가? +> 예: 성별과 생존 여부가 관련 있는가? + +```python +contingency = pd.crosstab(df['sex'], df['survived']) +chi2, p, dof, expected = stats.chi2_contingency(contingency) +# p < 0.05 → 두 변수 간 연관성 있음 +``` + +**피셔 정확 검정**: 2×2 교차표에서 기대빈도가 5 미만인 셀이 20% 초과할 때 +```python +oddsratio, p = stats.fisher_exact([[8, 2], [1, 5]]) +``` + +**맥네마 검정**: 동일 대상의 전·후 비교 (2×2 교차표, paired) +```python +from statsmodels.stats.contingency_tables import mcnemar +result = mcnemar(np.array([[40, 6], [2, 11]]), exact=True) +# exact=True: 셀2+셀3 < 25 / exact=False: ≥ 25 +``` + +--- + +### 7.6 상관 분석 +> 두 연속형 변수 사이의 선형 관계 강도를 측정. + +| r 범위 | 해석 | +|--------|------| +| 0.1 ~ 0.3 | 약한 상관 | +| 0.3 ~ 0.7 | 뚜렷한 상관 | +| 0.7 ~ 1.0 | 강한 상관 | + +```python +# 피어슨 상관계수 +r, p = stats.pearsonr(df['x'], df['y']) + +# 전체 상관행렬 +df[num_cols].corr() + +# 시각화 +sns.heatmap(df.corr(), annot=True, fmt='.2f', cmap='Blues') +``` + +--- + +### 7.7 다중공선성 (VIF) +> 회귀 분석에서 독립변수 간 높은 상관관계가 있을 때 발생. +> VIF > 10 이면 제거 검토 (변수 하나씩 제거하며 재확인). + +```python +from statsmodels.stats.outliers_influence import variance_inflation_factor +from patsy import dmatrices + +y, X = dmatrices('target ~ col1 + col2 + col3', data=df, return_type='dataframe') + +vif = pd.DataFrame() +vif['feature'] = X.columns +vif['VIF'] = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])] +print(vif) +``` + +--- + +### 7.8 회귀 분석 (통계적 관점) + +#### 단순/다중 선형 회귀 (statsmodels - 해석 중심) +```python +import statsmodels.api as sm +from patsy import dmatrices + +# 공식: 'y ~ x1 + x2 + C(범주형변수)' +y, X = dmatrices('price ~ area + rooms + C(district)', data=df, return_type='dataframe') + +model = sm.OLS(y, X) +result = model.fit() +print(result.summary()) +# 확인 항목: R-squared, 계수(coef), p-value, AIC/BIC +``` + +#### 규제 회귀 (sklearn — 예측 중심) + +| 방법 | 규제 | 특징 | +|------|------|------| +| Ridge | L2 (계수 제곱) | 계수를 0에 가깝게 축소, 변수 유지 | +| Lasso | L1 (계수 절댓값) | 불필요한 계수를 정확히 0으로 → 변수 선택 효과 | +| ElasticNet | L1 + L2 혼합 | Ridge + Lasso 장점 결합 | + +> `alpha`가 클수록 규제 강도 강함. `C = 1/alpha` (LogisticRegression은 C 사용). + +```python +from sklearn.linear_model import Ridge, Lasso, ElasticNet +from sklearn.model_selection import cross_val_score + +for alpha in [0.01, 0.1, 1, 10]: + model = Ridge(alpha=alpha) # Lasso, ElasticNet도 동일 사용법 + scores = cross_val_score(model, X_train, y_train, + scoring='neg_mean_squared_error', cv=5) + print(f'alpha={alpha}, RMSE={np.sqrt(-scores.mean()):.3f}') +``` + +--- + +### 7.9 요인 분석 +> 여러 변수 이면에 있는 소수의 잠재요인(Factor)을 추출. +> 예: 14개 항목 설문 → 3개 요인('편안함', '서비스', '편의') + +**요인 수 결정 기준:** +- 고유값(Eigenvalue) ≥ 1인 요인 수 채택 +- 누적 설명력 60% 이상 확보 + +**회전 방법:** +- **Varimax** (직각회전): 요인 간 독립 유지, 가장 많이 사용 +- **Oblimin** (사각회전): 요인 간 상관 허용 + +```python +from factor_analyzer import FactorAnalyzer + +fa = FactorAnalyzer(n_factors=3, rotation='varimax') +fa.fit(X) + +# 고유값 확인 +ev, _ = fa.get_eigenvalues() +print(ev) + +# 요인 적재량 (0.5 이상이면 해당 요인에 속함) +loads = pd.DataFrame(fa.loadings_, index=X.columns, columns=['F1','F2','F3']) +print(loads) + +# 요인 점수로 변환 (차원 축소 효과) +factor_scores = fa.fit_transform(X) +``` + +**신뢰도 검정 (Cronbach's alpha):** +같은 요인에 묶인 변수들이 실제로 같은 개념을 측정하는지 확인. α ≥ 0.6이면 허용. +```python +import pingouin as pg +alpha, ci = pg.cronbach_alpha(data=df[['item1','item2','item3']]) +``` + +--- + +### 7.10 주성분 분석 (PCA) +> 변수 간 상관관계를 이용해 정보 손실을 최소화하면서 차원 축소. +> **요인분석과의 차이**: PCA는 분산 최대화가 목적, 요인분석은 잠재구조 해석이 목적. + +```python +from sklearn.decomposition import PCA +from sklearn.preprocessing import StandardScaler + +X_scaled = StandardScaler().fit_transform(X) # PCA 전 정규화 필수 + +pca = PCA(n_components=2) +X_pca = pca.fit_transform(X_scaled) + +# 각 주성분의 설명력 +print(pca.explained_variance_ratio_) +# [0.7296, 0.2285] → 2개로 95.8% 설명 +``` + +--- + +### 7.11 다차원척도법 (MDS) +> 개체 간 거리(유사도)를 보존하면서 저차원으로 시각화. + +```python +from sklearn.manifold import MDS + +# 계량적 MDS (구간/비율 척도) +mds = MDS(n_components=2) +X_mds = mds.fit_transform(X) + +# 비계량적 MDS (순서 척도) +nmds = MDS(n_components=2, metric=False) +X_nmds = nmds.fit_transform(X) +``` + +--- + +### 7.12 연관 분석 (장바구니 분석) +> 함께 구매(발생)하는 항목의 규칙을 찾는 분석. + +**핵심 지표:** +| 지표 | 의미 | 계산 | +|------|------|------| +| 지지도(Support) | A,B가 함께 발생한 비율 | P(A∩B) | +| 신뢰도(Confidence) | A 발생 시 B도 발생하는 비율 | P(A∩B) / P(A) | +| 향상도(Lift) | A 없을 때 대비 A 있을 때 B 증가율 | P(A∩B) / (P(A)×P(B)) | + +> Lift = 1: 독립 / Lift > 1: 양의 연관 / Lift < 1: 음의 연관 + +```python +from mlxtend.preprocessing import TransactionEncoder +from mlxtend.frequent_patterns import apriori, association_rules + +# 트랜잭션 데이터 인코딩 +dataset = [['우유','빵','계란'], ['우유','기저귀'], ['빵','계란','버터']] +te = TransactionEncoder() +df = pd.DataFrame(te.fit_transform(dataset), columns=te.columns_) + +# 빈발 항목집합 추출 +frequent = apriori(df, min_support=0.3, use_colnames=True, max_len=4) + +# 연관 규칙 생성 +rules = association_rules(frequent, metric='confidence', min_threshold=0.5) +rules.sort_values('lift', ascending=False).head(10) +``` +--- + +## 8. 기계학습 전처리 + +### 8.1 Train / Test 분리 +```python +from sklearn.model_selection import train_test_split + +X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=0.3, random_state=42, stratify=y # stratify: 클래스 비율 유지 +) +``` + +### 8.2 스케일링 +> **중요**: Scaler는 반드시 train에만 `fit`, test에는 `transform`만 적용. +> test에 `fit_transform`을 쓰면 데이터 누출(data leakage) 발생. + +```python +from sklearn.preprocessing import StandardScaler, MinMaxScaler + +scaler = StandardScaler() # 평균 0, 표준편차 1 +scaler = MinMaxScaler() # 최솟값 0, 최댓값 1 + +X_train_sc = scaler.fit_transform(X_train) # train: fit + transform +X_test_sc = scaler.transform(X_test) # test: transform만 + +# y 변수 로그 변환 (분포 왜곡 완화) +y_log = np.log1p(y) +# 예측 후 역변환 +y_pred_orig = np.expm1(y_pred_log) +``` + +### 8.3 오버샘플링 (불균형 데이터) +> **중요**: 오버샘플링은 train set에만 적용. test set에 적용하면 안 됨. + +```python +from imblearn.over_sampling import SMOTE + +smote = SMOTE(random_state=42) +X_train_res, y_train_res = smote.fit_resample(X_train, y_train) +``` + +### 8.4 변수 선택 — VIF 기반 +```python +from statsmodels.stats.outliers_influence import variance_inflation_factor +import statsmodels.api as sm + +# VIF > 10인 변수를 하나씩 제거하며 반복 +X_with_const = sm.add_constant(X_train) +vif = pd.Series( + [variance_inflation_factor(X_with_const.values, i) + for i in range(X_with_const.shape[1])], + index=X_with_const.columns +) +print(vif[vif > 10]) +``` + +--- + +## 9. 분류 알고리즘 + +### 공통 패턴 +```python +from sklearn.metrics import accuracy_score + +model.fit(X_train, y_train) +y_pred = model.predict(X_test) +accuracy_score(y_test, y_pred) +``` + +### 9.1 로지스틱 회귀 +> 선형 경계면으로 이진/다중 분류. 계수 해석 가능. 정규화에 민감. + +```python +from sklearn.linear_model import LogisticRegression + +model = LogisticRegression(C=1.0, penalty='l2', max_iter=1000) +# C: 1/alpha (작을수록 강한 규제) +# penalty: 'l1' (변수 선택), 'l2' (기본) +``` + +### 9.2 SVM (Support Vector Machine) +> 마진을 최대화하는 경계면 탐색. 커널로 비선형 분류 가능. 대용량 데이터에 느림. + +```python +from sklearn.svm import SVC + +model = SVC(kernel='rbf', C=1.0, gamma='scale', probability=True) +# kernel: 'linear', 'rbf', 'poly' +# C: 마진 오류 허용 정도 (클수록 overfitting 위험) +# probability=True: predict_proba() 사용 가능 +``` + +### 9.3 Random Forest +> 다수의 결정 트리 앙상블(배깅). 과적합에 강하고, 변수 중요도 제공. + +```python +from sklearn.ensemble import RandomForestClassifier + +model = RandomForestClassifier( + n_estimators=200, # 트리 수 (많을수록 안정, 느림) + max_depth=10, # 트리 깊이 (제한 없으면 과적합) + min_samples_leaf=5, + random_state=42 +) +model.fit(X_train, y_train) + +# 변수 중요도 +feat_imp = pd.Series(model.feature_importances_, index=X_train.columns) +feat_imp.sort_values(ascending=False).plot(kind='bar') +``` + +### 9.4 XGBoost +> 부스팅 계열. 잔차를 순서대로 학습. 높은 성능, early stopping 지원. + +```python +from xgboost import XGBClassifier + +model = XGBClassifier( + n_estimators=500, + learning_rate=0.05, + max_depth=5, + eval_metric='logloss', + use_label_encoder=False, + random_state=42 +) +model.fit(X_train, y_train, + early_stopping_rounds=50, + eval_set=[(X_test, y_test)], + verbose=False) +``` + +### 9.5 LightGBM +> XGBoost보다 빠르고 메모리 효율적. 대용량 데이터에 적합. + +```python +from lightgbm import LGBMClassifier + +model = LGBMClassifier(n_estimators=500, learning_rate=0.05, random_state=42) +model.fit(X_train, y_train, + callbacks=[lgb.early_stopping(50), lgb.log_evaluation(0)]) +``` + +### 9.6 MLP (다층 퍼셉트론, sklearn) +> 비선형 패턴 학습. 은닉층 구조와 활성함수 설정 필요. + +```python +from sklearn.neural_network import MLPClassifier + +model = MLPClassifier( + hidden_layer_sizes=(128, 64), # 은닉층 2개, 각 128·64개 뉴런 + activation='relu', # 'relu', 'tanh', 'logistic' + learning_rate_init=0.001, + max_iter=300, + random_state=42 +) +``` + +### 9.7 하이퍼파라미터 튜닝 (GridSearchCV) +```python +from sklearn.model_selection import GridSearchCV + +params = {'max_depth': [5, 10, 15], 'n_estimators': [100, 200]} +grid = GridSearchCV(RandomForestClassifier(random_state=42), + param_grid=params, cv=5, scoring='f1', n_jobs=-1) +grid.fit(X_train, y_train) + +print(grid.best_params_) +best_model = grid.best_estimator_ +``` + +### 9.8 Voting (앙상블) +```python +from sklearn.ensemble import VotingClassifier + +voting = VotingClassifier( + estimators=[('lr', LogisticRegression()), ('rf', RandomForestClassifier())], + voting='soft' # 확률 평균 / 'hard': 다수결 +) +``` + +--- + +## 10. 분류 성능 평가 + +### 혼동행렬 (Confusion Matrix) +``` + 예측 Positive 예측 Negative +실제 Positive TP FN +실제 Negative FP TN +``` + +```python +from sklearn.metrics import (confusion_matrix, accuracy_score, + precision_score, recall_score, + f1_score, roc_auc_score, roc_curve) + +cm = confusion_matrix(y_test, y_pred) + +accuracy = accuracy_score(y_test, y_pred) # (TP+TN) / 전체 +precision = precision_score(y_test, y_pred) # TP / (TP+FP) — 예측 P 중 실제 P 비율 +recall = recall_score(y_test, y_pred) # TP / (TP+FN) — 실제 P 중 예측 P 비율 +f1 = f1_score(y_test, y_pred) # 정밀도·재현율 조화평균 +auc = roc_auc_score(y_test, y_pred) + +# 특이도 (Specificity): TN / (TN+FP) +tn, fp, fn, tp = confusion_matrix(y_test, y_pred).ravel() +specificity = tn / (tn + fp) + +# 다중 클래스 +print(classification_report(y_test, y_pred)) +``` + +> **언제 무엇을 중시하나?** +> - 암 진단 → **재현율** 중시 (실제 환자를 놓치지 않아야 함) +> - 스팸 필터 → **정밀도** 중시 (정상 메일을 스팸으로 분류하면 안 됨) +> - 불균형 데이터 → **F1, AUC** + +### ROC 곡선 +> x축: FPR (1-특이도), y축: TPR (재현율). 곡선이 좌상단에 가까울수록 좋음. + +```python +proba = model.predict_proba(X_test)[:, 1] +fpr, tpr, thresholds = roc_curve(y_test, proba) + +plt.plot(fpr, tpr, label=f'AUC={auc:.3f}') +plt.plot([0, 1], [0, 1], 'k--') +plt.xlabel('FPR'); plt.ylabel('TPR') +plt.legend(); plt.show() +``` + +--- + +## 11. 회귀 성능 평가 + +```python +from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score + +mae = mean_absolute_error(y_test, y_pred) # 평균 절대 오차 +mse = mean_squared_error(y_test, y_pred) # 평균 제곱 오차 +rmse = np.sqrt(mse) # 루트 MSE (단위 동일) +r2 = r2_score(y_test, y_pred) # 설명력 (0~1, 1에 가까울수록 좋음) + +def mape(y_true, y_pred): + """평균 절대 백분율 오차 (%)""" + return np.mean(np.abs((y_true - y_pred) / y_true)) * 100 +``` + +--- + +## 12. 회귀 ML 알고리즘 + +### 선형 계열 +```python +from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet + +lr = LinearRegression() +ridge = Ridge(alpha=1.0) +lasso = Lasso(alpha=0.1) +enet = ElasticNet(alpha=0.1, l1_ratio=0.5) # l1_ratio: L1 비중 +``` + +### 트리 계열 +```python +from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor +from xgboost import XGBRegressor +from lightgbm import LGBMRegressor + +models = { + 'RF': RandomForestRegressor(n_estimators=200, random_state=42), + 'GBM': GradientBoostingRegressor(n_estimators=200, random_state=42), + 'XGB': XGBRegressor(n_estimators=200, random_state=42), + 'LGB': LGBMRegressor(n_estimators=200, random_state=42), +} + +for name, model in models.items(): + model.fit(X_train, y_train) + pred = model.predict(X_test) + print(f'{name}: RMSE={np.sqrt(mean_squared_error(y_test, pred)):.3f}') +``` + +### 혼합 모델 (앙상블) +```python +# 여러 모델 예측값의 가중 평균 +pred_final = 0.4 * xgb_pred + 0.4 * lgb_pred + 0.2 * ridge_pred +``` + +### 다항 회귀 +> 비선형 관계를 다항 특성 생성으로 선형 회귀에 적용. + +```python +from sklearn.preprocessing import PolynomialFeatures + +poly = PolynomialFeatures(degree=2, include_bias=False) +X_poly = poly.fit_transform(X_train) + +model = LinearRegression() +model.fit(X_poly, y_train) +pred = model.predict(poly.transform(X_test)) +``` +--- + +## 13. 군집 분석 (Clustering) + +> 비지도 학습. 레이블 없이 유사한 데이터끼리 그룹화. + +### 13.1 K-means +> 각 군집의 중심(centroid)과의 거리로 군집 할당. 구형 군집에 적합. + +```python +from sklearn.cluster import KMeans + +kmeans = KMeans(n_clusters=3, init='k-means++', random_state=42) +kmeans.fit(X) +labels = kmeans.labels_ # 각 데이터의 군집 번호 +centers = kmeans.cluster_centers_ + +# 군집 결과를 DataFrame에 추가 +df['cluster'] = labels +``` + +**최적 군집 수 찾기 — 엘보우(Elbow)** +```python +sse = [] +for k in range(1, 11): + km = KMeans(n_clusters=k, random_state=42) + km.fit(X) + sse.append(km.inertia_) # SSE (군집 내 분산 합) + +plt.plot(range(1, 11), sse, marker='o') +plt.xlabel('군집 수 k'); plt.ylabel('SSE') +# SSE 감소율이 꺾이는 지점(엘보우)이 최적 +``` + +**최적 군집 수 찾기 — 실루엣** +> 실루엣 계수: −1 ~ 1, 높을수록 좋은 군집화. + +```python +from sklearn.metrics import silhouette_score, silhouette_samples + +for k in range(2, 11): + km = KMeans(n_clusters=k, random_state=42) + labels = km.fit_predict(X) + score = silhouette_score(X, labels) + print(f'k={k}: silhouette={score:.3f}') +``` + +**군집 프로파일링** +```python +df.groupby('cluster')[feature_cols].mean() +# 군집별 평균 비교 → 각 군집의 특성 해석 +``` + +--- + +### 13.2 계층적 군집화 (Hierarchical Clustering) +> 거리를 기반으로 가까운 것끼리 병합. 덴드로그램으로 시각화. + +```python +from sklearn.cluster import AgglomerativeClustering +from scipy.cluster.hierarchy import dendrogram, linkage + +# 덴드로그램 (군집 수 결정에 활용) +Z = linkage(X, method='ward') +dendrogram(Z) +plt.show() + +# 군집화 수행 +model = AgglomerativeClustering(n_clusters=3, linkage='ward') +labels = model.fit_predict(X) +``` + +--- + +### 13.3 DBSCAN (밀도 기반) +> 밀도가 높은 영역을 군집으로. 군집 수 사전 지정 불필요. 이상치 자동 탐지(레이블 −1). + +```python +from sklearn.cluster import DBSCAN + +db = DBSCAN(eps=0.5, # 이웃 반경 + min_samples=5) # 핵심 포인트 최소 이웃 수 +labels = db.fit_predict(X) +# labels == -1: 이상치(노이즈) +``` + +--- + +### 13.4 GMM (Gaussian Mixture Model) +> 여러 가우시안 분포의 혼합으로 데이터를 설명. 확률 기반 군집화. + +```python +from sklearn.mixture import GaussianMixture + +gmm = GaussianMixture(n_components=3, random_state=42) +labels = gmm.fit_predict(X) +proba = gmm.predict_proba(X) # 각 군집에 속할 확률 +``` + +--- + +### 13.5 군집화 결과 시각화 (PCA 활용) +```python +from sklearn.decomposition import PCA + +pca = PCA(n_components=2) +X_2d = pca.fit_transform(X) + +plt.scatter(X_2d[:, 0], X_2d[:, 1], c=labels, cmap='viridis') +plt.xlabel('PC1'); plt.ylabel('PC2') +plt.title('Clustering Result (PCA 2D)') +plt.show() +``` + +--- + +## 14. 시계열 분석 + +### 14.1 데이터 준비 +```python +df = pd.read_csv('data.csv') +df['date'] = pd.to_datetime(df['date']) +df = df.set_index('date').sort_index() +``` + +### 14.2 정상성(Stationarity) +> 정상 시계열: 평균·분산이 시간에 관계없이 일정. ARIMA 적합 조건. + +**시각적 확인**: 추세, 계절성, 분산 변화가 없으면 정상. + +**ADF 검정** (Augmented Dickey-Fuller): +```python +from statsmodels.tsa.stattools import adfuller + +result = adfuller(df['value']) +print(f'ADF Statistic: {result[0]:.4f}') +print(f'p-value: {result[1]:.4f}') +# p < 0.05 → 정상 시계열 (귀무가설: 단위근 존재 = 비정상) +``` + +### 14.3 차분 (Differencing) +> 비정상 시계열을 정상으로 변환. 1차 차분으로 추세 제거. + +```python +df['diff1'] = df['value'].diff() # 1차 차분 +df['diff2'] = df['value'].diff().diff() # 2차 차분 +df_diff = df['diff1'].dropna() +``` + +### 14.4 ACF / PACF +> **ACF** (자기상관함수): q (MA 차수) 결정 +> **PACF** (편자기상관함수): p (AR 차수) 결정 + +```python +import statsmodels.api as sm + +fig, ax = plt.subplots(1, 2, figsize=(12, 4)) +sm.graphics.tsa.plot_acf(df_diff, lags=30, ax=ax[0]) +sm.graphics.tsa.plot_pacf(df_diff, lags=30, ax=ax[1]) +plt.show() +``` + +**모델 선택 기준 (차분 후 ACF/PACF)**: +| ACF 패턴 | PACF 패턴 | 모델 | +|----------|----------|------| +| 지수 감소 | lag p 이후 절단 | AR(p) | +| lag q 이후 절단 | 지수 감소 | MA(q) | +| 둘 다 지수 감소 | 둘 다 지수 감소 | ARMA(p,q) | + +### 14.5 ARIMA(p, d, q) +- **p**: AR 차수 (PACF에서 결정) +- **d**: 차분 횟수 (정상화에 필요한 차분 수) +- **q**: MA 차수 (ACF에서 결정) + +```python +from statsmodels.tsa.arima.model import ARIMA + +train, test = df[:int(len(df)*0.8)], df[int(len(df)*0.8):] + +# 여러 (p,d,q) 조합 비교 — AIC 기준 선택 +import itertools +best_aic, best_order = np.inf, None +for order in itertools.product(range(3), [1], range(3)): + try: + model = ARIMA(train['value'], order=order).fit() + if model.aic < best_aic: + best_aic, best_order = model.aic, order + except: + continue +print(f'Best order: {best_order}, AIC: {best_aic:.2f}') + +# 최적 모델로 예측 +model = ARIMA(train['value'], order=best_order).fit() +pred = model.forecast(steps=len(test)) +``` + +### 14.6 SARIMA(p,d,q)(P,D,Q,s) +> 계절성이 있는 시계열. s = 계절 주기 (월별=12, 분기=4 등). + +```python +from statsmodels.tsa.statespace.sarimax import SARIMAX + +model = SARIMAX(train['value'], + order=(1, 1, 1), + seasonal_order=(1, 1, 1, 12)) +result = model.fit(disp=False) + +pred = result.get_forecast(steps=len(test)) +pred_mean = pred.predicted_mean +pred_ci = pred.conf_int() # 95% 신뢰구간 +``` + +### 14.7 시계열 성능 평가 +```python +from sklearn.metrics import mean_absolute_error, mean_squared_error + +mae = mean_absolute_error(test['value'], pred_mean) +rmse = np.sqrt(mean_squared_error(test['value'], pred_mean)) +mape = np.mean(np.abs((test['value'] - pred_mean) / test['value'])) * 100 +r2 = r2_score(test['value'], pred_mean) + +print(f'MAE={mae:.2f}, RMSE={rmse:.2f}, MAPE={mape:.2f}%, R²={r2:.4f}') +``` + +### 14.8 계절 분해 +```python +decomp = sm.tsa.seasonal_decompose(df['value'], model='additive', period=12) +decomp.plot() +# Observed = Trend + Seasonal + Residual (additive) +# Observed = Trend × Seasonal × Residual (multiplicative) +``` +--- + +## 15. 텍스트 마이닝 + +### 15.1 영어 텍스트 처리 (NLTK) + +```python +import nltk +from nltk import word_tokenize, sent_tokenize +from nltk.corpus import stopwords +from nltk.stem import WordNetLemmatizer + +text = "The cats are running quickly through the forest." + +# 토큰화 +tokens = word_tokenize(text.lower()) +# ['the', 'cats', 'are', 'running', ...] + +# 불용어 제거 +stop_words = set(stopwords.words('english')) +tokens = [t for t in tokens if t not in stop_words and t.isalpha()] +# ['cats', 'running', 'quickly', 'forest'] + +# 표제어 추출 (Lemmatization) +lemma = WordNetLemmatizer() +tokens = [lemma.lemmatize(t, pos='v') for t in tokens] +# ['cat', 'run', 'quickly', 'forest'] +``` + +> **Stemming vs Lemmatization** +> - Stemming: 규칙 기반 어간 추출 (빠르나 부정확. 예: running → run, studies → studi) +> - Lemmatization: 품사를 고려한 정확한 원형 복원 (느리나 정확. 예: studies → study) + +--- + +### 15.2 한국어 형태소 분석 (KoNLPy) + +| 분석기 | 특징 | +|--------|------| +| Okt (구 Twitter) | 가볍고 빠름, 신조어 처리 양호 | +| Komoran | 정확도 높음, 사용자 사전 지원 | +| Kkma | 세밀한 품사 태깅, 느림 | +| Mecab | 가장 빠름, 별도 설치 필요 | + +```python +from konlpy.tag import Komoran + +komoran = Komoran() +# 사용자 사전 적용 시: Komoran(userdic='사전.txt') + +text = "자연어 처리는 재미있어요" + +komoran.nouns(text) # 명사만 추출: ['자연어', '처리'] +komoran.pos(text) # 품사 태깅: [('자연어','NNG'), ('처리','NNG'), ...] +komoran.morphs(text) # 형태소 분리: ['자연어', '처리', '는', '재미있', '어요'] +``` + +**주요 품사 코드**: +| 코드 | 의미 | +|------|------| +| NNG | 일반 명사 | +| NNP | 고유 명사 | +| VV | 동사 | +| VA | 형용사 | +| MA | 부사 | + +--- + +### 15.3 텍스트 정제 (정규표현식) + +```python +import re + +text = "영화 기생충 리뷰!! 😂 #기생충 010-1234-5678" + +# 특수문자 제거 +text = re.sub(r'[^\w\s]', ' ', text) + +# 숫자 제거 +text = re.sub(r'\d+', ' ', text) + +# HTML 태그 제거 +text = re.sub(r'<[^>]+>', ' ', text) + +# 연속 공백 제거 +text = re.sub(r'\s+', ' ', text).strip() +``` + +--- + +### 15.4 단어 빈도 분석 + +```python +import collections + +# 명사 추출 후 빈도 계산 +nouns = komoran.nouns(' '.join(documents)) +freq = collections.Counter(nouns) + +# 2음절 이상만 선택 +freq_filtered = {w: c for w, c in freq.items() if len(w) >= 2} + +# 상위 20개 시각화 +freq_df = pd.DataFrame(freq_filtered.items(), columns=['word','count']) +freq_df.sort_values('count', ascending=False).head(20).plot( + kind='bar', x='word', y='count') +``` + +--- + +### 15.5 TF-IDF + +> **TF** (단어 빈도): 해당 문서에서 단어가 얼마나 자주 등장하는가 +> **IDF** (역문서 빈도): 전체 문서 중 해당 단어가 드물게 등장할수록 높은 가중치 +> **TF-IDF = TF × IDF** — 특정 문서에만 자주 나오는 단어에 높은 가중치 + +```python +from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer + +# 영어 (자동 토큰화) +tfidf = TfidfVectorizer(stop_words='english', max_features=5000, ngram_range=(1,2)) +X = tfidf.fit_transform(train_docs) # train에만 fit +X_test_vec = tfidf.transform(test_docs) + +# 한국어 (사용자 토크나이저 전달) +def ko_tokenizer(text): + return [n for n in komoran.nouns(text) if len(n) >= 2] + +tfidf_ko = TfidfVectorizer(tokenizer=ko_tokenizer, max_features=3000) +X_ko = tfidf_ko.fit_transform(documents) +``` + +--- + +### 15.6 텍스트 분류 + +```python +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import classification_report + +# TF-IDF → 분류 (Pipeline으로 묶으면 편리) +from sklearn.pipeline import Pipeline + +pipeline = Pipeline([ + ('tfidf', TfidfVectorizer(stop_words='english', ngram_range=(1,2))), + ('clf', LogisticRegression(C=5, max_iter=1000)) +]) +pipeline.fit(X_train_text, y_train) +y_pred = pipeline.predict(X_test_text) +print(classification_report(y_test, y_pred)) +``` + +--- + +## 16. 딥러닝 (TensorFlow / Keras) + +### 16.1 MLP 분류 +```python +import tensorflow as tf +from tensorflow import keras + +model = keras.Sequential([ + keras.layers.Dense(128, activation='relu', input_shape=(n_features,)), + keras.layers.Dropout(0.3), # 과적합 방지 + keras.layers.Dense(64, activation='relu'), + keras.layers.Dense(n_classes, activation='softmax') # 다중분류 + # 이진분류: Dense(1, activation='sigmoid') +]) + +model.compile( + optimizer='adam', + loss='sparse_categorical_crossentropy', # 정수 레이블 + # loss='categorical_crossentropy' # one-hot 레이블 + # loss='binary_crossentropy' # 이진분류 + metrics=['accuracy'] +) + +history = model.fit(X_train, y_train, + epochs=50, batch_size=32, + validation_split=0.2, + verbose=1) +``` + +### 16.2 MLP 회귀 +```python +model = keras.Sequential([ + keras.layers.Dense(128, activation='relu', input_shape=(n_features,)), + keras.layers.Dense(64, activation='relu'), + keras.layers.Dense(1) # 출력층: 활성함수 없음 (선형) +]) + +model.compile(optimizer='adam', loss='mse', metrics=['mae']) +``` + +### 16.3 RNN / LSTM + +> **순서 있는 데이터** (시계열, 텍스트)에 적합. +> LSTM은 장기 의존성 문제(vanishing gradient)를 해결한 RNN 변형. + +```python +# 입력 shape: (samples, timesteps, features) + +model = keras.Sequential([ + keras.layers.LSTM(64, return_sequences=True, # 중간 층: True + input_shape=(timesteps, n_features)), + keras.layers.LSTM(32), # 마지막 RNN: False (기본) + keras.layers.Dense(1) +]) +``` + +> `return_sequences=True`: 각 타임스텝의 출력을 모두 반환 (다음 RNN 층에 연결할 때 필요) +> `return_sequences=False` (기본): 마지막 타임스텝 출력만 반환 + +**양방향 LSTM (Bidirectional)**: +```python +keras.layers.Bidirectional(keras.layers.LSTM(64)) +# 순방향 + 역방향을 결합 → 텍스트 분류에서 성능 향상 +``` + +### 16.4 시계열 LSTM 데이터 준비 +```python +def make_sequences(series, window): + """시계열을 슬라이딩 윈도우로 X, y 분리""" + X, y = [], [] + for i in range(len(series) - window): + X.append(series[i:i+window]) + y.append(series[i+window]) + return np.array(X), np.array(y) + +window = 12 +X, y = make_sequences(values, window) +X = X.reshape(X.shape[0], X.shape[1], 1) # (samples, timesteps, 1) +``` + +### 16.5 학습 시각화 & 조기 종료 +```python +# 조기 종료 (과적합 방지) +early_stop = keras.callbacks.EarlyStopping( + monitor='val_loss', patience=10, restore_best_weights=True) + +history = model.fit(X_train, y_train, epochs=200, callbacks=[early_stop], + validation_data=(X_val, y_val), verbose=0) + +# 학습 곡선 +plt.plot(history.history['loss'], label='train') +plt.plot(history.history['val_loss'], label='val') +plt.legend(); plt.xlabel('Epoch'); plt.ylabel('Loss') +``` + +--- + +## 17. 교차 검증 (Cross Validation) + +```python +from sklearn.model_selection import cross_val_score, KFold, StratifiedKFold + +# 회귀: neg_mean_squared_error 사용 (sklearn 규칙: 큰 값이 좋음 → 음수) +scores = cross_val_score(model, X, y, + scoring='neg_mean_squared_error', cv=5) +rmse = np.sqrt(-scores.mean()) + +# 분류: accuracy, f1, roc_auc +scores = cross_val_score(model, X, y, scoring='f1', cv=5) + +# 불균형 데이터: StratifiedKFold (각 fold에서 클래스 비율 유지) +skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) +scores = cross_val_score(model, X, y, cv=skf, scoring='roc_auc') +``` + +--- + +## 18. 정규표현식 빠른 참조 + +| 패턴 | 의미 | 예 | +|------|------|----| +| `\d` | 숫자 | `\d+` → 1개 이상 숫자 | +| `\D` | 숫자 외 | | +| `\w` | 문자·숫자·_ | | +| `\W` | 문자·숫자·_ 외 | | +| `\s` | 공백 | `\s+` → 1개 이상 공백 | +| `.` | 임의 문자 1개 | | +| `*` | 0회 이상 반복 | | +| `+` | 1회 이상 반복 | | +| `?` | 0회 또는 1회 | | +| `[abc]` | a, b, c 중 하나 | | +| `[^abc]` | a, b, c 제외 | | + +```python +import re +re.sub(r'[^\가-힣\s]', '', text) # 한글과 공백만 남기기 +re.findall(r'\d+', text) # 모든 숫자 추출 +re.split(r'\s+', text) # 공백 기준 분리 +``` diff --git a/doc_section1.md b/doc_section1.md new file mode 100644 index 0000000..74540bf --- /dev/null +++ b/doc_section1.md @@ -0,0 +1,237 @@ +# ADP 데이터분석 핵심 정리 + +--- + +## 1. 환경 설정 + +```python +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt +import seaborn as sns +%matplotlib inline + +# 출력 옵션 +from IPython.core.interactiveshell import InteractiveShell +InteractiveShell.ast_node_interactivity = "all" +pd.options.display.max_columns = None + +# 한글 폰트 (Linux) +from matplotlib import font_manager, rc +font_name = font_manager.FontProperties(fname="malgun.ttf").get_name() +rc('font', family=font_name) +plt.rcParams['axes.unicode_minus'] = False + +# 경고 무시 +import warnings +warnings.filterwarnings('ignore') +``` + +--- + +## 2. 데이터 입출력 + +### CSV / TXT +```python +df = pd.read_csv('file.csv', encoding='cp949') +# 주요 파라미터 +# sep=',' 구분자 +# header=0 컬럼명 행 번호 +# names=[...] 컬럼명 직접 지정 +# engine='python' 한글 경로 오류 시 + +df.to_csv('out.csv', index=False, encoding='cp949') +``` + +### Excel +```python +df = pd.read_excel('file.xlsx', sheet_name='Sheet1') +df.to_excel('out.xlsx', sheet_name='Sheet1', index=False) +``` + +### 일반 텍스트 직접 파싱 (구분자가 특이한 경우) +```python +with open('file.txt', 'r', encoding='cp949') as f: + raw = f.read() + +rows = [] +for line in raw.split('\n'): # 줄바꿈으로 행 분리 + rows.append(line.split(',')) # 콤마로 열 분리 + +df = pd.DataFrame(rows[1:], columns=rows[0]) +``` + +### Pickle +```python +import pickle +# 저장 +with open('data.pkl', 'wb') as f: + pickle.dump(obj, f) +# 불러오기 +with open('data.pkl', 'rb') as f: + obj = pickle.load(f) +``` + +### 날짜 형식 문자열 포맷 +| 코드 | 의미 | 예시 | +|------|------|------| +| `%Y` | 4자리 연도 | 2024 | +| `%m` | 2자리 월 | 03 | +| `%d` | 2자리 일 | 15 | +| `%H` | 24시간 시 | 14 | +| `%M` | 분 | 30 | +| `%S` | 초 | 05 | + +```python +# 문자열 → datetime +df['date'] = pd.to_datetime(df['date']) +df['date'] = pd.to_datetime(df['date'], format='%d.%m.%Y') + +# timestamp → datetime +from datetime import datetime +datetime.fromtimestamp(1599989445) + +# 연·월·일 추출 +df['year'] = df['date'].dt.year +df['month'] = df['date'].dt.month +df['day'] = df['date'].dt.day +df['weekday'] = df['date'].dt.day_name() +``` + +--- + +## 3. NumPy 핵심 + +```python +# 배열 생성 +a = np.array([1, 2, 3]) +np.zeros((3, 3)) # 0으로 채운 3×3 +np.ones((3, 4)) # 1로 채운 3×4 +np.eye(3) # 3×3 단위행렬 +np.arange(5) # [0,1,2,3,4] +np.random.random((2, 2)) # 0~1 랜덤 + +# reshape: -1은 자동 계산 +a = np.arange(6).reshape(2, 3) # 2행 3열 +a = np.arange(6).reshape(-1, 2) # 열 2개, 행 자동 + +# 수학 함수 +np.abs(a) # 절댓값 +np.sqrt(a) # 제곱근 +np.log(a) # 자연로그 +np.log1p(a) # log(1+a) — 0 포함 데이터에 안전 +np.expm1(a) # log1p의 역함수 +``` + +--- + +## 4. Pandas 핵심 + +### DataFrame 생성 & 기본 확인 +```python +df = pd.DataFrame({'name': ['Alice', 'Bob'], 'age': [25, 30]}) + +df.shape # (행, 열) +df.dtypes # 컬럼별 타입 +df.info() # 결측치 포함 요약 +df.describe() # 수치형 기술통계 +df.describe(include='object') # 범주형 기술통계 +df['col'].unique() # 고유값 +df['col'].value_counts() # 빈도 +``` + +### 조회 +```python +df['col'] # 열 선택 +df[['col1', 'col2']] # 복수 열 +df.iloc[0:3, 1:4] # 행·열 번호로 +df.loc[df['age'] > 25, 'name'] # 조건 + 열 이름 +df.query('age > 25 and name == "Alice"') +``` + +### 정렬 & 인덱스 +```python +df.sort_values(by='age', ascending=False) +df.sort_values(by=['col1', 'col2'], ascending=[True, False]) +df.set_index('date') # 컬럼 → 인덱스 +df.reset_index() # 인덱스 → 컬럼 +``` + +### 컬럼 조작 +```python +df.rename(columns={'old': 'new'}) +df.columns = ['a', 'b', 'c'] # 전체 교체 +df['new_col'] = df['a'] + df['b'] # 파생변수 +df.drop('col', axis=1, inplace=True) # 열 삭제 +``` + +### 타입 변환 +```python +df['col'].astype('int') +df['col'].astype('float') +df['col'].astype('str') +pd.to_numeric(df['col'], errors='coerce') # 변환 불가 → NaN +pd.Categorical(df['col']) # 범주형 +pd.to_datetime(df['col']) # 날짜형 +``` + +### apply / map +```python +# map: Series의 값 하나하나에 함수 적용 +df['B'] = df['A'].map(lambda x: x * 2) + +# apply: 행 단위로 여러 컬럼 접근 +df['C'] = df.apply(lambda row: row['A'] + row['B'], axis=1) +``` + +### 문자열 처리 +```python +df['col'].str.contains('keyword') # 포함 여부 +df['col'].str.replace('old', 'new') +df['col'].str.strip() # 앞뒤 공백 제거 +df['col'].str.split(',') +``` + +### GroupBy & 집계 +```python +df.groupby('key')['value'].mean() +df.groupby('key').agg({'col1': 'sum', 'col2': 'mean'}) +df.groupby('key').agg(['min', 'max', 'mean']) + +# 피벗테이블 +df.pivot_table(index='sex', columns='class', values='fare', aggfunc='mean') +``` + +### 병합 +```python +# 열 기준 합치기 (SQL JOIN) +pd.merge(df1, df2, on='key', how='left') # left join +pd.merge(df1, df2, on='key', how='outer') # full outer join + +# 행/열 단순 이어붙이기 +pd.concat([df1, df2], axis=0) # 행 방향 (아래로) +pd.concat([df1, df2], axis=1) # 열 방향 (옆으로) +``` + +### MultiIndex +```python +# 다중 컬럼 인덱스를 단일로 변환 +lv0 = df.columns.get_level_values(0) +lv1 = df.columns.get_level_values(1) +df.columns = lv0 + '_' + lv1 + +# 다중 행 인덱스 → 단일 +df.reset_index() +``` + +### Dummy 변수 (원-핫 인코딩) +```python +dummies = pd.get_dummies(df['category']) +df = pd.concat([df.drop('category', axis=1), dummies], axis=1) +``` + +### Rolling & Shift (시계열용) +```python +df['ma3'] = df['value'].rolling(3).mean() # 3기간 이동평균 +df['lag1'] = df['value'].shift(1) # 1기간 lag +``` diff --git a/doc_section2.md b/doc_section2.md new file mode 100644 index 0000000..d9370ac --- /dev/null +++ b/doc_section2.md @@ -0,0 +1,127 @@ +--- + +## 5. 데이터 전처리 + +### 결측치 +```python +df.isnull().sum() # 컬럼별 결측 수 +df.dropna() # 결측 행 제거 +df.dropna(how='all') # 모든 값이 결측인 행만 제거 +df.dropna(subset=['col']) # 특정 컬럼 기준 + +df.fillna(0) # 상수로 대체 +df.fillna(df['col'].mean()) # 평균으로 대체 +df.fillna(method='ffill') # 앞 값으로 채우기 +df.fillna(method='bfill') # 뒤 값으로 채우기 +``` + +### 중복 제거 +```python +df.duplicated().sum() # 중복 행 개수 +df.drop_duplicates() # 중복 행 제거 +df.drop_duplicates(subset=['col'], keep='last') # 기준 컬럼 지정 +``` + +### 이상치 처리 (IQR 방식) +> IQR(사분위범위) = Q3 − Q1. 이상치 기준: Q1 − 1.5×IQR 미만 또는 Q3 + 1.5×IQR 초과. + +```python +Q1 = df['col'].quantile(0.25) +Q3 = df['col'].quantile(0.75) +IQR = Q3 - Q1 + +lower = max(Q1 - 1.5 * IQR, df['col'].min()) +upper = min(Q3 + 1.5 * IQR, df['col'].max()) + +# 이상치 확인 +df[(df['col'] < lower) | (df['col'] > upper)] + +# 윈저라이징(Winsorizing): 이상치를 경계값으로 대체 +df.loc[df['col'] < lower, 'col'] = lower +df.loc[df['col'] > upper, 'col'] = upper +``` + +### 기초통계량 종합 함수 +```python +def describe_num(data): + """연속형 변수에 대한 기술통계 + 결측, 분산, 왜도, 첨도""" + da = data.describe(percentiles=[.01,.05,.1,.25,.5,.75,.9,.95,.99]).T + da['missing'] = data.isnull().sum() + da['variance'] = data.var() + da['skewness'] = data.skew() # 양수: 오른쪽 꼬리, 음수: 왼쪽 꼬리 + da['kurtosis'] = data.kurtosis() # 0보다 클수록 뾰족한 분포 + return da.round(2) +``` + +### 범주형 변수 빈도표 +```python +def freq_table(df, col): + cnt = df[col].value_counts(dropna=False).rename('count') + ratio = df[col].value_counts(dropna=False, normalize=True).rename('ratio') + return pd.concat([cnt, ratio], axis=1) +``` + +--- + +## 6. EDA & 시각화 + +### 분포 확인 +```python +# 히스토그램 +df['col'].hist(bins=20) + +# 박스플롯 — 이상치, 분포 동시에 확인 +sns.boxplot(x='category', y='value', data=df) + +# KDE (밀도 추정) +sns.kdeplot(df['col']) + +# 히스토그램 + KDE +sns.histplot(df['col'], kde=True) +``` + +### 관계 확인 +```python +# 산점도 +sns.scatterplot(x='col1', y='col2', hue='category', data=df) + +# 상관 히트맵 +sns.heatmap(df.corr(), annot=True, fmt='.2f', cmap='Blues') + +# 산점도 행렬 (변수 4개 이하 권장) +sns.pairplot(df, hue='target') +``` + +### 범주형 변수 +```python +# 막대 그래프 (빈도) +sns.countplot(x='category', hue='target', data=df) + +# 막대 그래프 (평균값) +sns.barplot(x='category', y='value', data=df) + +# 파이 차트 +df['category'].value_counts().plot(kind='pie', autopct='%.1f%%') +``` + +### 그룹별 비교 +```python +# 그룹별 평균 막대 그래프 +df.groupby('group')['value'].mean().plot(kind='bar') + +# 박스플롯으로 그룹 비교 +df.boxplot(column='value', by='group') +``` + +### 시각화 저장 +```python +fig, ax = plt.subplots(figsize=(10, 6)) +# ... 그래프 코드 ... +fig.savefig('output.png', dpi=150, bbox_inches='tight') +plt.show() +``` + +### 변수 선택 기준 (EDA 결과 활용) +- **연속형**: 데이터 발생 구간의 90% 이내 범위를 사용하는 변수 채택 +- **범주형**: 특정 클래스에 95% 이상 집중된 변수는 정보량이 낮아 제거 검토 +- **상관계수 ≥ 0.7** 인 변수쌍은 다중공선성 위험 → VIF로 추가 검증 diff --git a/doc_section3.md b/doc_section3.md new file mode 100644 index 0000000..e324dc7 --- /dev/null +++ b/doc_section3.md @@ -0,0 +1,268 @@ +--- + +## 7. 통계 분석 + +### 7.1 가설 검정 기본 개념 +- **귀무가설(H₀)**: 차이가 없다 / 독립이다 +- **대립가설(H₁)**: 차이가 있다 / 관련이 있다 +- **p-value < 0.05** → 귀무가설 기각 (통계적으로 유의) + +--- + +### 7.2 정규성 검정 +> 가설: 정규분포를 따른다(H₀) vs 따르지 않는다(H₁) +> **샘플 수 ≤ 5000**: Shapiro-Wilk 권장 + +```python +from scipy import stats + +stat, p = stats.shapiro(df['col']) +# p < 0.05 → 정규성 없음 +``` + +--- + +### 7.3 등분산 검정 +> 두 집단의 분산이 같은지 검정. t-test 전에 확인. + +```python +stat, p = stats.levene(group1, group2) +# p < 0.05 → 등분산 아님 → t-test에서 equal_var=False +``` + +--- + +### 7.4 평균 비교 검정 (t-test, ANOVA) + +| 상황 | 검정 방법 | +|------|----------| +| 1표본 평균 vs 기준값 | One-sample t-test | +| 2 독립 집단 평균 비교 | Two-sample t-test | +| 동일 집단 전·후 비교 | Paired t-test | +| 3집단 이상 평균 비교 | ANOVA (F-test) | + +```python +# 1표본 t-test: 표본 평균이 mu=5인가? +stats.ttest_1samp(sample, popmean=5) + +# 2표본 t-test: A, B 평균이 같은가? +stats.ttest_ind(A, B, equal_var=False) # 등분산 모르면 False + +# 대응 t-test: before, after 차이가 없는가? +stats.ttest_rel(before, after) + +# ANOVA: A, B, C 중 하나라도 다른가? +stats.f_oneway(A, B, C) +# ANOVA는 '어떤 집단이 다른지'는 알려주지 않음 → 사후검정 필요 (Tukey HSD 등) +``` + +--- + +### 7.5 범주형 독립성 검정 + +**카이제곱 검정**: 두 범주형 변수가 독립인가? +> 예: 성별과 생존 여부가 관련 있는가? + +```python +contingency = pd.crosstab(df['sex'], df['survived']) +chi2, p, dof, expected = stats.chi2_contingency(contingency) +# p < 0.05 → 두 변수 간 연관성 있음 +``` + +**피셔 정확 검정**: 2×2 교차표에서 기대빈도가 5 미만인 셀이 20% 초과할 때 +```python +oddsratio, p = stats.fisher_exact([[8, 2], [1, 5]]) +``` + +**맥네마 검정**: 동일 대상의 전·후 비교 (2×2 교차표, paired) +```python +from statsmodels.stats.contingency_tables import mcnemar +result = mcnemar(np.array([[40, 6], [2, 11]]), exact=True) +# exact=True: 셀2+셀3 < 25 / exact=False: ≥ 25 +``` + +--- + +### 7.6 상관 분석 +> 두 연속형 변수 사이의 선형 관계 강도를 측정. + +| r 범위 | 해석 | +|--------|------| +| 0.1 ~ 0.3 | 약한 상관 | +| 0.3 ~ 0.7 | 뚜렷한 상관 | +| 0.7 ~ 1.0 | 강한 상관 | + +```python +# 피어슨 상관계수 +r, p = stats.pearsonr(df['x'], df['y']) + +# 전체 상관행렬 +df[num_cols].corr() + +# 시각화 +sns.heatmap(df.corr(), annot=True, fmt='.2f', cmap='Blues') +``` + +--- + +### 7.7 다중공선성 (VIF) +> 회귀 분석에서 독립변수 간 높은 상관관계가 있을 때 발생. +> VIF > 10 이면 제거 검토 (변수 하나씩 제거하며 재확인). + +```python +from statsmodels.stats.outliers_influence import variance_inflation_factor +from patsy import dmatrices + +y, X = dmatrices('target ~ col1 + col2 + col3', data=df, return_type='dataframe') + +vif = pd.DataFrame() +vif['feature'] = X.columns +vif['VIF'] = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])] +print(vif) +``` + +--- + +### 7.8 회귀 분석 (통계적 관점) + +#### 단순/다중 선형 회귀 (statsmodels - 해석 중심) +```python +import statsmodels.api as sm +from patsy import dmatrices + +# 공식: 'y ~ x1 + x2 + C(범주형변수)' +y, X = dmatrices('price ~ area + rooms + C(district)', data=df, return_type='dataframe') + +model = sm.OLS(y, X) +result = model.fit() +print(result.summary()) +# 확인 항목: R-squared, 계수(coef), p-value, AIC/BIC +``` + +#### 규제 회귀 (sklearn — 예측 중심) + +| 방법 | 규제 | 특징 | +|------|------|------| +| Ridge | L2 (계수 제곱) | 계수를 0에 가깝게 축소, 변수 유지 | +| Lasso | L1 (계수 절댓값) | 불필요한 계수를 정확히 0으로 → 변수 선택 효과 | +| ElasticNet | L1 + L2 혼합 | Ridge + Lasso 장점 결합 | + +> `alpha`가 클수록 규제 강도 강함. `C = 1/alpha` (LogisticRegression은 C 사용). + +```python +from sklearn.linear_model import Ridge, Lasso, ElasticNet +from sklearn.model_selection import cross_val_score + +for alpha in [0.01, 0.1, 1, 10]: + model = Ridge(alpha=alpha) # Lasso, ElasticNet도 동일 사용법 + scores = cross_val_score(model, X_train, y_train, + scoring='neg_mean_squared_error', cv=5) + print(f'alpha={alpha}, RMSE={np.sqrt(-scores.mean()):.3f}') +``` + +--- + +### 7.9 요인 분석 +> 여러 변수 이면에 있는 소수의 잠재요인(Factor)을 추출. +> 예: 14개 항목 설문 → 3개 요인('편안함', '서비스', '편의') + +**요인 수 결정 기준:** +- 고유값(Eigenvalue) ≥ 1인 요인 수 채택 +- 누적 설명력 60% 이상 확보 + +**회전 방법:** +- **Varimax** (직각회전): 요인 간 독립 유지, 가장 많이 사용 +- **Oblimin** (사각회전): 요인 간 상관 허용 + +```python +from factor_analyzer import FactorAnalyzer + +fa = FactorAnalyzer(n_factors=3, rotation='varimax') +fa.fit(X) + +# 고유값 확인 +ev, _ = fa.get_eigenvalues() +print(ev) + +# 요인 적재량 (0.5 이상이면 해당 요인에 속함) +loads = pd.DataFrame(fa.loadings_, index=X.columns, columns=['F1','F2','F3']) +print(loads) + +# 요인 점수로 변환 (차원 축소 효과) +factor_scores = fa.fit_transform(X) +``` + +**신뢰도 검정 (Cronbach's alpha):** +같은 요인에 묶인 변수들이 실제로 같은 개념을 측정하는지 확인. α ≥ 0.6이면 허용. +```python +import pingouin as pg +alpha, ci = pg.cronbach_alpha(data=df[['item1','item2','item3']]) +``` + +--- + +### 7.10 주성분 분석 (PCA) +> 변수 간 상관관계를 이용해 정보 손실을 최소화하면서 차원 축소. +> **요인분석과의 차이**: PCA는 분산 최대화가 목적, 요인분석은 잠재구조 해석이 목적. + +```python +from sklearn.decomposition import PCA +from sklearn.preprocessing import StandardScaler + +X_scaled = StandardScaler().fit_transform(X) # PCA 전 정규화 필수 + +pca = PCA(n_components=2) +X_pca = pca.fit_transform(X_scaled) + +# 각 주성분의 설명력 +print(pca.explained_variance_ratio_) +# [0.7296, 0.2285] → 2개로 95.8% 설명 +``` + +--- + +### 7.11 다차원척도법 (MDS) +> 개체 간 거리(유사도)를 보존하면서 저차원으로 시각화. + +```python +from sklearn.manifold import MDS + +# 계량적 MDS (구간/비율 척도) +mds = MDS(n_components=2) +X_mds = mds.fit_transform(X) + +# 비계량적 MDS (순서 척도) +nmds = MDS(n_components=2, metric=False) +X_nmds = nmds.fit_transform(X) +``` + +--- + +### 7.12 연관 분석 (장바구니 분석) +> 함께 구매(발생)하는 항목의 규칙을 찾는 분석. + +**핵심 지표:** +| 지표 | 의미 | 계산 | +|------|------|------| +| 지지도(Support) | A,B가 함께 발생한 비율 | P(A∩B) | +| 신뢰도(Confidence) | A 발생 시 B도 발생하는 비율 | P(A∩B) / P(A) | +| 향상도(Lift) | A 없을 때 대비 A 있을 때 B 증가율 | P(A∩B) / (P(A)×P(B)) | + +> Lift = 1: 독립 / Lift > 1: 양의 연관 / Lift < 1: 음의 연관 + +```python +from mlxtend.preprocessing import TransactionEncoder +from mlxtend.frequent_patterns import apriori, association_rules + +# 트랜잭션 데이터 인코딩 +dataset = [['우유','빵','계란'], ['우유','기저귀'], ['빵','계란','버터']] +te = TransactionEncoder() +df = pd.DataFrame(te.fit_transform(dataset), columns=te.columns_) + +# 빈발 항목집합 추출 +frequent = apriori(df, min_support=0.3, use_colnames=True, max_len=4) + +# 연관 규칙 생성 +rules = association_rules(frequent, metric='confidence', min_threshold=0.5) +rules.sort_values('lift', ascending=False).head(10) +``` diff --git a/doc_section4.md b/doc_section4.md new file mode 100644 index 0000000..092fa75 --- /dev/null +++ b/doc_section4.md @@ -0,0 +1,300 @@ +--- + +## 8. 기계학습 전처리 + +### 8.1 Train / Test 분리 +```python +from sklearn.model_selection import train_test_split + +X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=0.3, random_state=42, stratify=y # stratify: 클래스 비율 유지 +) +``` + +### 8.2 스케일링 +> **중요**: Scaler는 반드시 train에만 `fit`, test에는 `transform`만 적용. +> test에 `fit_transform`을 쓰면 데이터 누출(data leakage) 발생. + +```python +from sklearn.preprocessing import StandardScaler, MinMaxScaler + +scaler = StandardScaler() # 평균 0, 표준편차 1 +scaler = MinMaxScaler() # 최솟값 0, 최댓값 1 + +X_train_sc = scaler.fit_transform(X_train) # train: fit + transform +X_test_sc = scaler.transform(X_test) # test: transform만 + +# y 변수 로그 변환 (분포 왜곡 완화) +y_log = np.log1p(y) +# 예측 후 역변환 +y_pred_orig = np.expm1(y_pred_log) +``` + +### 8.3 오버샘플링 (불균형 데이터) +> **중요**: 오버샘플링은 train set에만 적용. test set에 적용하면 안 됨. + +```python +from imblearn.over_sampling import SMOTE + +smote = SMOTE(random_state=42) +X_train_res, y_train_res = smote.fit_resample(X_train, y_train) +``` + +### 8.4 변수 선택 — VIF 기반 +```python +from statsmodels.stats.outliers_influence import variance_inflation_factor +import statsmodels.api as sm + +# VIF > 10인 변수를 하나씩 제거하며 반복 +X_with_const = sm.add_constant(X_train) +vif = pd.Series( + [variance_inflation_factor(X_with_const.values, i) + for i in range(X_with_const.shape[1])], + index=X_with_const.columns +) +print(vif[vif > 10]) +``` + +--- + +## 9. 분류 알고리즘 + +### 공통 패턴 +```python +from sklearn.metrics import accuracy_score + +model.fit(X_train, y_train) +y_pred = model.predict(X_test) +accuracy_score(y_test, y_pred) +``` + +### 9.1 로지스틱 회귀 +> 선형 경계면으로 이진/다중 분류. 계수 해석 가능. 정규화에 민감. + +```python +from sklearn.linear_model import LogisticRegression + +model = LogisticRegression(C=1.0, penalty='l2', max_iter=1000) +# C: 1/alpha (작을수록 강한 규제) +# penalty: 'l1' (변수 선택), 'l2' (기본) +``` + +### 9.2 SVM (Support Vector Machine) +> 마진을 최대화하는 경계면 탐색. 커널로 비선형 분류 가능. 대용량 데이터에 느림. + +```python +from sklearn.svm import SVC + +model = SVC(kernel='rbf', C=1.0, gamma='scale', probability=True) +# kernel: 'linear', 'rbf', 'poly' +# C: 마진 오류 허용 정도 (클수록 overfitting 위험) +# probability=True: predict_proba() 사용 가능 +``` + +### 9.3 Random Forest +> 다수의 결정 트리 앙상블(배깅). 과적합에 강하고, 변수 중요도 제공. + +```python +from sklearn.ensemble import RandomForestClassifier + +model = RandomForestClassifier( + n_estimators=200, # 트리 수 (많을수록 안정, 느림) + max_depth=10, # 트리 깊이 (제한 없으면 과적합) + min_samples_leaf=5, + random_state=42 +) +model.fit(X_train, y_train) + +# 변수 중요도 +feat_imp = pd.Series(model.feature_importances_, index=X_train.columns) +feat_imp.sort_values(ascending=False).plot(kind='bar') +``` + +### 9.4 XGBoost +> 부스팅 계열. 잔차를 순서대로 학습. 높은 성능, early stopping 지원. + +```python +from xgboost import XGBClassifier + +model = XGBClassifier( + n_estimators=500, + learning_rate=0.05, + max_depth=5, + eval_metric='logloss', + use_label_encoder=False, + random_state=42 +) +model.fit(X_train, y_train, + early_stopping_rounds=50, + eval_set=[(X_test, y_test)], + verbose=False) +``` + +### 9.5 LightGBM +> XGBoost보다 빠르고 메모리 효율적. 대용량 데이터에 적합. + +```python +from lightgbm import LGBMClassifier + +model = LGBMClassifier(n_estimators=500, learning_rate=0.05, random_state=42) +model.fit(X_train, y_train, + callbacks=[lgb.early_stopping(50), lgb.log_evaluation(0)]) +``` + +### 9.6 MLP (다층 퍼셉트론, sklearn) +> 비선형 패턴 학습. 은닉층 구조와 활성함수 설정 필요. + +```python +from sklearn.neural_network import MLPClassifier + +model = MLPClassifier( + hidden_layer_sizes=(128, 64), # 은닉층 2개, 각 128·64개 뉴런 + activation='relu', # 'relu', 'tanh', 'logistic' + learning_rate_init=0.001, + max_iter=300, + random_state=42 +) +``` + +### 9.7 하이퍼파라미터 튜닝 (GridSearchCV) +```python +from sklearn.model_selection import GridSearchCV + +params = {'max_depth': [5, 10, 15], 'n_estimators': [100, 200]} +grid = GridSearchCV(RandomForestClassifier(random_state=42), + param_grid=params, cv=5, scoring='f1', n_jobs=-1) +grid.fit(X_train, y_train) + +print(grid.best_params_) +best_model = grid.best_estimator_ +``` + +### 9.8 Voting (앙상블) +```python +from sklearn.ensemble import VotingClassifier + +voting = VotingClassifier( + estimators=[('lr', LogisticRegression()), ('rf', RandomForestClassifier())], + voting='soft' # 확률 평균 / 'hard': 다수결 +) +``` + +--- + +## 10. 분류 성능 평가 + +### 혼동행렬 (Confusion Matrix) +``` + 예측 Positive 예측 Negative +실제 Positive TP FN +실제 Negative FP TN +``` + +```python +from sklearn.metrics import (confusion_matrix, accuracy_score, + precision_score, recall_score, + f1_score, roc_auc_score, roc_curve) + +cm = confusion_matrix(y_test, y_pred) + +accuracy = accuracy_score(y_test, y_pred) # (TP+TN) / 전체 +precision = precision_score(y_test, y_pred) # TP / (TP+FP) — 예측 P 중 실제 P 비율 +recall = recall_score(y_test, y_pred) # TP / (TP+FN) — 실제 P 중 예측 P 비율 +f1 = f1_score(y_test, y_pred) # 정밀도·재현율 조화평균 +auc = roc_auc_score(y_test, y_pred) + +# 특이도 (Specificity): TN / (TN+FP) +tn, fp, fn, tp = confusion_matrix(y_test, y_pred).ravel() +specificity = tn / (tn + fp) + +# 다중 클래스 +print(classification_report(y_test, y_pred)) +``` + +> **언제 무엇을 중시하나?** +> - 암 진단 → **재현율** 중시 (실제 환자를 놓치지 않아야 함) +> - 스팸 필터 → **정밀도** 중시 (정상 메일을 스팸으로 분류하면 안 됨) +> - 불균형 데이터 → **F1, AUC** + +### ROC 곡선 +> x축: FPR (1-특이도), y축: TPR (재현율). 곡선이 좌상단에 가까울수록 좋음. + +```python +proba = model.predict_proba(X_test)[:, 1] +fpr, tpr, thresholds = roc_curve(y_test, proba) + +plt.plot(fpr, tpr, label=f'AUC={auc:.3f}') +plt.plot([0, 1], [0, 1], 'k--') +plt.xlabel('FPR'); plt.ylabel('TPR') +plt.legend(); plt.show() +``` + +--- + +## 11. 회귀 성능 평가 + +```python +from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score + +mae = mean_absolute_error(y_test, y_pred) # 평균 절대 오차 +mse = mean_squared_error(y_test, y_pred) # 평균 제곱 오차 +rmse = np.sqrt(mse) # 루트 MSE (단위 동일) +r2 = r2_score(y_test, y_pred) # 설명력 (0~1, 1에 가까울수록 좋음) + +def mape(y_true, y_pred): + """평균 절대 백분율 오차 (%)""" + return np.mean(np.abs((y_true - y_pred) / y_true)) * 100 +``` + +--- + +## 12. 회귀 ML 알고리즘 + +### 선형 계열 +```python +from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet + +lr = LinearRegression() +ridge = Ridge(alpha=1.0) +lasso = Lasso(alpha=0.1) +enet = ElasticNet(alpha=0.1, l1_ratio=0.5) # l1_ratio: L1 비중 +``` + +### 트리 계열 +```python +from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor +from xgboost import XGBRegressor +from lightgbm import LGBMRegressor + +models = { + 'RF': RandomForestRegressor(n_estimators=200, random_state=42), + 'GBM': GradientBoostingRegressor(n_estimators=200, random_state=42), + 'XGB': XGBRegressor(n_estimators=200, random_state=42), + 'LGB': LGBMRegressor(n_estimators=200, random_state=42), +} + +for name, model in models.items(): + model.fit(X_train, y_train) + pred = model.predict(X_test) + print(f'{name}: RMSE={np.sqrt(mean_squared_error(y_test, pred)):.3f}') +``` + +### 혼합 모델 (앙상블) +```python +# 여러 모델 예측값의 가중 평균 +pred_final = 0.4 * xgb_pred + 0.4 * lgb_pred + 0.2 * ridge_pred +``` + +### 다항 회귀 +> 비선형 관계를 다항 특성 생성으로 선형 회귀에 적용. + +```python +from sklearn.preprocessing import PolynomialFeatures + +poly = PolynomialFeatures(degree=2, include_bias=False) +X_poly = poly.fit_transform(X_train) + +model = LinearRegression() +model.fit(X_poly, y_train) +pred = model.predict(poly.transform(X_test)) +``` diff --git a/doc_section5.md b/doc_section5.md new file mode 100644 index 0000000..3824b1a --- /dev/null +++ b/doc_section5.md @@ -0,0 +1,231 @@ +--- + +## 13. 군집 분석 (Clustering) + +> 비지도 학습. 레이블 없이 유사한 데이터끼리 그룹화. + +### 13.1 K-means +> 각 군집의 중심(centroid)과의 거리로 군집 할당. 구형 군집에 적합. + +```python +from sklearn.cluster import KMeans + +kmeans = KMeans(n_clusters=3, init='k-means++', random_state=42) +kmeans.fit(X) +labels = kmeans.labels_ # 각 데이터의 군집 번호 +centers = kmeans.cluster_centers_ + +# 군집 결과를 DataFrame에 추가 +df['cluster'] = labels +``` + +**최적 군집 수 찾기 — 엘보우(Elbow)** +```python +sse = [] +for k in range(1, 11): + km = KMeans(n_clusters=k, random_state=42) + km.fit(X) + sse.append(km.inertia_) # SSE (군집 내 분산 합) + +plt.plot(range(1, 11), sse, marker='o') +plt.xlabel('군집 수 k'); plt.ylabel('SSE') +# SSE 감소율이 꺾이는 지점(엘보우)이 최적 +``` + +**최적 군집 수 찾기 — 실루엣** +> 실루엣 계수: −1 ~ 1, 높을수록 좋은 군집화. + +```python +from sklearn.metrics import silhouette_score, silhouette_samples + +for k in range(2, 11): + km = KMeans(n_clusters=k, random_state=42) + labels = km.fit_predict(X) + score = silhouette_score(X, labels) + print(f'k={k}: silhouette={score:.3f}') +``` + +**군집 프로파일링** +```python +df.groupby('cluster')[feature_cols].mean() +# 군집별 평균 비교 → 각 군집의 특성 해석 +``` + +--- + +### 13.2 계층적 군집화 (Hierarchical Clustering) +> 거리를 기반으로 가까운 것끼리 병합. 덴드로그램으로 시각화. + +```python +from sklearn.cluster import AgglomerativeClustering +from scipy.cluster.hierarchy import dendrogram, linkage + +# 덴드로그램 (군집 수 결정에 활용) +Z = linkage(X, method='ward') +dendrogram(Z) +plt.show() + +# 군집화 수행 +model = AgglomerativeClustering(n_clusters=3, linkage='ward') +labels = model.fit_predict(X) +``` + +--- + +### 13.3 DBSCAN (밀도 기반) +> 밀도가 높은 영역을 군집으로. 군집 수 사전 지정 불필요. 이상치 자동 탐지(레이블 −1). + +```python +from sklearn.cluster import DBSCAN + +db = DBSCAN(eps=0.5, # 이웃 반경 + min_samples=5) # 핵심 포인트 최소 이웃 수 +labels = db.fit_predict(X) +# labels == -1: 이상치(노이즈) +``` + +--- + +### 13.4 GMM (Gaussian Mixture Model) +> 여러 가우시안 분포의 혼합으로 데이터를 설명. 확률 기반 군집화. + +```python +from sklearn.mixture import GaussianMixture + +gmm = GaussianMixture(n_components=3, random_state=42) +labels = gmm.fit_predict(X) +proba = gmm.predict_proba(X) # 각 군집에 속할 확률 +``` + +--- + +### 13.5 군집화 결과 시각화 (PCA 활용) +```python +from sklearn.decomposition import PCA + +pca = PCA(n_components=2) +X_2d = pca.fit_transform(X) + +plt.scatter(X_2d[:, 0], X_2d[:, 1], c=labels, cmap='viridis') +plt.xlabel('PC1'); plt.ylabel('PC2') +plt.title('Clustering Result (PCA 2D)') +plt.show() +``` + +--- + +## 14. 시계열 분석 + +### 14.1 데이터 준비 +```python +df = pd.read_csv('data.csv') +df['date'] = pd.to_datetime(df['date']) +df = df.set_index('date').sort_index() +``` + +### 14.2 정상성(Stationarity) +> 정상 시계열: 평균·분산이 시간에 관계없이 일정. ARIMA 적합 조건. + +**시각적 확인**: 추세, 계절성, 분산 변화가 없으면 정상. + +**ADF 검정** (Augmented Dickey-Fuller): +```python +from statsmodels.tsa.stattools import adfuller + +result = adfuller(df['value']) +print(f'ADF Statistic: {result[0]:.4f}') +print(f'p-value: {result[1]:.4f}') +# p < 0.05 → 정상 시계열 (귀무가설: 단위근 존재 = 비정상) +``` + +### 14.3 차분 (Differencing) +> 비정상 시계열을 정상으로 변환. 1차 차분으로 추세 제거. + +```python +df['diff1'] = df['value'].diff() # 1차 차분 +df['diff2'] = df['value'].diff().diff() # 2차 차분 +df_diff = df['diff1'].dropna() +``` + +### 14.4 ACF / PACF +> **ACF** (자기상관함수): q (MA 차수) 결정 +> **PACF** (편자기상관함수): p (AR 차수) 결정 + +```python +import statsmodels.api as sm + +fig, ax = plt.subplots(1, 2, figsize=(12, 4)) +sm.graphics.tsa.plot_acf(df_diff, lags=30, ax=ax[0]) +sm.graphics.tsa.plot_pacf(df_diff, lags=30, ax=ax[1]) +plt.show() +``` + +**모델 선택 기준 (차분 후 ACF/PACF)**: +| ACF 패턴 | PACF 패턴 | 모델 | +|----------|----------|------| +| 지수 감소 | lag p 이후 절단 | AR(p) | +| lag q 이후 절단 | 지수 감소 | MA(q) | +| 둘 다 지수 감소 | 둘 다 지수 감소 | ARMA(p,q) | + +### 14.5 ARIMA(p, d, q) +- **p**: AR 차수 (PACF에서 결정) +- **d**: 차분 횟수 (정상화에 필요한 차분 수) +- **q**: MA 차수 (ACF에서 결정) + +```python +from statsmodels.tsa.arima.model import ARIMA + +train, test = df[:int(len(df)*0.8)], df[int(len(df)*0.8):] + +# 여러 (p,d,q) 조합 비교 — AIC 기준 선택 +import itertools +best_aic, best_order = np.inf, None +for order in itertools.product(range(3), [1], range(3)): + try: + model = ARIMA(train['value'], order=order).fit() + if model.aic < best_aic: + best_aic, best_order = model.aic, order + except: + continue +print(f'Best order: {best_order}, AIC: {best_aic:.2f}') + +# 최적 모델로 예측 +model = ARIMA(train['value'], order=best_order).fit() +pred = model.forecast(steps=len(test)) +``` + +### 14.6 SARIMA(p,d,q)(P,D,Q,s) +> 계절성이 있는 시계열. s = 계절 주기 (월별=12, 분기=4 등). + +```python +from statsmodels.tsa.statespace.sarimax import SARIMAX + +model = SARIMAX(train['value'], + order=(1, 1, 1), + seasonal_order=(1, 1, 1, 12)) +result = model.fit(disp=False) + +pred = result.get_forecast(steps=len(test)) +pred_mean = pred.predicted_mean +pred_ci = pred.conf_int() # 95% 신뢰구간 +``` + +### 14.7 시계열 성능 평가 +```python +from sklearn.metrics import mean_absolute_error, mean_squared_error + +mae = mean_absolute_error(test['value'], pred_mean) +rmse = np.sqrt(mean_squared_error(test['value'], pred_mean)) +mape = np.mean(np.abs((test['value'] - pred_mean) / test['value'])) * 100 +r2 = r2_score(test['value'], pred_mean) + +print(f'MAE={mae:.2f}, RMSE={rmse:.2f}, MAPE={mape:.2f}%, R²={r2:.4f}') +``` + +### 14.8 계절 분해 +```python +decomp = sm.tsa.seasonal_decompose(df['value'], model='additive', period=12) +decomp.plot() +# Observed = Trend + Seasonal + Residual (additive) +# Observed = Trend × Seasonal × Residual (multiplicative) +``` diff --git a/doc_section6.md b/doc_section6.md new file mode 100644 index 0000000..bfe247d --- /dev/null +++ b/doc_section6.md @@ -0,0 +1,293 @@ +--- + +## 15. 텍스트 마이닝 + +### 15.1 영어 텍스트 처리 (NLTK) + +```python +import nltk +from nltk import word_tokenize, sent_tokenize +from nltk.corpus import stopwords +from nltk.stem import WordNetLemmatizer + +text = "The cats are running quickly through the forest." + +# 토큰화 +tokens = word_tokenize(text.lower()) +# ['the', 'cats', 'are', 'running', ...] + +# 불용어 제거 +stop_words = set(stopwords.words('english')) +tokens = [t for t in tokens if t not in stop_words and t.isalpha()] +# ['cats', 'running', 'quickly', 'forest'] + +# 표제어 추출 (Lemmatization) +lemma = WordNetLemmatizer() +tokens = [lemma.lemmatize(t, pos='v') for t in tokens] +# ['cat', 'run', 'quickly', 'forest'] +``` + +> **Stemming vs Lemmatization** +> - Stemming: 규칙 기반 어간 추출 (빠르나 부정확. 예: running → run, studies → studi) +> - Lemmatization: 품사를 고려한 정확한 원형 복원 (느리나 정확. 예: studies → study) + +--- + +### 15.2 한국어 형태소 분석 (KoNLPy) + +| 분석기 | 특징 | +|--------|------| +| Okt (구 Twitter) | 가볍고 빠름, 신조어 처리 양호 | +| Komoran | 정확도 높음, 사용자 사전 지원 | +| Kkma | 세밀한 품사 태깅, 느림 | +| Mecab | 가장 빠름, 별도 설치 필요 | + +```python +from konlpy.tag import Komoran + +komoran = Komoran() +# 사용자 사전 적용 시: Komoran(userdic='사전.txt') + +text = "자연어 처리는 재미있어요" + +komoran.nouns(text) # 명사만 추출: ['자연어', '처리'] +komoran.pos(text) # 품사 태깅: [('자연어','NNG'), ('처리','NNG'), ...] +komoran.morphs(text) # 형태소 분리: ['자연어', '처리', '는', '재미있', '어요'] +``` + +**주요 품사 코드**: +| 코드 | 의미 | +|------|------| +| NNG | 일반 명사 | +| NNP | 고유 명사 | +| VV | 동사 | +| VA | 형용사 | +| MA | 부사 | + +--- + +### 15.3 텍스트 정제 (정규표현식) + +```python +import re + +text = "영화 기생충 리뷰!! 😂 #기생충 010-1234-5678" + +# 특수문자 제거 +text = re.sub(r'[^\w\s]', ' ', text) + +# 숫자 제거 +text = re.sub(r'\d+', ' ', text) + +# HTML 태그 제거 +text = re.sub(r'<[^>]+>', ' ', text) + +# 연속 공백 제거 +text = re.sub(r'\s+', ' ', text).strip() +``` + +--- + +### 15.4 단어 빈도 분석 + +```python +import collections + +# 명사 추출 후 빈도 계산 +nouns = komoran.nouns(' '.join(documents)) +freq = collections.Counter(nouns) + +# 2음절 이상만 선택 +freq_filtered = {w: c for w, c in freq.items() if len(w) >= 2} + +# 상위 20개 시각화 +freq_df = pd.DataFrame(freq_filtered.items(), columns=['word','count']) +freq_df.sort_values('count', ascending=False).head(20).plot( + kind='bar', x='word', y='count') +``` + +--- + +### 15.5 TF-IDF + +> **TF** (단어 빈도): 해당 문서에서 단어가 얼마나 자주 등장하는가 +> **IDF** (역문서 빈도): 전체 문서 중 해당 단어가 드물게 등장할수록 높은 가중치 +> **TF-IDF = TF × IDF** — 특정 문서에만 자주 나오는 단어에 높은 가중치 + +```python +from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer + +# 영어 (자동 토큰화) +tfidf = TfidfVectorizer(stop_words='english', max_features=5000, ngram_range=(1,2)) +X = tfidf.fit_transform(train_docs) # train에만 fit +X_test_vec = tfidf.transform(test_docs) + +# 한국어 (사용자 토크나이저 전달) +def ko_tokenizer(text): + return [n for n in komoran.nouns(text) if len(n) >= 2] + +tfidf_ko = TfidfVectorizer(tokenizer=ko_tokenizer, max_features=3000) +X_ko = tfidf_ko.fit_transform(documents) +``` + +--- + +### 15.6 텍스트 분류 + +```python +from sklearn.linear_model import LogisticRegression +from sklearn.metrics import classification_report + +# TF-IDF → 분류 (Pipeline으로 묶으면 편리) +from sklearn.pipeline import Pipeline + +pipeline = Pipeline([ + ('tfidf', TfidfVectorizer(stop_words='english', ngram_range=(1,2))), + ('clf', LogisticRegression(C=5, max_iter=1000)) +]) +pipeline.fit(X_train_text, y_train) +y_pred = pipeline.predict(X_test_text) +print(classification_report(y_test, y_pred)) +``` + +--- + +## 16. 딥러닝 (TensorFlow / Keras) + +### 16.1 MLP 분류 +```python +import tensorflow as tf +from tensorflow import keras + +model = keras.Sequential([ + keras.layers.Dense(128, activation='relu', input_shape=(n_features,)), + keras.layers.Dropout(0.3), # 과적합 방지 + keras.layers.Dense(64, activation='relu'), + keras.layers.Dense(n_classes, activation='softmax') # 다중분류 + # 이진분류: Dense(1, activation='sigmoid') +]) + +model.compile( + optimizer='adam', + loss='sparse_categorical_crossentropy', # 정수 레이블 + # loss='categorical_crossentropy' # one-hot 레이블 + # loss='binary_crossentropy' # 이진분류 + metrics=['accuracy'] +) + +history = model.fit(X_train, y_train, + epochs=50, batch_size=32, + validation_split=0.2, + verbose=1) +``` + +### 16.2 MLP 회귀 +```python +model = keras.Sequential([ + keras.layers.Dense(128, activation='relu', input_shape=(n_features,)), + keras.layers.Dense(64, activation='relu'), + keras.layers.Dense(1) # 출력층: 활성함수 없음 (선형) +]) + +model.compile(optimizer='adam', loss='mse', metrics=['mae']) +``` + +### 16.3 RNN / LSTM + +> **순서 있는 데이터** (시계열, 텍스트)에 적합. +> LSTM은 장기 의존성 문제(vanishing gradient)를 해결한 RNN 변형. + +```python +# 입력 shape: (samples, timesteps, features) + +model = keras.Sequential([ + keras.layers.LSTM(64, return_sequences=True, # 중간 층: True + input_shape=(timesteps, n_features)), + keras.layers.LSTM(32), # 마지막 RNN: False (기본) + keras.layers.Dense(1) +]) +``` + +> `return_sequences=True`: 각 타임스텝의 출력을 모두 반환 (다음 RNN 층에 연결할 때 필요) +> `return_sequences=False` (기본): 마지막 타임스텝 출력만 반환 + +**양방향 LSTM (Bidirectional)**: +```python +keras.layers.Bidirectional(keras.layers.LSTM(64)) +# 순방향 + 역방향을 결합 → 텍스트 분류에서 성능 향상 +``` + +### 16.4 시계열 LSTM 데이터 준비 +```python +def make_sequences(series, window): + """시계열을 슬라이딩 윈도우로 X, y 분리""" + X, y = [], [] + for i in range(len(series) - window): + X.append(series[i:i+window]) + y.append(series[i+window]) + return np.array(X), np.array(y) + +window = 12 +X, y = make_sequences(values, window) +X = X.reshape(X.shape[0], X.shape[1], 1) # (samples, timesteps, 1) +``` + +### 16.5 학습 시각화 & 조기 종료 +```python +# 조기 종료 (과적합 방지) +early_stop = keras.callbacks.EarlyStopping( + monitor='val_loss', patience=10, restore_best_weights=True) + +history = model.fit(X_train, y_train, epochs=200, callbacks=[early_stop], + validation_data=(X_val, y_val), verbose=0) + +# 학습 곡선 +plt.plot(history.history['loss'], label='train') +plt.plot(history.history['val_loss'], label='val') +plt.legend(); plt.xlabel('Epoch'); plt.ylabel('Loss') +``` + +--- + +## 17. 교차 검증 (Cross Validation) + +```python +from sklearn.model_selection import cross_val_score, KFold, StratifiedKFold + +# 회귀: neg_mean_squared_error 사용 (sklearn 규칙: 큰 값이 좋음 → 음수) +scores = cross_val_score(model, X, y, + scoring='neg_mean_squared_error', cv=5) +rmse = np.sqrt(-scores.mean()) + +# 분류: accuracy, f1, roc_auc +scores = cross_val_score(model, X, y, scoring='f1', cv=5) + +# 불균형 데이터: StratifiedKFold (각 fold에서 클래스 비율 유지) +skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) +scores = cross_val_score(model, X, y, cv=skf, scoring='roc_auc') +``` + +--- + +## 18. 정규표현식 빠른 참조 + +| 패턴 | 의미 | 예 | +|------|------|----| +| `\d` | 숫자 | `\d+` → 1개 이상 숫자 | +| `\D` | 숫자 외 | | +| `\w` | 문자·숫자·_ | | +| `\W` | 문자·숫자·_ 외 | | +| `\s` | 공백 | `\s+` → 1개 이상 공백 | +| `.` | 임의 문자 1개 | | +| `*` | 0회 이상 반복 | | +| `+` | 1회 이상 반복 | | +| `?` | 0회 또는 1회 | | +| `[abc]` | a, b, c 중 하나 | | +| `[^abc]` | a, b, c 제외 | | + +```python +import re +re.sub(r'[^\가-힣\s]', '', text) # 한글과 공백만 남기기 +re.findall(r'\d+', text) # 모든 숫자 추출 +re.split(r'\s+', text) # 공백 기준 분리 +```