오늘은 확률분포 개념을 정리하고, 결측값·이상치·중복값을 처리하는 방법을 배웠다. 이후 타이타닉 데이터로 상관관계를 살펴보고, 가설을 세워 집단별 생존율을 비교했다.
기초 통계에서는 확률변수와 확률분포, 확률질량함수와 확률밀도함수를 다뤘다. 이산형은 각 값이 나올 확률을, 연속형은 밀도 곡선 아래 구간의 면적으로 확률을 생각하는 방식이었다.
예제에서는 1~4가 각각 25번씩 나오도록 데이터를 만들고, 횟수를 전체 100개로 나눠 비율을 구했다. 실제 경주 결과가 아니라 분포표를 만드는 연습용 데이터였다.
전처리 실습은 info(), describe(), isna().sum()으로 데이터 상태를 확인하는 것부터 시작했다. 예제 CSV에는 나이·도시·급여에 결측값이 각각 하나씩 있었다.
나이와 급여는 중앙값, 도시는 최빈값으로 채웠다. 아래는 실습 코드를 간단히 묶은 것이다.
df1["age"] = df1["age"].fillna(df1["age"].median())
df1["salary"] = df1["salary"].fillna(df1["salary"].median())
df1["city"] = df1["city"].fillna(df1["city"].mode()[0])
df1["age"] = df1["age"].astype(int)
df1["salary"] = df1["salary"].astype(int)
앞선 예제에서는 결측 행을 삭제하는 방법과 평균·중앙값·최빈값으로 채우는 방법도 비교했다. 같은 빈 값이라도 수치인지 범주인지, 분포가 어떤지에 따라 처리 기준을 정해야 했다.
박스플롯과 사분위수를 사용해 이상치 후보를 확인했다. IQR은 3사분위수에서 1사분위수를 뺀 값이고, 실습에서는 아래 범위를 벗어나는 행을 제외했다.
def remove_outlier(df, col_name):
df_copy = df.copy()
Q1 = df_copy[col_name].quantile(0.25)
Q3 = df_copy[col_name].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
mask = ~(
(df_copy[col_name] < lower)
| (df_copy[col_name] > upper)
)
return df_copy[mask]
급여를 먼저 처리한 뒤, 남은 데이터에서 나이를 기준으로 다시 처리했다. 급여 기준은 하한 1,850, 상한 6,050으로 계산됐다.
범위 밖에 있다는 이유만으로 모두 잘못된 값이라고 볼 수는 없다. 이번 코드는 필터링 방법을 연습한 것이고, 실제 데이터에서는 제거할 근거도 함께 확인해야 한다.
중복 확인에는 duplicated(), 제거에는 drop_duplicates()를 사용했다. ID가 달라도 나머지 정보가 같은 행을 찾기 위해 비교할 컬럼을 지정했다.
df5 = df4.drop_duplicates(
subset=["name", "age", "gender", "city"]
).reset_index(drop=True)
이 실습에서는 이름·나이·성별·도시가 같으면 중복으로 처리했다. 어떤 컬럼을 기준으로 삼는지에 따라 남는 데이터가 달라진다.
EDA에서는 컬럼의 의미와 분포를 확인하고, 관심 있는 조건에 따라 데이터를 비교했다. 먼저 수치형 컬럼의 상관계수를 구하고 색으로 표현했다.
plt.matshow(df.corr(numeric_only=True))
plt.colorbar()
plt.show()
상관계수는 선형적인 관계를 살펴보는 지표다. 값이 크다고 원인과 결과가 확인되는 것은 아니라는 점도 함께 정리했다.
타이타닉 전처리에서는 일부 컬럼을 제외하고, Age의 결측값 177개를 중앙값 28로 채우는 코드를 다뤘다. 나이를 정수형으로 바꾸는 과정도 있었다.
다만 최솟값인 0.42를 중앙값으로 바꾸는 코드도 있어 다시 살펴볼 필요가 있다. 1세 미만의 나이는 영아를 나타낼 수 있으므로 작은 값 자체가 오류의 근거는 아니다.
첫 번째는 성별에 따라 생존율이 다른지 보는 것이었다.
df1.groupby("Sex")["Survived"].agg(["count", "mean", "sum"])
집단별 인원수와 생존자 수, 생존 비율을 함께 보고 Matplotlib과 seaborn으로 막대그래프를 그렸다.
sns.barplot(
x="Sex", y="Survived", data=df1,
hue="Sex", palette="pastel", width=.5
)
추가로 18세 미만 데이터를 골라 Parch가 0인 경우와 아닌 경우를 비교했다.
child_df = df1[df1["Age"] < 18]
child_df[child_df["Parch"] != 0]["Survived"].mean()
child_df[child_df["Parch"] == 0]["Survived"].mean()
저장된 출력은 각각 47.5%, 50%였다. Parch는 동반 부모와 자녀 수를 합친 컬럼이므로, 이 조건은 부모 동반 여부를 직접 기록한 값과는 구분해야 한다. 이번에는 비율을 비교한 단계이며 통계적 유의성 검정까지 진행한 것은 아니다.
전처리는 함수를 적용하는 것뿐 아니라 어떤 기준으로 채우고 지울지 정하는 과정이었다. EDA에서도 가설에 맞는 컬럼을 골랐는지, 전처리 후 데이터가 의도대로 남아 있는지 함께 확인해야겠다.