Must Learning With Statistics

5. R 기본문법 4단계 본문

MustLearning with R 1편

5. R 기본문법 4단계

Doublek Park 2020. 1. 29. 20:12

R

Chapter5. R 기본문법 4단계

이번 챕터에서는 이전에 했던 데이터에 비해서는 좀 더 어려운 데이터를 통해 R을 익혀보도록 하겠습니다.

1. 데이터 불러오기 및 데이터 설명

데이터 출처는 Kaggle이며, 해당 드롭박스 링크에서 받으실 수 있습니다.

링크 : https://www.dropbox.com/sh/xx1w2syi768kfu0/AACZgxgo1fcxyDMgv9U-iTz8a?dl=0

# 데이터 불러오기
IMDB = read.csv("D:\\Dropbox\\DATA SET(Dropbox)\\IMDB-Movie-Data.csv")

변수 설명

  • Rank
  • Title : 영화 제목
  • Genre : 영화 장르
  • Description : 영화 설명
  • Director : 감독명
  • Actors : 배우
  • Year : 영화 상영 년도
  • Runtime..Minutes : 상영시간
  • Rating : Rating 점수
  • Votes : 관객 수
  • Revenue..Millions : 수익
  • Metascore : 메타 스코어

2. 결측치(Missing Value)

결측치에 대한 정의

결측치(Missing Value)는 말 그대로 데이터에 값이 없는 것을 뜻합니다. 줄여서 ’NA’라고 표현하기도 하고, 다른 언어에서는 Null 이란 표현을 많이 씁니다. 결측치는 데이터를 분석하는데에 있어서 매우 방해가 되는 존재입니다. 결측치는 다음과 같은 문제를 야기합니다.

먼저 결측치를 다 제거해버리는 경우, 결측치의 비율에 따라서 막대한 데이터 손실을 불러일으킬 수 있습니다. 다음으로는 결측치를 잘못 대체할 경우, 데이터에서 편향(bias)이 생길 수가 있습니다. 또한 결측치를 처리하는 데에 있어 분석가의 견해가 가장 많이 반영되기 때문에 대체가 잘못될 경우 분석결과가 매우 틀어질 수도 있습니다.

결론은 결측치를 자세히 처리하기 위해서는 시간이 많이 투자되어야 합니다. 무엇보다, 데이터에 기반한 결측치 처리가 진행되어야 분석을 정확하게 진행할 수 있습니다.

3. R을 통한 결측치 처리

결측치 확인

# Metascore 변수 내에서 결측치 논리문 판단 (TRUE, FALSE)
is.na(IMDB$Metascore)[1:20]
 [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[12] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
# Metascore 변수 내에 결측치 갯수
sum(is.na(IMDB$Metascore)) 
[1] 64
# IMDB 내 모든 변수별 결측치 갯수
colSums(is.na(IMDB))
              Rank              Title              Genre 
                 0                  0                  0 
       Description           Director             Actors 
                 0                  0                  0 
              Year  Runtime..Minutes.             Rating 
                 0                  0                  0 
             Votes Revenue..Millions.          Metascore 
                 0                128                 64 

결측치 제거

전부 삭제하는 경우, 가장 극단적인 방법입니다.(결측치가 하나라도 포함된 obs(행)은 삭제)

IMDB2 = na.omit(IMDB) 
colSums(is.na(IMDB2))
              Rank              Title              Genre 
                 0                  0                  0 
       Description           Director             Actors 
                 0                  0                  0 
              Year  Runtime..Minutes.             Rating 
                 0                  0                  0 
             Votes Revenue..Millions.          Metascore 
                 0                  0                  0 

특정 변수에 결측치가 존재하는 행만 삭제하는 경우

# 12번째 열에 결측치가 있는 경우에만 삭제
IMDB3 = IMDB[complete.cases(IMDB[ ,12]),]
colSums(is.na(IMDB3))
              Rank              Title              Genre 
                 0                  0                  0 
       Description           Director             Actors 
                 0                  0                  0 
              Year  Runtime..Minutes.             Rating 
                 0                  0                  0 
             Votes Revenue..Millions.          Metascore 
                 0                 98                  0 

결측치를 특정값으로 대체할 경우

  • is.na가 True인 값(결측치)들에 대해 58.99 지정
# Rawdata를 새로운 변수에 복사 
IMDB$Metascore2 = IMDB$Metascore 

# 결측치 대체
IMDB$Metascore2[is.na(IMDB$Metascore2)]=58.99 

결측치 생략하고 계산할 경우

mean(IMDB$Revenue..Millions.) # NA 생성
[1] NA
mean(IMDB$Revenue..Millions.,na.rm = TRUE) # NA 생략하고 계산
[1] 82.95638

4. 결측치 처리시 주의할 점

데이터를 다룰 때, 기본적으로 raw 데이터는 절대 건드리는 것이 아닙니다.보통의 경우, 데이터를 새로 복사를 해두고 진행을 하는 것이 나중을 위해 매우 좋은 습관이 됩니다. 때로는 나머지 정보를 잃지 않기 위하여 최대한 합리적으로 결측치를 대체해야 할 필요가 있습니다. 가장 기본적인 방법은 다음과 같습니다.

  • 연속형 변수 : 평균으로 대체

  • 이산형 변수 : 최빈값으로 대체

하지만 이렇게 무턱대고 결측치를 대체하는 경우에는 데이터가 심하게 쏠릴 수가 있습니다. 결측치를 대체할 때는 항상 다음의 사항들을 확인해야 됩니다.

  1. 결측치의 비율

    만약, 결측치의 비율이 상당한 경우, 일단 지우고 보는 방식은 크나큰 정보 손실을 불러올 수가 있습니다.

  2. 데이터의 분포

    데이터를 분석 할 때, 항상 데이터의 분포를 확인하고 진행을 해야됩니다. 데이터가 평균을 중심으로 균형있게 퍼져있는 ’정규분포’형태를 뛰고 있는 경우라면 모르겠지만, 현실의 대부분 데이터는 그렇게 이상적이지는 않기 때문입니다. 다음의 그래프를 보면서 확인해보겠습니다.

  3. 다른 변수와의 관계가 있는지

    다른 변수와의 관계를 파악하여, 결측치를 대체하는 방법입니다. 전문적인 용어로는 ’Mssing value Imputation’이라고 하는데, 해당 방법론은 이 책의 수준을 벗어나기때문에 다루지 않도록 하겠습니다.

5. 결측치 처리를 위한 데이터의 분포 탐색

결측치를 처리하기 위하여, Revenue..Millions.의 분포에 대해 확인해보도록 하겠습니다.

library(ggplot2)

ggplot(IMDB,aes(x=Revenue..Millions.)) +
geom_histogram(fill='royalblue', alpha = 0.4) +
ylab('') +
xlab("Revenue_Millions") +
theme_classic()

ggplot(IMDB,aes(x = "",y=Revenue..Millions.)) +
geom_boxplot(fill='red', alpha = 0.4,outlier.color = 'red') +
xlab('') +
ylab("Revenue_Millions") +
theme_classic()

)

summary(IMDB$Revenue..Millions.)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
   0.00   13.27   47.98   82.96  113.72  936.63     128 

Revenu_Millions를 볼 때 데이터가 한쪽으로 매우 치우쳐져 있습니다. 이런 분포는 항상 평균과 중위수의 차이를 확인해봐야합니다. (평균: 82.96, 중위수: 47.98) 따리서, 이런 분포의 데이터의 결측치는 평균으로 대체하면 매우 위험합니다. 평균은 극단값(Outlier, 이상치)에 영향을 받기 때문입니다. 극단값은 패턴을 벗어난 특수한 상황이지, 결코 일반적인 상황을 대변해주지 않습니다.

그럼 어떻게 해야하는가?

평균보다는 중위수가 안전합니다. 중위수는 극단값(Outlier)에 영향을 받지 않기 때문입니다. 물론 중위수로 대체 하는 것은 차선책이지 완벽한 방법은 아닙니다. 다른 변수들과의 관계를 보면서 대체하는 것은 많은 시간을 요구하지만, 더 정교한 분석결과를 확인할 수 있습니다.

6. 이상치(Outlier) 뽑아내기

이상치(Outlier)는 ‘패턴에서 벗어난 값’으로 정의를 내릴 수 있습니다. 또는 ’중심에서 좀 많이 떨어져 있는 값’ 이라고 할 수 있습니다. 이상치는평균에 막대한 영향을 미칩니다. 예를 들어 [1,2,3,4,5] 의 평균은 3이지만, [1,2,3,4,100]의 평균은 22입니다. 하지만 중위수는 3으로 같습니다. 따라서 종종 평균으로 값을 나타내기보다는 중위수로 요약값을 나타내는 것이 더 현실을 반영하는 경우가 많습니다. 이상치 여부는 다음과 같은 식으로 계산을 합니다.

ggplot(IMDB,aes(x=as.factor(Year),y=Revenue..Millions.))+
  geom_boxplot(aes(fill=as.factor(Year)),outlier.colour = 'red',alpha=I(0.4))+
  xlab("년도") + ylab("수익") + guides(fill = FALSE) +
  theme_bw() +
  theme(axis.text.x = element_text(angle = 90))

박스플롯을 그린 이유는 이상치 탐색을 가장 하기 좋은 플롯이 박스플롯이기 때문입니다. 박스플롯의 구성을 살펴보시면 다음과 같습니다.

상자 안에 그려져 있는 직선은 중위수(Median)을 나타냅니다. 상자의 밑변은 1분위수를 나타애며, 윗변은 3분위수를 나타냅니다. 상자 테두리 외부에 직선이 있는 것을 볼 수 있습니다. 이 직선은 울타리라고 부릅니다.

상자로부터 아래 울타리의 계산식 : Q1 - 1.5 * (Q3 - Q1)

상자로부터 위 울타리의 계산식 : Q3 + 1.5 *(Q3 - Q1)

이 울타리를 벗어난 값들을 Outlier라고 부릅니다. Outlier는 기본적으로 통계추정에 있어서 방해가 되고는 합니다. 통계분석은 전부 귀납법인데, 이상치같은 특수 케이스가 규칙을 만드는데 방해가 되기 때문입니다.

  • Outlier의 처리방법

제거를 하는 방법이 쓰이기는 하지만, 개인적으로 좋아하는 방식은 아닙니다. 어찌됐든 데이터를 버리는거니깐요. 데이터 변형을 통해 Outlier문제를 줄여줍니다. 통계추정에세는 정규분포를 맞추어 주는 것이 매우 중요합니다. 보통 Outlier로 인해 한 쪽으로 치우친 분포는 log 변환을 통해 정규성을 맞추어주고는 합니다. (이 부분은 후에 다루도록 하겠습니다.)

# Outlier인 데이터 제거하기

# 1분위수 계산
Q1 = quantile(IMDB$Revenue..Millions.,probs = c(0.25),na.rm = TRUE) 
# 3분위수 계산
Q3 = quantile(IMDB$Revenue..Millions.,probs = c(0.75),na.rm = TRUE)

LC = Q1 - 1.5 * (Q3 - Q1) # 아래 울타리
UC = Q3 + 1.5 * (Q3 - Q1) # 위 울타리

IMDB2 = subset(IMDB,
     Revenue..Millions. >  LC & Revenue..Millions. < UC)

7. 문자열 데이터 다루기 1편

이번에는 문자열 데이터를 처리하는 방법에 대해 다루겠습니다.

  • 문자열을 다룰 때 기본적으로 숙지하고 있어야 하는 명령어는 다음과 같습니다.
  • 문자열 대체 : gsub()
  • 문자열 분리 : strsplit()
  • 문자열 합치기 : paste()
  • 문자열 추출 : substr()
  • 텍스트마이닝 함수: Corpus() & tm_map(), & tdm()

문자열 추출

substr(IMDB$Actors[1],1,5)
[1] "Chris"
  • 첫번째 obs의 Actors변수에서 1 ~ 5번째에 해당하는 문자열 추출

문자열 붙이기

paste(IMDB$Actors[1],"_",'A') # 첫번째 obs의 Actors변수에서 _ A 붙이기
[1] "Chris Pratt, Vin Diesel, Bradley Cooper, Zoe Saldana _ A"
paste(IMDB$Actors[1],"_",'A',sep="") # 띄어쓰기 없이 붙이기
[1] "Chris Pratt, Vin Diesel, Bradley Cooper, Zoe Saldana_A"
paste(IMDB$Actors[1],"_","Example",sep="|") # |로 붙이기
[1] "Chris Pratt, Vin Diesel, Bradley Cooper, Zoe Saldana|_|Example"
  • paste()는 기본적으로 붙이는 문자열 사이에 " “(한칸 빈칸)이 기본 설정입니다. 이를 수정하기 위해서는 sep =”"옵션을 주어야 합니다.

문자열 분리

strsplit(as.character(IMDB$Actors[1]), split= ",") 
[[1]]
[1] "Chris Pratt"     " Vin Diesel"     " Bradley Cooper" " Zoe Saldana"   

문자열 대체

IMDB$Genre2=gsub(","," ",IMDB$Genre) # , 를 띄어쓰기로 대체 
  • gsub은 데이터 핸들링에서 매우 많이 사용되는 명령어이기에 꼭 기억하고 있어야 합니다.

8. 문자열 데이터 다루기 2편

텍스트 마이닝 1

텍스트마이닝의 절차는 다음과 같습니다.

  1. 코퍼스(말뭉치) 생성
  2. TDM(문서 행렬) 생성
  3. 문자 처리(특수문자 제거, 조사 제거, 숫자 제거 등..)
  4. 문자열 변수 생성

Genre 변수는 영화에 대한 장르를 나타냅니다. 하지만 모든 영화가 하나의 장르에만 해당되는 것이 아니라, 여러 장르에 해당되는 것을 알 수 있습니다. 이를 분석하기 위해서는 각 영화가 어느 장르에 해당되는지 나타내줄 수 있는 변수를 만들어야 합니다. 해당 변수를 만들기 위해서 텍스트 마이닝 기법을 적용시켜보도록 하겠습니다.

  • 1단계 : 코퍼스 생성

영어의 경우, 대문자와 소문자가 다른 글자로 인식되기 때문에 바꿔주는 작업이 필요합니다.

library(tm) # tm 패키지 설치 필요

CORPUS = Corpus(VectorSource(IMDB$Genre2)) # 코퍼스 생성
CORPUS_TM = tm_map(CORPUS,removePunctuation) # 특수문자 제거
CORPUS_TM = tm_map(CORPUS_TM, removeNumbers) # 숫자 제거 
CORPUS_TM = tm_map(CORPUS_TM, tolower) # 알파벳 모두 소문자로 바꾸기

Corpus는 말뭉치라는 의미로, 텍스트 마이닝을 하기 전에 문자열 데이터를 정리하는 과정이라고 생각하시면 될 것 같습니다.

  • 2단계 : 문서행렬 생성

문서행렬을 만드는 이유는 다음과 같은 이유입니다.

  • 특정 단어를 변수로 만들어, 분석에 사용하려는 목적
  • 특정 단어가 포함되어 있는 데이터만 따로 추출하거나 특정 단어가 많이 등장하였을 때, 이것이 다른 무언가와 상관성이 있는지 분석하기 위한 목적

즉, 문자열 데이터를 가지고 통계적인 분석을 하기 위한 준비과정이라고 생각하시면 될 것 같습니다.

TDM=DocumentTermMatrix(CORPUS_TM) # 문서행렬 생성
inspect(TDM)
<<DocumentTermMatrix (documents: 1000, terms: 20)>>
Non-/sparse entries: 2555/17445
Sparsity           : 87%
Maximal term length: 9
Weighting          : term frequency (tf)
Sample             :
    Terms
Docs action adventure comedy crime drama horror mystery romance scifi
  1       1         1      0     0     0      0       0       0     1
  10      0         1      0     0     1      0       0       1     0
  11      0         1      0     0     0      0       0       0     0
  12      0         0      0     0     1      0       0       0     0
  2       0         1      0     0     0      0       1       0     1
  4       0         0      1     0     0      0       0       0     0
  5       1         1      0     0     0      0       0       0     0
  6       1         1      0     0     0      0       0       0     0
  7       0         0      1     0     1      0       0       0     0
  9       1         1      0     0     0      0       0       0     0
    Terms
Docs thriller
  1         0
  10        0
  11        0
  12        0
  2         0
  4         0
  5         0
  6         0
  7         0
  9         0
TDM = as.data.frame(as.matrix(TDM)) # 문서행렬을 데이터프레임 형태로 만들어주기.
head(TDM) 
  action adventure scifi mystery horror thriller animation comedy family
1      1         1     1       0      0        0         0      0      0
2      0         1     1       1      0        0         0      0      0
3      0         0     0       0      1        1         0      0      0
4      0         0     0       0      0        0         1      1      1
5      1         1     0       0      0        0         0      0      0
  fantasy drama music biography romance history crime western war musical
1       0     0     0         0       0       0     0       0   0       0
2       0     0     0         0       0       0     0       0   0       0
3       0     0     0         0       0       0     0       0   0       0
4       0     0     0         0       0       0     0       0   0       0
5       1     0     0         0       0       0     0       0   0       0
  sport
1     0
2     0
3     0
4     0
5     0
 [ reached 'max' / getOption("max.print") -- omitted 1 rows ]
  • 3단계 : 기존데이터와 결합하기
IMDB_GENRE = cbind(IMDB,TDM)

기존에 있는 데이터와, 장르 변수들로 구성된 데이터를 합쳐야 합니다. 합쳐야 할 두 데이터가 같은 행을 가지고 순서도 같다면, cbind(Column bind) 명령어를 쓰면 됩니다. 만약, 반대로 합쳐야 할 두 데이터가 같은 열을 가지고, 순서도 같은데, 행을 합쳐야 한다면 rbind(row bind) 명령어를 쓰면 됩니다.

데이터 결합하기 명령어

  • cbind : 행이 동일하고, 순서도 같을 때 옆으로(변수) 합치기

  • rbind : 열이 동일하고, 순서도 같을 때 아래로(obs) 합치기

  • merge : 열과 행이 다른 두 데이터 셋을 하나의 기준을 잡고 합치고자 할 때 사용

Genre 변수를 통해 간단하게 다룰어봤다면, 이번에는 Description 변수를 통해 복습도 할겸 다루어보도록 하겠습니다. Description 변수는 Genre 변수와 비교했을 때, 다음의 차이점이 존재합니다.

  1. 단어의 중복 등장
  2. 조사, 동사, 명사 등 등장

1단계 : stopwords를 이용한 단어 제거

library(tm)
CORPUS=Corpus(VectorSource(IMDB$Description))
CORPUS_TM = tm_map(CORPUS,stripWhitespace)
CORPUS_TM = tm_map(CORPUS_TM,removePunctuation)
CORPUS_TM = tm_map(CORPUS_TM, removeNumbers)
CORPUS_TM = tm_map(CORPUS_TM, tolower)

DTM = DocumentTermMatrix(CORPUS_TM)
inspect(DTM)
<<DocumentTermMatrix (documents: 1000, terms: 5960)>>
Non-/sparse entries: 20465/5939535
Sparsity           : 100%
Maximal term length: 23
Weighting          : term frequency (tf)
Sample             :
     Terms
Docs  and for from her his that the their who with
  155   2   1    1   0   0    0   4     0   0    2
  232   1   0    0   0   1    0   2     0   1    2
  323   1   2    0   0   0    0   3     0   0    0
  324   1   0    0   0   0    1   5     0   0    1
  704   0   0    1   1   0    0   2     0   1    1
  764   4   0    1   0   0    0   1     2   0    0
  774   0   0    3   2   2    2   3     0   1    1
  836   2   0    0   0   0    0   5     1   0    0
  863   2   0    1   0   3    0   0     1   1    1
  960   2   0    0   0   1    0   5     0   0    0

and, for, from, with 이런 단어들은 자주 쓰이는 단어이지만, 실제로 의미를 전달하는 단어는 아닙니다. 그러므로 텍스트마이닝 시에는 이런 단어들을 제거해주는 것이 더 원할한 분석을 진행할 수 있습니다.

CORPUS_TM = tm_map(CORPUS_TM,removeWords, 
         c(stopwords("english"),"my","custom","words"))

stopwords 기능을 사용하면 and, his같은 단어들을 모두 삭제할 수 있습니다. 더 나아가 추가로 삭제하고 싶은 단어는 c( )명령어 안에 넣어주면 삭제를 시킬 수 있습니다. 현재는 ‘my’, ‘custom’,‘words’ 이 3가지 단어를 추가적으로 삭제시킨 상황입니다.

2단계 : 중복등장 단어 처리 결정

문장을 분해한 경우, 중복단어처리를 어떻게 하느냐도 결정해야될 사항입니다.

  • 1안 : 특정 단어가 문장에 포함되어 있냐 없냐로 표시 -> 0 , 1로 코딩 (0: 포함 x, 1: 포함 o)
convert_count = function(x) {
y <- ifelse(x > 0,1,0)
y = as.numeric(y)
y
}
  • 2안 : 특정 단어가 문장에서 몇번 등장했나를 표시 -> 등장 빈도로 코딩
convert_count = function(x) {
y <- ifelse(x > 0,x,0)
y = as.numeric(y)
y
}
  • 사용자 함수 적용

매트릭스 형태인 TDM에 convert_count를 하나씩 적용하여 값을 배출

DESCRIPT_IMDB=apply(DTM,MARGIN=2,convert_count)
DESCRIPT_IMDB=as.data.frame(DESCRIPT_IMDB)

3단계 : 문자열 데이터 시각화

# Temr Document Matrix 생성

TDM = TermDocumentMatrix(CORPUS_TM)

# 워드클라우드 생성
m =  as.matrix(TDM) 
v = sort(rowSums(m),decreasing=TRUE) # 빈도수를 기준으로 내림차순 정렬
d = data.frame(word = names(v),freq=v) 
library("SnowballC")
library("wordcloud")
library("RColorBrewer")

# min.freq -> 최소 5번 이상 쓰인 단어만 띄우기
# max.words -> 최대 200개만 띄우기
# random.order -> 단어 위치 랜덤 여부

wordcloud(words = d$word, freq = d$freq, min.freq = 5,  
max.words=200, random.order=FALSE,
colors=brewer.pal(8, "Dark2"))

# 단어 빈도 그래프 그리기
ggplot(d[1:10,]) +
  geom_bar(aes(x = reorder(word,freq), y = freq), stat = 'identity') +
  coord_flip()+ xlab("word") + ylab("freq") +
  theme_bw()

)

텍스트 마이닝은 사실 통계적인 이용보다는 알고리즘에 기반한 Computer Science영역에 가깝습니다. 또한 텍스트를 제대로 분석하기 위해서는 형태소 분석을 기반으로 여러가지 알고리즘을 적용시켜야 그나마 깔끔하게 정리되는 경우가 많습니다. 현재는 R을 이용하여 비정형 데이터를 어떻게 가공하며, 이를 분석에서 사용하려면 어떻게 하는지에 집중을 하도록 하겠습니다. 기회가 된다면 후에 더 심화된 내용을 다루도록 하겠습니다.

9. 연습문제

  1. IMDB 데이터 셋의 Revenue Millions 변수에 존재하는 결측치를 모두 0으로 전환시켜 Revenue_NonNA라는 변수를 만드시오.

  2. Revenue Millions의 이상치범위를 계산해보세요

'MustLearning with R 1편' 카테고리의 다른 글

7. R 중급문법 3단계  (0) 2020.01.29
6. R 중급문법 2단계  (0) 2020.01.29
4. R 기본문법 3단계  (0) 2020.01.29
3. R 기본문법 2단계  (1) 2020.01.29
2. R 기본문법 1단계  (2) 2020.01.29
Comments