230419 / BSA05. Housing 데이터로 전처리, 회귀 모델 적용, 시각화

BSA05-Housing-Project.ipynb

 

필요한 패키지

import pandas as pd
import pyspark
from pyspark.sql import SparkSession

from sklearn.preprocessing import OneHotEncoder
from sklearn.impute import KNNImputer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
import statsmodels.api as sm

import matplotlib.pyplot as plt
import folium

 

스파크 세션 시작 및 데이터 불러오기

스파크 = SparkSession.builder.appName('Test').getOrCreate()
스파크.conf.set("spark.sql.execution.arrow.enabled","true")

sparkDF = 스파크.read.csv("hdfs://localhost:9000/Spark/housing.csv", header=True, inferSchema="true")
sparkDF.show()


# Spark를 이용해 Hadoop data를 읽은 후 Pandas로 변환
# 판다스의 데이터프레임으로 만들고 '주택'으로 저장함
주택 = sparkDF.toPandas()  
주택.info()
주택.describe()

 

※ 스파크 세션이 연결되지 않는 경우

import os
import sys
os.environ['JAVA_HOME'] = "C:\Java"
os.environ['SPARK_HOME'] = "C:\spark-3.2.3"
os.environ['PYLIB'] = "C:\spark-3.2.3\python\lib"
sys.path.insert(0,os.environ['PYLIB']+"\py4j-0.10.9.5-src.zip")
sys.path.insert(0,os.environ['PYLIB']+"\pyspark.zip")

 

데이터 전처리

1. 범주형 데이터 → 더미변수 변환

# 결측값이 얼마나 있는지 확인 (True=1 : 결측값 / False=0 : 결측값이 아님)
주택.isna().sum()  

# 결측값인 데이터들만 보이도록
주택[주택["total_bedrooms"].isna()]

# 'ocean_proximity'를 더미변수로
가변수처리 = OneHotEncoder()
해변가변수 = 가변수처리.fit_transform(주택[['ocean_proximity']])
해변가변수.toarray()

# numpy의 array를 dataframe으로 변환
가변수DF = pd.DataFrame(해변가변수.toarray(),columns=["I1","I2","I3","I4","I5"],index=주택.index)
가변수DF.head()

# 기존의 '주택' 데이터프레임과 '가변수' 데이터프레임을 결합
# 공통된 변수명이 없으므로 concat (axis=1)로 좌우(side-by-side)로 결합
# 기존의 '주택' 데이터프레임에 있는 'ocean_proximity'열 (마지막 열)은 '가변수' 데이터프레임의 정보와 겹치므로 제외하고 결합
새주택  = pd.concat([주택.iloc[:,0:-1],가변수DF],axis=1)
새주택.iloc[[290,341,538],]

 

2. 결측값 대체

1) k-nn

# weights="uniform" : 거리를 고려하지 않고 거리가 가까운 5개 데이터의 평균값으로 대체
# weights="distance" : 거리를 고려해 가까운 데이터에 가중치를 크게
근접대체 = KNNImputer(n_neighbors=5, weights="uniform")

# output은 pandas의 데이터프레임으로
근접대체.set_output(transform = 'pandas')
대체주택 = 근접대체.fit_transform(새주택)
대체주택.isna().sum()

# total_bedrooms = 242.6, 276.4, 1194.4, ... : 실수 형태로 대체됨 (평균 계산했기 때문)
# 'median_house_value'의 값이 크기 때문에 이 변수의 영향을 많이 받음
대체주택.iloc[[290,341,538],]

 

2) 표준화 후에 k-nn

평균 = 새주택.mean()
표준편차 = 새주택.std()
표준주택 = (새주택-평균)/표준편차

근접대체 = KNNImputer(n_neighbors=5, weights="uniform")
근접대체.set_output(transform = 'pandas')
표준대체주택 = 근접대체.fit_transform(표준주택)
표준대체주택.head()

# 표준화의 역작업으로 원래 스케일로 되돌림
대체주택 = 표준대체주택*표준편차+평균
대체주택.iloc[[290,341,538],]

 

회귀 모델 적용

1. sklearn - LinearRegression

# 설명변수, 반응변수 분리
X = 대체주택.drop("median_house_value",axis=1)  # 설명변수
y = 대체주택["median_house_value"]  # 반응변수

# 학습데이터, 검증데이터 분리
# test data의 비율이 30%이고, train data의 비율이 70%
train_X, test_X, train_y, test_y = train_test_split(X,y, test_size = 0.3, random_state=316)

# 선형회귀 모델 적합
선형회귀 = LinearRegression()
선형회귀.fit(train_X,train_y)  # train data로 베타 추정

print("절편(bias):",선형회귀.intercept_)
print("회귀계수(weights):",선형회귀.coef_)

# train data로 적합시킨 모형으로 얻은 베타헷으로 test data에 적용해 예측값 얻음
예측값 = 선형회귀.predict(test_X)
print(test_y[0:10])
print(예측값[0:10])


# train data의 y와 test data의 y를 결합시켜 값의 분포를 확인하기 위해 두 데이터프레임 결합
# 데이터프레임의 한 열은 Series 형태임
비교 = pd.concat([pd.Series(list(test_y)),pd.Series(예측값)],axis=1)
비교.head()

# 시각화해서 그래프로 비교
# obs(관측값), pred(예측값) 컬럼명 설정
비교.columns = ["obs","pred"]

# scatterplot을 그림
# alpha=0.2 : 연하게
비교.plot(kind='scatter',x='obs',y='pred',alpha=0.2,figsize=(12,10))
plt.show()

 

2. statsmodels - OLS(ordinary least square)

# with statsmodels
# statsmodels에는 따로 옵션을 지정하지 않으면 베타0가 없음
# add_constant : 베타0도 계산하도록
train_X1 = sm.add_constant(train_X) # adding a constant

# OLS (ordinary least square) 방법
model = sm.OLS(train_y, train_X1).fit()  # 베타헷 추정

# test data에도 베타0를 추가하는 작업이 필요함
test_X1 = sm.add_constant(test_X)

# 적합한 모델로 test data의 값 예측
pred = model.predict(test_X1) 

비교 = pd.concat([pd.Series(list(test_y)),pd.Series(list(pred))],axis=1)
비교.head()

print_model = model.summary()
print(print_model)  # 모든 변수가 유의함

 

데이터 시각화

주택.plot(kind='scatter',x='longitude',y='latitude',alpha=0.1,figsize=(12,10))
plt.show()

주택.plot(kind='scatter',x='longitude',y='latitude',alpha=0.4,s=주택['population']/100,label='population',
             figsize=(12,8),c='median_house_value',cmap=plt.get_cmap('jet'),colorbar=True,sharex=False)
plt.legend()
plt.show()

 

folium 시각화

위도평균 = 주택["latitude"].mean()
경도평균 = 주택["longitude"].mean() 
print("평균: 위도",위도평균,"경도",경도평균)

위도 = list(주택["latitude"])
경도 = list(주택["longitude"])
가격 = list(5*주택["median_house_value"]/주택["median_house_value"].max())

지도표시 = folium.Map(location=[위도평균,경도평균],zoom_start=9)
지도표시


# 주택 위치 CircleMarker로 표시
for i in range(0,100):
    folium.CircleMarker([위도[i], 경도[i]]).add_to(지도표시)
지도표시


# 주택 가격에 따라 CircleMarker 크기 다르게 위치 표시
지도표시 = folium.Map(location=[위도평균,경도평균],zoom_start=9)
for i in range(0,1000):
    folium.CircleMarker([위도[i], 경도[i]],radius=가격[i]).add_to(지도표시)
지도표시


# 
지도표시 = folium.Map(location=[위도평균,경도평균],zoom_start=9)
for i in range(0,1000):
    folium.CircleMarker([위도[i], 경도[i]],radius=가격[i],opacity=0.3,fill=True,fill_opacity=0.2).add_to(지도표시)
지도표시


# 
지도표시 = folium.Map(location=[위도평균,경도평균],zoom_start=9)
for i in range(0,1000):
    folium.CircleMarker([위도[i], 경도[i]],radius=가격[i],opacity=0.3,fill=True,fill_opacity=0.2).add_to(지도표시)
지도표시


# 'ocean_proximity' 그룹별로 색깔 다르게 
주택위치 = list(주택['ocean_proximity'])
위치 = list(주택['ocean_proximity'].values)

지도표시 = folium.Map(location=[위도평균,경도평균],zoom_start=9)
for i in range(0,1000):
    if 주택위치[i]==위치[0]: 색깔 = "red"
    elif 주택위치[i]==위치[1]: 색깔 = "orange"
    elif 주택위치[i]==위치[2]: 색깔 = "yellow"
    elif 주택위치[i]==위치[2]: 색깔 = "green"
    else: 색깔 = "blue"    
    folium.CircleMarker([위도[i], 경도[i]],radius=가격[i],color=색깔, opacity=0.3,fill=True,fill_opacity=0.2).add_to(지도표시)
지도표시