오늘은 프로젝트 가이드 세션에서 교안으로 사용된 내용을 참고하여 데이터 전처리(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.58beds의 전체 행 수: 22308
beds
1.0 | 58.65
2.0 | 22.94
3.0 | 8.50bathrooms의 전체 행 수: 22308
bathrooms
1.0 | 78.46
2.0 | 11.68
1.5 | 4.29
3. 이상치 처리
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차
- 통계 가설 선정!!!! 뭐가 재밌을까? ㅎㅎ
'프로젝트' 카테고리의 다른 글
| [프로젝트 #2-4] EDA 1차 진행 : 단변량/다변량 분석 | 파생변수는 어쩌지...? (0) | 2025.10.16 |
|---|---|
| [프로젝트 #2-2] 데이터 전처리 및 EDA(1차)를 했는데, (안)했습니다.🤪 (0) | 2025.10.14 |
| [프로젝트 #2-1] 주제 선정 및 컬럼 확인을 해보았더니...👀 (1) | 2025.10.13 |