Coding Study/Machine Learning

부동산 가격 예측 및 원인 분석

김 도경 2024. 10. 30. 22:29
문제상황 및 데이터 살펴보기

 

  • 경제 보고서 레퍼런스
    - KB연구 보고서 : https://www.kbfg.com/kbresearch/index.do
    - 다양한 Domain에 대한 보고서가 Data 기반으로 작성되어 있음 (1인가구보고서, 부자보고서 등등)
    - 데이터로 증명된 보고서, 연구보고서뿐만 아니라 다양한 데이터가 있음
    - 이를 관련해서 프로젝트를 진행해도 좋음 : 모두 무료로 개방이 되어있고, 조회수가 높진 않으며 양질의 데이터가 있음
    - KB연구 보고서 외에도, 하나은행 금융경영연구소, 우리금융경영보고서, Gartner이라는 사이트가 있음

    - 데이터적으로 ~한 시기에 와있구나를 인식하는 게 좋음
    - 가격이 아니라 변동률! 률!!을 봐야한다. %가 중요함..!

  • 그래프를 볼 때, 담고 있는 정보 확인이 중요함!
    - 그래프 파악하는 것에 공부를 하는 것이 좋음 : ~율 같은 것 조심해서 보기
    - 판단은 거시적으로 하기 
    - 각각의 상관관계의 파악도 중요함!

  • 통계청 데이터보다 날것의 데이터를 보는 게 좋다! -> 가공도 쉽고, 보기가 좋음
    - 전체 보고서에 대한 리뷰를 진행하면 좋음
Data Spec Check

- Boston 데이터 : 심플한 데이터!!!! 접근만 해서, 부동산 데이터의 플로우를 접근 방법을 공부하는 것

- public data이기에 데이터가 모두 살아있음 : feature을 모두 제공함

- boston 내에서 많은 지역들이 있는데, 각각의 동에 해당된다고 생각하면 됨

- 각각의 동에 대한 값이 raw
- 데이터 노이즈 없음!

- feature 네임에 대한 가공 X : 확인하기
- 동에 대한 중앙값 = y값 : 집하나에 대한 정보가 아닌, 동에 대한 정보이다.

문제해결 프로세스 정의

 

  • 문제 상황 설명
    1. 부동산은 사실 지역 정보 외 정보들이 영향을 미치는 경우가 많음
    2. 주어진 데이터를 활용하여 최대한 지역을 대변할 수 있는 중요 변수로 데이터 셋을 구성
    3. 다양한 변수를 활용하여 Boston 집 값에 대한 중요한 변수를 찾아봄

  • Check Point
    - 세세한 것을 분석 할 때는 변수의 중요도도 중요하지만, 세밀하게 해석이 가능해야한다
    : 복잡한 모델을 사용해서 Black Box를 열어보고 해석을 진행함
        - Black Box를 까보는 것보다 그냥 Liner이나 DecisionTree같은 걸 진행해도 괜찮음!
        - 재산, 값을 예상할 때는 완벽한 인터프리터 머신러닝 이용이좋을 수도 있다.
       → 거시적인 관점보다 미시적인 관점에서 Local한 Point를 찾아봄
       → 거시적인 관점에서 집 값의 전체적인 방향을 예측하려면 다른 변수들이 많이 필요함
       → 거시적인 관점에서는 금리, 분양가구, 미분양가구 등 다양한 변수 필요
       → 이러한 거시적 관점은 세밀한 데이터 분석보다 Domain과 오랜시간 쌓아온 경험이 필요
              : 경험이 없다면, fact 기반, data기반의 보고서를 많이 읽어야한다!!! 진짜 많이 읽어서 경험으로 만들어라.
       → 데이터의 Trend, Seasonality 등 긴 시간 동안의 Pattern 분석 필요
       → 따라서, 당장의 집 값을 예측하는 것 보다 긴시간을 두고 Trend를 예측하는 것이 필요함
       → 이번 데이터는 거시적인 분석 후 미시적인 분석으로 세밀하게 집값에 영향을 미치는 인자들을 도출해보는 시간을 갖음

  • 기대효과
       → 세부적인 부동산 사항에서 중요 인자를 도출해봄
       → 부동산 가격 예측 및 원인분석에 대한 Insight와 노하우 축적

  • 성과측정
       → 부동산 구매 후 가격 상승분

  • 전체적인 Process
      1. 전체적인 Trend를 알수 있는 데이터를 수집함(국토부, 부동산123 등)
          → 살것인가 말것인가
      2. 거시적인 분석이 끝났으면 유망 지역을 선별하기 위해 미시적인 데이터 수집
          → 어떤지역을 살 것인가

가장 중요한 포인트!!!!!
* 거시적인 분석 -> 미시적인 분석

* 영향을 미치는 인자들의 도출을 확인해야함!

* 세밀한 데이터 분석보다는 fact, data 기반의 경험이 더 필요!

 

Data Info Check

데이터 정보에 대한 체크는 정말정말 중요하다!! 어떤 데이터를 담고 있는 걸 꼭 알아야한다!

  • 보스턴 주택 가격 데이터셋의 일부
    CRIM: 지역별 1인당 범죄율
            - 높은 범죄율은 주거 환경의 안전성에 대한 우려로 이어져 집값에 부정적인 영향을 줄 가능성
            - 범죄율이 높을수록 집값이 낮아지는 경향
    ZN: 25,000 평방피트 이상의 주거 구역 비율
            - 큰 집에 대한 비율 : 큰집이 얼마나 있는가
            - 고급 주거지가 많음을 의미
            -  집값에 긍정적인 영향
    INDUS: 비소매 업종 구역 비율
            - 상업 구역이 많을수록 주거 지역이 아닌 상업 지역으로 발전할 가능성이 크기 때문에, 집값에 부정적인 영향
            -  =상업지가 많은 지역의 경우, 주거 환경이 다소 열악
    CHAS: 찰스강 인접 여부
            - 강이랑 집이 닿아있으면 1, 아니면 0 : 1일수록 비쌀 확률이 높음
    NOX: 일산화질소 농도 (백만분의 1 단위)
            - 공기 오염도가 높을수록 주거 환경이 좋지 않기 때문에, 집값에 부정적인 영향
            - NOX 농도가 높을수록 주택 가격이 낮아질 수 있음
    RM: 주택당 평균 방 수
            - 방의 개수가 많을수록 집이 넓고 크다는 의미이기 때문에, 집값에 긍정적인 영향
    AGE: 1940년 이전에 건축된 소유 주택 비율
            - 오래된 집이 많을수록 집값에 부정적인 영향
            - 주택이 오래된 지역은 리모델링이나 유지보수가 필요한 경우가 많기 때문에, 집값이 낮아질 가능성
    DIS: 5개의 보스턴 직업센터까지의 접근성 지수
           - 도시 중심부로 부터 거리 : 접근지수가 높을수록 도심
           - 고용 센터와 가까운 지역은 출퇴근이 편리해 인기가 높으
           - 고용 센터와의 거리가 멀어질수록 집값에 부정적인 영향을 줄 가능성 있음
    RAD: 방사형 도로까지의 접근성 지수
           - 도시 중심부로 부터 거리 : 접근지수가 높을수록 도심
           - 고속도로 접근성이 좋을수록 교통이 편리해 집값에 긍정적인 영향
           - 지나치게 접근성이 좋으면 소음 등으로 인해 오히려 집값이 낮아질 수도 있음
    TAX: 10,000달러당 재산세율
           - 높을 수록 좋은 주택
           - 재산세율이 높으면 집을 소유하는 비용이 높아지기 때문에 집값에 부정적인 영향을 미칠 가능성
    PTRATIO: 지역별 학생-교사 비율
           - 학생-교사 비율이 낮을수록 교육 환경이 좋다고 평가되며, 이는 집값에 긍정적인 영향
    B: 1000(Bk-0.63)^2, 여기서 Bk는 자치시별 흑인의 비율을 말함.
           - 당시 주택 시장의 인종적 편견이 포함된 변수
           - 빼고 분석하는 게 좋을 듯함...
    LSTAT: 모집단의 하위계층의 비율(%)
          - 낮은 사회경제력 지위를 가진 인구 비율
          - 하위 계층 비율이 높을수록 집값에 부정적인 영향을 미칠 가능성이 큼
          - 낮은 사회경제적 지위를 가진 사람들이 많은 지역일수록 주거 환경에 대한 선호도가 낮음
  • 높을수록 좋은 가능성이 높은 것
    - ZN, CHAS, RM

  • 낮을수록 좋은 가능성이 높은 것
    - CRIM, NOX, AGE, DIS, PTRATIO, LSTAT

  • 애매한 것
    - INDUS, RAD, TAX, 
  • *** MEDV: 주택 가격 중앙값 (천 달러 단위)
       - 결과값! 
Data Info Check Code


- 데이터 셋을 불러옴

from sklearn import datasets
X, y = datasets.fetch_openml('boston', return_X_y=True)        # 보스턴 데이터셋을 불러온거

- sklearn에서 제공해주는 dataset을 사용

- 코드는 X와 y라는 두 데이터프레임 또는 시리즈 객체를 열 방향(axis=1)으로 병합하는 코드

data = pd.concat([X, y], axis=1)

 

   - pd.concat(): pandas 라이브러리의 concat 함수는 여러 개의 데이터프레임이나 시리즈를 지정된 축(axis) 방향으로 이어 붙임
   - [X, y]: concat 함수에 병합할 데이터프레임 또는 시리즈들을 리스트로 전달 
   - axis=1은 열 방향으로 병합하라는 의미

 

- 데이터 파악하기

data               # 데이터 보기
data.head()        # 앞에 5개 보기
data.describe()    # 데이터의 통계 요약 정보를 출력


- 데이터 정보 확인

data['INDUS'].unique().shape       # ['~~']열에 존재하는 고유 값의 개수

data[data['LSTAT'] == 37.97]       # '~~' 열의 값이 37.97인 행들을 필터링하여 반환
Counter(data['RAD'])              # '~~' 열의 값들을 카운트하여 각 카테고리별 빈도수

data['MEDV'][data['RAD'] == '24'].mean() # 'RAD' 값이 24인 행들의 'MEDV' 열의 평균값을 계산합니다.

np.mean(data['MEDV'][data['CHAS'] == '0'])   # 'CHAS' 값이 0인 행들의 'MEDV' 열의 평균값을 계산
np.mean(data['MEDV'][data['CHAS'] == '1'])   # 'CHAS' 값이 1인 행들의 'MEDV' 열의 평균값을 계산

 

- 데이터 셋 크기를 plot으로 확인

plt.figure(figsize=(15,5))
sns.histplot(data['MEDV'], bins=50)
plt.show()

 

Data Readliness Check

- 데이터가 분석이나 모델링에 적합한 상태인지 평가하는 프로세스
- 데이터가 모델링 및 분석에 적합한지 검증하고, 이후의 분석 과정이 원활하게 진행될 수 있도록 기본적인 데이터 품질을 확보하는 중요

 

  • 미싱 데이터 확인
    -데이터프레임의 각 열에 대해 결측치 비율을 계산하여 리스트에 저장하는 과정
col = []             # 열 이름을 저장할 빈 리스트를 초기화

missing = []
for name in data.columns:
    
    # Missing
    missper = data[name].isnull().sum() / data.shape[0]
    missing.append(round(missper, 4))

  - missing = []: 결측치 비율을 저장할 빈 리스트를 초기화
  - for name in data.columns: data 데이터프레임의 각 열(column)의 이름을 하나씩 가져옴

   - missper = data[name].isnull().sum() / data.shape[0]: 결측치 개수를 전체 행 개수로 나누어 해당 열의 결측치 비율을 계산
    - data[name].isnull().sum(): 해당 열의 결측치(null 값) 개수를 계산
    - data.shape[0]: 데이터프레임의 전체 행(row) 개수

missing.append(round(missper, 4)): 결측치 비율을 소수점 네 자리로 반올림하여 missing 리스트

  • 데이터프레임의 각 열에 대해 고유한 값의 개수를 계산하여 리스트에 저장하는 과정
col = []: 열 이름을 저장할 빈 리스트를 초기화

level = [] 
for name in data.columns:
    
    # Leveling
    lel = data[name].dropna()
    level.append(len(list(set(lel))))

    # Columns
    col.append(name)

- level = []: 각 열의 고유 값 개수를 저장할 빈 리스트를 초기화

- for name in data.columns: data 데이터프레임의 각 열(column)의 이름을 순회.
- lel = data[name].dropna(): 해당 열에서 결측치(null 값)를 제거하고 값이 있는 데이터만 남김.
- level.append(len(list(set(lel)))):
       - set(lel): 결측치를 제거한 열의 값들로 고유 값들만 포함하는 집합을 만듬
       - list(set(lel)): 집합을 리스트로 변환
       - len(list(set(lel))): 리스트의 길이를 계산하여 해당 열에 있는 고유 값의 개수를 구한 후 level 리스트에 추가
- col.append(name): 열의 이름을 col 리스트에 추가

 

  • 결측치 비율과 고유 값 개수에 대한 요약 정보를 담은 데이터프레임
    - summary에는 각 열의 이름, 결측치 비율, 고유 값 개수가 정리된 하나의 데이터프레임이 생성
summary = pd.concat([pd.DataFrame(col, columns=['name']), 
                     pd.DataFrame(missing, columns=['Missing Percentage']), 
                     pd.DataFrame(level, columns=['Unique'])], axis=1)

pd.DataFrame(col, columns=['name']): col 리스트를 데이터프레임으로 변환하며, 열 이름을 'name'으로 설정
                -> col 리스트에는 각 열의 이름이 들어감
pd.DataFrame(missing, columns=['Missing Percentage']):
          - missing 리스트를 데이터프레임으로 변환하며, 열 이름을 'Missing Percentage'로 설정.
          - missing 리스트에는 각 열의 결측치 비율이 저장
pd.DataFrame(level, columns=['Unique']):
          - level 리스트를 데이터프레임으로 변환하며, 열 이름을 'Unique'로 설정
          - level 리스트에는 각 열의 고유 값 개수가 저장
pd.concat([...], axis=1):
          - 세 개의 데이터프레임을 열 방향(axis=1)으로 이어붙여 하나의 데이터프레임을 생성

summary


-> summary는  name (이름)  / Missing Percentage (결측치 비율)  / Unique (고유 값 개수)를 갖는 표로 만들어짐!!!!

  • 데이터를 정리(cutting)하여 분석에 불필요한 열을 제거
drop_col = summary['name'][(summary['Unique'] <= 1) | (summary['Missing Percentage'] >= 0.8)]

data.drop(columns=drop_col, inplace=True)

print(">>>> Data Shape : {}".format(data.shape))

 

summary['name'][(summary['Unique'] <= 1) | (summary['Missing Percentage'] >= 0.8)]:

     - summary['Unique'] <= 1: Unique 열에서 고유 값이 1개 이하인 열을 찾음. (즉, 값이 하나만 존재하는 열을 의미)

     - summary['Missing Percentage'] >= 0.8: Missing Percentage 열에서 결측치 비율이 80% 이상인 열을 찾습니다.

     - | (or 연산자): 두 조건 중 하나라도 만족하는 열을 선택합니다.

-> drop_col 리스트에는 고유 값이 하나이거나 결측치 비율이 80% 이상인 열 이름들이 저장

 

data.drop(columns=drop_col, inplace=True):

   - data.drop(columns=drop_col): drop_col에 포함된 열들을 data 데이터프레임에서 삭제

   - inplace=True: 원본 data 데이터프레임에 바로 적용하여 변경사항을 저장

print(">>>> Data Shape : {}".format(data.shape)):

   - 열을 제거한 후 데이터프레임의 크기가 어떻게 변했는지 확인

  • 제거할 열의 개수를 반환
len(drop_col)

- drop_col 리스트에 저장된 열 이름의 수를 나타내며, 조건에 맞는 (즉, 고유 값이 하나이거나 결측치 비율이 80% 이상인) 열이 몇 개인지 확인

 

Feature Engineering

- feature의 category에 대해서 공부가 필요!

 

  • One Hotencoding
    - 범주형 데이터를 머신러닝 모델에 적합한 수치형 데이터로 변환
    - 범주형 변수의 각 고유 값을 별도의 열(column)로 변환하고, 해당 열에는 특정 행이 그 고유 값에 속하면 1, 그렇지 않으면 0을 할당
    - One Hotencoding의 효과 파악 : category 변수를 알아듣게 encoding 해주는 것!

    사용이유 : 머신러닝 모델은 수치 데이터를 입력으로 받기 때문에, 문자열이나 범주형 데이터를 그대로 사용할 수 없음
       -> "색상"이라는 범주형 변수가 Red, Green, Blue의 값을 가질 경우, 이를 숫자형 데이터로 변환해 모델에 입력
      - 이런 변환 과정에서 정보의 왜곡을 최소화하고 모델이 각 카테고리를 독립적으로 인식할 수 있도록 도움

    장점 : 각 범주를 독립적으로 처리할 수 있어 범주형 데이터가 모델에 적합하게 변환, 정보의 왜곡 없이 모델이 범주 간의 관계를 독립적으로 이해

  • 데이터의 타입을 확인
data.info()

- 데이터는 float나 int 데이터이어야 분석에 사용 가능 : 이 외의 데이터를 바꾸는 작업이 필요함
- 바꾸기 이전에 바꿔야할 컬럼 확인

 

  • 각 고유 값의 개수를 세어줌 : 
Counter(data['CHAS'])
Counter(data['RAD'])

- 값이 어떻게 분포되어 있는지 확인하여, 데이터 특성을 파악하고, 모델링 시 적절하게 처리할 수 있도록 돕기 위함

- CHAS : 이진 변수
       - 선형 회귀 모델은 연속형 수치 데이터로 처리되는 경우 범주형 변수의 특성을 이해하지 못할수도 있음
- RAD : 다중 범주형 변수( 다수의 고유 값을 가질 수 있는 변수)


-> 둘다 적절한 인코딩이 필요함

 

  • 특성(Feature)과 목표 변수(Target)로 분리하고, 일부 열을 모델이 잘 이해할 수 있도록 데이터 형 변환원-핫 인코딩을 적용
# X's & Y Split
Y = data['MEDV']
X = data.drop(columns=['MEDV']) 

# CHAS는 0또는1 밖에 없기 때문에 int로 변환 
# 그렇지 않으면 Liner regression에서 반응을 못함
X['CHAS'] = X['CHAS'].astype('int')

# RAD
X_dummy = pd.get_dummies(X, columns=['RAD'])

# RAD를 Int로 변형 시킨 후 정확도를 보기 위함
X['RAD'] = X['RAD'].astype('int')

 

Y : 주택 가격을 나타내는 목표 변수입니다. 여기서는 data 데이터프레임의 'MEDV' 열만 추출하여 Y에 저장

X: MEDV 열을 제외한 나머지 열들(즉, 모델의 특성(Features))로 구성된 데이터프레임

 

X['CHAS'] = X['CHAS'].astype('int')  : 'CHAS' 열을 정수(int)형으로 변환
      - CHAS 열은 값이 0과 1로만 구성된 범주형 변수로, 정수형(int)으로 변환하여 모델이 제대로 해석
      - CHAS가 float 형태라면, 모델(특히, 선형 회귀 모델)이 범주형임을 인식하지 못할 수 있음
   -> 정수형(int)으로 변환하여 명확히 범주형임을 표시해주는 역할

X_dummy = pd.get_dummies(X, columns=['RAD']) : 'RAD' 열에 원-핫 인코딩을 적용한 새로운 데이터프레임 X_dummy 생성

      - RAD 열은 범주형 변수를 나타냄
          - >pd.get_dummies()를 사용해 원-핫 인코딩을 적용하여 각 범주를 별도의 열로 변환. 

      - X_dummy는 원-핫 인코딩된 RAD 열을 포함한 데이터프레임으로, RAD의 고유 값들이 각각의 열로 분리

  • 원-핫 인코딩(One-Hot Encoding)
    - 범주형 데이터를 머신러닝 모델이 이해할 수 있는 수치 데이터로 변환하는 기법
    - 범주형 변수의 각 고유 값을 0과 1로 이루어진 이진 벡터로 변환하여 모델에 입력
    - 모델이 범주형 변수의 값을 독립적으로 처리하게 하여 수치형 변환의 오류를 방지  

 

X['RAD'] = X['RAD'].astype('int')   : 'RAD' 열을 정수(int)형으로 변환

       - 정확도 확인을 위한 RAD 변환: RAD 열의 원래 값을 정수형(int)으로 변환
       - 원-핫 인코딩을 적용하지 않은 X 데이터프레임에서도 RAD를 범주형 변수로 다루게 함

       - 원-핫 인코딩을 적용한 경우(X_dummy)와 그렇지 않은 경우(X)의 모델 정확도를 비교하기 위해 준비한 것

 

 

- Y: 목표 변수(MEDV)를 저장.

- X: 특성(Features)을 저장하며, CHAS와 RAD 열을 정수형으로 변환.

- X_dummy: X에서 RAD 열에 원-핫 인코딩을 적용하여 범주형 변수를 인코딩한 데이터프레임.

 

 

  • 잘 변환이 되었는지 확인
X_dummy.info()
X.info()

- 각각에서 RAD와 CHAS가 int64로 바뀐 것을 확인가능

 


 

  • 데이터셋을 훈련 데이터와 검증 데이터로 분할하여 학습 과정에서 모델 성능을 평가
    - 전체 데이터를 훈련과 검증 세트로 나누고, 각각의 개수를 출력하여 데이터 분할 결과를 확인하는 과정
idx = list(range(X.shape[0]))

train_idx, valid_idx = train_test_split(idx, test_size=0.2, random_state=112)

print(">>>> # of Train data : {}".format(len(train_idx)))
print(">>>> # of valid data : {}".format(len(valid_idx)))

- X.shape[0]: X 데이터프레임의 행 수. 이는 전체 데이터의 개수와 같음
  - list(range(X.shape[0])): 데이터의 인덱스를 0부터 전체 데이터 개수까지의 리스트로 생성

 

train_test_split: scikit-learn 라이브러리의 함수로, 데이터를 랜덤하게 훈련 세트검증 세트로 분리

   - idx: 전체 데이터의 인덱스 리스트를 입력으로 사용하여, 인덱스를 기준으로 데이터를 분할

   - test_size=0.2: 전체 데이터의 20%를 검증 세트로 할당하고, 나머지 80%는 훈련 세트로 사용
   - random_state=112: 분할 결과를 재현할 수 있도록 랜덤 시드를 설정하여, 항상 같은 방식으로 데이터를 분리

 

 

train_idx: 훈련 데이터의 인덱스 리스트.

    -> print(">>>> # of Train data : {}".format(len(train_idx)))   : Train data개수를 출력

valid_idx: 검증 데이터의 인덱스 리스트.

 

    -> print(">>>> # of valid data : {}".format(len(valid_idx)))    : valid data 개수를 출력

 

: 각 개수가 적당한지 확인하고 분석을 시작해야함

 

  • 선형 회귀 모델(Linear Regression)을 훈련 데이터로 구축하고, 이를 학습하는 단계
    - 훈련 데이터를 기반으로 선형 회귀 모델을 구축하고, 학습을 통해 최적의 회귀 계수를 계산
# Linear Regression 구축
model = sm.OLS(Y.iloc[train_idx], X.iloc[train_idx])
model_trained = model.fit()

 

sm.OLS(Y.iloc[train_idx], X.iloc[train_idx]):
  - OLS 회귀는 잔차의 제곱합을 최소화하는 회귀 모델을 찾는 방법
     - sm.OLS: statsmodels 라이브러리의 OLS(Ordinary Least Squares) 회귀 클래스를 사용하여 선형 회귀 모델을 생성
     - Y.iloc[train_idx]: 목표 변수 Y의 훈련 데이터 인덱스(train_idx)에 해당하는 행만 선택하여 모델에 사용
     - X.iloc[train_idx]: 특성 데이터 X의 훈련 데이터 인덱스(train_idx)에 해당하는 행만 선택하여 모델에 사용

model_trained = model.fit() : 모델 학습

 

    - fit() 메서드는 모델을 학습시킵니다.

    - 이 명령을 통해 model_trained는 훈련 데이터의 패턴을 학습한 훈련된 모델이 됨.

    - model_trained에는 모델의 회귀 계수, 절편, 통계적 요약 정보 등이 포함되며, 예측과 결과 해석을 위해 사용.

 

 

  • 훈련된 선형 회귀 모델에 대한 요약 통계 정보를 출력
print(model_trained.summary())

 

OLS(Ordinary Least Squares) 회귀 분석의 요약표

 

요약 부분 

- Dep. Variable: 종속 변수(목표 변수)

- R-squared: 결정 계수 : 모델이 데이터를 얼마나 잘 설명하는지를 나타냄
     - 높은 값일수록 모델이 데이터를 잘 설명
      0.958 : 모델이 데이터를 95.8% 설명한다는 뜻
- Adj. R-squared: 수정된 결정 계수로, R-squared를 설명 변수의 개수에 맞춰 조정한 값
     - 변수의 수가 많을 때, 높은 Adj. R-squared는 모델이 잘 맞는다는 것

- F-statisticProb (F-statistic): 모델 전체가 통계적으로 유의미한지를 검정하는 지표
      - F-statistic: 690.7
      - p-값(Prob): 2.38e-260   -> 0.05 이하일 경우 모델 전체가 통계적으로 유의미, 작을수록 좋은 값

- Log-Likelihood: 모델이 주어진 데이터를 얼마나 잘 설명하는지 나타내는 로그 가능도
      - 값이 클수록 모델이 데이터를 잘 설명하는 경향
      - 절대적인 임계값이 없고, 다른 모델들과 비교하여 Log-Likelihood 값이 클수록 더 나은 모델로 평가
- AIC (Akaike Information Criterion): 모델의 성능과 복잡성을 함께 고려한 지표
      - 값이 작을수록 모델이 데이터를 더 잘 설명하고, 모델이 더 단순
      - 절대적인 임계값 없음, AIC 값 차이가 2 이상이면 두 모델 간의 성능 차이가 있다고 봄
- BIC (Bayesian Information Criterion): AIC와 비슷하지만, 모델에 사용된 변수 수에 대해 더 엄격한 페널티를 부여
      - 작을수록 좋은 값: 모델을 비교할 때 사용
      - 절대적인 임계값 없음, BIC 값 차이가 2 이상이면 두 모델 간의 성능 차이가 있다고 봄

Coefficients Table (회귀 계수 테이블)

- coef : 각 특성의 회귀 계수
    - 값이 클수록 MEDV에 미치는 영향이 큼.
    - 양수이면 해당 특성이 MEDV에 양의 영향을 주고, 음수이면 음의 영향을 줌
- std err : 각 회귀 계수의 표준 오차
    - 값이 작을수록 신뢰도가 높아집니다.
- t : 각 계수가 0이 아닌지를 검정하기 위한 t-값
     - 큰 값일수록 유의미할 가능성 높음
- P>|t| (p-값)  : p-value, 회귀 계수가 0일 가능성
     - P>|t| 값이 0.05 미만인 특성들은 MEDV에 유의미한 영향을 미칩
- [0.025, 0.975]  : 95% 신뢰 구간
        - 이 구간에 0이 포함되지 않으면, 해당 계수가 통계적으로 유의미할 가능성이 높음

 

Diagnostic Information
Omnibus, Prob(Omnibus), Jarque-Bera (JB), Prob(JB): 잔차(오차)의 정규성을 검정하는 통계량
    - p-값이 작으면(일반적으로 0.05보다 작을 때) 잔차가 정규분포를 따르지 않는다는 의미입니다.

Durbin-Watson: 잔차의 자기상관을 확인하는 지표
    - 2에 가까울수록 자기상관이 적어 모델에 좋음. 이 값이 1.952로, 이상적에 가까움

Condition Number: 다중공선성(multicollinearity)의 정도
    - 일반적으로 1000을 넘으면 다중공선성 문제가 있을 가능성이 있음
     -> 이 경우 8.57e+03으로, 다중공선성 문제를 의심해볼 수 있다.

 

데이터 summary를 한 뒤에 봐야할 것!!!
요약
   - 유의미한 특성: P>|t| 값이 0.05 미만인 특성들은 MEDV에 유의미한 영향을 미침
   - 모델 적합도: R-squared와 Adjusted R-squared가 높아 모델의 설명력이 좋음.
   - F-statistic 유의미성: p-값이 거의 0에 가까워 모델 전체가 통계적으로 유의미함.
   - 다중공선성 문제: Condition Number가 높은 점을 고려해 일부 특성의 다중공선성을 점검할 필요가 있음.

 

 

  • 원-핫 인코딩된 데이터를 사용하여 선형 회귀 모델을 구축하고 학습하는 과정
    - 원-핫 인코딩을 적용한 경우와 적용하지 않은 경우 모델 성능을 비교하여, RAD 변수의 범주형 특성이 주택 가격 예측에 어떤 영향을 미치는지 평가
model_dummy = sm.OLS(Y.iloc[train_idx], X_dummy.iloc[train_idx])
model_dummy_trained = model_dummy.fit()

model_dummy = sm.OLS(Y.iloc[train_idx], X_dummy.iloc[train_idx])

   - sm.OLS: statsmodels 라이브러리의 Ordinary Least Squares(OLS) 회귀 클래스를 사용하여 선형 회귀 모델을 생성.

   - Y.iloc[train_idx]: 목표 변수 Y에서 훈련 데이터 인덱스(train_idx)에 해당하는 데이터만 선택하여 사용.

   - X_dummy.iloc[train_idx]: 원-핫 인코딩된 특성 데이터 X_dummy에서 훈련 데이터 인덱스에 해당하는 데이터만 선택하여 사용

 

model_dummy_trained = model_dummy.fit()

 

   - model_dummy.fit(): 모델을 학습시키는 메서드로, 주어진 훈련 데이터를 사용하여 최적의 회귀 계수를 찾음.

   - model_dummy_trained: 학습이 완료된 모델로, model_dummy_trained에는 회귀 계수, 절편, 통계 정보 등이 포함.
         -> 이 모델을 통해 예측을 수행하거나 회귀 계수를 해석할 수 있음.

 

 

  • 원-핫 인코딩된 데이터를 사용하여 학습한 선형 회귀 모델의 요약 통계 정보를 출력

 

print(model_dummy_trained.summary())

 


  • 훈련 데이터와 테스트 데이터에 대한 예측값을 생성하는 부분
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error

#y_train_pred = model_trained.predict(X.iloc[train_idx])
y_dummy_train_pred = model_dummy_trained.predict(X_dummy.iloc[train_idx])

#y_test_pred = model_trained.predict(X.iloc[valid_idx])
y_dummy_test_pred = model_dummy_trained.predict(X_dummy.iloc[valid_idx])

 

- model_dummy_trained.predict(X_dummy.iloc[train_idx]): 원-핫 인코딩된 훈련 데이터를 사용해 예측값을 생성
- y_dummy_train_pred는 훈련 데이터에 대한 예측값으로, 모델이 훈련 데이터에서 얼마나 잘 학습했는지 평가할 때 사용
- y_dummy_test_pred는 검증 데이터에 대한 예측값으로, 모델이 새로운 데이터에 대해 얼마나 잘 일반화되는지 평가할 때 사용

  • 훈련 데이터에 대한 모델 성능 평가를 다양한 지표로 출력
print('Training MSE: {:.3f}'.format(mean_squared_error(Y.iloc[train_idx], y_train_pred)))
print('Training RMSE: {:.3f}'.format(np.sqrt(mean_squared_error(Y.iloc[train_idx], y_train_pred))))
print('Training MAE: {:.3f}'.format(mean_absolute_error(Y.iloc[train_idx], y_train_pred)))
print('Training R2: {:.3f}'.format(r2_score(Y.iloc[train_idx], y_train_pred)))

 - MSE (Mean Squared Error): 평균 제곱 오차로, 예측값과 실제 값의 차이를 제곱한 후 평균을 계산
        - 값이 작을수록 예측값이 실제 값에 가깝다
        - 큰 오차에 더 큰 페널티를 부여하므로, 오차를 최소화하는 데 유리
        - 제곱을 통해 계산하기 때문에 이상치(outliers)에 매우 민감
 - RMSE (Root Mean Squared Error): 평균 제곱 오차의 제곱근
        - MSE와 비슷한 의미지만, RMSE는 오차의 단위가 실제 데이터의 단위와 동일하므로 해석이 더 직관적
        - MSE와 같이 큰 오차에 대해 더 큰 페널티를 부여하면서도, 원래 데이터 단위로 해석
        - MSE와 마찬가지로 이상치에 민감

 - MAE (Mean Absolute Error): 평균 절대 오차로, 예측값과 실제 값 간의 차이의 절대값을 평균 
        - MSE보다 이상치의 영향을 덜 받으며, 값이 작을수록 예측이 실제 값에 가까움을 의미
        - MSE와 RMSE에 비해 이상치에 덜 민감합니다. 이상치가 있더라도 MAE 값이 크게 변하지 않음
        - 큰 오차와 작은 오차를 동일하게 취급하므로, 큰 오차에 대한 페널티가 작아질 수 있음

 - R² Score: 결정 계수로, 모델이 데이터의 변동성을 얼마나 설명하는지를 나타내는 지표
        - 값이 1에 가까울수록 모델이 데이터를 잘 설명
        - 모델이 데이터를 얼마나 잘 설명하는지 직관적으로 알 수 있음. 여러 모델을 비교할 때 유용한 기준
        - R² 값이 높다고 해서 반드시 좋은 모델인 것은 아님
   - > 특히 과적합(overfitting) 모델의 경우 R² 값이 높을 수 있지만, 테스트 데이터에 대한 일반화 성능은 떨어질 수 있음
   : 조정된 R² (Adjusted R²)을 사용하는 것이 좋다.

 

  • 검증 데이터(테스트 데이터)에 대한 모델 성능 평가 지표를 출력
print('Testing MSE: {:.3f}'.format(mean_squared_error(Y.iloc[valid_idx], y_test_pred)))
print('Testing RMSE: {:.3f}'.format(np.sqrt(mean_squared_error(Y.iloc[valid_idx], y_test_pred))))
print('Testing MAE: {:.3f}'.format(mean_absolute_error(Y.iloc[valid_idx], y_test_pred)))
print('Testing R2: {:.3f}'.format(r2_score(Y.iloc[valid_idx], y_test_pred)))
  • 훈련 데이터에 대한 원-핫 인코딩된 모델의 성능을 평가하는 지표
print('Training MSE: {:.3f}'.format(mean_squared_error(Y.iloc[train_idx], y_dummy_train_pred)))
print('Training RMSE: {:.3f}'.format(np.sqrt(mean_squared_error(Y.iloc[train_idx], y_dummy_train_pred))))
print('Training MAE: {:.3f}'.format(mean_absolute_error(Y.iloc[train_idx], y_dummy_train_pred)))
print('Training R2: {:.3f}'.format(r2_score(Y.iloc[train_idx], y_dummy_train_pred)))
  • 검증 데이터에 대한 원-핫 인코딩된 모델의 성능을 평가하는 지표
print('Testing MSE: {:.3f}'.format(mean_squared_error(Y.iloc[valid_idx], y_dummy_test_pred)))
print('Testing RMSE: {:.3f}'.format(np.sqrt(mean_squared_error(Y.iloc[valid_idx], y_dummy_test_pred))))
print('Testing MAE: {:.3f}'.format(mean_absolute_error(Y.iloc[valid_idx], y_dummy_test_pred)))
print('Testing R2: {:.3f}'.format(r2_score(Y.iloc[valid_idx], y_dummy_test_pred)))

 

Modeling
  • Boston Housing 데이터셋을 불러와 pandas DataFrame으로 변환하는 작업
from sklearn import datasets

X, y = datasets.fetch_openml('boston', return_X_y=True)

data = pd.concat([X, y], axis=1)

- fetch_openml 함수로 데이터를 가져온 후, pd.concat을 사용하여 특성과 목표 변수

X, y = datasets.fetch_openml('boston', return_X_y=True)
   - boston이라는 데이터셋을 OpenML에서 다운로드
   - X : 특성(Features) 데이터를 의미하며, 보스턴 주택 데이터의 입력 변수들로 구성
   - y : 목표 변수(Target) 데이터로, 주택 가격의 중앙값(MEDV)을 나타냄
   - return_X_y=Tru : 데이터셋을 X와 y로 나누어 반환하겠다는 의미

data = pd.concat([X, y], axis=1)
  - pd.concat : X와 y를 열 방향(axis=1)으로 연결하여 하나의 DataFrame으로 만듬
  - data : 이제 X와 y가 하나의 데이터프레임으로 결합되었으며, data에는 보스턴 주택 데이터의 특성과 목표 변수가 포함

  • 회귀 문제를 분류 문제로 변환하기 위해 MEDV (주택 가격의 중앙값) 변수를 상위 40%와 하위 40%로 나누는 작업
# Problem Convert
# Regression to Classifcation
# 상위 40% (Class 1)과 하위 40% (Class 0)
per_60 = np.percentile(data['MEDV'], 60)
per_40 = np.percentile(data['MEDV'], 40)

print(">>>> 60 Percentile : {}".format(per_60))
print(">>>> 40 Percentile : {}".format(per_40))

- np.percentile(data['MEDV'], 60): MEDV 값에서 60번째 백분위수(퍼센타일)에 해당하는 값을 계산
     -  per_60는 MEDV의 상위 40%에 해당하는 경계값
- np.percentile(data['MEDV'], 40): MEDV 값에서 40번째 백분위수에 해당하는 값을 계산
    - per_40는 MEDV의 하위 40%에 해당하는 경계값

  • 데이터의 분포를 시각화
# Y Plotting
plt.figure(figsize=(15,5))

sns.histplot(data['MEDV'], bins=25)

plt.show()

- plt.figure(figsize=(15,5)): 그래프의 크기를 설정

      - 가로로 넓은 형태의 히스토그램이 생성되어 데이터의 분포를 더 잘 관찰

- sns.histplot(data['MEDV'], bins=25)
   - sns.histplot: seaborn 라이브러리의 히스토그램 생성 함수

   - data['MEDV']: MEDV 열을 입력으로 하여 MEDV 값의 분포를 시각화
   - bins=25: 히스토그램의 막대(bin) 개수를 25개로 설정하여 데이터의 세부 분포

 - plt.show(): 생성한 히스토그램을 화면에 출력

 

  • 통계치 계산
data['MEDV'].mean()            # MEDV 열의 평균값을 계산

np.percentile(data['MEDV'], 50)    # MEDV의 중앙값을 계산
  • 회귀 문제를 분류 문제로 변환하고, 주택 가격이 높은 그룹과 낮은 그룹을 구분
# Data Selection
data = data[(data['MEDV'] >= per_60) | (data['MEDV'] <= per_40)]

data.reset_index(inplace=True, drop=True)

print('Data shape : {}'.format(data.shape))

 

- data['MEDV'] >= per_60: MEDV 값이 상위 40% 이상인 데이터만 선택

- data['MEDV'] <= per_40: MEDV 값이 하위 40% 이하인 데이터만 선택

- | 연산자: 상위 40% 또는 하위 40%에 속하는 데이터를 모두 선택하기 위해 OR 연산을 수행

- > MEDV가 상위 40% 또는 하위 40%에 속하는 데이터만 남기고, 나머지는 제거됩니다. 이로써 MEDV 값이 중간 수준에 해당하는 데이터는 제외

 

- data.reset_index(inplace=True, drop=True): 필터링 후 인덱스를 다시 설정하여 연속된 인덱스

       - inplace=True: 기존 데이터프레임을 직접 수정

       - drop=True: 기존의 인덱스 열을 삭제하고, 새 인덱스를 생성

 

- data.shape: 새로운 data 데이터프레임의 행(row)과 열(column)의 개수를 확인. 이를 통해 필터링 후 데이터의 크기를 파악

 

  • 필터링된 MEDV 열의 값들을 히스토그램으로 시각화
# Y Plotting
plt.figure(figsize=(15,5))
sns.histplot(data['MEDV'], bins=50)
plt.show()

- plt.figure(figsize=(15,5)): 그래프의 크기를 설정하여, 가로로 넓고 세로로 얇은 형태(15x5 인치)의 그래프를 생성

sns.histplot(data['MEDV'], bins=50)
- sns.histplot: seaborn의 히스토그램 함수로, MEDV 열의 분포를 시각화

- data['MEDV']: MEDV 열을 선택하여, 주택 가격이 필터링된 후 상위 40%와 하위 40%로 나뉜 상태에서의 분포
- bins=50: 히스토그램 막대(bin) 수를 50개로 설정하여 데이터의 분포를 세부적으로 나타냄
       - 작은 변동까지 확인할 수 있어 데이터의 분포 특성을 더 잘 이해

  • 회귀 문제를 이진 분류 문제로 변환하여, MEDV 값에 따라 상위 40%와 하위 40%를 분류하는 작업
data['Label'] = 3

data['Label'].iloc[np.where(data['MEDV'] >= per_60)[0]] = 1
data['Label'].iloc[np.where(data['MEDV'] <= per_40)[0]] = 0

print("Unique Label : {}".format(set(data['Label'])))

- data['Label'] = 3
  - 새로운 Label 열을 추가하고, 모든 값에 3을 할당하여 초기화합니다. 이렇게 하면 이후에 1과 0 레이블을 조건에 따라 쉽게 설정

- 상위 40%에 해당하는 행의 Label을 1로 설정
- 하위 40%에 해당하는 행의 Label을 0으로 설정
-> Label 열에 {0, 1}이 잘 할당되었는지 확인합니다. 출력 결과가 {0, 1}이라면 레이블 할당이 제대로 된 것

  • 데이터 전처리 및 훈련/검증 데이터 분할을 수행
# Category 처리하기
data['CHAS'] = data['CHAS'].astype('int')
data = pd.get_dummies(data, columns=['RAD'])

# Data Split
Y = data['Label']
X = data.drop(columns=['MEDV', 'Label'])
idx = list(range(X.shape[0]))
train_idx, valid_idx = train_test_split(idx, test_size=0.2, random_state=119)

print(">>>> # of Train data : {}".format(len(train_idx)))
print(">>>> # of valid data : {}".format(len(valid_idx)))

- 범주형 데이터 처리
 1. CHAS 열을 정수형으로 변환: CHAS는 이진 변수(0 또는 1)를 나타내므로, 정수형으로 변환하여 모델이 잘 인식
 2. RAD 열에 원-핫 인코딩 적용: pd.get_dummies를 사용하여 RAD 열의 각 고유 값을 별도의 열로 변환

데이터 분할
- 목표 변수 Y 설정: Label 열을 목표 변수로 설정
- 특성 변수 X 설정: MEDV와 Label 열을 제외한 나머지 열을 특성 변수로 설정

인덱스 생성 및 데이터 분할
- idx = list(range(X.shape[0]))   : 전체 데이터 인덱스 생성: X의 행 수만큼 인덱스를 생성
- train_idx, valid_idx = train_test_split(idx, test_size=0.2, random_state=119)
    - train_test_split을 사용하여 데이터 분할: 전체 인덱스를 훈련 세트와 검증 세트로 나눔
    - test_size=0.2: 전체 데이터의 20%를 검증 세트로 사용
    - random_state=119: 동일한 결과를 재현할 수 있도록 랜덤 시드를 설정

훈련 데이터와 검증 데이터의 크기 출력: 

    - print(">>>> # of Train data : {}".format(len(train_idx)))
    - print(">>>> # of valid data : {}".format(len(valid_idx))) 

       -> 각각의 데이터 개수를 출력하여 데이터 분할이 제대로 되었는지 확인

  • 결정 트리 모델의 최대 깊이(max_depth)를 2에서 10까지 변경하며 모델 성능을 평가하는 작업

# Parameter Searching ==> Depth 2 ~ 10
for i in range(2,11,1):
    print(">>>> Depth {}".format(i))

    model = DecisionTreeClassifier(max_depth=i, criterion='entropy')
    model.fit(X.iloc[train_idx], Y.iloc[train_idx])

    # Train Acc
    y_pre_train = model.predict(X.iloc[train_idx])
    cm_train = confusion_matrix(Y.iloc[train_idx], y_pre_train)
    print("Train Confusion Matrix")
    print(cm_train)
    print("Train Acc : {}".format((cm_train[0,0] + cm_train[1,1])/cm_train.sum()))

    # Test Acc
    y_pre_test = model.predict(X.iloc[valid_idx])
    cm_test = confusion_matrix(Y.iloc[valid_idx], y_pre_test)
    print("Valid Confusion Matrix")
    print(cm_test)
    print("TesT Acc : {}".format((cm_test[0,0] + cm_test[1,1])/cm_test.sum()))

 

# Parameter Searching ==> Depth 2 ~ 10
  - max_depth를 2부터 10까지 설정하여 결정 트리 모델을 학습하고 평가
  - DecisionTreeClassifier: 결정 트리 분류 모델을 생성. 여기서 max_depth는 트리의 최대 깊이를 설정하며, criterion='entropy'는 정보 이득을 기준으로 분할을 수행.
  - model.fit(...): 훈련 데이터를 사용하여 모델을 학습합니다.

# Train Acc
   - 훈련 데이터에 대한 예측: X의 훈련 데이터로 예측을 수행
   - 혼동 행렬 계산: confusion_matrix를 사용하여 훈련 데이터의 실제 레이블과 예측된 레이블을 비교.
   - 혼동 행렬을 출력하고, 정확도를 계산하여 출력


# Test Acc

   - 검증 데이터에 대한 예측: X의 검증 데이터로 예측을 수행
   - 혼동 행렬을 계산하고 출력하며, 정확도를 계산하여 출력

 

 

Model evaluation and Summary

Regression -> classification : regression은 정답이 무한대이지만 classfication은 범주화!

  • 결정 트리 모델을 최대 깊이 5로 설정하여 학습하는 과정
# Depth가 깊어질 수록 정확도는 높게 나오지만 해석력에 대한 가독성을 위해 Depth 4를 선택함
model = DecisionTreeClassifier(max_depth=5, criterion='entropy')
model.fit(X.iloc[train_idx], Y.iloc[train_idx])

model = DecisionTreeClassifier(max_depth=5, criterion='entropy')

 - DecisionTreeClassifier: 결정 트리 분류 모델을 생성
 - max_depth=5: 트리의 최대 깊이를 5로 설정하여 모델의 복잡성을 제한.
        - 깊이가 너무 깊어지면 모델이 과적합(overfitting)될 수 있으니 유의
- criterion='entropy': 분할 기준으로 엔트로피를 사용합니다. 이는 정보 이득을 기준으로 데이터를 분할하는 방법.

 

model.fit(X.iloc[train_idx], Y.iloc[train_idx])

- X.iloc[train_idx]와 Y.iloc[train_idx]를 사용하여 모델을 훈련 데이터로 학습

- X의 입력에 대해 Y의 클래스(상위 40% 또는 하위 40%)를 예측

 

  • 결정 트리의 각 분기와 노드의 특성을 시각적으로 확인
from sklearn import tree
# Creating the tree plot
# Creating the tree plot (left = True, Right = False)
tree.plot_tree(model, filled=True, feature_names=X.columns, class_names = ['BAD', 'GOOD'])

plt.rcParams['figure.figsize'] = [40,10]

tree.plot_tree(model, filled=True, feature_names=X.columns, class_names = ['BAD', 'GOOD'])

- tree.plot_tree: sklearn.tree 모듈의 함수를 사용하여 결정 트리 모델을 시각화
- model: 학습된 결정 트리 모델을 입력으로 사용
- filled=True: 각 노드의 색을 채워서 노드의 클래스 비율에 따라 색상이 다르게 표현
- feature_names=X.columns: 각 노드에서 사용되는 특성의 이름을 지정하여 그래프에 표시.
- class_names=['BAD', 'GOOD']: 클래스 레이블을 설정합니다. 여기서는 상위 40%를 'GOOD', 하위 40%를 'BAD'로 명명

 

plt.rcParams['figure.figsize'] = [40,10]

   - plt.rcParams['figure.figsize']: 그래프의 크기를 설정
   - 가로 크기를 40인치, 세로 크기를 10인치로 지정하여 넓은 트리 시각화


 

1차 스터디

공부방법
- 다른 기법도 공부해보기 ; 앙상블 기법 등등
- 다른 데이터에도 적용 시켜보기 : 캘리포니아 데이터
- ML 플로우 관련 공부하기
- 성능지표, 평가지표에 대해서 공부하기

'Coding Study > Machine Learning' 카테고리의 다른 글

Computer Vision_Classification  (4) 2024.12.23