의미1. observation을 줄이는 것. X_1, \cdots, X_n ⇒ \overline{X}
의미2. variable을 줄이는 것.
우리가 가지고 있는 데이터 ⇒ 모두 matrix
관심 있는 데이터 = 수치형 데이터. binary면 좀 애매하긴 하다.
이때 가장 많이 쓰이는 통계적 차원축소 기법은 다음과 같다.
- 주성분분석(PCA), 특이값분해(SVD)
- 고차원 행렬을 근사하는 선형적인 방법.
행렬 데이터와 패턴
데이터 생성 & 패턴 추가
40개의 row와 10개의 column을 가진 데이터 생성.
데이터를 시각화하고, 히트맵을 이용해 클러스터링을 한다.
해답
# 데이터 생성
set.seed(12345)
dataMat = matrix(rnorm(400), nrow = 40)
# 데이터 시각화 및 히트맵 그리기
image(1:10, 1:40, t(dataMat)[, nrow(dataMat):1])
heatmap(dataMat)
특정한 패턴이 없으므로, 행렬 데이터에 임의로 패턴을 추가해보자.
- 시드넘버를 678910으로 고정한다.
- 행에 패턴을 넣는 것이므로
for()문을 쓰되, 그 길이를 40으로 한다. coinflip변수를 만들고, 해당 변수가 T일 때 패턴을 넣는다.- 패턴은
rep(c(0, 3), each=5)로 만들고 각 열에 더한다.
해답
set.seed(678910)
for(i in 1:40){
coinflip = rbinom(1, size=1, prob=0.5)
## coin이 앞면일 경우, 패턴 추가
if (coinflip){
dataMatrix[i, ] = dataMatrix[i, ] + rep(c(0, 3), each=5)
}
}
인제 패턴이 들어간 상태에서의 행렬을 시각화해보자.
히트맵을 그리고(image() 사용), 클러스터링도 해본다. (heatmap() 사용)
해답
# 히트맵 그리기
image(1:10, 1:40, t(dataMat)[, nrow(dataMatrix):1])
# 클러스터링 해보기
heatmap(dataMat)
⇒ 첫번째 히트맵을 볼 때, 오른쪽에 있는 몇몇 행들이 나머지 열보다 더 높은 값을 가지고 있는 것을 볼 수 있다.
⇒ 두번째 덴드로그램(히트맵)으로부터 열(column)이 정확히 둘로 나눠지는 것을 볼 수 있다.
행과 열의 패턴
행과 열의 평균을 확인해서 패턴이 잘 들어갔는지를 자세히 살펴보자.
해답
## 패턴 찾기
hh = hclust(dist(dataMat))
ordered = dataMat[hh$order, ]
par(mfrow=c(1, 3))
image(t(ordered)[, nrow(ordered):1])
plot(rowMeans(ordered), 40:1,
xlab="Row Mean", ylab="Row", pch=19)
plot(colMeans(ordered), xlab="Column",
ylab="Column Mean", pch=19)
- 행과 열로 각각 2개씩 나눠지게 된다.
- 행(row) 기준: 아래 vs 위. 모양 자체는 정규분포
- 아래 : 평균 0
- 위 : 평균 3
- 열(column) 기준 : 아래 vs 위. 모양 자체는 이항분포
- 처음 5개 = 평균 0
- 나머지 다섯 개 = 평균 약 1.8
데이터를 만들 때를 생각해보자.
- 기본적으로 정규분포를 사용:
rnorm() - 패턴을 넣을 때(데이터의 절반) 이항분포를 사용:
rbinom() - 저런 모양이 나온 이유라고 할 수 있다.
실제 데이터는 이것보다 더 다양한 패턴이 나타날 수 있기 때문에
패턴들을 구분짓는 방법이 필요함!
가장 좋은 방법은 패턴을 나타내는 데이터만 뽑아내는 것이라고 할 수 있다!
이는 원 데이터에서 차원을 축소해서 저차원 데이터를 얻는 것과 같다.
- 하나의 방법은 변수들 중 어떤 것은 버리고, 다른 것은 살린다.
- 상관관계를 분석하는 방법. 그런데 모든 변수들을 다 하긴 어렵다.
- 다른 방법은 새로운 변수(feature)를 만드는 것이다.
- 새 변수는 서로 uncorrelated 되어야 한다.
- 또한 최대한 많은 분산을 표현해야 한다.
- 이때 사용하는 것이 PCA와 FA라고 할 수 있다.
PCA: 주성분분석
PCA란? 데이터 특성을 나타내는 공분산행렬을 분해해 일부를 얻어내는 것!
사영(projection)을 통해 데이터의 위치 변환 후 변하지 않는 축만 뽑는다.
이러한 과정은 SVD (특이값 분해)를 통해 데이터를 압축하는 것과 같다.
PCA의 원리
- 각각의 벡터를 가지고 표본 공분산행렬 S를 만든다.
- S의 고유값을 찾는다. 이때 tr(S) = 고유값의 합 = 표본 분산들의 합
- 직교인 고유벡터를 찾는다.
- S를 분해해보면 다음과 같다.
만약 A^T에 다변량 정규분포 X를 곱하면 그 값 Y 역시 다변량 정규분포를 따름.
- 여기서 차원을 줄인다.
- A = p \times p 행렬
- p 개 중 일부 = d개를 뽑아내기.
- 이때의 d만 뽑았을 때의 합이 tr(S)와 비슷하면 그냥 쓰면 된다.
- 이 행렬을 주성분이라고 한다.
(정리)
-
표본 공분산행렬 A를 만든다.
-
A를 대각행렬 L로 만든다.
-
A의 대각원소 중 일부를 뽑아서 A_d를 만든다.
-
원래 데이터인 X_c와 A_d를 곱해서 Z_d를 만든다.
고유벡터를 모은 A_d를 X_c에 곱해서 선형 변환을 수행한다.
(특징)
- 변수들 간의 선형관계가 강해야 PCA는 잘 작동한다.
- X들이 correlation이 강하지 않으면 의미가 없다.
- 설문조사처럼 모든 변수들이 같은 수준으로 점수화가 된 경우에는 공분산 행렬을 사용
- 그러나 변수들의 scale이 서로 많이 다른 경우에는 값이 큰 특정 변수가 전체적인 경향을 좌우
- 따라서 이때는 상관계수 행렬을 사용하여 추출해야 한다.
- R에서는
prcomp(scale=T)로 만들면 됨. - 대부분의 예시에서 상관계수 행렬과 공분산 행렬을 동시에 놓고 비교!
- 축소한 만큼의 손실(표현하지 못하는 분산)은 당연히 발생한다.
만약 X_c = \begin{pmatrix} X_1 & X_2 & X_3 \end{pmatrix} 인 경우
만약 X가 행렬일 경우, SVD는 다음으로 표현되는 행렬 분해이다.
- U는 XX^T에서 고유벡터를 뽑아 모은 것
- V는 X^TX에서 고유벡터를 뽑아 모은 것 = 주성분
- D의 대각원소는 X^TX와 XX^T의 각 고유값의 제곱근로 이뤄진 값.
- X의 랭크는 행의 갯수와 열의 갯수 중 최솟값.
예시 1: 패턴 1개
svd를 해 근사 데이터를 만들고, 원래 데이터와 근사 데이터를 비교해보자.
svd를 통한 근사 데이터를 만들 떄, U와 V의 1st 성분만 뽑아낸다.
해답
# pca 실행
svd1 = svd(scale(ordered))
# 1st 특이값벡터 => 근사행렬
approx = with(svd1, outer(u[, 1], v[, 1]))
# 기존데이터 vs 특이값분해
par(mfrow=c(1, 2))
image(t(ordered)[, nrow(ordered):1], main="Original Matrix")
image(t(approx)[, nrow(approx):1], main = "Approximated Matrix")
완전히 같지는 않아도, 왠만한 중요한 정보를 다 가지고 있다.
주성분행렬에서 특이값(=분산)들은 오름차순으로 정렬된다.
따라서 첫 번쨰 특이값은 모든 데이터의 분산 중 가장 크다.
설명된 분산
이때, 데이터의 첫 번째 분산이 전체 분산을 얼마나 반영하는지 살펴보자.
- 기존 데이터를 0과 1로만 이뤄진 데이터로 바꾼다.
- “특이값(전체 분산)” vs “설명된 분산의 비율” 비교
- 시각화
해답
# 0, 1로만 이뤄진 데이터 만들기
const = Ordered * 0
for (i in 1:dim(ordered)[1]) {
const[i, ] <- rep(c(0, 1), each = 5)
svd2 <- svd(const)
# 데이터 비교!
windows()
par(mfrow=c(1, 3))
image(t(const)[, nrow(const):1],
main="original data")
plot(svd2$d, xlab = "열", ylab = "특이값",
pch = 19)
plot(svd2$d^2/sum(svd2$d^2), xlab = "열",
ylab = "설명된 분산비율", pch = 19)
- 생각보다 잘 나타내며, 좋은 결과가 나온다.
- 첫 번째 그림 : 원래 데이터의 모습
- 두 번째 그림 : 원래 분산
- 세 번째 그림 : 설명된 분산의 비율
-
설명된 분산의 비율이 클 수록 정보 손실이 작다고 할 수 있다.
-
설명된 분산의 비율 = (선택된 고유값) / (전체 고유값의 합)
따라서 그 비율이 높을수록 원래 분산과 비슷해짐!
-
비교: PCA vs SVD
이렇게 만들어진 SVD의 특이값 = PCA의 주성분!
plot()으로 확인해보자.
해답
# pca와 svd 만들기
svd1 = svd(scale(ordered))
pca1 = prcomp(ordered, scale=TRUE)
# 그래프로 비교해보기
par(mfrow=c(1,1))
plot(pca1$rotation[, 1], svd1$v[, 1], pch=19,
xlab="Principal Component 1", ylab="Right singular vector")
abline(c(0,1))
예시 2: 패턴 2개
패턴이 2개 이상인 데이터 역시 svd를 통해 패턴을 발견할 수 있다.
먼저 패턴 2개를 넣은 새 데이터를 만들어보자.
해답
# 데이터 만들기
set.seed(678910)
dataMat2 = matrix(rnorm(400), nrow = 40)
# 패턴 만들기
for(i in 1:40){
coinflip1 = rbinom(1, size=1, prob=0.5)
coinflip2 = rbinom(1, size=1, prob=0.5)
if(coinflip1){
dataMat2[i, ] = dataMat2[i, ] + rep(c(0, 5), each=5)
}
if(coinflip2){
dataMat2[i, ] = dataMat2[i, ] + rep(c(0, 5), 5)
}
}
# 순서 정렬
hh2 = hclust(dist(dataMat2))
ordered2 = dataMat2[hh2$order, ]
데이터와 각 패턴의 모습은 다음과 같다.
해답
# 시각화
windows()
par(mfrow=c(1,3))
image(t(ordered2)[, nrow(ordered2):1])
plot(rep(c(0, 1), each=5), pch=19,
xlab="column", ylab="pattern 1",
main="pattern 1")
plot(rep(c(0, 1), 5), pch=19,
xlab="column", ylab="pattern 2",
main="pattern 2")
새 데이터 → scale() → svd() 해서 svd3를 만들고, 시각화해보자.
첫 번째 오른쪽 특이값벡터, 두 번째 특이값벡터의 plot을 그리면 된다.
해답
svd3 = svd(scale(ordered2))
windows()
par(mfrow=c(1, 2))
plot(svd3$v[, 1], pch=19, xlab="column",
ylab="1st right singular vector")
plot(svd3$v[, 2], pch=19, xlab="column",
ylab="2nd right singular vector")
첫번째 오른쪽 특이값벡터가 좀 더 상단의 두 패턴을 많이 반영했다고 할 수 있다.
설명된 분산
여기서도 설명된 분산의 비율을 확인해보자.
해답
par(mfrow=c(1, 2))
plot(svd3$d, pch=19,
xlab="column", ylab="Singular value")
plot(svd3$d^2/sum(svd3$d^2), pch=19,
xlab="column", ylab="percent of var. explained")
예시 3: 얼굴 데이터
얼굴 데이터를 SVD해서 원래 결과와 비슷한지 확인해보자.
이는 통계 데이터를 PCA하는 것 같은 과정이다.
- 얼굴데이터 : 일부만 시각화 → 전체 데이터와 비슷
- 통계데이터 : 일부만 뽑아내기 → 전체 데이터와 비슷.
얼굴 데이터를 살펴보고, scree plot을 그려보자.
해답
load("face.rda")
par(mfrow=c(1,1))
# 얼굴 보기
image(t(faceData)[, nrow(faceData):1])
# 설명된 분산의 비율
svd_face = svd(scale(faceData))
u = svd_face$u
d = svd_face$d
v = svd_face$v
svd_faceplot(d^2/sum(d^2), pch=19,
xlab="Singular Vector",
ylab="Var. explained")
특이값의 갯수를 다르게 해보며 얼굴이 원래와 비슷해지는지 보자.
해답
# 다양한 근사행렬
approx1 = u[, 1] %*% t(v[, 1]) * d[1]
approx5 = u[, 1:5] %*% diag(d[1:5]) %*% t(v[, 1:5])
approx10 = u[, 1:10] %*% diag(d[1:10]) %*% t(v[, 1:10])
# 얼굴 시각화
windows()
par(mfrow=c(1, 4))
image(t(approx1)[, nrow(approx1):1], main="1 vector")
image(t(approx5)[, nrow(approx5):1], main="5 vector")
image(t(approx10)[, nrow(approx10):1], main="10 vector")
image(t(faceData)[, nrow(faceData):1], main="origin")
FA: 요인분석
- 목적은 차원축소이고, outcome이 여러개.
- 기존 변수 m개의 정보를 담을 수 있는 새 변수 n개를 만든다.
- 기존 변수에 무언가를 더하고 곱해서 새 변수를 만든다.
- ex. 국, 수, 사, 과, 영 ⇒ 암기력, 학습능력 측정.
FA의 원리
요인분석 식을 만들어보면, 다음과 같은 변수 4개가 존재한다.
- X_{ij} : 관찰변수 = 줄여야 하는 기존 변수.
- f_{in} : 잠재변수 (common factors), 요인, 인자 = 만들어야 하는 새 변수
- 아직 무슨 값을 가졌는지 모르니까 분포를 가진다.
- \lambda_{ij} : 요인적재량 (factor loadings) = 잠재변수가 관찰변수에 미치는 가중치.
- 보통 0 아니면 1 이라고 가정한다.
- \epsilon_{ij} : 특정변수 (specific factors)
- 서로 상관관계가 없고, 잠재변수하고도 상관관계가 없다.
이때, 새 변수(관찰변수)의 분산 Var(X_{i})은 얼마일까?
모든 변수를 다 담는 행렬 형태로 다시 써보면 다음과 같다.
공분산행렬 S에서 우리가 모르는 변수 2개를 보자.
- 오차변수의 공분산행렬 \hat \psi
- \hat \psi을 대각행렬(diagonal matrix)라고 가정.
- 왜? 그래야 \mathbf{x}들끼리의 분산을 \mathbf{\Lambda}로 설명 가능.
- 요인행렬 \mathbf{\hat\Lambda}
- 요인적재량을 모은 것으로, 변수 간의 상관관계를 설명한다.
- 문제는 \mathbf{f_i}도 일종의 확률변수. 만약 \lambda \cdot R \cdot R^{-1} \cdot \mathbf{f_i}를 하면, 값이 달라짐. 따라서 값이 한개가 아님. 이 상태에서는 관찰변수들과 잠재변수들 사이의 관계가 명확하지 않음.
- 따라서 이를 회전시켜야 함. (= 분산 재분배)
요인행렬의 회전
- Varimax (직교회전)
- 요인 간 상관성이 없다고 가정
- 좌표축의 각도를 정확히 90도로 유지하여 회전
- 요인행렬 각 열의 분산을 최대화하는 기법
- 큰 값은 더 크게, 작은 값은 더 작게
- Promax (사각회전)
- 요인 간 상관성이 있다고 가정
- 좌표축의 각도가 90도가 되지 않도록 회전
- 추출한 요인들 간 상관관계를 다시 설명해야 함.
- 요인 간 군집관계를 잘 설명.
예제 4: 주식 데이터
데이터 불러오기 & 편집
-
데이터를 불러온다.
-
상관계수 행렬을 만들어준다.
prcomp(~~, scale = T)로 해줘도 된다.\text{matrix} - \text{avg. mat} \over \text{sigma. mat}
해답
# 데이터 불러오기
load("stockreturns.RData")
# 표준화된 행렬 만들기
mu = matrix(apply(stocks, 2, mean), dim(stocks)[1], dim(stocks)[2], byrow=T)
sig = matrix(apply(stocks, 2,sd), dim(stocks)[1], dim(stocks)[2], byrow=T)
stocks_new = (stocks - mu) / sig
PCA로 분석하기
PCA 작업을 해주고, 시각화를 시켜보자.
screeplot()과 biplot() 을 이용한다.
Scree Plot 이란?
- PCA 분석 후 주성분 수를 선정하기 위해 쓰는 차트
- 고유값-주성분의 분산 변화를 보여준다.
- 고유값 변화율이 완만해지는 부분이 필요한 주성분의 수이다.
해답
# pca 작업 실행
stock_pca1 = prcomp(stocks)
stock_pca2 = prcomp(stocks_new)
# screeplot
windows()
par(mfrow=c(1, 2))
screeplot(stock_pca1)
screeplot(stock_pca2)
# biplot()
windows()
par(mfrow=c(1, 2))
biplot(stock_pca1, 1:2)
abline(h=0, lty=3, col='red')
abline(v=0, lty=3, col='red')
biplot(stock_pca2, 1:2)
abline(h=0, lty=3, col='red')
abline(v=0, lty=3, col='red')
여기서는 필요한 주성분이 대충 5개 정도임을 알 수 있다.
각 축이 고유벡터라고 생각하면 된다.
biplot과 두 PCA
biplot이란? 각 개체들의 첫번째 주성분(=행), 두번째 주성분(=열) 값을 나타내는 행렬도를 시각화 한것
- 숫자는 데이터 그 자체를 의미
- Biplot 그림에서 화살표는 원변수와 PC의 상관계수를 뜻하며, PC와 평행할수록 해당 PC에 큰 영향.
- 화살표 벡터의 길이가 원변수의 분산을 표현하며, 길수록 분산이 크다.
해답
windows()
with(stock_pca1, plot(rotation[, 1], rotation[, 2], type='n', xlab='PC1', ylab='PC2'))
with(stock_pca1, text(rotation[, 1], rotation[, 2], 1:10))
abline(h=0, lty=3, col='red')
abline(v=0, lty=3, col='red')
결과 해석
- PC1에 가장 많은 영향을 미치는 요인은 comp1이다.
- PC2에 가장 많은 영향을 미치는 요인은 comp10이다.
- 가장 분산이 큰 애는 comp4이다.
생각해봐야 할 것
- 주성분분석의 대상인 변수의 선정
- 제1주성분의 정의
FA로 분석하기
데이터 : 주식 주가지수
첫번째 4개 회사는 기술기업, 3개는 투자은행, 3개는 리테일.
데이터를 준비해보자.
해답
kval = 3
stock_fa = factanal(stocks_new, kval, rotation="none", score="reg")
str(stock_fa)
stock_fa
stock_fa$loadings
stock_fa2 = factanal(stocks_new, kval, rotation="none")
요인분석의 상관관계와 원 데이터의 상관관계를 보자.
해답
# 비교1 : 요인분석의 상관관계 vs 원 상관관계
all.equal(stock_fa$correlation, cor(stocks))
# 비교2
x1 = apply(stocks_new, 2, var)
x2 = apply(stock_fa$loadings^2, 1, sum)
all.equal(x1-x2, stock_fa$uniquenesses)
회전행렬을 만들어보자.
해답
# 회전행렬 만들기
stock_fa_R1 = factanal(stocks_new, kval, rotation="varimax")
stock_fa_R2 = factanal(stocks_new, kval, rotation="promax")
이후 시각화를 해보자.
해답
## 텍스트
text = "Stocks (No rotation)"
text2 = "Stocks (Varimax)"
text3 = "Stocks (Promax)"
## 새 창 만들기
windows()
par(mfcol=c(2,3))
# 회전없이 갈 때
with(stock_fa, plot(loadings[, 1], loadings[, 2], xlim=c(-1, 1),
ylim=c(-1, 1), main=text, xlab='Factor 1', ylab="Factor 2"))
with(stock_fa, text(loadings[, 1], loadings[, 2], labels = 1:10, pos=4))
abline(v=0)
abline(h=0)
with(stock_fa, plot(loadings[, 1], loadings[, 3], xlim=c(-1, 1),
ylim=c(-1, 1), main=text, xlab='Factor 1', ylab="Factor 3"))
abline(v=0)
abline(h=0)
# varimax 일 때
with(stock_fa_R1, plot(loadings[, 1], loadings[, 2], xlim=c(-1, 1),
ylim=c(-1, 1), main=text2, xlab='Factor 1', ylab="Factor 2"))
with(stock_fa_R1, text(loadings[, 1], loadings[, 2], labels = 1:10, pos=4))
abline(v=0)
abline(h=0)
with(stock_fa_R1, plot(loadings[, 1], loadings[, 3], xlim=c(-1, 1),
ylim=c(-1, 1), main=text2, xlab='Factor 1', ylab="Factor 3"))
with(stock_fa_R1, text(loadings[, 1], loadings[, 3], labels = 1:10, pos=4))
abline(v=0)
abline(h=0)
# promax로 갈 때
with(stock_fa_R2, plot(loadings[, 1], loadings[, 2], xlim=c(-1, 1),
ylim=c(-1, 1), main=text3,
xlab='Factor 1', ylab="Factor 2"))
with(stock_fa_R2, text(loadings[, 1], loadings[, 2], labels = 1:10, pos=4))
abline(v=0)
abline(h=0)
with(stock_fa_R2, plot(loadings[, 1], loadings[, 3], xlim=c(-1, 1),
ylim=c(-1, 1), main=text3,
xlab='Factor 1', ylab="Factor 3"))
with(stock_fa_R2, text(loadings[, 1], loadings[, 3], labels = 1:10, pos=4))
abline(v=0)
abline(h=0)
결과 해석
-
factor 2의 관점에서 보자. rotation을 해보면 리테일이 두드러져 보인다.
factor 2가 리테일에 관련된 잠재요인임을 알 수 있다.
-
factor 3의 관점에서 보자. rotation을 해보면, 투자은행이 두드러져 보인다.
factor 3가 투자은행에 관련된 잠재요인임을 알 수 있다.
이러한 factor를 찾아냄으로써 연관된 (=같이 움직이는) 변수들을 찾을 수 있다.
- 추정된 람다를 바탕으로 factor score을 찾아냄.
cor(stock_fa$scores)
> as.matrix(cor(stock_fa$scores))
Factor1 Factor2 Factor3
Factor1 1.000000e+00 6.663222e-16 -4.337095e-16
Factor2 6.663222e-16 1.000000e+00 1.263550e-15
Factor3 -4.337095e-16 1.263550e-15 1.000000e+00
- factor가 많으면 많을수록, 설명은 잘 되나 해석하기 힘듬
- factor가 너무 적으면, 관계 설명이 너무 어려워짐.
정리 : PCA vs FA
-
주성분분석 : hclustering과 비슷.
-
요인분석 : d를 지정한다. k-means와 비슷
-
FA: 모델링 베이스. 안되는 걸 전부 error term으로
-
\mu, \Lambda를 estimation
-
결과를 하나 잡고, 제일 좋은 것을 선택.
차원은 2개야! ⇒ 확인!
-
따라서 답이 달라질 수 있음.
-
-
PCA: 분해 베이스. 수학적 방법을 사용
-
행렬의 trace와 eigenvalue 사용
-
결과는 나와있고, 이중에서 고르기
차원 중 3개를 잡자! ⇒ 확인!
-
따라서 답이 항상 1개
-
-
SVD, PCA, FA 모두 변수들을 줄이는 것이 목표.
변수를 줄인다 = 연관성이 높은 변수를 찾아서 지울 것을 지운다.
-
따라서, 먼저 표본공분산을 보면서 상관관계가 있는지를 봐야 함!
상관관계가 없으면 차원축소를 하는 의미가 없다.
상관관계가 있다 = 정보가 중복되어 있다. (시험문제!!!)