IT/Data & AI

안녕하세요 밥한그릇 입니다. ^^

무더운 여름인데 다들 잘 지내고 계시나요? 저는 저번 주 수요일을 마지막으로 1학기가 종강한 상태입니다.

막 종강을 하고 방학을 맞이하게 된 대학생의 심정이란... 후련하면서도 마음 한구석이 허무하네요. (ㅠㅠ)

지난 학기에 데이터사이언스라는 과목을 수강했었는데, 무시무시한 과제량과 진도 분량 때문에 정말 너무 힘들었어요. 그렇지만 힘들었던 만큼 배운 내용이 많았기에, 잊어버리기 전에 한번 중요한 내용들을 리뷰해보려해요!

 

마지막 보강주차에 내용기반(content based) 검색 중 tf-idf 기반 문서 검색에 대해 배우게 되었어요.

그러니 이번 포스팅에서는 문서 검색의 원리와 tf-idf 문서 유사도(코사인 유사도) 계산법에 대해 알아보는 시간을 가지려 합니다.

그럼 시작합니다~


🔎 문서 검색

우리는 우리가 원하는 정보(문서)를 찾을 때, 주로 구글이나 네이버 같은 검색엔진을 많이 사용하죠. 이 검색 엔진의 원리는 대체 무엇일까요? 우리가 검색한 내용과 연관성이 높은 문서들을 어떻게 찾아주는 것일까요?

바로, 검색한 내용과 가장 관련성이 높은(또는 유사한) 순서대로 문서 목록을 생성해주면 됩니다. 그리고 관련성이 높은 것들부터 사용자에게 보여주면 끝나는 거에요.

 

그럼 문서의 순위는 어떻게 정할까요? 대상 집합에 포함된 각 문서에 관련도(유사성) 점수를 부여하면 됩니다.

이 유사성 점수를 부여하는 방법에는 자카드 계수단어 가방 모델(tf, df, idf ...) 등이 있습니다.

참고로 자카드 유사도의 계산에 대한 내용은 아래 더보기를 눌러보세요. 이번 포스팅에서는 단어가방 모델에서의 유사도 계산이 메인이므로 그것 위주로 설명하겠습니다.

더보기

두 문서의 자카드 유사도는 |교집합| / |합집합|으로 구할 수 있습니다.

구글에 "흥부와 놀부"를 검색했다고 해봅시다.

 

1. 다음의 두 문서가 나옵니다. 둘 중 어느 문서가 내 질의와 더 유사할까요?

 

문서 1 : 흥부와 놀부는 형제다

문서 2 : 흥부는 착하다

 

2. 질의와 문서들을 각각 용어 집합으로 변환해보아야겠네요.

질의 : {흥부, 놀부}

문서 1 : {흥부, 놀부, 형제}

문서 2 : {흥부, 착하다}

 

3. 자카드 계수로 계산해봅시다.

질의-문서1 : |{흥부, 놀부}| / |{흥부, 놀부, 형제}|

질의-문서1 : |{흥부}| / |{흥부, 놀부, 착하다}|

 

| { } | 는 해당 집합 안에 있는 원소의 개수를 의미합니다.

따라서 첫번째 식의 유사도는 2/3, 두번째 식의 유사도는 1/3이죠.

그러니 내 질의는 문서 1과의 유사도가 문서 2보다 더 높다고 볼 수 있겠습니다.


💼 단어가방 모델

자카드 유사도는 출현 여부(단어가 나오는 지/아닌 지)만 고려하고, 그 빈도수(단어가 몇 번 나오는 지)는 고려하지 않는다는 한계가 있습니다. 단어의 빈도수는 문서의 관련성에 큰 영향을 미치거든요. 드물게 출현하는 용어가 자주 출현하는 용어보다 많은 정보를 가지기 때문입니다. (informative)

 

그래서 단어의 빈도수를 유사도에 반영하기 위해, 이 단어 가방 모델이라는 것을 사용할 수 있습니다.

여기에서는 문서를 쪼개서 거기 나오는 단어의 빈도수를 표현하거나(tf), 어떤 단어가 출현하는 문서의 개수를 세거나(df) 해서 이를 벡터로 표현합니다.

 

말이 어렵죠? 아래의 예시를 보고 이해해봅시다.

참고로, 아래의 예시에서는 소숫점 3번째 자리에서 값을 반올림하여 나타냅니다.


✍ tf와 idf 예제

1) 다음의 문서 3개를 tf(단어 빈도수 벡터)와 df(문서 빈도수 벡터)로 나타내 봅시다.

 

문서 1: 철수데이터 과목컴구조 과목좋아한다.

문서 2: 영이컴구조 과목싫어한다.

문서 3: 철수컴구조 과목싫어한다.

 

(주의 : df-idf 가중치 계산 및 유사도 계산은 내 계산이 틀렸을 수 있음. + 소숫점 반올림-내림 등에 따라 계산 결과 달라질 수 있음.

  따라서 계산 원리만 참고하기 바람)

 

tf

문서 / 단어 철수 영이 데이터 컴구조 과목 좋아 싫어
문서 1  1
문서 2
문서 3

 위와 같이, 문서 당 등장하는 단어의 빈도수를 벡터로 표시한 것을 tf(= term freqeuncy, 단어빈도수)라고 합니다.

 

df

문서 / 단어 철수 영이 데이터 컴구조 과목 좋아 싫어
통합 문서  2 1 1 3 3

 또 위와 같이 어떤 단어가 등장하는 문서의 수를 벡터로 표시한 것을 df(=document frequency, 문서빈도수)라고 합니다.

 

그런데 아까 "어떤 용어 t가 나오는 문서가 희귀할수록, t의 중요도 즉, 정보의 효용도는 커진다"라고 했잖아요. 그럼 df가 커질수록 해당 문서의 중요도가 작아지겠네요?

그래서 문서의 중요도를 반영하기 위해, 다시 용어 t의 역문서 빈도-idf (= inverse document freqency)-라는 것을 정의하게 됩니다.

다음의 식으로 df에서 idf를 계산할 수 있습니다.

 

df 와 idf

idf 계산 식

문서 / 단어 철수 영이 데이터 컴구조 과목 좋아 싫어
통합 문서 (df) 2 1 1 3 4
idf 0 0.18 0.18 -0.12 -0.22 0.18 0

반올림 전의 idf

더보기
문서 / 단어 철수 영이 데이터 컴구조 과목 좋아 싫어
통합 문서 (df) 2 1 1 3 4
idf 0 log(3/2) log(3/2) log(3/4) log(3/5) log(3/2) 0

반올림 전의 idf(= log형태) 값은 다음과 같다.

df에서 위의 계산식을 적용하면 idf를 구할 수 있습니다. 여기서 N은 문서의 수로, 문서가 문서1, 문서2, 문서3이 있으니 N은 3이 되겠네요. 그럼 여기서 df를 이용해 단어 '영이'의 idf를 구하면, log(3/(1+1))으로, 소수점 셋째자리에서 반 올림하여 약 0.18이 나오게 됩니다.

 

2) tf-idf 가중치 벡터를 구해볼까요?

 

tf-idf 가중치

그럼 용어 t의 문서 d에 있어서의 가중치(중요도)는 어떻게 될까요?

여러가지 방법이 있지만, "tf-idf 가중치"라는 방법이 정보 검색 분야에서 가장 많이 사용된다고 해요.

tf가 커지거나 idf가 커질수록 tf-idf 가중치는 증가합니다. 즉, 해당되는 문서에서 용어의 빈도수가 높아지거나 또는 용어가 나오는 문서가 희귀(희소)할수록 가중치가 증가한다는 것이죠.

 

tf-idf 가중치 =(tf 가중치) * (idf 가중치)

tf-idf 가중치는 tf 가중치와 idf 가중치를 곱하여 구하므로, 위와 같은 식을 통해 구할 수 있습니다. 앞의 log(1 + tf)는 tf 가중치를 구하는 식, 그리고 뒤의 식은 idf를 구하는 식이죠. 이때, 만일 용어의 빈도수가 0이라면, tf 가중치는 식을 계산하지 않고 0으로 계산한다는 점을 유의합시다.

 

tf-idf 가중치 벡터

그럼 위의 예시에서의 tf-idf 가중치 벡터를 구해보면 다음과 같이 나옵니다.

 

tf-idf 가중치 철수 영이 데이터 컴구조 과목 좋아 싫어
문서 1 0 0 0.05 -0.04 -0.11 0.05 0
문서 2 0.05 0 -0.04 -0.07 0 0
문서 3 0 0 0 -0.04 -0.07 0 0

반올림 전의 tf-idf 가중치 벡터

더보기
tf-idf 가중치 철수 영이 데이터 컴구조 과목 좋아 싫어
문서 1 0 0 log(2)*log(2/3) log(2)*log(3/4) log(3)*log(3/5) log(2)*log(3/2) 0
문서 2 log(2)*log(3/2) 0 log(2)*log(3/4) log(2)*log(3/5) 0 0
문서 3 0 0 0 log(2)*log(3/4) log(2)*log(3/5) 0 0

반올림 전의 tf-idf 가중치 벡터(= log형태) 값은 다음과 같다.

이때 tf 가중치와 idf 가중치를 곱하는 것이므로, idf가 0이 나온 열 또는 tf 벡터에서 0이 나온 부분을 모두 0으로 채우고 시작하면 더 편합니다. 그 이외의 나머지 부분만 계산하여 채우면 되기 때문입니다. 


✍ 코사인 유사도 구하기

3) 아까 구한 가중치 벡터를 바탕으로 '코사인 유사도'를 계산해봅시다.

기존에 질의와 문서에 공통된 용어의 tf-idf 값을 합산하기만 하는 방식은 길이가 긴 문서에 높은 점수를 준다는 단점이 있습니다. 따라서 문서의 길이가 아닌 문서의 내용, 특성만을 기반으로 관련도를 따지려면 코사인 유사도를 사용해야합니다. 문서의 내용(의미)을 결정하는 것은 각 용어의 절대적 빈도수가 아닌, 서로 다른 용어들의 빈도수 분포(비율)이므로 거리 대신 각도를 고려해야하기 때문입니다.

 

https://blog.solarmagic.dev/ml/2021/04/16/pose-similarity/

코사인 유사도를 구하려면 우선, 벡터의 크기로 해당 벡터 내부의 각 요소를 나누어주어 정규화시켜야합니다.

tf-idf 가중치 철수 영이 데이터 컴구조 과목 좋아 싫어
문서 1 0 0 0.05 -0.04 -0.11 0.05 0
문서 2 0.05 0 -0.04 -0.07 0 0
문서 3 0 0 0 -0.04 -0.07 0 0

 

여기서 문서 1의 벡터 크기는 다음과 같이 구할 수 있습니다. (반올림하기 전의 나머지 값들은 무시하고 계산합니다)

그러면 약 0.14가 나옵니다.

tf-idf 가중치 철수 영이 데이터 컴구조 과목 좋아 싫어
문서 1 0 0 0.05 -0.04 -0.11 0.05 0

문서 1의 벡터 크기 (=약 0.14)

여기 tf-idf 가중치 벡터의 각 요소를 저 벡터 크기인 0.14로 나누어주면 됩니다.

tf-idf 가중치 철수 영이 데이터 컴구조 과목 좋아 싫어
문서 1 0 0 0.05 / 0.14 -0.04 / 0.14 -0.11 / 0.14 0.05 / 0.14 0

정규화된 tf-idf 벡터에서, 문서 1에서 '데이터'는 0.05/0.14, 문서 1의 '컴구조'는 -0.04/0.14, 마지막으로 문서 1의 '과목'은 -0.11/0.14로 계산할 수 있습니다. 이런 방식으로 정규화해줍니다.

(반올림 해놓은 값을 이용해 계산하는 등의 오차 요인이 많아, 아래 계산이 틀렸을 수도 있음. 계산법만 숙지하기)

문서 1 뿐만 아니라 문서 2와 문서 3의 벡터도 같은 방식으로 정규화 해줍니다.

 

정규화된 tf-idf 가중치 벡터

tf-idf (정규화) 철수 영이 데이터 컴구조 과목 좋아 싫어
문서 1 0 0 0.38 -0.27 -0.78 0.38 0
문서 2 0 0.56 0 -0.38 -0.72 0 0
문서 3 0 0 0 -0.47 -0.87 0 0

정규화 결과, 위와 같은 결과를 얻게 됩니다. 

 

코사인 유사도 계산 & 가장 유사한 문서쌍 찾기

이제 이 정규화된 벡터로, 어떻게 문서들 간의 코사인 유사도를 구할까요?

아주 간답합니다. 가중치 벡터들끼리 내적해주기만 하면 됩니다.

 

이제 문서들 간의 유사도를 구해볼까요

내적은 간단히 문서1과 문서 2에서 '철수'를 서로 곱한 것 + '' '영이'를 서로 곱한 것 + ... 로 계산할 수 있습니다.

문서 1과 문서 2의 내적 설명

예를 들어, 문서 1과 문서 2의 벡터를 내적할 때는 저렇게 각 요소 별로 곱한 다음에 그 값들을 모두 더해주면 되는 것입니다.

 

따라서 코사인 유사도는 아래와 같이 계산됩니다.

 

문서1&문서2 유사도 = 0 + 0 + 0 + (-0.27) * (-0.38) + (-0.78) * (-0.72) + 0 + 0 = 0.6669

문서1&문서3 유사도 = 0.8075

문서2&문서3 유사도 = 0.8097

 

계산 결과, 문서2~문서3이 유사도 점수가 가장 높았습니다. 즉, 문서 2와 문서 3이 가장 유사하다는 것으로 볼 수 있습니다.

 


 

지적이나 피드백, 댓글, 좋아요 전부 환영합니다. 포스팅이 도움이 되었다면 좋아요 눌러주세요~

안녕하세요 밥한그릇입니다.

오늘은 R언어에서 데이터 분석을 할 때, 결측치이상치를 처리하는 간단한 방법들에 대해 알아볼거에요.

이 과정은 데이터 전처리 중, 데이터 정제 과정에 속합니다.

 

결측치는 데이터 분석 시 값을 처리할 수 없어 필수적으로 처리해야하고요. 극단치(=특이치, 이상치)는 데이터의 분포 및 평균을 왜곡하는 경우가 많아 이것도 처리하는 게 좋습니다.

 

극단치에 의한 통계 왜곡의 대표 사례로는, 미국의 노스캐롤라이나 대학의 졸업생 연봉(초봉)평균의 사례가 유명합니다.

졸업생들의 평균이 가장 높은 학과가 지리학과로 한화로 평균 1억이 넘는 연봉을 기록했는데요. 어떻게 지리학과에서 이렇게 많은 연봉을 받나 원인을 알아보니, 전설적인 농구선수 마이클 조던의 연봉 때문에 평균이 덩달아 저렇게 올라간 것이었습니다. (...)

그 선수의 연봉이 워낙 천문학적이다보니, 저렇게 평균값이 왜곡되어 나온 것이죠.

그럼 저 학과의 졸업생 연봉이 대체로 어떤 지 제대로 평균값을 구하려면 조던과 같은 특이치 연봉을 제외하고 졸업생 연봉 평균을 구해야겠죠? 그래서 극단치 제거가 데이터 분석에서 중요한 것이랍니다~ 

 

그럼 이제 예제코드와 결과를 보며 그 처리방법을 소개하도록 하겠습니다.


다음은 제가 과제 중 만든 ppt의 일부입니다.

결측치(= NA값)는 저렇게 is.na()로 확인합니다.

그다음 연속형 변수의 결측치는 boxplot으로 파악하고, 명목형 변수의 결측치는 barplot으로 확인하고 있죠.

 

결측치 처리

결측치 여부는 저렇게 table(is.na(data))로도 알 수 있지만, 좀 더 자세하게 어디에서 결측치가 나는 지 알기 위해서 다음과 같은 방법을 사용해도 좋습니다.

- 변수(열) 별로 몇 개의 결측치가 나오는 지 확인

colSums(is.na(df))
#varA varB varC varD varE varF 
#   0    1    1    1    0    2

- 변수(열) 별로 결측치가 있는지 / 없는 지 확인

colSums(is.na(df)) == 0     # converts to logical TRUE/FALSE
#varA  varB  varC  varD  varE  varF 
#TRUE FALSE FALSE FALSE  TRUE FALSE

 

극단치가 있다면 어떻게 처리하는가?

저는 다음과 같은 가장 간단한 방식을 주로 사용합니다.

 

- NA가 포함된 행을 제거

data <- na.omit(data)
# NA가 포함된 행을 전부 제거

- NA가 포함된 행의 값을 0으로 치환

data[is.na(data)] = 0

결측치를 0이 아닌 임의의 다른 값으로 채워주는 경우도 많다고 합니다. 참고하세요.

 

연속형 변수의 이상치(극단치) 처리

참고로, 이상치 = 극단치 = 특이값 입니다.

연속형(=수치형) 변수의 경우에는, boxplot을 출력하여 그 이상치를 처리할 수 있습니다.

 

boxplot에 대해서 더 자세히 알려면 다음의 링크를 클릭하여 살펴보고 옵시다.

https://bioinformaticsandme.tistory.com/245

 

[R] 상자그림(Box plot)

[R] 상자그림(Box plot) Start BioinformaticsAndMe 1. 상자그림(Box plot) : 상자그림은 특정한 수치 값을 기반으로 그려진, 자료 특성이 요약된 그래프 : 사분위수범위(Inter-Quartile Range;IQR) = Q1~Q3..

bioinformaticsandme.tistory.com

 

다음은 연속형의 이상치를 처리하는 예시 코드입니다.

하나의 변수만 예시로 처리해보겠습니다. 

 

boxplot(data$변수명)$stats를 시행하면 boxplot 도표와 함께 오른쪽과 같이 5행을 가진 어떤 리스트가 나와요. 

여기에서 주목해야할 부분은 첫번째 행과 5번째 행입니다.

0~12까지가 저기 boxplot의 수염선 범위이고요, 이 범위를 벗어나는 값들이 저기 boxplot에서 '하얀색 점'으로 표시된 이상치입니다.

그러니 극단치를 없애려면 저기 12보다 큰 값들을 없애면 된다는 거겠죠?

 

ifelse()를 통해 극단치 범위에 있는 값들을 NA로 치환해 준 뒤, NA가 포함된 행을 제거하면 극단치 처리가 끝납니다.

# 연속형 변수 극단치 파악 - box plot
boxplot(data$관공서.수)$stats # 극단치 O

# 극단치 제거
data$관공서.수 <- ifelse(data$관공서.수 > 12, NA, data$관공서.수) # 극단치 NA로 치환
data <- na.omit(data) # NA 포함된 행 제거

 

사실 저렇게 처리를 해줘도 boxplot을 출력했을 때 다시 하얀 점이 나타나기도 합니다. 이상치는 '상대적'이기 때문이죠. 이상치를 쳐내고 난 뒤의 값에서도 '상대적'으로 멀리 떨어진 값들을 그렇게 이상치로 표시할 수 있다는 이야기입니다. 다시 나온 이상치 부분들은 데이터를 분석하는 사람이 알아서 판단하여 처리해주면 되겠습니다.

 

명목형 변수의 극단치(특이값) 처리

이제 명목형 변수에서는 극단치를 어떻게 파악하고, 또 그것을 어떻게 처리할 수 있는 지 살펴볼까요?

 

다음은 예시 코드입니다.

여기에서는 먼저 백분위(%)를 통해 각 명목형 값들의 포함 비율을 먼저 살펴본 뒤, barplot을 통해 시각적으로 이를 표현합니다.

이 경우 따로 극단치라할만한 것은 없어보여서 처리하지 않았습니다. 그렇지만 만약 막대 그래프에서 한 명목형 값의 개수가 상대적으로 엄청 작게 나온다거나 하는 경우엔 그 값을 따로 빼버려야겠죠.

library(ggplot2)
library(patchwork) # 그래프 모아서 보기

# 명목형 변수 극단치 파악 - bar plot
prop.table(table(data$상권구분코드..발달상권.1.골목상권.2.기타.3..))
prop.table(table(data$대표상권유형))
prop.table(table(data$소속구역...소속구역.한정.1.소속구역.반경.500m.2.소속구역.반경.1000m.3.)) # 명목형 변수 분포 확인

# bar plot -- 눈에 띄는 극단치는 없음
p1 <- ggplot(data, aes(x=상권구분코드..발달상권.1.골목상권.2.기타.3..)) +
  geom_bar(width=0.7, fill="steelblue")
p2 <- ggplot(data, aes(x=대표상권유형)) +
  geom_bar(width=0.7, fill="steelblue")
p3 <- ggplot(data, aes(x=소속구역...소속구역.한정.1.소속구역.반경.500m.2.소속구역.반경.1000m.3.)) +
  geom_bar(width=0.7, fill="steelblue")
p1 + p2 + p3 # 그래프 모아서 보기

 

그럼 극단치가 나온 경우에는 어떻게 처리해야하나요?

 

그래서 다음 예제 코드를 통해 임의로 변수 필터링을 시켜봤습니다.

수가 많은 상업지역, 역세권, 주거지역 쪽 데이터 쪽만 뽑은 새로운 데이터셋을 구성해보았습니다.

만일 극단치가 나온 상황이라해도, 이렇게 극단치 이외의 명목 값들만 뽑아서 새로운 데이터셋을 만들 수 있겠죠. 이렇게 극단치인 값을 배제하여 처리할 수 있습니다.

# 기존 명목형 값 
table(data$대표상권유형) # 기존 명목형 값 분포
ggplot(data, aes(x=대표상권유형)) +
  geom_bar(width=0.7, fill="steelblue")

# 필터링 
new_data <- subset(data, c(대표상권유형 == "상업지역" | 대표상권유형 == "역세권" |  대표상권유형 == "주거지역"))

# 새로운 명목형 값
table(new_data$대표상권유형) # 새로운 명목형 값 분포
ggplot(new_data, aes(x=대표상권유형)) +
  geom_bar(width=0.7, fill="steelblue") # 시각화

table() 실행 결과 : 각 명목형 값의 개수
명목형 데이터의 분포를 시각화

 


이상으로 결측치와 이상치(=  극단치, 특이치)를 처리하는 간단한 방법을 소개하는 것이 끝났네요.

이 방법 이외에도 무수히 많은 방법들이 존재하고요, 해당 값을 제거하는 것이 나을 지 아닐 지 판단하는 객관적 방법들도 여러 가지입니다.

저와 같은 경우는 약간은 주관적인 기준으로 간단하게 처리해본 것입니다. 따라서, 다른 여러가지 인터넷 문서들도 함께 참고하여, 상황에 따라 올바른 데이터 정제를 수행하는 것을 추천합니다.

 

제 포스팅이 여러분께 도움이 되었으면 좋겠습니다. 도움이 되었다면 하단의 공감(좋아요) 버튼 눌러주세요

댓글과 지적 모두 환영합니다!

안녕하세요 밥한그릇입니다.

요즘 과제나 배우는 내용이 많아 바쁘다보니, 오히려 포스팅을 자주 못하게 되는 것 같네요.

그러나 공부하던 중 인상깊었던 지식을 공유하고 싶어 포스팅을 씁니다.

 


 

R로 데이터 분석을 하다보면, 가장 중요한 과정 중 하나가 변수 선택이죠.

데이터셋에 있는 여러 변수 중 결과에 영향력 있는 변수들만 따로 뽑아, 그걸로 모델을 학습시켜야하니까요.

그렇게 선별한 변수 집합에 서로 다중공선성이 있는 변수들이 포함되거나, 결과와 상관도가 낮은 변수들이 많으면 모델의 성능 및 분석의 정확도가 떨어질 수 있습니다.

그래서 변수의 수는 적게, 성능은 높게 해줄 변수 집합을 찾아야해요.

 

이 과정에서 사용할 수 있는 방법 중 하나가 PCA 분석입니다.

PCA 분석을 통해 차원 축소를 하여, 변수 개수를 줄일 수 있습니다.

말이 좀 어렵죠? 그냥 변수 여러 개를 합쳐서 새로운 변수들을 구성한다고 보면 됩니다.

 

예를 들면, 변수 3개를 새로운 변수 2개(a, b)로 차원축소 한다고 해봅시다. pca 함수를 돌려보니 변수1은 70%, 변수2는 40% 변수3은 10%를 반영한 새로운 변수 a가 만들어졌다고 쳐봐요. 또 변수1은 60% 변수2는 10%, 변수3을 60% 반영한 새로운 변수 b가 만들어지는 거죠. 예를 들자면 이런 원리입니다. 마치 선형 회귀와도 비슷하네요.

 

그럼 정리해봅시다.

PCA(주성분 분석)이란 무엇인가?

주성분 분석(PCA)기존 변수들의 선형 결합을 이용하여 새로운 축(변수) 만든다.
각 주성분 간의 상관계수는 0이기 때문에 주성분 값으로 회귀분석을 진행하게 될 경우, 다중공선성을 걱정하지 않아도 된다
효과적인 차원축소로 모델을 간단하게 만들 수 있다.
좌표공간의 수많은 직선 중 가장 분산이 넓은 위치의 직선에 좌표를 사영
사영된 좌표값의 중복을 최소화하여 데이터의 유실을 줄이기 위함이다.

다음은 차원 축소의 원리를 그림으로 나타낸 것입니다. 이 포스팅은 예제 부분이 메인이니, 원리 부분은 자세한 설명을 건너뛸게요.

좌표공간의 수많은 직선 중 가장 분산이 넓은 위치의 직선에 좌표를 사영

좌표공간의 수많은 직선 중 가장 분산이 넓은 위치의 직선에 좌표를 사영

1. 상관계수 분석

변수들 간의 선형 관계가 높지 않으면 굳이 PCA 분석을 할 필요가 없다고 합니다.

따라서 먼저 상관계수 분석이 필요합니다.

일단 제가 고른 데이터셋으로 상관계수 분석을 해본 결과, 대부분 1에 가까운 값으로 선형도가 매우 높게 나왔습니다.
Corr_mat = cor(data)
corrplot(Corr_mat, method = "color", outline = T, addgrid.col = "darkgray", 
        order="hclust", addrect = 4, rect.col = "black", 
        rect.lwd = 5,cl.pos = "b", tl.col = "indianred4", 
        tl.cex = 0.5, cl.cex = 0.5, addCoef.col = "white", 
        number.digits = 2, number.cex = 0.4, 
        col = colorRampPalette(c("darkred","white","midnightblue"))(100))
# 변수들 간의 선형관계가 높음 -> 주성분 분석을 통해 차원 축소 가능.

변수들 간의 선형관계 파악

2. PCA 추출, 플롯 출력

주어진 변수를 통해 PCA를 추출했습니다.

PC3까지가 개별 변동이 13% 이상이고 누적 변동이 82%가 나오는 군요. 그 이후로는 개별 변동이 크지 않아 pc3까지 고르는 것이 적절해보입니다.

또한 pca를 플롯하였을 때 elbow point도 3이 나와 PC1부터 PC3까지의 변수만 사용하기로 했습니다.

(참고로 elbow point는 그래프의 기울기가 갑자기 변하는 지점. 팔꿈치와도 같다고 하여 저렇게 부른다.)

# 데이터에서 주성분을 추출하여 변수를 축소하기 위한 기법이 바로 PCA
pca_dt <- prcomp(data,scale.unit = TRUE, graph = FALSE)
pca_dt
plot(pca_dt, type = "l") # elbow point = 3
summary(pca_dt) # PC3까지만 변수를 사용해도 데이터의 약 82%만큼의 변동을 설명할 수 있음

 

3. PCA 성분 선택

앞서 PCA 성분 중 pc1~pc3을 선택하는 것이 좋다고 결론 내렸으므로, 그렇게 합니다.

새롭게 변수를 구성했으므로, 이를 Plot 하여 데이터의 분포를 살펴보면 다음과 같습니다.

# First for principal components
comp <- data.frame(pca_dt$x[,1:3])
# Plot
plot(comp, pch=16, col=rgb(0,0,0,0.5))

 

4. PCA 성분으로 K-means 군집화 수행 - PCA 수행하지 않은 경우와 비교

군집화를 수행하여 그 성능을 비교하면 다음과 같습니다.

k-means 군집화는 거리기반 알고리즘이라 고차원에서 성능이 떨어지기 쉬우므로, 차원축소 시 성능 향상에 매우 효과적입니다.

set.seed(1234)
# 일반 변수들을 모두 포함한 군집화 수행 결과 
k_var <- kmeans(data, 3, iter.max=1000)
palette(alpha(brewer.pal(9,'Set1'), 0.5))
plot(data, col=k_var$clust, pch=16)
k_var # (between_SS / total_SS =  46.8 %)

# pca를 통한 군집화 수행 결과
k_pca <- kmeans(comp, 3, iter.max=1000)
palette(alpha(brewer.pal(9,'Set1'), 0.5))
plot(comp, col=k_pca$clust, pch=16)
k_pca # (between_SS / total_SS =  56.3 %))

 

5. PCA로 군집화한 결과의 의미 분석

모델의 성능(정확도)이 좋다고 해도, 그 의미를 모르면 무용지물이겠죠. 결과물이 어떤 의미를 가지는 지 알아야 합니다.

이때 의미에 대한 분석은 먼저 각 변수가 어떤 의미를 가지는 지 알아야 가능해요.

 

다음은 그전 군집화 모델에서 약간의 최적화를 더 수행하여 나온 모델(성능 약 60.6%)의 결과입니다. 군집의 중심점을 기준으로 군집화 결과를 분석하였습니다.

 

저는 다음과 같은 단계를 통해 임의로 의미를 분석해보았습니다.

 

a) 군집의 중심점과 각 변수의 분포 범위를 사용해서 군집의 대략적인 위치를 파악합니다.

b) 그 분포 범위를 각 성분에 적용해서 그 성분의 의미를 찾습니다.

c) 임의로 그 성분에 대한 이름을 지어주고, b번의 결과에 적용합니다.

d) c번을 이용해 데이터에 대한 분석(결론)을 도출합니다. 

pca 성분의 방향성
(a) 군집의 중심점과 각 변수의 분포 범위를 사용해서 군집의 위치를 파악
(b) 그 분포 범위를 각 성분에 적용 + 그 성분의 의미를 찾기 (높다, 보통, 낮다 등) / (c) 임의로 그 성분에 대한 이름을 지어주고, b번의 결과에 적용
(d) c번을 이용해 데이터에 대한 분석(결론)을 도출

 

우리가 데이터 마이닝을 할 때, 수치형 변수가 아닌 값을 수치형으로 변환시켜야하는 경우가 많다.

직접 하나하나 숫자를 대입시켜 주기는 너무 힘들다. 그 때 이 방법을 유용하게 쓸 수 있다.

 

결과는 다음과 같다.

숫자로 모두 매핑하기 전의 데이터 프레임과, 변환하여 완성한 새로운 데이터프레임의 값을 비교한 것이다.

passanger 열의 값을 보면 잘 변환된 것을 알 수 있을 것이다.

 

이렇게 문자열 형태의 값들을 숫자로 매핑시키는 아주 쉬운 방법을 소개한다.

 

그 방법은 이거다.

as.numeric(변수 열(행)) # factor형 값이라면
as.numeric(as.factor(변수 열(행)) # character형 값이라면

이게 끝이다.

 

활용 예시 :

d <- data.frame("toCoupon_GEQ25min"=df$toCoupon_GEQ25min ,"destination"=as.numeric(df$destination), 
	"passanger"=as.numeric(df$passanger), "weather"= as.numeric(df$weather),
    "time"=as.numeric(df$time), "coupon"=as.numeric(df$coupon),"expiration"= as.numeric(df$expiration), "CoffeeHouse"=as.numeric(as.factor(df$CoffeeHouse)),"Y"=as.numeric(df$Y))

이런 식으로 기존 데이터프레임의 명목형 값들을 수치형으로 모두 변환한 새로운 데이터프레임을 구성할 수 있다.

요즘 학교에서 데이터 사이언스 수업을 듣는데, 과제가 아주 많다.. ㅠㅠ

팀플이라서 항상 코드가 많은데, 뭐 다 구글링으로 만든거기도 해서 정리를 잘 안해뒀다.

그치만 r 코드 관련 자료는 그렇게 엄청 많지도 않아서, 검색해도 잘 안나오기도 한다. 원하는 자료를 찾으려면 영어로 검색하는 게 훨씬 쉬울 정도이다.

 

그래서 내가 만든 코드라도 남들에게 도움이 될까해서 올려본다.

 

댓글과 지적 환영합니다~

 

nomi_data <- data

nomi_data <- transform(nomi_data, 
                  sepal_length = cut(sepal_length, breaks = c(seq(min(data[1]), max(data[1]), length=4)),
                  include.lowest = TRUE,
                  right = FALSE,
                  labels = c("S", "M", "L")
                  ),
                  sepal_width = cut(sepal_width, breaks = c(seq(min(data[2]), max(data[2]), length=4)),
                  include.lowest = TRUE,
                  right = FALSE,
                  labels = c("S", "M", "L")
                  ),
                  petal_length = cut(petal_length, breaks = c(seq(min(data[3]), max(data[3]), length=4)),
                  include.lowest = TRUE,
                  right = FALSE,
                  labels = c("S", "M", "L")
                  ),
                  petal_width = cut(petal_width, breaks = c(seq(min(data[4]), max(data[4]), length=4)),
                  include.lowest = TRUE,
                  right = FALSE,
                  labels = c("S", "M", "L")
                  ))

nomi_data

 

참고로, data는 iris 데이터셋의 dataframe을 미리 불러온 것이다.

명목형 변수로 바꾼 값들은 nomi_data라는 형식으로 새롭게 저장해준다.

 

seq(min(data[i]), max(data[i]), length=4))를 통해, 해당 속성에서 최대값과 최소값 사이의 4개의 구간의 경계점을 구했다.

(참고로, 구해온 값은 벡터 형식)

그리고 cut()의 breaks 변수에 이를 넣어 그 구간에 맞춰 나누어주었다. 이때, 각각 S, M, L의 3개의 명목형 변수를 붙여주었다.

length에 n+1을 넣고 labels에 n개의 라벨을 넣어준다고 생각하자!  

 

참고로 라벨 이름은 스몰, 미들, 라지 사이즈의 이니셜을 따와 붙인 것이다. ㅎㅎ

 

코드 실행 예시.

내가 사용한 dataset이 150줄인가 그렇다. 15페이지까지 있으니 뒤로 넘기면 더 다양한 결과를 볼 수 있을 것이다.

 

summary()를 통해 이렇게 분포가 된 것을 확인할 수도 있다.

+ Recent posts