내일배움캠프

[내일배움캠프] TIL 26일차 26.02.05(목)

nom_de_plume 2026. 2. 5. 14:45

통계 실습 1회차

모르는 코드 정리

 

데이터 로드

iris = load_iris()
  • 머신러닝 라이브러리인 scikit-learn에서 제공하는 기본 데이터 가져오는 명령
  • 붓꽃 150송이의 꽃받침(Sepal)과 꽃잎(Petal)의 길이와 너비 값 포함.

표 형태로 변환

iris_df = pd.DataFrame(iris.data, columns=iris.feature_names)
  • pd.DataFrame() : 데이터를 행(Row)과 열(Column)이 있는 2차원 표 구조로 만드는 함수
  • iris.data : 표 안에 있는 실제 숫자 데이터 (데이터 내용)
  • columns=iris.feature_names : 각 데이터의 각 열이 무엇을 의미하는지 이름 표시 (열 이름)
    • 인자가 없다면 열 이름은 그냥 숫자인 0,1,2,3 으로 표시
      • sepal length (cm) (꽃받침 길이)
      • sepal width (cm) (꽃받침 너비)
      • petal length (cm) (꽃잎 길이)
      • petal width (cm) (꽃잎 너비)

라벨 추가

iris_df['species'] = iris.target
  • 꽃이 어떤 종인지 알려주는 정보를 species라는 이름의 새로운 열에 저장
  • 0,1,2 형태

숫자 이름으로 변환

iris_df['species_name'] = iris_df['species'].map({
    0: 'setosa', 1: 'versicolor', 2: 'virginica'
})
  • 숫자를 실제 이름으로 바꾸는 작업
  • species_name이라는 새로운 열에 저장 
    • 0 : setosa
    • 1 : versicolor
    • 2 : virginca

수치형 데이터의 기술 통계

iris_df.describe()
  • 숫자로 된 데이터(길이, 너비 등)의 전반적인 분포 출력
    • mean: 평균값
    • std: 표준편차 (데이터가 얼마나 퍼져 있는지)
    • min / max: 최솟값과 최댓값
    • 25% / 50% / 75%: 사분위수 (데이터를 크기순으로 세웠을 때 위치)
  • ** 50%(중앙값)와 mean(평균)이 비슷하면 데이터가 한쪽으로 쏠리지 않고 예쁘게 모여 있다는 뜻

범주형 데이터의 기술 통계

iris_df.describe(include=['object', 'category'])
  • 숫자만 계산하는 describe() 함수에 include=['object', 'category'] 옵션을 추가해 문자열(종 이름 등) 데이터 요약
  • 데이터 타입이 일반 문자열이든, 최적화된 범주형이든 상관없이 숫자가 아닌 데이터 모두 요약 
    • object (일반 문자열) : 파이썬의 일반적인 문자열(String)
    • category (범주형 데이터) : 데이터의 정류가 정해져 있는 경우에 사용
      • count: 데이터 개수
      • unique: 종류가 몇 가지인가? (붓꽃은 3종류이므로 3)
      • top: 가장 많이 등장하는 이름
      • freq: 그 이름이 몇 번 나왔는가?

데이터의 빈도 및 비율

iris_df['species_name'].value_counts(normalize=True)
  • 특정 항목이 몇 개씩 있는지 확인하는 단계
    • value_counts() : 개수 세기
    • normalize=True : 개수 대신 비율로 출력

도화지 나누기

fig, axes = plt.subplots(1, 2, figsize=(12,5))
  • plt.subplots(1, 2) : 1행 2열로 나눠주기. 즉, 가로로 두 개의 그래프를 나란히 배치
  • fig : 도화지
  • axes : 도화지 안에 그려진 낱개의 그래프 칸 (나누어진 각 칸을 가리키는 리스트)
    • axes[0] : 왼쪽
    • axes[1] : 오른쪽

막대 차트 (Bar Chart)

# 데이터 준비
class_counts = wine_df['class_name'].value_counts()

axes[0].bar(class_counts.index, class_counts.values)
axes[0].set_xlabel('와인 클래스')
axes[0].set_ylabel('개수')
axes[0].set_title('막대차트 : 카테고리별 개수 비교')
axes[0].grid(True)
  • 카테고리별로 몇 개가 있는지(빈도) 비교
    • class_counts.index : 와인 이름 (class_0, class 1 등)이 X축
    • class_counts.value : 해당 와인의 개수가 Y축
    • .grid(True) : 눈금선 그리기

파이 차트 (Pie Chart)

axes[1].pie(class_counts.values, labels=class_counts.index, autopct='%.1f%%', startangle=90)
axes[1].set_title('파이차트 : 전체 대비 비율확인')
  • 전체에서 각 항목이 차지하는 비율 확인
    • autopct='%.1f%%' : 파이 조각 위에 소수점 첫째 자리까지 백분율 자동 표시
      • % : "여기 숫자가 들어갈 거야"라는 신호
      • .1f : "소수점 아래 첫째 자리(float)까지 보여줘"
      • %% : "진짜 '%' 기호를 뒤에 붙여줘"
    • startangle=90 : 첫 번째 조각을 12시 방향에서 시작

 

산술평균 (Arithmetic Mean)

petal_length.mean()
  • 모든 값을 더해 개수로 나눈 값
  • 아주 큰 값이나 아주 작은 값(이상치)이 하나만 있어도 평균이 확 쏠리는 단점

절사평균 (Trimmed Mean)

stats.trim_mean(petal_length, 0.1)
stats.trim_mean(petal_length, 0.2)
  • 데이터를 크기순으로 나열한 뒤, 양끝에서 일정 비율을 잘라내고 남은 값들로만 평균 산출
    • 상,하 10% 제거 후 평균
    • 상,하 20% 제거 후 평균

가중평균 (Weighted Average)

species_mean = iris_df.groupby('species_name')['petal length (cm)'].mean()
display(species_mean)

# 클래스 별 가중치 (임의)
weights = np.array([50, 30, 20])

# 종별 가중 평균
weighted_mean = np.average(species_mean, weights=weights)
# setosa        1.46
# versicolor    4.26
# virginica     5.55

# (1.46*50 + 4.26*30 + 5.55*20) / (50+30+20)
print(f'가중평균:{weighted_mean:.2f}')
  • 모든 데이터가 동일한 중요도를 갖지 않을 때 사용
    • 각 붓꽃 종별 평균값에 임의의 가중치를 곱해 평균 계산

중앙값 (Median)

petal_length.median()
  • 데이터를 1열로 줄 세웠을 때 정확히 가운데 있는 값

최빈값 (Mode)

petal_length.mode()
  • 데이터 세트에서 가장 자주 등장하는 값
  • 특정 수치가 반복되는 경우 유용
  • 결과가 여러 개 나올 수 있어 .value로 확인 필요

최대값 (Max)

petal_length.max()
  • 데이터 세트에서 가장 큰 값

최소값 (Min)

petal_length.min()
  • 데이터 세트에서 가장 작은 값

 

히스토그램

# 시각화
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 히스토그램
axes[0].hist(investment_A, bins=20, alpha=0.6, color='blue', edgecolor='black',
             label=f'A: 표준편차={investment_A.std():.1f}%', density=True)
axes[0].hist(investment_B, bins=20, alpha=0.6, color='red', edgecolor='black',
             label=f'B: 표준편차={investment_B.std():.1f}%', density=True)
axes[0].axvline(0, color='black', linestyle=':', linewidth=1, label='손익분기점(0%)')
axes[0].set_xlabel('수익률 (%)')
axes[0].set_ylabel('밀도')
axes[0].set_title('수익률 분포: A는 모여있고, B는 넓게 퍼짐')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
  • hist() 함수
    • 데이터가 어느 구간에 얼마나 몰려 있는지 출력
      • bins=20 : 데이터를 20개의 구간으로 나누어 출력. 숫자가 클수록 막대가 촘촘해진다.
      • alpha=0.6 : 투명도(0~1 사이). 겹치는 부분을 잘 보이게 하기 위해 약간 투명하게 설정
      • density=True : 단순히 개수를 세는 것이 아니라 전체 면적이 1이 되도록 확률 밀도 표현
  • avline()
    • 기준선 긋기
    • Axis Vertical Line의 약자
      • axvline(0, . . .) : X축의 0점에 수직선 긋기

박스플롯 (Box Plot, 상자 수염 그림)

# 박스플롯
bp = axes[1].boxplot([investment_A, investment_B],
                     labels=['A (안정형)', 'B (공격형)'],
                     patch_artist=True)
bp['boxes'][0].set_facecolor('lightblue')
bp['boxes'][1].set_facecolor('lightcoral')
axes[1].axhline(0, color='black', linestyle=':', linewidth=1)
axes[1].set_ylabel('수익률 (%)')
axes[1].set_title('박스플롯: 상자 크기 = 데이터가 퍼진 정도')
axes[1].grid(True, alpha=0.3)
  • boxplot()
    • 그래프의 구성 요소(상자, 수염, 중앙값 선 등)를 딕셔너리 형태로 반환
      • 상자의 중간 선: 중앙값(Median).
      • 상자의 길이 (IQR): 데이터의 중간 50%가 모여있는 구간. B의 상자가 A보다 훨씬 길다면, 그만큼 수익률의 변동성이 크다는 뜻.
      • 수염 (Whiskers): 상자 밖으로 뻗은 선 (데이터의 범위).
      • 점 (Outliers): 수염 너머에 찍히는 점들은 이상치, 평소와 다르게 아주 높거나 낮았던 수익률을 의미. 
        • [investment_A, investment_B] : 여러 데이터를 리스트로 묶어 전달하면 한 그래프 안에 나란히 출력.
        • patch_artist=True : 상자 내부를 색상으로 채울 수 있게 허용하는 옵션.
        • True여야 나중에 set_facecolor로 색 입히기 가능
  • .set_facecolor()
    • bp['boxes'][0]: 첫 번째 상자(A)를 선택해 연한 파랑(lightblue) 색 입히기.
    • bp['boxes'][1]: 두 번째 상자(B)를 선택해 연한 산호색(lightcoral) 색 입히기.

 

편차

deviations = data - data.mean()
  • 각 값이 평균에서 얼마나 떨어져 있는가

분산 (Variance)

print(f'분산 (모집단) : {data.var(ddof=0):.4f}')
print(f'분산 (표본집단) : {data.var(ddof=1):.4f}')
  • 편차를 제곱하여 합한 뒤 개수로 나눈 값 (편차 제곱의 평균)
  •  데이터의 개수가 많으면 모집단과 표본집단의 값이 비슷해진다.
    • ddof (Delta Degrees of Freedom) : 자유도
      • ddof=0 (모집단) : 전체 데이터를 알고 있을 때 사용 (N으로 나눔)
      • ddof=1 (표본집단) : 전체 중 일부 샘플로 전체를 추측할 때 사용 (N - 1로 나눔)
      • ** 보통 전체의 일부인 경우가 많아 Pandas의 기본값은 ddof=1

표준편차 (Standard Deviation)

print(f'표준편차 (모집단) : {data.std(ddof=0):.4f}')
print(f'표준편차 (표본집단) : {data.std(ddof=1):.4f}')
  • 분산에 루트를 씌워 실제 데이터와 단위를 맞춘 값
    • ddof (Delta Degrees of Freedom) : 자유도
      • ddof=0 (모집단) : 전체 데이터를 알고 있을 때 사용 (N으로 나눔)
      • ddof=1 (표본집단) : 전체 중 일부 샘플로 전체를 추측할 때 사용 (N - 1로 나눔)
      • ** 보통 전체의 일부인 경우가 많아 Pandas의 기본값은 ddof=1

범위

print(f'범위 : {data.max() - data.min():.4f}')
  • 가장 단순한 산포 지표

백분위수

Q1 = data.quantile(0.25)
Q2 = data.quantile(0.5)
Q3 = data.quantile(0.75)
  • 데이터를 크기순으로 나열했을 때 특정위치에 나타나는 값
  • data.quantile(p) : 전체 데이터를 작은 값부터 큰 값으로 세웠을 때, p 비율(0~1) 위치에 있는 값
    • Q1 (1사분위수, 0.25): 하위 25% 지점의 값. 전체 데이터 중 앞부분의 중심.
    • Q2 (2사분위수, 0.50): 정확히 50% 지점의 값, 중앙값(Median).
    • Q3 (3사분위수, 0.75): 상위 25% 지점(하위 75%)의 값. 뒷부분의 중심

IQR (Interquartile Range)

IQR = Q3 - Q1
iqr_lower = Q1 - 1.5 * IQR
iqr_upper = Q3 + 1.5 * IQR
iqr_outliers = data[(data < iqr_lower) | (data > iqr_upper)] #이상치인 데이터
  • IQR
    • 전체 데이터의 핵심이라 할 수 있는 중간 50%가 모여있는 구간의 너비
    • 이 값이 클수록 데이터의 중간 부분이 넓게 퍼져 있다는 뜻
    • 작을수록 중앙 근처에 빽빽하게 모여 있다는 뜻
  • 이상치 판별 기준
    • Q1 - 1.5 * IQR 보다 작거나 Q3 + 1.5 * IQR 보다 크면 이상치로 간주
    • 데이터가 정규분포를 따르지 않아도 쓸 수 있음.

Z-Score (표준화된 거리)

z_scores = (data - data.mean()) / data.std()
z_outliers_2 = data[np.abs(z_scores)> 2] #절댓값이 2보다 큰 경우
z_outliers_3 = data[np.abs(z_scores)> 3] #절댓값이 3보다 큰 경우
print('Z-score 이상치 :')
print('|Z-score| > 2 이상치 개수 :', len(z_outliers_2))
print('|Z-score| > 3 이상치 개수 :', len(z_outliers_3))

# Z-score 이상치 기준 (|Z| > 2)
z_lower = data.mean() - 2 * data.std()
z_upper = data.mean() + 2 * data.std()
  • 데이터가 평균으로부터 표준편차의 몇 배만큼 떨어져 있는지를 나타내는 척도
    • Z-score > 2: 약 95%의 데이터를 벗어나는 값
    • Z-score > 3: 약 99.7%의 데이터를 벗어나는 값
  • Z-score 이상치 기준 (|Z| > 2)
    • 데이터가 정규분포(종 모양)을 따른다고 가정할 때
      • z_lower (평균 - 2 * 표준편차): 평균에서 왼쪽으로 표준편차의 2배만큼 떨어진 지점.
      • z_upper (평균 + 2 * 표준편차): 평균에서 오른쪽으로 표준편차의 2배만큼 떨어진 지점

 

범주별 그래프 분할 비교

# 클래스별 알코올 도수 히스토그램
g = sns.FacetGrid(wine_df, col='class_name', height=5, aspect=1)
g.map_dataframe(sns.histplot, x='alcohol', kde=True, color='steelblue')
g.set_titles('{col_name}')
g.set_axis_labels('알코올 도수', '빈도')
g.figure.suptitle('클래스별 알코올 도수 분포', fontsize=14, fontweight='bold', y=1.02)
plt.tight_layout()
plt.show()
  • sns.FacetGrid
    • col='class_name' : class_name의 종류에 따라 가로(Column)을 그래프로 나누어 그림.
    • height=5 : 각 그래프의 높이 5인치
    • aspect=1 : 가로세로 비율(aspect)을 1:1(정사각형)로 설정
  • g.map_dataframe (데이터 매핑)
    • sns.histplot: 분할된 각 칸에 히스토그램을 그리라는 명령.
    • kde=True: 히스토그램 위에 부드러운 곡선(Kernel Density Estimate)을 그려서 데이터의 전반적인 흐름을 더 쉽게 파악