티블의 기초 객체인 벡터에 대해 알아야 한다.
함수의 대부분은 벡터로 인하여 작동하기 때문 매우 중요.
purrr 패키지의 함수를 사용할 것
library(tidyverse)
1. 논리형(logical)
리스트 = 재귀 벡터라고도 불리며 한 리스트가 다른 리스트를 포함할 수 있다.
<원자 벡터와 리스트의 차이점>
1. 원자 벡터 = 동질적, 리스트 = 이질적
2. NULL은 벡터가 없는 것을 나타내기 위해 종종 사용된다.
모든 벡터는 두가지 주요 속성이 있다.
typeof(letters)
## [1] "character"
typeof(1:10)
## [1] "integer"
x <- list("a", "b", 1:10)
length(x)
## [1] 3
임의의 추가 메타 데이터를 속성(attribute)형식으로 벡터에 포함시켜 확장 벡터를 만드는데 사용한다. 확장 벡터에는 네 가지 중요한 유형이 있다.
1:10 %% 3 == 0
## [1] FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE TRUE FALSE
c(TRUE, TRUE, FALSE, NA)
## [1] TRUE TRUE FALSE NA
typeof(1)
## [1] "double"
typeof(1L)
## [1] "integer"
1.5L
## [1] 1.5
정수형과 더블형을 구분하는 게 일반적으로 중요하지 않지만 두 가지 중요한 차이점을 알고 있어야 한다.
예를 들어 2의 제곱근의 제곱은 무엇일까?
x <- sqrt(2) ^ 2
x
## [1] 2
x - 2
## [1] 4.440892e-16
즉, 대부분의 계산에는 근사 오차가 포함된다. ==를 사용하여 부동 소수점 수를 비교하는 대신 dplyr::near()를 사용해야 한다. 그러면 수치상 오차가 허용된다.
c(-1, 0, 1) / 0
## [1] -Inf NaN Inf
이러한 특수한 값을 확인할 목적으로 ==을 사용하면 안 된다.
대신 도우미 함수
문자형 벡터는 각 요소가 문자열이기 때문에 가장 복잡한 유형의 원자 벡터이다.
pryr::object_size()를 사용하여 이 동작을 직접 볼 수 있다.
x <- "적당히 긴 문자열입니다"
pryr::object_size(x)
## 136 B
y <- rep(x, 1000)
pryr::object_size(y)
## 8.13 kB
각 유형의 원자 벡터에는 고유한 결측값이 있다.
NA # 논리형
## [1] NA
NA_integer_ # 정수형
## [1] NA
NA_real_ # 더블형
## [1] NA
NA_character_ # 문자형
## [1] NA
여러 유형의 원자 벡터를 이해했으므로 이제 함께 사용할 수 있는 도구들을 검토하는 것이 좋다.
한 유형 벡터에서 다른 유형으로 강제 변환하는 방법은 두 가지이다.
logical(), as.integer(), as.double(), as.character()와 같은 함수를 호출할 때 발생명시적 강제 변환이 필요한 경우
-readr의 col_types 명세를 조정해야 할 수도 있다.
수치형 요약 함수에 논리형 벡터를 사용하거나 정수형 벡터가 예상되는 곳에 더블형 벡터를 사용 하는 경우이다.
이미 암묵적 강제 변환을 보았다. -> 수치형 문제에서 논리형 벡터를 사용 했던 것이다.
x <- sample(20, 100, replace = TRUE)
y <- x > 10
sum(y) # 10보다 큰 것의 개수?
## [1] 38
mean(y) # 10보다 큰 비율?
## [1] 0.38
반대 방향, 즉 정수형에서 논리형으로의 암시적 강제 변환에 의존하는 코드도 볼 수 있다.
if (length(x)) {
# 어떤 작업
}
이렇게 하면 코드를 이해하기가 더 어렵게 된다고 생각하기 때문에 이를 권장하지 않는다.
length(x) > 0
또한 c()로 여러 유형을 포함하는 벡터를 만들려고 할 때 다음과 같은 일이 일어난다는 것을 이해하는 것도 중요하며 가장 복잡한 유형으로 변환된다.
typeof(c(TRUE, 1L))
## [1] "integer"
typeof(c(1L, 1.5))
## [1] "double"
typeof(c(1.5, "a"))
## [1] "character"
벡터의 유형은 개별요소가 아닌 전체 벡터의 특성, 한 원자 벡터의 유형이 여러 개일 수는 없다.
동일한 벡터에서 여러 유형을 혼합해야 하는 경우에는 리스트를 사용하면 된다.
purrr이 제공하는 is_* 함수를 사용하는 것이 더 안전하다 위의 각 함수마다 is_scalar atomic()과 같은 '스칼라' 버전이 있는데 이는 길이가 1인지 확인한다.예로 든 코드이다
sample(10) + 100
## [1] 102 110 109 107 108 101 103 106 105 104
runif(10) > 0.5
## [1] TRUE FALSE TRUE TRUE TRUE FALSE TRUE FALSE FALSE FALSE
같은 길이의 두 벡터 또는 벡터와 ’스칼라’를 더할 경우 일어나야 하는 일은 직관적이다.
but 길이가 다른 두 개의 벡터를 더하면 어떻게 되는가?
1:10 + 1:2
## [1] 2 4 4 6 6 8 8 10 10 12
여기에서 R은 가장 짧은 벡터를 가장 긴 벡터 길이로 확장함 -> 이것을 재활용이라고 부른다.
1:10 + 1:3
## [1] 2 4 6 5 7 9 8 10 12 11
#> Warning in 1:10 + 1:3: longer object length is not a multiple of shorter
#> object length
벡터 재활용을 사용하면 매우 간결하고 영리한 코드를 작성할 수 있지만 조용히 문제가 숨겨질 수도 있다. 재활용을 하고싶다면 rep()으로 직접 처리해야 한다.
tibble(x = 1:4, y = 1:2)
#> Error: colum 'y' must be length 1 or 4, not 2
tibble(x = 1:4, y = rep(1:2, 2))
## # A tibble: 4 x 2
## x y
## <int> <int>
## 1 1 1
## 2 2 2
## 3 3 1
## 4 4 2
tibble(x = 1:4, y = rep(1:2, each = 2))
## # A tibble: 4 x 2
## x y
## <int> <int>
## 1 1 1
## 2 2 1
## 3 3 2
## 4 4 2
모든 유형의 벡터는 이름을 지정할 수 있다. c()를 사용하여 생성 시 이름을 지정할 수 있다.
c(x = 1, y = 2, z = 3)
## x y z
## 1 2 3
또는 생성 이후에 purrr::set_names()으로 이름을 지정할 수도 있다.
setNames(1:3, c("a", "b", "c"))
## a b c
## 1 2 3
명명된 벡터는 서브셋할 때 매우 유용하며 다음에 설명된다.
지금까지 dplyr::filter()를 사용하여 티블의 행을 필터링했다.
filter()는 티블에서만 작동하기 때문에 벡터용 도구가 새로 필요하다 -> [ 이다
[ 은 서브셋 하는 함수이며 x[a]와 같이 호출된다.
x <- c("one", "two", "three", "four", "five")
x[c(3, 2, 5)]
## [1] "three" "two" "five"
위치를 반복하면 실제로 입력보다 긴 출력을 만들 수 있다.
x[c(1, 1, 5, 5, 5, 2)]
## [1] "one" "one" "five" "five" "five" "two"
음수값은 해당 위치의 요소를 누락시킨다.
x[c(-1, -3, -5)]
## [1] "two" "four"
양수값과 음수값을 혼합하면 오류이다.
x[c(1, -1)]
#> Error in x[c(1, -1)]
#> only 0's may be mixed with negative subscripts
다음의 오류 메시지는 0으로 서브셋했기 때문에 아무 값도 반환하지 않았음을 나타낸다.
x[0]
## character(0)
x <- c(10, 3, NA, 5, 8, 1, NA)
# x 중 결측값이 아닌 모든 값
x[!is.na(x)]
## [1] 10 3 5 8 1
# x 중 모든 짝수(혹은 결측값!)
x[x %% 2 == 0]
## [1] 10 NA 8 NA
x <- c(abc = 1, def = 2, xyz = 5)
x[c("xyz", "def")]
## xyz def
## 5 2
가장 간단한 서브셋 동작은 x[ ]이며, 전체 x를 반환한다.
[ 의 변형 [[ 이다. [[ 은 오직 하나의 요소만 추출하고 항상 이름을 누락시킨다.
다양한 종류의 데이터를 가지는 데이터 묶음
리스트 자료형 자체도 데이터로 가질 수 있음
그래서 재귀형(recursive)라고도 표현함
원자 벡터 생성 : c() 함수 사용 (c = combin의 약자)
리스트 생성 : list() 함수 사용
이중 대괄호와 대괄호가 있는 형태
x <- list(1, 2, 3)
x
## [[1]]
## [1] 1
##
## [[2]]
## [1] 2
##
## [[3]]
## [1] 3
str(x)
## List of 3
## $ : num 1
## $ : num 2
## $ : num 3
x_named <- list(a = 1,b = 2,c = 3)
str(x_named)
## List of 3
## $ a: num 1
## $ b: num 2
## $ c: num 3
list()는 객체들을 혼합하여 포함할 수 있다.y <- list("a", 1L, 1.5, TRUE)
str(y)
## List of 4
## $ : chr "a"
## $ : int 1
## $ : num 1.5
## $ : logi TRUE
z <- list(list(1, 2), list(3, 4))
str(z)
## List of 2
## $ :List of 2
## ..$ : num 1
## ..$ : num 2
## $ :List of 2
## ..$ : num 3
## ..$ : num 4
복잡한 리스트는 시각적으로 표현하는 것이 편리하다.
x1 <- list(c(1, 2), c(3, 4))
x2 <- list(list(1, 2), list(3, 4))
x3 <- list(1, list(2, list(3)))
3가지 원칙이 있다.
리스트 서브셋하는 방법은 세 가지이다. 예시로 어떤 리스트가 a라 하자.
a <- list(a = 1:3, b = "a string", c = pi, d = list(-1, -5))
str(a[1:2])
## List of 2
## $ a: int [1:3] 1 2 3
## $ b: chr "a string"
str(a[4])
## List of 1
## $ d:List of 2
## ..$ : num -1
## ..$ : num -5
벡터에서와 같이 논리형, 정수형, 문자형 벡터로 서브셋할 수 있다.
str(a[[1]])
## int [1:3] 1 2 3
str(a[[4]])
## List of 2
## $ : num -1
## $ : num -5
a$a
## [1] 1 2 3
a[["a"]]
## [1] 1 2 3
[ 와 [[ 의 차이는 리스트에 있어서 정말 중요하다. [[ 는 리스트 안으로 내려가는 반면 [ 는 더 작은 새 리스트를 반환하기 때문이다.
[ 와 [[ 의 차이점은 매우 중요. 헷길리기 쉬움으로 후추통으로 비유해보자
이 후추통이 리스트 x라고 한다면 x[1]은 후추팩 하나가 들어있는 후추통이다.
x[2]는 똑같지만, 두 번째 팩을 포함한다. x[1:2]는 후추팩 두 개가 들어있는 후추통이다.
x[[1]]은 후추팩 하나이다.
후추팩의 내용물은 x[[1]][[1]]으로 얻을 수 있다.
attr() 함수를 사용하여 가져오거나 attributes()로 한번에 모두 볼 수 있다.x <- 1:10
attr(x, "greeting")
## NULL
attr(x, "greeting") <- "Hi!"
attr(x, "farewell") <- "Bye"
attributes(x)
## $greeting
## [1] "Hi!"
##
## $farewell
## [1] "Bye"
R의 기본 부분을 구현하는데 세 가지 매우 중요한 속성이 사용되었다.
Class는 제네릭 함수의 작동 방식을 제어한다.
일반적인 제너릭 함수는 다음과 같다.
as.Date
## function (x, ...)
## UseMethod("as.Date")
## <bytecode: 0x00000000116ec238>
## <environment: namespace:base>
‘UseMethod’ 호출은 이 함수가 제네릭 함수임을 의미하여 첫 번째 인수의 클래스를 기반으로 특정함수(메소드)를 호출한다. (모든 메소드는 함수이지만 모든 함수가 메서드인 것은 아니다.) methods()를 사용하여 제네릭의 모든 메서드를 나열할 수 있다.
methods("as.Date")
## [1] as.Date.character as.Date.default as.Date.factor
## [4] as.Date.numeric as.Date.POSIXct as.Date.POSIXlt
## [7] as.Date.vctrs_sclr* as.Date.vctrs_vctr*
## see '?methods' for accessing help and source code
예를 들어 x가 문자형 벡터이면 as.Date()는 as.Date.character()를 호출하고 팩터형이면 as.Date.factor()를 호출한다.
getS3method()로 메서드의 구현을 구체적으로 볼 수 있다.
getS3method("as.Date", "default")
## function (x, ...)
## {
## if (inherits(x, "Date"))
## x
## else if (is.null(x))
## .Date(numeric())
## else if (is.logical(x) && all(is.na(x)))
## .Date(as.numeric(x))
## else stop(gettextf("do not know how to convert '%s' to class %s",
## deparse1(substitute(x)), dQuote("Date")), domain = NA)
## }
## <bytecode: 0x00000000156a8628>
## <environment: namespace:base>
getS3method("as.Date", "numeric")
## function (x, origin, ...)
## {
## if (missing(origin)) {
## if (!length(x))
## return(.Date(numeric()))
## if (!any(is.finite(x)))
## return(.Date(x))
## stop("'origin' must be supplied")
## }
## as.Date(origin, ...) + x
## }
## <bytecode: 0x00000000149d3668>
## <environment: namespace:base>
가장 중요한 S3 제네릭은 print()인데 이는 콘솔에서 이름이 타이핑되었을때 객체가 출력되는 방식을 조정한다. 다른 중요한 제네릭은 서브넷 동작 함수들인 [,[[, $이다.
확장벡터는 클래스를 가지므로 원자 벡터와 다르게 동작한다.
x <- factor(c("ab", "cd", "ab"), levels = c("ab", "cd", "ef"))
typeof(x)
## [1] "integer"
attributes(x)
## $levels
## [1] "ab" "cd" "ef"
##
## $class
## [1] "factor"
R의 데이트형은 1970년 1월 1일부터 지난 일 수를 나타내는 수치형 벡터이다.
x <- as.Date("1971-01-01")
unclass(x)
## [1] 365
typeof(x)
## [1] "double"
attributes(x)
## $class
## [1] "Date"
데이트-타임형은 1970년 1월 1일부터 지난 초 수를 나타내는 POSIXct 클래스를 가진 수치형 벡터이다.
'POSIXct'는 Portable Operating System Interface calendar time을 의미한다.
x <- lubridate::ymd_hm("1970-01-01 01:00")
unclass(x)
## [1] 3600
## attr(,"tzone")
## [1] "UTC"
typeof(x)
## [1] "double"
attributes(x)
## $class
## [1] "POSIXct" "POSIXt"
##
## $tzone
## [1] "UTC"
tzone 속성은 선택사항이다. 이것은 시간이 출력되는 방식을 제어하는 것이지 절대 시간을 제어하는 것이 아니다.
attr(x, "tzone") <- "US/Pacific"
x
## [1] "1969-12-31 17:00:00 PST"
attr(x, "tzone") <- "US/Eastern"
x
## [1] "1969-12-31 20:00:00 EST"
데이트-타임형의 또 다른 유형은 POSIXlt라고 부르는 것이다. 이는 명명된 리스트를 기반으로 만들어졌다.
y <- as.POSIXlt(x)
typeof(y)
## [1] "list"
attributes(y)
## $names
## [1] "sec" "min" "hour" "mday" "mon" "year" "wday" "yday"
## [9] "isdst" "zone" "gmtoff"
##
## $class
## [1] "POSIXlt" "POSIXt"
##
## $tzone
## [1] "US/Eastern" "EST" "EDT"
POSIXlt는 tidyverse 내부에서 거의 쓰지 않는다.
tb <- tibble::tibble(x = 1:5, y = 5:1)
typeof(tb)
## [1] "list"
attributes(tb)
## $names
## [1] "x" "y"
##
## $row.names
## [1] 1 2 3 4 5
##
## $class
## [1] "tbl_df" "tbl" "data.frame"
전통적인 데이터프레임은 매우 유사한 구조를 가지고 있다.
df <- data.frame(x = 1:5, y = 5:1)
typeof(df)
## [1] "list"
attributes(df)
## $names
## [1] "x" "y"
##
## $class
## [1] "data.frame"
##
## $row.names
## [1] 1 2 3 4 5
여기서의 차이점은 클래스이다. 티블의 클래스는 ’data.frame’을 포함하는데 이는 티블이 기본값으로 일반적인 데이터프라임 동작을 물려받음을 의미한다.
티블 데이터프라임과 리스트의 차이점은 모든 요소는 길이가 같은 벡터이어야 한다는 점이다.