집앞의 횡단보도를 자주 지나가는데, 횡단보도를 기다릴지 아닐지 늘 고민하면서 지나갑니다.'4명 기다리고 있는데 금방 바뀌지 않을까? 좀 더 가서 사거리에서 건너는게 나을까?' 저는 데이터를 수집해서 언제쯤 횡단보도가 바뀌는지, 그리고 이를 활용할 수 있는 분포는 뭐가 있는지 확인해보자는 문제의식을 정의하고 해결하는 방법을 작성했습니다. 월간 데이터노트 4기의 스터디로 진행된 분석입니다. 월간 데이터노트 참여에 관심이 있다면 다음 공지를 확인해주세요!

월간데이터 노트 홈페이지

월간데이터 노트 링크드인


1. 주변상황 정보

  • 횡단보도 기준 동편에는 1710세대, 서편에는 1068세대의 아파트 단지
  • 횡단보도 바로 앞에 카페, 빠리바게트 등의 소규모 상가가 있으며, 도서관은 현재 운영 X
  • 북쪽에 역시 대단지, 남쪽에는 공공기관이 있음
  • 횡단보도를 이용하는 사람은 주민, 학생, 출퇴근 직장인들이 주류를 이루며, 10시/ 4시 정도에는 일부 유모차를 동반한 엄마가 등장

2. 데이터 수집

  • 4차선도로이며 횡단보도 기준 빨간불 2분, 파란불 25초
  • 제가 주로 이동하는 시간인 출퇴근/점심시간/퇴근 시간에 수시로 22개 수기 수집

  • 평균 4.1명, 중위 3.5, 표준편차 3.6명, IQR 기준 이상치 2개(13, 14)

  • 시간대 분포

3. 분포 모델 검토

처음에는 포아송 분포라고 생각하고 호기심 발제했음
  • 포아송 분포
    • 정의: 일정 시간 & 공간 내에 독립적으로 발생하는 사건의 횟수를 모델링
    • 분포의 특징과 적합성 체크
      • 사건은 서로 독립적으로 발생 → 맞음
      • 사건은 동시에 발생하지 않음 → 동시에 사람들이 도착하기도 함
      • 평균 발생률은 관측 구간 내 일정 → 맞음
      • 분산/평균의 값이 1임 → 아님 3.01임 (과산포임)
다른 조건이 어느정도 맞으나 분산과 평균이 같지 않아서 Claude가 음이항 분포를 추천함
  • 음이항 분포
    • 정의: r번째 성공을 관측할 때 까지 필요한 시행 횟수나 성공 확률이 p인 베르누이 시행에서 r번의 성공을 관측할때까지 실패한 횟수의 분포
    • 분포의 특징과 적합성 체크
      • 분산이 평균보다 큰 과산포(overdispersion) 데이터에 적합
        • Ex) 특정 지역의 범죄 발생 건수(범죄가 특정 지역에 집중)
        • Ex2) 소비자 구매 행동(일부 고객이 훨씬 더 자주 구매)
    • 데이터에 과산포가 존재(분산> 평균) → 맞음
    • 사건 발생이 군집화 되는 경향이 있음 → 맞음 일부시간대 몰림
    • 개체별로 발생률에 차이가 있음 → 있을 수 있음(주부/학생 등)
    • 발생 확률이 시간에 따라 변할 수 있음 → 그러함

4. 분석

  1. 히스토그램 및 분포 시각화
  2. 포아송 분포와 음이항 분포 적합
  3. 적합도 검정 (카이제곱 검정)
  4. AIC 및 BIC를 사용한 모델 비교
import matplotlib.pyplot as plt
import platform

def set_matplotlib_font():
    system = platform.system()

    if system == "Windows":
        plt.rc('font', family='Malgun Gothic')
    elif system == "Darwin":  # macOS
        plt.rc('font', family='AppleGothic')
    elif system == "Linux":
        plt.rc('font', family='NanumGothic')
    else:
        print("Unknown system. Please set font manually.")

    plt.rcParams['axes.unicode_minus'] = False

# 폰트 설정 함수 호출
set_matplotlib_font()

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import scipy.stats as stats
from statsmodels.discrete.discrete_model import Poisson, NegativeBinomial

df = data.copy()
df.columns = ['timestamp','count']
print("데이터 샘플:")
print(df.head())

# 기본 통계량
print("\n기본 통계량:")
print(df['count'].describe())

mean_count = df['count'].mean()
var_count = df['count'].var()
print(f"평균: {mean_count:.4f}")
print(f"분산: {var_count:.4f}")
print(f"분산/평균 비율: {var_count/mean_count:.4f}")

# 히스토그램 및 분포 시각화
plt.figure(figsize=(12, 6))

# 히스토그램
plt.subplot(1, 2, 1)
# sns.histplot(df['count'], kde=True, stat='density', discrete=True)
df['count'].hist(density = True)
plt.title('횡단보도 건너는 사람 수 히스토그램')
plt.xlabel('사람 수')
plt.ylabel('빈도')

# 경험적 분포와 이론적 분포 비교
x = np.arange(0, df['count'].max() + 1)

# 포아송 분포 확률 질량 함수
poisson_pmf = stats.poisson.pmf(x, mean_count)

# 음이항 분포 매개변수 추정
# 방법 1: 모멘트 방법
r = mean_count**2 / (var_count - mean_count) if var_count > mean_count else 100
p = mean_count / var_count if var_count > mean_count else mean_count / (mean_count + 1)

# 음이항 분포 확률 질량 함수
nb_pmf = stats.nbinom.pmf(x, r, p)

plt.subplot(1, 2, 2)
plt.bar(x, [df['count'].value_counts().get(i, 0) / len(df) for i in x], alpha=0.5, label='실제 데이터')
plt.plot(x, poisson_pmf, 'ro-', label=f'포아송 분포 (λ={mean_count:.2f})')
plt.plot(x, nb_pmf, 'go-', label=f'음이항 분포 (r={r:.2f}, p={p:.2f})')
plt.title('경험적 분포와 이론적 분포 비교')
plt.xlabel('사람 수')
plt.ylabel('확률')
plt.legend()
plt.tight_layout()

# 포아송 분포 적합도 검정 (카이제곱 검정)
observed = np.zeros(df['count'].max() + 1)
for i in range(len(observed)):
    observed[i] = df['count'].value_counts().get(i, 0)

expected = len(df) * stats.poisson.pmf(np.arange(len(observed)), mean_count)

# 카이제곱 값 계산
# 기대빈도가 5 미만인 셀은 통합
min_expected = 5
observed_adj = []
expected_adj = []
last_obs = 0
last_exp = 0
for obs, exp in zip(observed, expected):
    if exp >= min_expected:
        observed_adj.append(obs)
        expected_adj.append(exp)
    else:
        last_obs += obs
        last_exp += exp

if last_exp > 0:
    observed_adj.append(last_obs)
    expected_adj.append(last_exp)

observed_adj = np.array(observed_adj)
expected_adj = np.array(expected_adj)

chi2 = np.sum((observed_adj - expected_adj) ** 2 / expected_adj)
df_chi2 = len(observed_adj) - 1 - 1  # 매개변수 1개 추정 (자유도 = 셀 수 - 1 - 추정된 매개변수 수)
p_value = 1 - stats.chi2.cdf(chi2, df_chi2) if df_chi2 > 0 else None

print("\n포아송 분포 적합도 검정 (카이제곱 검정):")
print(f"카이제곱 값: {chi2:.4f}")
print(f"자유도: {df_chi2}")
print(f"p-value: {p_value if p_value is not None else '자유도가 0 이하'}")
print(f"결론: {'포아송 분포에 적합하지 않음 (p < 0.05)' if p_value is not None and p_value < 0.05 else '포아송 분포에 적합함 (p >= 0.05)' if p_value is not None else '검정 불가 (자유도 부족)'}")

# AIC 및 BIC를 사용한 모델 비교
# 인공 변수를 만들어 모델 적합 (상수 모델)
X = np.ones((len(df), 1))
y = df['count'].values

# 포아송 회귀 모델
poisson_model = Poisson(y, X)
poisson_results = poisson_model.fit(disp=0)

# 음이항 회귀 모델
try:
    nb_model = NegativeBinomial(y, X)
    nb_results = nb_model.fit(disp=0)
    
    print("\n모델 비교 (AIC 및 BIC):")
    print(f"포아송 모델 AIC: {poisson_results.aic:.4f}")
    print(f"음이항 모델 AIC: {nb_results.aic:.4f}")
    print(f"포아송 모델 BIC: {poisson_results.bic:.4f}")
    print(f"음이항 모델 BIC: {nb_results.bic:.4f}")
    print(f"더 적합한 모델: {'음이항 분포' if nb_results.aic < poisson_results.aic else '포아송 분포'} (AIC 기준)")
except:
    print("\n음이항 회귀 모델을 적합할 수 없습니다. 데이터가 충분하지 않거나 모델이 수렴하지 않습니다.")

# 시간대별 분석
df['hour'] = df['timestamp'].dt.hour
df['time_of_day'] = pd.cut(df['hour'], 
                          bins=[0, 12, 18, 24], 
                          labels=['아침(0-12시)', '오후(12-18시)', '저녁(18-24시)'],
                          include_lowest=True)

time_stats = df.groupby('time_of_day')['count'].agg(['mean', 'var', 'count'])
time_stats['var/mean'] = time_stats['var'] / time_stats['mean']

print("\n시간대별 통계:")
print(time_stats)

# 시간대별 분포 시각화
plt.figure(figsize=(10, 6))
sns.boxplot(x='time_of_day', y='count', data=df)
plt.title('시간대별 횡단보도 건너는 사람 수 분포')
plt.xlabel('시간대')
plt.ylabel('사람 수')

plt.tight_layout()
plt.show()

# 결론
print("\n분포 분석 결론:")
if var_count > mean_count:
    print(f"분산({var_count:.4f})이 평균({mean_count:.4f})보다 크므로 과산포(overdispersion)가 존재합니다.")
    print("따라서 포아송 분포보다 음이항 분포가 더 적합할 수 있습니다.")
elif var_count < mean_count:
    print(f"분산({var_count:.4f})이 평균({mean_count:.4f})보다 작으므로 과소산포(underdispersion)가 존재합니다.")
    print("이 경우 포아송 분포보다 이항 분포가 더 적합할 수 있습니다.")
else:
    print(f"분산({var_count:.4f})이 평균({mean_count:.4f})과 거의 같으므로 포아송 분포에 적합합니다.")

  • 모델 비교 (AIC 및 BIC) ->  더 적합한 모델: 음이항 분포 (AIC 기준)
    • 포아송 모델 AIC: 126.3588
    • 음이항 모델 AIC: 111.9646
    • 포아송 모델 BIC: 127.4498
    • 음이항 모델 BIC: 114.1467
  • 모수 추정 방법: 모먼트 방법과 최대 우도 추정법 존재
    • 모멘트 방법
# 데이터의 평균과 분산 계산
mean = np.mean(data)
var = np.var(data, ddof=1)  # 표본 분산

# 모수 추정
p = mean / var
r = mean * p / (1 - p)

5. 결론

  • 눈으로 보기엔 포아송 분포가 더 잘 맞는거같은데 AIC기준으로는 음이항 분포가 더 잘맞다고 한다.
  • 카이제곱을 검정을 쓰는 이유는 관측된 빈도와 분포의 기대 빈도와 비교하는 방법이기 떄문
  • AIC/BIC가 사용된 이유는 다른 통계 모델의 상대적 품질을 비교하는 지표이기 때문(상대적 평가)
  • 음이항 분포의 모수
    • r(성공 횟수): 1.94
    • p(성공 확률): 0.32
위 내용을 가지고 95%의 신뢰도로 보행자 수는 11명  -> 엥? 너무 많고 비현실적임, 수집된 데이터중 13,14명인 사례는 출퇴근,점심시간 이였으며 10명 이상 건너는 상황은 잘 발견되지 않음, 50%정도만 넘어도 기다릴 가치는 있다고 생각되어 수정
from scipy.stats import nbinom

# 95% 확률 범위의 최대값 계산
max_count_95 = nbinom.ppf(0.95, r, p)
max_count_95
# 11명

# 50% 확률 범위의 최대값 계산
max_count_95 = nbinom.ppf(0.5, r, p)
max_count_95
# 3명

누적확률 50% 이상은 3명, 3명 있으면 기다릴 가치가 있다.

6. 회고

  • GPT는 GP"F"로 바꿔야함. 앞뒤 서론이 너무 길고 공감 투가 너무 많음. Claude는 코딩능력은 좋은데 필요 이상으로 스켈레톤 코드를 제공해주며, 단순 수식 계산도 틀린 점이 자주 발견됨
  • 포아송,음이항 분포 좀 다시 고찰할 필요가 있음. 내 상황에 적절한 분포를 선정하고 데이터를 수집하는게 좋겠다.
  • 균등한 데이터 수집을 위해서 하루종일 혹은 대표성을 띄는 시간에 주기적으로 측정하는게 좋겠다.(평소에 부지런하자!)
    • Ex) 평일 오전 출근시간 가정 8-9시 집중관찰

 

본 주제는 매달 한 번씩 호기심을 주제로 분석하는 모임 <월간 데이터 노트>의 결과입니다.
관심이 있으시면 다음 링크를 확인해 보세요!!

 

지난번 통계편에 이은 머신러닝문서입니다! 잘못된 내용이나 모호한 내용, 추가되어야하는 내용이 있으면 지적해주세요! 다들 시험에 합격하시길 바라요~


0. 목차

클릭시 해당 컨텐츠로 이동합니다.

목차

    https://snowgot.tistory.com/181

     

    ADP 빅분기 이론과 Python 모듈, 실습코드 정리 - 통계편

    ADP 시험을 준비하기 위하여 정리했던 방법론과 코드를 저장합니다.빅분기는 범위가 더 좁지만 공통된 부분이 많습니다. 선형계획법이나 기타 내용은 저도 몰라서 못넣었습니다 🤣 잘못된 내용

    snowgot.tistory.com

     

    1. 모듈별 디렉토리 정리

    •  ADP 지원 버전: Python 3.7.(공지사항)
    • ADP 기본제공 Python Package & version
    pip install ipykernel=5.1.0
    pip install numpy==1.12.6
    pip install pandas==1.1.2
    pip install scipy==1.7.3
    pip install matplotlib==3.0.3
    pip install seaborn==0.9.0
    pip install statsmodels==0.13.2
    pip install scikit-learn==0.23.2
    pip install imbalanced-learn==0.5.0

    [제34회ADP실기]기본제공_python_package_list.txt
    0.01MB

    2. 머신러닝 - 지도학습

    • 전체 프로세스
      1. 데이터 로딩 및 탐색
      2. 결측치 처리
      3. 이상치 탐지 및 처리
      4. 범주형 변수 인코딩
      5. 데이터 스케일링
      6. 불균형 데이터 처리
      7. 모델링 및 성능 평가

    2.1. 결측치 처리 방법

    방법 설명 예시
    제거 결츷치가 적을 경우 df.dropna()
    평균/중앙값 대치 연속형 변수 df['column'].fillna(_mean)
    최빈값 대치 범주형 변수 df['column'].fillna(_mode)
    KNN Imputer 패턴 반영대체 from sklearn.impute import KNNImputer
    Simple Imputer 간단한 대치 from sklearn.impute import SimpleImputer
    • KNN 대치
    from sklearn.impute import KNNImputer
    imputer = KNNImputer(n_neighbors= 2)
    y = df[['y']]
    X = df.drop(columns= 'y')
    X_impute = imputer.fit_transform(X)
    
    #fit_transform결과가 ndarray로 나오므로 데이터프레임으로 원복
    pd.DataFrame(X_impute, columns = X.columns, index = df.index)
    • Simple Imputer 대치
    import numpy as np
    from sklearn.impute import SimpleImputer
    imp_mean = SimpleImputer(missing_values=np.nan, strategy='mean')
    imp_mean.fit([[7, 2, 3], [4, np.nan, 6], [10, 5, 9]])
    SimpleImputer()
    X = [[np.nan, 2, 3], [4, np.nan, 6], [10, np.nan, 9]]
    print(imp_mean.transform(X))
    
    [[ 7.   2.   3. ]
     [ 4.   3.5  6. ]
     [10.   3.5  9. ]]

    2.2. 이상치 처리 방법

    기법 설명 예시
    IQR 방식  사분위수 기반 Q1 - 1.5*IQR, Q3 + 1.5*IQR
    Z-score  표준정규분포 기반  abs(z) > 3
    시각화 boxplot, scatter 사용 sns.boxplot( x= df['col'])
    • IQR을 이용한 이상치 처리
    import numpy as np
    # 1. IQR(Interquartile Range) 방법을 이용한 이상치 탐지
    def detect_outliers_iqr(df, col):
        Q1 = df[col].quantile(0.25)
        Q3 = df[col].quantile(0.75)
        IQR = Q3 - Q1
        lower_bound = Q1 - 1.5 * IQR
        upper_bound = Q3 + 1.5 * IQR
        return df[(df[col] < lower_bound) | (df[col] > upper_bound)]
    
    # 각 수치형 변수별 이상치 확인
    for col in df_impute_X.columns:
        outliers = detect_outliers_iqr(df_impute_X, col)
        print(f'Column {col} has {len(outliers)} outliers')
    
    # 2. 이상치 처리 방법
    # 방법 1: 상한/하한 경계값으로 대체 (Capping)
    def treat_outliers_capping(df, col):
        Q1 = df[col].quantile(0.25)
        Q3 = df[col].quantile(0.75)
        IQR = Q3 - Q1
        lower_bound = Q1 - 1.5 * IQR
        upper_bound = Q3 + 1.5 * IQR
        df[col] = np.where(df[col] < lower_bound, lower_bound, df[col])
        df[col] = np.where(df[col] > upper_bound, upper_bound, df[col])
        return df
    
    # 수치형 변수에 대해 이상치 처리
    data_cleaned_cap = df_impute_X.copy()
    for col in df_impute_X.columns:
        data_cleaned_cap = treat_outliers_capping(data_cleaned_cap, col)
    
    # 방법 2: 이상치가 있는 행 제거 (데이터가 충분할 경우만)
    def remove_outliers(df, cols):
        df_clean = df.copy()
        for col in cols:
            Q1 = df[col].quantile(0.25)
            Q3 = df[col].quantile(0.75)
            IQR = Q3 - Q1
            lower_bound = Q1 - 1.5 * IQR
            upper_bound = Q3 + 1.5 * IQR
            df_clean = df_clean[(df_clean[col] >= lower_bound) & (df_clean[col] <= upper_bound)]
        return df_clean
    
    # 중요 변수에 대해서만 이상치 행 제거 (모든 변수에 적용하면 데이터 손실이 클 수 있음)
    important_numeric_cols = ['ALB', 'ALP', 'ALT', 'AST', 'BIL', 'CHE', 'CHOL', 'CREA', 'GGT', 'PROT']
    data_no_outliers = remove_outliers(df_impute_X, important_numeric_cols)
    display(data_no_outliers)

    2.3. 범주형 변수인코딩

    방법 예시 비고
    Label Encoding 순서형 변수 from sklearn.preprocessing import LabelEncoder
    One-hot Encoding 명목형 변수 import pd
    pd.get_dummies()
    • Label Encoding 방법
    from sklearn import preprocessing
    le = preprocessing.LabelEncoder()
    le.fit([1, 2, 2, 6])
    LabelEncoder()
    le.classes_
    #array([1, 2, 6])
    le.transform([1, 1, 2, 6])
    #array([0, 0, 1, 2]...)

    2.3. 데이터 시각화

    sns.heatmap(df.corr(), annot= True, cmap = "YlGnBu" )

    2.4. PCA

    • 차원축소기법으로 데이터의 분산을 최대한 보존하면서 고차원이 데이터를 저차원으로 변환
    • 판단 기준
      • 분산 설명률: 80~90%의 분산을 설명하는데 필요한 주성분이 원래 특성 수의 절반 이하인가?
      • 모델 성능: PCA적용 후 모델 성능이 유지되거나 향상되었는가?
      • 다중공선성: 원본 특성간에 높은 상관관계가 많이 존재하는가?
    • 절차
      1. 결측치, 이상치 처리, 원-핫 인코딩 사전 진행
      2. 스케일링
      3. PCA 적용
      4. 설명된 분산확인 및 분산비율 시각화
      5. 적절한 주성분의 갯수 선택
      6. 선택된 주성분의 갯수로 PCA다시 적용
      7. 각 주성분이 원래 특성에 얼마나 기여하는지 확인
      8. 상위 5개 특성확인
    from sklearn.decomposition import PCA
    import matplotlib.pyplot as plt
    import numpy as np
    import pandas as pd
    from sklearn.preprocessing import StandardScaler
    
    # 데이터는 이미 전처리되었다고 가정 (결측치, 이상치 처리, 원-핫 인코딩 등)
    # X는 전처리된 특성 데이터
    
    # 1. 스케일링 (PCA 적용 전 반드시 필요)
    scaler = StandardScaler()
    X_scaled = scaler.fit_transform(X)
    
    # 2. PCA 적용
    pca = PCA()
    X_pca = pca.fit_transform(X_scaled)
    
    # 3. 설명된 분산 비율 확인
    explained_variance_ratio = pca.explained_variance_ratio_
    cumulative_variance_ratio = np.cumsum(explained_variance_ratio)
    
    # 4. 설명된 분산 비율 시각화
    plt.figure(figsize=(10, 6))
    plt.plot(range(1, len(explained_variance_ratio) + 1), cumulative_variance_ratio, marker='o', linestyle='-')
    plt.xlabel('Number of Principal Components')
    plt.ylabel('Cumulative Explained Variance Ratio')
    plt.title('Explained Variance by Components')
    plt.axhline(y=0.8, color='r', linestyle='--', label='80% Variance Threshold')
    plt.axhline(y=0.9, color='g', linestyle='--', label='90% Variance Threshold')
    plt.legend()
    plt.grid(True)
    plt.show()
    
    # 5. 적절한 주성분 개수 선택 (예: 80% 분산 유지)
    n_components = np.argmax(cumulative_variance_ratio >= 0.8) + 1
    print(f"80% 분산을 유지하기 위한 주성분 개수: {n_components}")
    
    # 6. 선택된 주성분 개수로 PCA 다시 적용
    pca_selected = PCA(n_components=n_components)
    X_pca_selected = pca_selected.fit_transform(X_scaled)
    
    # 7. 각 주성분이 원래 특성에 얼마나 기여하는지 확인
    components_df = pd.DataFrame(
        pca_selected.components_,
        columns=X.columns
    )
    
    # 8. 각 주성분에 대한 기여도가 높은 상위 5개 특성 확인
    for i, component in enumerate(components_df.values):
        sorted_indices = np.argsort(np.abs(component))[::-1]
        top_features = [X.columns[idx] for idx in sorted_indices[:5]]
        top_values = [component[idx] for idx in sorted_indices[:5]]
        print(f"PC{i+1} 주요 특성:")
        for feature, value in zip(top_features, top_values):
            print(f"  {feature}: {value:.4f}")
        print()
    
    # 9. 2D 시각화 (처음 두 개 주성분으로)
    plt.figure(figsize=(10, 8))
    plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap='viridis', alpha=0.8)
    plt.title('First Two Principal Components')
    plt.xlabel('PC1')
    plt.ylabel('PC2')
    plt.colorbar(label='Target Class')
    plt.grid(True)
    plt.show()

    • 판단근거1: 설명된 분산비율
      • 하기의 누적분산 비율이 총 컬럼 12개에서 7개를 담아야지 80%를 설명하므로 차원축소가 효과적이지 않다는 판단 가능
    # 누적 설명된 분산 비율을 확인
    print("각 주성분별 설명된 분산 비율:")
    for i, ratio in enumerate(explained_variance_ratio):
        print(f"PC{i+1}: {ratio:.4f}")
    
    print("\n누적 설명된 분산 비율:")
    for i, ratio in enumerate(cumulative_variance_ratio):
        print(f"PC1-PC{i+1}: {ratio:.4f}")
        
        
    각 주성분별 설명된 분산 비율:
    PC1: 0.2566
    PC2: 0.1327
    PC3: 0.1071
    PC4: 0.0823
    PC5: 0.0815
    PC6: 0.0719
    PC7: 0.0608
    PC8: 0.0567
    PC9: 0.0472
    PC10: 0.0388
    PC11: 0.0335
    PC12: 0.0308
    
    누적 설명된 분산 비율:
    PC1-PC1: 0.2566
    PC1-PC2: 0.3893
    PC1-PC3: 0.4964
    PC1-PC4: 0.5787
    PC1-PC5: 0.6602
    PC1-PC6: 0.7321
    PC1-PC7: 0.7930
    PC1-PC8: 0.8497
    PC1-PC9: 0.8969
    PC1-PC10: 0.9357
    PC1-PC11: 0.9692
    PC1-PC12: 1.0000
    • 스크리 플롯(Scree Plot)
      • Elbow 지점 확인: 대략 4번 지점이 후보군
    plt.figure(figsize=(10, 6))
    plt.plot(range(1, len(explained_variance_ratio) + 1), explained_variance_ratio, marker='o', linestyle='-')
    plt.xlabel('Principal Component')
    plt.ylabel('Explained Variance Ratio')
    plt.title('Scree Plot')
    plt.grid(True)
    plt.show()

    • 원본데이터와 PCA데이터 성능비교
      • cross_val_score의 경우 이진 분류만 가능하기에 다중클래스는 안됨(변환 필요)
    from sklearn.model_selection import cross_val_score
    from sklearn.ensemble import RandomForestClassifier
    
    # 원본 스케일링된 데이터에 대한 교차 검증
    clf_original = RandomForestClassifier(random_state=42)
    scores_original = cross_val_score(clf_original, X_scaled, y, cv=5, scoring='roc_auc')
    
    # PCA 적용 데이터에 대한 교차 검증
    clf_pca = RandomForestClassifier(random_state=42)
    scores_pca = cross_val_score(clf_pca, X_pca_selected, y, cv=5, scoring='roc_auc')
    
    print(f"원본 데이터 평균 ROC-AUC: {scores_original.mean():.4f} (±{scores_original.std():.4f})")
    print(f"PCA 데이터 평균 ROC-AUC: {scores_pca.mean():.4f} (±{scores_pca.std():.4f})")
    • 다중공선성 검사
      • 특성간 높은 상관관계 발견(0.7 이상) 되면 PCA가 유용할 수 있음
      • 해당 높은 특성들은 독립적인 주성분으로 변환
    from sklearn.preprocessing import StandardScaler
    from scipy.stats import spearmanr
    import seaborn as sns
    
    # 상관관계 행렬 계산
    correlation_matrix = X.corr()
    
    # 상관관계 히트맵 표시
    plt.figure(figsize=(12, 10))
    sns.heatmap(correlation_matrix, annot=False, cmap='coolwarm', center=0)
    plt.title('Feature Correlation Matrix')
    plt.tight_layout()
    plt.show()
    
    # 높은 상관관계(0.7 이상)를 가진 특성 쌍 찾기
    high_corr_pairs = []
    for i in range(len(correlation_matrix.columns)):
        for j in range(i+1, len(correlation_matrix.columns)):
            if abs(correlation_matrix.iloc[i, j]) > 0.7:
                high_corr_pairs.append((correlation_matrix.columns[i], correlation_matrix.columns[j], correlation_matrix.iloc[i, j]))
    
    print("높은 상관관계를 가진 특성 쌍:")
    for pair in high_corr_pairs:
        print(f"{pair[0]} - {pair[1]}: {pair[2]:.4f}")

    2.3. 스케일링

    방법 설명 특장점
    Standard Scaler(정규화) 각 데이터에 평균을 뺴고 표준편차 나누기
    (x - x_bar / x_std)
    이상치, 분포가 skewed되어있을때
    MinMax Scaler(표준화) 데이터를 0과 1사이로 조정
    ( x- x_min / x_max - x_min)
    이상치에 영향을 많이 받음
    Robust Scaler 중앙 값과 IQR를 이용해 조정
    (x - median / IQR)
    이상치에 덜 민감
    • 정규화 스케일링
    from sklearn.preprocessing import StandardScaler
    data = [[0, 0], [0, 0], [1, 1], [1, 1]]
    scaler = StandardScaler()
    print(scaler.fit(data))
    StandardScaler()
    print(scaler.mean_)
    #[0.5 0.5]
    print(scaler.transform(data))
    #[[-1. -1.]
    # [-1. -1.]
    # [ 1.  1.]
    # [ 1.  1.]]

    2.4. 데이터 샘플링

    방법 설명 사용법
    언더샘플링 다수 클래스 줄임 from imblearn.under_sampling import RandomUnderSampler

    SMOTE 클래스 대신 사용하면 됨
    오버샘플링 소수 클래수 복제 SMOTE
    클래스 가중치 모델이 직접 적용, 데이터가 작아 오버샘플링이 부담스러울 때 class_weight = 'balanced'
    (Logistic, RF, SVC, GBC 등 지원)
    • SMOTE 코드
    from imblearn.over_sampling import SMOTE
    
    X = df.drop(columns=['Category', 'target'])
    y = df['target']
    smote = SMOTE(random_state = 42)
    X_resampled, y_resampled = smote.fit_resample(X, y)

    2.4. 평가지표 metrics

    메트릭 설명 사용시점
    Precision Positive  예측 한 것 중 실제 Positive FP가 중요한 문제(스팸 필터처럼 잘못 걸러내면 안됄 때)
    Recall(Sensitivity) 실제 Positive중 Positive로 예측한 비율 FN가 중요힌 문제(암 진단처럼 놓지면 안될 때)
    F1-score Precision과 Recall의 조화 평균 둘 다 중요할 때
    AUC-ROC 다양한 threshhold에서 TPR  vs FPR 곡선의 아래 면적 전체적인 분류 성능이 중요할 때
    PR-AUC Precision vs Recall 곡선의 면적 불균형이 매우 심하여 Positive가 극소수 일때
    • 메트릭 코드 모음

    # 분류모델
    from sklearn.metrics import accuracy_score, precision_score,recall_score
    from sklearn.metrics import f1_score, confusion_matrix, roc_auc_score, classification_report
    # 회귀모델
    from sklearn.metrics import mean_squared_error, mean_absolute_error
    from sklearn.metrics import r2_score, adjusted_rand_score
    
    # 비지도학습 군집
    from sklearn.metrics import silhouette_score
    • classification_report 읽기
      • support: 해당 클래스의 샘플 수 
      • macro avg: 일반적인 지표
      • weight avg: 샘플수 가중치를 더한 지표

    • ROC Curve
    import matplotlib.pyplot as plt
    from sklearn.metrics import roc_curve, roc_auc_score
    
    # 모델 학습 (예시)
    model = RandomForestClassifier(random_state=42)
    model.fit(X_train, y_train)
    
    # 테스트 데이터에 대한 예측 확률
    y_pred_proba = model.predict_proba(X_test)[:, 1]  # 양성 클래스(1)에 대한 확률
    
    # ROC 커브 계산
    fpr, tpr, thresholds = roc_curve(y_test, y_pred_proba)
    
    # AUC 계산
    roc_auc = roc_auc_score(y_test, y_pred_proba)
    
    # ROC 커브 그리기
    plt.figure()
    plt.plot(fpr, tpr, color='blue', label=f'ROC curve (AUC = {roc_auc:.2f})')
    plt.plot([0, 1], [0, 1], color='gray', linestyle='--')  # 대각선 기준선
    plt.xlim([0.0, 1.0])
    plt.ylim([0.0, 1.05])
    plt.xlabel('False Positive Rate')
    plt.ylabel('True Positive Rate')
    plt.title('Receiver Operating Characteristic')
    plt.legend(loc="lower right")
    plt.show()

    2.5. 머신러닝 스켈레톤 코드 - 분류

    from sklearn.linear_model import LogisticRegression
    from sklearn.ensemble import RandomForestClassifier
    from sklearn.svm import SVC
    from sklearn.model_selection import train_test_split
    
    X = df.drop(columns = ['Kwh8','date','datetime'])
    y = df['Kwh8']
    
    X_train, X_test, y_train, y_test = train_test_split(X,y, random_state= 42, stratify= y, test_size= 0.2)
    print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)
    
    # 모델 리스트와 이름
    models = [
    	('Logistic', LogisticRegression(solver='liblinear')), #sklearn 0.23. 에러 대응
        ('RandomForest', RandomForestClassifier(random_state=42)),
        ('SVM', SVC(random_state=42))
    ]
    
    # 모델 학습 및 평가
    for model_name, model in models:
        model.fit(X_train, y_train)
        
        # 예측 수행
        y_pred_train = model.predict(X_train)
        y_pred_test = model.predict(X_test)
        
        # 성능 평가 (train)
        print(f'Train - {model_name} Model')
        print(classification_report(y_train, y_pred_train))
        
        # 성능 평가 (test)
        print(f'Test - {model_name} Model')
        print(classification_report(y_test, y_pred_test))

    2.5. 머신러닝 스켈레톤 코드 - 분류(cv)

    from sklearn.metrics import classification_report, accuracy_score
    from sklearn.model_selection import cross_val_score, KFold, StratifiedKFold
    
    # 데이터 분할
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42, stratify=y, test_size=0.3)
    print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)
    
    # 교차 검증 설정
    cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
    
    # 모델 리스트와 이름
    models = [
        ('Logistic', LogisticRegression(solver='liblinear')), #sklearn 0.23. 에러 대응
        ('RandomForest', RandomForestClassifier(random_state=42)),
        ('SVM', SVC(random_state=42))
    ]
    
    # 모델 학습 및 평가
    for model_name, model in models:
        # 교차 검증 수행
        cv_scores = cross_val_score(model, X_train, y_train, cv=cv, scoring='accuracy')
        print(f'\n{model_name} Cross-Validation:')
        print(f'CV Scores: {cv_scores}')
        print(f'Mean CV Score: {cv_scores.mean():.4f} ± {cv_scores.std():.4f}')
        
        # 전체 훈련 데이터로 모델 학습
        model.fit(X_train, y_train)
        
        # 예측 수행
        y_pred_train = model.predict(X_train)
        y_pred_test = model.predict(X_test)
        
        # 성능 평가 (train)
        print(f'\nTrain - {model_name} Model')
        print(classification_report(y_train, y_pred_train))
        
        # 성능 평가 (test)
        print(f'\nTest - {model_name} Model')
        print(classification_report(y_test, y_pred_test))
        
        print('-' * 70)
    
    # 다양한 교차 검증 방법 비교 (선택적)
    print("\n다양한 교차 검증 방법 비교 (RandomForest 모델):")
    cv_methods = {
        'KFold (k=5)': KFold(n_splits=5, shuffle=True, random_state=42),
        'StratifiedKFold (k=5)': StratifiedKFold(n_splits=5, shuffle=True, random_state=42),
        'StratifiedKFold (k=10)': StratifiedKFold(n_splits=10, shuffle=True, random_state=42)
    }
    
    rf_model = RandomForestClassifier(random_state=42)
    for cv_name, cv_method in cv_methods.items():
        cv_scores = cross_val_score(rf_model, X_train, y_train, cv=cv_method, scoring='accuracy')
        print(f'{cv_name}: {cv_scores.mean():.4f} ± {cv_scores.std():.4f}')

    2.6. 종속변수에 영향 해석하기

    • 로지스틱 회귀 베타계수 추정
      • 양수는 변수가 증가감에 따라 간염 화률의 증가, 음수는 감소
      • 로즈오즈 해석:  계수가 0.5이면 1단위 증가할 때 간염 발생 로그오즈 0.05 증가
      • 오즈비 해석: exp(계수)값으로 계산하며 1.5이면 변수 1단위 증가할 때 간염 발생 오즈 50% 증가 0.8이면 20% 감소, 1이면 영향 미치지 않음 
    # 로지스틱 회귀 모델 학습
    log_model = LogisticRegression(solver='liblinear')
    log_model.fit(X_train, y_train)
    
    # 계수 및 변수명 가져오기
    coefficients = log_model.coef_[0]
    feature_names = X.columns
    
    # 계수 크기순으로 정렬하여 출력
    coef_df = pd.DataFrame({'Feature': feature_names, 'Coefficient': coefficients})
    coef_df = coef_df.sort_values('Coefficient', ascending=False)
    print("로지스틱 회귀 계수 (양수=간염 위험 증가, 음수=간염 위험 감소):")
    print(coef_df)
    
    # 오즈비(Odds Ratio) 계산 - 해석이 더 직관적임
    coef_df['Odds_Ratio'] = np.exp(coef_df['Coefficient'])
    print("\n오즈비 (1보다 크면 위험 증가, 1보다 작으면 위험 감소):")
    print(coef_df[['Feature', 'Odds_Ratio']])
    • Feature Importance 
    # Random Forest 모델 학습
    rf_model = RandomForestClassifier(random_state=42)
    rf_model.fit(X_train, y_train)
    
    # 특성 중요도 추출 및 시각화
    importances = rf_model.feature_importances_
    indices = np.argsort(importances)[::-1]
    
    feature_importance_df = pd.DataFrame({
        'Feature': feature_names[indices],
        'Importance': importances[indices]
    })
    
    plt.figure(figsize=(10, 6))
    sns.barplot(x='Importance', y='Feature', data=feature_importance_df[:10])
    plt.title('Top 10 Important Features for Hepatitis Prediction (Random Forest)')
    plt.tight_layout()
    plt.show()
    • SHAP value
      • 게임 이론이 기반, 각 특성이 모델 예측에 미치는 기여도를 배분
      • 양수면 해당 특성이 예측 확률을 증가시키는 방향으로 기여,  음수는 감소시키는 방향으로 기여
      • 개별 예측 분석, 상호작용 효과 파악, 모든 모델에 적용할 수 있는 장점
    #!pip install shap
    import shap
    
    # SHAP 값 계산 (예: Random Forest 모델)
    explainer = shap.TreeExplainer(rf_model)
    shap_values = explainer.shap_values(X_test)
    
    # SHAP 값 요약 시각화
    shap.summary_plot(shap_values, X_test, feature_names=feature_names)

    2.6. tensorflow 1.13.1 기준 지도학습 코드

    import tensorflow as tf
    import numpy as np
    import os
    
    # TensorFlow 1.x 환경에서 Eager Execution을 비활성화
    tf.compat.v1.disable_eager_execution()
    
    # 데이터 생성 (이진 분류용)
    X_data = np.random.rand(100, 2)  # 100개의 샘플, 2개의 특성
    y_data = np.random.randint(2, size=(100, 1))  # 100개의 이진 타겟 값 (0 또는 1)
    
    # 모델 정의 (Sequential API)
    model = tf.keras.models.Sequential()
    model.add(tf.keras.layers.Dense(10, input_dim=2, activation='relu'))  # 은닉층
    model.add(tf.keras.layers.Dense(1, activation='sigmoid'))  # 이진 분류를 위한 sigmoid 출력층
    
    # 모델 컴파일
    model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
    
    # 체크포인트 설정
    checkpoint_path = "training_checkpoints/cp.ckpt"
    checkpoint_dir = os.path.dirname(checkpoint_path)
    
    # 체크포인트 콜백 설정
    cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_path,
                                                     save_weights_only=True,
                                                     verbose=1)
    # 모델 학습 (체크포인트 콜백 적용)
    model.fit(X_data, y_data, epochs=10, batch_size=10, callbacks=[cp_callback])
    # 모델 저장 (전체 모델 저장)
    model.save('saved_model/my_model')
    # 모델 불러오기
    new_model = tf.keras.models.load_model('saved_model/my_model')
    # 불러온 모델 평가
    loss, accuracy = new_model.evaluate(X_data, y_data)
    print(f"불러온 모델 정확도: {accuracy:.4f}")
    # 체크포인트에서 가중치만 불러오기
    new_model.load_weights(checkpoint_path)
    # 불러온 체크포인트의 가중치로 다시 평가
    loss, accuracy = new_model.evaluate(X_data, y_data)
    print(f"체크포인트에서 불러온 모델 정확도: {accuracy:.4f}")
    # 분류 예측
    predictions = new_model.predict(X_data)
    print(f"예측값 (0 또는 1로 변환): \n{(predictions > 0.5).astype(int)}")

    3. 머신러닝 - 비지도학습

    3.1. Kmeans

    import pandas as pd
    from sklearn.cluster import KMeans
    
    # KMeans 설정 및 클러스터링 수행
    kmeans = KMeans(n_clusters=3, random_state=42)
    kmeans.fit(data)
    
    # 클러스터 결과를 데이터프레임에 추가
    data['cluster'] = kmeans.labels_
    
    # 결과 확인
    print(data.head())

    3.2. DBSCAN

    import pandas as pd
    from sklearn.cluster import DBSCAN
    
    # DBSCAN 설정 및 클러스터링 수행
    dbscan = DBSCAN(eps=0.5, min_samples=5)
    dbscan.fit(data)
    
    # 클러스터 결과를 데이터프레임에 추가
    data['cluster'] = dbscan.labels_
    
    # 결과 확인
    print(data.head())

    3.3. Elbow method

    # 클러스터 수에 따른 inertia (군집 내 거리 합) 계산
    inertia = []
    K = range(1, 11)  # 1~10개의 클러스터를 테스트
    for k in K:
        kmeans = KMeans(n_clusters=k, random_state=42)
        kmeans.fit(data)
        inertia.append(kmeans.inertia_)
    
    # Elbow Method 그래프 시각화
    plt.figure(figsize=(8, 6))
    plt.plot(K, inertia, 'bx-')
    plt.xlabel('Number of clusters (k)')
    plt.ylabel('Inertia')
    plt.title('Elbow Method For Optimal k')
    plt.show()

    3.4. 실루엣 계수

    from sklearn.metrics import silhouette_score
    
    # 실루엣 계수를 저장할 리스트
    silhouette_avg = []
    
    # 2~10개의 클러스터를 테스트
    K = range(2, 11)
    for k in K:
        kmeans = KMeans(n_clusters=k, random_state=42)
        labels = kmeans.fit_predict(data)
        silhouette_avg.append(silhouette_score(data, labels))
    
    # 실루엣 계수 시각화
    plt.figure(figsize=(8, 6))
    plt.plot(K, silhouette_avg, 'bx-')
    plt.xlabel('Number of clusters (k)')
    plt.ylabel('Silhouette Score')
    plt.title('Silhouette Score For Optimal k')
    plt.show()

    4. 생존 분석

    생존분석 정리글입니다.

    https://snowgot.tistory.com/137

     

    생존분석과 lifeline 패키지 활용 - LogRank, 카플란-마이어, 콕스비례위험모형

    1. 생존분석이란시간-이벤트 데이터(예: 생존 시간, 고장 시간 등)를 분석하는 데 사용됨주요 목표는 생존 시간 분포를 추정하고, 생존 시간에 영향을 미치는 요인을 식별하며, 여러 그룹 간의 생

    snowgot.tistory.com

    4.1. 생존 분석 분류

    • 생존 분석: 다양한 모델링 기법을 포함하는 광범위한 분야로, 시간-이벤트 데이터를 분석
      • 카플란-마이어 추정법: 특정 시간까지 이벤트가 발생하지 않을 확률을 추정하는 방법입니다. 사건이 독립적이라는 가정
      • Log-Rank 테스트: 두 그룹 간의 시간에 따른 생존율 차이를 검정
      • 콕스 비례 위험 모형: 시간에 따른 위험을 모델링하며, 공변량이 시간에 따라 비례적으로 위험률에 영향을 미친다는 가정을 기반

    4.2. 카플란 마이어

    • 특정 시간까지 이벤트가 발생하지 않을 확률(생존 함수)을 비모수적으로 추정하는 방법
    • 각 시간 점에서 생존 확률을 계산하고, 이를 통해 전체 생존 곡선을 작성.
    • 사건이 독립적이라는 가정이 있지만, 실제로는 이 가정이 항상 만족되지 않을 수 있음(실제로 병은 누적되는 대미지가 있으므로)
    • $\hat{S}(t) = \prod_{t_i \leq t} \left(1 - \frac{d_i}{n_i}\right)$

    import pandas as pd
    import matplotlib.pyplot as plt
    from lifelines import KaplanMeierFitter
    
    # 예시 데이터 생성
    data = {
        'duration': [5, 6, 6, 7, 8, 8, 10, 12, 14, 15, 18, 20, 25],
        'event': [1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1]
    }
    
    df = pd.DataFrame(data)
    
    # Kaplan-Meier Fitter 생성
    kmf = KaplanMeierFitter()
    
    # 생존 함수 적합
    kmf.fit(durations=df['duration'], event_observed=df['event'])
    
    # 생존 곡선 시각화
    plt.figure(figsize=(10, 6))
    kmf.plot_survival_function()
    plt.title('Kaplan-Meier Survival Curve')
    plt.xlabel('Time')
    plt.ylabel('Survival Probability')
    plt.grid(True)
    plt.show()

    4.3. Log-Rank

    • 카플란 마이어 기법의 응용
    • 두 그룹 간의 시간에 따른 생존율 차이를 검정하는 방법
    • 각 시간 점에서 관찰된 사건 수와 기대 사건 수를 비교하여(카이제곱검정), 두 그룹의 생존 곡선이 통계적으로 유의미하게 다른지를 평가
    • $\chi^2 = \frac{(O_1 - E_1)^2}{V_1} + \frac{(O_2 - E_2)^2}{V_2}$

    import pandas as pd
    from lifelines import KaplanMeierFitter
    from lifelines.statistics import logrank_test
    
    # 데이터 생성
    data = {
        'group': ['A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B'],
        'duration': [6, 7, 10, 15, 23, 5, 8, 12, 18, 22],
        'event': [1, 0, 1, 0, 1, 1, 1, 0, 1, 1]
    }
    
    df = pd.DataFrame(data)
    
    # 두 그룹 나누기
    groupA = df[df['group'] == 'A']
    groupB = df[df['group'] == 'B']
    
    # Kaplan-Meier Fitter 생성
    kmf_A = KaplanMeierFitter()
    kmf_B = KaplanMeierFitter()
    
    # 생존 함수 적합
    kmf_A.fit(durations=groupA['duration'], event_observed=groupA['event'], label='Group A')
    kmf_B.fit(durations=groupB['duration'], event_observed=groupB['event'], label='Group B')
    
    # Log-Rank 테스트 수행
    results = logrank_test(groupA['duration'], groupB['duration'], event_observed_A=groupA['event'], event_observed_B=groupB['event'])
    results.print_summary()

    • 시각화 코드
    import pandas as pd
    import matplotlib.pyplot as plt
    from lifelines import KaplanMeierFitter
    
    # 데이터 생성
    data = {
        'group': ['A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B'],
        'duration': [6, 7, 10, 15, 23, 5, 8, 12, 18, 22],
        'event': [1, 0, 1, 0, 1, 1, 1, 0, 1, 1]
    }
    
    df = pd.DataFrame(data)
    
    # 두 그룹 나누기
    groupA = df[df['group'] == 'A']
    groupB = df[df['group'] == 'B']
    
    # Kaplan-Meier Fitter 생성
    kmf_A = KaplanMeierFitter()
    kmf_B = KaplanMeierFitter()
    
    # 생존 함수 적합
    kmf_A.fit(durations=groupA['duration'], event_observed=groupA['event'], label='Group A')
    kmf_B.fit(durations=groupB['duration'], event_observed=groupB['event'], label='Group B')
    
    # 생존 곡선 시각화
    plt.figure(figsize=(10, 6))
    kmf_A.plot_survival_function()
    kmf_B.plot_survival_function()
    plt.title('Kaplan-Meier Survival Curves')
    plt.xlabel('Time (months)')
    plt.ylabel('Survival Probability')
    plt.legend()
    plt.grid(True)
    plt.show()

    4.4. 콕스비레 위험모형

    • 카플란마이어 추정법은 사건이 독립적이라는 가정의 한계 따라서 시간에 따른 사건 발생 위험률(위험 함수)을 모델링
    • 공변량(독립변수)이 시간에 따라 비례적으로 위험률에 영향을 미친다는 가정
    • 기준 위험률와 공변량의 선형 결합을 지수 함수 형태로 결합하여 위험률을 표현
      • 기준위험률(λ0(t)): 시간 t 기준 위험률로 공변량의 영향을 제거한 상태의 기본적인 위험률
      • 공변량(Covariates, X): 사건 발생에 영향을 미칠 수 있는 변수들
    • 시간-의존적 위험률을 모델링할 수 있으며, 공변량이 생존 시간에 미치는 영향을 평가하는 데 유용
    • $\lambda(t \mid X) = \lambda_0(t) \exp(\beta_1 X_1 + \beta_2 X_2 + \cdots + \beta_p X_p)​$

    import pandas as pd
    from lifelines import CoxPHFitter
    import matplotlib.pyplot as plt
    
    # 예시 데이터 생성
    ```
    duration: 환자가 생존한 기간(개월 수)
    event: 사건 발생 여부(1 = 사망, 0 = 생존)
    age: 환자의 나이
    treatment: 치료 방법(0 = 치료 A, 1 = 치료 B)
    ```
    data = {
        'duration': [5, 6, 6, 7, 8, 8, 10, 12, 14, 15, 18, 20, 25, 5, 7, 12, 13, 14, 16, 20],
        'event': [1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1],
        'age': [50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, 105, 110, 55, 60, 65, 70, 75, 80, 85],
        'treatment': [0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1]
    }
    
    df = pd.DataFrame(data)
    
    # 콕스 비례 위험 모형 적합
    cph = CoxPHFitter()
    cph.fit(df, duration_col='duration', event_col='event')
    
    # 모형 요약 출력
    cph.print_summary()
    
    # 생존 곡선 시각화
    cph.plot()
    plt.title('Hazard Ratios')
    plt.show()

     

    지난장 배치에서는 유닉스의 배치처리 방식과 맵리듀스를 기반으로 한 분산파일 시스템에대해서 알아보았습니다. 많이 혼란스럽고 어려운 장이였는데요, 이번 장에서는 스트림 처리에 대해서 알아봅니다. 배치를 기반으로 하는 설명이 나온다고 해서 많이 어려울 줄 알았는데 일부 읽을만한 내용들이 있어서 재밌었습니다.


     

     

    목차

       

       

      신규개념(New)

      개념 설명
      스트림(stream) 시간 흐름에 따라 점진적으로 생산된 데이터
      레코드(record) 이벤트, 특정 시점에 일어난 사건에 대한 세부사항을 포함하는 작고 독립된 불변 객체
      생산자(producer) publisher, sender
      소비자(consumer) subscrier, recipient
      트리거(trigger) 테이블에 대한 이벤트에 반응해 자동으로 실행되는 작업
      메시징 시스템(Messaging system) 로그 데이터, 이벤트 메시지 등 API로 호출할 때 보내는 데이터를 처리하는 시스템
      윈도우(window) 집계하는 시간의 간격

      1. 배치와 스트림 처리의 차이

        배치 스트림
      정의 입력으로 파일 집합을 읽어 출력으로 새로운 파일 집합을 생성하는 기술 연속적으로 발생하는 데이터를 실시간 혹은 준 실시간으로 처리하는 기술
      활용예시 검색 색인, 추천시스템, 분석 실시간 로그 분석, 거래 모니터링, 실시간 알림 시스템 등
      출력 파생 데이터(요약 데이터ㅡ 결과 리포트 등) 실시간으로 처리된 이벤트 결과(알림 등)
      특징  끝이 있음(작업 단위가 명확) 영원히 끝나지 않음(데이터 흐름이 지속적으로 발생)

      2. 메시징 시스템

      2.1 메시징 시스템 기본

      새로운 이벤트에 대해서 소비자에게 알려주고 쓰이는 일반적인 방법은 메시징 시스템(messaging system) 이다. 메시징 시스템을 이해하기 위해서 다음 블로그에서 글 발췌했다.

      (Amendment) 메시징 시스템의 이해

      https://victorydntmd.tistory.com/343
       

      메시징 시스템( Messaging System )의 이해

      요즘 관심있게 살펴보는 주제가 MSA인데요, MSA에서는 데이터 송수신 방법으로 메시징 시스템을 사용합니다.메시징 시스템은 Kafka, RabbitMQ, Active MQ, AWS SQS, Java JMS 등이 있는데요.MSA에서는 시스템

      victorydntmd.tistory.com

       

      위는 자동 메일 발송 시스템으로 3가지 시스템이 혼재

      • 회원가입을 했을 때, 이메일을 발송하는 MemberService
      • 주문완료가 되었을 때, 이메일을 발송하는 OrderService
      • 메일을 실제 발송하는 MailService

      이렇게 서비스가 분리되었을 때 프로세스는 다음과 같습니다.

      1. MemberService에서 회원가입, OrderService에서 주문완료 이벤트가 발생
      2. Messaging Client로 메일 전송에 필요한 데이터( 받는/보내는 사람 이메일 주소, 메시지 제목/내용 등.. )를 API 호출
      3. Messaging Client에서 MOM을 구현한 소프트웨어(ex. kafka)로 메시지를 생산
      4. MailService에서 메시지가 존재하는지 구독하고 있다가 메시지가 존재하면 메시지를 소비
      5. MailService에서 API 정보들을 통해 User에게 메일 발송
      이러한 구조를 Publish/Subscribe 또는 Producer/Consumer라고 합니다.
      인용 끝

      발행(publish), 구독(subsribe) 모델에서 시스템에서  생산자가 소비자가 메시지를 처리하는 속도보다 빠르게 메시지를 전송하는 경우 3가지 선택지 존재

      • 메시지를 버림
      • 큐에 메시지를 버퍼링
      • 배압(backpressure), 흐름제어(flow control) 
        • 유닉스 파이프와 TCP의 예시

      이 경우 메시지의 유실이 허용할지 말지 등의 규칙은 어플리케이션마다 다르며 이는 배치 시스템와 대조적인 특징. 배치 시스템은 실패한 태스크를 자동으로 재시도하고 실패한 태스크가 남긴 출력을 자동으로 폐기하기 때문

      2.2. 생산자 -> 소비자로 메시지 전달하기

      • 직접 전달하기
        • 대부분의 메시지 시스템이 중간노드 없이 전달
        • 소비자가 오프라인인 경우 잃어버릴 가능성 있음. 생산자 장비가 죽어있는 경우 재시도하려고 하는 버퍼를 잃어버릴 가능 성 있음
      • 메시지 브로커(메시지 큐) 이용하기
        • 메시지 스트림을 처리하는데 최적화된 데이터베이스의 일종
        • 브로커(서버)에 데이터가 모이기 때문에 클라이언트의 상태변경(접속, 접속 해제, 장애)등에 대응 하기 좋음
        • 큐 대기시 소비자는 일반적으로 비동기로 동작
          • 생산자는 브로커가 해당 메시지를 버퍼에 넣었는지만 확인하고 소비자가 메시지를 처리하기까지 기다리지 않는 장점

      3. 스트림 처리

      3.1. 스트림이 할 수 있는 일

      1. 이벤트에서 데이터를 꺼내 데이터베이스(또는 캐시, 검색 색인 등)에 기록하고 다른 클라이언트가 시스템에 해당 데이터를 질의하게 하는 일
      2. 이벤트를 직접 사용자에게 보내는 일(Ex 이메일 경고, 푸시 알람)
      3. 하나 이상의 입력 스트림을 처리해 하나 이상의 출력 스트림을 생산
        • 이 스트림을 처리하는 코드 뭉치를 연산자(Operator), 작업(job)이라 불림

      3.2. 스트림의 예시

      • 사기 감시 시스템: 신용카드 사용 패턴의 변화로 카드 도난 감지
      • 거래 시스템: 금융시장의 Rule base 거래
      • 제조 시스템: 공장 기계상태 모니터링 및 오작동 감지
      • 군사 첩보 시스템: 침략자의 공격 신호가 있으면 경보 발령

      3.3. 스트림 분석

      • 특정 유형의 이벤트 빈도 측정
      • 특정 기간에 걸친 이동 평균(Rolling aveage) 계산
      • 이전 시간 간격과 현재 통계 값의 비교(추세를 감지, 높거나 낮은 지표 경고)

      (Amendment)스트리밍 분석이란?

      https://cloud.google.com/learn/what-is-streaming-analytics?hl=ko

       

      https://cloud.google.com/learn/what-is-streaming-analytics?hl=ko

       

      cloud.google.com

       

      4. 참고문헌

      ADP 시험을 준비하기 위하여 정리했던 방법론과 코드를 저장합니다.빅분기는 범위가 더 좁지만 공통된 부분이 많습니다.  선형계획법이나 기타 내용은 저도 몰라서 못넣었습니다 🤣 잘못된 내용이나 모호한 내용, 추가되어야하는 내용이 있으면 지적해주세요! 다들 시험에 합격하시길 바라요~


      0. 목차

      클릭시 해당 컨텐츠로 이동합니다.

          https://snowgot.tistory.com/189

           

          ADP 빅분기 이론과 Python 모듈, 실습코드 정리 - 머신러닝 편

          지난번 통계편에 이은 머신러닝문서입니다! 잘못된 내용이나 모호한 내용, 추가되어야하는 내용이 있으면 지적해주세요! 다들 시험에 합격하시길 바라요~0. 목차클릭시 해당 컨텐츠로 이동합니

          snowgot.tistory.com

          1. 모듈별 디렉토리 정리

          •  ADP 지원 버전: Python 3.7.(공지사항)
          • ADP 기본제공 Python Package & version
          pip install ipykernel=5.1.0
          pip install numpy==1.12.6
          pip install pandas==1.1.2
          pip install scipy==1.7.3
          pip install matplotlib==3.0.3
          pip install seaborn==0.9.0
          pip install statsmodels==0.13.2
          pip install pmdarima==2.0.1
          pip scikit-learn==0.23.2

          [제34회ADP실기]기본제공_python_package_list.txt
          0.01MB

          • 기본 모듈 로드
          # 일반모듈
          import warnings
          warnings.filterwarnings('ignore')
          import pandas as pd
          import numpy as np
          import os
          import datetime as dt
          
          # 머신러닝 - 전처리
          from sklearn.preprocessing import StandardScaler, MinMaxScaler, 
          LabelEncoder, OneHotEncoder, RobustScaler
          from sklearn.impute import SimpleImputer, KNNImputer
          from imblearn.over_sampling import SMOTE, RandomOverSampler
          from imblearn.under_sampling import RandomUnderSampler
          from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score, 
          KFold, StratifiedKFold
          
          # 머신러닝 - 모델
          from sklearn.linear_model import LogisticRegression, RidgeClassifier, LinearRegression, 
          Lasso, Ridge, ElasticNet
          from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier, 
          RandomForestRegressor, GradientBoostingRegressor
          from sklearn.ensemble import VotingClassifier
          from sklearn.svm import SVC, SVR
          from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor
          from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor
          
          # 머신러닝 - 비지도
          from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering
          from sklearn.decomposition import PCA
          
          # 머신러닝 - 평가
          from sklearn.metrics import (classification_report, confusion_matrix, 
          roc_auc_score, precision_recall_curve, auc,
          mean_squared_error, mean_absolute_error, r2_score, accuracy_score, 
          precision_score, recall_score, f1_score)
          
          from sklearn.metrics.pairwise import cosine_similarity
          
          # 시각화
          import matplotlib.pyplot as plt
          import seaborn as sns
          
          # 통계 - scipy
          import scipy.stats as stats
          from scipy.stats import ttest_ind, ttest_rel, f_oneway, chi2_contingency, 
          mannwhitneyu, wilcoxon, pearsonr, spearmanr, normaltest
          
          # 통계 - statsmodels
          import statsmodels.api as sm
          import statsmodels.formula.api as smf
          from statsmodels.stats.multicomp import pairwise_tukeyhsd
          from statsmodels.stats.outliers_influence import variance_inflation_factor
          from statsmodels.stats.diagnostic import het_breuschpagan
          
          # 시계열
          from statsmodels.tsa.stattools import adfuller, acf, pacf, kpss
          from statsmodels.tsa.seasonal import seasonal_decompose
          from statsmodels.tsa.arima.model import ARIMA
          from statsmodels.tsa.holtwinters import ExponentialSmoothing, SimpleExpSmoothing
          from statsmodels.tsa.statespace.sarimax import SARIMAX
          from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
          
          # pmdarima (자동 ARIMA)
          from pmdarima import auto_arima
          • 한글폰트 설정(선택사항)
          import matplotlib.pyplot as plt
          import platform
          
          def set_matplotlib_font():
              system = platform.system()
          
              if system == "Windows":
                  plt.rc('font', family='Malgun Gothic')
              elif system == "Darwin":  # macOS
                  plt.rc('font', family='AppleGothic')
              elif system == "Linux":
                  plt.rc('font', family='NanumGothic')
              else:
                  print("Unknown system. Please set font manually.")
          
              plt.rcParams['axes.unicode_minus'] = False
          
          # 폰트 설정 함수 호출
          set_matplotlib_font()

          1.1. datetime

          datetime
          │
          ├── datetime                   # 날짜와 시간을 모두 처리하는 클래스
          │   ├── now()                  # 현재 날짜와 시간을 반환
          │   ├── strptime()             # 문자열을 datetime 객체로 변환
          │   ├── strftime()             # datetime 객체를 문자열로 변환
          │   ├── year,month, day        # 년도, 월, 일 추출
          │   ├── hour,minute, second    # 시간, 분, 초 추출
          │   ├── date(), time()         # 날짜 (년, 월, 일)만, 시간 (시, 분, 초)만 반환
          │   ├── replace()              # 특정 날짜/시간 값을 변경하여 새로운 datetime 객체 반환
          │   ├── weekday()              # 요일 반환 (월: 0, 일: 6)
          │   └── combine()              # 날짜와 시간을 결합하여 새로운 datetime 객체 생성
          │
          ├── date                       # 날짜만 처리하는 클래스
          │   ├── today()                # 오늘 날짜 반환
          │   ├── fromisoformat()        # ISO 형식의 문자열에서 date 객체 생성
          │   ├── year,month, day        # 년도, 월, 일 추출
          │
          ├── time                       # 시간만 처리하는 클래스
          │   ├── hour,minute, second    # 시간, 분, 초 추출
          │   └── microsecond            # 마이크로초 추출
          │
          ├── timedelta                  # 두 날짜 또는 시간 간의 차이를 계산하는 클래스
          │   ├── days                   # 차이 나는 일수
          │   ├── seconds                # 차이 나는 초
          │   └── total_seconds()        # 총 차이 시간을 초 단위로 반환
          │
          └── timezone                   # 시간대 정보를 다루는 클래스
              ├── utc                    # UTC 시간대 객체
              └── tzinfo                 # 사용자 정의 시간대 설정을 위한 클래스

          1.2. numpy==1.21.6

          numpy
          │
          ├── 기본 통계 함수
          │   ├── mean()                 # 데이터의 평균값 계산
          │   ├── median()               # 데이터의 중앙값 계산
          │   ├── std()                  # 데이터의 표준편차 계산
          │   ├── var()                  # 데이터의 분산 계산
          │   ├── sum()                  # 데이터의 합계 계산
          │   ├── prod()                 # 데이터의 곱 계산
          │
          ├── 퍼센타일 및 백분위 함수
          │   ├── percentile()           # 데이터의 특정 퍼센타일 값 계산
          │   ├── quantile()             # 데이터의 특정 분위 값 계산
          │
          ├── 최소값/최대값 관련 함수
          │   ├── min()                  # 데이터의 최소값 반환
          │   ├── max()                  # 데이터의 최대값 반환
          │   ├── argmin()               # 최소값의 인덱스 반환
          │   ├── argmax()               # 최대값의 인덱스 반환
          │
          ├── 데이터 생성 및 처리 함수
          │   ├── histogram()            # 데이터의 히스토그램 계산
          │   ├── unique()               # 데이터에서 고유 값 반환
          │   ├── bincount()             # 정수 배열의 값의 빈도 계산
          │
          ├── 랜덤 데이터 생성 (통계적 실험 시 사용 가능)
          │   ├── random.randn()         # 표준 정규분포를 따르는 랜덤 값 생성
          │   ├── random.normal()        # 정규분포를 따르는 랜덤 값 생성
          │   ├── random.randint()       # 정수 범위에서 랜덤 값 생성
          │   ├── random.choice()        # 데이터에서 랜덤 샘플 추출

          1.3. scipy==1.7.3

          scipy
          │
          ├── stats                      # 통계 분석과 확률 분포 관련 함수 제공
          │   ├── norm                   # 정규분포 관련 함수 (PDF, CDF, 랜덤 샘플링 등)
          |   |── uniform                # 균등분포
          |   |── bernoulli              # 베르누이 분포
          |   |── binom                  # 이항분포
          │   ├── ttest_ind              # 독립 두 표본에 대한 t-검정
          │   ├── ttest_rel              # 대응표본 t-검정
          │   ├── mannwhitneyu           # Mann-Whitney U 비모수 검정
          │   ├── chi2_contingency        # 카이제곱 독립성 검정
          │   ├── shapiro                # Shapiro-Wilk 정규성 검정
          │   ├── kstest                 # Kolmogorov-Smirnov 검정 (분포 적합성 검정)
          │   ├── probplot               # Q-Q plot 생성 (정규성 시각화)
          │   ├── pearsonr               # Pearson 상관계수 계산
          │   ├── spearmanr              # Spearman 순위 상관계수 계산
          │   └── describe               # 기술 통계량 제공 (평균, 표준편차 등)
          │
          ├── optimize                   # 함수 최적화 및 곡선 피팅 관련 모듈
          │   ├── minimize               # 다변수 함수의 최소값을 찾는 최적화 도구
          │   ├── curve_fit              # 비선형 곡선 피팅 (최소제곱법 기반)
          │   └── root                   # 방정식의 근을 찾는 도구
          │
          ├── linalg                     # 선형대수 관련 함수
          │   ├── inv                    # 행렬의 역행렬 계산
          │   ├── det                    # 행렬식 계산
          │   └── svd                    # 특이값 분해(SVD) 수행
          │
          ├── interpolate                # 데이터 보간 관련 함수
          │   ├── interp1d               # 1차원 선형 보간 함수
          │   └── griddata               # 다차원 보간 수행 (비정형 데이터에 사용)
          │
          └── special                    # 특수 함수 (감마 함수, 베타 함수 등)
              ├── gamma                  # 감마 함수
              └── beta                   # 베타 함수

          1.4. pandas==1.1.2

          pandas
          │
          ├── melt: pivot 형태를 다시바꾸기
          │
          ├── DataFrame                  # 2차원 데이터 구조, 테이블 형태의 데이터 관리
          │   ├── groupby                # 데이터프레임 그룹핑
          │   ├── merge                  # SQL 스타일로 데이터프레임 병합
          │   ├── join                   # 데이터프레임 조인 (인덱스를 기준)
          │   ├── pivot_table            # 피벗 테이블 생성
          │   ├── apply                  # 사용자 정의 함수를 데이터프레임에 적용
          │   ├── isnull                 # 결측치 여부 확인
          │   ├── fillna                 # 결측치 대체
          │   └── drop                   # 행 또는 열 삭제
          │
          ├── Series                     # 1차원 데이터 구조, 배열 형태의 데이터 관리
          │   ├── value_counts           # 고유 값의 빈도수 반환
          │   └── unique                 # 고유 값 반환
          │
          ├── time_series                # 시계열 데이터 처리 도구
          │   ├── to_datetime            # 문자열을 날짜 형식으로 변환
          │   ├── resample               # 시계열 데이터 리샘플링
          │   ├── shift                  # 데이터를 앞이나 뒤로 이동
          │   └── rolling                # 이동 평균 등 롤링 윈도우 계산
          │
          └── plotting                   # 데이터 시각화 도구
              ├── plot                   # 라인 플롯, 기본 시각화 함수
              ├── hist                   # 히스토그램 생성
              ├── boxplot                # 박스 플롯 생성
              └── scatter                # 산점도 생성

          1.5. statsmodel==0.13.2

          statsmodels
          │
          ├── api                        # 주요 모듈에 접근하기 위한 API 제공
          │
          ├── stats                      # 통계적 테스트 및 유틸리티
          │   ├── diagnostic            # 모델 진단 도구 (자기상관, 이분산성 등)
          │   ├── stattools             # 통계 도구 (자기상관 함수, 부분자기상관 함수 등)
          │   ├── multivariate          # 다변량 통계 분석
          │   ├── anova                 # 분산 분석(ANOVA) 관련 함수
          │   │   ├── anova_lm          # 선형 모델에 대한 ANOVA 테이블
          │   │   └── multicomp         # 다중 비교 방법
          │   └── nonparametric         # 비모수 통계 검정 (KDE, 커널 회귀 등)
          │
          ├── duration                   # 생존 분석(Survival Analysis) 관련 모듈
          │   ├── hazard_regression     # 위험 회귀 모델
          │   │   ├── cox               # Cox 비례 위험 모델
          │   │   └── phreg             # 비례 위험 회귀 모델
          │   ├── survfunc              # 생존 함수 추정 (Kaplan-Meier 등)
          │   │   ├── kaplan_meier      # 카플란-마이어 추정량
          │
          ├── tsa                        # 시계열 분석(Time Series Analysis) 관련 모듈
          │   ├── arima                  # ARIMA 모델 구현
          │   ├── statespace            # 상태 공간 모델 (State Space Models)
          │   ├── vector_ar             # 벡터 자기회귀(VAR) 모델
          │   ├── seasonal              # 계절성 분해 (SARIMAX 등)
          │   └── filters               # 칼만 필터, HP 필터 등

          1.6. sklearn==0.23.2

          scikit-learn
          │
          ├── model_selection          # 모델 평가, 검증, 및 데이터 분할
          │   ├── train_test_split      # 데이터를 훈련 세트와 테스트 세트로 분할
          │   ├── cross_val_score       # 교차 검증 점수 계산
          │   ├── GridSearchCV          # 하이퍼파라미터 최적화
          │   └── KFold                 # K-폴드 교차 검증
          │
          ├── preprocessing             # 데이터 전처리 및 스케일링
          │   ├── StandardScaler        # 데이터 표준화 (평균 0, 표준편차 1로 스케일링)
          │   ├── MinMaxScaler          # 데이터 값을 0과 1 사이로 스케일링
          │   ├── LabelEncoder          # 범주형 데이터를 숫자로 변환
          │   └── OneHotEncoder         # 범주형 데이터를 원-핫 인코딩
          │
          ├── decomposition             # 차원 축소 기법
          │   ├── PCA                   # 주성분 분석 (Principal Component Analysis)
          │   ├── TruncatedSVD          # 차원 축소 (Singular Value Decomposition)
          │   └── NMF                   # 비음수 행렬 분해 (Non-negative Matrix Factorization)
          │
          ├── metrics                   # 모델 평가 지표
          │   ├── accuracy_score        # 정확도 평가
          │   ├── confusion_matrix      # 혼동 행렬 계산
          │   ├── classification_report # 분류 모델 평가 보고서
          │   ├── roc_auc_score         # ROC AUC 점수 계산
          │   └── mean_squared_error    # 회귀 모델의 MSE 계산
          │
          ├── linear_model              # 선형 모델
          │   ├── LinearRegression      # 선형 회귀
          │   ├── LogisticRegression    # 로지스틱 회귀
          │   ├── Ridge                 # 릿지 회귀 (L2 정규화)
          │   ├── Lasso                 # 라쏘 회귀 (L1 정규화)
          │   └── ElasticNet            # 엘라스틱넷 회귀 (L1 + L2 정규화)
          │
          ├── ensemble                  # 앙상블 학습
          │   ├── RandomForestClassifier# 랜덤 포레스트 분류기
          │   ├── GradientBoostingClassifier # 그래디언트 부스팅 분류기
          │   ├── RandomForestRegressor # 랜덤 포레스트 회귀
          │   └── GradientBoostingRegressor # 그래디언트 부스팅 회귀
          │
          ├── neighbors                 # 최근접 이웃 알고리즘
          │   ├── KNeighborsClassifier  # K-최근접 이웃 분류기
          │   ├── KNeighborsRegressor   # K-최근접 이웃 회귀
          │   └── NearestNeighbors      # 최근접 이웃 검색
          │
          ├── svm                       # 서포트 벡터 머신
          │   ├── SVC                   # 서포트 벡터 분류기
          │   └── SVR                   # 서포트 벡터 회귀
          │
          ├── tree                      # 결정 트리 알고리즘
          │   ├── DecisionTreeClassifier # 결정 트리 분류기
          │   └── DecisionTreeRegressor  # 결정 트리 회귀
          │
          └── cluster                   # 클러스터링 알고리즘
              ├── KMeans                # K-평균 클러스터링
              ├── DBSCAN                # 밀도 기반 클러스터링
              └── AgglomerativeClustering # 계층적 클러스터링

          2. 데이터 전처리

          • datetime 자료형
            • 날짜와 시간을 표현하는 자료형
            • datetime(2023, 10, 9, 14, 30, 0) # 2023-10-09 14:30:00
            • ts.to_pydatetime() 함수로 ts→ dt변환 가능
          • timstamp 자료형
            • 유닉스 타임 스탬프로, 1970년 1월 1일로부터 경과 시간을 측정
            • pd.Timestamp() 함수로 dt → ts 변환 가능
          • 문자형 자료형 -> datetime
            • pd.to_datetime('2020-01-01 01:00', format = ‘%Y-%m-%d %H:%M’)
          • 날짜의 기간별로 만들기
            • pd.date_range(start='2020-01-01', end='2020-01-31', freq = 'D')
          • 예시
          from datetime import datetime
          
          # 문자열을 datetime 객체로 변환
          date_string = '01/01/2018 00:15'
          date_object = datetime.strptime(date_string, '%m/%d/%Y %H:%M')
          
          # 년, 월, 일, 시간, 분 추출
          year = date_object.year
          month = date_object.month
          day = date_object.day
          hour = date_object.hour
          minute = date_object.minute
          • 특정 시간 기준으로 시간 간격 구하기
          #실용 예제1
          time_mid = '2024-10-09 00:00:00'
          from datetime import datetime
          time_stamp = datetime.strptime(time_mid, '%Y-%m-%d %H:%M:%S')
          (datetime.now() - time_stamp).total_seconds()/60
          
          #실용예제2
          from datetime import datetime
          y = df['datetime'] - df['datetime'].apply(lambda x : datetime.combine(x, datetime.min.time()))
          df['nsd'] = y.apply(lambda td :td.total_seconds()/60)
          • 피벗
          pd.pivot_table(values, index, columns, aggfunc='mean')
          • 언피벗
          pd.melt(id_vars = [기준컬럼], value_vars = [값컬럼], value_name = '값이름')
          • 머지
          pd.merge(df1, df2, how = 'left', on = 'key')

          3. 통계 추정

          3.1. 점 추정

          • 모수를 하나의 값으로 추정하는 방법
          • 모평균에 대한 점추정: $\bar{x} = \frac{1}{n}\sum_{i=1}^{n}x_{i}$
          import numpy as np
          sample_mean = np.mean(data)
          • 모분산에 대한 점추정: $s^{2} = \frac{1}{n-1}\sum_{i=1}^{n}(x_{i} - \bar{x})^2$
            • n-1을 자유도로 나누어 불편성을 보장
          sample_std = np.var(data, ddof = 1) #자유도 n-1
          • 모비율에 대한 점추정: $p^{2} = \frac{x}{n}$
            • $x$: 표본의 사건 횟수
          x = np.sum(x)
          n = len(data)
          
          x/n

          3.2. 구간 추정

          • 모수가 특정 구간 내에 있을 것이라는 신뢰 구간을 제시하는 방법
          • 모평균에 대한 구간 추정
            • $\sigma^{2}$ Known: $ci = \bar{x} \pm z_{\frac{\alpha}{2}}(\frac{\sigma}{\sqrt{n}})$
            • $\sigma^{2}$ UnKnown: $ci = \bar{x} \pm t_{\frac{\alpha}{2}, n-1}(\frac{\sigma}{\sqrt{n}})$
          import numpy as np
          from scipy.stats import t
          
          # 신뢰수준 설정
          confidence_level = 0.95
          alpha = 1 - confidence_level
          
          # 1. 모분산을 알때
          z = stats.norm.ppf(1 - alpha/2)
          margin_of_error = z * (sample_std/np.sqrt(n))
          print(sample_mean- margin_of_error, sample_mean + margin_of_error)
          
          # 2. 모분산을 모를 때
          t_crit = t.ppf(1-alpha /2, df = n-1)
          margin_of_error_t = t_crit *(sample_std/np.sqrt(n))
          print(sample_mean- margin_of_error_t, sample_mean + margin_of_error_t
          
          # 3. t.interval 사용
          stats.t.interval(confidnece_level, df = n-1, loc = sample_min, scale = sample_std/np.sqrt(n))
          • 모 분산에 대한 구간 추정
            • $\left( \frac{(n - 1) s^2}{\chi^2_{1 - \alpha/2, \; n - 1}}, \; \frac{(n - 1) s^2}{\chi^2_{\alpha/2, \; n - 1}} \right)$
          # 자유도
          df = n - 1
          
          # 카이제곱 값 계산
          chi2_lower = stats.chi2.ppf(alpha / 2, df)
          chi2_upper = stats.chi2.ppf(1 - alpha / 2, df)
          
          # 신뢰구간 계산
          lower_bound = (df * sample_variance) / chi2_upper
          upper_bound = (df * sample_variance) / chi2_lower
          
          print(f"{confidence_level*100}% 신뢰구간: ({lower_bound}, {upper_bound})")
          • 모 비율에 대한 구간 추정
            • $\hat{p} \pm z_{\alpha/2}\sqrt{\frac{\hat{p}(1-\hat{p})}{n}}$
          from statsmodels.stats.proportion import proportion_confint
          # 성공 횟수와 전체 시행 횟수
          x = np.sum(data)  # 성공 횟수
          n = len(data)     # 전체 시행 횟수
          
          lower_bound, upper_bound = proportion_confint(success_count, n, alpha=alpha, method='normal')
          
          print(f"{confidence_level*100}% 신뢰구간: ({lower_bound}, {upper_bound})")

          4. 통계 검정 - 모수적 방법

          통계 검정에 대한 마인드맵은 다음 글 참고하세요

          https://snowgot.tistory.com/153

           

          (8) Khan Academy: 유의성 검정과 절차, 통계방법론 정리

          이번 글에서는 추론통계의 핵심 유의성 검정과 등장하는 개념, 그리고 일반적인 통계방법론을 정리해본다.1. 글목차유의성 검정에 필요한 개념1종오류와 2종오류유의성 검정 절차통계검정 절차

          snowgot.tistory.com

          • 정규성 검정
            • $H_o:$ 데이터가 정규분포를 따른다.
            • $H_1:$ 데이터가 정규분포를 따르지 않는다.
            1. shapiro-wilk : 작은 표본(n≤ 50) 에 적합
            2. Kolmogorov-Smirnov: 표본이 클 때 사용, shaprio보다 덜 강력
          • t-test검정을 위한 정규성을 시행할 시, 두 그룹 중에 하나라도 정규성을 만족하지 않으면 바로 맨-휘트니 방법으로 넘어감
          #정규성 검정 - Shapiro-Wilk
          from scipy.stats import shapiro
          statistic, p_value = shapiro(data)
          
          #정규성 검정 - Kolmogorov-Smirnov 검정 
          from scipy.stats import kstest
          statistic, p_value = kstest(data, 'norm')
          
          print(f"Test Statistic: {statistic}")
          print(f"p-value: {p_value}")
          
          if p_value > alpha:
              print("정규성을 따른다고 볼 수 있습니다.")
          else:
              print("정규성을 따른다고 볼 수 없습니다.")
          • 시각화 예제
          from scipy.stats import shapiro, probplot
          import matplotlib.pyplot as plt
          import seaborn as sns
          
          man = df3[df3['성별'] =='남']['급여']
          woman = df3[df3['성별'] =='여']['급여']
          
          # Shapiro-Wilk test
          statistic_m, p_value_m = shapiro(man)
          statistic_w, p_value_w = shapiro(woman)
          
          # Figure 생성
          plt.figure(figsize=(12, 6))
          
          # 1. 남성 그룹 히스토그램과 Q-Q plot
          plt.subplot(2, 2, 1)
          sns.histplot(man, kde=True, label='man', color='blue')
          plt.title(f'Man Salary Distribution\nShapiro p-value: {p_value_m:.3f}')
          plt.legend()
          
          plt.subplot(2, 2, 2)
          probplot(man, dist="norm", plot=plt)
          plt.title('Man Q-Q Plot')
          
          # 2. 여성 그룹 히스토그램과 Q-Q plot
          plt.subplot(2, 2, 3)
          sns.histplot(woman, kde=True, label='woman', color='green')
          plt.title(f'Woman Salary Distribution\nShapiro p-value: {p_value_w:.3f}')
          plt.legend()
          
          plt.subplot(2, 2, 4)
          probplot(woman, dist="norm", plot=plt)
          plt.title('Woman Q-Q Plot')
          
          plt.tight_layout()
          plt.show()
          • 등분산 검정
            • $H_o:$ 두 집단의 분산이 같다.
            • $H_1:$ 두 집단의 분산이 다르다.
          검정 이름 정규성 가정 이상치에 대한 민감도 특징
          Levene 필요 없음 중간 여러 그룹의 분산이 동일한지 검정
          Bartlett 필요함 민감함 정규성 만족 시 Levene보다 검정력 높음
          Fligner 필요 없음 강건함 순위 기반 비모수 검정, 이상치 존재 시 유리
          from scipy.stats import levene, bartlett, fligner
          
          statistic, p_value = levene(data1, data2)
          # statistic, p_value = bartlett(data1, data2)
          # statistic, p_value = fligner(data1, data2)
          • 일표본 t 검정
            • $H_o:$ 표본의 평균이 특정 값과 같다.
            • $H_1:$ 표본의 평균의 특정 값과 같지 않다.
          • 이표본 t 검정
            • $H_o:$ 두 집단의 평균이 같다.
            • $H_1:$ 두 집단의 평균이 다르다.
          이표본 t검정일 때 정규성 & 등분산 검정을 진행하게 되는데, 등분산일 경우 두 표본의 분산을 하나로 합쳐서 추정하는 "합동 분산"활용이 가능해짐. 이는 검정통계량의 분산을 하나로 합치게 되고 좀 더 정확한 추정이 가능하게함. 하지만 등분산이 만족되지 않은 경우 비모수 검정인 맨-휘트니를 진행함
          # 독립표본 t 검정
          from scipy.stats import ttest_ind,ttest_rel
          # 등분산성을 만족하는 경우
          statistic, p_value = ttest_ind(data1, data2, equal_var=True)
          # 등분산성을 만족하지 않는 경우
          # statistic, p_value = ttest_ind(data1, data2, equal_var=False)
          
          #대응표본 t검정
          # statistic, p_value = ttest_rel(data_before, data_after)
          
          print(statistic, p_value)
          • 분산 분석
            • 목적: 그룹간 분산과그룹내 분산을 비교
            • $H_0:$ 모든 그룹의 평균값이 같다.
            • $H_1:$ 적어도 하나 그룹의 평균값이 다른 그룹과 다르다.
          import pandas as pd
          import numpy as np
          
          # 그룹별 데이터 생성 (예시 데이터)
          np.random.seed(123)
          
          group_A = np.random.normal(loc=50, scale=5, size=30)
          group_B = np.random.normal(loc=55, scale=5, size=30)
          group_C = np.random.normal(loc=60, scale=5, size=30)
          
          # 데이터 프레임 생성
          df = pd.DataFrame({
              'score': np.concatenate([group_A, group_B, group_C]),
              'group': ['A'] * 30 + ['B'] * 30 + ['C'] * 30
          })
          
          import statsmodels.api as sm
          from statsmodels.formula.api import ols
          
          # ANOVA 모델 생성
          model = ols('score ~ C(group)', data=df).fit()
          
          # 분산분석표 생성
          anova_table = sm.stats.anova_lm(model, typ=2)
          
          # 결과 출력
          print(anova_table)
          • 사후 분석
            • Tukey’s HSD 검정
          from statsmodels.stats.multicomp import pairwise_tukeyhsd
          
          # 일원분산분석의 사후분석 예시
          tukey_result = pairwise_tukeyhsd(endog=df['score'], groups=df['group'], alpha=0.05)
          
          print(tukey_result)
          • two-way anova
          import statsmodels.api as sm
          from statsmodels.formula.api import ols
          
          # 이원분산분석 모델 생성 (교호작용 포함)
          model = ols('값 ~ C(요인1) * C(요인2)', data=df).fit()
          
          # 분산분석표 생성
          anova_table = sm.stats.anova_lm(model, typ=2)
          # 결과 출력
          print(anova_table)
          • Welch Anova
            • 목적: 그룹 간의 등분산성이 가정이 필요없는 경우 사용
            • $H_0:$ 모든 그룹의 평균이 같다.
            • $H_1:$ 적어도 하나 그룹의 평균값이 다른 그룹과 다르다.
          import numpy as np
          import pandas as pd
          import scipy.stats as stats
          import statsmodels.api as sm
          from statsmodels.formula.api import ols
          
          # 기술 통계량 확인
          print(data.groupby('group').agg(['mean', 'std', 'count']))
          
          # 등분산성 검정 (Levene's test)
          levene_stat, levene_p = stats.levene(group1, group2, group3)
          print(f"\nLevene's test: statistic={levene_stat:.4f}, p-value={levene_p:.4f}")
          if levene_p < 0.05:
              print("The variances are significantly different. Welch ANOVA is appropriate.")
          else:
              print("The variances are not significantly different. Standard ANOVA can be used.")
          
          # Welch ANOVA 수행
          welch_stat, welch_p = stats.f_oneway(group1, group2, group3, welch=True)
          print(f"\nWelch ANOVA: F-statistic={welch_stat:.4f}, p-value={welch_p:.4f}")
          
          if welch_p < 0.05:
              print("Reject null hypothesis: At least one group mean is different.")
          else:
              print("Fail to reject null hypothesis: No significant difference between group means.")
          
          # 사후 검정 (Games-Howell post-hoc test)
          from statsmodels.stats.multicomp import pairwise_tukeyhsd
          # 참고: statsmodels에는 Games-Howell 검정이 직접 구현되어 있지 않아 Tukey HSD를 대신 사용
          # 실제 연구에서는 Games-Howell 테스트 사용 권장 (등분산 가정 필요 없음)
          tukey_results = pairwise_tukeyhsd(data['value'], data['group'], alpha=0.05)
          print("\nPost-hoc test (Tukey HSD, note: Games-Howell would be better for unequal variances):")
          print(tukey_results)

          4. 통계검정 - 비모수적 방법

          4.1. 비모수방법 표 정리

          • 데이터가 특정 분포를 따르지 않거나 데이터 양이 적어 분포가정을 만족시키기 어려울때 사용. 데이터의 순위나 중앙값을 사용
          검정명 대응 표본 그룹 수 데이터 예시 연관 있는 검정
          카이제곱 X 2 or 3 범주형 성별에 따른 선호도 차이 -
          피셔 정확검정 O 2 범주형 성공/실패 비율 비교 카이제곱
          맨 휘트니 X 2 연속형 두 집단의 성적 중앙값 비교 독립표본 t 검정
          크루스칼 왈리스 X 3이상 연속형 여러 그룹의 성적비교 ANOVA
          윌콕슨 O 2 연속형 치료 전후의 차이 대응표본 t검정
          맥니마 O 2 이항형 치료 전후의 호전 약화 여부 대응표본 카이제곱
          부호검정 O 2 연속형/이항형 치료 전후의 비율 변화 대응표본 t검정
          프리드먼 O 3이상 연속형 세 시점에서의 결과 비교 반복측정 ANOVA
          코크란Q O 3이상 이항형 세 시점에서의 성공/실패 비교 반복측정 카이제곱

          4.2. 카이제곱 검정

          1. 독립성 검정
          • $H_0$: 두 범주형 변수는 독립이다.
          • $H_1$: 두 범주형 변수는 독립이 아니다.

          2. 적합도 검정

          • 수식: $\chi^{2} = \sum^{n}{i}\frac{(O{i} - E_{i})^{2}}{E_{i}}$
          • $H_0$: 관측과 기대 비율은 차이가 없다.
          • $H_1$: 관측과 기대 비율은 차이가 있다.

          import numpy as np
          from scipy.stats import chisquare
          
          # 주장하는 방문 비율(%)을 기반으로 기대값을 계산
          expected_ratios = np.array([10, 10, 15, 20, 30, 15])
          total_visits = 200
          expected_counts = (expected_ratios / 100) * total_visits
          
          # 관찰된 방문 수
          observed_counts = np.array([30, 14, 34, 45, 57, 20])
          
          # 카이제곱 적합도 검정을 수행
          chi2_stat, p_val = chisquare(f_obs=observed_counts, f_exp=expected_counts)
          
          # 결과 출력
          print(f"Chi-squared Statistic: {chi2_stat:.3f}") # 11.442
          print(f"P-value: {p_val:.3f}") # 0.043

          3. 동질성 검정

          import numpy as np
          from scipy.stats import chi2_contingency
          
          # 관찰된 데이터: 각 학교에서 학생들이 선호하는 과목의 빈도수
          data = np.array([
              [50, 60, 55],  # 수학 선호
              [40, 45, 50],  # 과학 선호
              [30, 35, 40]   # 문학 선호
          ])
          
          # 카이제곱 통계량, p-value, 자유도, 기대값을 계산
          chi2_stat, p_val, dof, expected = chi2_contingency(data)
          
          # 결과 출력
          print(f"Chi-squared Statistic: {chi2_stat:.3f}"): 0.817
          print(f"P-value: {p_val:.3f}") : 0.936
          print(f"Degrees of Freedom: {dof:.3f}") # 0.400
          print("Expected frequencies:")
          print(expected.round(3))
          
          '''
          [[48.889 57.037 59.074]
           [40.    46.667 48.333]
           [31.111 36.296 37.593]]
          '''

          4.3. Wilcoxon 검정

          • 목적: 짝지어진 관측 값(대응 표본) 이 차이가 0이라는 가설 검정
          • 가정: 데이터는 연속적이거나 ordinal, 데이터는 대칭
          • $H_o:$ 두 그룹의 중앙값 차이는 0이다.
          • $H_1:$ 두 그룹의 중앙값 차이는 0이 아니다.
          from scipy.stats import wilcoxon
          
          # 예시 데이터 (대응 표본)
          before = [20, 21, 22, 23, 24, 25]
          after = [21, 22, 19, 23, 26, 25]
          
          # Wilcoxon 부호 순위 검정
          stat, p_value = wilcoxon(before, after)

          4.4 Mann-Whitney U 검정

          • 목적: 독립된 두 표본간의 중위수 차이 검정
          • 가정: 데이터는 연속적이거나 ordinal, 두 표본은 독립
          • $H_o:$ 두 그룹의 중위 수 차이는 0이다.
          • $H_1:$ 두 그룹의 중위수 차이는 0 이 아니다.
          from scipy.stats import mannwhitneyu
          
          # 예시 데이터 (독립 표본)
          group1 = [14, 16, 18, 20, 22]
          group2 = [18, 20, 22, 24, 26]
          # Mann-Whitney U 검정
          stat, p_value = mannwhitneyu(group1, group2, alternative='two-sided')

          4.5. Kruskal-Wallis 검정

          • 목적: 세 개 이상의 독립된 표본의 중앙값을 비교. anova의 대응
          • 가정: 각 표본은 독립, 데이터는 ordinal
          • $H_o:$ 모든 그룹의 중위수 차이가 0이다.
          • $H_1:$ 적어도 하나 그룹의 중앙값이 다른 그룹과 다르다.
          from scipy.stats import kruskal
          
          # 예시 데이터 (세 그룹의 독립 표본)
          group1 = [10, 12, 14, 16, 18]
          group2 = [15, 17, 19, 21, 23]
          group3 = [24, 26, 28, 30, 32]
          
          # Kruskal-Wallis H 검정
          stat, p_value = kruskal(group1, group2, group3)

          4.6. 맥니마 검정

          • 목적: 이항 데이터에서 전후 또는 대응된 두 집단의 비율 변화
          • 가정: 데이터는 binary, 대응 표본 존재, 표본크기 충분히 커야함
          • $H_0$: 두 대응된 이항 분포간 변화 없음
          • $H_1$: 두 대응된 이항 분포간 변화 있음
          import numpy as np
          from statsmodels.stats.contingency_tables import mcnemar
          
          # 2x2 분할표 생성 (a, b, c, d)
          # 예시 데이터: (성공/실패 전후)
          # [[성공 전/성공 후, 성공 전/실패 후],
          #  [실패 전/성공 후, 실패 전/실패 후]]
          table = np.array([[40, 10],  # 성공 전/후
                            [5, 45]])  # 실패 전/후
          
          # 맥니마 검정 수행
          result = mcnemar(table, exact=True)  # exact=True: 정확한 검정 수행 (표본 크기가 작을 경우)
          
          # 결과 출력
          print('맥니마 검정 통계량:', result.statistic)
          print('p-value:', result.pvalue)

          4.7. 피셔의 정확검정

          • 목적: 작은 표본에서 두 범주형 변수간의 독립성의 검정
          • 가정: 각셀 기대빈도(N≤5) 이항 분포 또는 범주형 자료료 표현됨, 행과 열의 합은 고정
          • $H_0$: 두 변수는 독립적이다.
          • $H_1$: 두 변수는 독립적이지 않다.
          import numpy as np
          from scipy import stats
          
          # 2x2 분할표 데이터 (예시)
          # [[그룹 A, 그룹 B],
          #  [그룹 A, 그룹 B]]
          table = np.array([[8, 2],  # 결과 X
                            [1, 5]]) # 결과 Y
          
          # 피셔의 정확검정 수행
          odds_ratio, p_value = stats.fisher_exact(table, alternative='two-sided')
          
          # 결과 출력
          print('오즈비:', odds_ratio)
          print('p-value:', p_value)

          4.8. 프리드먼 검정

          • 목적: 반복 측정된 세개 그룹 간의 차이를 비교하는 검정
          • 가정: 연속형 or 순위형 데이처 사용, 각 그룹은 동일한 주제/대상에 여러번 측정된 결과
          • $H_0$ : 세 개 이상의 관련된 그룹 간의 차이가 없다.
          • $H_1$: 적어도 하나의 그룹에서 다른 그룹과 유의미한 차이가 있다.
          import numpy as np
          from scipy.stats import friedmanchisquare
          
          # 예시 데이터: 세 개의 처리에 대한 측정값
          # 동일한 대상(피험자)에 대해 세 번의 처리를 반복적으로 측정한 값
          # 행은 피험자, 열은 각각의 처리
          data1 = [10, 20, 30, 40]  # 처리 1
          data2 = [12, 21, 29, 35]  # 처리 2
          data3 = [14, 22, 27, 37]  # 처리 3
          
          # 프리드먼 검정 수행
          statistic, p_value = friedmanchisquare(data1, data2, data3)
          
          # 결과 출력
          print('프리드먼 검정 통계량:', statistic)
          print('p-value:', p_value)

          4.9. 부호검정

          • 목적: 관련된 두 그룹 간의 차이를 평가하는 검정, 중앙 값의 차이를 평가
          • 가정: 대응된 표본
          • $H_0:$ 두 그룹간 중앙값 차이가 없다.
          • $H_1$ : 두 그룹간 중앙값 차이가 있다.
          import numpy as np
          from scipy import stats
          
          # 예시 데이터: 두 그룹에 대한 대응된 표본 (처치 전후 데이터)
          before = [85, 70, 90, 80, 65, 75, 95, 70]  # 처리 전
          after  = [80, 65, 88, 82, 63, 78, 92, 72]  # 처리 후
          
          # 부호검정 수행
          result = stats.binom_test((before > after).sum(), n=len(before), p=0.5, alternative='two-sided')
          
          # 결과 출력
          print('p-value:', result)

          4.10. 코크란 q검정

          • 목적: 세개 이상의 이항 처리에 대한 대응 표본의 비율의 차이를 비교하기 위한 검정
          • 가정: 데이터는 binary, 대응된 표본
          • $H_0$: 세 개 이상의 처리 간에 차이가 없다.
          • $H_1$ : 적어도 하나의 처리에서는 다른 처리와 유의미한 차이가 있다.
          import numpy as np
          import pandas as pd
          from statsmodels.sandbox.stats.runs import cochrans_q
          
          # 예시 데이터: 세 가지 처리에 대한 이항형 결과 (성공/실패)
          # 각 행은 개별 피험자, 각 열은 다른 처리
          data = pd.DataFrame({
              'treatment1': [1, 0, 1, 1, 0],  # 처리 1 결과 (성공: 1, 실패: 0)
              'treatment2': [0, 1, 1, 1, 0],  # 처리 2 결과
              'treatment3': [1, 0, 1, 0, 1],  # 처리 3 결과
          })
          
          # 코크란 Q 검정 수행
          statistic, p_value = cochrans_q(data)
          
          # 결과 출력
          print('코크란 Q 검정 통계량:', statistic)
          print('p-value:', p_value)

          5. 회귀 분석

          • 선형성 가정: 독립변수와 종속변수와 선형
            1. sns.pariplot 혹은 변수간 산점도를 이용해 확인
            2. sns.heatmap(df.corr()) 로 확인
          • 독립성 가정: 관측치들간에 서로 독립
            • 더빈왓슨(Durbin Watson)통계량
              • $DW = \frac{\sum_{t=2}^{n} (e_t - e_{t-1})^2}{\sum_{t=1}^{n} e_t^2}$
              • $H_0$: 잔차 간에는 자기 상관이 없다.
              • $H_1$: 잔차 간에는 자기 상관이 존재 한다.
              • 해석
                • 0에 가까움: 잔차들 간에 양의 자기 상관이 존재
                • 2에 가까움: 잔차들 간 자기 상관이 없다(독립성 가정 만족)
                • 4에 가까움: 잔차들 간의 음의 자기 상관이 존재
          • 등분산 가정: 잔차의 분산은 모든 수준의 독립변수에 대해서 일정
            1. 잔차 vs 예측값 plot
            2. Breush-Pagan 검정
              • $H_0$: 잔차의 분산이 일정하다(등분산성이 있다)
              • $H_1$: 잔차의 분산이 일정하지 않다.(이분산성이 있다)
          import statsmodels.stats.api as sms
          import statsmodel.api as sm
          model = sm.OLS(y,X).fit
          static , pvalue, f_static , f_pv = sms.het_breuschpagan(model.resid,model.model.exog)
          • 정규성 가정: 잔차항이 정규분포를 따름
            1. QQ플랏
            2. sharpiro-wilk 검정
              • $H_0:$ 정규성을 따른다.
              • $H_1:$ 정규성을 따르지 않는다.
          from scipy.stats import shapiro
          static, pvalue = shapiro(model.resid)
          • 전체 코드
          # 1. 잔차 vs 예측값 : 잔차의 등분산성 가정 
          # 2. 잔차의 히스토그램: 잔차의 정규성 가정
          # 3. QQPLOT: 잔차의 정규성 가정
          # 4. 다중공선성: 독립변수의 독립성 가정
          # 5. 더빈왓슨 검정: 잔차의 독립성 가정
          import statsmodels.api as sm
          import matplotlib.pyplot as plt
          import seaborn as sns 
          from statsmodels.stats.outliers_influence import variance_inflation_factor as VIF
          
          def review_lr(X,y, add_const = True):
              if add_const:
                  X = sm.add_constant(X)
                  model = sm.OLS(y, X).fit()
              else:
                  model = sm.OLS(y, X).fit()
              
          
              residuals = model.resid
              fitted_values = model.fittedvalues
          
              plt.figure(figsize = (10,6))
              #1. 잔차 vs 예측값
              plt.subplot(2,2,1)
              sns.residplot(x = fitted_values, y = residuals, lowess = True)
              plt.title('Residual vs Fitted')
          
              # 2. 잔차의 히스토그램
              plt.subplot(2,2,2)
              sns.histplot(residuals, kde = True)
              plt.title('Residuals Distribution')
          
              # 3. 잔차의 QQplot
              plt.subplot(2,2,3)
              sm.qqplot(residuals, line = '45', fit = True, ax=plt.gca())
              plt.title('QQ plot)')
          
              # 4. 다중공선성
              plt.subplot(2,2,4)
              vif_data = pd.DataFrame()
              vif_data['VIF'] = [VIF(X.values,i) for i in range(X.shape[1])]
              vif_data['Feature'] = X.columns
          
              sns.barplot(x = 'VIF', y = 'Feature', data = vif_data)
              plt.title('VIF')
              plt.show()
          
              # 5. 잔차의 독립성 검정: 더빈왓슨검정
              # -> 기본적으로 sm.OLS(Y,X).summary()에서 제공
              dw_stat = sm.stats.durbin_watson(residuals)
              print(f"Durbin-Watson statistic: {dw_stat:.3f}")
              '''
              0에 가까우면 잔차의 양의 자기 상관성
              2에 가까우면 잔차의 독립성 가정 만족
              4에 가까우면 잔차가 음의 자기 상관성을 가짐
              '''
          
              #6. 잔차의 정규성 검정: # Shapiro-Wilk 검정
              from scipy import stats
              residuals = model.resid
              shapiro_test = stats.shapiro(residuals)
          
              print(f"Shapiro-Wilk Test Statistic: {shapiro_test.statistic:3f}")
              print(f"p-value: {shapiro_test.pvalue:3f}")
          
              # 결과 해석
              if shapiro_test.pvalue > 0.05:
                  print("p-value가 0.05보다 큼: 잔차가 정규분포를 따를 가능성이 높음 (귀무가설을 기각할 수 없음).")
              else:
                  print("p-value가 0.05보다 작음: 잔차가 정규분포를 따르지 않을 가능성이 높음 (귀무가설 기각).")
          		
          		return model.summary()
          y = df['MEDV']
          X = df.drop(columns=['MEDV'])
          review_lr(X,y)

          Durbin-Watson statistic: 1.246
          Shapiro-Wilk Test Statistic: 0.917579
          p-value: 0.000000
          p-value가 0.05보다 작음: 잔차가 정규분포를 따르지 않을 가능성이 높음 (귀무가설 기각).

           

          • 선형회귀 다중공선성 관리 방법
            • 상관관계가 0.7 이상인 것 기준
            • 도메인지식을 바탕으로 중요하다고 생각되는 변수를 유지
            • 다중공선성이 높은 변수들이 많다면 차원 축소 진행
          corr_df = df.drop(columns =['MEDV']).corr().unstack().reset_index()
          corr_df.columns =['col1','col2','corr']
          corr_df =corr_df[corr_df['col1'] != corr_df['col2']]
          	.sort_values('corr').reset_index(drop=True)[::2].reset_index(drop=True)
          display(corr_df)
          
          print('''
          강한 양의 상관관계 및 강한 음의 상관관계를 가지는 컬럼들이 다수 존재한다. 다중공선성이 발생할 여지가 크기에 이를 해결하기 위해 두가지 접근을 진행한다.
          1. vif값을 기준으로 변수를 제거하는 방식
          2. pca를 이용해서 변수간 선형관계를 줄인 축소된 차원으로 분석을 진행
          ''')

          • VIF
            • $VIF_i = \frac{1}{1 - R_i^2}$
            • $R^{2}_{i}$: VIF를 측정하고 싶은 독립변수를 종속변수로 놓고, 나머지 독립변수를 이용해 회귀분석을 진행한 결정계수
            • 즉 결정계수가 90%이 된다는 것은 VIF가 10이 된다는 것
          from statsmodels.stats.outliers_influence import variance_inflation_factor
          
          df_vif = df.drop(columns =['MEDV'])
          vif  = pd.DataFrame()
          vif['VIF Factor'] = [variance_inflation_factor(df_vif.values,i) for i in range(df_vif.shape[1])]
          vif['features'] = df_vif.columns
          display(vif)
          • 방법1) 변수 제거
          • 방법2) 차원축소(PCA)
            • 거리기반의 알고리즘이기 때문에 정규화 필요
            • 전체 변수를 넣어도, 부분변수를 넣어도되나, 전자의 경우 해석력이 많이 손상됌
          from sklearn.decomposition import PCA
          from sklearn.preprocessing import StandardScaler
          
          sc =StandardScaler()
          
          scaled_df = sc.fit_transform(df.drop(columns =['MEDV']))
          pca = PCA(n_components=7)
          pca_df = pca.fit_transform(scaled_df)
          
          X = pca_df
          y = df['MEDV']
          X = sm.add_constant(X)
          
          model = sm.OLS(y,X).fit()
          display(model.summary())
          • 방법3) Lidge/Lasso 회귀
            • Lasso(L1, 절대값):
              • 변수를 선택하고 싶은 경우
              • 일부 변수만 중요하고, 나머지는 무시해도 되는 상황
              • 희소한 데이터셋에서 불필요한 변수를 제거하여 모델의 해석력을 높이고 싶은 경우
            • Ridge(L2, 제곱):
              • 모든 변수가 모델에 중요할 때
              • 다중공선성 문제를 해결하고 과적합을 방지하고 싶은 경우
              • 변수 선택보다는 모델 성능 향상이 더 중요한 경우

          6. 시계열 검정

          • 정상성 검정
          import pandas as pd
          import numpy as np
          from statsmodels.tsa.stattools import adfuller
          
          # ADF 검정 수행
          result = adfuller(data)
          
          print('ADF Statistic:', result[0])
          print('p-value:', result[1])
          • 잔차 분석
          arima_model.plot_diagnostics
          plt.show()
          • 잔차의 자기상관성 확인:Ljung-box 검정
            • $H_0$: 잔차끼리의 자기 상관이 없다
            • $H_1$: 잔차끼리의 자기 상관이 있다
          from statsmodels.stats.diagnostic import acorr_ljungbox
          
          # Ljung-Box 검정 (잔차의 독립성 검정)
          ljung_box_test = acorr_ljungbox(arima_model.resid, lags=[10], return_df=True)
          print(ljung_box_test)
          • 시계열 분해
          from statsmodels.tsa.seasonal import seasonal_decompose
          
          # 시계열 분해 (Additive 모델)
          # period는 데이터의 계절성 주기 설정 (예: 월별 데이터의 경우 12)
          decomposition = seasonal_decompose(data, model='additive', period=12)
          
          # 분해 결과 시각화
          decomposition.plot()
          plt.show()
          • 지수 평활 모형
          from statsmodels.tsa.holtwinters import ExponentialSmoothing
          
          # 단순 지수 평활 모델 적합
          model = ExponentialSmoothing(data, trend=None, seasonal=None)
          model_fit = model.fit(smoothing_level=0.8, optimized=False)
          
          # 예측값 생성
          fitted_values = model_fit.fittedvalues
          
          # 원본 데이터와 예측값 비교
          plt.figure(figsize=(12, 6))
          plt.plot(data, label='원본 데이터')
          plt.plot(fitted_values, label='예측값', color='red')
          plt.legend()
          plt.show()

          6.1. 자기회귀(AR) 모형

          • 현재 값이 과거 값들의 선형 조합으로 표현될 수 있다고 가정
          • 자기상관에 강한 시계열과 추세나 패턴이 지속되는 경향이 있을때 유용(Ex 주식, 기온 데이터)
          • PACF: p 시차 이후 급격히 0에 가까워지며, ACF는 점진적으로 감소
          from statsmodels.tsa.ar_model import AutoReg
          model_ar = AutoReg(data, lags=p).fit()
          forecast = model_ar.forecast(steps=10)

          6.1. PACF

          • 편자기상관함수는 현재 값과 특정 시점이 값의 상관관계를 구하기 위한 함수
          • 시차가 2라면 $y_t$와 $y_{t-2}$의 두 시차를 고려하며 중간 시차 $y_{t-1}$이 영향을 제거
          • 첫 번째 선은 차수 1(lag1)
          • 다음 그래프를 보고 AR(2) 결정

          파란선: 유의성 경계

          6.3.  MA 모형

          • 현재 값이 과거의 오차항(예측 오류)의 선형 조합으로 표현된다고 가정
          • 단기적인 충격이나 이벤트의 반응을 모델링 하는데 유용(주가 실적 발표)
          • PACF: 점진적으로 감소, ACF: q  시차 이후 급격히 0에 가까워짐
          # MA(q) 모델 - ARMA로 구현
          from statsmodels.tsa.arima.model import ARIMA
          model_ma = ARIMA(data, order=(0,0,q)).fit()
          forecast = model_ma.forecast(steps=10)

          6.4. ACF(Auto Correlation Function)

          • 자기 상관 함수는 각 시차(lag)에 대해 현재 값과 과거 값의 상관관계를 계산, 시차 q를 결정
          • 첫 번째 세로 선은 차수 0으로 자기와의 상관관계(항상 1.0)
          • 다음은 MA(3)이 결정되는 ACF 그림

          • ACF, PACF 그리기 
          import matplotlib.pyplot as plt
          from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
          
          # 한 그림에 두 플롯 함께 표시하기
          fig, axes = plt.subplots(2, 1, figsize=(12, 8))
          plot_acf(data, lags=40, alpha=0.05, ax=axes[0])
          axes[0].set_title('ACF')
          plot_pacf(data, lags=40, alpha=0.05, ax=axes[1])
          axes[1].set_title('PACF')
          plt.tight_layout()
          plt.show()

          6.5. ARIMA

          • ARIMA
          from statsmodels.tsa.arima.model import ARIMA
          import matplotlib.pyplot as plt
          
          # ARIMA 모델 적합 (p=1, d=1, q=1 파라미터로 예시)
          model = ARIMA(data, order=(1, 1, 1))
          model_fit = model.fit()
          
          # 예측값 생성
          fitted_values = model_fit.fittedvalues
          
          # 원본 데이터와 예측값 비교
          plt.figure(figsize=(12, 6))
          plt.plot(data, label='원본 데이터')
          plt.plot(fitted_values, label='ARIMA 예측값', color='red')
          plt.legend()
          plt.title('ARIMA 모델 적합 결과')
          plt.show()
          
          # 모델 요약 출력 (선택사항)
          print(model_fit.summary())
          • SARIMA
          from statsmodels.tsa.statespace.sarimax import SARIMAX
          
          # SARIMA 모델 적합 (p, d, q, P, D, Q, m 설정)
          p = 1  # AR 차수
          d = 1  # 차분 차수
          q = 1  # MA 차수
          P = 1  # 계절 AR 차수
          D = 1  # 계절 차분 차수
          Q = 1  # 계절 MA 차수
          m = 12  # 계절 주기 (예: 월별 데이터의 경우 12)
          
          model = SARIMAX(data, order=(p, d, q), seasonal_order=(P, D, Q, m))
          model_fit = model.fit()
          
          print(model_fit.summary())
          
          # 예측
          forecast = model_fit.forecast(steps=12)
          print(forecast)

          6.6 auto ARIMA

          import pmdarima as pm
          from statsmodels.tsa.arima.model import ARIMA
          
          data = df.copy()
          # 자동으로 최적의 ARIMA 모델 찾기
          auto_model = pm.auto_arima(data, 
                                    start_p=0, start_q=0,
                                    max_p=5, max_q=5,
                                    d=None, max_d=2,
                                    seasonal=False,
                                    trace=True,
                                    error_action='ignore',
                                    suppress_warnings=True,
                                    stepwise=True)
          
          print(f"최적의 ARIMA 모델 파라미터: {auto_model.order}")
          
          # 최적 모델 적합
          best_model = ARIMA(data, order=auto_model.order)
          best_model_fit = best_model.fit()
          
          # 예측값 생성
          best_fitted_values = best_model_fit.fittedvalues
          
          # 원본 데이터와 예측값 비교
          plt.figure(figsize=(12, 6))
          plt.plot(data, label='원본 데이터')
          plt.plot(best_fitted_values, label=f'최적 ARIMA{auto_model.order} 예측값', color='red')
          plt.legend()
          plt.title('optimization result')
          
          #진단코드
          model.plot_diagnostics(figsize=(12, 8))
          plt.tight_layout()
          plt.show()

           

          • VAR 모형
          from statsmodels.tsa.vector_ar.var_model import VAR
          
          # 데이터 분할 (학습 및 테스트 데이터)
          train_data = df[:-10]
          test_data = df[-10:]
          
          # VAR 모델 적합
          model = VAR(train_data)
          model_fit = model.fit(maxlags=15, ic='aic')
          
          print(model_fit.summary())
          
          # 예측
          lag_order = model_fit.k_ar
          forecast_input = train_data.values[-lag_order:]
          forecast = model_fit.forecast(y=forecast_input, steps=10)
          
          # 예측 결과를 데이터프레임으로 변환
          forecast_df = pd.DataFrame(forecast, index=test_data.index, columns=df.columns)
          
          print(forecast_df)

          7. 문서기록

          • 2025.04.18. 최초 작성
          • 2025.04.22. 머신러닝 항목 분리
          • 2025.04.25. stastsmodel 디렉토리 등 추가

           

           

          이번 장에는 batch processing이라고 불리우는 일괄처리 방법에 대해서 기술한다. 분산 저장 시스템에서 함께 등장하는 맵리듀스 방식과 과거 유닉스 방식의 유사점을 비교하면서 설명하는 장이다.


          신규개념

          개념 설명
          데몬 프로세스 멀티태스킹 운영 체재에서 사용자가 직접 제어하지 않고, 백그라운드에서 작업을 하는 프로그램
          NAS Network Attached Storage, 여러 사용자가 TCP/IP 네트워크로 파일을 저장하고 공유할 수 있는 중앙 집중식 파일 서버
          SAN Storage Area Network, 서버, 스토리지 시스템, 소프트웨어 및 서비스를 결합하여 특정 환경에 맞게 조정된 전용 네트워크
          RAID 여러개의 디스크를 묶어 하나의 디스크처럼 사용하는 기술
          맵리듀스 워크플로 Work Flow, 맵 리듀스 작업을 관리하는 워크 플로우 시스템

           

          1. 요청응답 방식의 시스템 3가지

          • 서비스(온라인 시스템)

          서비스는 클라이언트로부터 요청이나 지시가 올 때까지 기다린다. 요청이 들어오면 가능한 빨리 요청을 처리하며 응답시간이 그 지표가된다.

          • 일괄 처리 시스템(오프라인 시스템)

          매우 큰 입력 데이터를 받아 처리하는 작업을 수행하고 결과 데이터를 생산한다. 하루에 한번 수행과 같이 반복적인 일정으로 수행하며, 성능지표는 처리량이다.

          • 스트림 처리 시스템(준실시간 시스템)

          스트림 처리는 입력 이벤트가 발생한 후 바로 작동한다. 일괄 처리 시스템보다는 지연시간이 낮지만 기반은 같다.

          2. 일괄 처리 시스템의 예시

          맵 리듀스는 검색 엔진에서 사용할 색인을 위하여 처음 사용됨.  색인은 용어 사전 파일로 효율적으로 특정 키워드를 조회해 키워드가 포함된 문서 ID의 모록을 찾아 검색 결과를 관련성 순으로 순위를 매기는 등의 일을 함.  머신러닝 시스템(스팸 필터, 이상 검출, 이미지 인식)을 구축하거나 추천시스템이 그 예

           

          3. 유닉스의 철학과 단점

          1. 각 프로그램이 한가지 일만 하도록 작성하라.
          2. 모든 프로그램의 출력은 아직 알려지지 않은 다른 프로그램의 입력으로 쓰일 수 있다고 생각하라.
          3. 소프트웨어를 빠르게 써볼 수 있게하고 구축하라.
          4. 프로그래밍 작업을 줄이려면 미숙한 도움보다는 도구를 사용하라.

          유닉스 도구는 입력 파일에 손상을 주지 않으며, 어느 시점이든 less로 보내 원하는 형태의 출력이 확인 가능하여 디버깅이 유용하고, 특정 파이프라인 단계의 출력에 파일을 쓰고 다음 단계의 입력으로 사용가능하여 전체 파이프라인 재시작이 필요없는 장점 등이 있다. 하지만 이런 장점에도 불구하고 단일장비에서만 실행된다는 점 때문에 하둡 같은 도구의 필요성이 생겼다.

           

          4. 맵리듀스와 분산파일 시스템

          하둡 맵리듀스 구현에서 파일 시스템은 HDFS(Hadoop Distributed File System)이라고 하는데 GFS(Google File System)를 재구현한 오픈소스이다. HDFS는 비공유 원칙을 기반으로 하기 때문에 NAS, SAN의 아키텍쳐와 다른 점이다. 이때 2가지 콜백 함수로 구현되어있다.

          • 매퍼(Mapper): 모든 입력 레코드마다 한번씩 실행되며, 입력 레코드로 부터 키와 값을 추출하는 작업이다. 
          • 리듀서(Reducer): 맵리듀스 프레임워크는 매퍼가 생산한 키-값 쌍을 받아 같은 키를 가지느 레코드를 모으고 해당 값의 집합을 반복해 리듀서 함수를 호출한다. 리듀서는 이때 출력 레코드를 생산한다.

          https://blog.naver.com/bellepoque7/222984401079

           

          [개발] Hadoop vs Spark 등장 배경과 차이

          1. 데이터 분산 처리 시스템의 등장 - Hadoop Hadoop과 Spark를 알기전에 등장배경을 알면 이해하기 쉽...

          blog.naver.com

          • 조인 알고리즘
            • 정렬 병합 조인
            • 브로드캐스트 해시 조인
            • 파티션 해시 조인

          5. 분산 일괄 처리 프레임워크가 해결해야할 문제

          • 파티셔닝

          맵리듀스에서 맵퍼는 입력 파일 블록에 따라서 파티셔닝됨. 매퍼의 출력은 재파티셔닝해 정렬하고 리듀셔 파이션으로 병합한다. 이 과정의 목적은 모든 데이터(같은 키 값을 가지는 모든 레코드)를 같은 장소로 가져오는 것이다. 맵리듀스 이후 데이터플로 엔진은 필요한 경우가 아니라면 정렬하지 않는다. 그렇지 않다면 대체로 비슷한 방법으로 파티셔닝 한다.

          • 내결함성

          맵리듀스는 빈번히 디스크에 기록한다. 디스크에 기록하면 개별 태스크가 실패하더라도 전체 작업을 재수행하지 않고 쉽게 복구할 수 있다. 하지만 작업이 실패하지 않는 경우 수행 시간이 느려지는 것은 감수해야한다. 데이터플로 엔진은 중간 상태를 최대한 구체화하지 않고 대신 메모리 상태를 유지한다. 이것은 특정 노드가 실패한다면 재계산 양이 늘어난다는 것을 의미한다. 결정적 연산자를 사용하면 재계산이 필요한 데이터의 양을 줄일 수 있다.

           

          6. 참고문헌

          글을 쓰는 것이 중요성을 신입 때는 몰랐습니다. 내 기억력은 충분히 좋을줄 알았고 대화로 모든 것을 해결할 수 있을 것 같았습니다. 하지만 기억은 휘발되고 일의 절차와 내용은 문서로 남겨야만 의미가 있다는 것을 알게 되었고 글쓰기 모임은 이런 저의 갈증을 해결할 수 있는 선택지였습니다. 2023년 봄, 글을 쓰지 않는 사람은 사기꾼 이라는 전 팀장님의 말이 떠올라 글또 신청서를 넣고 8, 9 10기까지 진행했습니다. 이번 글은 지난 3년간 개발자 글쓰기 모임을 하면서 했던 활동을 회고하여 좋았던 점과 나의 삶의 방향이 어떻게 바뀌었는지 한번 작성해보고자 합니다.


          1.  참여자로서의 8기

          • 왜 하필 글쓰기 모임인가?

          왜 하필 개발자글쓰기 모임일까? 저는 본래 공학에 대한 관심이 매우 많습니다. 하드웨어와 소프트웨어의 원리, 물체의 이치 등에 관심이 많은 사람이였고 자연스럽게 데이터 분야를 접하면서 이를 기반으로하는 개발 지식도 흡수하길 좋아했습니다. 또한 글쓰기의 중요성은 서두에 기술한 바있지만, 꽤나 생산적인 활동이고 꾸준히 내 지식을 축적해 나간다는 점에서 마음에 들었습니다. 재밌게도 이 기술 블로그의 출발은 대학교 시절 실험 연구 보고서를 아카이빙 하기 위한 목적으로 시작되었습니다.(실제로 화학생명공학 실험 키워드로 여전히 유입이 존재한다..) 

          • 운영진을 만나다.

          2주마다 글쓰는 것이 대단한 어떤 결과를 만들어준다고는 기대하기 어려울 수 있습니다. 하지만 인생은 적분이라고 작은 변화들이 모여 지금의 나를 크게 변화시켰던 것 같습니다. 특히 IT 와 데이터 과학분야는 변화의 발걸음이 커서 그때 마다 적절한 자료나 레퍼런스를 아카이빙하는 것이 꽤나 도움이 됩니다. 이렇게 조금씩 글을 작성해 나갈때 쯔음 자체 데이터 반상회 모임에서 운영 담당에 지원했습니다. 그렇게 만난 글또의 운영진 정희님께 왜 운영진을 하냐는 질문을 해보았습니다.

          성윤님을 옆에서 본다는 것. 그 이상의 이유가 필요할까요?

          단순하지만 명확했습니다. 사실 성윤님이라는 한 명의 가치 뿐 아니라, 커뮤니티의 가치에 대해서 생각해보았습니다. 데이터 직군은 신생된지 얼마 되지 않아서 어떤 북극성 지표가 필요했으며, 다른사람이 어떻게 하고 있나라는 메타인지를 통해서 스스로 발전하는 변화가 필요한직군입니다. 그런 점에서 옆에서 함께 많이 배울 수 있는 사람과 환경이라고 생각 되었고 곰곰히 생각한 끝에 9기에는 운영진으로 지원하기로 결심하게 되었습니다.
           

          2. 운영진으로서의 9기

          • Activation 크루 지원

          9기 운영진을 준비하기 위해 내가 무엇을 할 수 있는가 뾰족하게 고민했습니다. 뒤돌아보니 제가 했던 다양한 활동들과 장으로서 일했던 경험이 소규모의 동호회를 유지하고 발전시키는데 장점이 있음을 파악했습니다. 그래서 저는 한장짜리 제안서를 함께 제출했습니다.

          사실 이런 아이디어의 근간은 ChatGPT 가 활황이던 23년의 시대적 배경과 함께합니다. 8기 쯤하여 AI 코딩 어시스트 기술이 발전하고 대부분의 IT직군들이 일자리를 잃지 않을까하는 두려움에 다들 만나서 ChatGPT과 일자리에 대한 논의가 활발했습니다. 또한, 이직이나 조직생활 나아가서 밥먹고 사는 문제까지 다들 모여서 고민을을 나누는 시간들이 빈번했습니다. 저는 이런 자유로운 토론과 대화가 글또의 가치를 사용자들에게 주는 경험이라고 생각했고, 이를 좀 더 Global하게 키우는 것이 장기적으로 사용자의 리텐션 향상에 도움이 될 것이라는 가설을 세웠습니다. 실제로 지난 8기에서 오프라인으로 만난 참여자 6명은 글제출을 9.1번으로 평균 이상의 활동량을 보임을 확인했습니다. 이러한 경험을 바탕으로 다음 지원서를 작성했습니다.
           

          9기 운영진 지원 형식

           

          • 글또 하우스 2회 개최

          위 고민을 담아 9기에서 글또하우스를 2회 개최했습니다. 지난 서비스 "클럽하우스" 본따 소수의 스피커와 다수의 청중으로 이루어진 고민상담소로 기획했습니다. 실제로 좋은 반응을 보였고, 1회에는 53명이 지원할 들어올정도로 많은 호응을 받았습니다. 이때 몇가지 회고를 하자면

          • Zoom으로 진행되어 소수의 스피커로 집중되지만 채팅 참여가 활발해서 다수의 의견을 들을 수 있는 장점
          • 1차에는 인원이 많아서 정해진 시간에 쫓기는 느낌이 들어서 아쉬웠고, 참여자가 적은 2차는 여유로웠음
          • 진행자가 상담을 하는 상황이 자주 벌어지는데 모르는 분야에 대해서 코멘트 다는게 어려운 단점
          • 정해진 시간 내 탐색 -> 수렴 -> 결과 등의 진행방식의 필요성
          • 상담자들의 도전지향형/안정지향형이 해법이 섞여있어서 좋았음

          정도로 기억이 납니다. 이런 상담을 하는 와중에 진정 내가 이렇게 사람들에게 조언을 할 수 있는 똑똑한 혹은 대단한 사람인가에 대한 회의가 커졌고, 다음에는 pivot하여 다른 방법으로 기여해보자는 방향을 설정하게 됩니다.
           

          3. 10기의 시작

          • CRM 크루로 pivot과 글또 내 활동들

          Activation 크루는 재밌었고 좋았지만 좀 더 데이터 기반하여서 보길 원했습니다. 그래서 CRM 크루로 일단 이전을 신청했습니다. CRM의 사전적 의미는 고객관계관리이지만, 글또에서는 데이터를 가장 가까이 보고 관심있어하는분들이 많았습니다. 9기에 보냈던, 슬랙기반의 데이터를 활용한 글또 내 활동요약이 핵심 프로젝트 중 하나였고 역시 10기도 추진하게 되었습니다.

          https://geultto.github.io/blog/bokjumoney-message/

           

          글또 10기의 복주머니, 639명의 참여율을 높인 개인화 메시지 개발기

           

          geultto.github.io

           
          특히 10기에는 운영진 역할에 국한하지 않고 적극적으로 스터디와 4채널 활동을 많이 했던 기억이 납니다. 작은 호기심을 가지고 짧은 분석을 해보는 월간 데이터 노트, 테니스모임, 다진마늘 모임, 마인크래프트 서버 운영기, LLM 사이드프로젝트까지 오프라인 모임이 제약되는 상황에서도 다양한 활동을 할 수 있어서 매우 좋았습니다. 각 활동들에 대한 후기는 나중에 한번 정리를... 특히 월간 데이터 노트는 본격적으로 시스템화 해서 월별 루틴으로 올릴 예정이라서 나름 기대하고 있습니다.

          • 지금까지 작성한 글 정리 - 프로젝트
          번호 글제목 분류
          1 서버리스 환경 구상하기 AWS Lambda, RDS, ec2 프로젝트
          2 사이드프로젝트  중간 회고 & 서버 제공 서비스 비교하기(S3,Blob,Firebase) 프로젝트
          3 데이터 파이프라인 자동화 구축기 with 농수산물데이터, GCP, crontab 프로젝트
          4 제3회 기상-AI 부스트캠프 해커톤 후기 프로젝트
          5 시계열 Prophet 모형과 하이퍼파라미터 뜯어보기 with 주가데이터 프로젝트
          6 Google bigquery에 데이터를 적재하고 태블로로 데이터 가져오기 프로젝트
          7 연간 가계부 관리하기 (feat. 뱅크샐러드) 프로젝트
          8 슬랙 봇으로 커뮤너티 활성화하기(with CRM 메시지, 마인크래프트) 프로젝트

          처음에는 책과 같은 레퍼런스에서 얻을 수 있는 프로젝트가 다수인데 갈수록 내 호기심과 아이디어로 구현하가는 과정이 흥미롭습니다. 특히 8번 슬랙 봇 커뮤니티 활성하기는 순수하게 호기심과 재미로 시작한 것인데, 글또 내 큐레이션 선정도 되고 공감도 가장 높은 글이라서 작성하던 시기에도 후에도 보람찼던 경험이 있습니다. 역시 사람은 재밌는거 해야해?

          • 지금까지 작성한 글 정리 - 스터디 활동
          번호 글제목 분류
          1 분석알고리즘(머신러닝, 딥러닝) 간단 정리해보기 1편 스터디
          2 추천시스템 이론 정리하기 스터디
          3 추천시스템 실습 - 아이템기반 협업 필터링 스터디
          4 신뢰구간의 2가지 계산방법: t분포와 부트스트래핑 스터디
          5 기호주의와 연결주의로 알아보는 AI 발전 스터디
          6 LLM 관련 모듈 동향 살펴보기- OPEN AI, Langchain , Pandas AI 스터디
          7 왜 프로덕트 팀은 A/B테스트를 사랑할까? (feat 인과추론) 스터디
          8 DDIA Chapter 2: 데이터 모델과 질의 언어 스터디
          9 DDIA Chapter 3: 저장소와 검색 스터디
          10 DDIA Chapter 6: 파티셔닝 스터디

          사실 회사다닐 때는 책이나 스터디를 등한시 해왔습니다만, 갈수록 AI툴들이 수단을 고도화하고 기존의 툴을 대체하는 시대에 결국 살아남는 가치는 그 원리를 파악하고 잘 이용한는 방법이라는 결론에 도달하게 되었습니다. 에이 무슨 6년차에 스터디야 라고 생각했던 저는 다시 활자를 읽기 시작했고, 기존에 DA,DS에만 국한하던 스터디에서 CS까지 확장하게 되었습니다. 특히 데이터중심어플리케이션 설계(DDIA)는 백엔드 개발자들에게는 필독서로 추앙받는데, 함께 스터디하면서 전공을 다시듣는 그런 재미난 경험이었습니다. 

          • 지금까지 작성한 글 정리 - 일상생각, idea
          번호 글제목 분류
          1 나의 올해의 컨셉은 분발자로 정했다 idea
          2 데이터분석가의 끝은 어디일까? idea
          3 네? 데분 멘토링왔는데 알고리즘 2달 알려주라고요? idea
          4 글또 8기 활동과 개인 상반기를 정리하며 idea
          5 프리랜서로 산다는 것 2023 idea
          6 당신은 절대 글을 잘 쓸 수 없다 idea
          7 알고리즘은 데이터 분석가에게 필요할까? idea
          8 Analytics Engineer 직무 뜯어보기 idea
          9 데이터 분석을 위한 준비하고 배울 것들 idea
          10 글또 10기를 시작하며 다짐 글 idea
          11 일의 기쁨과 슬픔 idea
          12 치앙마이 워케이션 후기(업무장소, 음식, 휴식 추천) idea
          13 1인 사무실 한 달 후기 (ft 범계비욘드워크) idea

          글또의 대부분이 그러하든 나의 역할과 방향성에 고민을 하는 글들이 다수 발견됩니다. ㅋㅋ 생각이 많은 1인... 그런데 활자로 적는것 만큼 또 정리가 잘되는게 없습니다. 초반에는 아 뭐해먹고 살지 데이터분석은 뭘까 라는 등등에 대한 방향성으로 글을 써왔는데 점차 나의 목표가 뚜렷해지니 주제가 더 명확해지는 흐름이 보이네요. 이런 회고도 참 재밌습니다!
           

          4. 갈무리 - 정량적 성과보기

          • 블로그 성과 보기

          직무에 대한 고민 글과 프로젝트, 스터디를 하면서 올라간 내면의 질적인 향상도 있지만 명시적으로 오른 지표는 방문자 수와 조회 수입니다. 월 천명, 조회수 2천건의 블로그로 성장했습니다. 데이터 분야 입문할 때만해도 Ratsgo's Blog어쩐지 오늘은 같은 데이터 블로그 글들로 공부를 많이했는데 어쩌면 저도 그런 블로그가 될 수 있을까 싶습니다 ㅋ_ㅋ

          두번째로는 노출과 CTR입니다. 점점 글을 작성하면서 알게모르게 블로그의 랭크가 높아졌고, 메타 태그 설정, 구글 서치콘솔까지 연결하니 가장 인기 있는 글은 13%의 CTR를 달성하기도 했습니다.

          블로그 전체의 CTR

          물론 일반 웹서비스와 다르게, 기술블로그 특성상 적절한 주제라고 생각하면 클릭으로 전환하는 비용이 크지 않기 때문에 큰 값은 아니지만 그래도 꽤나 전환율이 좋은 글들이 많습니다. 특히 통계책으로 검색하면  일반 서점 사이트와 교수님이 쓰신 글! 까지 제치고 상위노출하는 쾌거를 이루어서 기분이 좋았네요.

          (좌) 상단노출되는 내 글 (우) 인기목록 Top10

          글또에서 양질의 컨텐츠를 얻으면서 배웠던 지식과 커피챗을 하면서 얻었던 아이디어등이 결합된 결과라고 생각됩니다. 물론 글또 뿐 아니라 다양한 글을 완성도에 집착하지 않고 일단 하자! 라는 결심으로 차곡히 쌓아온 결실이기도 하구요. 이런 환경을 만들어주신 성윤님께 무한 감사!

          • 그래서 앞으로는?

          아마 글또가 마무리되니 압박에 시달려서 2주마다 글을 쓰진 않을 수도 있을 것 같습니다. 하지만 글을 쓰는 것에 대한 가치는 더 커졌고 오픈소스처럼 나의 지식을 나누어주는 것에 대한 기쁨도 동반되고 있습니다. 문득 글을 정리하다가 10기의 목표를 "히언님의 친절한 데이터사이언티스트 되기 강좌" 과 같이 동영상보다 좋은 글을 남기자 였는데 이 목표에 지속해서 다가가려 합니다.  앞으로는 방통대를 입학해서 공부하고 추후에 대학원 고려도 해볼 수 있을 것 같은데 그때마다 떠오르는 생각과 기술들을 한번씩 적어보겠습니다. 그럼 20000

          앞으로 또 좋은 수영장을 찾아서 헤엄치고 발전하는 나를 위해서 한잔 🍻해 

          단일 시스템에서 다중시스템으로 확장하는 것은 단순히 공수의 N배가 아니라는 것을 느끼는 단원. 초반에는 네트워크장애에 대한 기술을 서술하나가는데 그 이후는 그만 정신이 혼미해지는 단원입니다. 그야말로 골칫거리군요. 후반은 읽다가 잠시 정신을 잃어서 이만.. 




          신규개념

          개념 설명
          타임아웃(Timeout) 서버로 요청을 보냈지만 일정 시간동안 답변을 받지 못할 때 발생시키는 오류
          TCP 전송 제어 프로토콜(Transmission Control Protocol), 애플리케이션 사이에서 안전하게 데이터를 통신하는 규약. IP(Internet Protocol)와 엮어서 TCP/IP라고 불리기도 하며, 통신속도를 높이면서도 안전성, 신뢰성을 보장할 수 있는 규약
          UDP 사용자 데이터그램 프로토콜 (Universial Datagram Protocol). TCP처럼 네트워크  프로토콜이지만 비연결형 프로토콜이라느 차이점.
          이더넷 다수의 시스템이 랜선 및 통신포트에 연결되어 통신이 가능한 네트워크 구조(인터넷 ㄴㄴ)
          패킷(Packet) 네트워크에서 주고받을 때 사용되는 데이터 조각들
          동기식 통신 Synchronous 동시에 일어남. Request를 보내면 얼마나 시간이 걸리든 그 자리에서 Resonse를 받음
          비동기식 통신 Asynchronous, Request 보내도 Response를 언제 받아도 상관 없다. 할일이 끝난 후 Callback을 통해 완료를 알림

           

          1. 비공유 시스템의 오류 관리 방법: 타임 아웃

           (New) 분산 시스템은 비공유시스템. 즉 네트워크로 연결된 다수의 장비이다. 네트워크는 장비들이 통신하는 유일한 수단이라는 특징을 가지고 있다. 인터넷과 이더넷은 비동기 패킷 네트워크(asynchronouse packet network)다. 이런 종류의 네트워크에서는 다른 노드로 메시지(패킷)을 보낼 수 있지만 네트워크는 메시지가 언제 도착할지 혹은 머세지가 도착하기는 할지 보장하지 않는다. 여기서 패킷이란 네트워크에서 주고받는 데이터의 조각이다. 

          약간 mRNA 닮은거는 기분탓인가?

          패킷의 기본 구조. 익숙한 단어가 보인다.

          요청을 보내고 응답을 기다릴 때 여러가지 오류의 상황이 발생할 수 있다.

          1. 요청이 손실되었을 수 있다.(누군가 네트워크 케이블을 뽑았을 수도)
          2. 요청이 큐에서 대기하다가 나중에 전송될 수 있다.(네트워크나 수신자에 과부화가 걸렸을 수 도 있다.)
          3. 원격 노드에 장애가 생겼을 수 있다.(죽었거나 전원이 나갔을 수 있다.)
          4. 원격 노드가 일시적으로 응답하기를 멈췄지만 나중에는 다시 응답하기 시작할 수 있다.
          5. 원격 노드가 요청을 처리했지만 응답이 네트워크에서 손실됐을 수 있다.
          6. 원격 노드가 요청을 처리했지만 응답이 지연되다가 나중에 전솔될 수 있다.(네트워크가 요청을 보낸 장비가 과부하가 걸렸을 수 있다.)

          (a) 요청의 손실 1번 사례, (b) 원격 노드 다운 3번 사례, (c) 응답이 손실 5번 사례

          이 문맥을 읽으면서 외계 탐사행성을 위해 발사된 보이저1호의 상황이 상상이 되었다. 응답할 것이라고 기대하고 보내는 전파가 돌아오지 않는 상황이 네트워크 오류와 비슷한 느낌이랄까?
          출처: https://www.thedailypost.kr/news/articleView.html?idxno=104231

          우리의 요청을 오기까지 그럼 무제한 대기해야할까? 그렇지 않다. 발송이 잘못되었는지, 처리는 되었는지 어느 부분에서 장애가 일어난지 모르니 타임아웃을 이용해서 관리한다고 책은 기술한다. 토스페이먼츠 API Read Timeout은 30-60초로 설정한다고 한다.  이는 경험적인 수치인듯?

          이런 문제를 다루는 흔한 방법은 타임아웃이다. 얼마간의 시간이 지나면 응답 대기를 멈추고 응답이 도착하지 않는다고(장애라고) 가정한다. 그렇다면 타임아웃은 얼마나 길어야할까? 타임아웃이 길면 노드가 죽었다고 선언될 때까지 기다리는 시간이 길어진다. 그 시간동안 사용자들은 기다리거나 오류 메시지를 보아야한다. 반면 타임아웃이 짧으면 결함을 빨리 발견하지만 노드가 일시적으로 느려졌을 뿐 결함이 아닌데도 죽었다고 선언할 확률이 높아진다. (뭐예요 나 아직 살아있어요) 단순히 오해에 그치는게 아니라 노드가 죽었다고 판단되면 그 역할이 다른 노드로 전달되는 내결함성으로 인하여 연쇄장애의 원인이 될 수 있다. 

          이때 인터넷 전화(VoIP)는 지연시간이 민감하기 때문에 TCP보다는 UDP를 선택하는 사례에 대해서 기술한다. UDP는 흐름제어를 따로 하지않고 속도가 빠르며 잃어버린 패킷에 대한 부분을 사람 계층에서 재시도 한다는 표현이 재밌었다. 대표적인 VoIP 서비스인 디스코드가 UDP 통신을 선택하고 있다.

           

          2. 패킷이 유실되지 않는 견고한 네트워크는 어때?

          (New)  네트워크에서 패킷이 손실될 수 있다는 점이 단점으로 명확해지는데 그럼 견고한 네트워크는 어디 없을까? 이를 고정회선 네트워크의 예시를 가져온다. 전화 네트워크는 극단적인 신뢰성을 가져서 음성이 지연되거나 통화가 유실되는 매우 드물다. (고양이가 전화선을 뜯지 않는다면 말이지)

          이러한 견고함을 유지할 수 있는 이유는 통화를 하는 두 명사이에 있는 전체 보장된 대역폭이 할당된다. 언제까지? 통화가 명시적으로 끝날때까지. 이런 네트워크는 동기식으로 큐 대기가 없으므로 종단 지연시간의 최대치가 고정되어 있다. (제한 있는 지연) 따라서 특정 시간의 간격이 지나도 응답이 오지 않는다면 명백한 통신실패임이 확실하다. 

          반면 TCP연결은 가용한 네트워크 폭을 변화하면서 사용한다. 그러니까 연결이 없다면 어떤 대역폭도 사용하지 않는다. 연결이 있다면 기약없는 지연이 발생할 수 없는 구조이다. 그저 빨리 완료되길 기도하는 메타이다. 

          3. 출처

          (Amendment)

          사이드프로젝트 혹은 사내 작은 프로젝트를 하기 위한 클라우드 서비스별 크레딧을 정리해보았습니다. 하기 내용은 정확하지 않을 수 있으며, 해당 서비스의 공식홈페이지를 참고하시는 것을 추천드립니다. 


          문서버전: 2025-03-24 최초 등록

          일반 사용자

          • NCP 10만원, 3개월
          접근성이 쉬움 + Naver API 사용하기도 좋음
          • GCP 300 달러 크레딧, 3개월
          GCP는 Computing resource 하나 조건무관 제공 (Disk 30GiB)
          • AWS 프리티어 12개월 무료(쿠폰적용시 100달러)
          GCP와 달리 1년 지나면 무료 해지인듯!
          • Azure 200 달러
          후발 주자여서 그런지 지원 정책이 올라오고 있는 중

           

          사업자/스타트업

          개인의견:

          사업자 & 스타트업 신청은 굳이라고 생각하실 수도 있지만, 개인사업자의 경우 국내 홈택스에 쉽게 사업자를 낼 수 있습니다. 다만 청년창업소득세면제와 같은 쿠폰을 써버리는게 아깝다고 사업자를 내는걸 꺼려할 수도 있지만 사실 뭘 안하는것보다 일단 하는게 좋다는 생각입니다.  법인사업자의 경우 절차와 신고 등이 부가적인 것이기 때문에 굳이 추천 드리진 않습니다. 

           

           

          이번 글에서는 복제에 이어 데이터 파티셔닝 방법과 이를 인덱싱하기 위한 전략을 알아봅니다. 특히 데이터가 커지는 경우에는 필수적으로 파티셔닝이 필요하며, 이를 나누기 위한 키-범위 파티셔닝과 해쉬 파티셔닝에 대해서 알아보고, 쓰기와 읽기 성능을 고려한 로컬과 글로벌 이차 인덱스방법에 대해서 기술합니다. 또한 개발이 끝이 아니듯 운영에서 발생할 수 있는 리밸런싱 전략에 대해서 알아봅니다. 전반적으로 대용량 데이터베이스를 설계자 입장에서 고려하고 고민할 것들이 많이 도출 되는 좋은 단원이였습니다!


           
           

          신규개념

          개념설명
          파티셔닝(Partitioning)성능, 확장성, 유지성을 목적으로 논리적인 데이터를 다수의 엔터티로 분할하는 행위
          복제(Replication)동일한 데이터를 여러 노드에 저장하여 장애 복구를 대비하는 방법
          노드(Node)스토리지 단일 서비스 혹은 네트워크 일부를 구성하는 서버
          핫스팟(Hospot)특정 파티션에 부하가 집중되는 현상
          해시 파티셔닝데이터를 균등하기 분산시키기 위해서 해시 함수를 사용
          키 범위 파티셔닝연속된 키 범위에 따라 데이터를 분배
          이차 인덱스 파티셔닝검색을 위한 인덱스를 파티셔닝하는 방식
          리밸런싱(Rebalancing)노드 추가/삭제 시 데이터를 재분배하는 과정
          라우팅(Routing)네트워크에서 경로를 선택하는 프로세스

           

          1. 파티셔닝의 정의

          • 파티셔닝이란 큰 데이터베이스를 여러 개의 작은 데이터 베이스로 나누는 기법
          • 네트워크 분할(Network Partition)과는 다르며 주 목적은 확장성(Scalability) 확보
          • 대용량 데이터를 효율적으로 관리하고 고 성능 쿼리 처리를 가능하게 한다.
          • 보통 샤딩(Sharding)이라는 용어와 혼동되며 데이터 베이스마다 다르게 불

          (Amendment) 사실 파티셔닝이라는 개념은 윈도우 설치할 때 하나의 디스크에 "파티셔닝"하여 C / D드라이브로 만들 때 마주할 수 있다.

          이처럼 파티셔닝은 데이터베이스를 논리적으로나 물리적으로 분할하여 관리하는 기술로, 데이터 베이스의 성능응 향상시키고 관리를 용이하게 합니다. 파티셔닝을 통해 데이터 베이스 조회 속도를 높이고 백업 및 복구시간을 단축할 수 있기 때문입니다. 

          출처: F-lab 데이터 파티셔닝 전략

           

          2. 파티셔닝과 복제

          • 대부분의 시스템에서 파티셔닝과 복제가 함께 사용됨
          • 파티셔닝: 데이터를 여러 노드에 나누어서 저장
          • 복제: 동일한 데이터를 여러 노드에 저장하여 장애 대비

          (Amendment) 파티셔닝과 복제가 주로 이루어지는 이유는 각 목적을 살펴보면 된다. 데이터의 크기가 커질수록 한 노드(서버)에 저장할 수 없게 된다. 이를 여러 노드에 나워서 수평 확장을 노려볼 수 있다. 파티션 단위로 데이터 읽기/쓰기 작업을 하여 성능 향상을 하는 한편, 특정 파티션만 검색하도록 최적화하여 쿼리 성능을 향상시킬 수 있다. 이로 인한 단점은 한 노드에 한 개만 저장되므로 노드 장애 시 데이터 유실 위험이 존재한다. 
          반면, 복제는 고가용성(High Availability)와 내결함성(Fault Tolerance) 확보가 목적으로 동일한 데이터를 여러 노드에 복제하여 데이터 손실을 방지하며, 특정 노드가 장애가 나더라도 복제본의 데이터를 제공을 가능하게 한다. 읽기 부하(Read Load)를 여러 복제본에 분산하여 읽기 성능 향상이 가능하며 특히 리더 -팔로워 모델이 유용하다. 단점으론느 복제된 데이터를 주기적으로 동기화해야하므로 쓰기 성능 저하 가능하며 데이터 일관성의 유지가 필요하다. 따라서, 파티셔닝. 을 통해 확장성을 확보하는 한편 복제를 통해 가용성을 보장하는 것이 대규모 데이터 베이스의 운영의 핵심이라고 할 수 있다.
           

          3. 파티셔닝 전략

          1. 키 - 범위 파티셔닝(Key Range Partitioning)

          • 연속적인 키 범위를 기준으로 데이터를 분할
          • 장점: 키 순서가 유지되므로 범위 쿼리에 적합
          • 단점: 특정 키 범위에 쿼리가 집중되면 핫 스팟 발생 가능

          쉽게 말하면 백과사전에서 특정 단어를 찾는 과정과 비슷하며, Hbase, RethinkDB, MongDB에 쓰임. 핫스팟의 문제는 센서 데이터의 예를 들면 편한데, 만약 센서 데이터의 파티션이 날짜로 지정해되면 특정 파티션은 과부화가 걸리는 반면 다른 파티션은 유후상태(Idle)이 된다. 이를 보완하기 위해서 센서이름 + 타임스탬프 형태의 키를 구성하면 각 센서별로 파티션에 나뉘고 이후 시간 순서대로 저장되므로 쓰기 부하가 고루 분산될 수 있는 장점이 있다.

           
          2. 해시 파티셔닝(Hash Partitioning)

          • 키를 해시 함수에 통과시켜 균등하게 분산
          • 장점: 데이터가 균등하게 분배되어 핫스팟 발생 가능성 적음
          • 단점: 키 정렬이 깨져서 범위 쿼리의 어려움

          skwed된 데이터와 hotspot의 문제 때문에 고안된 데이터 모델. 파티션 간의 고른 데이터 분배를 노릴 수 있음.
           

          4. 파티셔닝과 이차 인덱스

          이차 인덱스는 데이터 베이스에서 기본 키 이외에 다른 컬럼을 기준으로 빠르게 데이터를 검색하기 위한 인덱스이다. 파티셔닝이 적용된 데이터베이스에서는 기본 키를 기준으로 데이터를 찾는 것은 쉽지만, 기본 키가 아닌 다른 컬럼으로 검색할 때 문제가 발생할 수 있으며 이를 해결하기 위해 이차 인덱스가 등장한다. 
          (Amendement)

          CREATE TABLE users (
              user_id INT PRIMARY KEY,
              name VARCHAR(100),
              email VARCHAR(255),
              country VARCHAR(50)
          );

          위 경우 user_id가 기본키이므로 user_id = 1234 는 빠르게 검색 가능한 반면, where country = 'USA' 같은 쿼리는 효율적으로 실행되지 않으므로, 다음 이차 인덱스를 생성하여 검색 성능 향상을 노린다.

          CREATE INDEX idx_country ON users(country);

          이런 이차 인덱스를 저장함에 있어서 2가지 방식을 선택할 수 있다.
          1. 문서 기반 인덱스( Document-partitioned indexes, Local Index)

          각 index를 각 파티션에 저장

           

          • 이차 인덱스를 해당 데이터가 있는 파티션 내부에 저장

          이차 인덱스가 기본키 및 값과 동일한 파티션에 저장됨. 즉 데이터 쓰기를 할때는 해당 파티션 하나만 업데이트하면 되므로 쓰기 성능이 우수하다. 반면 이차 인덱스를 기반으로 조회할 경우 모든 파티션을 검색(Scatter)하고 결과를 모아야(Gather)해야하므로 성능 저하 가능성이 있다.
          (Amendment) 예를 들어 country = 'USA" 데이터가 각 파티션에 분산되어 있다면 각 파티션 마다 country 인덱스가 생성 됨. 데이터를 추가하거나 수정할 때 해당하는 파티션만 업데이트하면 되므로 쓰기 성능이 좋으나, 데이터 검색할 때는 쿼리 속도가 느려질 수 있다.
          2. 용어 기반 인덱스(Term-partitioned indexes, Global Index)

          Gloabl 인덱스를 여러 파티션에 나눠 저장
          • 이차 인덱스를 별도의 파티션으로 저장

          이 방식에서는 이차 인덱스가 기본키와 독립적으로 분할되어 저장되어 이차 인덱스는 특정 필드 기준으로 별도로 파티셔닝된다. 또한 하나의 인덱스 방식이 여러 개의 파티션에 있는 데이터를 포함할 수 있다. 쓰기 시에는 여러개의 파티션을 업데이트 해야하므로 쓰기 성능이 저하될 수 있으나 읽기 시에는 특정 값을 포함하는 단일 파티션에서만 데이터를 가져올 수 있으므로 조회 성능이 향상된다. 
          (Amendement) where country = 'USA' 검색 시 USA 관련 데이터가 저장된 단일 파티션만 조회하면 되므로 성능이 빠르고 읽기 성능이 최적화, 데이터를 추가할 때, 이차 인덱스가 여러 파티션에 걸쳐 존재하므로 여러 개의 파티션을 업데이트 해야하여 쓰기 성능이 저하될 수 있다.

           문서기반(로컬 인덱스)용어기반(글로벌 인덱스)
          쓰기성능빠름느림
          검색성능느림빠름

           

          5. 리밸런싱

          개발보다 중요한 것은 운영이라고 했던가 필수적으로 데이터베이스를 운영하다보면 특정 노드에 데이터가 집중되거나 부하가 편향되는 불균형(imbalance)가 일어날 수 있다. 따라서 리밸런싱을 통해서 데이터와 쿼리 부하를 공정하게 분배해야하는 요구사항이 반드시 생겨난다. 
          또한 리밸런싱을 할 떄 지켜야할 원칙은 ① 리밸런싱 후 노드간의 데이터와 부하가 균등해야하며 ② 리밸런싱 과정에서 읽기와 쓰기에 대한 수행은 계속되어야 하며 ③ 리밸런싱의 속도를 빠르고 네트워크 및 디스크 I/O 부하를 최소화하기 위하여 필요한 데이터 이상의 데이터가 노드 간 이동해서는 안된다. 등 이다.
          1. 고정된 파티셔닝(Fixed Partitioning)

          • 데이터베이스를 고정된 파티션으로 나누는 기법. 노드가 추가되거나 삭제되어도 파티션의 갯수는 동일

          가장 고전적인 방법으로 고정된 파티션의 갯수를 유지하는 방법이다. 하지만 처음에 너무 적은 파티션을 설정하면 확장시 부담이 커지고 많으면 관리 오버헤드가 커진다. 시간이 지나면 특정 파티션에 데이터가 몰려 일부 파티션에만 과부화가 생길 수 있으며, 초기 설정된 파티션의 갯수가 적절한지 어려운 단점이 있다. 
          따라서 데이터 양이 일정하고 급격한 변화가 없는 경우, 시스템 운영이 단순해야하는 정형데이터의 경우나 리밸런싱 속도가 중요한 경우 고정된 파티셔닝 방법을 고려할 수 있다.
          2. 동적 파티셔닝(Dynamic Partitioning)
          동적 파티셔닝은 데이터가 증가하면 자동으로 파티션을 2개로 분할하고 반대로 삭제하면 통합하는 방식으로 관리하는 기법이다. 하지만 데이터가 작은 경우 모든 쓰기의 연산이 하나의 노드에서 처리되며 나머지 노드는 유후(Idle)상태가 된다. 

          6. 라우팅 요청(Request Routing)

          리밸런싱이 필요하고 멋지지만 비싼 작업인건 확인해보았다. 그럼 이렇게 수동/자동 혹은 고정/동적인 리밸런싱 과정 속에서 데이터 읽기/쓰기에 대한 요청이 들어왔을 때 누가 중재해줄 것인가에 대한 질문이 자동으로 생긴다. 이를 Service Discovery 라고 한다. 

          foo 라는 자료를 찾을 때 시도할 수 있는 방법 3가지

          아마 합리적인 방법은 각 파티션이 가지고 있는 key range에 대한 정보를 저장하는 것이고 이를 ZooKeeper 라고 한다.

           

          7. 마무리

          사실 데이터에 쿼리를 날려만 봤지 대용량 데이터를 저장하는 설계쪽에서는 생각해본적이 거의 없다. 데이터의 무결성과 정합성을 지키기위한 방법부터 초기 데이터베이스에서 확장가능하면서 성능을 최적화하기 위한 파티셔닝 그리고 그 파티셔닝을 운영단계에서 리밸런싱 하는 과정 등을 한번에 볼 수 있는 너무나 좋은 단원이라고 생각되었다. 물론 SaaS 서비스에서는 이를 서비스에 맡기고 인간의 개입은 최소화하겠지만 이런 이론들을 알고 공부할 수 있는 이 책의 전개 흐름과 다이어그램이 너무 좋았다. 또또 공부해보고 싶은 마음이 든다. 
          이 단원을 통해 앞으로 알았으면 좋겠는 것들은 다음과 같다.

          • 대용량 데이터에서 파티셔닝의 중요성과 관리 서비스
          • SQL 윈도우 함수에서 파티셔닝을 수행할 때 쿼리 최적화 과정

          8. 참고자료

           

          솔직히 말이죠 이런 책을 읽는다는게 쉽지만은 않습니다만, 특히 이번장에서는 데이베이스의 저장구조와 검색에 대한 테크니컬한 내용이 많이 들어가서 중간에 도망갈뻔 했습니다. 그런데 참고 읽다보니 OLTP와 OLAP에 대한 구조가 너무나도 비교가 잘되었고 최근(?) 유행하게 되었는 칼럼 데이터베이스도 눈 여겨볼 수 있는 좋은 단원이였습니다. 얼마나 좋았나면 분석하는 분들에게 일부 문단을 뜯어서 읽어주고 싶은 느낌이였어요. 필자는 2장에서는 어플리케이션 개발자가 데이터 베이스에 데이터를 제공하는 형식을 설명한다면 3장은 데이터베이스 관점에서 데이터를 저장하는 방법과 요청했을 때 다시 찾을 수 있는 방법에 대해서 안내하고 있습니다. 한번 가보시죠~

           


          신규개념

          개념 설명
          로그(Log) 컴퓨터 시스템과 네트워크에서 발생하는 활동과 사건에 대한 기록
          컴팩션(Compaction) 로그에서 중복된 키를 버리고 각 키의 최신 갱신 값만 유지하는 것을 의미한다.
          오버헤드(overhead) 어떤 처리를 하기위해 들어가는 간접적인 처리시간
          오프셋(offset) 두번째 주소를 만들기 위해 기준이 되는 주소에 더해진 값(like 상대주소)
          병합정렬(mergesort) 하나의 리스트를 균등한 크기로 분할을 하고, 분할된 부분을 정렬한뒤 합치면서 전체 정렬하는 방법
          SS테이블(Sorted String Table) 세그먼트 파일 형식에서 키-값 쌍을 가진 테이블 형태의 데이터 구조
          LSM 트리 Log Structed merge Tree, 정렬된 파일 병함과 컴팩션 원리를 기반으로 하는 저장소 엔진
          OLTP Online Transaction Processing,인터넷을 통해 많은 사람들이 동시에 데이터베이스를 처리하는 시스템
          OLAP Online Analytical Processing, 온라인상에서 데이터를 분석 처리하는 기술
          데이터 웨어하우스 Data Warehouse, 다양한 데이터 소스에서 데이터를 추출, 변환, 요약하여 사용자에게 제공할 수 있는 데이터베이스의 집합체

           

          1. 해시색인, B-tree

          (New) 데이터베이스를 가장 쉽게 만드는 방법은 키-값 저장소의 형태로 로그(log)처럼 추가 전용(append only)한 특성을 사용할 수 있다. 하지만 레코드가 많을수록 검색의 비용은 O(n)이기 때문에 성능 최적화 측면에서 바람직하진 않다. 따라서 색인(index)를 이용할 수 있다. 색인은 기본 데이터의 파생된 추가적인 구조이기 때문에, 내용에는 영향을 미치지 않지만 단지 질의 성능에만 영향을 준다.

          갑자기 생각난건데, 내가 입사한 회사에서는 의료처방기록을 나라에서 공급받아 분석했었는데, 인덱스가 걸려있는 컬럼이 환자 식별자에만 걸려있냐고 물어보니까, 그냥 모든 컬럼을 걸었다고 답변받았던 기억이난다. 이유는 데이터가 실시간도 아니고 행이 몇 억개밖에 안되어서 그렇다고... 

          key-value 저장소는 보통 해시 맵(has map)으로 구현하며, 색인 전략은 키를 데이터 파일의 바이트 오프셋에 매핑해 인메모리 해시 맵을 유지하는 전략이다.

          하지만 파일에 항상 추가만 한다면 결국 디스크 공간이 부족해진다. 이는 특정 크기의 세그멘트(segment)로 나누는 방식이 좋은 해결책이다. 이를 컴팩션(compaction)을 수행하여 해결할 수 있다. 컴팩션은 로그에서 중복된 키를 버리고 각 키의 최신 갱신 값만 유지하는 것을 의미한다.

          고양이(mew) 동영상의 조회수가 1082 최신값으로 업데이트

          내가 운영하는 마인크래프트 서버도 latest.log파일에 저장이 되고 일정 수준이 넘어가면 2025-02-21-(1), (2)와 같이 Segemet가 저장되는데 이것도 일부의 compaction 이라 할 수 있을까? 생각했지만 정보를 덮어쓰거나 하지 않으므로 거리가 있다고 생각했다.

           

          (궁금한 점) Youtube 도 Compaction을 이용해 조회수를 업데이트 할까? 근데 그럼 1시간 전의 조회수나 24시간의 조회수는 나중 시점에 알 수 없는걸까? 갓 구글이 이런 데이터가 없을리 없을 것 같고...그냥 log별로 따로 저장할까? 책의 예시를 Youtube에 저장하려니 조금 결이 안맞는 것 같은 생각이 든다.

           

          2. SS테이블과 LSM트리

          (New) 해시테이블은 메모리에 저장해야하므로 키가 너무 많으면 문제가된다. 무작위 접근이 많이 필요하고 가득찼을 때 확장비용이 비싸기 때문. 또한 해시 테이블은 범위 질의(range query)에 효율적이지 않다. 키를 정렬하여 이를 보완한 것이 정렬된 문자 테이블(Sorted String Table)이다. 세그멘트 병합은 병합정렬과 비슷하다(다음 그림)

           (Difficulty) 그리고 이 SS테이블과 memtable(쓰기가 들어오면 인메모리 균형트리 데이터구조?)의 동작의 단점으로 memtable의 가장 최신 쓰기가 손실되어 분리된 로그를 디스크 상에 유지하기 위하여 LSM(log-structed Merge Tree)가 나왔다는데 memtable에서 이해가 멈췄다....

           

          3. 사실 가장 많이 쓰이는 건 B트리

          70년대부터 사용된 가장 보편적인 색인 구조로 워낙 유명해서 패스 ~

          (Amentment) https://yeongjaekong.tistory.com/38

           

          [자료구조] B-tree란? / B-tree의 연산 / B*tree, B+tree / B+tree 구현

          B-tree란? B-tree는 Self-balanced Tree 중 가장 유명한 자료구조입니다. Balanced-tree를 의미하며, 이진트리를 확장해 하나의 노드가 가질 수 있는 자식 노드의 최대 숫자가 2보다 큰 트리 구조입니다. 즉,

          yeongjaekong.tistory.com

           

          4. 트랜잭션 처리와 분석

          특성 트랜잭션 처리 시스템(OLTP) 분석 시스템(OLAP)
          주요 읽기 패턴 질의당 적은 수의 레코드, 키 기준으로 가져옴 많은 레코드에 대한 집계
          주요 쓰기 패턴 임의 접근, 사용자 입력을 낮은 지연 시간으로 기록 대규모 불러오기(bulk import, ETL) 또는 이벤트 스트림
          주요 사용처 웹 어플리케이션을 통한 최종 사용자/소비자 의사결정을 지원을 위한 분석가
          데이터 표현 데이터의 최신 상태(현재 시점) 시간이 지나며 일어난 이벤트 이력
          데이터셋 크기 기가바이트에서 테라바이트 테라바이트에서 페타바이트


          데이터 웨어하우스(Data Warehouse)
          는 분석가들이 OLTP 작업에 영향을 주지 않고 마음껏 질의할 수 있는 개별 데이터베이스이며, OLTP 시스템에 있는 데이터의 읽기 전용 복사본이다. 

          솔직히 이 단원을 보기 전까지는 웹어플리케이션에서 발생하는 데이터에 대한 경험이 다소 적었기 때문에 OLTP와 OLAP에 대한 특별한 구분점을 찾지 못하였다. 하지만 OLTP 시스템 자체가 비즈니스 데이터 처리(Ex 전자 상거래)를 위해 즉 서비스 운영을 위해 만들어진 본래의 목적을 가지고 있다면, 분석을 그 자체에서 수행한다는 것은 매우 위험한 일임을 이제야 깨닫게 되었다. 따라서 이를 "복제"하여 데이터의 복사본을 가지고 분석을 수행해야함을 이해할 수 있었고, 또한 분석이라는 쿼리 자체의 특성 예를 들면 일부 컬럼을 가져오고 대량의 행을 가져오는 등의 특성에 필요한 데이터베이스의 설계가 필요한 점을 이해할 수 있었다.

          (Amendment) 스타스키마(star schema) 다음 databrick 블로그의 인용문구로 대체!

          스타 스키마는 데이터베이스에서 데이터를 정리하는 데 사용하는 다차원적 데이터 모델로, 쉽게 이해하고 분석할 수 있습니다. 스타 스키마는 데이터 웨어하우스, 데이터베이스, 데이터 마트 등의 툴에 적용할 수 있습니다. 스타 스키마는 대규모 데이터 세트에 대한 쿼리를 최적화하도록 설계되었습니다.
          Ralph Kimball이 1990년대에 도입한 스타 스키마는 반복적 비즈니스 정의의 복제를 줄여 데이터 웨어하우스에서 데이터를 빠르게 집계하고 필터링하도록 지원하므로 데이터 저장, 내역 관리, 데이터 업데이트에 효율적입니다.
          https://www.databricks.com/kr/glossary/star-schema

          5.  컬럼지향 데이터베이스

          (New) 대부분의 OLTP는 로우 지향의 방식으로 데이터를 배치. 문서 데이터와비비슷하며, 컬럼 지향 저장소는 칼럼 별로 모든 값을 함께 저장함. 또한 압축에 능함!!

          특히, 나는 Pandas라는 모듈을 자주 사용하곤 하는데 이 문단에서 Pandas의 자료형이 생각났다. 해당 모듈에서는 DataFame이라는 행열 구조의 matrix와 Series라는 열구조 2가지의 데이터 자료형을 선언해놓았다. 왜 하필 행 구조가 아닌 열구조인 Series로 선언했을까 고민했을 때 얼핏 보았던, 분석은 컬럼단위로 진행된다는 이 공감이 가서 그렇게 이해했는데 이게 OLAP의 특성과 비슷하다고 생각했다. 

           

           

          6. 마무리

          초반에는 지끈지끈 했지만 데이터베이스의 저장과 검색 관점에서 차근히 풀어가는 책의 전개방식이 되게 좋았다. 발생 -> 문제 -> 해결 -> 발전으로 이루어지는 데이터베이스의 발전 방향과 특히 데이터 분석 쪽에 이렇게 연결되어 있는 (책 발간당시는 꽤나 과거) 자료를 이제야 찾다니 그것도 흥미로웠다. 생각보다 어플리케이션 분석은 유래가 오래되었고 이를 잡마켓 수면위로 올라온지 얼마안된 것 뿐이구나 라고 생각했다

          + Recent posts