목차
- CSV 파일 불러오기
- 데이터 분석
- data_info
- data_head
- data_nulls
- 데이터 분석
- 데이터셋 탐색 및 전처리
- 결측치 처리
- 행 제거
- 데이터 손실 40개 발생
- 행 제거
- 이상치 탐지 및 제거
- boxplot 그래프
- CRIM
- NOX
- AGE
- IQR
- CRIM 이상치 제거
- boxplot 그래프
- 결측치 처리
- 특성 선택
- 여러 회귀 모델 비교
- 데이터 분할(훈련, 테스트 데이터)
- 모델 학습
- 선형 회귀
- 의사결정나무
- 랜덤 포레스트 등
- 모델 성능 평가
- Mean Absolute Error (MAE): 예측값과 실제값의 절대 오차의 평균.
- Mean Squared Error (MSE): 예측값과 실제값의 제곱 오차의 평균.
- R² Score: 모델이 데이터의 변동성을 얼마나 설명하는지 나타내는 지표.
- 결과 분석
주제, 목표, 학습 내용
💡 주제
주택 가격 예측 모델 구축
🎯 목표
주어진 주택 데이터셋을 사용하여 주택 가격을 예측하는 회귀 모델을 구축한다.
📖 학습 내용
- 지도 학습의 기본 개념과 회귀 분석을 이해하고, 실제 데이터에 적용하는 능력
- 데이터 전처리 및 탐색
- 데이터의 품질을 높이는 방법과 특징 선택의 중요성
- 여러 회귀 모델의 이해
- 다양한 회귀 기법의 원리와 적용 방법
- 모델 성능 평가
- 성능 지표의 이해 및 비교 분석을 통해 최적의 모델 선택
과제 가이드
- 데이터셋 탐색 및 전처리:
- 결측치 처리
- 이상치 탐지 및 제거
- 특징 선택
- 여러 회귀 모델 비교:
- 선형 회귀
- 의사결정나무
- 랜덤 포레스트 등
- 모델 성능 평가:
- 지표를 사용하여 모델 성능을 비교합니다.
- Mean Absolute Error (MAE): 예측값과 실제값의 절대 오차의 평균.
- Mean Squared Error (MSE): 예측값과 실제값의 제곱 오차의 평균.
- R² Score: 모델이 데이터의 변동성을 얼마나 설명하는지 나타내는 지표.
- 지표를 사용하여 모델 성능을 비교합니다.
- 결과 분석:
- 각 모델의 성능을 비교하고 최적의 모델을 선택하여 결과를 시각화합니다.
- 시각화: 성능 지표를 막대 그래프로 시각화하여 쉽게 비교할 수 있도록 합니다. matplotlib 또는 seaborn을 사용하여 막대 그래프를 그립니다.
- 각 모델의 성능을 비교하고 최적의 모델을 선택하여 결과를 시각화합니다.
0. 자료 분석
- 주제 : 보스턴 주택 가격 데이터
- 칼럼 수 : 총 14개
- CRIM : 범죄율
- ZN : 25,000 평방피트 이상으로 구획된 주거 지역의 비율
- INDUS : 비소매 상업 지역 면적의 비율
- CHAS : 찰스강 인접 여부
- NOX : 일산화질소 농도
- RM : 주택의 평균 방 개수
- AGE : 1940년 이전에 건축된 소유 주택의 비율
- DIS : 보스턴의 5개 고용센터까지의 가중 거리
- RAD : 방사형 고속도로 접근성 지수
- TAX : 10,000달러당 재산세율
- PTRATIO : 학생-교사 비율
- B : 흑인 인구 비율
- LSTAT : 하위 계층의 비율(%)
- MEDV : 주택의 중앙값 (단위:$1000)
- 행 개수 : 총 506개
- NaN 수
- CRIM, ZN, INDUS, CHAS, AGE, LSTAT열에 각각 20개씩, 120개
✅
저는 주택 가격에 있어서 범죄율(CRIM), 일산화질소 농도(NOX), 주택 나이(AGE)가 영향력이 높다고 생각했어요.
이유는
- 범죄율(평균값) : 범죄율이 높으면 치안이 좋지 않기 때문에 주택 가격에 변동이 심할 거라 생각했어요.
- 일산화질소 농도(결측치 없음) : 주택 근처에 유해물질의 농도가 짙다면 쾌적한 생활 환경이 되지 못한다고 생각했어요.
- 주택 나이(최빈값) : 노후된 주택이라면 수리비가 추가적으로 더 들 테니, 주택 가격에 영향이 있다고 생각했어요.
영향력이 있다고 생각되는 CRIM, NOX, AGE에 대해서 데이터를 어떻게 처리하고, 모델을 학습시킬지를 해볼게요
제 가설이 맞는지 아닌지를 검증하는 단계예요!
검증하러 가봅시다 😎
1. CSV 파일 불러오기
# 원본 CSV 파일 불러오기
import pandas as pd
df_housingdata = pd.read_csv('housingdata.csv')
df_housingdata
CRIM, NOX, AGE만 있는 데이터셋 불러오기
# Filtered_housingdata2 불러오기_CRIM, NOX, AGE,MEDV만 있는 파일)
import pandas as pd
df_filtered_housingdata2 = pd.read_csv("filtered_housingdata2.csv")
df_filtered_housingdata2
❤️🩹 데이터셋을 따로 만들어보려고 했는데 계속 KeyError가 나더라구요 그래서 gpt한테 파일 만들어달라고 요청해서 불러왔어요
1.1. 데이터 분석
- filtered_housingdata의 구조를 알아볼 거예요
- info, head, null을 써서 간략하게 알아볼게요
1.1.1. data_info
- 데이터의 타입을 알아보는 겁니다.
- 칼럼명과 수, Non-Null 수, date-type을 알아볼 수 있어요
# 데이터 구조 확인_데이터 타입 확인_(칼럼 수, non-null 수, 데이터 타입)
data_info = df_filtered_housingdata2.info()
📚 자료 분석
- RangeIndex: 506 entries, 0 to 505
- 506개의 행이 있고, 0부터 505까지 있어요
- Data columns (total 3 columns)
- 3개의 칼럼이 있다는 것을 알 수 있어요
- dtypes: float64(3)
- 데이터 타입은 float64가 3개가 있어요
1.1.2. data_head
# 자료의 상위 5개만 추출_head()
data_head = df_filtered_housingdata2.head()
data_head
📚 자료 분석
- 데이터셋의 상위 5줄을 뽑아오는 코드입니다.
1.1.3. data_nulls
# nulls 개수 확인_isnull().sum()
data_nulls = df_filtered_housingdata2.isnull().sum()
data_nulls
📚 자료 분석
- 각 칼럼(열)의 결측값 개수를 알려주는 isnull() 함수예요
- 반대로, 결측값이 아닌 데이터를 확인하려면 notnull() 함수를 써주시면 돼요
- 해당 데이터셋에는 CRIM, AGE에 각각 20개의 결측값이 있는 걸 확인할 수 있어요
2. 데이터셋 탐색 및 전처리
2.1. 결측치 처리
2.1.1. 행 제거
# 결측치 처리_행 제거(데이터 손실 발생 40개)
# NaN이 입력된 값에 다른 값을 대체하는 순간 신뢰성이 떨어진다고 판단해서 "제거"로 결측치를 처리함
df_filtered_housingdata_dropped_rows = df_filtered_housingdata2.dropna()
df_filtered_housingdata_dropped_rows
📚 자료 분석
- 결측치가 있는 행을 제거했어요.
- 처음에는 총 506개의 행을 가지고 있었으나, 결측값이 있는 행을 제거하니까 40개가 손실되었어요
- 원래부터 NaN였던 값에 다른 값으로 대체를 한다는 것부터가 신뢰성이 많이 떨어진다고 생각해요
- 그래서 제거로 결측값을 처리할 겁니다.
2.2. 이상치 탐지 및 제거
🤔 이상치 탐지를 처리하기 전, 저의 생각
- 이상치 탐지를 하는 방법에는 "제거", "변환", "IQR 방법"이 있어요
- 제일 깔끔하게 처리할 거면 제거하는 것이 알맞다고 생각했어요
- 그래도 제거하기 전에 얼마나 분포되어 있는지 확인을 해봐야겠단 생각이 들었어요
- 변환은 이상치를 다른 값으로 변환하는 것이기 때문에 신뢰성이 높다고 생각하지 않았어요
- IQR 방법은 1사분위, 2사분위, 3사분위, 4사분위로 각각 0~25%, 25~50%, 50~75%, 75~100%의 범위를 가져요
- 1사분위 수(값 기준 하위 25%되는 값)와 3사분위 수(값 기준 상위 25%되는 값)을 찾으면 돼요
2.2.1 boxplot 그래프
- boxplot() 함수의 옵션
- ylim : y축의 범위
- names : 변수에 이름 붙이기
- col : 색상
2.2.1.1. CRIM
# 그래프 만들기(CRIM 칼럼의 이상치, 최댓&최솟값, 유효값 확인)
# 해석 : 데이터 양이 적어서 판단이 불가하다고 판단함
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.boxplot(df_filtered_housingdata_dropped_rows['CRIM'])
plt.title("CRIM") # 그래프 제목 적기
📚 자료 분석
- 결측치를 제거한 파일에서 "CRIM"의 botplot 그래프를 만들어냈어요
- 데이터 총량이 부족하여 이상치 확인이 어려워 보여요
2.2.1.2. NOX
# 그래프 만들기(NOX 칼럼의 이상치, 최댓&최솟값, 유효값 확인)
# 해석 : 이상치가 없다고 판단함
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.boxplot(df_filtered_housingdata_dropped_rows['NOX'])
plt.title("NOX") # 그래프 제목 적기
📚 자료 분석
- 이상치를 뜻하는 동그라미가 없는 걸 보니, 이상치가 확인되지 않는다고 판단했어요
- 중간값은 약 0.53이고, 1사분위 값은 약 0.45, 3사분위 값은 약 0.61로 확인되었어요
- 최솟값은 약 0.4이고, 최댓값은 약 0.9로 확인되었어요
2.2.1.3. AGE
# 그래프 만들기(AGE 칼럼의 이상치, 최댓&최솟값, 유효값 확인)
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.boxplot(df_filtered_housingdata_dropped_rows['AGE'])
plt.title("AGE") # 그래프 제목 적기
📚 자료 분석
- AGE도 마찬가지로 이상치가 발견되진 않았어요
- 중간값으로 약 80이고, 1사분위 값은 약 43이고, 3사분위 값은 약 97로 확인되었어요
- 최솟값은 약 0.2이고, 최댓값은 약 100으로 확인되었어요
이제 확인이 되었으니 CRIM 칼럼을 어떻게 처리해야 할지 고민해보았어요
일단 제거를 해보았습니다.
2.2.2. IQR
# 특정 열의 이상치 확인(IQR 방법)
Q1 = df_filtered_housingdata_dropped_rows['CRIM'].quantile(0.25)
Q3 = df_filtered_housingdata_dropped_rows['CRIM'].quantile(0.75)
IQR = Q3 - Q1
# 이상치 범위 설정
lower_bound = Q1 - 1.5 *IQR
upper_bound = Q3 + 1.5 *IQR
outliers = df_filtered_housingdata_dropped_rows[
(df_filtered_housingdata_dropped_rows['CRIM'] < lower_bound) |
(df_filtered_housingdata_dropped_rows['CRIM'] > upper_bound)]
outliers
📚 자료 분석
- 총 62개의 행이 나온다는 걸 확인할 수 있었어요
- 그 행들은 1사분위 값과 3사분위 범위를 벗어난 이상치들을 뜻한다고 생각했어요
2.2.3. CRIM 이상치 제거
# CRIM 이상치 제거
df_no_outliers = df_filtered_housingdata_dropped_rows[
(df_filtered_housingdata_dropped_rows['CRIM'] >= lower_bound) &
(df_filtered_housingdata_dropped_rows['CRIM'] <= upper_bound)]
df_no_outliers
📚 자료 분석
- 그래서 아까 구해놓은 범위에 있던 값들을 삭제해주었어요
- 데이터 양이 404개까지 줄어들었어요
제거하는 방식으로 이상치를 처리해주었어요
이제 데이터를 학습 데이터와 테스트 데이터로 분할해준 뒤, 모델을 학습시켜볼게요
4. 여러 회귀 모델 비교
4.1. 데이터 분할(훈련, 테스트 데이터)
# 훈련 데이터와 테스트 데이터 분리
from sklearn.model_selection import train_test_split
# 입력 변수(X)와 타겟 변수(y) 분리
X = df_no_outliers.drop(columns=['MEDV']) # 'target'은 목표 변수(예: CRIM과 무관한 타겟 변수)
y = df_no_outliers['MEDV'] # 타겟 변수 이름을 지정
# 훈련 데이터와 테스트 데이터로 분리 (80% 훈련, 20% 테스트)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 결과 확인
print("훈련 데이터 크기:", X_train.shape)
print("테스트 데이터 크기:", X_test.shape)
4.2. 모델 학습
4.2.1. 선형 회귀형 모델
# 선형 회귀형 모델 생성, 학습
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
# 선형 회귀 모델 생성 및 학습
model = LinearRegression()
model.fit(X_train, y_train)
# 예측
y_pred = model.predict(X_test)
4.2.2. 의사결정나무
# 의사결정나무 회귀 모델 생성 및 학습
from sklearn.tree import DecisionTreeRegressor
# 의사결정 회귀 나무 모델 생성 및 학습
tree_model = DecisionTreeRegressor()
tree_model.fit(X_train, y_train)
# 예측
y_pred = tree_model.predict(X_test)
# DecisionTreeRegressor의 하이퍼 파라미터
tree_model = DecisionTreeRegressor(
max_depth=5, # 개별 트리의 최대 길이를 5로 설정
min_samples_split=10) # 노드를 분할 시 필요한 최소 샘플 개수 10으로 지정
4.2.3. 랜덤 포레스트
# 랜덤 포레스트 모델 생성 및 학습
from sklearn.ensemble import RandomForestRegressor
# 랜덤 포레스트 회귀 모델 생성 및 학습
forest_model = RandomForestRegressor(
n_estimators=100, random_state=42
)
forest_model.fit(X_train, y_train)
# 예측
y_pred = forest_model.predict(X_test)
# RandomForestRegressor의 하이퍼 파라미터
forest_model = RandomForestRegressor(
n_estimators=200, # 생성할 나무 개수를 200으로 설정
max_depth=10, # 개별 트리의 최대 길이를 10으로 설정
min_samples_split=5, # 노드를 분할하기 위한 최소 샘플 수를 5개로 설정
random_state=42 # 모델의 결과를 재현 가능하도록 랜덤 시드를 설정함(생략 가능)
)
## n_estimators : 랜덤 포레스트에서 생성할 나무의 개수 지정 (기본값 == 100)
## max_depth: 개별 트리의 최대 깊이.
## min_samples_split: 노드를 분할하기 위한 최소 샘플 수.
## random_state : 모델의 결과를 재현 가능하게 하기 위해 랜덤 시드를 설정함 (필요 시, 생략 가능)
5. 모델 성능 평가
선형 회귀형 모델 평가
📚 선형 회귀형 모델 평가 결과 분석
- 오차의 평균(높을수록 오차가 많이 일어난다는 것을 의미)
- Mean Squared Error: 56.675544148847685
- Mean Squared Error는 제곱근의 평균이에요 대충 56에 루트를 씌운 만큼의 차이가 발생한다는 거예요
- 숫자가 적을수록 오차가 적다는 것을 의미해요
- R^2 Score: 0.006371530245328549
- R^2는 변동성을 뜻해요
- 범위가 0~1 사이에 있는 값이에요
- 1에 가까울수록 예측을 잘한다는 뜻이고, 0에 가까울수록 예측을 못한다는 뜻이에요 :)
- Mean Squared Error: 56.675544148847685
의사결정나무 회귀 모델 평가
# 의사결정나무 회귀 모델 평가
## 선형 회귀형과 비교하였을 때 mse가 더 높은 것을 보아 선형 회귀형 모델의 성능이 좋아보임
from sklearn.metrics import mean_squared_error, r2_score
# 성능 평가
mse = mean_squared_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
print(f"Mean Squared Error: {mse}")
print(f"R2 Score: {r2}")
📚 의사결정나무 회귀 모델 평가 결과 분석
- Mean Squared Error: 68.93506172839507
- MSE가 선형 회귀형 보다 높게 나왔기 때문에 선형 회귀형 보단 성능이 낮아 보인다고 판단함
- R2 Score: -0.20856078095584807
- 1과 가까울수록 예측을 잘한다는 것을 의미하지만, 음수값이 도출됨
랜덤 포레스트 모델 평가
# 랜덤 포레스트 모델 평가
## 다른 모델들과 비교했을 시, mse 지수가 가장 낮게 나옴
## 따라서, 가장 성능이 좋은 모델은 랜덤 포레스트라고 판단함
from sklearn.metrics import mean_squared_error, r2_score
# 성능 평가
mse = mean_squared_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
print(f"Mean Squared Error: {mse}")
print(f"R2 Score: {r2}")
📚 의사결정나무 회귀 모델 평가 결과 분석
- Mean Squared Error: 44.79970725925926
- MSE가 앞선 2개의 모델 중 가장 낮게 나옴
- R2 Score: 0.2145772001312125
- R^2 Score도 마찬가지로 가장 높게 나옴
요약
- 결측치가 포함된 행을 삭제하면 데이터 양이 줄어들어 이상치를 찾기 어렵다.
- CRIM, NOX, AGE의 적은 데이터 양만으로는 MEDV(주택 가격 예측)를 알기 힘듦
- 모델을 학습 시킬 때 데이터 양이 많을수록 R^2 Score은 1에 가까워지고, MAE, MSE들의 값은 작아질 것으로 추정됨
결론
- 데이터 전처리 작업부터 잘못하였기 때문에 모델 끼리 성능 평가를 하는 것은 의미없어 보임
- 데이터 전처리 작업을 처음부터 다시 해볼 생각임
'📄 과제' 카테고리의 다른 글
[LLM 필수 과제 1] OpenAI로 나만의 챗봇 만들기 (0) | 2025.01.24 |
---|---|
[Django] Django 개인 과제, Post 앱 (0) | 2025.01.21 |
[Django] Django 개인 과제, User 앱 (0) | 2025.01.20 |
[머신러닝_비지도 학습] 고객 세분화 분석 (0) | 2024.12.30 |