R언어에는 다른 언어에는 없는자료형이 있다. 처음 접했다면 각 자료형끼리는 어떤 차이가 있는지 어떻게 사용해야하는지 헷갈린다. R에만 있는 자료형들을 중점으로 각 자료형의 사용법을 알아보고 예제를 통해 익혀보자.
R은 통계를 위한 언어이다.
R은 통계를 위해 태어났다. 아무리 파이썬이 데이터 시각화에 좋다고 하더라도 한 우물만 파는 R에는 안된다. 여러 분포함수와 통계함수를 지원하기 때문이다. data frame 만 봐도 그렇다. 딱 봐도 통계를 위해 존재한다. 따라서 R의 장점을 제대로 활용하기 위해서는 R에만 있는 것 들을 잘 다룰 수 있어야한다. R의 장점을 활용하지 못한다면 R을 쓰는 이유가 없다. 자주 헷갈리는, R에만 있는 자료형에 집중하며 데이터 형식을 알아보자.
이 글에서는 vector, matrix, array, list, data.frame, factor를 다룬다. 글이 길기 때문에 ctrl+f / cmd+f로 검색해서 필요한 부분만 읽는 것을 추천한다.
열거형 데이터 타입을 잘 다루지 못하면 R을 쓰는이유가 없다.
통계를 위해서는 여러개의 데이터를 다룰 수 밖에 없고 이는 필연적으로 열거형 데이터를 사용으로 이어진다. 그래서인지 R에는 많은 열거형 데이터 타입이 존제한다. 열거형 데이터 타입에 대해 알아보자.
1. 벡터(Vector)
벡터는 동일 타입의 데이터가 순차적으로 저장된 데이터 구조다. 뒤에서 다룰 배열(array)로 표현하자면 1차원 배열이다. 벡터의 요소는 숫자(numeric), 문자(character), 논리(logical)로 이루어질 수 있다. 벡터의 연산은 요소연산이 기본이다. index는 1부터 시작한다.
v1 = c(1,2,3,4,5,6,7)
v1 > 4 # 벡터는 요소 연산이 기본이다.
# [1] FALSE FALSE FALSE FALSE TRUE TRUE TRUE
v1[1] # index는 1부터 시작한다
# [1] 1
벡터 생성 함수에는 c, seq, rep이 있다.
(1) c (concatenate)
지금까지 계속 써오던 방법이다. 요소들을 묶어 벡터로 만들어준다.
v1 = c(1,2,3,4) # [1] 1 2 3 4
벡터도 묶을 수 있다.
v1 = c(1,2,3,4)
v2 = c(5,6,7)
vc = c(v1,v2,8,9) # [1] 1 2 3 4 5 6 7 8 9
각 요소에 이름을 부여할 수도 있다.
v1 = c(red=1,green=2,blue=5)
# red green blue
# 1 2 5
만약 타입이 다른 요소를 c(...)로 묶는다면 제약이 가장 적은 형식으로 변환된다.
c(FALSE, 3) # [1] 0 3
c(pi, "abc") # [1] "3.14159265358979" "abc"
c(TRUE, "abc") # [1] "TRUE" "abc"
(2) seq (sequence)
등간격 벡터이다. seq(from, to, by)이다.
seq(4,10) # [1] 4 5 6 7 8 9 10
seq(4,10,2) # [1] 4 6 8 10
간격이 1일경우 ":"를 이용해 많이 사용한다.
seq(4,9) # [1] 4 5 6 7 8 9
4:9 # [1] 4 5 6 7 8 9
(3) rep (replicate)
반복된 값으로 이루어진 벡터를 만들때 사용한다.
# 1을 5번 반복
rep(1,5) # [1] 1 1 1 1 1
# 1부터 3까지 3번 반복
rep(1:3, 3) # [1] 1 2 3 1 2 3 1 2 3
3 (2, 1, 4)를 2번 반복
rep(c(2,1,4), 2) # [1] 2 1 4 2 1 4
벡터를 초기화 할때 자주 사용된다.
zeros = rep(0,5) # [1] 0 0 0 0 0
반복 횟수를 다르게 할 수도 있다.
# 2를 1번, 4를 2번, 6을 3번 반복
rep(c(2,4,6),1:3) # [1] 2 4 4 6 6 6
# 1을 4번, 2를 2번, 3을 0번 반복
rep(1:3,c(4,2,0)) # [1] 1 1 1 1 2 2
반복하는 요소 벡터와 반복 횟수 벡터의 길이가 다르면 오류가 발생한다.
각 요소를 같은 횟수만큼 반복시키고 싶다면 each 매개변수를 이용하면 된다.
# [1] 1 2 3 1 2 3 1 2 3
rep(c(1,2,3), 3)
# [1] 1 1 1 2 2 2 3 3 3
rep(c(1,2,3), c(3,3,3))
rep(c(1,2,3), each = 3)
rep(c(1,2,3), rep(3,3))
- 벡터는 요소 계산을 수행한다.
2*1:4 # [1] 2 4 6 8
sqrt(1:4) # [1] 1.000000 1.414214 1.732051 2.000000
1:6 + rep(1:3,2) # [1] 2 4 6 5 7 9
2. 행렬(Metrix)
행렬은 2차원 배열(array)이다. 기본적으로 열방향으로 요소를 채우고 byrow 값을 TRUE로 하면 행방향으로 요소를 채울 수 있다. 행렬의 곱 역시 요소의 곱이다. 수학에서의 행렬의 곱 연산(dot)을 생각하면 안된다.
행렬은 matrix() 와 cbind(), rbind()를 이용해 만들 수 있다.
(1) matrix()
a = matrix(1:12,nrow = 3)
# [,1] [,2] [,3] [,4]
# [1,] 1 4 7 10
# [2,] 2 5 8 11
# [3,] 3 6 9 12
b = matrix(1:12,ncol = 3)
# [,1] [,2] [,3]
# [1,] 1 5 9
# [2,] 2 6 10
# [3,] 3 7 11
# [4,] 4 8 12
c = matrix(1:12,nrow = 3, byrow = T)
# [,1] [,2] [,3] [,4]
# [1,] 1 2 3 4
# [2,] 5 6 7 8
# [3,] 9 10 11 12
벡터로 행, 열을 정의할 수 있다.
matrix(1:12, c(2,6)) # 2행 3열 행렬 생성
# [,1] [,2] [,3] [,4] [,5] [,6]
# [1,] 1 3 5 7 9 11
# [2,] 2 4 6 8 10 12
(2) cbind(), rbind()
cbind()는 column으로, rbind()는 row로 벡터를 묶어 matrix를 만든다.
cbind(1:4,5:8,9:12)
# [,1] [,2] [,3]
# [1,] 1 5 9
# [2,] 2 6 10
# [3,] 3 7 11
# [4,] 4 8 12
rbind(1:4,5:8,9:12)
# [,1] [,2] [,3] [,4]
# [1,] 1 2 3 4
# [2,] 5 6 7 8
# [3,] 9 10 11 12
행과 열에 이름을 붙일 수도 있다.
# cbind에서 열에 이름 붙이기
cbind(A=1:4, B=5:8, C=9:12)
# A B C
# [1,] 1 5 9
# [2,] 2 6 10
# [3,] 3 7 11
# [4,] 4 8 12
# rbind에서 행에 이름 붙이기
rbind(A=1:4, B=5:8, C=9:12)
# [,1] [,2] [,3] [,4]
# A 1 2 3 4
# B 5 6 7 8
# C 9 10 11 12
# 이미 생성된 matrix에 행, 열에 이름 붙이기
m = matrix(1:12, nrow=3)
rownames(m) = LETTERS[1:3] # 행에 이름 붙이기
m
# [,1] [,2] [,3] [,4]
# A 1 4 7 10
# B 2 5 8 11
# C 3 6 9 12
colnames(m) = letters[1:4] # 열에 이름 붙이기
m
# a b c d
# A 1 4 7 10
# B 2 5 8 11
# C 3 6 9 12
LETTERS와 letters는 각각 A-Z, a-z의 값을 갖고있는 상수 벡터다 letters[1:3]은 letters벡터의 첫번째에서 세번째 요소를 추출해 새로운 벡터를 만든 것으로 슬라이싱이라 부른다.
paste 함수를 이용해 문자를 연결할 수도 있다. sep은 연결할 때 문자 사이에 넣는 문자를 의미한다.
m = matrix(1:12, nrow=3)
rownames(m) = paste("Row",1:3) # paste("Row",1:3)의 결과는 [1] "Row 1" "Row 2" "Row 3"
colnames(m) = paste("col",1:4, sep = "-")
m
# col-1 col-2 col-3 col-4
# Row 1 1 4 7 10
# Row 2 2 5 8 11
# Row 3 3 6 9 12
(3) 행렬의 연산
연산 예시를 위해 2x2 행렬 A와 B를 만든다.
A = matrix(1:4, nrow = 2)
A
# [,1] [,2]
# [1,] 1 3
# [2,] 2 4
B = matrix(c(0,1,1,1), nrow = 2)
B
# [,1] [,2]
# [1,] o 1
# [2,] 1 1
3.1) 전치행렬 (transpose): t()
t(A)
# [,1] [,2]
# [1,] 1 2
# [2,] 3 4
3.3) 성분의 합: +
A + B
# [,1] [,2]
# [1,] 1 4
# [2,] 3 5
3.4) 성분의 곱: *
A * B
# [,1] [,2]
# [1,] 0 3
# [2,] 2 4
3.5) 성분의 역수: -1승
A^(-1)
# [,1] [,2]
# [1,] 1.0 0.3333333
# [2,] 0.5 0.2500000
3.6) 행렬의 곱(AXB): %*%
A %*% B
# [,1] [,2]
# [1,] 3 4
# [2,] 4 6
3.7) 역행렬(inverse matrix): solve()
solve(A)
# [,1] [,2]
# [1,] -2 1.5
# [2,] 1 -0.5
solve(A,B)는 A %*% X == B인 X를 반환한다. 즉 B의 기본값이 단위행렬E이다.
solve(A,B)
# [,1] [,2]
# [1,] 1.5 -0.5
# [2,] -0.5 0.5
역행렬은 MASS package의 ginv()함수를 사용해도된다.
3.8) 대각행렬(diagonal matrix): diag()
diag(1:4)
# [,1] [,2] [,3] [,4]
# [1,] 1 0 0 0
# [2,] 0 2 0 0
# [3,] 0 0 3 0
# [4,] 0 0 0 4
diag(2)
# [,1] [,2]
# [1,] 1 0
# [2,] 0 1
대각행렬에 숫자만 입력하면 단위행렬을 만들 수 있다.
(4) 행렬의 요소 접근
A[3] # 1차원 벡터에서 요소 순서로 접근
# [1] 3
A[1,] # 1행 벡터
# [1] 1 3
A[,1] # 1열 벡터
# [1] 1 2
A[1,2] # 1행 2열 요소
# [1] 3
A[1,][2] # 1행 백터의 2번째 요소
# [1] 3
3. 배열(Array)
배열은 동일 타입의 데이터가 순차적으로 저장된 구조이다. 1차원, 2차원은 물론이고 n차원이 가능하다. 따라서 차원을 정의해야 한다.
arr = array(1:12) # 1차원 배열 arr
arr
# [1] 1 2 3 4 5 6 7 8 9 10 11 12
dim(arr) = c(3,4) # arr 배열의 차원을 2차원 (3 by 4)로 설정
arr
# [,1] [,2] [,3] [,4]
# [1,] 1 4 7 10
# [2,] 2 5 8 11
# [3,] 3 6 9 12
배열을 정의할때 dim매개변수를 이용해 차원을 설정할 수 있다.
arr = array(1:2, dim = c(3,4))
arr
# [,1] [,2] [,3] [,4]
# [1,] 1 4 7 10
# [2,] 2 5 8 11
# [3,] 3 6 9 12
다차원 배열도 가능하다. 3차원 배열로 예시를 들면 다음과 같다.
arr = array(1:12, dim= c(2,3,2)) # 3차원 (2x3x2) 배열
arr
# , , 1
#
# [,1] [,2] [,3]
# [1,] 1 3 5
# [2,] 2 4 6
#
# , , 2
#
# [,1] [,2] [,3]
# [1,] 7 9 11
# [2,] 8 10 12
3. 리스트(List)
리스트는 객체의 집합이다. 이때 각 객체들은 다른 타입이어도 된다.
v1 = c(1,2,3,4)
m1 = matrix(1:4,nrow = 2)
lst = list(1, v1, m1, "hi")
lst
# [[1]]
# [1] 1
#
# [[2]]
# [1] 1 2 3 4
#
# [[3]]
# [,1] [,2]
# [1,] 1 3
# [2,] 2 4
#
# [[4]]
# [1] "hi"
리스트의 각 요소에 이름을 붙일 수 이있다.
lst = list(int = 1, vec = v1, mat = m1, char = "hi")
lst
# $int
# [1] 1
#
# $vec
# [1] 1 2 3 4
#
# $mat
# [,1] [,2]
# [1,] 1 3
# [2,] 2 4
#
# $char
# [1] "hi"
(1) 리스트의 요소 접근
여기서부터 헷갈린다!! 리스트와 데이터 프레임은 "$"기호를 이용해 요소에 접근할 수 있다. 필자는 이를 편의상 서랍장과 서랍을 예로 들어 설명하겠다. 필자의 설명이 정확하지 않을 수 있다. 하지만 메모리에서 추상적인 stack을 물리적 모델로 형상화해 생각하는 것 처럼 추상적인 개념을 예외 없이 물리적 모델로 매핑 할 수 있다면 정확하지 않더라도 물리적 모델은 추상적 개념을 이해하는데 큰 도움이 된다. 한 마디로 대충 잘 맞는 모델을 만들어 모델을 예시로 설명하겠다.
먼저 우리가 익숙한 대괄호(brackets)를 이용해 위에서 만든 lst의 요소에 접근해 보자.
# 아래 두 코드는 같은 결과를 리턴한다.
lst[3] # lst의 세번째 요소 (matrix)
lst["mat"] # lst의 mat 요소
# $mat
# [,1] [,2]
# [1,] 1 3
# [2,] 2 4
그렇다면 lst의 세번째 요소인 2x2 행렬의 요소에 접근하려면 어떻게 해야할까? mat 행렬의 1행 2열 요소인 3에 접근해 보자.
lst[3][1,2]
lst["mat"][1,2]
# Error in lst[3][1, 2] : incorrect number of dimensions
lst[3]의 [1,2]로 접근하기위해 lst[3][1,2]로 접근해 봤지만 차원 오류가 생긴다. 왜그럴까?
필자가 내린 결론은 "list는서랍들로 이루어진 서랍장으로 생각하면 된다"이다. 4개의 칸으로 이루어진 서랍장을 list에 비유하면 첫번째 서랍이 list[1]이다. 서랍안에 들어있는 노트의 첫번째장 두번째 줄을 읽기 위해 list[1][1,2]로 접근했다. 하지만 list[1][1,2]는 서랍의 1장 2열을 의미한다. 서랍 자체에는 행과 열이 없다. 서랍안에 들어있는 노트에 장과 열이 있다. 그림으로 표현하면 다음과 같다.
파란색의 서랍으로 연결된 서랍장에는 서랍이 있고 그 안에 붉은색 내용물이 있다. 따라서 lst[3]는 세번째 파란색 칸 하나를 가리키므로 lst[3][2,1]은 존재하지 않는다. 칸 속에 있는 내용물에 접근하기 위해서는 lst[[3]]로 서랍안의 내용물에 접근 해야 한다. 이중 대괄호는 $와 같은 의미를 갖는다. 즉, lst[["mat"]]은 lst$mat과 같은 결과를 반환한다.
lst[[3]]
# [,1] [,2]
# [1,] 1 3
# [2,] 2 4
lst[[3]][1,2]
# [1] 3
lst[["mat"]][1,2]
# [1] 3
lst$mat
# [,1] [,2]
# [1,] 1 3
# [2,] 2 4
lst$mat[1,2]
# [1] 3
위의 결과에서 알 수 있듯 lst[3]은 $mat이라는 서랍의 이름도 반환했지만 lst[[3]]은 서랍안의 내용물만 보여주기 때문에 이름이 보이지 않는다. 이중 대괄호보다 "$"를 쓰는게 더 편하기 때문에 앞으로는 "$"로 통일하겠다.
추가로 length()함수로 길이를 확인해보면 다음과 같다.
length(lst[3])
# [1] 1
length(lst[[3]])
# [1] 4
length(lst$mat)
# [1] 4
lst[3]은 서랍 한칸을 의미하기에 길이가 1이지만 lst$mat은 서랍안의 내용물이기에 길이가 4이다.
(2) 리스트 펴기
unlist()이름처럼 list를 해제한다. 즉, 리스트의 모든 요소로 이루어진 벡터를 만들 수 있다.
unlist(lst)
# int vec1 vec2 vec3 vec4 mat1 mat2 mat3 mat4 char
# "1" "1" "2" "3" "4" "1" "2" "3" "4" "hi"
위 vector에서 설명했 듯 타입이 다른 데이터를 벡터로 만들면 제약이 가장 적은 데이터 타입으로 변환된다. 문자열이 있기때문에 모두 문자열로 타입이 변환됐다.
4. 데이터 프레임(Data frame)
데이터 프레임은 R에서 가장 중요한 데이터 타입이다. SQL을 배웠다면 데이터베이스의 테이블로 생각하면 이해가 쉽다. 행과 열로 이루어진 2차원 데이터타입이고 이때 행은 동일 실험단위(experimental unit) 혹은 관찰단위(observational unit)에서 얻어진 것이라 할 수 있고, 열은 열 객체의 속성을 의미한다. 데이터프레임의 한 행을 가르킬 때 case나 record라는 용어를 사용하기도 한다. 데이터베이스로 예를 들면 행은 튜플, 열은 스키마이다.
데이터프레임을 생성하고 조작하며 왜 다른 언어에는 없는 데이터 타입이 만들어 졌는지 생각해보고 리스트나 배열과 비교해보자.
(1) 데이터프레임 생성과 간단한 조작
데이터 프레임은 data.frame()을 이용해 생성할 수 있고 is.data.frame()으로 데이터프레임 자료형인지 확인할 수 있다.
weight = c(60, 70, 55, 91, 87, 66)
height = c(1.75, 1.77, 1.64, 1.83, 1.80, 1.73)
my.df = data.frame(height, weight)
my.df
# height weight
# 1 1.75 60
# 2 1.77 70
# 3 1.64 55
# 4 1.83 91
# 5 1.80 87
# 6 1.73 66
행은 한사람(객체) 열은 각 객체의 속성을 나타낸다. 위에서 말했던 동일 실험단위/ 관찰단위 라고 표현 했던 것이 이 뜻이다. R은 통계를 위한 언어이다. 데이터를 다루기 때문에 데이터베이스와 비슷한 구조가 생길 수 밖에 없다.
리스트와 마찬가지로 이름을 부여할 수도 있다.
my.df = data.frame(h=height, w=weight)
my.df
# h w
# 1 1.75 60
# 2 1.77 70
# 3 1.64 55
# 4 1.83 91
# 5 1.80 87
# 6 1.73 66
대괄호로 접근하면 데이터프레임 형태가 유지되고 $로 접근하면 벡터형태로 출력된다.
my.df[1]
# h
# 1 1.75
# 2 1.77
# 3 1.64
# 4 1.83
# 5 1.80
# 6 1.73
my.df$h
# [1] 1.75 1.77 1.64 1.83 1.80 1.73
(2) 데이터 프레임(date frame) vs 리스트(list)
데이터 프레임과 리스트는 모두 여러개의 벡터를 포함 할 수 있다. 하지만 몇가지 차이가 있다.
- 둘다 다른 타입의 벡터로 구성가능
- list는 서로 다른 크기의 벡터로 구성 가능하지만 data frame은 같은 크기여야한다.
- list는 행, 열 개념이 없다.
a = seq(1,6,2)
b = seq(2,6,2)
lst = list(a,b)
# [[1]]
# [1] 1 3 5
#
# [[2]]
# [1] 2 4 6
d.f = data.frame(a,b)
# a b
# 1 1 2
# 2 3 4
# 3 5 6
# 리스트는 행, 열의 개념이 없다.
lst[2,] # 리스트 2행 추출 시도
# Error in lst[2, ] : incorrect number of dimensions
d.f[2,] # 데이터 프레임 2행 추출
# a b
# 2 3 4
(3) 데이터 프레임(date frame) vs 행렬(matrix)
행렬은 모든 요소가 동일 타입이어야 한다. 하지만 데이터 프레임은 열벡터만 동일타입이어도 된다.
5. 펙터(Factor)
통계데이터에는 범주화 된 데이터가 많다. 펙터는 범주에 의미 있는 이름을 붙일 수 있는 데이터 구조다. python으로 설명하면 순서를 지정할 수 있는 set 로 생각할 수 있을 것 같다.
(1) (norminal) factor
예를들어 옷 사이즈를 나타내고자 한다면 factor를 사용하면 좋다.
size = c('small', 'x-large', 'medium', 'small','large','medium')
f.size = factor(size)
f.size
# [1] small x-large medium small large medium
# Levels: large medium small x-large
factor는 index와 level로 구성된다. unclass()로 index와 level을 확인 할 수 있다. as.numaric()으로 index를 얻을 수 있다.
unclass(f.size)
# [1] 3 4 2 3 1 2
# attr(,"levels")
# [1] "large" "medium" "small" "x-large"
as.numeric(f.size)
# [1] 3 4 2 3 1 2
level은 기본적으로 알파벳 순서이다.
table()은 각 level의 도수를 나타낸다.
table(f.size)
# f.size
# large medium small x-large
# 1 2 2 1
levels()는 level들을, nlevels()는 레벨의 수를 반환한다.
levels(f.size)
# [1] "large" "medium" "small" "x-large"
nlevels(f.size)
# [1] 4
levels는 해당 벡터의 level을 반환하고 unique는 유일한 값을 반환한다. 따라서 둘은 결과가 다를 수 있다.
fac = factor(letters[1:2], levels = letters[1:3])
levels(fac)
# [1] "a" "b" "c"
unique(fac)
# [1] a b
# Levels: a b c
(2) ordered factor
옷 사이즈도 ordinal scale이지만 예시를 위해 위에서는 그냥 nominal scale처럼 다뤘다. 이제 옷 사이즈에 순서를 부여해보자.
factor에 순서를 부여하려면 factor()함수의 ordered 매개변수를 이요하면 된다.
o.size = factor(size, levels = c("small", "medium", "large", "x-large"), ordered = T)
o.size
# [1] small x-large medium small large medium
# Levels: small < medium < large < x-large
unclass(o.size)
# [1] 1 4 2 1 3 2
# attr(,"levels")
# [1] "small" "medium" "large" "x-large"
(3) data frame과 factor
데이터 프레임에는 factor를 포함하는 경우가 많다.
d.f = data.frame(w=weight, h=height, size=o.size)
d.f
# w h size
# 1 60 1.75 small
# 2 70 1.77 x-large
# 3 55 1.64 medium
# 4 91 1.83 small
# 5 87 1.80 large
# 6 66 1.73 medium
split()함수를 이용하여 factor로 그루핑 할 수 있다. 이때 결과는 리스트이고 unsplit으로 다시 데이터프레임으로 합칠 수 있다.
d.f_split = split(d.f, d.f$size)
# $small
# w h size
# 1 60 1.75 small
# 4 91 1.83 small
#
# $medium
# w h size
# 3 55 1.64 medium
# 6 66 1.73 medium
#
# $large
# w h size
# 5 87 1.8 large
#
# $`x-large`
# w h size
# 2 70 1.77 x-large
d.f2 = unsplit(d.f_split, d.f$size)
열거형 다루기
글이 너무 길어져 indexing, 요소 접근 등 열거형데이터를 다루는 방법은 다음 포스트로 작성하겠다.
'Languages > R' 카테고리의 다른 글
[R] 인덱싱(Indexing) (0) | 2021.12.26 |
---|---|
[R] R 언어 훑어보기 (0) | 2021.10.31 |