- 데이터: http://lib.stat.cmu.edu/datasets/boston
- 데이터에 대한 설명

  • CRIM     per capita crime rate by town
  • ZN       proportion of residential land zoned for lots over 25,000 sq.ft.
  • INDUS    proportion of non-retail business acres per town
  • CHAS     Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)
  • NOX      nitric oxides concentration (parts per 10 million)
  • RM       average number of rooms per dwelling
  • AGE      proportion of owner-occupied units built prior to 1940
  • DIS      weighted distances to five Boston employment centres
  • RAD      index of accessibility to radial highways
  • TAX      full-value property-tax rate per $10,000
  • PTRATIO  pupil-teacher ratio by town
  • B        1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town
  • LSTAT    % lower status of the population
  • MEDV     Median value of owner-occupied homes in $1000's

 MEDV 컬럼이 타겟이고 나머지가 피처

 

 

1. 데이터 가져오기

data_url = "http://lib.stat.cmu.edu/datasets/boston"

raw_df = pd.read_csv(data_url, sep="\s+", skiprows=22, header=None)
print(raw_df.head())
          0      1      2    3      4      5     6       7    8      9     10
0    0.00632  18.00   2.31  0.0  0.538  6.575  65.2  4.0900  1.0  296.0  15.3
1  396.90000   4.98  24.00  NaN    NaN    NaN   NaN     NaN  NaN    NaN   NaN
2    0.02731   0.00   7.07  0.0  0.469  6.421  78.9  4.9671  2.0  242.0  17.8
3  396.90000   9.14  21.60  NaN    NaN    NaN   NaN     NaN  NaN    NaN   NaN
4    0.02729   0.00   7.07  0.0  0.469  7.185  61.1  4.9671  2.0  242.0  17.8
  • 공백 문자 단위로 데이터를 분할해서 읽을 공백 문자가 \s 이고
    +를 추가한 이유는 맨 앞에 공백이 있으면 제거하기 위해서
    뒤의 3개의 데이터도 하나의 행에 포함이 되어야 하는데 뒷줄로 넘어감

 

# 홀수 행에 짝수 행에서 앞의 2개의 열의 데이터를 붙이고 타겟 만들기

#피처 만들기
data = np.hstack([raw_df.values[::2, :], raw_df.values[1::2, :2]])

#타겟 만들기
target = raw_df.values[1::2, 2]

...

 

 

 

2. DataFrame 생성

  • 피처의 이름을 출력하거나 데이터를 전처리할 때 피처의 이름을 사용하기 위함
bostonDF = pd.DataFrame(data, columns=["CRIM", "ZN", "INDUS", "CHAS", "NOX",
                                      "RM", "AGE", "DIS", "RAD", "TAX", "PTRATIO",
                                      "B", "LSTAT"])
bostonDF["PRICE"] = target
bostonDF.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 506 entries, 0 to 505
Data columns (total 14 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   CRIM     506 non-null    float64
 1   ZN       506 non-null    float64
 2   INDUS    506 non-null    float64
 3   CHAS     506 non-null    float64
 4   NOX      506 non-null    float64
 5   RM       506 non-null    float64
 6   AGE      506 non-null    float64
 7   DIS      506 non-null    float64
 8   RAD      506 non-null    float64
 9   TAX      506 non-null    float64
 10  PTRATIO  506 non-null    float64
 11  B        506 non-null    float64
 12  LSTAT    506 non-null    float64
 13  PRICE    506 non-null    float64
dtypes: float64(14)
memory usage: 55.5 KB

 

 

 

3. 타겟과 피처 사이의 선형 관계 파악

  • 산포도를 이용(여러 개의 컬럼 사이의 산포도를 만들 때는 seaborn 의 pairplot 이나 regplot을 이용)
    • 예전에는 숫자가 아닌 컬럼이 있으면 제외하고 출력을 했는데
    • 최신 API에서는 숫자 컬럼에 대해서만 동작하기 때문에 숫자가 아닌 컬럼이 있으면 에러
    • 피처가 많은 경우 불필요한 산포도가 너무 많이 그려져서 알아보기가 어려움
sns.pairplot(bostonDF, height=2.5)
plt.tight_layout()
plt.show()

 

 

#타겟 과의 관계를 파악하고자 하는 feature 리스트를 생성

lm_features = ["RM", "ZN", "INDUS", "NOX", "AGE", "PTRATIO", "LSTAT", "RAD"]

#한 줄에 4개씩 8개의 그래프 영역을 생성
fig, axe = plt.subplots(figsize=(16, 8), ncols=4, nrows=2)
#python에서 list를 순회할 때 인덱스를 같이 사용하는 방법
for i, feature in enumerate(lm_features):
    row = int(i / 4)
    col = i % 4
    sns.regplot(x=feature, y="PRICE", data=bostonDF, ax=axe[row][col])

 

 

# 타겟과 피처의 상관 계수 확인

#상관 계수 구하기
cols = lm_features.copy()
cols.append("PRICE")
cm = np.corrcoef(bostonDF[cols].values.T)
print(cm)
[[ 1.          0.31199059 -0.39167585 -0.30218819 -0.24026493 -0.35550149
  -0.61380827 -0.20984667  0.69535995]
 [ 0.31199059  1.         -0.53382819 -0.51660371 -0.56953734 -0.39167855
  -0.41299457 -0.31194783  0.36044534]
 [-0.39167585 -0.53382819  1.          0.76365145  0.64477851  0.38324756
   0.60379972  0.59512927 -0.48372516]
 [-0.30218819 -0.51660371  0.76365145  1.          0.7314701   0.18893268
   0.59087892  0.61144056 -0.42732077]
 [-0.24026493 -0.56953734  0.64477851  0.7314701   1.          0.26151501
   0.60233853  0.45602245 -0.37695457]
 [-0.35550149 -0.39167855  0.38324756  0.18893268  0.26151501  1.
   0.37404432  0.46474118 -0.50778669]
 [-0.61380827 -0.41299457  0.60379972  0.59087892  0.60233853  0.37404432
   1.          0.48867633 -0.73766273]
 [-0.20984667 -0.31194783  0.59512927  0.61144056  0.45602245  0.46474118
   0.48867633  1.         -0.38162623]
 [ 0.69535995  0.36044534 -0.48372516 -0.42732077 -0.37695457 -0.50778669
  -0.73766273 -0.38162623  1.        ]]
plt.figure(figsize=(15, 7))
sns.set(font_scale=1.5) #화면 크기에 따라 변경 - 폰트 크기

hm = sns.heatmap(cm, cbar=True, annot=True, square=True, fmt='.2f',
                annot_kws={'size':15}, yticklabels=cols, xticklabels=cols)
plt.show()
  • ticklabels는 보여질 문자열
  • annot_kws 는 글자 크기
  • fmt는 포맷으로 숫자의 표시 형식
  • annot는 값 출력 여부

 

 

- 데이터 탐색 결과

  • RM 과 LSTAT 의 PRICE 영향도가 높음
  • RM은 양의 상관 관계를 가지고 LSTAT는 음의 상관 관계를 가짐
  • 단변량(피처가 1개) 선형 회귀를 수행한다면 2개의 피처 중 하나를 이용하는 것이 효율적

 

 

scipy 를 이용한 RM 과 PRICE에 대한 단변량 선형 회귀

from scipy import stats

result = stats.linregress(bostonDF['RM'], bostonDF['PRICE'])

#하나의 속성 출력
print(result.pvalue)

#예측 - 방이 4개 인 경우
print("방이 4개인 경우:", result.slope * 4 + result.intercept)
2.487228871008377e-74
방이 4개인 경우: 1.7378151482826851
  • scipy는 머신러닝을 수행하지만 머신러닝만을 위한 패키지는 아님
  • 피처를 설정할 때 2차원 배열이 아니어도 됨
  • scipy 나 seaborn 의 데이터는 pandas의 자료구조가 가능

 

 

sklearn을 이용한 단변량 선형 회귀

from sklearn.linear_model import LinearRegression

#모델 생성 - 행렬 분해를 이용하는 선형 회귀 모델
slr = LinearRegression()

#타겟과 피처 생성
X = bostonDF[['LSTAT']].values #sklearn 에서는 피처가 2차원 배열
y = bostonDF['PRICE'].values #타겟은 일반적으로 1차원 배열

#훈련
slr.fit(X, y)
  • 통계 패키지는 훈련의 결과를 리턴
  • 머신러닝 패키지는 훈련의 결과를 내부에 저장
  • 다음 데이터를 예측할 때 통계 패키지는 수식을 직접 만들어야 함
  • 머신러닝 패키지는 수식을 만들지 않고 새로운 데이터만 predict에게 제공
  • API를 확인할 때 통계 패키지는 리턴하는 데이터를 확인해야 하고
  • 머신러닝 패키지는 모델을 확인해야 함
#기울기 와 절편 출력
print("기울기:", slr.coef_[0])
print("절편:", slr.intercept_)

#예측
print(slr.coef_[0] * 4 + slr.intercept_)
print(slr.predict([[4]]))
기울기: -0.9500493537579905
절편: 34.5538408793831
30.75364346435114
[30.75364346]