학습 목표


객체 생성

콘솔창에 수학연산을 타이핑해서 출력값을 얻을 수 있다.

3 + 5
## [1] 8
12/7
## [1] 1.714286

하지만, 유용하고 흥미로운 작업을 수행하려면, _객체(object)_에 _값(value)_을 대입할 필요가 있다. 객체를 생성하려면, 객체명을 부여하고 나서 대입연산자 <-를 타이핑하고 나서 객체에 부여할 값을 타이핑한다:

weight_kg <- 55

객체는 x, current_temperature, subject_id 처럼 어떤 명칭도 부여될 수 있다. 객체명을 명시적이고 너무 길지 않게 부여한다. 하지만, 숫자로 시작하면 안된다(2x는 부적합, 하지만 x2은 적합). R은 대소문자를 구별한다(즉, weight_kgWeight_kg와 다르다). 일부 명칭은 사용될 수 없는데 이유는 R에 내장된 기본적인 함수명이기 때문이다 (즉, if, else, for, 자세한 내용은 R 예약어를 참조한다). 일반적으로, 허락되기도 하지만, 다른 함수명은 사용하지 않는 것이 최선이다 (즉, c, T, mean, data, df, weights). 변수명 사용이 꺼름직하면, 도움말을 확인해서 명칭이 이미 점유되었는지 확인한다. 또한, . 점표기법도 변수명내부에 my.dataset처럼 사용하는 것을 피한다. 역사적인 이유로 함수명에 점을 갖는 함수가 많이 존재하지만, 점표기법은 R과 다른 프로그래밍 언어에서 메쏘드(method)로 특수한 의미를 갖기 때문에, 가능하면 피하는 것이 최선이다. 변수명에는 명사를 함수명에는 동사를 사용하는 것을 추천한다. 코드를 작성할 때 스타일에 일관성(공백 위치, 변수명 등)을 유지하는 것이 중요하다. R에는 두가지 일반적인 코딩 스타일 지침이 존재한다: Hadley Wickham’s, Google’s.

값을 객체에 대입할 때 R은 어떤 것도 출력하지 않는다. 값을 출력하려면 괄호로 감싸거나 객체명을 타이핑한다:

weight_kg <- 55    # 출력결과 없음.
(weight_kg <- 55)  # 괄호로 감싸게 되면 `weight_kg` 값을 출력함.
## [1] 55
weight_kg          # 객체명을 타이핑하면 출력됨
## [1] 55

weight_kg 객체가 메모리에 올라갔기 때문에, 사칙연산을 수행할 수 있다. 예를 들어 파운드 무게를 변환할 수 있다(파운드 무게는 kg에 2.2 를 곱하면 된다):

2.2 * weight_kg
## [1] 121

변수에 저장된 값을 변경하려면 변수에 새로운 값을 대입한다:

weight_kg <- 57.5
2.2 * weight_kg
## [1] 126.5

특정 변수에 값을 대입하게 되면 다른 변수의 값은 변경되지 않는 것을 의미한다. 예를 들어 새로운 변수로 파운드 단위를 갖는 동물 무게를 저장한다, weight_lb:

weight_lb <- 2.2 * weight_kg

그리고 나서, weight_kg 를 100 으로 변경시킨다.

weight_kg <- 100

객체 weight_lb의 현재 담긴 정보는 어떻게 될까? 126.5 혹은 200?

도전과제

다음 명령어를 각각 실행시킨 뒤에 값은 각각 얼마일까?

mass <- 47.5            # mass?
age  <- 122             # age?
mass <- mass * 2.0      # mass?
age  <- age - 20        # age?
mass_index <- mass/age  # mass_index?

벡터와 자료형

벡터는 R에서 가장 흔하고 기본적인 자료구조로 R의 상당부분 업무를 처리한다. 벡터는 값을 그룹으로 묶은 것으로 주로 숫자거나 문자다. 한 항목에 대해 수행했듯이, 이런 값목록을 변수에 대입한다. 예를 들어, 다음과 같이 동물 무게를 측정한 벡터를 생성할 수 있다:

weight_g <- c(50, 60, 65, 82)
weight_g
## [1] 50 60 65 82

벡터는 문자를 담을 수도 있다:

animals <- c("mouse", "rat", "dog")
animals
## [1] "mouse" "rat"   "dog"

벡터에 담긴 내용물을 검사할 수 있는 함수가 많다. length() 함수는 특정 벡터에 얼마나 많은 원소가 담겨있는지 일러준다:

length(weight_g)
## [1] 4
length(animals)
## [1] 3

벡터의 중요한 특징은 모든 원소가 동일한 자료형이라는 점이다. class() 함수는 객체에 대한 클래스(원소 자료형)를 확인하는 데 사용된다:

class(weight_g)
## [1] "numeric"
class(animals)
## [1] "character"

str() 함수는 객체에 대한 개요와 객체에 담긴 원소정보를 제공한다. 매우 크고 복잡한 객체를 작업할 때 정말 유용한 함수다:

str(weight_g)
##  num [1:4] 50 60 65 82
str(animals)
##  chr [1:3] "mouse" "rat" "dog"

원소를 벡터에 추가할 때 c() 함수를 사용한다:

weight_g <- c(weight_g, 90) # 벡터 끝에 원소를 추가한다.
weight_g <- c(30, weight_g) # 벡터 시작점에 원소를 추가한다.
weight_g
## [1] 30 50 60 65 82 90

앞서 작성한 것을 설명하면, 최초 weight_g 벡터를 잡아 끝에 원소를 하나 추가하고 나서 시작점에 또다른 원소를 추가했다. 벡터를 키워 나가거나 데이터셋을 조합하는데 반복적으로 이런 작업을 수행한다. 프로그래밍을 하면서 계산을 하거나 수집을 하는 방법으로 결과를 추가하는 유용하다.

지금까지 R에서 사용되는 6가지 원자벡터(atomic vector) 중에서 두가지를 살펴봤다: 문자형("character") 와 숫자형("numeric"). 모든 R 객체를 만들어내는 기본적인 레고 블록이고, 나머지 4개는 다음과 같다:

벡터는 R에서 사용되는 가장 흔한 자료구조(data structure) 중 하나. 중요하게 다뤄지는 다른 자료구조는 리스트(list), 행렬(matrix), 데이터프레임(data.frame), 요인(factor)이 있음.

도전과제

  • 질문: 원자벡터는 문자형, 숫자형, 정수형, 논리형이 가능함을 살펴봤다. 그런데 벡터 하나에 다양한 자료형을 뒤섞으면 어떻게 될까?

  • 질문: 다음 예제를 실행 시키면 자료형은 어떻게 변하게 될까? (힌트: class() 명령어를 사용해서 객체 자료형을 검사한다):

num_char <- c(1, 2, 3, 'a')
num_logical <- c(1, 2, 3, TRUE)
char_logical <- c('a', 'b', 'c', TRUE)
tricky <- c(1, 2, 3, '4')
  • Question: 변형된 자료형에 대해 이유를 설명할 수 있는가?

  • 질문: 자료구조의 위계를 도식적으로 표현할 수 있는가?

벡터 부분집합 뽑아내기

벡터에서 값을 하나 혹은 다수 값을 추출하려면, 꺾쇠 괄호 내부에 인덱스 하나 혹은 다수 인덱스를 넣어 뽑아낸다. 예를 들어:

animals <- c("mouse", "rat", "dog", "cat")
animals[2]
## [1] "rat"
animals[c(3, 2)]
## [1] "dog" "rat"

인덱스를 반복해서 원벡터에서 더 많은 원소를 갖는 객체를 생성할 수도 있다:

more_animals <- animals[c(1, 2, 3, 2, 1, 4)]
more_animals
## [1] "mouse" "rat"   "dog"   "rat"   "mouse" "cat"

R 인덱스는 1에서 시작된다. 포트란, 매트랩, R 프로그래밍 언어는 1 에서 인덱스를 시작하는데, 이유는 일반적으로 사람과 동일한 방식을 취한다. 프로그래밍 언어가 C 가족(C++, 자바, 펄(Perl), 파이썬)에 포함된 언어는 0 에서 시작하는데, 이유는 컴퓨터가 작업하는데 쉽기 때문이다.

조건을 활용한 부분집합 뽑아내기

부분집합을 뽑아내는 다르게 흔히 사용되는 방식은 논리 벡터를 활용한 것이다: TRUE값을 갖는 인덱스는 뽑아내고, FALSE 값을 갖는 인덱스는 추출하지 않는다.

weight_g <- c(21, 34, 39, 54, 55)
weight_g[c(TRUE, FALSE, TRUE, TRUE, FALSE)]
## [1] 21 39 54

일반적으로, 이런 논리형 벡터를 손으로 일일이 타이핑하지는 않고, 다른 함수나 논리적 테스트 결과로 나온 것을 받아 사용한다. 예를 들어, 무게가 50 이상되는 것만 추출하려면 다음과 같이 명령식을 구성한다:

weight_g > 50    # 조건이 만족되는 인덱스에 대해서만 TRUE를 갖는 논리벡터를 반환한다.
## [1] FALSE FALSE FALSE  TRUE  TRUE
## 그래서, 이를 활용하여 50 이상이 되는 값만 선택한다.
weight_g[weight_g > 50]
## [1] 54 55

& (양쪽 조건이 모두 참, AND) 혹은 | (양쪽 조건 중 적어도 하나는 참, OR)을 조합해서 다수 테스트를 조합한다:

weight_g[weight_g < 30 | weight_g > 50]
## [1] 21 54 55
weight_g[weight_g >= 30 & weight_g == 21]
## numeric(0)

문자형 벡터를 작업할 때, 다수 조건식을 조합하려면, 타이핑하는 것이 너무 많다. %in% 함수를 사용해서 특정 값이 벡터에 포함되어 있는지 테스트하면 편리하다:

animals <- c("mouse", "rat", "dog", "cat")
animals[animals == "cat" | animals == "rat"] # rat, cat을 반환
## [1] "rat" "cat"
animals %in% c("rat", "cat", "dog", "duck")
## [1] FALSE  TRUE  TRUE  TRUE
animals[animals %in% c("rat", "cat", "dog", "duck")]
## [1] "rat" "dog" "cat"

도전과제

  • "four" > "five" 표현식이 왜 TRUE를 반환할까요?

결측 데이터

R은 데이터셋을 분석하도록 설계되어서, 결측 데이터 개념이 포함되어 있다 (다른 프로그래밍 언어에서는 일반적이지 않음). 결측 데이터를 벡터로 표현하면 NA가 된다.

planets <- c("Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus",
             "Neptune", NA)

숫자에 연산작업을 수행할 때, 데이터에 결측값이 포함되어 작업이 수행되면 NA가 반환된다. 이런 작업결과 반환이 더 안전한 기계적 움직임이 되는데 이유는 그렇지 않고 조용히 넘어가면 결측값을 포함해서 처리하는지 알수 없기 때문이다. na.rm=TRUE 인자를 추가하면, 결측값을 무시하고 결과를 계산하게 된다.

heights <- c(2, 4, 4, NA, 6)
mean(heights)
## [1] NA
max(heights)
## [1] NA
mean(heights, na.rm = TRUE)
## [1] 4
max(heights, na.rm = TRUE)
## [1] 6

데이터에 결측값이 포함되어 있다면, is.na(), na.omit(), complete.cases() 함수와 곧 친숙하게 된다. 예제로 아래를 참조한다.

## 결측값이 없는 원소만 추출한다.
heights[!is.na(heights)]
## [1] 2 4 4 6
## 완비되지 않는 사례는 제거된 객체를 반환한다. 반환된 객체는 원자형.
na.omit(heights)
## [1] 2 4 4 6
## attr(,"na.action")
## [1] 4
## attr(,"class")
## [1] "omit"
## 완비된 사례만 갖는 원소를 추출한다.
heights[complete.cases(heights)]
## [1] 2 4 4 6

도전과제

  • 질문: 다음 코드는 왜 오류 메시지가 출력이 될까요?
sample <- c(2, 4, 4, "NA", 6)
mean(sample, na.rm = TRUE)
## Warning in mean.default(sample, na.rm = TRUE): argument is not numeric or
## logical: returning NA
## [1] NA
  • 질문: 오류 메시지에 왜 인자가 숫자형이 아니라고 언급되었을까요?

다음 시간에, “surveys” 데이터셋을 사용해서 data.frame 자료구조를 탐색하게 된다. 데이터프레임은 가장 흔한 자료구조 R 객체 중 하나다.