시계열 : 서울시 평균기온 예측 프로젝트 5(전처리)
1. 데이터 로드
import pandas as pd
train = pd.read_csv('preprocessing_train.csv', parse_dates=['일시'])
train = train.set_index('일시')
display(train.head(2))
2. boxplot을 활용한 각 변수의 이상값 확인
import seaborn as sns
import matplotlib.pyplot as plt
plt.figure(figsize=(20,10))
ax = sns.boxplot(data=train.iloc[:,:-3])
plt.title("모든 변수에 대한 이상값 확인")
plt.show()
※ 결과 해석
강수량 값이 이상값인 부분이 많은 것으로 보입니다.
boxplot의 최소값, 최대값을 넘었다고 무조건 이상값이라고 판별할 수 없지만, 그래프에서 각 데이터포인트의 떨어진 점이 일부 많은 것을 보았을 때 이를 적절하게 처리할 필요가 있을 것으로 보입니다.
3. 강수량 이상값 대체
import numpy as np
# 이상값 대체 이전 분포 확인
sns.displot(train['강수량'])
# 강수량 값이 60이상인 값을 60으로 대체
train['강수량'] = np.where(train['강수량']>=60, 60, train['강수량'])
display(train[train['강수량'] > 60])
# 이상값 대체 이후 분포 확인
sns.displot(train['강수량'])
plt.show()
※ 결과 해석
이상값을 처리한 후 분포로 보았을 때 최댓값이 60으로 설정된 것을 볼 수 있습니다.
이는 데이터에서 호우주의보에 해당할 만한 높은 강수량의 값은 일관되게 처리함으로써 얻어진 결과입니다.
처리를 통해, 데이터의 극단적 변동성을 줄이고, 보다 일반적인 강수량의 범위 내에서 분석을 진행할 수 있게 되었습니다.
이는 이상값으로 인한 분석의 왜곡을 방지하고, 모델의 예측력을 향상시킬 것으로 기대됩니다.
또한, 이상값을 제한한 결과, 강수량 분포의 전반적인 형태가 더 명확해졌습니다.
이는 데이터의 대부분이 낮은 강수량 값에 집중되어 있으며, 이는 일반적인 날씨 조건을 반영합니다.
특히, 강수량이 0에 가까운 날의 빈도가 가장 높아, 비가 오지 않는 날이 대부분임을 알 수 있습니다.
4. 변수별 분포 확인
ax = train.hist()
plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=1, hspace=1)
plt.show(
※ 결과 해석
강수량 데이터의 분포는 그 비대칭성에서 눈에 띄는 특성을 가집니다.
대다수의 관측값이 0 또는 0에 가까운 강수량을 보이는데, 이는 강수가 드물게 발생하는 지역의 기후 패턴을 반영합니다.
강수량은 종종 극단적인 기상 현상에 의해 큰 변동을 보이며, 예외적으로 매우 높은 강수량을 기록할 수 있습니다.
이런 예외적인 사건은 비록 드물지만, 전체 데이터 분포에 큰 영향을 미칠 수 있습니다.
반면, 일조합과 일조율은 강수량과 달리 상대적으로 극단적인 값이 흔히 발생할 빈도가 낮습니다.
이는 두 변수의 전체적인 데이터 분포가 강수량에 비해 왜곡될 위험이 적음을 의미합니다.
따라서, 일조합과 일조율은 특별한 변환 없이도 분석에 사용할 수 있습니다.
이러한 분석을 바탕으로, 강수량 데이터에는 변환을 적용하여 데이터를 조정하지만, 반면 일조합과 일조율을 포함한 나머지 데이터에는 변환을 적용하지 않을 것입니다.
5. boxcox 변환 적용
강수량 의 경우, 대부분의 값이 0에 집중되어 있는데, 이러한 데이터에 로그 변환을 적용하면 변환된 데이터의 분포가 여전히 매우 치우칠 수 있습니다.
이는 로그 변환의 매우 작은 값들이 변환 후에 매우 큰 값으로 변할 수 있어, 결과적으로 데이터의 분포를 더 불균형하게 만들 수 있기 때문입니다.
위 이미지는 밑이 e인 자연 로그 변환과 밑이 10인 로그 변환을 보여줍니다.
이 그래프에서 주목할 점은 두 로그 변환 모두 1 이하의 값들에 대해 큰 변화를 주며, 값이 1보다 작을 때 로그 변환된 값이 더 크게 감소하는 경향을 나타냅니다.
이와 대조적으로 Box-Cox 변환은 데이터의 왜곡을 완화하는 데 유용한 방법입니다.
Box-Cox 변환은 데이터를 정규분포에 가깝게 변환하는 통계적 방법 중 하나입니다.
이 변환은 양수 데이터에만 적용되며, 주로 양수 데이터의 왜도(skewness)를 줄이기 위해 사용됩니다.
scipy.stats.boxcox() 함수는 입력 데이터에 대한 최적의 람다(λ) 값을 찾기 위해 최대 우도 함수를 사용합니다.
람다가 None일 경우(기본값) 최대 우도 함수를 사용하여 람다 값을 찾은 후 변환을 수행하며, 람다가 주어진 경우 해당 람다 값을 사용하여 변환을 수행합니다.
Box-Cox 변환은 로그 변환보다 0에 가까운 값들을 더 효과적으로 처리할 수 있으며, 결과적으로 변환된 데이터는 이상값에 덜 민감하고, 모델링 과정에 보다 적합한 분포를 가질 가능성이 높아집니다.
from scipy.stats import boxcox
# 0 값을 포함하는 데이터셋에 작은 상수를 더하는 함수
def positive_shift(series, shift_value=1e-3):
return series + shift_value if np.any(series <= 0) else series
# lmda 에는 람다가 반환되지만, 이번 프로젝트에서는 사용하지 않습니다.
train['강수량'], lmda = boxcox(positive_shift(train['강수량']))
sns.displot(train['강수량'])
plt.show()
※ 결과 해석
Box-Cox 변환을 적용한 결과, 원래 분포의 왜도를 줄이고 데이터를 더 대칭적으로 만들었습니다.
Box-Cox 변환 후의 분포는 중앙에 더 많은 데이터 포인트를 가지며, 이는 정규 분포에 조금 더 가까운 형태를 가집니다.
이러한 변환은 이상값의 영향을 줄이고, 데이터의 전반적인 분포를 정규화하여 모델의 가정에 더 잘 부합하도록 합니다.
6. 상관관계 확인
plt.figure(figsize=(8,8))
sns.heatmap(train.corr(), annot = True)
plt.show()
※ 결과 해석
상관관계 히트맵에 따르면, 최저기온과 최고기온 간에 매우 높은 상관관계(0.96)가 있는 것으로 보입니다.
이 두 변수는 다중공선성이 있을 가능성이 높습니다.
또한, 일조합과 일조율 사이에도 매우 높은 상관관계(0.97)가 나타났고, 일사량 및 일교차 변수도 0.6 이상의 강한 상관관계를 가지고 있습니다.
이에 대해 분산팽창계수(Variance Inflation Factor, VIF)를 통해 다중공선성에 해당하는지 확인할 수 있습니다.
7. 분산팽창계수 확인
※ VIF의 원리
- 데이터셋에서 한 독립 변수를 선택합니다.
- 선택된 독립 변수를 종속 변수로 가정하고, 나머지 독립 변수를 사용하여 선형 회귀 모델을 학습합니다.
- 이 선형 회귀 모델의 결정계수(R-squared, R²)를 계산합니다.
- 계산된 결정계수를 사용하여 VIF를 계산합니다. VIF를 구하는 식은 다음과 같습니다.
※ VIF 해석
VIF가 10 이상이면 다중공선성이 존재하며, 5 이상일 경우 다중공선성에 주의해야 합니다. 5 미만일 경우 다른 변수들과의 상관관계가 낮음을 의미합니다.
※ VIF 사용 시 고려사항
- VIF는 모든 독립 변수에 대해 계산되며, 높은 VIF 값을 가진 변수는 모델에서 제거하거나 조정해야 할 필요가 있습니다.
- 높은 VIF 값을 가진 변수들은 서로 정보를 중복하고 있을 수 있으며, 변수 선택 또는 PCA와 같은 차원 축소 기법을 고려해야 합니다.
- VIF는 변수가 다른 독립 변수와 선형 관계를 가지는 정도를 측정하며, 복잡한 비선형 관계나 상호작용을 고려하지 못하는 한계점이 있습니다.
from statsmodels.stats.outliers_influence import variance_inflation_factor
# 상관관계가 높은 것으로 의심되는 4개 컬럼명을 리스트로 작성합니다.
suspected_columns = ['일교차', '일조합', '일사합', '일조율']
vif_df = pd.DataFrame()
vif_df["변수명"] = suspected_columns
vif_df["VIF"] = [variance_inflation_factor(train[suspected_columns].values, i) for i in range(len(suspected_columns))]
# 계산된 VIF 점수 출력
display(vif_df)
※ 결과 해석
일교차 는 약 9.31로 높은 다중공선성을, 나머지 세 변수는 VIF 값이 10이상으로 매우 높은 다중공선성을 가지고 있습니다.
이러한 결과는 이 변수들이 회귀 모델에 부정적인 영향을 미칠 수 있음을 의미합니다. 따라서 변수 전처리가 필요합니다.
8. 표준화 적용
표준화(Standardization) 는 각 변수들이 서로 다른 범위와 단위를 가질 때 필요한 전처리 방법입니다.
이 과정은 데이터의 평균을 0으로, 표준편차를 1로 조정함으로써, 각 변수의 범위를 동일하게 조정합니다.
변수들의 범위가 다를 경우, 특히 값이 큰 변수들 상대적으로 더 큰 영향력을 가지게 되는 경향이 있습니다.
평균풍속 은 백분율로 측정되어 0에서 100 사이의 값으로 표현되지만, 일조합 은 시간 단위로 측정되며 값의 범위가 다양할 수 있습니다.
이 두 변수를 표준화하지 않고 모델에 사용할 경우, 더 큰 값을 가지는 평균습도가 모델에 더 큰 영향을 미치게 될 수 있습니다.
이는 변수 간의 실제 중요도가 아닌 단순히 스케일의 크기에 의해 모델의 예측이 좌우되는 결과를 초래할 수 있습니다.
또한, 거리에 기반한 알고리즘들은 변수 간의 균일한 스케일을 필요로 합니다.
표준화되지 않은 데이터에서는 범위가 큰 변수들이 결과에 과도한 영향을 미치게 되며, 이는 모델의 성능을 왜곡시킬 수 있습니다.
반면, 표준화를 통해 모든 변수를 동일한 기준으로 조정하면, 각 변수의 실제 중요도가 모델에 반영될 수 있게 됩니다.
결론적으로, 표준화는 데이터를 보다 일관되게 해석할 수 있게 하며, 모델의 정확도와 일반화 능력을 향상시키는 데 기여합니다.
다음 스텝에서 PCA 를 사용하여 차원을 축소하고자 합니다.
PCA를 사용하기 전 표준화를 수행한 이유는 PCA를 수행할 때 각 특성이 서로 다른 스케일을 가질 경우, 스케일이 큰 특성이 결과에 더 큰 영향을 미치기 때문입니다.
from sklearn.preprocessing import StandardScaler
# 전체 데이터에 대해 표준화를 적용합니다.
scaler = StandardScaler()
scaler.fit(train)
scaled_data = scaler.transform(train)
data = pd.DataFrame(scaled_data)
# train 데이터의 일자 및 칼럼을 저장합니다.
dates = train.index
columns = train.columns
# 저장했던 train 데이터의 일자 및 칼럼을 표준화를 적용한 데이터에 적용합니다.
data.index = dates
data.columns = columns
data.head()
9. PCA를 이용한 차원 축소 (1)
※ PCA
PCA는 고차원의 데이터를 저차원으로 축소하는 통계적 기법입니다.
이 방법은 데이터의 분산을 최대화하는 방향으로 새로운 축을 찾아, 데이터를 이 축에 투영합니다.
2차원일 때는 그림과 같이 첫 번째 주성분은 데이터가 가장 넓게 퍼져 있는 방향을 나타내며,
두 번째 주성분은 첫 번째 주성분에 직교하는 방향으로 설정됩니다.
3차원 이상일 때, 첫 번째 주성분은 여전히 가장 큰 분산을 나타내고,
두 번째 주성분은 첫 번째 주성분에 직교하는 방향 중 가장 큰 분산을 설명합니다.
추가적으로, 세 번째 주성분은 이전 두 주성분에 모두 직교하며 남은 분산을 포함합니다.
이렇게 함으로써 데이터의 주요 특성을 유지하면서 차원을 줄일 수 있고,
데이터의 복잡성을 감소시켜 계산 효율을 높입니다.
장점
데이터의 중요한 특성을 유지하며 차원을 줄이고, 데이터셋의 복잡성을 감소시킵니다. 불필요한 변동성을 제거함으로써 데이터의 핵심 특성을 더 명확하게 드러냅니다. 저차원으로 변환된 데이터는 시각화와 해석이 용이합니다. 차원이 축소되면 데이터 처리와 분석이 더 빠르고 효율적으로 이루어질 수 있습니다.
단점
주성분은 원본 변수들의 선형 결합으로 구성되므로 이들이 무엇을 의미하는지 해석하기 어려울 수 있습니다. 차원을 줄이는 과정에서 일부 정보가 손실될 수 있습니다. 모든 변수가 동일한 중요도를 가지고 있다고 가정하므로, 어떤 변수가 더 중요한지 구별하기 어렵습니다.
주요 매개변수
- n_components: 유지하려는 주성분의 수나 유지하려는 분산의 비율을 지정합니다. 정수로 주어진 경우 해당 수의 주성분을, 실수의 범위로(0에서 1까지의 범위) 주어진 경우 데이터의 n%를 설명하는 주성분을 자동으로 지정합니다.
- svd_solver: 사용할 SVD(Singular Value Decomposition) 방법을 지정합니다. 'full', 'arpack', 'randomized' 등이 있으며 기본값은 'auto'입니다. 'auto'의 경우 기본적으로 'full' 방법을 SVD로 수행하나, 데이터가 너무 크고 추출하려는 주성분의 수가 적을 경우 'randomized' 방법을 수행합니다.
주요 메서드
- fit()
- 기능: 주어진 데이터에 대해 PCA 모델을 학습합니다.
- 이 과정에서 모델은 데이터의 평균, 주성분, 그리고 분산 등을 계산합니다.
- transform()
- 기능: fit() 메서드를 통해 학습된 PCA 모델을 사용하여 데이터를 변환합니다.
- 이 방식을 통해 데이터를 주성분의 공간으로 매핑합니다.
아래의 코드에서 n_components의 값을 0.7로 설정해 전체 데이터의 70% 이상을 설명할 수 있도록 하였습니다.
일반적으로 PCA 등 차원 축소 기법을 적용할 때 누적 분산 비율이 70% 이상이 되는 주성분의 개수를 선택합니다.
일교차 변수는 온도와 관련되었지만, 태양과 관련된 변수들과 상관관계가 강하므로(0.6 이상),
태양과 관련된 변수들과 함께 PCA를 적용할 수 있을 것입니다.
from sklearn.decomposition import PCA
sun_pca = PCA(n_components=0.7)
sun_pca.fit_transform(data[['일교차', '일조합', '일사합', '일조율']])
data['pca_sun'] = sun_pca.transform(data[['일교차', '일조합', '일사합', '일조율']])
data.drop(['일교차', '일조합', '일사합', '일조율'], axis = 1, inplace = True)
print(f'PCA 결과 {sun_pca.n_components_} 차원으로 축소되었습니다.')
print(f'변수 {sun_pca.n_components_} 개가 전체 데이터의 {sun_pca.explained_variance_ratio_[0]}% 를 설명합니다.')
PCA 결과 1 차원으로 축소되었습니다.
변수 1 개가 전체 데이터의 0.8009457796517434% 를 설명합니다.
※ 결과 해석
4개의 변수를 PCA로 1차원으로 축소한 결과, 첫 번째 주성분이 전체 데이터 분산의 대략 80%를 차지함으로써,
PCA가 다중공선성 문제를 효과적으로 해결하는 방법임을 확인할 수 있습니다.
10. PCA를 이용한 차원 축소 (2)
이전 스텝에서 상관관계를 확인했을 때 두 칼럼(최고기온, 최저기온)이 매우 높은 상관관계를 가지고 있었습니다.
이 경우, 다중공선성이 발생할 우려가 있으며, 어떤 변수가 결과에 실제로 영향을 미치는지 명확히 파악하기 어려울 수 있습니다.
따라서 이를 제거하기 위해 두 칼럼에 대해 PCA를 적용합니다.
temperature_pca = PCA(n_components=0.7)
temperature_pca.fit(data[['최고기온', '최저기온']])
data['pca_temperature'] = temperature_pca.transform(data[['최고기온', '최저기온']])
data.drop(['최고기온', '최저기온'], axis = 1, inplace = True)
print(f'PCA 결과 {temperature_pca.n_components_} 차원으로 축소되었습니다.')
print(f'변수 {temperature_pca.n_components_} 개가 전체 데이터의 {temperature_pca.explained_variance_ratio_[0]}% 를 설명합니다.')
PCA 결과 1 차원으로 축소되었습니다.
변수 1 개가 전체 데이터의 0.9813988721272655% 를 설명합니다.
11. 상관관계 재확인
sns.heatmap(data.corr(), annot = True)
plt.show()
display(data.head())