이번 EDA 과정에서는 PCIAT 데이터의 결측치 처리 및 SII 점수 재계산을 수행하였습니다. 이후 결과를 바탕으로 데이터의 이상값을 분석하고 시각화하는 과정까지 진행했습니다.
PCIAT_cols = [f'PCIAT-PCIAT_{i+1:02d}' for i in range(20)]
recalc_total_score = train_with_sii[PCIAT_cols].sum(
axis=1, skipna=True
)
(recalc_total_score == train_with_sii['PCIAT-PCIAT_Total']).all() # PCIAT Valuse Sum == Total
PCIAT_cols: 각 질문 열 이름을 동적으로 생성합니다.sum(axis=1, skipna=True): 결측치(NaN)를 무시하고 각 행의 합계를 계산합니다.all(): 합계가 PCIAT-PCIAT_Total과 동일한지 전체적으로 확인합니다.합계가 동일한지를 확인하여, 데이터의 정합성을 검증할 수 있습니다. 이후 이 정보를 바탕으로 SII 값을 재계산하였습니다.
def recalculate_sii(row):
# PCIAT-PCIAT_Total 값이 결측치인 경우 NaN 반환
if pd.isna(row['PCIAT-PCIAT_Total']):
return np.nan, np.nan
# 최대 가능한 점수 계산
max_possible = row['PCIAT-PCIAT_Total'] + row[PCIAT_cols].isna().sum() * 5
# 디버깅용 출력 (필요 없으면 제거 가능)
print(f"PCIAT-PCIAT_Total: {row['PCIAT-PCIAT_Total']}")
print(f"Missing questions: {row[PCIAT_cols].isna().sum()}")
print(f"Added to max_possible: {row[PCIAT_cols].isna().sum() * 5}")
print('-' * 80)
# SII 카테고리 재계산
if row['PCIAT-PCIAT_Total'] <= 30 and max_possible <= 30:
return max_possible, 0
elif 31 <= row['PCIAT-PCIAT_Total'] <= 49 and max_possible <= 49:
return max_possible, 1
elif 50 <= row['PCIAT-PCIAT_Total'] <= 79 and max_possible <= 79:
return max_possible, 2
elif row['PCIAT-PCIAT_Total'] >= 80 and max_possible >= 80:
return max_possible, 3
# 범위에 해당하지 않을 경우
return max_possible, np.nan
# train 데이터프레임에 함수 적용
train[['recalc_total', 'recalc_sii']] = train.apply(
recalculate_sii, axis=1, result_type='expand'
)
apply()를 통해 각 행에 대해 recalculate_sii() 함수를 적용합니다.recalc_total)과 재분류된 SII(recalc_sii)를 데이터프레임에 추가합니다.mismatch_rows = train[
(train['recalc_sii'] != train['sii']) & train['sii'].notna()
]
mismatch_rows[PCIAT_cols + [
'PCIAT-PCIAT_Total', 'sii', 'recalc_sii'
]].style.applymap(
lambda x: 'background-color: #FFC0CB' if pd.isna(x) else ''
)
sii와 재계산된 recalc_sii가 일치하지 않는 행을 필터링합니다.style.applymap(): 결측치(NaN)가 있는 셀에 배경색(핑크색)을 추가하여 직관적으로 확인할 수 있도록 합니다.# 기존 SII 값을 재계산 값으로 교체
train['sii'] = train['recalc_sii']
# 완전한 응답의 총합만 유지, 결측치가 있으면 NaN으로 설정
train['complete_resp_total'] = train['PCIAT-PCIAT_Total'].where(
train[PCIAT_cols].notna().all(axis=1), np.nan
)
# SII 범주화 및 순서 지정
sii_map = {0: '0 (None)', 1: '1 (Mild)', 2: '2 (Moderate)', 3: '3 (Severe)'}
train['sii'] = train['sii'].map(sii_map).fillna('Missing')
sii_order = ['Missing', '0 (None)', '1 (Mild)', '2 (Moderate)', '3 (Severe)']
train['sii'] = pd.Categorical(train['sii'], categories=sii_order, ordered=True)
# 불필요한 열 삭제
train.drop(columns='recalc_sii', inplace=True)
sii_counts = train['sii'].value_counts().reset_index()
total = sii_counts['count'].sum()
sii_counts['percentage'] = (sii_counts['count'] / total) * 100
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# SII 분포 시각화
sns.barplot(x='sii', y='count', data=sii_counts, palette='Blues_d', ax=axes[0])
axes[0].set_title('Distribution of Severity Impairment Index (sii)', fontsize=14)
for p in axes[0].patches:
height = p.get_height()
percentage = sii_counts.loc[sii_counts['count'] == height, 'percentage'].values[0]
axes[0].text(
p.get_x() + p.get_width() / 2,
height + 5, f'{int(height)} ({percentage:.1f}%)',
ha="center", fontsize=12
)
# PCIAT_Total 분포 시각화
sns.histplot(train['complete_resp_total'].dropna(), bins=20, ax=axes[1])
axes[1].set_title('Distribution of PCIAT_Total', fontsize=14)
axes[1].set_xlabel('PCIAT_Total for Complete PCIAT Responses')
plt.tight_layout()
plt.show()
complete_resp_total)에 대해 점수 분포를 히스토그램으로 표시합니다.데이터 컬럼이 너무 많아서 분석할 내용이 많다 보니 하나씩 공부해가는 과정이라 EDA 작업이 늦어지고 있네요,,,
다음 글에서는 다른 컬럼들의 정보를 바탕으로 추가적인 EDA를 진행해볼게요. 🚀