[프로젝트 #2-3] 알아도 끝이 없는 결측치 처리, 오늘은 무엇이? 🔍

오늘은 프로젝트 가이드 세션에서 교안으로 사용된 내용을 참고하여 데이터 전처리(2차)를 진행하였다.

 

데이터 전처리 2차 진행

1.  사용할 컬럼만을 가지고 전처리 진행

cols = [
    # 호스트 정보
    'host_id', 'host_is_superhost', 'host_identity_verified',
    # 위치 정보
    'neighbourhood_group_cleansed',
    # 숙소 정보
    'property_type', 'room_type', 'accommodates', 'bedrooms', 'beds', 'bathrooms',
    'amenities',
    # 가격 및 예약 정보
    'price', 'minimum_nights', 'maximum_nights', 'availability_365',
    'instant_bookable'
]

 

df[cols]

df_1 = df[cols] 로 다시 저장해서 진행하였음.

 

2. 타입 변환

price 컬럼(object)을 float 타입으로 변경했다. 가격(Price), 소득(Income), 매출(Sales) 같은 변수는 오른쪽으로 긴 꼬리를 가진 분포(=우측 편향, right-skewed) 인 경우가 많다는 사실을 알게되었다. 즉, 대부분 값은 낮지만, 일부 극단적으로 큰 값이 있어서 평균을 왜곡시킬 수가 있다는 것.

  • 로그 변환의 효과 ➡️ "극단적인 금액 분포를 부드럽게 만들어서 통계적 가정(정규성 등)을 만족시키기 위해" 변환한다.
    • 정규성 개선
    • 이상치 영향 감소
    • 해석이 용이
df_1['price_log'] = np.log1p(df_1['price'])  
#np.log1p(x)는 log(1+x)이기 때문에 0도 처리가능

 

 

3. 결측치 처리 과정

  • 범주형을 'f'로 채운 이유 : 슈퍼호스트인지 아닌지, 신원확인 된건지 아닌지를 볼 때, 등록이 되어있지 않다면 아닌 것으로 판단하여 'f'값으로 채워넣음
  • 수치형 데이터를 최빈값인 1.0으로 채운 이유는 아래에 있다.
# 결측치 처리
print("\n" + "="*60)
print("결측치 처리")
print("="*60)

# host_is_superhost :결측치를 'f' 값으로 대체
if 'host_is_superhost' in df_1.columns:
    df_1['host_is_superhost'].fillna('f', inplace=True)
    print(" host_is_superhost 결측치 처리 완료")

# host_identity_verified : 결측치를 'f' 값으로 대체
if 'host_identity_verified' in df_1.columns:
    df_1['host_identity_verified'].fillna('f', inplace=True)
    print(" host_identity_verified 결측치 처리 완료")

# 수치형 데이터 변수의 결측치는 최빈값(mode) 대체
bed_cols = ['bedrooms', 'beds']
for col in bed_cols:
    if df_1[col].isnull().sum() > 0:
        mode_val = df_1[col].mode()
        df_1[col].fillna(mode_val, inplace=True)
        print(f" {col} 결측치를 최빈값({mode_val}으로 대체")

print("\n 결측치 처리 완료")
print(f"최종 데이터 shape: {df_1.shape}")

이렇게 하니까 범주형 데이터는 제대로 채워졌으나, 수치형 데이터가 채워지지 않음.

🔍 원인 분석 : mode_val = df_1[col].mode()
mode_val은 리스트처럼 [1.0] 하나의 값을 가진 시리즈입니다. fillna()는 단일 값만 받아야 하므로 Series 전체를 넣으면 작동하지 않아요. 그래서 결측치가 그대로 남게 됩니다.
-> mode() 결과에서 첫 번째 값을 꺼내야 합니다. 즉, .iloc[0] 또는 [0]을 사용해야 합니다
mode_val = df_1[col].mode()[0]   # <-- 이렇게 사용해야 함.

▼ 최빈값은 이런데, 평균값과 중앙값은 어떻게 작동하지?

더보기

⚙️ 차이점 정리

통계량 메서드 반환 타입 결측치 대체 시 주의점
평균값 (Mean) df[col].mean() float (단일 값) 바로 fillna()에 넣어도 됨 ✅
중앙값 (Median) df[col].median() float (단일 값) 바로 fillna()에 넣어도 됨 ✅
최빈값 (Mode) df[col].mode() Series (여러 값일 수 있음) [0] 또는 .iloc[0]으로 첫 번째 값 꺼내야 함 ⚠️
📌 수치형 데이터 결측치를 채울 때에는 어떤 것으로 채워야할지 아니면 행 삭제를 할지 충분히 고민해봐야 할 사항이다.
내가 현재 데이터로 확인해본 바 [bedrooms, beds, bathrooms] 컬럼은 각각의 개수(정수)를 의미하는 것으로, 평균이나 중앙값으로 대체할 경우 실수(소수점 발생)가 되어버리기 때문에 합당하지 않다고 생각을 했다.
결론적으로 가장 값의 빈도가 많은 것을 의미하는 최빈값(mode)로 선택했고, 그 값을 결측치에 채워넣었다.

최빈값으로 채워넣게된 근거를 조금 더 확보하고자, 전체 행에서 각 컬럼의 값들이 어느정도의 비율을 차지하는지 top3를 확인하였다.
bedrooms의 전체 행 수: 22308
bedrooms
1.0       |  63.57
2.0      |  17.32
0.0      |   9.58
beds의 전체 행 수: 22308
beds
1.0      |  58.65
2.0      |  22.94
3.0      |   8.50
bathrooms의 전체 행 수: 22308
bathrooms
1.0      |  78.46
2.0      |  11.68
1.5       |   4.29

 

3. 이상치 처리

price 의 이상치 처리 : 로그 변환을 한 값으로 박스플롯과 히스토그램을 뽑아보니, 그래프가 이쁘게 나타남.

log(price)의 히스토그램과 박스플랏

이 그래프를 보시고서, 튜터님께서 "IQR 방식의 이상치 제거"를 사용해도 괜찮을 것 같다는 의견을 주셨다.

 

 

팀 의견 취합 및 결정사항

1. price 이상치 제거 : IQR 방식의 이상치 제거 ✅

2. 결측치 처리

  • 범주형은 (만약 머신러닝까지 고려해야 한다면,) "unknown"으로 대체하는 것이 결과 편향에 있어서 안정적!! ✅
  • 수치형은 숙소 유형 등과 매칭을 통해 해당하는 값을 채울 것 vs 최빈값으로 채워도 문제가 없을 것 : 아직은 미정 💭

3. 단기 숙소 / 장기 숙소 유형 구분 (파생변수) ➡️ EDA 과정에서 확인해 볼 것으로 결정! 

minimum_nights(최소 숙박일 수), maximum_nights(최대 숙박일 수) 컬럼을 기준으로 단기/장기 숙박을 나누고, 데이터의 특성과 패턴을 파악해보자는 의견이 있었으나, 그러면 단기/장기를 나누는 기준을 무엇으로 해야하지? 라는 의문이 생기게 되었다. 에어비앤비의 운영규정을 찾아보니 28일을 기준으로 단기/장기숙박을 구분하였다. 하지만 분포가 너무 치우쳐서 다른 방안을 고민해 봐야한다.

여기서 나온 가설? 장기 숙소보다 단기 숙소가 1박 평균 금액이 더 비쌀 것이다!? 확인 필요 🔍

 

4. df_1[df_1['maximum_nights'] == 10000] 인 값이 존재, 이거는 이상치로 봐야겠다. ✅

 

5. neighbourhood_group_cleansed 컬럼 : 대분류 지역명

지역 구분을 해보는 건 어떨까?

맨해튼, 브루클린 (관광 특구 라벨링 추가)  /  퀸스, 브롱스, 스태튼아일랜드(비관광) : 아직은 미정 💭

 

 

오늘의 회고

✚ 오늘은 그래도 어제보다는 한 단계라도 더 나아간 듯 하다. 팀원들끼리 의견을 내고 의문인 점은 튜터님들께 여쭈어보고 하나씩 결정해 나간 것들이 있어서 좋았다. 

➖ 어려웠던 점은 분석에 필요한 파생변수를 만들어가는 부분에서 브레인스토밍이 어려웠다. 가격에 영향을 주는 요인을 확인해 볼 수 있는 파생변수는 뭐가 있을까? 

 

 

내일 할 거

- EDA 1차

- 통계 가설 선정!!!! 뭐가 재밌을까? ㅎㅎ