금융데이터 분석을 위한 R을 이용한 개발 입문

박찬엽

2017년 7월 20일

강사 소개

kakao : parkets
fb : mrchypark

대부분의 에러 문제를 해결할 수 있는 기초 자료형에 대한 이해

자료형에 대해 알고 있는 것과 자료형을 파악하는 것은 매우 중요합니다. 함수가 입력 데이터로 사용이 가능한 데이터를 입력 데이터로 지정해 주어야 실행이 되기 때문인데요. 많은 분들이 여기에서 문제가 발생합니다. 특히 작업을 진행하면서 기대하지 않은 자료형으로 중간 결과물이 나오는 경우에는 이후에 생기는 문제에 대해서 이해하기 어려울 수 있습니다.
그렇기 때문에 기본 R에서 제공하는 자료형에 대해서 공부해 두는 것은 대부분의 함수가 사용하는 데이터의 형식을 공부하는 것과 같습니다. 물론 패키지마다 자체 자료형을 정의하기도 합니다. 하지만 지금 기초 자료형을 공부하면서 새로운 자료형을 파악하는 방법을 자연스럽게 배울 수 있을 거라 생각합니다.

자료형을 파악하는 방법은 class 함수를 사용하는 것입니다.

x <- 1
class(1)
## [1] "numeric"
class(x)
## [1] "numeric"
class("x")
## [1] "character"

?class를 입력해서 class 함수에 대해서 파악해 보세요.

위에 class 함수의 결과로 나오는 것이 자료형의 종류입니다. numeric은 숫자를, character는 글자를 뜻합니다. 이제 아래에서 하나하나 확인해 보겠습니다. 기초 자료형에 대해서 MS의 edX 강의, datacamp, r-tutor의 내용을 참고했습니다.

기초 자료형

논리형 logical

logical(로지컬) 자료형은 기다 아니다 같이 True, False를 표현하기 위해 만들어진 자료형입니다. R에서는 각각 두 가지 표현법이 있습니다.

TRUE;  class(TRUE)
## [1] TRUE
## [1] "logical"
F;     class(F)
## [1] FALSE
## [1] "logical"

언어에 따라 True, False를 채택하는 경우도 있습니다만 R의 경우 위와 같이 전부 대문자인 글자를 logical 자료형으로 인식합니다.

이렇게 class 함수를 이용해서 자료형이 무엇인지 파악하는 것도 중요하지만, 조건문 등을 통해서 일을 자동적으로 수행하게 시키기 위해서는 class 같이 결과값이 다양한 것 보다는 logical 자료형으로 결과가 나오는 것이 앞으로 배우게 될 if 문 등에 조건으로 사용하기 좋습니다. 아래와 같은 is.logical 함수는 괄호안의 데이터가 logical 자료형이 맞는지를 물어보는 함수 입니다. 다른 자료형도 대부분 is.자료형이름을 함수로 사용할 수 있습니다.

test <- TRUE
is.logical(test)
## [1] TRUE
is.logical(TRUE)
## [1] TRUE
is.logical("test")
## [1] FALSE
R Studio에서는 함수의 자동완성 기능을 제공합니다. is.을 입력해서 다양한 자료형을 확인하는 명령어를 구경해보세요.
autocomplete

숫자형 numeric

numeric(뉴메릭) 자료형은 그 영어 뜻이 숫자의인 것 같이 숫자를 표현하는 자료형입니다. 물론 복소수 같이 다른 여러 표현들도 사용할 수 있습니다만, R에서 숫자는 보통 numeric 자료형으로 처리합니다.

2
## [1] 2
class(2)
## [1] "numeric"
is.numeric(2)
## [1] TRUE

numeric은 소수점 이하 값을 가지는 숫자도 포함합니다.

2.5
## [1] 2.5
class(2.5)
## [1] "numeric"
is.numeric(2.5)
## [1] TRUE

숫자를 표현하는 자료형으로 integer도 있습니다. 영어 뜻 그대로 정수를 표현하는 자료형인데요, 소수점이 없는 숫자를 표현할 때 사용합니다. R에서는 integer로 바로 인식시키기 위해서 정수인 숫자 뒤에 L(대문자 엘)을 붙여서 표현합니다.

2L
## [1] 2
class(2L)
## [1] "integer"
is.numeric(2L)
## [1] TRUE
is.integer(2L)
## [1] TRUE

integernumeric에 비해 차지하는 메모리가 적으며 때문에 연산이 좀 더 빠르다는 장점이 있습니다.

글자형 character

character(캐릭터) 자료형은 그 영어 뜻이 문자인 것 같이 글자를 표현하는 자료형입니다. R에서는 변수와 글자를 구분하기 위해서 글자의 앞뒤에 "를 붙이는데요. '도 같은 역할을 합니다. 그래서 "' 사이에 있는 것은 모두 글자, 즉 character로 인식합니다.

"2"
## [1] "2"
class("2")
## [1] "character"
is.numeric("2")
## [1] FALSE
is.character("2")
## [1] TRUE

한글도 됩니다.

is.character("한글")
## [1] TRUE

심지어 " 사이에 있는 ', ' 사이에 있는 "도 글자로 인식합니다. 이걸 이용해서 글자내에 "'가 들어가야 할 경우, 안에 들어가지 않는 것을 글자로 인식시키기 위한 표시로 사용하기도 합니다.

"'"
## [1] "'"
is.character("'")
## [1] TRUE

이렇게 다른 자료형의 기반이 되는 기초 자료형 3개에 대해 알아보았습니다. 이 각각은 지금까지 데이터가 한 개인 경우였습니다. 그렇다면 이 자료형들이 모여 이루는 새로운 자료형에 대해서 알아보겠습니다.

벡터

하나의 기초 자료형만으로 구성된 1차원 데이터 집합

vector(벡터) 자료형은 가장 간단하게 하나의 자료형에 해당하는 데이터들의 1차원적인 집합입니다. vector를 만드는 가장 간단한 함수는 c입니다. ?c를 실행해서 내용을 한 번 읽어주세요.

vec_char <- c("a","b","c")
vec_char
## [1] "a" "b" "c"
class(vec_char)
## [1] "character"
is.vector(vec_char); is.character(vec_char)
## [1] TRUE
## [1] TRUE

vec_charcharacter이기도 하면서 vector입니다. 그럼 이번엔 숫자 벡터를 한번 만들어 보겠습니다.

vec_num <- c(1,2,3,4,5)
vec_num
## [1] 1 2 3 4 5
class(vec_num)
## [1] "numeric"
is.vector(vec_num)
## [1] TRUE
is.numeric(vec_num)
## [1] TRUE

이번엔 vec_numnumeric이기도 하면서 vector입니다. 마지막으로 논리 벡터를 만들어 볼까요.

vec_logi <- c(T,T,T,F,F,T)
vec_logi
## [1]  TRUE  TRUE  TRUE FALSE FALSE  TRUE
class(vec_logi)
## [1] "logical"
is.vector(vec_logi)
## [1] TRUE
is.logical(vec_logi)
## [1] TRUE

지금까지 살펴본 바에 따르면 vector라는 사실은 class로 파악할 수 없고, is.vector로 확인할 수 있습니다. 사실 파악할 수 없다보다는 파악할 필요가 없습니다. vector는 한 번에 같이 처리할 수 있는 데이터를 묶어둔 것이기 때문에 한 vector로 묶인 데이터들은 모두 자료형이 강제로 같게 만들어 집니다. 아래와 같이 다양한 시도를 해서 강제로 자료형이 통일되게 저장되는 것을 확인해 보세요.

vec_test1 <- c(1,2,3,"test")
vec_test1
## [1] "1"    "2"    "3"    "test"
class(vec_test1)
## [1] "character"

이렇게 vector는 하나의 기초 자료형만으로 구성된 1차원 데이터 집합인 것을 알게 되었습니다. 그럼 이제 vector에서 일부의 데이터만 불러오는 방법을 알아보겠습니다.

vector에서 일부 데이터 불러오기

데이터를 모아 놓은 것은 좋은 전략이지만 그 중 일부를 자유롭게 사용할 수 있을 때 더욱 빛을 발하는 것 같습니다. vector는 같은 종류의 데이터를 한 줄로 늘어뜨려 놓은 것으로 상상하시면 좋을 것 같은데, 왜냐면 위치로 데이터의 일부를 선택하기 위해서 입니다.

컴퓨터 언어에서 무엇을 언급하는 방법은 두 가지가 있습니다. 하나는 임의의 이름으로 지정된 주소값 자체를 언급하는 것이고, 다른 하나는 그 주소값에 이름을 부여해서 그 이름을 부르는 것입니다. 정확한 비유는 아닙니다만, vector에서 특정 위치에 있는 값을 부르는 것은 위치의 주소값이나 지정해둔 이름으로 값을 부르는 것과 비슷합니다. 값의 순서라고 볼 수 있는 주소값을 index라고 합니다.

R에서 index를 사용하는 방법은 [ ](대괄호)를 변수뒤에 붙여서 사용합니다.

vec_ind <- c("b","a","ab","b","o","ab")
vec_ind[1]
## [1] "b"

위에서 지정해둔 이름으로 값을 부르는 방법이 있다고 했습니다. 그러면 우선 vector의 값에 이름을 부여하는 방법을 살펴보겠습니다. 새롭게 살펴볼 명령어는 names입니다. ?names를 실행해서 살펴보세요.

blood_type1 <- c(bob = "A", sam = "B", john="O",jim="AB",tom="A")
blood_type1
##  bob  sam john  jim  tom 
##  "A"  "B"  "O" "AB"  "A"
names(blood_type1)
## [1] "bob"  "sam"  "john" "jim"  "tom"

위의 첫번째 방법은 처음 vector를 만들 때 부터 이름을 짓고 시작하는 것입니다. =를 기준으로 왼쪽이 이름, 오른쪽이 이름에 해당하는 값입니다. 그럼 다른 방법을 보겠습니다.

(blood_type2 <- c("A","B","O","AB","A"))
## [1] "A"  "B"  "O"  "AB" "A"
names(blood_type2)
## NULL
(blood_type2_name <- c("bob", "sam", "john", "jim", "tom"))
## [1] "bob"  "sam"  "john" "jim"  "tom"
names(blood_type2) <- blood_type2_name
blood_type2
##  bob  sam john  jim  tom 
##  "A"  "B"  "O" "AB"  "A"

names 함수가 vector의 이름을 보여주는 함수이기도 하면서 값을 지정받을 수 있는 변수의 기능도 합니다. names(blood_type2) 전체가 하나의 변수로 동작하는 것이지요. 그래서 처음 선언한 vector에 이름이 없는 것을 확인하고 새롭게 이름에 해당하는 변수 blood_type2_name을 선언한 후에 names(blood_type2)에 선언해줌으로써 이름을 작성했습니다. 두번째 방법을 보면 언제든 새롭게 이름을 지정해 줄 수 있음을 알 수 있습니다.

원래 내용으로 돌아가서, 그럼 이제 이름으로 그 위치에 해당하는 값을 출력해 보겠습니다.

blood_type1 <- c(bob = "A", sam = "B", john="O",jim="AB",tom="A")
blood_type1[bob]

조금 이상합니다. Error: object 'bob' not found라고 하면서 동작하질 않네요. Error 메세지는 함수가 작동하는데 문제가 있어서 실행이 되질 않았다는 뜻입니다. (참고로 Warning은 실행은 됬지만 기대하는 결과가 아닐 수 있으니 확인해보라는 뜻입니다.) object는 지금까지 한글로 변수라고 불렀습니다. 그럼 변수인 bob이 없다는 에러가 발생한 겁니다. 글자형에서 R에서는 변수와 글자를 구분하기 위해서 글자의 앞뒤에 "를 붙인다고 했습니다. 그렇다면 blood_type1[bob]에서 bob의 앞뒤에 "표시를 하지 않았기 때문에 R이 변수로 인식한 듯합니다. 그럼 "로 묶어서(앞뒤에 표시해서) 다시 실행해 보겠습니다.

blood_type1 <- c(bob = "A", sam = "B", john="O",jim="AB",tom="A")
blood_type1["bob"]
## bob 
## "A"

이제 원하는 결과가 나왔습니다. 이런 문법에 대해서는 규칙을 정확히 이해하는 것도 중요하지만 계속 console에 입력해보면서 테스트를 하는 것이 매우 중요합니다. 그렇게 하면 자주 사용하는 것은 익숙해지고, 기억이 흐릿한 것은 확인해가며 코드를 작성할 수 있게 됩니다.

더 확인해봐야 할 것이 있습니다. 지금까지 한 개만 불러오는 것을 했는데, 여러 개를 동시에 해도 되는지를 확인해 보겠습니다.

blood_type1 <- c(bob = "A", sam = "B", john="O",jim="AB",tom="A")
blood_type1[c("bob","john")]
##  bob john 
##  "A"  "O"

여러 개도 가능합니다. 여러 개를 지정하기 위해 c함수를 함께 사용한 것에 주목해 주세요. c로 이름을 묶어서 사용하지 않으면 어떻게 되는지 확인해 보세요.

blood_type1 <- c(bob = "A", sam = "B", john="O",jim="AB",tom="A")
blood_type1["bob","john"]

Error in blood_type1["bob", "john"] : incorrect number of dimensions이라는 Error를 보셨으면 맞게 동작하는 것입니다. 메세지를 한번 잘 보겠습니다. dimension은 차원이라는 뜻으로 제가 vector는 1차원 데이터 집합이라는 표현을 썼었습니다. 그렇다면 차원이 맞지 않게 요청이 되었다는 뜻입니다. R은 [ ]안에서 ,(쉼표)는 차원을 구분하는 것으로 인식합니다. 예를 들어 [ , , , ]라고 하면 4차원 데이터를 뜻한다고 할 수 있습니다. 그렇기 때문에 blood_type1["bob","john"]은 저희가 의도하지 않는 방식으로 R이 이해한 것입니다. R이 이해하는 대로 해석하면 첫번째 차원은 이름이 ’bob’인 데이터를, 두번째 차원은 이름이 ’john’인 데이터를 불러옴이 됩니다. 하지만 우리는 첫번째 차원에서 이름이 ’bob’인 데이터와 이름이 ’john’인 데이터를 불러옴을 실행하고 싶기 때문에 같은 차원에서 두 개의 이름을 사용했다는 것을 R에게 알려줘야 합니다. 그래서 c 함수를 사용해서 이름을 묶어, 여러 데이터를 불러오는 이름을 사용할 때 vector를 사용한 것입니다. 이 방법은 차원이 늘어나거나 하더라도 같은 문법을 사용합니다. 그럼 이제 2차원 데이터 집합인 matrix를 살펴보겠습니다.

메트릭스

vector의 2차원 확장

matrix는 간단하게 1차원의 vector를 2차원으로 확장한 것입니다. 여기서 2차원이란 1차원 vector가 두 줄이 생겼다는 것이 아니라 행과 열로 2차원을 표현하게 됩니다. matrix는 많이 보시는 엑셀 시트와 같은 형태를 띕니다. 차원의 의미가 여러 가지가 가능하겠습니다만, 지금의 차원은 데이터를 표현하는 축의 갯수라고 이해하시면 좋을 것 같습니다.

소개해야 할 자료형 중에 array가 있는데 배열이라는 뜻입니다. vector1차원 array의 특별한 이름이고, matrix2차원 array의 특별한 이름입니다. 3차원 이상 부터는 전부 n차원 array라고 합니다.

그래서 vectormatrix, array는 앞서 vector에서 확인했던 문법적 규칙과 모두 같은 규칙을 따릅니다. 그럼 이제 matrix를 한번 만들어 보겠습니다.

matrix(1:6, nrow = 2)
##      [,1] [,2] [,3]
## [1,]    1    3    5
## [2,]    2    4    6
matrix(1:6, ncol = 3)
##      [,1] [,2] [,3]
## [1,]    1    3    5
## [2,]    2    4    6
matrix(1:6, nrow = 2, byrow = TRUE)
##      [,1] [,2] [,3]
## [1,]    1    2    3
## [2,]    4    5    6

위에서 1:6은 1부터 6까지의 숫자 벡터를 뜻합니다. c(1,2,3,4,5,6)과 같습니다. row는 행을, colcolumn의 줄임말로 열을 나타내구요. byrow옵션이 TRUErow방향으로 먼저 벡터를 나열하라는 뜻입니다.

matrix는 행열의 크기를 지정하기 때문에 지정된 크기보다 작은 양의 데이터를 선언할 때 어떻게 동작할 것인지 정해져 있습니다.

matrix(1:3, nrow = 2, ncol = 3)
##      [,1] [,2] [,3]
## [1,]    1    3    2
## [2,]    2    1    3
matrix(1:4, nrow = 2, ncol = 3)
## Warning in matrix(1:4, nrow = 2, ncol = 3): 데이터의 길이[4]가 열의 개수[3]
## 의 배수가 되지 않습니다
##      [,1] [,2] [,3]
## [1,]    1    3    1
## [2,]    2    4    2

첫번째 명령에서는 부족한 갯수만큼 있던 것을 다시 사용해서 자동으로 채워줬네요. 두번째 명령에서도 같은 방식으로 동작한 것 같은데 Warning 메세지가 나왔습니다. 행의 갯수의 약수거나 배수이지 않다는 것을 알려줌으로써 의도적인 행동인지 확인할 수 있게 도와줍니다. 결과를 확인해보면, 4개까지 사용하고 다시 처음부터 1, 2를 사용하면서 행열의 크기만큼 데이터를 채웠습니다. Warning이 나오면 확인해보면 좋겠네요.

행열의 모양을 배우면서 이제 cbindrbind를 확인해 보겠습니다. cbind열 방향으로 묶는다.라는 의미이구요. rbind행 방향으로 묶는다는 뜻입니다. 아래 코드로 어떻게 행열의 모양이 달라지는지 확인해 보세요.

cbind(1:3, 1:3)
##      [,1] [,2]
## [1,]    1    1
## [2,]    2    2
## [3,]    3    3
rbind(1:3, 1:3)
##      [,1] [,2] [,3]
## [1,]    1    2    3
## [2,]    1    2    3
m <- matrix(1:6, byrow = TRUE, nrow = 2)
m
##      [,1] [,2] [,3]
## [1,]    1    2    3
## [2,]    4    5    6
rbind(m, 7:9)
##      [,1] [,2] [,3]
## [1,]    1    2    3
## [2,]    4    5    6
## [3,]    7    8    9
cbind(m, c(10, 11))
##      [,1] [,2] [,3] [,4]
## [1,]    1    2    3   10
## [2,]    4    5    6   11

vector에서 사용한 names처럼, 행열에 대응하는 함수가 있습니다. rownames, colnames가 그것인데요. ?rownames?colnames로 자세한 내용을 더 확인해 보시기 바랍니다.

m <- matrix(1:6, byrow = TRUE, nrow = 2)
m
##      [,1] [,2] [,3]
## [1,]    1    2    3
## [2,]    4    5    6
rownames(m)
## NULL
rownames(m) <- c("row1", "row2")
m
##      [,1] [,2] [,3]
## row1    1    2    3
## row2    4    5    6
colnames(m)
## NULL
colnames(m) <- c("col1", "col2", "col3")
m
##      col1 col2 col3
## row1    1    2    3
## row2    4    5    6

vector처럼 처음부터 만들 때 이름을 지정해 줄 수도 있습니다.

m <- matrix(1:6, byrow = TRUE, nrow = 2,
            dimnames = list(c("row1", "row2"),
                            c("col1", "col2", "col3")))
m
##      col1 col2 col3
## row1    1    2    3
## row2    4    5    6

matrixvector와 같이 한 matrix내의 데이터는 모두 같은 자료형이어야 합니다. 이건 자연스럽다고 생각하는 건지 Warning도 주지 않으니 조심하셔야 합니다.

num <- matrix(1:8, ncol = 2)
char <- matrix(LETTERS[1:6], nrow = 4, ncol = 3)
cbind(num, char)
##      [,1] [,2] [,3] [,4] [,5]
## [1,] "1"  "5"  "A"  "E"  "C" 
## [2,] "2"  "6"  "B"  "F"  "D" 
## [3,] "3"  "7"  "C"  "A"  "E" 
## [4,] "4"  "8"  "D"  "B"  "F"

matrix에서 일부 데이터 불러오기

matrix에서 일부 데이터를 불러오는 방법은 vector와 완전히 같습니다. vector에서 [ ]를 설명하면서 차원에 대해 이야기 했었는데요. matrix는 2차원이기 때문에 중간에 쉼표가 들어가야 합니다.

m <- matrix(sample(1:15, 12), nrow = 3)
m
##      [,1] [,2] [,3] [,4]
## [1,]    9    3    7    6
## [2,]   12   11   14   15
## [3,]    1   13    8    2
m[1,3]
## [1] 7
m[3,2]
## [1] 13

쉼표만 사용하고 어느 하나의 차원을 지정하지 않으면, 모두 불러온 것으로 가정합니다.

m[3,]
## [1]  1 13  8  2
m[,3]
## [1]  7 14  8

신기하게도, 1차원인 vector의 일부 데이터 불러오기의 문법도 사용할 수 있습니다. m[9]가 어느 위치의 값인지 잘 찾아보시기 바랍니다.

m[4]
## [1] 3
m[9]
## [1] 8

여전히 vector와 같이 어느 한 차원 내에서 여러개를 사용하기 위해서는 c와 함께 사용합니다.

m[2, c(2, 3)]
## [1] 11 14
m[c(1, 2), c(2, 3)]
##      [,1] [,2]
## [1,]    3    7
## [2,]   11   14
m[c(1, 3), c(1, 3, 4)]
##      [,1] [,2] [,3]
## [1,]    9    7    6
## [2,]    1    8    2

역시 이름을 사용해서 일부 데이터만 불러오는 것도 가능합니다.

rownames(m) <- c("r1", "r2", "r3")
colnames(m) <- c("a", "b", "c", "d")
m
##     a  b  c  d
## r1  9  3  7  6
## r2 12 11 14 15
## r3  1 13  8  2
m[2,3] ; m["r2","c"] ; m[2,"c"] ; m[3, c("c", "d")]
## [1] 14
## [1] 14
## [1] 14
## c d 
## 8 2

팩터

명목형 변수

R에 대해서 이야기 하면서 통계에 대한 이야기가 나오지 않을 수가 없는데요. factorcategorical data를 표현하기 위해 만들어진 자료형이기 때문입니다. 통계에서 사용하는 자료형은 여기를 참고하세요. 가장 대표적인 categorical data인 혈액형을 예시로 factor를 사용해 보겠습니다.

blood <- c("B", "AB", "O", "A", "O", "O", "A", "B")
str(blood)
##  chr [1:8] "B" "AB" "O" "A" "O" "O" "A" "B"
blood_factor <- factor(blood)
str(blood_factor)
##  Factor w/ 4 levels "A","AB","B","O": 3 2 4 1 4 4 1 3

자료의 상태를 파악하는 함수로 str를 사용했습니다. str은 변수의 구조가 어떻게 구성되어 있는지를 보여주는 함수로, 데이터를 파악하는데 좋은 방법입니다. 비슷한 다른 방법들로, summary, head 등이 있습니다. ?str, ?summary, ?head를 입력해서 내용을 확인해 보세요.

chr [1:8] "B" "AB" "O" "A" "O" "O" "A" "B" 에서 chrcharacter의 줄임 표현입니다. 그 다름 [1:8]은 데이터가 8개 있다는 뜻입니다. 그 이후에는 8개의 데이터가 모두 출력되었습니다.

factercharacter와 다르게 Levels: A AB B O라는 줄이 추가 되었습니다. 그리고 데이터를 표현할 때 "가 없어졌네요. 우선 그것만으로도 character가 아님을 판단할 수 있습니다. str(blood_factor)의 결과도 보겠습니다. Factor w/ 4 levels "A","AB","B","O": 3 2 4 1 4 4 1 3에서 독특한 부분이 있습니다. 데이터가 숫자로 되어 있네요.

데이터와 비교해 보면 levels "A", "AB", "B", "0"에서 3번째는 "B"입니다. 첫번째 데이터가 "B"인걸 보니 숫자는 levels에서 몇 번째 데이터인지를 뜻하는 것 같습니다. 다른 숫자들도 확인해 보세요.

vector처럼 변수에 선언할 때 처음부터 levels를 지정할 수 도 있습니다. 순서를 지정하는 것도 가능합니다. 처음에 만든 blood_factorblood_factor2가 어떻게 다른지 비교해 보세요.

blood_factor2 <- factor(blood,
                        levels = c("O", "A", "B", "AB"))
blood_factor2
## [1] B  AB O  A  O  O  A  B 
## Levels: O A B AB
str(blood_factor2)
##  Factor w/ 4 levels "O","A","B","AB": 3 4 1 2 1 1 2 3
str(blood_factor)
##  Factor w/ 4 levels "A","AB","B","O": 3 2 4 1 4 4 1 3

names처럼 levels도 중간에 바꿀 수 있습니다. labels라는 것도 있는데, 이것은 데이터의 값을 뜻합니다. 어떻게 달라지는지 확인해 보세요.

blood <- c("B", "AB", "O", "A", "O", "O", "A", "B")
blood_factor <- factor(blood)
blood_factor
## [1] B  AB O  A  O  O  A  B 
## Levels: A AB B O
levels(blood_factor) <- c("BT_A", "BT_AB", "BT_B", "BT_O")
blood_factor
## [1] BT_B  BT_AB BT_O  BT_A  BT_O  BT_O  BT_A  BT_B 
## Levels: BT_A BT_AB BT_B BT_O
factor(blood, labels = c("BT_A", "BT_AB", "BT_B", "BT_O"))
## [1] BT_B  BT_AB BT_O  BT_A  BT_O  BT_O  BT_A  BT_B 
## Levels: BT_A BT_AB BT_B BT_O

factor는 명목형 변수여서 크기를 비교할 수 없습니다.

blood <- c("B", "AB", "O", "A", "O", "O", "A", "B")
blood_factor <- factor(blood)
blood_factor[1] < blood_factor[2]
## Warning in Ops.factor(blood_factor[1], blood_factor[2]): '<' not meaningful
## for factors
## [1] NA

하지만 크기를 비교할 수 있는 형태로 선언할 수 도 있습니다.

tshirt <- c("M", "L", "S", "S", "L", "M", "L", "M")
tshirt_factor <- factor(tshirt, ordered = TRUE,
                        levels = c("S", "M", "L"))
tshirt_factor
## [1] M L S S L M L M
## Levels: S < M < L
tshirt_factor[1] < tshirt_factor[2]
## [1] TRUE

리스트

다른 종류의 자료형들이 함께

지금까지 전부 같은 자료형의 데이터가 모여 있는 형태의 자료형을 알아봤습니다. 같은 자료형으로 구성하는 것은 계산의 효율등 분명한 장점도 있지만, 그것을 사용하는 사람이 이해하기 어렵거나 불편할 때가 있습니다. 특히 다양한 자료형으로 구성된 데이터일 때 하나의 변수로 관리하기 위해서는 list를 사용해 합니다. 아래 clist로 만든 데이터를 비교해 보겠습니다.

c("Rsome times", 190, 5)
## [1] "Rsome times" "190"         "5"
list("Rsome times", 190, 5)
## [[1]]
## [1] "Rsome times"
## 
## [[2]]
## [1] 190
## 
## [[3]]
## [1] 5

list 역시 is.list 함수가 있네요. c로 만들어진 데이터는 자료형을 강제로 전부 character로 바꾸었습니다. list는 데이터가 주욱 늘어지고, 숫자들은 숫자 데이터로 표현된 것 같습니다. 이 때 처음 보는 것이 있는데요. [[ ]]입니다. 이것은 vector가 하나의 자료형으로만 이루어져야 하는 점에 착안하여, vector를 여러 개 합쳐 list를 만듬으로써 문제를 해결했습니다. 그렇기 때문에 일부의 데이터를 불러오는 [ ]과 비슷하지만 합쳐져 있는 그 내부의 vector를 불러오는 [[ ]] 문법이 만들어 지게 되었습니다. [ ][[ ]]를 실험해 보세요.

song <- list("Rsome times", 190, 5)
song[3]
## [[1]]
## [1] 5
class(song[3])
## [1] "list"
song[[3]]
## [1] 5
class(song[[3]])
## [1] "numeric"

list도 다른 자료형들 처럼 names를 사용합니다.

song <- list("Rsome times", 190, 5)
names(song) <- c("title", "duration", "track")
song
## $title
## [1] "Rsome times"
## 
## $duration
## [1] 190
## 
## $track
## [1] 5

이번엔 [[ ]] 위치에 $title 같이 $ + names의 문법이 나타났습니다. 이것이 list의 이름을 짓는 방법입니다. 이름으로 데이터를 부르는 방법은 더 복잡한 모양의 데이터를 만들고 실습해보기로 하고, list가 가진 다른 특성을 확인해 보겠습니다.

song <- list(title = "Rsome times",
             duration = 190,
             track = 5)
str(song)
## List of 3
##  $ title   : chr "Rsome times"
##  $ duration: num 190
##  $ track   : num 5
similar_song <- list(title = "R you on time?",
                     duration = 230)

song <- list(title = "Rsome times",
             duration = 190, track = 5,
             similar = similar_song)

str(song)
## List of 4
##  $ title   : chr "Rsome times"
##  $ duration: num 190
##  $ track   : num 5
##  $ similar :List of 2
##   ..$ title   : chr "R you on time?"
##   ..$ duration: num 230

list 안에 list가 들어가 있네요 str로 확인해 보니 list 안에 list..$로 들여쓰기 되어 표현되어 있습니다. 데이터 표현도 List of 2라고 str(song)의 맨 윗줄(List of 4)과 같은 모양이 보입니다. 이것처럼 list는 대부분의 자료형을 요소로 가질 수 있습니다.

list는 인터넷에서 많이 사용하는 자료형인 JSON에 대응됩니다. 과거에는 XML도 사용했다고 알고 있는데, 최근에는 많은 곳에서 JSON으로 사용하고 있습니다. JSON에 대해서는 이곳예제를 참고하세요

library(N2H4)
url<-"http://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=100&oid=437&aid=0000152054"
tem<-getComment(url)
## [1] "success : TRUE"
str(tem)
## 'data.frame':    1 obs. of  7 variables:
##  $ success: logi TRUE
##  $ code   : chr "1000"
##  $ message: chr "요청을 성공적으로 처리하였습니다."
##  $ lang   : chr "ko"
##  $ country: chr "KR"
##  $ result :'data.frame': 1 obs. of  11 variables:
##   ..$ listStatus    : chr "current"
##   ..$ sort          : chr "FAVORITE"
##   ..$ graph         :'data.frame':   1 obs. of  3 variables:
##   .. ..$ gender:'data.frame':    1 obs. of  2 variables:
##   .. .. ..$ male  : int 75
##   .. .. ..$ female: int 25
##   .. ..$ old   :List of 1
##   .. .. ..$ :'data.frame':   5 obs. of  5 variables:
##   .. .. .. ..$ age  : chr  "10" "20" "30" "40" ...
##   .. .. .. ..$ value: int  2 18 33 24 22
##   .. .. .. ..$ type : chr  "exact" "exact" "exact" "exact" ...
##   .. .. .. ..$ min  : logi  TRUE FALSE FALSE FALSE FALSE
##   .. .. .. ..$ max  : logi  FALSE FALSE TRUE FALSE FALSE
##   .. ..$ empty : logi FALSE
##   ..$ count         :'data.frame':   1 obs. of  8 variables:
##   .. ..$ comment           : int 1038
##   .. ..$ reply             : int 480
##   .. ..$ exposeCount       : int 1110
##   .. ..$ delCommentByUser  : int 0
##   .. ..$ delCommentByMon   : int 4
##   .. ..$ blindCommentByUser: int 0
##   .. ..$ blindReplyByUser  : int 0
##   .. ..$ total             : int 1518
##   ..$ config        :'data.frame':   1 obs. of  77 variables:
##   .. ..$ useSnsLogin                : logi TRUE
##   .. ..$ lineFeedOn                 : logi FALSE
##   .. ..$ useBest                    : logi FALSE
##   .. ..$ useVote                    : logi TRUE
##   .. ..$ useVoteSelf                : logi FALSE
##   .. ..$ useVoteGoodOnly            : logi FALSE
##   .. ..$ useReport                  : logi TRUE
##   .. ..$ useCommonReport            : logi FALSE
##   .. ..$ useSort                    : logi TRUE
##   .. ..$ sortTypes                  :List of 1
##   .. .. ..$ : chr  "FAVORITE" "NEW" "RELATIVE"
##   .. ..$ defaultSort                : chr "FAVORITE"
##   .. ..$ useByteLength              : logi FALSE
##   .. ..$ useReply                   : logi TRUE
##   .. ..$ useAutoRefresh             : logi FALSE
##   .. ..$ min                        : int 1
##   .. ..$ max                        : int 300
##   .. ..$ useSticker                 : logi FALSE
##   .. ..$ stickerOnly                : logi FALSE
##   .. ..$ stickerSupportedCategories : logi NA
##   .. ..$ stickerDefaultCategory     : logi NA
##   .. ..$ stickerCategory            : logi NA
##   .. ..$ stickerContentsUrl         : logi NA
##   .. ..$ stickerKeyUrl              : logi NA
##   .. ..$ stickerTabUrl              : logi NA
##   .. ..$ stickerType                : logi NA
##   .. ..$ stickerText                : logi FALSE
##   .. ..$ stickerMarketUrl           : logi NA
##   .. ..$ stickerMobileResize        : logi FALSE
##   .. ..$ useProfile                 : logi FALSE
##   .. ..$ profileEmptyImage          : logi NA
##   .. ..$ displayMaskedUserId        : logi FALSE
##   .. ..$ useManager                 : logi FALSE
##   .. ..$ managerDelete              : logi FALSE
##   .. ..$ managerBlock               : logi FALSE
##   .. ..$ managerNotice              : logi FALSE
##   .. ..$ contentsManagerIcon        : logi FALSE
##   .. ..$ secretComment              : logi FALSE
##   .. ..$ exposureConfig             : logi FALSE
##   .. ..$ deleteAllAfterBlock        : logi FALSE
##   .. ..$ useFold                    : logi TRUE
##   .. ..$ replyPreviewCount          : int 0
##   .. ..$ maxImageUploadCount        : int 0
##   .. ..$ maxImageUploadFileSize     : int 0
##   .. ..$ imageAutoRotate            : logi FALSE
##   .. ..$ autoRefreshTime            : int 0
##   .. ..$ autoRefreshDefaultOff      : logi FALSE
##   .. ..$ autoRefreshChat            : logi FALSE
##   .. ..$ maxChatFPS                 : int 0
##   .. ..$ commentModify              : logi FALSE
##   .. ..$ useCommentModify           : logi FALSE
##   .. ..$ useViewAll                 : logi TRUE
##   .. ..$ useSnsComment              : logi TRUE
##   .. ..$ snsCommentDefaultOn        : logi TRUE
##   .. ..$ useUserLevel               : logi FALSE
##   .. ..$ useUserBlind               : logi FALSE
##   .. ..$ maxUserBlindCount          : int 0
##   .. ..$ useImageComment            : logi FALSE
##   .. ..$ useUrlLink                 : logi FALSE
##   .. ..$ useCommentListIncludeDelete: logi FALSE
##   .. ..$ useStats                   : logi TRUE
##   .. ..$ useGpopCache               : logi FALSE
##   .. ..$ useEnterSubmit             : logi FALSE
##   .. ..$ useListReverse             : logi FALSE
##   .. ..$ useTranslation             : logi FALSE
##   .. ..$ useDeletedList             : logi TRUE
##   .. ..$ useAudio                   : logi FALSE
##   .. ..$ combinedDeletedList        : logi TRUE
##   .. ..$ displayDeletedList         : logi FALSE
##   .. ..$ photoInfraUploadDomain     : logi NA
##   .. ..$ photoInfraSelectDomainHttp : logi NA
##   .. ..$ photoInfraSelectDomainHttps: logi NA
##   .. ..$ pcTempThumbnailType        : chr "ff80_80"
##   .. ..$ mobileTempThumbnailType    : chr "ff50_50"
##   .. ..$ realnameVerificationBlock  : logi FALSE
##   .. ..$ realnameVerificationMessage: logi NA
##   .. ..$ useBlindByReport           : logi TRUE
##   .. ..$ hashtagMaxLength           : int 20
##   ..$ exposureConfig:'data.frame':   1 obs. of  2 variables:
##   .. ..$ reason: logi NA
##   .. ..$ status: chr "COMMENT_ON"
##   ..$ bestList      :List of 1
##   .. ..$ : list()
##   ..$ notice        :'data.frame':   1 obs. of  4 variables:
##   .. ..$ noticeNo: int 12
##   .. ..$ title   : chr "6/22 밤10시부터 댓글접기/이력공개 오픈"
##   .. ..$ content : chr "안녕하세요, 네이버 뉴스입니다.\r<br>\r<br>하루에도 수 많은 의견들이 교환되는 뉴스 댓글 공간을 더 투명하게 서비"| __truncated__
##   .. ..$ regTime : chr "2017-06-22T22:51:57+0900"
##   ..$ userInfo      :'data.frame':   1 obs. of  0 variables
##   ..$ pageModel     :'data.frame':   1 obs. of  16 variables:
##   .. ..$ page          : int 1
##   .. ..$ pageSize      : int 10
##   .. ..$ indexSize     : int 10
##   .. ..$ startRow      : int 1
##   .. ..$ endRow        : int 10
##   .. ..$ totalRows     : int 1038
##   .. ..$ startIndex    : int 0
##   .. ..$ totalPages    : int 104
##   .. ..$ firstPage     : int 1
##   .. ..$ prevPage      : int 0
##   .. ..$ nextPage      : int 2
##   .. ..$ lastPage      : int 10
##   .. ..$ current       : logi NA
##   .. ..$ moveToLastPage: logi FALSE
##   .. ..$ moveToComment : logi FALSE
##   .. ..$ moveToLastPrev: logi FALSE
##   ..$ commentList   :List of 1
##   .. ..$ :'data.frame':  10 obs. of  68 variables:
##   .. .. ..$ ticket          : chr  "news" "news" "news" "news" ...
##   .. .. ..$ objectId        : chr  "news437,0000152054" "news437,0000152054" "news437,0000152054" "news437,0000152054" ...
##   .. .. ..$ categoryId      : chr  "*" "*" "*" "*" ...
##   .. .. ..$ templateId      : chr  "default" "view" "view_politics" "default" ...
##   .. .. ..$ commentNo       : int  895990872 895990402 895993542 895994142 895997652 895994952 895998652 895990312 895993642 896006322
##   .. .. ..$ parentCommentNo : int  895990872 895990402 895993542 895994142 895997652 895994952 895998652 895990312 895993642 896006322
##   .. .. ..$ replyLevel      : int  1 1 1 1 1 1 1 1 1 1
##   .. .. ..$ replyCount      : int  46 28 25 6 0 1 3 4 0 0
##   .. .. ..$ replyAllCount   : int  0 0 0 0 0 0 0 0 0 0
##   .. .. ..$ replyPreviewNo  : logi  NA NA NA NA NA NA ...
##   .. .. ..$ replyList       : logi  NA NA NA NA NA NA ...
##   .. .. ..$ imageCount      : int  0 0 0 0 0 0 0 0 0 0
##   .. .. ..$ imageList       : logi  NA NA NA NA NA NA ...
##   .. .. ..$ imagePathList   : logi  NA NA NA NA NA NA ...
##   .. .. ..$ imageWidthList  : logi  NA NA NA NA NA NA ...
##   .. .. ..$ imageHeightList : logi  NA NA NA NA NA NA ...
##   .. .. ..$ commentType     : chr  "txt" "txt" "txt" "txt" ...
##   .. .. ..$ stickerId       : logi  NA NA NA NA NA NA ...
##   .. .. ..$ sticker         : logi  NA NA NA NA NA NA ...
##   .. .. ..$ sortValue       : num  1.49e+12 1.49e+12 1.49e+12 1.49e+12 1.49e+12 ...
##   .. .. ..$ contents        : chr  "국회의원에게 권력을 나눠주느니 차라리 대통령중심제 해라. 각당 원내대표들이 돌아가며 총리 해먹는 꼴 못본다" "4년 중임제가 답이다" "의원내각제는 진짜로 박지원 상왕 만들자는 개헌이지" "박지원이 그렇게 개헌 개헌 하더니만...안후보 대통령만들고 나면 총리자리 달라고 하겠네 ㅎㅎ" ...
##   .. .. ..$ userIdNo        : chr  "oCJq" "5qcIZ" "9oKyX" "6U92Q" ...
##   .. .. ..$ lang            : chr  "ko" "ko" "ko" "ko" ...
##   .. .. ..$ country         : chr  "KR" "KR" "KR" "KR" ...
##   .. .. ..$ idType          : chr  "naver" "naver" "naver" "naver" ...
##   .. .. ..$ idProvider      : chr  "naver" "naver" "naver" "naver" ...
##   .. .. ..$ userName        : chr  "ssan****" "zamp****" "tjto****" "hb12****" ...
##   .. .. ..$ userProfileImage: chr  "http://profile.phinf.naver.net/47661/c2300c203d789c4761cb7256042c54e1434afc037bc9e510593d610b89108090.jpg" "http://profile.phinf.naver.net/27148/f77ca3b0a86973c627361a946e9ae3a198c8557fd608f07ead8df1f060808693.jpg" "http://profile.phinf.naver.net/43378/842d4ba6140c89c75e27abf1f0a48910de3c4f95bac6fe9e7347cbbf831e6f42.jpg" "http://profile.phinf.naver.net/46045/e902b85b86479646dd8db2c3b95857cbe4dcd058ea7fb0c36592ecdf6107350d.jpg" ...
##   .. .. ..$ profileType     : chr  "naver" "naver" "naver" "naver" ...
##   .. .. ..$ modTime         : chr  "2017-04-12T21:00:55+0900" "2017-04-12T21:00:21+0900" "2017-04-12T21:04:44+0900" "2017-04-12T21:05:26+0900" ...
##   .. .. ..$ modTimeGmt      : chr  "2017-04-12T12:00:55+0000" "2017-04-12T12:00:21+0000" "2017-04-12T12:04:44+0000" "2017-04-12T12:05:26+0000" ...
##   .. .. ..$ regTime         : chr  "2017-04-12T21:00:55+0900" "2017-04-12T21:00:21+0900" "2017-04-12T21:04:44+0900" "2017-04-12T21:05:26+0900" ...
##   .. .. ..$ regTimeGmt      : chr  "2017-04-12T12:00:55+0000" "2017-04-12T12:00:21+0000" "2017-04-12T12:04:44+0000" "2017-04-12T12:05:26+0000" ...
##   .. .. ..$ sympathyCount   : int  2360 1907 1914 645 470 428 446 404 262 237
##   .. .. ..$ antipathyCount  : int  70 100 152 37 5 10 39 34 4 5
##   .. .. ..$ userBlind       : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##   .. .. ..$ hideReplyButton : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##   .. .. ..$ status          : int  0 0 0 0 0 0 0 0 0 0
##   .. .. ..$ mine            : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##   .. .. ..$ best            : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##   .. .. ..$ mentions        : logi  NA NA NA NA NA NA ...
##   .. .. ..$ userStatus      : int  0 0 0 0 0 0 0 0 0 0
##   .. .. ..$ categoryImage   : logi  NA NA NA NA NA NA ...
##   .. .. ..$ open            : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##   .. .. ..$ levelCode       : logi  NA NA NA NA NA NA ...
##   .. .. ..$ sympathy        : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##   .. .. ..$ antipathy       : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##   .. .. ..$ snsList         : logi  NA NA NA NA NA NA ...
##   .. .. ..$ metaInfo        : logi  NA NA NA NA NA NA ...
##   .. .. ..$ extension       : logi  NA NA NA NA NA NA ...
##   .. .. ..$ audioInfoList   : logi  NA NA NA NA NA NA ...
##   .. .. ..$ translation     : logi  NA NA NA NA NA NA ...
##   .. .. ..$ report          : logi  NA NA NA NA NA NA ...
##   .. .. ..$ visible         : logi  TRUE TRUE TRUE TRUE TRUE TRUE ...
##   .. .. ..$ manager         : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##   .. .. ..$ deleted         : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##   .. .. ..$ blindReport     : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##   .. .. ..$ shardTableNo    : int  144 144 144 144 144 144 144 144 144 144
##   .. .. ..$ secret          : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##   .. .. ..$ blind           : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##   .. .. ..$ expose          : logi  TRUE TRUE TRUE TRUE TRUE TRUE ...
##   .. .. ..$ profileUserId   : logi  NA NA NA NA NA NA ...
##   .. .. ..$ containText     : logi  TRUE TRUE TRUE TRUE TRUE TRUE ...
##   .. .. ..$ maskedUserId    : chr  "ssan****" "zamp****" "tjto****" "hb12****" ...
##   .. .. ..$ maskedUserName  : chr  "ss****" "za****" "tj****" "hb****" ...
##   .. .. ..$ validateBanWords: logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##   .. .. ..$ exposeByCountry : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##   .. .. ..$ virtual         : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ date   : chr "2017-07-22T05:25:46+0000"

JSON을 전문적으로 다루기 위한 패키지도 있으니 확인해 보세요.

데이터프레임

사람이 이해하기 쉬운 자료형

다양한 형태의 자료형을 묶어서 사용하는 list를 만들고 보니, 반대로 너무 자유도가 높아서 사용하기 어려운 문제들이 나타났습니다. 그렇다 보니 조금 제약사항을 만들어 보기로 합니다. 그렇게 해서 탄생한게 data.frame입니다. 기존에 알고 계시는 엑셀 시트나 설문조사를 정리한 표 같은 걸 생각하시면 도움이 되실 겁니다. data.framelist와 달리 vector를 각 열로 유지하여 합치는 방식을 사용했습니다. 그래서 하나의 열 내에서는 데이터가 vector와 같이 자료형이 모두 같아야 하고, 하나의 행에서는 자료형이 여러 개가 가능합니다.

name <- c("Anne", "Pete", "Frank", "Julia", "Cath")
age <- c(28, 30, 21, 39, 35)
child <- c(FALSE, TRUE, TRUE, FALSE, TRUE)
df <- data.frame(name, age, child)
df
##    name age child
## 1  Anne  28 FALSE
## 2  Pete  30  TRUE
## 3 Frank  21  TRUE
## 4 Julia  39 FALSE
## 5  Cath  35  TRUE

물론 names도 사용합니다. names는 열의 이름을 뜻합니다. 똑같이 처음부터 지정해 줄수 있구요. 지정하지 않으면 임의로 data.frame이 선정해서 가지고 있습니다.

names(df)
## [1] "name"  "age"   "child"
names(df) <- c("Name", "Age", "Child")
df
##    Name Age Child
## 1  Anne  28 FALSE
## 2  Pete  30  TRUE
## 3 Frank  21  TRUE
## 4 Julia  39 FALSE
## 5  Cath  35  TRUE
(df <- data.frame(Name = name, Age = age, Child = child))
##    Name Age Child
## 1  Anne  28 FALSE
## 2  Pete  30  TRUE
## 3 Frank  21  TRUE
## 4 Julia  39 FALSE
## 5  Cath  35  TRUE

data.frame은 각 열의 데이터 갯수가 맞지 않으면 만들어 지지 않습니다.

> data.frame(name[-1], age, child)
## Error in data.frame(name[-1], age, child) : 
##   arguments imply differing number of rows: 4, 5

그리고 글자를 모두 factor를 기본 값으로 지정합니다. 그래서 stringsAsFactors 옵션을 FALSE로 해줘야 charater로 데이터를 만들 수 있습니다. stringsAsFactorsoptions에서도 지정해서 사용할 수 있습니다.

df <- data.frame(name, age, child, 
                 stringsAsFactors = FALSE)
str(df)
## 'data.frame':    5 obs. of  3 variables:
##  $ name : chr  "Anne" "Pete" "Frank" "Julia" ...
##  $ age  : num  28 30 21 39 35
##  $ child: logi  FALSE TRUE TRUE FALSE TRUE

list와 같이 위치값이나 이름으로 데이터의 일부를 불러올 수 있습니다.

name <- c("Anne", "Pete", "Frank", "Julia", "Cath")
age <- c(28, 30, 21, 39, 35)
child <- c(FALSE, TRUE, TRUE, FALSE, TRUE)
people <- data.frame(name, age, child, stringsAsFactors = FALSE)
people
##    name age child
## 1  Anne  28 FALSE
## 2  Pete  30  TRUE
## 3 Frank  21  TRUE
## 4 Julia  39 FALSE
## 5  Cath  35  TRUE
people[3,2]
## [1] 21
people[3,"age"]
## [1] 21
people
##    name age child
## 1  Anne  28 FALSE
## 2  Pete  30  TRUE
## 3 Frank  21  TRUE
## 4 Julia  39 FALSE
## 5  Cath  35  TRUE
people[3,]
##    name age child
## 3 Frank  21  TRUE
people[,"age"]
## [1] 28 30 21 39 35
people[c(3, 5), c("age", "child")]
##   age child
## 3  21  TRUE
## 5  35  TRUE
people
##    name age child
## 1  Anne  28 FALSE
## 2  Pete  30  TRUE
## 3 Frank  21  TRUE
## 4 Julia  39 FALSE
## 5  Cath  35  TRUE
people[2]
##   age
## 1  28
## 2  30
## 3  21
## 4  39
## 5  35
people$age
## [1] 28 30 21 39 35
people[["age"]]
## [1] 28 30 21 39 35

data.frame은 데이터를 추가하는 여러 가지 방법을 지원합니다. 이전에 한번씩 본 방식이니 테스트해보세요.

height <- c(163, 177, 163, 162, 157)
people$height <- height
people[["height"]] <- height
people
##    name age child height
## 1  Anne  28 FALSE    163
## 2  Pete  30  TRUE    177
## 3 Frank  21  TRUE    163
## 4 Julia  39 FALSE    162
## 5  Cath  35  TRUE    157
weight <- c(74, 63, 68, 55, 56)
cbind(people, weight)
##    name age child height weight
## 1  Anne  28 FALSE    163     74
## 2  Pete  30  TRUE    177     63
## 3 Frank  21  TRUE    163     68
## 4 Julia  39 FALSE    162     55
## 5  Cath  35  TRUE    157     56
> tom <- data.frame("Tom", 37, FALSE, 183)
> rbind(people, tom)
## Error in match.names(clabs, names(xi)) : 
##   names do not match previous names
tom <- data.frame(name = "Tom", age = 37,
                  child = FALSE, height = 183)
rbind(people, tom)
##    name age child height
## 1  Anne  28 FALSE    163
## 2  Pete  30  TRUE    177
## 3 Frank  21  TRUE    163
## 4 Julia  39 FALSE    162
## 5  Cath  35  TRUE    157
## 6   Tom  37 FALSE    183

순서에 대해 작업하기

순서는 data.frame뿐만 아니라 다른 자료형에서도 그대로 적용되는 내용입니다. 여기서는 sortorder에 대해서 알아보겠습니다.

sort(people$age)
## [1] 21 28 30 35 39
ranks <- order(people$age)
ranks
## [1] 3 1 2 5 4
people$age
## [1] 28 30 21 39 35
ranks <- order(people$age)
ranks
## [1] 3 1 2 5 4
people[ranks, ]
##    name age child height
## 3 Frank  21  TRUE    163
## 1  Anne  28 FALSE    163
## 2  Pete  30  TRUE    177
## 5  Cath  35  TRUE    157
## 4 Julia  39 FALSE    162
people[order(people$age, decreasing = TRUE), ]
##    name age child height
## 4 Julia  39 FALSE    162
## 5  Cath  35  TRUE    157
## 2  Pete  30  TRUE    177
## 1  Anne  28 FALSE    163
## 3 Frank  21  TRUE    163

sort는 순서가 있는 데이터(지금의 예시로는 나이)를 오름차순으로 정렬해줍니다. 글자라면 알파벳순으로 정렬해 줄 것입니다. 한 번 실험해 보세요.
order는 그 위치에 있는 데이터가 전체 데이터 내에서 몇 번째에 위치하는지를 알려줍니다. sort가 정렬이 끝난 결과를 보여주는 것이라면 order는 그 데이터의 순서값 자체를 보여주는 것이죠. 그렇기 때문에 order[ ]의 행부분 조건과 결합하여 많이 사용됩니다.

날짜

날짜는 어느 곳에서든 다루기 어려운 데이터입니다. 불규칙적인 윤달이라던지 하는 여러 가지 문제로 실제로 사용할 때 많은 문제가 있는데요, R에서는 여러 형태의 날짜를 표현하는 데이터를 Date라는 자료형으로 관리하고 있습니다. 사람들이 관행적으로 사용하는 형태의 character 날짜를 Date형으로 바꿔보겠습니다.

(dates <- c("2016/01/01","2016/02/01","2016/03/01","2016/04/01","2016/05/01","2016/06/01"))
## [1] "2016/01/01" "2016/02/01" "2016/03/01" "2016/04/01" "2016/05/01"
## [6] "2016/06/01"
class(dates)
## [1] "character"
(trans.dates <- as.Date(dates))
## [1] "2016-01-01" "2016-02-01" "2016-03-01" "2016-04-01" "2016-05-01"
## [6] "2016-06-01"
class(trans.dates)
## [1] "Date"

Date형 일때의 장점은 계산이 가능하다는 것입니다.

trans.dates[3]-trans.dates[1]
## Time difference of 60 days

다른 모양의 character도 되는지 한번 보겠습니다.

dates2 <- c("2016-01-01","2016-02-01","2016-03-01","2016-04-01","2016-05-01","2016-06-01")
trans.dates2 <- as.Date(dates2)
trans.dates2
## [1] "2016-01-01" "2016-02-01" "2016-03-01" "2016-04-01" "2016-05-01"
## [6] "2016-06-01"
class(trans.dates2)
## [1] "Date"

어려운 모양은 인식하지 못합니다. 그래서 일부러 양식을 알려주면 R이 고칠 수 있는데요.

> as.Date("2016년 4월 5일")
## Error in charToDate(x) : 문자열이 표준서식을 따르지 않습니다
as.Date("2016년 4월 5일", format="%Y년 %m월 %d일")
## [1] "2016-04-05"

양식은 저도 다 외우지 못하고 매번 검색해서 사용합니다.

http://www.stat.berkeley.edu/classes/s133/dates.html

하지만 year의 y(4자 Y, 2자 y), month의 m, day의 d 로 생각하시면 우선 해결되고 나머지는 위에 링크를 참고해 보시면 좋을 것 같습니다.

as.Date(34519, origin="1900-01-01")
## [1] "1994-07-06"

시간은 POSIXctPOSIXlt 두 가지로 준비되어 있습니다. 분석에 적용하는데 있어 특별히 구분해서 사용하지 않아서, 하나를 선택하셨다면 일관되게 한 가지만 계속 사용하시면 좋을 것 같습니다.

# 시간 자료형
as.POSIXct("2017-04-12 12:00:00")
## [1] "2017-04-12 12:00:00 KST"
as.POSIXlt("2017-04-12 12:00:00")
## [1] "2017-04-12 12:00:00 KST"

날짜와 시간을 다루는 패키지로 유명한 lubridate가 있습니다. 아래 여러 코드의 실행결과를 보시면 그 유연한 기능에 감탄하시게 될 겁니다.

library(lubridate)
## 
## Attaching package: 'lubridate'
## The following object is masked from 'package:base':
## 
##     date
ymd("2017-05-05")
## [1] "2017-05-05"
ymd("170505")
## [1] "2017-05-05"
ymd("20170505")
## [1] "2017-05-05"
dmy("17-05-15")
## [1] "2015-05-17"
ymd("2017년 5월 5일")
## [1] "2017-05-05"
dmy("5일5월2017년")
## [1] "2017-05-05"
dates <- c("2017-05-05","170505","20170505","17-05-15","2017년 5월 5일")
ymd(dates)
## [1] "2017-05-05" "2017-05-05" "2017-05-05" "2017-05-15" "2017-05-05"
(data <- ymd("2017-05-05"))
## [1] "2017-05-05"
year(data)
## [1] 2017
month(data)
## [1] 5
month(data, label=T)
## [1] May
## 12 Levels: Jan < Feb < Mar < Apr < May < Jun < Jul < Aug < Sep < ... < Dec
week(data)
## [1] 18
yday(data)
## [1] 125
mday(data)
## [1] 5
wday(data)
## [1] 6
wday(data, label=T)
## [1] Fri
## Levels: Sun < Mon < Tues < Wed < Thurs < Fri < Sat

단순 반복 업무를 위한 for문과 apply류 맛보기

조건문

if

ifif (조건) {조건이 true 이면 실행할 부분}으로 구성됩니다.

if(TRUE){print(1)}
## [1] 1
print(2)
## [1] 2

조건은 결과가 하나의 logical 값으로 나와야 하고 여러 개의 logical 값이면 맨 앞의 값만 사용한다는 warning을 같이 출력합니다.

if(c(T,F,F,F)){print(1)}
## Warning in if (c(T, F, F, F)) {: length > 1 이라는 조건이 있고, 첫번째 요소
## 만이 사용될 것입니다
## [1] 1
print(2)
## [1] 2

보통은 아래와 같은 형식으로 사용합니다.

x<-1
if ( x > 0 ){
  print(1)
}

이제까지 조건이라고 말하는 것이 있었는데, 조건이란 TRUE, FALSE로 결과가 나오는 표현 전부를 뜻합니다. 제가 if문에서 나올 만한 예시를 준비했습니다.

x<-c()
if(identical(x,c())){print("x has no data.")}
## [1] "x has no data."
print("if part done.")
## [1] "if part done."
options(stringsAsFactors = F)

y<-c(1,2,3)
z<-c(1,2,3)
if(length(y)==length(z)){
  tem<-data.frame(y,z)
  print(tem)
}
##   y z
## 1 1 1
## 2 2 2
## 3 3 3
print("if part done.")
## [1] "if part done."

identical은 두 개의 변수를 비교해주고 같으면 TRUE, 다르면 FALSE를 결과로 주는 함수입니다. 결과를 logical로 주는 덕분에 조건문에 사용하기 딱 좋은 함수 입니다. 예를 들어 데이터에 무언가 문제가 생겨서 함수에 들어가지 못하거나 할때 우회하는 조건을 작성하는데 좋습니다. 저같은 경우는 N2H4 패키지를 작성할 때 getContent에서 사용했습니다. 물론 이상적으로는 정규식과 네이버 뉴스의 root url을 바탕으로 비교하는 식으로 해야 더 정교하겠습니다만, getUrlListByCategory 함수에서 생성되는 link를 사용하는 형태도 구성되어 있어서 아래와 같이 작성하였습니다.

...
  if(!identical(url,character(0))){
    if (RCurl::url.exists(url)&
       "error_msg 404"!=(read_html(url)%>%html_nodes("div#main_content div div")%>%html_attr("class"))[1]
        ) {
...

조건을 여러개 두고 andor로 묶어서 상황을 파악해야 할 때가 있습니다. 조건이 두 개라고 했을 때 참고할 수 있는 그림이 아래와 같습니다. logical

else

영어 표현을 보면 생각하기 쉬우시겠지만 if(조건){조건이 true 이면 실행할 부분} 이후에 사용해서 조건이 false면 실행할 부분을 작성하는데 사용합니다. if (조건) {조건이 true 이면 실행할 부분} else {조건이 false 면 실행할 부분}으로 구성됩니다.

if(TRUE){
  print(1)
} else {
  print(3)
}
## [1] 1
print(2)
## [1] 2

else는 앞에 if가 조건을 작성했기 때문에 추가적인 조건을 작성하지는 않습니다. 여러 if 조건을 사용하고 그 이후에 else를 사용할 수도 있습니다.

x<-1

if(x<0){
  print(1)
} 
if(x>10) {
  print(3)
} else {
  print(2)
}
## [1] 2
print(4)
## [1] 4

최근의 코드 작성 스타일은 누가 봐도 읽고 이해하기 쉽게 이다 보니 고려하면 좋을 것 같습니다. 특히 이 스타일은 자기 자신에게도 적용이 되어서, 나중에 봐도 기억하기 쉽게 작성하는 것이 좋습니다.

ifelse

ifelse는 앞에 함수와는 다른 결과를 제공해서 사용하는 곳이 다릅니다. 우선 형태는 ifelse(조건, 조건이 true일때 할 것, 조건이 false일때 할 것)으로 구성됩니다. 그래서 기존의 데이터를 새로운 기준으로 조정해서 사용할 때 많이 사용합니다.

library(readr)
sd<-read_csv("./제3회 Big Data Competition-분석용데이터-05.멤버십여부.txt")
## Parsed with column specification:
## cols(
##   고객번호 = col_character(),
##   멤버십명 = col_character(),
##   가입년월 = col_integer()
## )
names(sd)[1]<-"고객번호"
sd<-data.frame(sd)

str(sd)
## 'data.frame':    7456 obs. of  3 variables:
##  $ 고객번호: chr  "00011" "00021" "00037" "00043" ...
##  $ 멤버십명: chr  "하이마트" "하이마트" "하이마트" "하이마트" ...
##  $ 가입년월: int  201512 201506 201306 201403 201411 201312 201506 201404 201406 201311 ...
summary(sd)
##    고객번호           멤버십명            가입년월     
##  Length:7456        Length:7456        Min.   :201210  
##  Class :character   Class :character   1st Qu.:201311  
##  Mode  :character   Mode  :character   Median :201407  
##                                        Mean   :201412  
##                                        3rd Qu.:201504  
##                                        Max.   :201512
sd$"최근고객"<-ifelse(sd$"가입년월">mean(sd$"가입년월"),"최근","최근아님")
head(sd)
##   고객번호 멤버십명 가입년월 최근고객
## 1    00011 하이마트   201512     최근
## 2    00021 하이마트   201506     최근
## 3    00037 하이마트   201306 최근아님
## 4    00043 하이마트   201403 최근아님
## 5    00044 하이마트   201411 최근아님
## 6    00061 하이마트   201312 최근아님

관련해서 여기를 가보시면 for문에 대한 간략한 방법을 질문하시고, 댓글로 여러 답변이 달렸는데, ifelse가 가장 좋은 해결책으로 보입니다. 확인해보세요.

try

tryerror를 우회하거나 활용하기 위해서 사용하는 함수입니다. 직접 사용할 일은 많지 않지만 함수의 실행에서 에러가 났을 때 (ex> data.frame은 데이터의 길이가 다르면 변수를 만들지 못하고 에러를 출력합니다.) 에러가 난 부분만 기록하고 넘기는 형태로 코드를 작성 할 수 있습니다. 더 섬세하 기능의 tryCatch 도 있으니 ?tryCatch를 확인해주세요.

noObj
print(1)
## Error in try(noObj) : object 'noObj' not found
try(noObj)
print(1)
## [1] 1

콘솔에서 실행하면 try(noObj)에서 에러가 발생합니다. 하지만 멈추는 것이 아니라 다음 코드를 실행하는 것이 그냥 noObj를 코드에 작성한 것과 차이점입니다. try를 입력해 보시면 silect 옵션이 있는데 TRUE로 해주면 에러 출력도 하지 않습니다.

err<-try(noObj)
err
## [1] "Error in try(noObj) : 객체 'noObj'를 찾을 수 없습니다\n"
## attr(,"class")
## [1] "try-error"
## attr(,"condition")
## <simpleError in doTryCatch(return(expr), name, parentenv, handler): 객체 'noObj'를 찾을 수 없습니다>
class(err)
## [1] "try-error"

위와 같은 식으로 try(함수)를 변수에 선언하면 class(변수)를 통해 조건문을 활용해서 에러가 발생했을 때를 직접적으로 우회할 수 있습니다.

반복문

repeat

repeat은 가장 단순한 형태의 반복 구분입니다. 그냥 repeat만 사용할 수도 있습니다만, repeat(print(1))을 실행하면 무한히 1을 출력하고 멈추지 않습니다. 강제로 멈추는 활동을 해주어야만 멈추니 주의해 주세요. 그래서 break 문법이 준비되어 있습니다.

break

break는 말 그대로 멈추라는 명령입니다. break는 독특하게 뒤에 ()를 붙이지 않고 활용하는 함수로 조건문이나 반복문 안에 쓰여서 조건문과 반복문을 멈추는 역할을 합니다.

x<-1
repeat(
  if(x>10){
    break
  } else {
  print(x)
  x<-x+1
  }
)
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6
## [1] 7
## [1] 8
## [1] 9
## [1] 10

while

사실 repeat문은 사용법이 조금 길이서 잘 사용하지 않습니다. 기능적인 대체는 while문으로 가능한데, whilewhile(조건){조건이 true인 동안 할 것}으로 구성됩니다.

x<-1
while(x<10){
  print(x)
  x<-x+1
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6
## [1] 7
## [1] 8
## [1] 9

위의 코드가 아까 repeat으로 만든 식과 같은 결과를 보여줍니다. 안의 조건이 달라서 이해가 어려우실 수 있어서 repeat을 다시 작성해보겠습니다.

x<-1
repeat(
  if(x<10){
    print(x)
    x<-x+1
  } else {
    break
  }
)
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6
## [1] 7
## [1] 8
## [1] 9

자유도가 높은 repeat에 비해서 while은 괄고 안의 조건이 TRUE일 때 동안만 동작합니다. 하지만 한 방법으로만 고정되어 있어서 오히려 혼란을 막고, 코드가 읽기 좋게 작성할 수 있는 장점이 있습니다.

for

for는 반복하는 내용을 쉽게 다루기 위해서 준비되어 있습니다. 예를 들어서 위에서 while로 작성된 것을 for로 다시 작성해 보겠습니다.

for(x in 1:9){
  print(x)
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6
## [1] 7
## [1] 8
## [1] 9

while에 비해 훨씬 간결해 졌습니다. 이해하기도 좋구요. for(반복에 사용할 변수 in 반복에 사용할 변수에 넣을 데이터를 가지는 벡터){반복 실행할 내용 - 반복에 사용할 변수를 활용함}의 형태로 사용합니다. 말로 풀어 쓰려니 오히려 어려워 보이는 것 같네요. 몇 가지 예시를 더 들어 보겠습니다.

data<-head(sd$"고객번호")
for(cNum in data){
  print(sd[sd$"고객번호"==cNum,])
}
##   고객번호 멤버십명 가입년월 최근고객
## 1    00011 하이마트   201512     최근
##   고객번호 멤버십명 가입년월 최근고객
## 2    00021 하이마트   201506     최근
##   고객번호 멤버십명 가입년월 최근고객
## 3    00037 하이마트   201306 최근아님
##   고객번호 멤버십명 가입년월 최근고객
## 4    00043 하이마트   201403 최근아님
##   고객번호 멤버십명 가입년월 최근고객
## 5    00044 하이마트   201411 최근아님
##   고객번호 멤버십명 가입년월 최근고객
## 6    00061 하이마트   201312 최근아님

N2H4의 사용예시도 복잡하게 무려 5중 for문(!)으로 구성되어 있습니다. 중간에 while, try, if도 다 사용되었으니 설명해 드리겠습니다.

next

에러에 대해 우회하는 것에 대해서 조건문을 주는 방법을 설명드렸었습니다. nextbreak와 비슷하지만 조건문이나 반복문을 멈추는 것이 아니라 다음 번으로 넘기는 역할을 합니다. 예를 들면 아래와 같습니다.

data<-head(sd$"고객번호")
for(cNum in data){
  if(sd[sd$"고객번호"==cNum,"최근고객"]=="최근"){next}
  print(sd[sd$"고객번호"==cNum,])
}
##   고객번호 멤버십명 가입년월 최근고객
## 3    00037 하이마트   201306 최근아님
##   고객번호 멤버십명 가입년월 최근고객
## 4    00043 하이마트   201403 최근아님
##   고객번호 멤버십명 가입년월 최근고객
## 5    00044 하이마트   201411 최근아님
##   고객번호 멤버십명 가입년월 최근고객
## 6    00061 하이마트   201312 최근아님

출력된 내용을 보면 sd[sd$"고객번호"==cNum,"최근고객"]=="최근"일 때 다음 줄에 있는 print를 하지 않고 다음(next)으로 넘어간 것을 확인할 수 있습니다. 이걸 통해서 조건에 따라 그 아래 내용을 실행하지 않고 다음번 반복으로 넘기는 것이 가능합니다.

N2H4의 사용예시에는 next를 사용하지 않고 while을 사용했는데, 크롤링 특성상 요청이 일부 실패도 할 수 있기 때문에 추가적인 시도를 하기 위해서 사용했습니다. 데이터를 전부 가져오는 것이 많이 중요하지 않다면 next를 사용하는 것이 더 간편하고 빠르게 작성하는 방법이 될 것 같습니다.

지금 예시를 눈으로 보여드리기 위해 for문 안을 print로 계속 채우고 있는데, print의 위치에 수행하고자 하는 함수를 작성하시면 됩니다.

X<-as.data.frame(matrix(1:64, ncol=4, dimnames=list(seq(1:16), c("a", "b", "c", "d"))))

X$a[c(1,3,10)]<-0


for (i in 1:nrow(X)){
  if (X$a[i]==0) {
      X$e[i]<-(-999) 
    } else {
      X$e[i]<-X$b[i]/X$c[i]
    }
}
X
##     a  b  c  d            e
## 1   0 17 33 49 -999.0000000
## 2   2 18 34 50    0.5294118
## 3   0 19 35 51 -999.0000000
## 4   4 20 36 52    0.5555556
## 5   5 21 37 53    0.5675676
## 6   6 22 38 54    0.5789474
## 7   7 23 39 55    0.5897436
## 8   8 24 40 56    0.6000000
## 9   9 25 41 57    0.6097561
## 10  0 26 42 58 -999.0000000
## 11 11 27 43 59    0.6279070
## 12 12 28 44 60    0.6363636
## 13 13 29 45 61    0.6444444
## 14 14 30 46 62    0.6521739
## 15 15 31 47 63    0.6595745
## 16 16 32 48 64    0.6666667

ifelse 함수에서 소개했던 질문쪽의 for로 작성된 코드입니다. for로 작성하는 것이 사실 생각하기 쉬운 방법이라고 생각합니다. 저는 심지어 처음에는 for로 작성하라고 권장합니다. 문제를 직접 겪고, 그 문제를 해결하는 방법을 찾으려할 때 그 방법이 더 몸에 남는 것 같습니다.

아래 apply를 하기 전에 forifelse가 얼마나 다른지 한 번 비교해 보겠습니다.

library(ggplot2)
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:lubridate':
## 
##     intersect, setdiff, union
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(tidyr)

times<-c(100,1000,10000,30000,50000,100000)
tData<-c()
for(tm in times){

  X<-as.data.frame(matrix(1:tm, ncol=4, dimnames=list(seq(1:(tm/4)), c("a", "b", "c", "d"))))
  
  X$a[c(1,3,10)]<-0
  
  forTime<-system.time(
    for (i in 1:nrow(X)){
      if (X$a[i]==0) {
          X$e[i]<-(-999) 
        } else {
          X$e[i]<-X$b[i]/X$c[i]
        }
    }
  )
  ifelseTime<-system.time(X$e <- ifelse(X$a == 0, -999, X$b/X$c))
  
  forTime<-cbind(data.frame(tm,cate="forTime"),t(as.matrix(forTime)))
  ifelseTime<-cbind(data.frame(tm,cate="ifelseTime"),t(as.matrix(ifelseTime)))
  tData<-rbind(tData,forTime,ifelseTime)
  
}

tData<-tData %>% select(tm:elapsed) %>% gather(tm,cate)
names(tData)<-c("iter","cate","timeName","time")
ggplot(tData,aes(x=iter,y=time,fill=cate,color=cate)) + geom_point(stat="identity")

tm<-1000000
X<-as.data.frame(matrix(1:tm, ncol=4, dimnames=list(seq(1:(tm/4)), c("a", "b", "c", "d"))))
ifelseTime<-system.time(X$e <- ifelse(X$a == 0, -999, X$b/X$c))
ifelseTime
##    user  system elapsed 
##    0.03    0.00    0.03

이렇게 for를 사용하지 않고 다른 방법을 사용하는 것으로 벡터연산이 있습니다. 이름이 중요하진 않으니 R이 감당할 수 있는 수준의 데이터를 apply를 통해서 다루는 법을 알아보겠습니다.

apply류의 함수들

apply

apply 함수에 대해서 알아보겠습니다. apply는 행이나 열 방향의 데이터를 한 번에 계산하는데 사용합니다.

set.seed(1)
( myMat <- matrix(round(rnorm(16,10),2),4,4) )
##       [,1]  [,2]  [,3]  [,4]
## [1,]  9.37 10.33 10.58  9.38
## [2,] 10.18  9.18  9.69  7.79
## [3,]  9.16 10.49 11.51 11.12
## [4,] 11.60 10.74 10.39  9.96

위의 mymat에서 각 열의 평균을 구하고 싶으면 이렇게 하면 됩니다.

mean(myMat[,1])
## [1] 10.0775
mean(myMat[,2])
## [1] 10.185
mean(myMat[,3])
## [1] 10.5425
mean(myMat[,4])
## [1] 9.5625

우리는 for를 배웠으니 좀 고쳐 봅시다.

for(i in 1:4){
  mean(myMat[,i])
}

이게 또 데이터가 따로따로라 c도 해줘야 하는 군요.

myMean <- c(
  mean(myMat[,1]),
  mean(myMat[,2]),
  mean(myMat[,3]),
  mean(myMat[,4])
)
myMean
## [1] 10.0775 10.1850 10.5425  9.5625

for를 사용하면 이렇게 됩니다.

myMean <- c()
for(i in 1:4){
  myMean<-c(myMean,mean(myMat[,i]))
}
myMean
## [1] 10.0775 10.1850 10.5425  9.5625

여기서 함수화도 많이 진행하는 것 같더군요.

myLoop <- function(somemat) {
  myMean <- c()
  for(i in 1:ncol(somemat)){
    myMean<-c(myMean,mean(myMat[,i]))
  }
  return(myMean)
}

myLoop(myMat)
## [1] 10.0775 10.1850 10.5425  9.5625

근데 이제 열방향 mean 함수를 만드는게 끝났네요. 행방향을 진행하려면 똑같은걸 더 만들어야 합니다. 한 함수에 합쳐서 옵션으로 줘도 좋을 것 같군요. 한번 만들어 보세요.

하지만 apply는 이 걸 한줄에 할 수 있게 해줍니다.

apply(myMat, 2, mean)
## [1] 10.0775 10.1850 10.5425  9.5625

위에 결과와 비교해 보세요. 위에서 사용한 identical 함수로 두 결과를 비교해 보겠습니다.

identical(myLoop(myMat),apply(myMat, 2, mean))
## [1] TRUE

?apply를 통해 중간의 숫자가 어떤 의미를 가지는지 확인해 보세요. 1은 같은 행끼리의 계산을, 2는 같은 열끼리의 계산을 의미합니다. apply는 3가지 옵션을 가지는데, 첫 번째는 데이터, 두 번째는 계산의 방향, 세 번째는 계산에 사용할 함수입니다. 함수부분은 다양한 함수를 사용할 수 있습니다.

apply(myMat,2,class)
## [1] "numeric" "numeric" "numeric" "numeric"
apply(myMat,2,sum) 
## [1] 40.31 40.74 42.17 38.25
apply(myMat,2,quantile) 
##         [,1]    [,2]    [,3]    [,4]
## 0%    9.1600  9.1800  9.6900  7.7900
## 25%   9.3175 10.0425 10.2150  8.9825
## 50%   9.7750 10.4100 10.4850  9.6700
## 75%  10.5350 10.5525 10.8125 10.2500
## 100% 11.6000 10.7400 11.5100 11.1200

자주 사용하는 평균이나 합 같은 경우는 함수로도 구현되어 있습니다. rowMeans, colMeans, rowSums, colSums가 그것 입니다. 각각 apply로 어떻게 하면 되는지 생각해 보세요.

apply에 적용하는 함수 안에 데이터만 들어가는 함수 이외에 다른 옵션을 지정해야 할 수 있습니다. apply,를 이용해서 다음 옵션으로 사용하는 함수 안의 옵션을 작성할 수 있습니다.

myMat[1,4]<-NA
apply(myMat,2,sum)
## [1] 40.31 40.74 42.17    NA
apply(myMat,2,sum, na.rm = TRUE)
## [1] 40.31 40.74 42.17 28.87

apply에서 계산에 사용할 함수는 사용자가 만들어서 진행할 수도 있고, 임시로 만들 수도 있습니다.

naSum <- function(x){
  return(sum(x,na.rm = TRUE))
}

apply(myMat,2,naSum)
## [1] 40.31 40.74 42.17 28.87
apply(myMat,2,function(x) sum(x,na.rm = TRUE))
## [1] 40.31 40.74 42.17 28.87

만들어야 할 함수가 복잡하지 않으면 저는 임시로 작성하는 방법을 사용하는 편입니다.

apply-family

applylapply, tapply, sapply, mapply 등의 apply-family를 가지고 있습니다.

우선 lapply부터 살펴보겠습니다. 앞에 l이 붙으면서 list 자료형에 대해 apply의 역할을 수행하는 함수라는 의미가 붙었습니다. 결과도 list로 나옵니다.

(listData <- list(a = 1, b = 1:3, c = 10:100) )
## $a
## [1] 1
## 
## $b
## [1] 1 2 3
## 
## $c
##  [1]  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26
## [18]  27  28  29  30  31  32  33  34  35  36  37  38  39  40  41  42  43
## [35]  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60
## [52]  61  62  63  64  65  66  67  68  69  70  71  72  73  74  75  76  77
## [69]  78  79  80  81  82  83  84  85  86  87  88  89  90  91  92  93  94
## [86]  95  96  97  98  99 100
lapply(listData, length) 
## $a
## [1] 1
## 
## $b
## [1] 3
## 
## $c
## [1] 91
lapply(listData, sum)
## $a
## [1] 1
## 
## $b
## [1] 6
## 
## $c
## [1] 5005

list 자료형은 사용하시면서 느끼시겠지만 다른 곳에 사용하기 불편한 점이 있습니다. 그래서 다시 list를 푸는 방법으로 unlist를 사용하는데요. ?unlist를 입력해서 내용을 확인해보세요.

(listData <- list(a = 1, b = 1:3, c = 10:100) )
## $a
## [1] 1
## 
## $b
## [1] 1 2 3
## 
## $c
##  [1]  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26
## [18]  27  28  29  30  31  32  33  34  35  36  37  38  39  40  41  42  43
## [35]  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60
## [52]  61  62  63  64  65  66  67  68  69  70  71  72  73  74  75  76  77
## [69]  78  79  80  81  82  83  84  85  86  87  88  89  90  91  92  93  94
## [86]  95  96  97  98  99 100
unlist(lapply(listData, length))
##  a  b  c 
##  1  3 91
unlist(lapply(listData, sum))
##    a    b    c 
##    1    6 5005

그런데 입력을 list로 받는 것은 어쩔수 없다고 쳐도, 결과물은 위처럼 vector로 받는 것이 편한 경우가 많습니다. unlist(lapply(데이터,함수))sapply와 같은 동작을 합니다.

(listData <- list(a = 1, b = 1:3, c = 10:100) )
## $a
## [1] 1
## 
## $b
## [1] 1 2 3
## 
## $c
##  [1]  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26
## [18]  27  28  29  30  31  32  33  34  35  36  37  38  39  40  41  42  43
## [35]  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60
## [52]  61  62  63  64  65  66  67  68  69  70  71  72  73  74  75  76  77
## [69]  78  79  80  81  82  83  84  85  86  87  88  89  90  91  92  93  94
## [86]  95  96  97  98  99 100
sapply(listData, length)
##  a  b  c 
##  1  3 91
sapply(listData, sum)
##    a    b    c 
##    1    6 5005

이외에도 list안에 list까지 계산하는 rapply, 지정한 이름으로 실행할 수 있는 tapply 등이 있습니다만, 거의 applysapply만 사용한 것 같습니다.

더 궁금하신 사항은 여기를 참고해 주세요.

tidy data 개념과 dplyr+tidyr로 데이터 다루기

데이터 분석을 어렵게 하는 여러 이유들이 있습니다. 이게 개발 커뮤니티에서 말하는 기술 부채와 같은 개념이지 않나 생각이 들어 데이터 부채라는 표현을 사용해 보았습니다. 여러 데이터 관련 구루들이 강조하는 바, 데이터 분석의 대부분의 시간(약 80%)은 데이터 수집과 전처리, 정제에 사용됩니다. 계륵 같은 일이죠. garbage in, garbage out; GIGO 이니까요. 데이터가 많지 않았던 시대에는 처리에 시간을 쏟는 것이 너무 당연한 일이었습니다. 하지만 데이터 생성을 설계할 수 있는 입장(서비스 제공자, 마케터 등)에서는 저 시간은 명확하게 비용, 즉 데이터 부채가 되는 것입니다.

이 데이터 부채가 쌓이는 것을 처음부터 막을 수 있도록 데이터가 저장되는 방식에 대해 제안된 개념이 있는데 그것이 tidy data입니다. tidy data란 개념은 Hadley Wickham이 제안했습니다.

단정한 데이터

이것에 대해 본인이 직접 장문의 설명을 한 것도 있고 R 한글 형태소 분석 패키지인 konlp을 만드신 고감자님의 한글 설명, MS의 데이터 과학자이시자 헬로우 데이터과학의 저자이신 김진영님의 도서 블로그에도 너무 잘 설명되어 있습니다. 추가로 더 내용이 필요하시면 참고하시기 바랍니다. 먼저 tidy data의 개념이 필요한 이유는 컴퓨터에게 분석을 시켜야(!) 하기 때문입니다. 그래서 tidy data는 사람이 눈으로 이해하기에는 적절하지 않을 수 있습니다. 이 곳이 진입장벽이 되기도 하는데, 현재 사용하고 있는 엑셀을 바로 R에 넣고 사용하고 싶은데, 잘 안되는 경우가 많습니다. 그것은 엑셀 파일내 데이터를 사람이 “보기 좋게” 위치했기 때문입니다. 그럼 이제 tidy data의 세 가지 조건을 원문(1)과 고감자님 번역(2), 김진영님 번역(3)순으로 살펴보겠습니다.

1.1 Each variable forms a column.
1.2 각 변수는 개별의 열(column)으로 존재한다.
1.3 각 열에는 개별 속성이 들어간다.

2.1 Each observation forms a row.
2.2 각 관측치는 행(row)를 구성한다.
2.3 각 행에는 개별 관찰 항목이 들어간다.

3.1 Each type of observational unit forms a table.
3.2 각 테이블은 단 하나의 관측기준에 의해서 조직된 데이터를 저장한다.
3.3 각 테이블에는 단일 유형의 데이터가 들어간다.

tidydata

tidydata

1번을 보기 전에 2번을 먼저 보겠습니다.(2>1>3 순으로 쉬워요.) 2번은 단순합니다. 하나의 데이터가 한 줄(행)을 구성해야 한다는 것입니다. 설문지를 예로 들면 한명의 설문 결과가 한 줄로써 저장되는 것이죠. Each observation(개별 관찰)은 하나의 관찰 결과(=설문지 하나)를 뜻합니다. sql이나 data.frame에서 보셨듯 row는 조건으로 데이터를 filter할 수 있는 공간입니다. 그렇기 때문에 각 row는 개별 데이터를 의미합니다.

1번은 2번과 같은 하나가 들어가는 개념이긴 합니다만 하나의 variable, 변수, 개별 속성이라는 점이 조금 어렵습니다. 설문지 예시는 쉽습니다. 하나의 문항이라고 이해하면 되거든요. 그런데 variable이라는게 뭔지를 아는 것이 저는 조금 어려웠습니다. 찾아보니 영어상은 변수, 변할수 있는 수(여기서는 수보다는 값이라고 이해하시면 좋습니다.)인데 그 변수를 대표하는 이름이 컬럼명이라고 생각하면 좋더군요.

그런데 컬럼이 변수에 속하는 값으로 구성되는 경우가 있습니다. 예를들어 날짜가 컬럼명에 들어간 경우죠. 이렇게 생긴 데이터를 wide form이라고 합니다. 날짜는 변수에 들어갈 값이기 때문에 컬럼명을 날짜(date, datetime, when 등)로 정하고 컬럼에 속하는 cell에 날짜가 들어가는 형태로 구성하는 것이 tidy data의 조건을 충족하는 셈이 됩니다.

3번은 단일 테이블이 어떻게 구성되어야 하는지를 알려주는 조건입니다. 김진영님의 번역이 좀 이해하기 쉬운 것 같습니다. 테이블 하나에 하나의 데이터가 들어가야 된다는 뜻인데요, 아래 dplyr과 tidyr을 배우면서 예시들도 같이 보겠습니다.

dplyr + tidyr

dplyrplyr 패키지의 data.frame 전용이라는 의미를 가지고 있습니다. plyr은 데이터의 분해 - 적용 - 재조립 전략을 실행할 수 있는 패키지입니다. 이 전략을 data.frame 형태에서 실행하기 위해서 여러 명령어들을 제공합니다. 잘 정돈된 데이터 프레임분해 - 적용 - 재조립 전략을 실행하기 쉬우며 데이터를 잘 정돈하기 위해 tidyr 패키지를 함께 사용할 수 있습니다. 최근 ggplot2, dplyr, tidyr 등 tidy data의 개념과 같은 맥락에 있는 패키지들이 하나로 모여 tidyverse 패키지가 되었습니다.

library(tidyverse)
## Loading tidyverse: tibble
## Loading tidyverse: purrr
## Conflicts with tidy packages ----------------------------------------------
## as.difftime(): lubridate, base
## date():        lubridate, base
## filter():      dplyr, stats
## intersect():   lubridate, base
## lag():         dplyr, stats
## setdiff():     lubridate, base
## union():       lubridate, base

pipe 연산자 %>%

%>%는 함수의 사용 방향을 바꿔서 읽고 이해하기 쉽게 만든 연산자입니다.

g(f(y)) == y %>% f() %>% g()

이렇게 사용하고 tidyverse 패키지 전반적으로 사용하는 방식입니다.
.으로 앞의 변수의 위치를 지정할 수도 있고, 괄호 안에 작성할 것이 없을 때는 괄호를 생략할 수도 있습니다.

g(f(x,y,z)) == y %>% f(x, . , z) %>% g

dplyr 명령어 소개

dplyr에는 행에 조건을 줘서 부분을 불러오는 filter(), 필요한 컬럼만 선택하는 select(), 새로운 컬럼을 계산하는 mutate(), 조건에 따라 재정렬 할 수 있는 arrange(), group_by()와 함께 써서 요약값을 계산할 수 있는 summarise()가 있습니다. group_by()mutate(), filter()와도 사용할 수 있습니다.

if(!require(nycflights13)) install.packages("nycflights13")
## Loading required package: nycflights13
library(nycflights13)
flights
## # A tibble: 336,776 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     1     1      517            515         2      830
##  2  2013     1     1      533            529         4      850
##  3  2013     1     1      542            540         2      923
##  4  2013     1     1      544            545        -1     1004
##  5  2013     1     1      554            600        -6      812
##  6  2013     1     1      554            558        -4      740
##  7  2013     1     1      555            600        -5      913
##  8  2013     1     1      557            600        -3      709
##  9  2013     1     1      557            600        -3      838
## 10  2013     1     1      558            600        -2      753
## # ... with 336,766 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>

첫번째 filter()를 사용해 보겠습니다.

filter(flights, month == 1, day == 1)
## Warning: package 'bindrcpp' was built under R version 3.4.1
## # A tibble: 842 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     1     1      517            515         2      830
##  2  2013     1     1      533            529         4      850
##  3  2013     1     1      542            540         2      923
##  4  2013     1     1      544            545        -1     1004
##  5  2013     1     1      554            600        -6      812
##  6  2013     1     1      554            558        -4      740
##  7  2013     1     1      555            600        -5      913
##  8  2013     1     1      557            600        -3      709
##  9  2013     1     1      557            600        -3      838
## 10  2013     1     1      558            600        -2      753
## # ... with 832 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>
jan1 <- filter(flights, month == 1, day == 1)
(dec25 <- filter(flights, month == 12, day == 25))
## # A tibble: 719 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013    12    25      456            500        -4      649
##  2  2013    12    25      524            515         9      805
##  3  2013    12    25      542            540         2      832
##  4  2013    12    25      546            550        -4     1022
##  5  2013    12    25      556            600        -4      730
##  6  2013    12    25      557            600        -3      743
##  7  2013    12    25      557            600        -3      818
##  8  2013    12    25      559            600        -1      855
##  9  2013    12    25      559            600        -1      849
## 10  2013    12    25      600            600         0      850
## # ... with 709 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>
filter(flights, month == 11 | month == 12)
## # A tibble: 55,403 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013    11     1        5           2359         6      352
##  2  2013    11     1       35           2250       105      123
##  3  2013    11     1      455            500        -5      641
##  4  2013    11     1      539            545        -6      856
##  5  2013    11     1      542            545        -3      831
##  6  2013    11     1      549            600       -11      912
##  7  2013    11     1      550            600       -10      705
##  8  2013    11     1      554            600        -6      659
##  9  2013    11     1      554            600        -6      826
## 10  2013    11     1      554            600        -6      749
## # ... with 55,393 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>
nov_dec <- filter(flights, month %in% c(11, 12))
nov_dec
## # A tibble: 55,403 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013    11     1        5           2359         6      352
##  2  2013    11     1       35           2250       105      123
##  3  2013    11     1      455            500        -5      641
##  4  2013    11     1      539            545        -6      856
##  5  2013    11     1      542            545        -3      831
##  6  2013    11     1      549            600       -11      912
##  7  2013    11     1      550            600       -10      705
##  8  2013    11     1      554            600        -6      659
##  9  2013    11     1      554            600        -6      826
## 10  2013    11     1      554            600        -6      749
## # ... with 55,393 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>
filter(flights, !(arr_delay > 120 | dep_delay > 120))
## # A tibble: 316,050 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     1     1      517            515         2      830
##  2  2013     1     1      533            529         4      850
##  3  2013     1     1      542            540         2      923
##  4  2013     1     1      544            545        -1     1004
##  5  2013     1     1      554            600        -6      812
##  6  2013     1     1      554            558        -4      740
##  7  2013     1     1      555            600        -5      913
##  8  2013     1     1      557            600        -3      709
##  9  2013     1     1      557            600        -3      838
## 10  2013     1     1      558            600        -2      753
## # ... with 316,040 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>
filter(flights, arr_delay <= 120, dep_delay <= 120)
## # A tibble: 316,050 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     1     1      517            515         2      830
##  2  2013     1     1      533            529         4      850
##  3  2013     1     1      542            540         2      923
##  4  2013     1     1      544            545        -1     1004
##  5  2013     1     1      554            600        -6      812
##  6  2013     1     1      554            558        -4      740
##  7  2013     1     1      555            600        -5      913
##  8  2013     1     1      557            600        -3      709
##  9  2013     1     1      557            600        -3      838
## 10  2013     1     1      558            600        -2      753
## # ... with 316,040 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>

arrange()는 조건을 바탕으로 정렬을 다시 해줍니다.

arrange(flights, year, month, day)
## # A tibble: 336,776 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     1     1      517            515         2      830
##  2  2013     1     1      533            529         4      850
##  3  2013     1     1      542            540         2      923
##  4  2013     1     1      544            545        -1     1004
##  5  2013     1     1      554            600        -6      812
##  6  2013     1     1      554            558        -4      740
##  7  2013     1     1      555            600        -5      913
##  8  2013     1     1      557            600        -3      709
##  9  2013     1     1      557            600        -3      838
## 10  2013     1     1      558            600        -2      753
## # ... with 336,766 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>
arrange(flights, desc(arr_delay))
## # A tibble: 336,776 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     1     9      641            900      1301     1242
##  2  2013     6    15     1432           1935      1137     1607
##  3  2013     1    10     1121           1635      1126     1239
##  4  2013     9    20     1139           1845      1014     1457
##  5  2013     7    22      845           1600      1005     1044
##  6  2013     4    10     1100           1900       960     1342
##  7  2013     3    17     2321            810       911      135
##  8  2013     7    22     2257            759       898      121
##  9  2013    12     5      756           1700       896     1058
## 10  2013     5     3     1133           2055       878     1250
## # ... with 336,766 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>
df <- tibble(x = c(5, 2, NA))
arrange(df, x)
## # A tibble: 3 x 1
##       x
##   <dbl>
## 1     2
## 2     5
## 3    NA
arrange(df, desc(x))
## # A tibble: 3 x 1
##       x
##   <dbl>
## 1     5
## 2     2
## 3    NA

select()는 컬럼을 선택하는 함수라고 했습니다.

select(flights, year, month, day)
## # A tibble: 336,776 x 3
##     year month   day
##    <int> <int> <int>
##  1  2013     1     1
##  2  2013     1     1
##  3  2013     1     1
##  4  2013     1     1
##  5  2013     1     1
##  6  2013     1     1
##  7  2013     1     1
##  8  2013     1     1
##  9  2013     1     1
## 10  2013     1     1
## # ... with 336,766 more rows
select(flights, year:day)
## # A tibble: 336,776 x 3
##     year month   day
##    <int> <int> <int>
##  1  2013     1     1
##  2  2013     1     1
##  3  2013     1     1
##  4  2013     1     1
##  5  2013     1     1
##  6  2013     1     1
##  7  2013     1     1
##  8  2013     1     1
##  9  2013     1     1
## 10  2013     1     1
## # ... with 336,766 more rows
select(flights, -(year:day))
## # A tibble: 336,776 x 16
##    dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay
##       <int>          <int>     <dbl>    <int>          <int>     <dbl>
##  1      517            515         2      830            819        11
##  2      533            529         4      850            830        20
##  3      542            540         2      923            850        33
##  4      544            545        -1     1004           1022       -18
##  5      554            600        -6      812            837       -25
##  6      554            558        -4      740            728        12
##  7      555            600        -5      913            854        19
##  8      557            600        -3      709            723       -14
##  9      557            600        -3      838            846        -8
## 10      558            600        -2      753            745         8
## # ... with 336,766 more rows, and 10 more variables: carrier <chr>,
## #   flight <int>, tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>,
## #   distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>

select와 함께 사용하는 함수로 starts_with("abc"), ends_with("xyz"), contains("ijk"), matches("(.)\\1"), num_range("x", 1:3)등을 들 수 있습니다. ?select를 실행해서 자세한 사항을 확인해보세요.

rename()은 컬럼의 이름을 바꾸는 함수고, everything()은 선택한 것 이외에 전부를 뜻합니다.

rename(flights, tail_num = tailnum)
## # A tibble: 336,776 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     1     1      517            515         2      830
##  2  2013     1     1      533            529         4      850
##  3  2013     1     1      542            540         2      923
##  4  2013     1     1      544            545        -1     1004
##  5  2013     1     1      554            600        -6      812
##  6  2013     1     1      554            558        -4      740
##  7  2013     1     1      555            600        -5      913
##  8  2013     1     1      557            600        -3      709
##  9  2013     1     1      557            600        -3      838
## 10  2013     1     1      558            600        -2      753
## # ... with 336,766 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tail_num <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>
select(flights, time_hour, air_time, everything())
## # A tibble: 336,776 x 19
##              time_hour air_time  year month   day dep_time sched_dep_time
##                 <dttm>    <dbl> <int> <int> <int>    <int>          <int>
##  1 2013-01-01 05:00:00      227  2013     1     1      517            515
##  2 2013-01-01 05:00:00      227  2013     1     1      533            529
##  3 2013-01-01 05:00:00      160  2013     1     1      542            540
##  4 2013-01-01 05:00:00      183  2013     1     1      544            545
##  5 2013-01-01 06:00:00      116  2013     1     1      554            600
##  6 2013-01-01 05:00:00      150  2013     1     1      554            558
##  7 2013-01-01 06:00:00      158  2013     1     1      555            600
##  8 2013-01-01 06:00:00       53  2013     1     1      557            600
##  9 2013-01-01 06:00:00      140  2013     1     1      557            600
## 10 2013-01-01 06:00:00      138  2013     1     1      558            600
## # ... with 336,766 more rows, and 12 more variables: dep_delay <dbl>,
## #   arr_time <int>, sched_arr_time <int>, arr_delay <dbl>, carrier <chr>,
## #   flight <int>, tailnum <chr>, origin <chr>, dest <chr>, distance <dbl>,
## #   hour <dbl>, minute <dbl>

mutate()는 새로운 변수 계산을 위해서 필요합니다.

flights_sml <- select(flights, 
  year:day, 
  ends_with("delay"), 
  distance, 
  air_time
)
mutate(flights_sml,
  gain = arr_delay - dep_delay,
  speed = distance / air_time * 60
)
## # A tibble: 336,776 x 9
##     year month   day dep_delay arr_delay distance air_time  gain    speed
##    <int> <int> <int>     <dbl>     <dbl>    <dbl>    <dbl> <dbl>    <dbl>
##  1  2013     1     1         2        11     1400      227     9 370.0441
##  2  2013     1     1         4        20     1416      227    16 374.2731
##  3  2013     1     1         2        33     1089      160    31 408.3750
##  4  2013     1     1        -1       -18     1576      183   -17 516.7213
##  5  2013     1     1        -6       -25      762      116   -19 394.1379
##  6  2013     1     1        -4        12      719      150    16 287.6000
##  7  2013     1     1        -5        19     1065      158    24 404.4304
##  8  2013     1     1        -3       -14      229       53   -11 259.2453
##  9  2013     1     1        -3        -8      944      140    -5 404.5714
## 10  2013     1     1        -2         8      733      138    10 318.6957
## # ... with 336,766 more rows
mutate(flights_sml,
  gain = arr_delay - dep_delay,
  hours = air_time / 60,
  gain_per_hour = gain / hours
)
## # A tibble: 336,776 x 10
##     year month   day dep_delay arr_delay distance air_time  gain     hours
##    <int> <int> <int>     <dbl>     <dbl>    <dbl>    <dbl> <dbl>     <dbl>
##  1  2013     1     1         2        11     1400      227     9 3.7833333
##  2  2013     1     1         4        20     1416      227    16 3.7833333
##  3  2013     1     1         2        33     1089      160    31 2.6666667
##  4  2013     1     1        -1       -18     1576      183   -17 3.0500000
##  5  2013     1     1        -6       -25      762      116   -19 1.9333333
##  6  2013     1     1        -4        12      719      150    16 2.5000000
##  7  2013     1     1        -5        19     1065      158    24 2.6333333
##  8  2013     1     1        -3       -14      229       53   -11 0.8833333
##  9  2013     1     1        -3        -8      944      140    -5 2.3333333
## 10  2013     1     1        -2         8      733      138    10 2.3000000
## # ... with 336,766 more rows, and 1 more variables: gain_per_hour <dbl>
transmute(flights,
  gain = arr_delay - dep_delay,
  hours = air_time / 60,
  gain_per_hour = gain / hours
)
## # A tibble: 336,776 x 3
##     gain     hours gain_per_hour
##    <dbl>     <dbl>         <dbl>
##  1     9 3.7833333      2.378855
##  2    16 3.7833333      4.229075
##  3    31 2.6666667     11.625000
##  4   -17 3.0500000     -5.573770
##  5   -19 1.9333333     -9.827586
##  6    16 2.5000000      6.400000
##  7    24 2.6333333      9.113924
##  8   -11 0.8833333    -12.452830
##  9    -5 2.3333333     -2.142857
## 10    10 2.3000000      4.347826
## # ... with 336,766 more rows

특별히 mutate()와 함께 사용하는 함수중에 lag()lead()를 소개할까 합니다.

(x <- 1:10)
##  [1]  1  2  3  4  5  6  7  8  9 10
lag(x)
##  [1] NA  1  2  3  4  5  6  7  8  9
lead(x)
##  [1]  2  3  4  5  6  7  8  9 10 NA

mutate()가 각 행에 대한 계산 결과를 하나의 컬럼으로 만들어 주는 것이라면 summarise()는 일정 조건(대부분 group_by()를 이용한 그룹화)에 해당하는 계산을 수행해줍니다.

summarise(flights, delay = mean(dep_delay, na.rm = TRUE))
## # A tibble: 1 x 1
##      delay
##      <dbl>
## 1 12.63907
by_day <- group_by(flights, year, month, day)
summarise(by_day, delay = mean(dep_delay, na.rm = TRUE))
## # A tibble: 365 x 4
## # Groups:   year, month [?]
##     year month   day     delay
##    <int> <int> <int>     <dbl>
##  1  2013     1     1 11.548926
##  2  2013     1     2 13.858824
##  3  2013     1     3 10.987832
##  4  2013     1     4  8.951595
##  5  2013     1     5  5.732218
##  6  2013     1     6  7.148014
##  7  2013     1     7  5.417204
##  8  2013     1     8  2.553073
##  9  2013     1     9  2.276477
## 10  2013     1    10  2.844995
## # ... with 355 more rows
daily <- group_by(flights, year, month, day)
(per_day   <- summarise(daily, flights = n()))
## # A tibble: 365 x 4
## # Groups:   year, month [?]
##     year month   day flights
##    <int> <int> <int>   <int>
##  1  2013     1     1     842
##  2  2013     1     2     943
##  3  2013     1     3     914
##  4  2013     1     4     915
##  5  2013     1     5     720
##  6  2013     1     6     832
##  7  2013     1     7     933
##  8  2013     1     8     899
##  9  2013     1     9     902
## 10  2013     1    10     932
## # ... with 355 more rows
(per_month <- summarise(per_day, flights = sum(flights)))
## # A tibble: 12 x 3
## # Groups:   year [?]
##     year month flights
##    <int> <int>   <int>
##  1  2013     1   27004
##  2  2013     2   24951
##  3  2013     3   28834
##  4  2013     4   28330
##  5  2013     5   28796
##  6  2013     6   28243
##  7  2013     7   29425
##  8  2013     8   29327
##  9  2013     9   27574
## 10  2013    10   28889
## 11  2013    11   27268
## 12  2013    12   28135
(per_year  <- summarise(per_month, flights = sum(flights)))
## # A tibble: 1 x 2
##    year flights
##   <int>   <int>
## 1  2013  336776
daily %>% 
  ungroup() %>% 
  summarise(flights = n())
## # A tibble: 1 x 1
##   flights
##     <int>
## 1  336776

group_by()mutate(), filter()와도 사용할 수 있다고 했습니다.

flights_sml %>% 
  group_by(year, month, day) %>%
  filter(rank(desc(arr_delay)) < 10)
## # A tibble: 3,306 x 7
## # Groups:   year, month, day [365]
##     year month   day dep_delay arr_delay distance air_time
##    <int> <int> <int>     <dbl>     <dbl>    <dbl>    <dbl>
##  1  2013     1     1       853       851      184       41
##  2  2013     1     1       290       338     1134      213
##  3  2013     1     1       260       263      266       46
##  4  2013     1     1       157       174      213       60
##  5  2013     1     1       216       222      708      121
##  6  2013     1     1       255       250      589      115
##  7  2013     1     1       285       246     1085      146
##  8  2013     1     1       192       191      199       44
##  9  2013     1     1       379       456     1092      222
## 10  2013     1     2       224       207      550       94
## # ... with 3,296 more rows
popular_dests <- flights %>% 
  group_by(dest) %>% 
  filter(n() > 365)
popular_dests
## # A tibble: 332,577 x 19
## # Groups:   dest [77]
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1  2013     1     1      517            515         2      830
##  2  2013     1     1      533            529         4      850
##  3  2013     1     1      542            540         2      923
##  4  2013     1     1      544            545        -1     1004
##  5  2013     1     1      554            600        -6      812
##  6  2013     1     1      554            558        -4      740
##  7  2013     1     1      555            600        -5      913
##  8  2013     1     1      557            600        -3      709
##  9  2013     1     1      557            600        -3      838
## 10  2013     1     1      558            600        -2      753
## # ... with 332,567 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>
popular_dests %>% 
  filter(arr_delay > 0) %>% 
  mutate(prop_delay = arr_delay / sum(arr_delay)) %>% 
  select(year:day, dest, arr_delay, prop_delay)
## # A tibble: 131,106 x 6
## # Groups:   dest [77]
##     year month   day  dest arr_delay   prop_delay
##    <int> <int> <int> <chr>     <dbl>        <dbl>
##  1  2013     1     1   IAH        11 1.106740e-04
##  2  2013     1     1   IAH        20 2.012255e-04
##  3  2013     1     1   MIA        33 2.350026e-04
##  4  2013     1     1   ORD        12 4.239594e-05
##  5  2013     1     1   FLL        19 9.377853e-05
##  6  2013     1     1   ORD         8 2.826396e-05
##  7  2013     1     1   LAX         7 3.444441e-05
##  8  2013     1     1   DFW        31 2.817951e-04
##  9  2013     1     1   ATL        12 3.996017e-05
## 10  2013     1     1   DTW        16 1.157257e-04
## # ... with 131,096 more rows

tidyr 명령어 소개

tidyr에는 long formwide form으로 바꿔주는 spread(), 반대로 wide formlong form으로 바꿔주는 gather(), 여러 의미를 지닌 데이터를 특정 글자를 기준으로 분리해 주는 seperate(), 그 반대로 합치는 unite(), 데이터를 분리하는 폼을 지정해 줄 수 있는 extract()가 있습니다.

우선 내장된 데이터를 소개하겠습니다.

table1
## # A tibble: 6 x 4
##       country  year  cases population
##         <chr> <int>  <int>      <int>
## 1 Afghanistan  1999    745   19987071
## 2 Afghanistan  2000   2666   20595360
## 3      Brazil  1999  37737  172006362
## 4      Brazil  2000  80488  174504898
## 5       China  1999 212258 1272915272
## 6       China  2000 213766 1280428583
table2
## # A tibble: 12 x 4
##        country  year       type      count
##          <chr> <int>      <chr>      <int>
##  1 Afghanistan  1999      cases        745
##  2 Afghanistan  1999 population   19987071
##  3 Afghanistan  2000      cases       2666
##  4 Afghanistan  2000 population   20595360
##  5      Brazil  1999      cases      37737
##  6      Brazil  1999 population  172006362
##  7      Brazil  2000      cases      80488
##  8      Brazil  2000 population  174504898
##  9       China  1999      cases     212258
## 10       China  1999 population 1272915272
## 11       China  2000      cases     213766
## 12       China  2000 population 1280428583
table3
## # A tibble: 6 x 3
##       country  year              rate
## *       <chr> <int>             <chr>
## 1 Afghanistan  1999      745/19987071
## 2 Afghanistan  2000     2666/20595360
## 3      Brazil  1999   37737/172006362
## 4      Brazil  2000   80488/174504898
## 5       China  1999 212258/1272915272
## 6       China  2000 213766/1280428583
table4a
## # A tibble: 3 x 3
##       country `1999` `2000`
## *       <chr>  <int>  <int>
## 1 Afghanistan    745   2666
## 2      Brazil  37737  80488
## 3       China 212258 213766
table4b
## # A tibble: 3 x 3
##       country     `1999`     `2000`
## *       <chr>      <int>      <int>
## 1 Afghanistan   19987071   20595360
## 2      Brazil  172006362  174504898
## 3       China 1272915272 1280428583

이제 많이 사용하게 될 gather() 함수를 보겠습니다.

table4a
## # A tibble: 3 x 3
##       country `1999` `2000`
## *       <chr>  <int>  <int>
## 1 Afghanistan    745   2666
## 2      Brazil  37737  80488
## 3       China 212258 213766
table4a %>% 
  gather(`1999`, `2000`, key = "year", value = "cases")
## # A tibble: 6 x 3
##       country  year  cases
##         <chr> <chr>  <int>
## 1 Afghanistan  1999    745
## 2      Brazil  1999  37737
## 3       China  1999 212258
## 4 Afghanistan  2000   2666
## 5      Brazil  2000  80488
## 6       China  2000 213766
gather

gather

이번엔 반대과정인 spread()를 보겠습니다.

table2
## # A tibble: 12 x 4
##        country  year       type      count
##          <chr> <int>      <chr>      <int>
##  1 Afghanistan  1999      cases        745
##  2 Afghanistan  1999 population   19987071
##  3 Afghanistan  2000      cases       2666
##  4 Afghanistan  2000 population   20595360
##  5      Brazil  1999      cases      37737
##  6      Brazil  1999 population  172006362
##  7      Brazil  2000      cases      80488
##  8      Brazil  2000 population  174504898
##  9       China  1999      cases     212258
## 10       China  1999 population 1272915272
## 11       China  2000      cases     213766
## 12       China  2000 population 1280428583
spread(table2, key = type, value = count)
## # A tibble: 6 x 4
##       country  year  cases population
## *       <chr> <int>  <int>      <int>
## 1 Afghanistan  1999    745   19987071
## 2 Afghanistan  2000   2666   20595360
## 3      Brazil  1999  37737  172006362
## 4      Brazil  2000  80488  174504898
## 5       China  1999 212258 1272915272
## 6       China  2000 213766 1280428583
spread

spread

한 셀에 여러 값이 있어서 나눠야 할 때는 seperate()를 사용합니다. sep옵션을 주지 않아도, 간단한 것은 알아서 나눠줍니다.

table3
## # A tibble: 6 x 3
##       country  year              rate
## *       <chr> <int>             <chr>
## 1 Afghanistan  1999      745/19987071
## 2 Afghanistan  2000     2666/20595360
## 3      Brazil  1999   37737/172006362
## 4      Brazil  2000   80488/174504898
## 5       China  1999 212258/1272915272
## 6       China  2000 213766/1280428583
table3 %>% 
  separate(rate, into = c("cases", "population"))
## # A tibble: 6 x 4
##       country  year  cases population
## *       <chr> <int>  <chr>      <chr>
## 1 Afghanistan  1999    745   19987071
## 2 Afghanistan  2000   2666   20595360
## 3      Brazil  1999  37737  172006362
## 4      Brazil  2000  80488  174504898
## 5       China  1999 212258 1272915272
## 6       China  2000 213766 1280428583
table3 %>% 
  separate(rate, into = c("cases", "population"), convert = TRUE)
## # A tibble: 6 x 4
##       country  year  cases population
## *       <chr> <int>  <int>      <int>
## 1 Afghanistan  1999    745   19987071
## 2 Afghanistan  2000   2666   20595360
## 3      Brazil  1999  37737  172006362
## 4      Brazil  2000  80488  174504898
## 5       China  1999 212258 1272915272
## 6       China  2000 213766 1280428583
table3 %>% 
  separate(year, into = c("century", "year"), sep = 2)
## # A tibble: 6 x 4
##       country century  year              rate
## *       <chr>   <chr> <chr>             <chr>
## 1 Afghanistan      19    99      745/19987071
## 2 Afghanistan      20    00     2666/20595360
## 3      Brazil      19    99   37737/172006362
## 4      Brazil      20    00   80488/174504898
## 5       China      19    99 212258/1272915272
## 6       China      20    00 213766/1280428583

unite()는 합쳐주는 seperate()와는 반대의 기능을 가진 함수입니다.

table5 %>% 
  unite(new, century, year)
## # A tibble: 6 x 3
##       country   new              rate
## *       <chr> <chr>             <chr>
## 1 Afghanistan 19_99      745/19987071
## 2 Afghanistan 20_00     2666/20595360
## 3      Brazil 19_99   37737/172006362
## 4      Brazil 20_00   80488/174504898
## 5       China 19_99 212258/1272915272
## 6       China 20_00 213766/1280428583
table5 %>% 
  unite(new, century, year, sep = "")
## # A tibble: 6 x 3
##       country   new              rate
## *       <chr> <chr>             <chr>
## 1 Afghanistan  1999      745/19987071
## 2 Afghanistan  2000     2666/20595360
## 3      Brazil  1999   37737/172006362
## 4      Brazil  2000   80488/174504898
## 5       China  1999 212258/1272915272
## 6       China  2000 213766/1280428583

dplyr과 join

dplyr에는 join() 기능도 있습니다. 데이터를 먼저 소개하겠습니다.

airlines
## # A tibble: 16 x 2
##    carrier                        name
##      <chr>                       <chr>
##  1      9E           Endeavor Air Inc.
##  2      AA      American Airlines Inc.
##  3      AS        Alaska Airlines Inc.
##  4      B6             JetBlue Airways
##  5      DL        Delta Air Lines Inc.
##  6      EV    ExpressJet Airlines Inc.
##  7      F9      Frontier Airlines Inc.
##  8      FL AirTran Airways Corporation
##  9      HA      Hawaiian Airlines Inc.
## 10      MQ                   Envoy Air
## 11      OO       SkyWest Airlines Inc.
## 12      UA       United Air Lines Inc.
## 13      US             US Airways Inc.
## 14      VX              Virgin America
## 15      WN      Southwest Airlines Co.
## 16      YV          Mesa Airlines Inc.
airports
## # A tibble: 1,458 x 8
##      faa                           name      lat        lon   alt    tz
##    <chr>                          <chr>    <dbl>      <dbl> <int> <dbl>
##  1   04G              Lansdowne Airport 41.13047  -80.61958  1044    -5
##  2   06A  Moton Field Municipal Airport 32.46057  -85.68003   264    -6
##  3   06C            Schaumburg Regional 41.98934  -88.10124   801    -6
##  4   06N                Randall Airport 41.43191  -74.39156   523    -5
##  5   09J          Jekyll Island Airport 31.07447  -81.42778    11    -5
##  6   0A9 Elizabethton Municipal Airport 36.37122  -82.17342  1593    -5
##  7   0G6        Williams County Airport 41.46731  -84.50678   730    -5
##  8   0G7  Finger Lakes Regional Airport 42.88356  -76.78123   492    -5
##  9   0P2   Shoestring Aviation Airfield 39.79482  -76.64719  1000    -5
## 10   0S9          Jefferson County Intl 48.05381 -122.81064   108    -8
## # ... with 1,448 more rows, and 2 more variables: dst <chr>, tzone <chr>
planes
## # A tibble: 3,322 x 9
##    tailnum  year                    type     manufacturer     model
##      <chr> <int>                   <chr>            <chr>     <chr>
##  1  N10156  2004 Fixed wing multi engine          EMBRAER EMB-145XR
##  2  N102UW  1998 Fixed wing multi engine AIRBUS INDUSTRIE  A320-214
##  3  N103US  1999 Fixed wing multi engine AIRBUS INDUSTRIE  A320-214
##  4  N104UW  1999 Fixed wing multi engine AIRBUS INDUSTRIE  A320-214
##  5  N10575  2002 Fixed wing multi engine          EMBRAER EMB-145LR
##  6  N105UW  1999 Fixed wing multi engine AIRBUS INDUSTRIE  A320-214
##  7  N107US  1999 Fixed wing multi engine AIRBUS INDUSTRIE  A320-214
##  8  N108UW  1999 Fixed wing multi engine AIRBUS INDUSTRIE  A320-214
##  9  N109UW  1999 Fixed wing multi engine AIRBUS INDUSTRIE  A320-214
## 10  N110UW  1999 Fixed wing multi engine AIRBUS INDUSTRIE  A320-214
## # ... with 3,312 more rows, and 4 more variables: engines <int>,
## #   seats <int>, speed <int>, engine <chr>
weather
## # A tibble: 26,130 x 15
##    origin  year month   day  hour  temp  dewp humid wind_dir wind_speed
##     <chr> <dbl> <dbl> <int> <int> <dbl> <dbl> <dbl>    <dbl>      <dbl>
##  1    EWR  2013     1     1     0 37.04 21.92 53.97      230   10.35702
##  2    EWR  2013     1     1     1 37.04 21.92 53.97      230   13.80936
##  3    EWR  2013     1     1     2 37.94 21.92 52.09      230   12.65858
##  4    EWR  2013     1     1     3 37.94 23.00 54.51      230   13.80936
##  5    EWR  2013     1     1     4 37.94 24.08 57.04      240   14.96014
##  6    EWR  2013     1     1     6 39.02 26.06 59.37      270   10.35702
##  7    EWR  2013     1     1     7 39.02 26.96 61.63      250    8.05546
##  8    EWR  2013     1     1     8 39.02 28.04 64.43      240   11.50780
##  9    EWR  2013     1     1     9 39.92 28.04 62.21      250   12.65858
## 10    EWR  2013     1     1    10 39.02 28.04 64.43      260   12.65858
## # ... with 26,120 more rows, and 5 more variables: wind_gust <dbl>,
## #   precip <dbl>, pressure <dbl>, visib <dbl>, time_hour <dttm>

%>%join() 명령어로 쉽게 데이터를 합칠 수 있습니다.

flights2 <- flights %>% 
  select(year:day, hour, origin, dest, tailnum, carrier)
flights2
## # A tibble: 336,776 x 8
##     year month   day  hour origin  dest tailnum carrier
##    <int> <int> <int> <dbl>  <chr> <chr>   <chr>   <chr>
##  1  2013     1     1     5    EWR   IAH  N14228      UA
##  2  2013     1     1     5    LGA   IAH  N24211      UA
##  3  2013     1     1     5    JFK   MIA  N619AA      AA
##  4  2013     1     1     5    JFK   BQN  N804JB      B6
##  5  2013     1     1     6    LGA   ATL  N668DN      DL
##  6  2013     1     1     5    EWR   ORD  N39463      UA
##  7  2013     1     1     6    EWR   FLL  N516JB      B6
##  8  2013     1     1     6    LGA   IAD  N829AS      EV
##  9  2013     1     1     6    JFK   MCO  N593JB      B6
## 10  2013     1     1     6    LGA   ORD  N3ALAA      AA
## # ... with 336,766 more rows
flights2 %>%
  select(-origin, -dest) %>% 
  left_join(airlines, by = "carrier")
## # A tibble: 336,776 x 7
##     year month   day  hour tailnum carrier                     name
##    <int> <int> <int> <dbl>   <chr>   <chr>                    <chr>
##  1  2013     1     1     5  N14228      UA    United Air Lines Inc.
##  2  2013     1     1     5  N24211      UA    United Air Lines Inc.
##  3  2013     1     1     5  N619AA      AA   American Airlines Inc.
##  4  2013     1     1     5  N804JB      B6          JetBlue Airways
##  5  2013     1     1     6  N668DN      DL     Delta Air Lines Inc.
##  6  2013     1     1     5  N39463      UA    United Air Lines Inc.
##  7  2013     1     1     6  N516JB      B6          JetBlue Airways
##  8  2013     1     1     6  N829AS      EV ExpressJet Airlines Inc.
##  9  2013     1     1     6  N593JB      B6          JetBlue Airways
## 10  2013     1     1     6  N3ALAA      AA   American Airlines Inc.
## # ... with 336,766 more rows
flights2 %>%
  select(-origin, -dest) %>% 
  mutate(name = airlines$name[match(carrier, airlines$carrier)])
## # A tibble: 336,776 x 7
##     year month   day  hour tailnum carrier                     name
##    <int> <int> <int> <dbl>   <chr>   <chr>                    <chr>
##  1  2013     1     1     5  N14228      UA    United Air Lines Inc.
##  2  2013     1     1     5  N24211      UA    United Air Lines Inc.
##  3  2013     1     1     5  N619AA      AA   American Airlines Inc.
##  4  2013     1     1     5  N804JB      B6          JetBlue Airways
##  5  2013     1     1     6  N668DN      DL     Delta Air Lines Inc.
##  6  2013     1     1     5  N39463      UA    United Air Lines Inc.
##  7  2013     1     1     6  N516JB      B6          JetBlue Airways
##  8  2013     1     1     6  N829AS      EV ExpressJet Airlines Inc.
##  9  2013     1     1     6  N593JB      B6          JetBlue Airways
## 10  2013     1     1     6  N3ALAA      AA   American Airlines Inc.
## # ... with 336,766 more rows

key를 선정해주는 것과 아닌 것이 어떻게 다른지 봐주세요.

flights2 %>% 
  left_join(weather)
## Joining, by = c("year", "month", "day", "hour", "origin")
## # A tibble: 336,776 x 18
##     year month   day  hour origin  dest tailnum carrier  temp  dewp humid
##    <dbl> <dbl> <int> <dbl>  <chr> <chr>   <chr>   <chr> <dbl> <dbl> <dbl>
##  1  2013     1     1     5    EWR   IAH  N14228      UA    NA    NA    NA
##  2  2013     1     1     5    LGA   IAH  N24211      UA    NA    NA    NA
##  3  2013     1     1     5    JFK   MIA  N619AA      AA    NA    NA    NA
##  4  2013     1     1     5    JFK   BQN  N804JB      B6    NA    NA    NA
##  5  2013     1     1     6    LGA   ATL  N668DN      DL 39.92 26.06 57.33
##  6  2013     1     1     5    EWR   ORD  N39463      UA    NA    NA    NA
##  7  2013     1     1     6    EWR   FLL  N516JB      B6 39.02 26.06 59.37
##  8  2013     1     1     6    LGA   IAD  N829AS      EV 39.92 26.06 57.33
##  9  2013     1     1     6    JFK   MCO  N593JB      B6 39.02 26.06 59.37
## 10  2013     1     1     6    LGA   ORD  N3ALAA      AA 39.92 26.06 57.33
## # ... with 336,766 more rows, and 7 more variables: wind_dir <dbl>,
## #   wind_speed <dbl>, wind_gust <dbl>, precip <dbl>, pressure <dbl>,
## #   visib <dbl>, time_hour <dttm>
flights2 %>% 
  left_join(planes, by = "tailnum")
## # A tibble: 336,776 x 16
##    year.x month   day  hour origin  dest tailnum carrier year.y
##     <int> <int> <int> <dbl>  <chr> <chr>   <chr>   <chr>  <int>
##  1   2013     1     1     5    EWR   IAH  N14228      UA   1999
##  2   2013     1     1     5    LGA   IAH  N24211      UA   1998
##  3   2013     1     1     5    JFK   MIA  N619AA      AA   1990
##  4   2013     1     1     5    JFK   BQN  N804JB      B6   2012
##  5   2013     1     1     6    LGA   ATL  N668DN      DL   1991
##  6   2013     1     1     5    EWR   ORD  N39463      UA   2012
##  7   2013     1     1     6    EWR   FLL  N516JB      B6   2000
##  8   2013     1     1     6    LGA   IAD  N829AS      EV   1998
##  9   2013     1     1     6    JFK   MCO  N593JB      B6   2004
## 10   2013     1     1     6    LGA   ORD  N3ALAA      AA     NA
## # ... with 336,766 more rows, and 7 more variables: type <chr>,
## #   manufacturer <chr>, model <chr>, engines <int>, seats <int>,
## #   speed <int>, engine <chr>

왼쪽 테이블과 오른쪽 테이블의 어떤 key를 기준으로 join()할 건지 지정할 수 있습니다.

flights2 %>% 
  left_join(airports, c("dest" = "faa"))
## # A tibble: 336,776 x 15
##     year month   day  hour origin  dest tailnum carrier
##    <int> <int> <int> <dbl>  <chr> <chr>   <chr>   <chr>
##  1  2013     1     1     5    EWR   IAH  N14228      UA
##  2  2013     1     1     5    LGA   IAH  N24211      UA
##  3  2013     1     1     5    JFK   MIA  N619AA      AA
##  4  2013     1     1     5    JFK   BQN  N804JB      B6
##  5  2013     1     1     6    LGA   ATL  N668DN      DL
##  6  2013     1     1     5    EWR   ORD  N39463      UA
##  7  2013     1     1     6    EWR   FLL  N516JB      B6
##  8  2013     1     1     6    LGA   IAD  N829AS      EV
##  9  2013     1     1     6    JFK   MCO  N593JB      B6
## 10  2013     1     1     6    LGA   ORD  N3ALAA      AA
## # ... with 336,766 more rows, and 7 more variables: name <chr>, lat <dbl>,
## #   lon <dbl>, alt <int>, tz <dbl>, dst <chr>, tzone <chr>
flights2 %>% 
  left_join(airports, c("origin" = "faa"))
## # A tibble: 336,776 x 15
##     year month   day  hour origin  dest tailnum carrier
##    <int> <int> <int> <dbl>  <chr> <chr>   <chr>   <chr>
##  1  2013     1     1     5    EWR   IAH  N14228      UA
##  2  2013     1     1     5    LGA   IAH  N24211      UA
##  3  2013     1     1     5    JFK   MIA  N619AA      AA
##  4  2013     1     1     5    JFK   BQN  N804JB      B6
##  5  2013     1     1     6    LGA   ATL  N668DN      DL
##  6  2013     1     1     5    EWR   ORD  N39463      UA
##  7  2013     1     1     6    EWR   FLL  N516JB      B6
##  8  2013     1     1     6    LGA   IAD  N829AS      EV
##  9  2013     1     1     6    JFK   MCO  N593JB      B6
## 10  2013     1     1     6    LGA   ORD  N3ALAA      AA
## # ... with 336,766 more rows, and 7 more variables: name <chr>, lat <dbl>,
## #   lon <dbl>, alt <int>, tz <dbl>, dst <chr>, tzone <chr>

join() 함수는 base::merge(), SQL과 비교할 수 있습니다.

inner_join(x, y) == merge(x, y)
left_join(x, y)  == merge(x, y, all.x = TRUE)
right_join(x, y) == merge(x, y, all.y = TRUE),
full_join(x, y)  == merge(x, y, all.x = TRUE, all.y = TRUE)

inner_join(x, y, by = "z") == SELECT * FROM x INNER JOIN y ON x.z = y.z
left_join(x, y, by = "z")  == SELECT * FROM x LEFT OUTER JOIN y ON x.z = y.z
right_join(x, y, by = "z") == SELECT * FROM x RIGHT OUTER JOIN y ON x.z = y.z
full_join(x, y, by = "z")  == SELECT * FROM x FULL OUTER JOIN y ON x.z = y.z

data.table

data.table은 지금까지와는 조금 다른 문법을 가지고 있습니다. freadfwrite이라는 강력한 IO함수를 가지고 있으며 data.table은 패키지 명이면서 data.frame과 호환되는 자료형이기도 합니다. 자세한 내용은 여기를 참고해 주세요.

library(data.table)
## 
## Attaching package: 'data.table'
## The following object is masked from 'package:purrr':
## 
##     transpose
## The following objects are masked from 'package:dplyr':
## 
##     between, first, last
## The following objects are masked from 'package:lubridate':
## 
##     hour, isoweek, mday, minute, month, quarter, second, wday,
##     week, yday, year
url<-"https://github.com/arunsrinivasan/flights/wiki/NYCflights14/flights14.csv"
dir.create("./data",showWarnings = F)
download.file(url,destfile = "./data/flights14.csv")
system.time(flights <- read.csv("./data/flights14.csv"))
##    user  system elapsed 
##    2.44    0.11    2.60
system.time(flights <- fread("./data/flights14.csv"))
##    user  system elapsed 
##    0.27    0.02    0.33
flights
##         year month day dep_time dep_delay arr_time arr_delay cancelled
##      1: 2014     1   1      914        14     1238        13         0
##      2: 2014     1   1     1157        -3     1523        13         0
##      3: 2014     1   1     1902         2     2224         9         0
##      4: 2014     1   1      722        -8     1014       -26         0
##      5: 2014     1   1     1347         2     1706         1         0
##     ---                                                               
## 253312: 2014    10  31     1459         1     1747       -30         0
## 253313: 2014    10  31      854        -5     1147       -14         0
## 253314: 2014    10  31     1102        -8     1311        16         0
## 253315: 2014    10  31     1106        -4     1325        15         0
## 253316: 2014    10  31      824        -5     1045         1         0
##         carrier tailnum flight origin dest air_time distance hour min
##      1:      AA  N338AA      1    JFK  LAX      359     2475    9  14
##      2:      AA  N335AA      3    JFK  LAX      363     2475   11  57
##      3:      AA  N327AA     21    JFK  LAX      351     2475   19   2
##      4:      AA  N3EHAA     29    LGA  PBI      157     1035    7  22
##      5:      AA  N319AA    117    JFK  LAX      350     2475   13  47
##     ---                                                              
## 253312:      UA  N23708   1744    LGA  IAH      201     1416   14  59
## 253313:      UA  N33132   1758    EWR  IAH      189     1400    8  54
## 253314:      MQ  N827MQ   3591    LGA  RDU       83      431   11   2
## 253315:      MQ  N511MQ   3592    LGA  DTW       75      502   11   6
## 253316:      MQ  N813MQ   3599    LGA  SDF      110      659    8  24
dim(flights)
## [1] 253316     17
ans <- flights[origin == "JFK" & month == 6L]
head(ans)
##    year month day dep_time dep_delay arr_time arr_delay cancelled carrier
## 1: 2014     6   1      851        -9     1205        -5         0      AA
## 2: 2014     6   1     1220       -10     1522       -13         0      AA
## 3: 2014     6   1      718        18     1014        -1         0      AA
## 4: 2014     6   1     1024        -6     1314       -16         0      AA
## 5: 2014     6   1     1841        -4     2125       -45         0      AA
## 6: 2014     6   1     1454        -6     1757       -23         0      AA
##    tailnum flight origin dest air_time distance hour min
## 1:  N787AA      1    JFK  LAX      324     2475    8  51
## 2:  N795AA      3    JFK  LAX      329     2475   12  20
## 3:  N784AA      9    JFK  LAX      326     2475    7  18
## 4:  N791AA     19    JFK  LAX      320     2475   10  24
## 5:  N790AA     21    JFK  LAX      326     2475   18  41
## 6:  N785AA    117    JFK  LAX      329     2475   14  54
ans <- flights[1:2]
ans
##    year month day dep_time dep_delay arr_time arr_delay cancelled carrier
## 1: 2014     1   1      914        14     1238        13         0      AA
## 2: 2014     1   1     1157        -3     1523        13         0      AA
##    tailnum flight origin dest air_time distance hour min
## 1:  N338AA      1    JFK  LAX      359     2475    9  14
## 2:  N335AA      3    JFK  LAX      363     2475   11  57
ans <- flights[order(origin, -dest)]
head(ans)
##    year month day dep_time dep_delay arr_time arr_delay cancelled carrier
## 1: 2014     1   5      836         6     1151        49         0      EV
## 2: 2014     1   6      833         7     1111        13         0      EV
## 3: 2014     1   7      811        -6     1035       -13         0      EV
## 4: 2014     1   8      810        -7     1036       -12         0      EV
## 5: 2014     1   9      833        16     1055         7         0      EV
## 6: 2014     1  13      923        66     1154        66         0      EV
##    tailnum flight origin dest air_time distance hour min
## 1:  N12175   4419    EWR  XNA      195     1131    8  36
## 2:  N24128   4419    EWR  XNA      190     1131    8  33
## 3:  N12142   4419    EWR  XNA      179     1131    8  11
## 4:  N11193   4419    EWR  XNA      184     1131    8  10
## 5:  N14198   4419    EWR  XNA      181     1131    8  33
## 6:  N12157   4419    EWR  XNA      188     1131    9  23
ans <- flights[, arr_delay]
head(ans)
## [1]  13  13   9 -26   1   0
ans <- flights[, .(arr_delay, dep_delay)]
head(ans)
##    arr_delay dep_delay
## 1:        13        14
## 2:        13        -3
## 3:         9         2
## 4:       -26        -8
## 5:         1         2
## 6:         0         4
ans <- flights[, .(delay_arr = arr_delay, delay_dep = dep_delay)]
head(ans)
##    delay_arr delay_dep
## 1:        13        14
## 2:        13        -3
## 3:         9         2
## 4:       -26        -8
## 5:         1         2
## 6:         0         4
flights[, sum((arr_delay + dep_delay) < 0)]
## [1] 141814
flights[origin == "JFK" & month == 6L,
           .(m_arr = mean(arr_delay), m_dep = mean(dep_delay))]
##       m_arr    m_dep
## 1: 5.839349 9.807884
flights[origin == "JFK" & month == 6L, length(dest)]
## [1] 8422
flights[, .(.N), by = .(origin)]
##    origin     N
## 1:    JFK 81483
## 2:    LGA 84433
## 3:    EWR 87400
flights[carrier == "AA", .N, by = origin]
##    origin     N
## 1:    JFK 11923
## 2:    LGA 11730
## 3:    EWR  2649
flights[carrier == "AA", .N, by = .(origin,dest)]
##     origin dest    N
##  1:    JFK  LAX 3387
##  2:    LGA  PBI  245
##  3:    EWR  LAX   62
##  4:    JFK  MIA 1876
##  5:    JFK  SEA  298
##  6:    EWR  MIA  848
##  7:    JFK  SFO 1312
##  8:    JFK  BOS 1173
##  9:    JFK  ORD  432
## 10:    JFK  IAH    7
## 11:    JFK  AUS  297
## 12:    EWR  DFW 1618
## 13:    LGA  ORD 4366
## 14:    JFK  STT  229
## 15:    JFK  SJU  690
## 16:    LGA  MIA 3334
## 17:    LGA  DFW 3785
## 18:    JFK  LAS  595
## 19:    JFK  MCO  597
## 20:    JFK  EGE   85
## 21:    JFK  DFW  474
## 22:    JFK  SAN  299
## 23:    JFK  DCA  172
## 24:    EWR  PHX  121
##     origin dest    N
flights[carrier == "AA", .N, by = .(origin, dest)][order(origin, -dest)][1:10,]
##     origin dest    N
##  1:    EWR  PHX  121
##  2:    EWR  MIA  848
##  3:    EWR  LAX   62
##  4:    EWR  DFW 1618
##  5:    JFK  STT  229
##  6:    JFK  SJU  690
##  7:    JFK  SFO 1312
##  8:    JFK  SEA  298
##  9:    JFK  SAN  299
## 10:    JFK  ORD  432

주식 데이터를 tidy 개념으로 tidyquant

tidyquantquantmod 등 주식 분석을 주 목적으로 하는 중요 함수를 제공하는 중요한 패키지입니다. tidy data 개념을 활용한 데이터 핸들링, ggplot과 연계된 강한 차트 그리기, 야후를 기본으로 구글 및 각자 독자적인 데이터 소스로 부터 필요한 데이터를 손쉽게 가져오는 기능, 성능 분석 함수들을 제공하고 있습니다.

주가 지수 가져오기

tidyquant야후 파이넨스에서 정보를 가져옵니다. 가져오는 데이터 소스를 바꾸고 싶으면 어떤 곳에서 가져올지 결정할 수 있는데, tq_get_options()는 가능한 후보를 보여줍니다.

if (!require(tidyquant)) install.packages("tidyquant", verbose = F)
## Loading required package: tidyquant
## Loading required package: PerformanceAnalytics
## Warning: package 'PerformanceAnalytics' was built under R version 3.4.1
## Loading required package: xts
## Warning: package 'xts' was built under R version 3.4.1
## Loading required package: zoo
## Warning: package 'zoo' was built under R version 3.4.1
## 
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
## 
## Attaching package: 'xts'
## The following objects are masked from 'package:data.table':
## 
##     first, last
## The following objects are masked from 'package:dplyr':
## 
##     first, last
## 
## Attaching package: 'PerformanceAnalytics'
## The following object is masked from 'package:graphics':
## 
##     legend
## Loading required package: quantmod
## Warning: package 'quantmod' was built under R version 3.4.1
## Loading required package: TTR
## Warning: package 'TTR' was built under R version 3.4.1
## Version 0.4-0 included new data defaults. See ?getSymbols.
## 
## Attaching package: 'tidyquant'
## The following object is masked from 'package:tibble':
## 
##     as_tibble
## The following object is masked from 'package:dplyr':
## 
##     as_tibble
library(tidyquant)
tq_get_options()
##  [1] "stock.prices"       "stock.prices.japan" "financials"        
##  [4] "key.stats"          "key.ratios"         "dividends"         
##  [7] "splits"             "economic.data"      "exchange.rates"    
## [10] "metal.prices"       "quandl"             "quandl.datatable"

이때 코스피와 코스닥을 이르는 이름이 각각 ^KS11^KOSDAQ입니다. 각각 한번 가져와 보겠습니다.

tq_get("^KS11")
## # A tibble: 2,627 x 7
##          date    open    high     low   close volume adjusted
##        <date>   <dbl>   <dbl>   <dbl>   <dbl>  <dbl>    <dbl>
##  1 2007-01-02 1438.89 1439.71 1430.06 1435.26 147800  1435.26
##  2 2007-01-03 1436.42 1437.79 1409.31 1409.35 203200  1409.35
##  3 2007-01-04 1410.55 1411.12 1388.50 1397.29 241200  1397.29
##  4 2007-01-05 1398.60 1400.59 1372.36 1385.76 277200  1385.76
##  5 2007-01-08 1376.76 1384.65 1366.48 1370.81 177600  1370.81
##  6 2007-01-09 1376.71 1381.99 1367.74 1374.34 216800  1374.34
##  7 2007-01-10 1372.52 1372.52 1345.08 1355.79 225400  1355.79
##  8 2007-01-11 1357.57 1375.31 1355.63 1365.31 211800  1365.31
##  9 2007-01-12 1379.00 1389.00 1372.87 1388.37 213800  1388.37
## 10 2007-01-15 1396.87 1397.64 1385.81 1390.96 163800  1390.96
## # ... with 2,617 more rows
tq_get("^KOSDAQ")
## # A tibble: 1 x 7
##         date   open   high   low close volume adjusted
##       <date>  <dbl>  <dbl> <dbl> <dbl>  <dbl>    <dbl>
## 1 2017-07-21 677.84 678.15 674.6 676.6 523728    676.6

각 기업의 주가를 가져오려면 종목 번호를 알고 있어야 합니다. 양식은 종목 번호.KS입니다. 종목번호는 세종기업 데이터에서 가져온 정보를 활용하겠습니다.

url<-"https://github.com/mrchypark/sejongFinData/raw/master/codeData.csv"
download.file(url,destfile = "./codeData.csv")
codeData<-read.csv("./codeData.csv",stringsAsFactors = F)
head(codeData)
##   종목번호     종목명
## 1   005930   삼성전자
## 2   000660 SK하이닉스
## 3   005935 삼성전자우
## 4   005380     현대차
## 5   035420      NAVER
## 6   015760   한국전력

삼성전자를 가져와 볼까요.

tar<-paste0(codeData[grep("^삼성전자$",codeData$`종목명`),1],".KS")
tq_get(tar)
## # A tibble: 2,627 x 7
##          date     open     high      low  close volume adjusted
##        <date>    <dbl>    <dbl>    <dbl>  <dbl>  <dbl>    <dbl>
##  1 2007-01-02 698337.0 706221.5 693831.6 626000 352146 555777.5
##  2 2007-01-03 706221.5 707347.8 688199.9 614000 393530 545123.6
##  3 2007-01-04 688200.0 689326.3 679189.2 606000 365178 538020.9
##  4 2007-01-05 684820.8 685947.1 670178.2 595000 568008 528255.0
##  5 2007-01-08 666799.1 669051.8 652156.6 584000 661631 518489.0
##  6 2007-01-09 661167.6 669052.0 655535.8 586000 402197 520264.5
##  7 2007-01-10 655535.7 657788.4 647651.3 578000 515126 513162.0
##  8 2007-01-11 652156.7 662293.8 651030.3 583000 596787 517601.1
##  9 2007-01-12 664546.6 682568.2 658914.9 606000 806921 538020.9
## 10 2007-01-15 689326.2 692705.2 683694.4 612000 673506 543348.0
## # ... with 2,617 more rows

날짜를 지정할 수도 있습니다.

tq_get(tar, from="2016-01-01", to="2016-05-05")
## # A tibble: 84 x 7
##          date    open    high     low   close volume adjusted
##        <date>   <dbl>   <dbl>   <dbl>   <dbl>  <dbl>    <dbl>
##  1 2016-01-04 1284780 1284780 1228698 1205000 306939  1181759
##  2 2016-01-05 1225639 1241954 1209325 1208000 216002  1184701
##  3 2016-01-06 1231757 1231757 1190971 1175000 366752  1152337
##  4 2016-01-07 1188931 1206266 1173636 1163000 282388  1140569
##  5 2016-01-08 1185872 1209325 1185872 1171000 257763  1148415
##  6 2016-01-11 1178735 1188931 1168538 1152000 241277  1129781
##  7 2016-01-12 1170577 1188931 1166499 1146000 206283  1123897
##  8 2016-01-13 1175676 1181794 1170578 1148000 143316  1125858
##  9 2016-01-14 1153243 1164459 1153243 1138000 209022  1116051
## 10 2016-01-15 1162420 1174656 1146105 1132000 209464  1110167
## # ... with 74 more rows

배당금 정보는 dividends 에서 확인하시면 됩니다.

tq_get(tar, get = "dividends")
## # A tibble: 21 x 2
##          date dividends
##        <date>     <dbl>
##  1 2007-06-28       500
##  2 2007-12-27      7500
##  3 2008-06-27       500
##  4 2008-12-29      5000
##  5 2009-06-29       500
##  6 2009-12-29      7500
##  7 2010-06-29      4230
##  8 2010-12-29      5000
##  9 2011-06-29       500
## 10 2011-12-28      5000
## # ... with 11 more rows

야후 파이넨스가 데이터 소스이다 보니 모든 정보가 있다고 보기 어렵니다. 네이버 파이넨스에서 크롤링 하는 것이 방법이라고 생각합니다.

Quandl

Quandl은 방대한 양의 경제, 주식에 대한 정보를 가지고 서비스하는 데이터 판매 기업입니다. Quandl이라는 자체 패키지만을 사용해도 되고, tidyquant가 내장하고 있어서 같이 사용해도 됩니다.

tidyverse와 함께 사용하는 시계열 데이터

그 동안의 주식관련 패키지들은 파이프 연산자 %>%와 함꼐 사용하지 못했는데, tidyquant는 그런 문제를 해결하였습니다. 아래 2가지 중요한 함수를 추가함으로써 dplyrtidyr의 함수와 함께 사용할 수 있게 되었습니다.

tq_에서 계산 가능한 함수들

tq_transmute_fun_options() 함수는 각 참고 패키지에서 활용할 수 있는 함수의 리스트를 보여줍니다. 모두 zoo, xts, quantmod, TTR, PerformanceAnalytics의 5개 패키지내의 함수를 지원합니다.

tq_transmute_fun_options() %>% str
## List of 5
##  $ zoo                 : chr [1:14] "rollapply" "rollapplyr" "rollmax" "rollmax.default" ...
##  $ xts                 : chr [1:27] "apply.daily" "apply.monthly" "apply.quarterly" "apply.weekly" ...
##  $ quantmod            : chr [1:25] "allReturns" "annualReturn" "ClCl" "dailyReturn" ...
##  $ TTR                 : chr [1:62] "adjRatios" "ADX" "ALMA" "aroon" ...
##  $ PerformanceAnalytics: chr [1:7] "Return.annualized" "Return.annualized.excess" "Return.clean" "Return.cumulative" ...

zoo 함수

tq_transmute_fun_options()$zoo
##  [1] "rollapply"          "rollapplyr"         "rollmax"           
##  [4] "rollmax.default"    "rollmaxr"           "rollmean"          
##  [7] "rollmean.default"   "rollmeanr"          "rollmedian"        
## [10] "rollmedian.default" "rollmedianr"        "rollsum"           
## [13] "rollsum.default"    "rollsumr"

xts 함수

tq_transmute_fun_options()$xts
##  [1] "apply.daily"     "apply.monthly"   "apply.quarterly"
##  [4] "apply.weekly"    "apply.yearly"    "diff.xts"       
##  [7] "lag.xts"         "period.apply"    "period.max"     
## [10] "period.min"      "period.prod"     "period.sum"     
## [13] "periodicity"     "to.daily"        "to.hourly"      
## [16] "to.minutes"      "to.minutes10"    "to.minutes15"   
## [19] "to.minutes3"     "to.minutes30"    "to.minutes5"    
## [22] "to.monthly"      "to.period"       "to.quarterly"   
## [25] "to.weekly"       "to.yearly"       "to_period"

quantmod 함수

tq_transmute_fun_options()$quantmod
##  [1] "allReturns"      "annualReturn"    "ClCl"           
##  [4] "dailyReturn"     "Delt"            "HiCl"           
##  [7] "Lag"             "LoCl"            "LoHi"           
## [10] "monthlyReturn"   "Next"            "OpCl"           
## [13] "OpHi"            "OpLo"            "OpOp"           
## [16] "periodReturn"    "quarterlyReturn" "seriesAccel"    
## [19] "seriesDecel"     "seriesDecr"      "seriesHi"       
## [22] "seriesIncr"      "seriesLo"        "weeklyReturn"   
## [25] "yearlyReturn"

TTR 함수

tq_transmute_fun_options()$TTR
##  [1] "adjRatios"          "ADX"                "ALMA"              
##  [4] "aroon"              "ATR"                "BBands"            
##  [7] "CCI"                "chaikinAD"          "chaikinVolatility" 
## [10] "CLV"                "CMF"                "CMO"               
## [13] "DEMA"               "DonchianChannel"    "DPO"               
## [16] "DVI"                "EMA"                "EMV"               
## [19] "EVWMA"              "GMMA"               "growth"            
## [22] "HMA"                "KST"                "lags"              
## [25] "MACD"               "MFI"                "momentum"          
## [28] "OBV"                "PBands"             "ROC"               
## [31] "rollSFM"            "RSI"                "runCor"            
## [34] "runCov"             "runMAD"             "runMax"            
## [37] "runMean"            "runMedian"          "runMin"            
## [40] "runPercentRank"     "runSD"              "runSum"            
## [43] "runVar"             "SAR"                "SMA"               
## [46] "SMI"                "SNR"                "stoch"             
## [49] "TDI"                "TRIX"               "ultimateOscillator"
## [52] "VHF"                "VMA"                "volatility"        
## [55] "VWAP"               "VWMA"               "wilderSum"         
## [58] "williamsAD"         "WMA"                "WPR"               
## [61] "ZigZag"             "ZLEMA"

PerformanceAnalytics 함수

tq_transmute_fun_options()$PerformanceAnalytics
## [1] "Return.annualized"        "Return.annualized.excess"
## [3] "Return.clean"             "Return.cumulative"       
## [5] "Return.excess"            "Return.Geltner"          
## [7] "zerofill"

Return.annualizedReturn.annualized.excess : 기간 반환을 취하여 연간 수익으로 통합합니다. Return.clean : 반환 값에서 특이 값을 제거합니다. Return.excess : 무위험 이자율을 초과하는 수익률로 수익률에서 무위험 이자율을 제거합니다. zerofill : ’NA’값을 0으로 대체하는 데 사용됩니다.

ggplot2와 연계된 차트 그리기

ggplot2 차트를 그리는데 R에서 가장 유명한 패키지 입니다. ggGrammar of Graphics의 줄임말로 그림을 생성하는 것에 대한 규칙을 제안하고 있습니다. tidyquantggplot2에 더해 아래와 같은 기능을 추가로 제공합니다.

살펴보기

tq_get를 이용해서 사용할 데이터를 가져옵니다. 내장 데이터인 FANG과 애플, 아마존을 예시로 사용하겠습니다.

data("FANG") 

AAPL <- tq_get("AAPL", get = "stock.prices", from = "2015-09-01", to = "2016-12-31")
AMZN <- tq_get("AMZN", get = "stock.prices", from = "2000-01-01", to = "2016-12-31")

’end` 매개 변수는 예제 전체에서 날짜 제한을 설정할 때 사용됩니다.

end <- as_date("2016-12-31")

차트 종류

라인 차트

tidyquantgeom_함수를 사용하여 가로 막대형 차트와 촛대형 차트를 시각화하기 전에 단순한 선 차트로 주가를 시각화하여 그래픽 문법을 확인해보겠습니다. 이것은ggplot2 패키지의geom_line을 사용하여 이루어집니다. 주식 데이터로 시작하고 파이프 연산자 (%> %)를 사용하여ggplot ()함수로 보냅니다.

AAPL %>%
    ggplot(aes(x = date, y = close)) +
    geom_line() +
    labs(title = "AAPL Line Chart", y = "Closing Price", x = "") + 
    theme_tq()

바 차트

바 차트는 geom_linegeom_barchart로 바꾸는 걸로 해결됩니다. aes()내의 내용을 의미에 맞게 조정하는 것으로 바 차트를 그리는 것이 끝납니다.

AAPL %>%
    ggplot(aes(x = date, y = close)) +
    geom_barchart(aes(open = open, high = high, low = low, close = close)) +
    labs(title = "AAPL Bar Chart", y = "Closing Price", x = "") + 
    theme_tq()

우리는coord_x_date를 사용하여 특정 섹션을 확대 / 축소합니다.이 섹션에는xlimylim 인수가c (start, end)로 지정되어 차트의 특정 영역에 초점을 맞 춥니 다. xlim의 경우 우리는lubridate를 사용하여 문자 날짜를 날짜 클래스로 변환 한 다음weeks ()함수를 사용하여 6 주를 뺍니다. ylim의 경우 가격을 100에서 120까지 확대합니다.

AAPL %>%
    ggplot(aes(x = date, y = close)) +
    geom_barchart(aes(open = open, high = high, low = low, close = close)) +
    labs(title = "AAPL Bar Chart", 
         subtitle = "Zoomed in using coord_x_date",
         y = "Closing Price", x = "") + 
    coord_x_date(xlim = c(end - weeks(6), end),
                 ylim = c(100, 120)) + 
    theme_tq()

색상은color_upcolor_down 인수를 사용하여 수정할 수 있으며size와 같은 매개 변수를 사용하여 모양을 제어 할 수 있습니다.

AAPL %>%
    ggplot(aes(x = date, y = close)) +
    geom_barchart(aes(open = open, high = high, low = low, close = close),
                     color_up = "darkgreen", color_down = "darkred", size = 1) +
    labs(title = "AAPL Bar Chart", 
         subtitle = "Zoomed in, Experimenting with Formatting",
         y = "Closing Price", x = "") + 
    coord_x_date(xlim = c(end - weeks(6), end),
                 ylim = c(100, 120)) + 
    theme_tq()

캔들 차트

캔들 차트 또한 바 차트를 그리는 것과 거의 같습니다.

AAPL %>%
    ggplot(aes(x = date, y = close)) +
    geom_candlestick(aes(open = open, high = high, low = low, close = close)) +
    labs(title = "AAPL Candlestick Chart", y = "Closing Price", x = "") +
    theme_tq()

색상은color_upcolor_down을 사용하여 선 색상을 조절할 수 있고, fill_upfill_down은 사각형을 채 웁니다.

AAPL %>%
    ggplot(aes(x = date, y = close)) +
    geom_candlestick(aes(open = open, high = high, low = low, close = close),
                        color_up = "darkgreen", color_down = "darkred", 
                        fill_up  = "darkgreen", fill_down  = "darkred") +
    labs(title = "AAPL Candlestick Chart", 
         subtitle = "Zoomed in, Experimenting with Formatting",
         y = "Closing Price", x = "") + 
    coord_x_date(xlim = c(end - weeks(6), end),
                 ylim = c(100, 120)) + 
    theme_tq()

여러개의 차트를 그리기

facet_wrap을 사용하여 동시에 여러 주식을 시각화 할 수 있습니다. ggplot ()aes()group을 추가하고ggplot 워크 플로우의 끝에서facet_wrap()함수와 결합함으로써 네 개의 “FANG”주식을 동시에 모두 볼 수 있습니다.

start <- end - weeks(6)
FANG %>%
    filter(date >= start - days(2 * 15)) %>%
    ggplot(aes(x = date, y = close, group = symbol)) +
    geom_candlestick(aes(open = open, high = high, low = low, close = close)) +
    labs(title = "FANG Candlestick Chart", 
         subtitle = "Experimenting with Mulitple Stocks",
         y = "Closing Price", x = "") + 
    coord_x_date(xlim = c(start, end)) +
    facet_wrap(~ symbol, ncol = 2, scale = "free_y") + 
    theme_tq()

트랜드 시각화

Moving averages are critical to evaluating time-series trends. tidyquant includes geoms to enable “rapid prototyping” to quickly visualize signals using moving averages and Bollinger bands.

이동 평균

tidyquant에서는 다양한 이동평균 함수를 제공합니다.

이동 평균은geom_ma 함수로 차트에 추가 된 레이어로 적용됩니다. 기하 구조는TTR 패키지에서SMA,EMA,WMA,DEMA,ZLEMA,VWMA,EVWMA와 같은 기본 이동 평균 함수의 래퍼입니다.

Example 1: 50일/200일 단순 이동 평균 차트 작성

AAPL %>%
    ggplot(aes(x = date, y = close)) +
    geom_candlestick(aes(open = open, high = high, low = low, close = close)) +
    geom_ma(ma_fun = SMA, n = 50, linetype = 5, size = 1.25) +
    geom_ma(ma_fun = SMA, n = 200, color = "red", size = 1.25) + 
    labs(title = "AAPL Candlestick Chart", 
         subtitle = "50 and 200-Day SMA", 
         y = "Closing Price", x = "") + 
    coord_x_date(xlim = c(end - weeks(24), end),
                 ylim = c(100, 120)) + 
    theme_tq()

Example 2: 지수 이동 평균 차트

AAPL %>%
    ggplot(aes(x = date, y = close)) +
    geom_barchart(aes(open = open, high = high, low = low, close = close)) +
    geom_ma(ma_fun = EMA, n = 50, wilder = TRUE, linetype = 5, size = 1.25) +
    geom_ma(ma_fun = EMA, n = 200, wilder = TRUE, color = "red", size = 1.25) + 
    labs(title = "AAPL Bar Chart", 
         subtitle = "50 and 200-Day EMA", 
         y = "Closing Price", x = "") + 
    coord_x_date(xlim = c(end - weeks(24), end),
                 ylim = c(100, 120)) +
    theme_tq()

볼린저 밴드

[Bollinger Bands] https://en.wikipedia.org/wiki/Bollinger_Bands)는 이동 평균(일반적으로 상하 2SD) 주위의 범위를 플로팅하여 변동성을 시각화하는 데 사용됩니다. 그것들은 이동 평균을 사용하기 때문에,geom_bbands 함수는geom_ma와 거의 동일하게 작동합니다. 동일한 7 개의 이동 평균이 호환됩니다. 가장 큰 차이점은 기본적으로 2 인 표준 편차 인sd 인수와 밴드를 계산하는 데 필요한 ‘high’, ’low’및 ’close’를 aes()에 추가하는 것입니다.

Example 1: SMA를 사용하여 BBands 적용

간단한 이동 평균을 사용하여 Bollinger Bands를 추가하는 기본 예제를 살펴 보겠습니다.

AAPL %>%
    ggplot(aes(x = date, y = close, open = open,
               high = high, low = low, close = close)) +
    geom_candlestick() +
    geom_bbands(ma_fun = SMA, sd = 2, n = 20) +
    labs(title = "AAPL Candlestick Chart", 
         subtitle = "BBands with SMA Applied", 
         y = "Closing Price", x = "") + 
    coord_x_date(xlim = c(end - weeks(24), end),
                 ylim = c(100, 120)) + 
    theme_tq()

Example 2: Bollinger Bands의 모양 바꾸기

모양은color_ma,color_bands,alpha,fill 인자를 사용하여 수정할 수 있습니다. BBands에 새로운 서식을 적용한 Example 1과 같은 그림이 있습니다.

AAPL %>%
    ggplot(aes(x = date, y = close, open = open,
               high = high, low = low, close = close)) +
    geom_candlestick() +
    geom_bbands(ma_fun = SMA, sd = 2, n = 20, 
                linetype = 4, size = 1, alpha = 0.2, 
                fill        = palette_light()[[1]], 
                color_bands = palette_light()[[1]], 
                color_ma    = palette_light()[[2]]) +
    labs(title = "AAPL Candlestick Chart", 
         subtitle = "BBands with SMA Applied, Experimenting with Formatting", 
         y = "Closing Price", x = "") + 
    coord_x_date(xlim = c(end - weeks(24), end),
                 ylim = c(100, 120)) + 
    theme_tq()

Example 3: 여러 주식에 BBands 추가

start <- end - weeks(24)
FANG %>%
    filter(date >= start - days(2 * 20)) %>%
    ggplot(aes(x = date, y = close, 
               open = open, high = high, low = low, close = close, 
               group = symbol)) +
    geom_barchart() +
    geom_bbands(ma_fun = SMA, sd = 2, n = 20, linetype = 5) +
    labs(title = "FANG Bar Chart", 
         subtitle = "BBands with SMA Applied, Experimenting with Multiple Stocks", 
         y = "Closing Price", x = "") + 
    coord_x_date(xlim = c(start, end)) +
    facet_wrap(~ symbol, ncol = 2, scales = "free_y") + 
    theme_tq()

ggplot2 함수

기본 ggplot2는 재무 데이터를 분석하는데 유용한 많은 기능을 가지고 있습니다. 아마존(AMZN)을 사용하여 몇 가지 간단한 예제를 살펴 보겠습니다.

Example 1 : scale_y_log10을 사용한 로그 스케일

ggplot2는 y 축을 로그 스케일로 스케일하기위한scale_y_log10 ()함수를 가지고 있습니다. 이는 분석 할 수있는 선형 추세를 조정하는 경향이 있으므로 매우 유용합니다.

Continuous Scale:

AMZN %>%
    ggplot(aes(x = date, y = adjusted)) +
    geom_line(color = palette_light()[[1]]) + 
    scale_y_continuous() +
    labs(title = "AMZN Line Chart", 
         subtitle = "Continuous Scale", 
         y = "Closing Price", x = "") + 
    theme_tq()

Log Scale:

AMZN %>%
    ggplot(aes(x = date, y = adjusted)) +
    geom_line(color = palette_light()[[1]]) + 
    scale_y_log10() +
    labs(title = "AMZN Line Chart", 
         subtitle = "Log Scale", 
         y = "Closing Price", x = "") + 
    theme_tq()

Example 2: geom_smooth로 회귀 추세선

우리는 워크 플로우에geom_smooth ()함수를 빠르게 추가하는 추세선을 적용 할 수 있습니다. 이 함수는 선형(lm)과 loess(loess) 를 포함한 몇 가지 예측 방법을 가지고 있습니다.

Linear:

AMZN %>%
    ggplot(aes(x = date, y = adjusted)) +
    geom_line(color = palette_light()[[1]]) + 
    scale_y_log10() +
    geom_smooth(method = "lm") +
    labs(title = "AMZN Line Chart", 
         subtitle = "Log Scale, Applying Linear Trendline", 
         y = "Adjusted Closing Price", x = "") + 
    theme_tq()

Example 3: geom_segment로 차트 볼륨

우리는geom_segment ()함수를 사용하여 라인의 시작과 끝을 xy 점으로하는 일일 볼륨을 차트로 표시 할 수 있습니다. aes()를 사용하여 볼륨의 값을 기준으로 색상을 지정하여 이러한 데이터를 강조 표시합니다.

AMZN %>%
    ggplot(aes(x = date, y = volume)) +
    geom_segment(aes(xend = date, yend = 0, color = volume)) + 
    geom_smooth(method = "loess", se = FALSE) +
    labs(title = "AMZN Volume Chart", 
         subtitle = "Charting Daily Volume", 
         y = "Volume", x = "") +
    theme_tq() +
    theme(legend.position = "none") 

특정 지역을 확대 할 수 있습니다. scale_color_gradient를 사용하여 고점 및 저점을 빠르게 시각화 할 수 있으며geom_smooth를 사용하여 추세를 볼 수 있습니다.

start <- end - weeks(24)
AMZN %>%
    filter(date >= start - days(50)) %>%
    ggplot(aes(x = date, y = volume)) +
    geom_segment(aes(xend = date, yend = 0, color = volume)) +
    geom_smooth(method = "loess", se = FALSE) +
    labs(title = "AMZN Bar Chart", 
         subtitle = "Charting Daily Volume, Zooming In", 
         y = "Volume", x = "") + 
    coord_x_date(xlim = c(start, end)) +
    scale_color_gradient(low = "red", high = "darkblue") +
    theme_tq() + 
    theme(legend.position = "none") 

테마

tidyquant 패키지는 3 가지 테마로 구성되어있어 신속하게 재무 차트를 조정 할 수 있습니다.

Dark

n_mavg <- 50 # Number of periods (days) for moving average
FANG %>%
    filter(date >= start - days(2 * n_mavg)) %>%
    ggplot(aes(x = date, y = close, color = symbol)) +
    geom_line(size = 1) +
    geom_ma(n = 15, color = "darkblue", size = 1) + 
    geom_ma(n = n_mavg, color = "red", size = 1) +
    labs(title = "Dark Theme",
         x = "", y = "Closing Price") +
    coord_x_date(xlim = c(start, end)) +
    facet_wrap(~ symbol, scales = "free_y") +
    theme_tq_dark() +
    scale_color_tq(theme = "dark") +
    scale_y_continuous(labels = scales::dollar)

웹 크롤링 기초 httr과 rvest

서버와 클라이언트

인터넷은 각각의 컴퓨터를 통신으로 연결하여 정보를 주고 받는 것입니다. 전화번호와 같은 IP라는 것이 있고, 번호를 외우는게 어려워서 주소라는 것을 만들어 관리합니다. 8.8.8.8IP이고, www.google.com주소입니다. 뒤가 더 기억하기 쉽겠죠?

컴퓨터를 연결해서 자기 것처럼 사용하는 것을 원격 연결이라고 합니다. 그와 달리 인터넷의 웹페이지는 모두 제한된 텍스트 문서나 그림 같은 파일들만 확인할 있도록 구성되어 있습니다. 저런 문서와 그림을 가지고 있다가 약속된 요청을 하면 전송해주는 역할을 하것 것을 서버라고 하고, 문서와 그림을 규칙에 맞게 요청하고, 정해진대로 보여주는 역할을 하는 것을 클라이언트(브라우저)라고 합니다.

http/s 1.1

http/s는 이렇게 서버에 파일을 요청하는 방식을 미리 약속해 둔 것입니다. 요청의 종류는 다양하지만 크롤링에서 사용할 종류는 GETPOST입니다. GET은 지정된 주소에 저장되어 있는 파일을 달라고 요청하는 것이고, POST는 내가 가지고 있는 데이터가 있는데 이걸 잘 바꿔서 결과를 돌려주세요 라고 하는 것입니다. 언어에서 함수와 비슷합니다.

위에 요청이라는 표현을 사용했는데, requestresponse라는 개념이 있습니다. http 통신은 모두 요청을 해서 응답을 받는 구조 입니다. GET은 정상이라고 하는 코드와 몇몇 파일들, 이외에 쿠키, 해더 등을 응답으로 받습니다. POSTGET과 같고, 계산 결과를 추가적으로 응답으로 보내기도 합니다.

위와 같은 요청을 하는 방식을 그대로 따라해서 정보를 받아오는 패키지가 curl, httr입니다. 이번에는 다루기 쉬운 httr을 사용해 보겠습니다.

httr 패키지 소개

요청

httr 패키지는 GET요청을 할 있는 GET()함수가 있습니다.

library(httr)
## Warning: package 'httr' was built under R version 3.4.1
r <- GET("http://httpbin.org/get")

위에 요청으로 r이라는 응답 객체가 생깁니다. 응답 객체를 확인하면 사용된 실제 URL (모든 리디렉션 이후), http 상태, 파일 (내용) 유형, 크기 및 텍스트 파일 인 경우 처음 몇 줄의 출력과 같은 유용한 정보가 제공됩니다.

r
## Response [http://httpbin.org/get]
##   Date: 2017-07-22 05:27
##   Status: 200
##   Content-Type: application/json
##   Size: 326 B
## {
##   "args": {}, 
##   "headers": {
##     "Accept": "application/json, text/xml, application/xml, */*", 
##     "Accept-Encoding": "gzip, deflate", 
##     "Connection": "close", 
##     "Host": "httpbin.org", 
##     "User-Agent": "libcurl/7.53.1 r-curl/2.6 httr/1.2.1"
##   }, 
##   "origin": "221.148.180.35", 
## ...

다양한 헬퍼 메소드를 사용하여 응답의 중요한 부분을 추출하거나 객체로 직접 파고들 수 있습니다.

status_code(r)
## [1] 200
headers(r)
## $connection
## [1] "keep-alive"
## 
## $server
## [1] "meinheld/0.6.1"
## 
## $date
## [1] "Sat, 22 Jul 2017 05:27:49 GMT"
## 
## $`content-type`
## [1] "application/json"
## 
## $`access-control-allow-origin`
## [1] "*"
## 
## $`access-control-allow-credentials`
## [1] "true"
## 
## $`x-powered-by`
## [1] "Flask"
## 
## $`x-processed-time`
## [1] "0.000625848770142"
## 
## $`content-length`
## [1] "326"
## 
## $via
## [1] "1.1 vegur"
## 
## attr(,"class")
## [1] "insensitive" "list"
str(content(r))
## List of 4
##  $ args   : Named list()
##  $ headers:List of 5
##   ..$ Accept         : chr "application/json, text/xml, application/xml, */*"
##   ..$ Accept-Encoding: chr "gzip, deflate"
##   ..$ Connection     : chr "close"
##   ..$ Host           : chr "httpbin.org"
##   ..$ User-Agent     : chr "libcurl/7.53.1 r-curl/2.6 httr/1.2.1"
##  $ origin : chr "221.148.180.35"
##  $ url    : chr "http://httpbin.org/get"

이 소개에서`httpbin.org ’를 사용할 것이다. 그것은 많은 종류의 http 요청을 받아 들여 그것이 수신 한 데이터를 설명하는 json을 반환합니다. 이렇게하면 httr이하는 일을 쉽게 볼 수 있습니다.

응답

서버에서 보낸 데이터는 상태 표시 줄, 헤더 및 본문의 세 부분으로 구성됩니다. 상태 표시 줄의 가장 중요한 부분은 http 상태 코드입니다. 요청이 성공했는지 여부를 알려줍니다. 해당 데이터에 액세스하는 방법, 본문 및 헤더에 액세스하는 방법을 보여 드리겠습니다.

상태코드

상태 코드는 요청이 성공했는지 여부를 요약하는 3 자리 숫자입니다 (대화중인 서버에서 정의한대로). http_status ()를 사용하여 설명적인 메시지와 함께 상태 코드에 접근 할 수 있습니다.

r <- GET("http://httpbin.org/get")
http_status(r)
## $category
## [1] "Success"
## 
## $reason
## [1] "OK"
## 
## $message
## [1] "Success: (200) OK"
r$status_code
## [1] 200

요청이 성공하면 항상 200을 반환합니다. 일반적인 오류는 404(파일을 찾을 수 없음) 및 403 (사용 권한을 거부함)입니다. 웹 API와 대화하는 경우 일반 오류 코드 인 500이 표시 될 수 있습니다. http status cats로 추가적인 내용을 확인하세요.

You can automatically throw a warning or raise an error if a request did not succeed.

warn_for_status(r)
stop_for_status(r)

body

r을 만들고 나면 객체로써 내용에 접근하는 방법이 필요합니다. content()는 응답 객체에서 body부분만 보여주는 함수로 옵션으로 text, raw, parsed가 있습니다.

content(r, "text") 는 내용을 모두 텍스트로 인지하고 보여줍니다.

r <- GET("http://httpbin.org/get")
content(r, "text")
## No encoding supplied: defaulting to UTF-8.
## [1] "{\n  \"args\": {}, \n  \"headers\": {\n    \"Accept\": \"application/json, text/xml, application/xml, */*\", \n    \"Accept-Encoding\": \"gzip, deflate\", \n    \"Connection\": \"close\", \n    \"Host\": \"httpbin.org\", \n    \"User-Agent\": \"libcurl/7.53.1 r-curl/2.6 httr/1.2.1\"\n  }, \n  \"origin\": \"221.148.180.35\", \n  \"url\": \"http://httpbin.org/get\"\n}\n"

httr은 인코딩을 사용하여 서버의 콘텐츠를 자동으로 해독합니다. content-type라는 정보가 http 헤더에 제공됩니다. 불행히도 항상 그런 것은 아닙니다.

content(r, "text", encoding = "euc-kr")

올바른 인코딩이 무엇인지 알아내는 데 문제가있는 경우 stringi :: stri_enc_detect (content (r, "raw"))를 사용해볼 수 있습니다.

비 텍스트 요청의 경우(대부분 이미지) 원시 벡터로 요청 본문에 액세스 할 수 있습니다.

content(r, "raw")
##   [1] 7b 0a 20 20 22 61 72 67 73 22 3a 20 7b 7d 2c 20 0a 20 20 22 68 65 61
##  [24] 64 65 72 73 22 3a 20 7b 0a 20 20 20 20 22 41 63 63 65 70 74 22 3a 20
##  [47] 22 61 70 70 6c 69 63 61 74 69 6f 6e 2f 6a 73 6f 6e 2c 20 74 65 78 74
##  [70] 2f 78 6d 6c 2c 20 61 70 70 6c 69 63 61 74 69 6f 6e 2f 78 6d 6c 2c 20
##  [93] 2a 2f 2a 22 2c 20 0a 20 20 20 20 22 41 63 63 65 70 74 2d 45 6e 63 6f
## [116] 64 69 6e 67 22 3a 20 22 67 7a 69 70 2c 20 64 65 66 6c 61 74 65 22 2c
## [139] 20 0a 20 20 20 20 22 43 6f 6e 6e 65 63 74 69 6f 6e 22 3a 20 22 63 6c
## [162] 6f 73 65 22 2c 20 0a 20 20 20 20 22 48 6f 73 74 22 3a 20 22 68 74 74
## [185] 70 62 69 6e 2e 6f 72 67 22 2c 20 0a 20 20 20 20 22 55 73 65 72 2d 41
## [208] 67 65 6e 74 22 3a 20 22 6c 69 62 63 75 72 6c 2f 37 2e 35 33 2e 31 20
## [231] 72 2d 63 75 72 6c 2f 32 2e 36 20 68 74 74 72 2f 31 2e 32 2e 31 22 0a
## [254] 20 20 7d 2c 20 0a 20 20 22 6f 72 69 67 69 6e 22 3a 20 22 32 32 31 2e
## [277] 31 34 38 2e 31 38 30 2e 33 35 22 2c 20 0a 20 20 22 75 72 6c 22 3a 20
## [300] 22 68 74 74 70 3a 2f 2f 68 74 74 70 62 69 6e 2e 6f 72 67 2f 67 65 74
## [323] 22 0a 7d 0a

이것은 정확히 웹 서버가 보낸 바이트 순서이므로 그대로 저장하면 서버에서 받은 것과 정확히 같습니다.

bin <- content(r, "raw")
writeBin(bin, "myfile.txt")

httr은 일반적인 파일 형식에 대한 여러 가지 기본 파서를 제공합니다.

str(content(r, "parsed"))
## List of 4
##  $ args   : Named list()
##  $ headers:List of 5
##   ..$ Accept         : chr "application/json, text/xml, application/xml, */*"
##   ..$ Accept-Encoding: chr "gzip, deflate"
##   ..$ Connection     : chr "close"
##   ..$ Host           : chr "httpbin.org"
##   ..$ User-Agent     : chr "libcurl/7.53.1 r-curl/2.6 httr/1.2.1"
##  $ origin : chr "221.148.180.35"
##  $ url    : chr "http://httpbin.org/get"

rvest 패키지 소개

rvesthtml문서에서 필요한 정보만 가져오기 위해 설계되었습니다. html 문서는 각 node의 성격을 정의하는 tagid, class를 가지고 있습니다. 이 세 가지로 정보가 필요한 node를 특정할 수 있고, rvest는 특정된 nodes의 정보를 가져올 다양한 방법을 제공합니다.

chrome 개발자 도구

chrome은 구글이 만든 브라우저로 웹 페이지의 구조를 분석하는데 매우 유용한 기능들을 제공합니다. windows에서는 F12로 개발자 도구를 사용할 수 있습니다. macoption(alt) + cmd + i입니다.

여러 기능 중에 필요한 것은 ElementsNetwork입니다.

웹 페이지에서 node의 정보가 필요한 요소를 마우스 우클릭을 하고 검사를 확인해보면 Elements 창이 열리면서 요소에 해당하는 html문서를 하이라이트해서 보여줍니다. 그곳 nodetag, id, class를 확인하여 rvest로 특정 부분만 가져와 보겠습니다.

imdb에서 배우 정보 가져오기

imdb는 해외 영화정보 사이트로 html로 구성되어 있어 크롤링 연습에 많이 사용하는 대표적인 곳입니다.

rvest를 사용해서 설국열차에 참석한 사람들의 정보를 가져와 보겠습니다.

library(rvest)
## Loading required package: xml2
## 
## Attaching package: 'rvest'
## The following object is masked from 'package:readr':
## 
##     guess_encoding
html <- read_html("http://www.imdb.com/title/tt4481854/")
cast <- html_nodes(html, "#titleCast .itemprop")
html_text(cast)
##  [1] "\n Fershid Bharucha\n          "    
##  [2] "Fershid Bharucha"                   
##  [3] "\n Joon-ho Bong\n          "        
##  [4] "Joon-ho Bong"                       
##  [5] "\n Couetsch Bousset-Lob\n          "
##  [6] "Couetsch Bousset-Lob"               
##  [7] "\n Benjamin Legrand\n          "    
##  [8] "Benjamin Legrand"                   
##  [9] "\n Jacques Lob\n          "         
## [10] "Jacques Lob"                        
## [11] "\n Clark Middleton\n          "     
## [12] "Clark Middleton"                    
## [13] "\n Ondrej Nekvasil\n          "     
## [14] "Ondrej Nekvasil"                    
## [15] "\n Chan-wook Park\n          "      
## [16] "Chan-wook Park"                     
## [17] "\n Jean-Marc Rochette\n          "  
## [18] "Jean-Marc Rochette"                 
## [19] "\n Albert Spano\n          "        
## [20] "Albert Spano"                       
## [21] "\n Tilda Swinton\n          "       
## [22] "Tilda Swinton"

tagid, class를 세세하게 지정하는 것이 자료를 처리하기 쉽게 가져오는 요령입니다.

cast <- html_nodes(html, "#titleCast span.itemprop")
length(cast)
## [1] 11
html_text(cast)
##  [1] "Fershid Bharucha"     "Joon-ho Bong"         "Couetsch Bousset-Lob"
##  [4] "Benjamin Legrand"     "Jacques Lob"          "Clark Middleton"     
##  [7] "Ondrej Nekvasil"      "Chan-wook Park"       "Jean-Marc Rochette"  
## [10] "Albert Spano"         "Tilda Swinton"

tidyverse의 파이프 연산자를 사용해서 가져오는 것이 더 가독성과 재사용성이 좋습니다.

url <- "http://www.imdb.com/title/tt1490017/"

read_html(url) %>% 
  html_nodes("#titleCast span.itemprop") %>%
  html_text
##  [1] "Will Arnett"     "Elizabeth Banks" "Craig Berry"    
##  [4] "Alison Brie"     "David Burrows"   "Anthony Daniels"
##  [7] "Charlie Day"     "Amanda Farinos"  "Keith Ferguson" 
## [10] "Will Ferrell"    "Will Forte"      "Dave Franco"    
## [13] "Morgan Freeman"  "Todd Hansen"     "Jonah Hill"

형태소 분석기 KoNLP

한글 형태소 분석기인 KoNLP한나눔 형태소 분석기를 R에서 사용할 수 있게 작성한 패키지로 R에서 형태소 분석을 할 때 거의 유일한 선택지입니다. 덕분에 명사 추출등을 이용한 워드클라우드 샘플 코드가 공개되면서 많은 사람들이 다양한 워드 클라우드를 만들 수 있게 되었습니다. R에서는 wordcloud와 다양한 기능이 개선된 wordcloud2를 사용해서 쉽게 워드클라우드를 만들수 있습니다.

함수 설명

library(KoNLP)
## Warning: package 'KoNLP' was built under R version 3.4.1
## Checking user defined dictionary!
useSejongDic()
## Backup was just finished!
## 370957 words dictionary was built.

명사를 추출합니다.

extractNoun("롯데마트가 판매하고 있는 흑마늘 양념 치킨이 논란이 되고 있다.")
## [1] "롯데마트" "판매"     "흑마늘"   "양념"     "치킨"     "논란"

벡터를 입력으로 받을 수 있습니다. sapply 같은 함수를 함께 사용하지 않아도 됩니다.

extractNoun(c("R은 free 소프트웨어이고, [완전하게 무보증]입니다.", 
                  "일정한 조건에 따르면, 자유롭게 이것을 재배포할수가 있습니다.")
                )
## [[1]]
## [1] "R"          "free"       "소프트웨어" "완전"       "하게"      
## [6] "무보"       "증"        
## 
## [[2]]
## [1] "일정"         "한"           "조건"         "자유"        
## [5] "이것"         "재배포할수가"

형태소 태깅을 2단계 수준으로 조정해서 할 수 있습니다.

SimplePos09("롯데마트가 판매하고 있는 흑마늘 양념 치킨이 논란이 되고 있다.")
## $롯데마트가
## [1] "롯데마트/N+가/J"
## 
## $판매하고
## [1] "판매/N+하고/J"
## 
## $있는
## [1] "있/P+는/E"
## 
## $흑마늘
## [1] "흑마늘/N"
## 
## $양념
## [1] "양념/N"
## 
## $치킨이
## [1] "치킨/N+이/J"
## 
## $논란이
## [1] "논란/N+이/J"
## 
## $되고
## [1] "되/P+고/E"
## 
## $있다
## [1] "있/P+다/E"
## 
## $.
## [1] "./S"
SimplePos22("롯데마트가 판매하고 있는 흑마늘 양념 치킨이 논란이 되고 있다.")
## $롯데마트가
## [1] "롯데마트/NC+가/JC"
## 
## $판매하고
## [1] "판매/NC+하고/JC"
## 
## $있는
## [1] "있/PX+는/ET"
## 
## $흑마늘
## [1] "흑마늘/NC"
## 
## $양념
## [1] "양념/NC"
## 
## $치킨이
## [1] "치킨/NC+이/JC"
## 
## $논란이
## [1] "논란/NC+이/JC"
## 
## $되고
## [1] "되/PV+고/EC"
## 
## $있다
## [1] "있/PX+다/EF"
## 
## $.
## [1] "./SF"

품사 태그(KAIST 방식)

몇 가지 방식있는데, KoNLPKAIST 방식을 사용합니다. 엔진을 한나눔을 사용함으로써 종속적이 된 것으로 보입니다.

가능성 있는 모든 결과를 보여줍니다.

MorphAnalyzer("롯데마트가 판매하고 있는 흑마늘 양념 치킨이 논란이 되고 있다.")
## $롯데마트가
## [1] "롯데마트/ncn+가/jcc" "롯데마트/ncn+가/jcs"
## 
## $판매하고
## [1] "판매/ncpa+하고/jcj"             "판매/ncpa+하고/jct"            
## [3] "판매/ncpa+하/xsva+고/ecc"       "판매/ncpa+하/xsva+고/ecs"      
## [5] "판매/ncpa+하/xsva+고/ecx"       "판매/ncpa+하/xsva+어/ef+고/jcr"
## 
## $있는
## [1] "있/paa+는/etm" "있/px+는/etm" 
## 
## $흑마늘
## [1] "흑마늘/ncn" "흑마늘/nqq"
## 
## $양념
## [1] "양념/ncn"
## 
## $치킨이
## [1] "치킨/ncn+이/jcc" "치킨/ncn+이/jcs" "치킨/ncn+이/ncn"
## 
## $논란이
## [1] "논란/ncpa+이/jcc" "논란/ncpa+이/jcs" "논란/ncpa+이/ncn"
## 
## $되고
##  [1] "되/nbu+고/jcj"       "되/nbu+이/jp+고/ecc" "되/nbu+이/jp+고/ecs"
##  [4] "되/nbu+이/jp+고/ecx" "되/paa+고/ecc"       "되/paa+고/ecs"      
##  [7] "되/paa+고/ecx"       "되/pvg+고/ecc"       "되/pvg+고/ecs"      
## [10] "되/pvg+고/ecx"       "되/px+고/ecc"        "되/px+고/ecs"       
## [13] "되/px+고/ecx"       
## 
## $있다
## [1] "있/paa+다/ef" "있/px+다/ef" 
## 
## $.
## [1] "./sf" "./sy"

자모 단위로 잘라서 보여줍니다.

convertHangulStringToJamos("R는 많은 공헌자에의한 공동 프로젝트입니다")
##  [1] "R"      "ㄴㅡㄴ" " "      "ㅁㅏㄶ" "ㅇㅡㄴ" " "      "ㄱㅗㅇ"
##  [8] "ㅎㅓㄴ" "ㅈㅏ"   "ㅇㅔ"   "ㅇㅢ"   "ㅎㅏㄴ" " "      "ㄱㅗㅇ"
## [15] "ㄷㅗㅇ" " "      "ㅍㅡ"   "ㄹㅗ"   "ㅈㅔㄱ" "ㅌㅡ"   "ㅇㅣㅂ"
## [22] "ㄴㅣ"   "ㄷㅏ"

키보드 매칭 기준으로 영타로 바꿔줍니다.

convertHangulStringToKeyStrokes("R는 많은 공헌자에의한 공동 프로젝트입니다")
##  [1] "R"        "sms"   " "        "aksg" "dms"   " "       
##  [7] "rhd"   "gjs"   "wk"     "dp"     "dml"   "gks"  
## [13] " "        "rhd"   "ehd"   " "        "vm"     "fh"    
## [19] "wpr"   "xm"     "dlq"   "sl"     "ek"
convertHangulStringToKeyStrokes("R는 많은 공헌자에의한 공동 프로젝트입니다", isFullwidth =F)
##  [1] "R"    "sms"  " "    "aksg" "dms"  " "    "rhd"  "gjs"  "wk"   "dp"  
## [11] "dml"  "gks"  " "    "rhd"  "ehd"  " "    "vm"   "fh"   "wpr"  "xm"  
## [21] "dlq"  "sl"   "ek"

자모 단위로 잘라져 있는 결과를 다시 합칩니다.

str <- convertHangulStringToJamos("배포 조건의 상세한것에 대해서는 'license()' 또는 'licence()' 라고 입력해주십시오")
(str2 <-paste(str, collapse=""))
## [1] "ㅂㅐㅍㅗ ㅈㅗㄱㅓㄴㅇㅢ ㅅㅏㅇㅅㅔㅎㅏㄴㄱㅓㅅㅇㅔ ㄷㅐㅎㅐㅅㅓㄴㅡㄴ 'license()' ㄸㅗㄴㅡㄴ 'licence()' ㄹㅏㄱㅗ ㅇㅣㅂㄹㅕㄱㅎㅐㅈㅜㅅㅣㅂㅅㅣㅇㅗ"
HangulAutomata(str2)
## [1] "배포 조건의 상세한것에 대해서는 'license()' 또는 'licence()' 라고 입력해주십시오"

최근 NIADic을 공개하는 것을 KoNLP 제작자에게 의뢰함으로써 사전의 공개가 R에서 우선적으로 이루어졌습니다. 기존 세종 사전의 9만 단어에서 약 98만 단어로 사전이 확보되어 성능의 개선을 기대할 수 있습니다.

useSystemDic()
## Backup was just finished!
## 283949 words dictionary was built.
extractNoun("성긴털제비꽃은 너무 예쁘다.")
## [1] "성긴털제비꽃은"
SimplePos09("성긴털제비꽃은 너무 예쁘다.")
## $성긴털제비꽃은
## [1] "성긴털제비꽃은/N"
## 
## $너무
## [1] "너무/M"
## 
## $예쁘다
## [1] "예쁘/P+다/E"
## 
## $.
## [1] "./S"
SimplePos22("성긴털제비꽃은 너무 예쁘다.")
## $성긴털제비꽃은
## [1] "성긴털제비꽃은/NC"
## 
## $너무
## [1] "너무/MA"
## 
## $예쁘다
## [1] "예쁘/PA+다/EF"
## 
## $.
## [1] "./SF"
MorphAnalyzer("성긴털제비꽃은 너무 예쁘다.")
## $성긴털제비꽃은
## [1] "성긴털제비꽃/ncn+은/jxc" "성긴털제비꽃은/ncn"     
## [3] "성긴털제비꽃/nqq+은/jxc" "성긴털제비꽃은/nqq"     
## 
## $너무
## [1] "너무/mag"
## 
## $예쁘다
## [1] "예쁘/paa+다/ef"
## 
## $.
## [1] "./sf" "./sy"
useSejongDic()
## Backup was just finished!
## 370957 words dictionary was built.
extractNoun("성긴털제비꽃은 너무 예쁘다.")
## [1] "성긴털제비꽃"
SimplePos09("성긴털제비꽃은 너무 예쁘다.")
## $성긴털제비꽃은
## [1] "성긴털제비꽃/N+은/J"
## 
## $너무
## [1] "너무/M"
## 
## $예쁘다
## [1] "예쁘/P+다/E"
## 
## $.
## [1] "./S"
SimplePos22("성긴털제비꽃은 너무 예쁘다.")
## $성긴털제비꽃은
## [1] "성긴털제비꽃/NC+은/JX"
## 
## $너무
## [1] "너무/MA"
## 
## $예쁘다
## [1] "예쁘/PA+다/EF"
## 
## $.
## [1] "./SF"
MorphAnalyzer("성긴털제비꽃은 너무 예쁘다.")
## $성긴털제비꽃은
## [1] "성긴털제비꽃/ncn+은/jxc" "성긴털제비꽃/ncn+은/ncn"
## 
## $너무
## [1] "너무/mag"
## 
## $예쁘다
## [1] "예쁘/paa+다/ef"
## 
## $.
## [1] "./sf" "./sy"
useNIADic()
## Backup was just finished!
## 983012 words dictionary was built.
extractNoun("성긴털제비꽃은 너무 예쁘다.")
## [1] "성긴털" "제비꽃"
SimplePos09("성긴털제비꽃은 너무 예쁘다.")
## $성긴털제비꽃은
## [1] "성긴털제비꽃/N+은/J"
## 
## $너무
## [1] "너무/M"
## 
## $예쁘다
## [1] "예쁘/P+다/E"
## 
## $.
## [1] "./S"
SimplePos22("성긴털제비꽃은 너무 예쁘다.")
## $성긴털제비꽃은
## [1] "성긴털제비꽃/NC+은/JX"
## 
## $너무
## [1] "너무/MA"
## 
## $예쁘다
## [1] "예쁘/PA+다/EF"
## 
## $.
## [1] "./SF"
MorphAnalyzer("성긴털제비꽃은 너무 예쁘다.")
## $성긴털제비꽃은
## [1] "성긴털/ncn+제비꽃/ncn+은/jxc"      "성긴털/ncn+제비꽃/ncn+은/ncn"     
## [3] "성긴털/ncn+제비꽃/ncn+은/jcs"      "성긴털/ncn+제비/ncn+꽃/ncn+은/jxc"
## [5] "성긴털/ncn+제비/ncn+꽃/ncn+은/jcs"
## 
## $너무
## [1] "너무/mag"
## 
## $예쁘다
## [1] "예쁘/paa+다/ef"
## 
## $.
## [1] "./sf" "./sy"

java 문제 해결

아래는 제작자가 작성한 java 설치에 관련된 문제 해결법을 첨부하였습니다.

KoNLP는 한글 처리에 대한 전문적인 지식이 없는 사회학, 경영, 경제학 등의 연구자들 그리고 일반인들이 R 기반으로 통계적 텍스트 분석을 수행하기 위한 편의를 제공하는데 목적을 두고 있다. 최대한 전문적인 경험이 필요없게 구성을 하고 있으나 몇가지 사용자들이 어려움을 겪는 공통적인 부분이 있다는 것을 이곳에 정리하여 시행착오를 출이고자 한다. 물론 이런 부분을 패키지에 넣으면 되지 않을까 생각하시는 분들이 있을 수 있으나 대부분 이곳에 언급하는 어려움들은 패키지 레벨에서 해결하기 어려운 환경적인 이슈와 엮여 있다는 것을 미리 언급해 둔다. 어떤 사용자분들은 이런 경험을 하나도 하지 못할 경우도 있고, 어떤 분들은 대부분 경험해 봤을 수 있는 이슈들이라 생각되는 것을 정리하고자 한다.

윈도우 rJava loading error

R만으로 한글에 대한 처리를 하는건 매우 어렵다. 텍스트 처리 관련 함수도 적고, 여러 인코딩 이슈도 발생한다. 무엇보다 처리 속도 이슈가 있는데, 이를 위해 JavaScala 언어를 기본 처리 언어로 사용하고 있다. 따라서 대부분의 R함수들은 JavaScala 기반의 함수를 호출하기 위한 Wrapper 역할을 수행한다. 따라서 KoNLP가 수행되기 위한 자바 환경이 구축되는 것은 정상적으로 KoNLP를 사용하기 위한 필수 조건이다. 그리고 KoNLP가 의존성을 가지고 있는 rJava 패키지는 R에서 존재하는 수많은 자바 기반의 패키지들을 구동하는데 매우 필수적으로 사용되는 패키지이다. 따라서 적절히 환경을 설정해 놓는다면 앞으로 R을 사용하는데 많은 도움이 될 것이다.

rJava 로딩 에러는 대부분 윈도우 기반의 환경에서 발생한다. 가장 중요한 부분은 사용자가 설치한 R의 버전과 Java의 버전이 맞는지 확인하는 것이다.

Sys.getenv("R_ARCH")
## [1] "/x64"

위 명령어로 확인해보면 R이 32비트 기반인지 64비트 기반인지 출력된다. 만일 /x64로 출력되면 64비트의 R이 설치된 것이다. 따라서 자바의 경우도 설치된 R의 아키텍처에 맞게 설치가 되어야 되는데, rJava로 문제가 생기는 대부분 이 부분이 맞지 않아서 생기는 문제이다.

자바 환경 설치는 이곳에서 자동으로 다운로드 가능하다. 여기서 다른 문제가 있는데, 바로 구동되는 시스템이 아닌 구동된 브라우저의 아키텍처에 따라 64비트 자바 혹은 32비트 자바가 자동으로 선택되어져 다운로드 된다는 것이다. 이 부분은 여기를 확인해보면 정확한 정보를 얻을 수 있을 것이다. 64비트 자바를 설치하기 위해서는 64비트 브라우저를 열어 설치 링크를 통해 설치해야 된다. 현재 자신의 시스템에 설치된 자바를 보고 싶다면 윈도우 제어판에서 Java 정보를 확인해 보는것도 도움이 될 것이다.

맥에서 rJava 문제

Mac + R + rJava의 조합은 맥에서 가장 어려운 조합으로 보여진다. 이는 맥에서 기본적으로 제공하고있는 Java 1.6이 시스템의 자바 설정에 강제하고 있는 부분이 있는 것으로 판단되고 이를 R에서 해결하는게 매우 까다로운 것으로 알려져 있기 때문이다. 맥에서의 문제만 아니면 필자는 벌써 Java 1.7 이나 1.8로 KoNLP를 바꾸었을 것이다. 지금껏 KoNLP에서 Java 1.6을 지원하는 가장 큰 원인은 맥에서의 문제 때문이다.

word2vec을 R에서 해보자

word2vec은 딥러닝의 일종으로 한글은 한글 word2vec 테스트에서 확인해 볼 있습니다.

word2vec의 필요성과 역사 등은 김홍배님의 훌륭한 자료가 있어서 확인해보시면 좋을 것 같습니다. 정상근 박사님의 자료도 좋습니다.

알고리즘의 종류

빠른 성능으로 개선된 모델인 google의 word2vec을 시작으로 Stanford에발 발표한 Glove, facebook이 발표한 fastText 등이 있습니다. 우리는 실습이 용이한 Glove를 사용해 보겠습니다.

library(text2vec)
## 
## Attaching package: 'text2vec'
## The following object is masked from 'package:dplyr':
## 
##     collect
text8_file = "./text8"
if (!file.exists(text8_file)) {
  download.file("http://mattmahoney.net/dc/text8.zip", "./text8.zip")
  unzip ("./text8.zip", files = "text8", exdir = "./")
}
wiki = readLines(text8_file, n = 1, warn = FALSE)

tokens <- space_tokenizer(wiki)

it = itoken(tokens, progressbar = FALSE)
vocab <- create_vocabulary(it)
vocab <- prune_vocabulary(vocab, term_count_min = 5L)

vectorizer <- vocab_vectorizer(vocab)

tcm <- create_tcm(it, vectorizer)
glove = GlobalVectors$new(word_vectors_size = 50, vocabulary = vocab, x_max = 10)
word_vectors <- glove$fit_transform(tcm, n_iter = 20)
## INFO [2017-07-25 03:47:54] 2017-07-25 03:47:54 - epoch 1, expected cost 0.0877
## INFO [2017-07-25 03:48:21] 2017-07-25 03:48:21 - epoch 2, expected cost 0.0612
## INFO [2017-07-25 03:48:46] 2017-07-25 03:48:46 - epoch 3, expected cost 0.0541
## INFO [2017-07-25 03:49:11] 2017-07-25 03:49:11 - epoch 4, expected cost 0.0502
## INFO [2017-07-25 03:49:34] 2017-07-25 03:49:34 - epoch 5, expected cost 0.0476
## INFO [2017-07-25 03:50:04] 2017-07-25 03:50:04 - epoch 6, expected cost 0.0458
## INFO [2017-07-25 03:50:29] 2017-07-25 03:50:29 - epoch 7, expected cost 0.0444
## INFO [2017-07-25 03:50:54] 2017-07-25 03:50:54 - epoch 8, expected cost 0.0433
## INFO [2017-07-25 03:51:21] 2017-07-25 03:51:21 - epoch 9, expected cost 0.0425
## INFO [2017-07-25 03:51:46] 2017-07-25 03:51:46 - epoch 10, expected cost 0.0418
## INFO [2017-07-25 03:52:13] 2017-07-25 03:52:13 - epoch 11, expected cost 0.0411
## INFO [2017-07-25 03:52:37] 2017-07-25 03:52:37 - epoch 12, expected cost 0.0406
## INFO [2017-07-25 03:52:58] 2017-07-25 03:52:58 - epoch 13, expected cost 0.0402
## INFO [2017-07-25 03:53:25] 2017-07-25 03:53:25 - epoch 14, expected cost 0.0398
## INFO [2017-07-25 03:53:47] 2017-07-25 03:53:47 - epoch 15, expected cost 0.0394
## INFO [2017-07-25 03:54:14] 2017-07-25 03:54:14 - epoch 16, expected cost 0.0391
## INFO [2017-07-25 03:54:39] 2017-07-25 03:54:39 - epoch 17, expected cost 0.0388
## INFO [2017-07-25 03:55:06] 2017-07-25 03:55:06 - epoch 18, expected cost 0.0385
## INFO [2017-07-25 03:55:28] 2017-07-25 03:55:28 - epoch 19, expected cost 0.0383
## INFO [2017-07-25 03:55:51] 2017-07-25 03:55:51 - epoch 20, expected cost 0.0381
berlin <- word_vectors["paris", , drop = FALSE] - 
  word_vectors["france", , drop = FALSE] + 
  word_vectors["germany", , drop = FALSE]
cos_sim = sim2(x = word_vectors, y = berlin, method = "cosine", norm = "l2")
head(sort(cos_sim[,1], decreasing = TRUE), 5)
##     paris    vienna   germany    munich      rome 
## 0.7529268 0.7190628 0.7048425 0.6914796 0.6611500