R에서 결측치는 NA이다. 이것은 NULL값과 다르다. NA값은 하나의 값이 결측치라는 얘기이고, NULL은 NULL 객체를 뜻하며 객체를 정의되지 않은 상태로 만들 때 쓴다.
a=c(NA)
a
[1] NA
b=c()
b
NULL
NA값이 있을 경우 평균 등의 함수는 계산할 수 없으므로 Error를 내게 되며, 결측치를 빼고 계산하려면 na.rm=TRUE 옵션을 주고 계산하여야 한다.
a=c(1,2,3,NA,8,NA,10) # a에 2개의 결측치(누락된 값)가 있다.
mean(a)
[1] NA
mean(a,na.rm=TRUE) # NA값을 제거(remove)하고 계산
[1] 4.8
is.na() 함수는 객체의 원소 하나가 NA인 경우 참이 되며, 결측치의 개수를 세려면 sum(is.na(a))등과 같이 사용할 수 있다.
is.na(a)
[1] FALSE FALSE FALSE TRUE FALSE TRUE FALSE
sum(is.na(a)) # is.na() 함수의 값은 NA인 경우 1(즉 NA값의 개수를 센다).
[1] 2
a=c(a,NULL,8,NULL);a # NULL을 추가해도 에러는 나지 않지만 변화가 없다.
[1] 1 2 3 NA 8 NA 10 8
(a=c(a,NA)) # NA를 추가하면 결측치 데이터 하나가 추가된다.
[1] 1 2 3 NA 8 NA 10 8 NA
a=NULL은 a라는 객체 자체를 없애는 명령이다.
a=NULL # a라는 객체 자체를 없애는 명령이다.
a
NULL
NA와 NULL은 ==를 사용하여 비교할 수 없다. is.na()와 is.null() 을 사용한다.
a=NA
a==NA
[1] NA
is.na(a)
[1] TRUE
b<-NULL
b==NULL
logical(0)
is.null(b)
[1] TRUE
우리가 R을 배우는 목적은 데이터를 분석하고 그래프를 그리는 일을 R에게 시키기 위해서이다. R의 기능은 무궁무진하지만 우리가 무슨 일을 하고 싶어하는지는 알아내지 못한다. 또한 R이 한국 사람이 아니므로 우리나라 말로 일을 시키면 알아듣지 못한다. 따라서 우리가 R에게 일을 시키기 위해서는 R이 알아들을 수 있는 명령어로 명령을 내려야 한다. 이번 장에서는 R에게 일을 시키기 위한 기본 명령어(함수)들과 제어문(조건에 따라 다른 일 시키기)을 배워보기로 한다.
R에서 쓰는 명령어의 대부분은 함수로 되어있다. 앞장에서 본 변수 Height의 예를 들어보면 다음과 같다.
Height=c(168,173,160,145,180) # Height변수에 5명의 키 저장
result=mean(Height) # 평균
result
[1] 165.2
여기서 Height변수를 초기화할 때 사용한 c, 평균을 구할 때 쓴 mean, 결과를 표시할 때 쓴 print 등이 모두 함수이다. 평균을 구하는 mean을 예로 들면 함수로 내리는 명령은 평균을 구하라는 명령인데 함수의 이름은 mean이고 하는 일은 어떤 값을 가진 대상(객체)을 받아들여 평균값을 반환한다. 여기서는 함수에 Height라고 하는 numeric vector를 전달해 주었는데 함수에 전달해 주는 값을 인수라고 한다. 또한 함수는 어떤 심부름을 하면 꼭 무언가를 갖고 오는데 이를 반환값이라고 한다. mean 함수의 경우 평균값을 갖고 온다. 단 여기에는 제한이 있는데 하나의 함수는 한 번에 하나의 반환값만 가져올 수 있다. 이러한 제한을 극복하기 위해 반환할 값이 같은 종류의 데이터라면 벡터로 만들어 가져오면 되고, 만일 가져올 것이 많다면 큰 하나의 보따리에 여러 개를 넣어서 리스트로 만들어 하나의 리스트만 가져오면 된다.
R에는 쓸모 있는 함수가 아주 많지만 필요할 경우 새로운 함수를 만들어야 할 때가 있다. 이때는 함수의 이름을 정한 후 어떤 인수를 받아들일지, 어떤 결과를 반환할지 생각한 후 새롭게 함수를 정의하면 된다. 예를 들어 어떤 값의 세제곱을 반환하는 함수 mypower를 만든다면 다음과 같이 정의한다.
mypower=function(x){ # 새로운 함수 정의
result=c(x^3)
return(result) # 결과 반환
}
mypower(3)
[1] 27
위의 예를 보면 mypower라는 함수를 새로 정의하고 있는데, 이는 어떤 숫자를 받아들여 세제곱값을 result 에 넣어 결과를 반환해준다.
사실 우리가 쓰는 연산자도 함수이다. 다음의 예를 보자.
1+2
[1] 3
1+2와 같이 수학식에 쓰는 더하기, 빼기, 곱하기, 나누기 등도 사실은 함수이다. 연산자는 두 개의 인수를 받아들여서 결과를 반환하는 함수이다. 다음의 예를 보면 알수 있다.
'+'(1,2) # 1+2와 같다.
[1] 3
'*'(3,4) # 3*4와 같다.
[1] 12
'/'(9,2) # 9/2와 같다.
[1] 4.5
R에서 사용하는 명령어에는 함수 외에도 제어문이라는 것이 있다. 신호등을 예로 들어보자. 신호등이 녹색불이면 직진하고 좌회전 불이 들어오면 좌회전하고 빨간 불이 들어오면 정지한다. 즉 신호등은 교통의 흐름을 제어한다. R에서도 이렇게 흐름을 제어하는 명령들이 있는데 이를 제어문이라고 한다.
가장 많이 사용하는 제어문 중의 하나는 if… else …이다. 다음과 같이 쓴다.
if(조건문) {
... # 조건문이 참일때 실행할 명령들
...
} else {
... # 조건문이 거짓일때 실행할 명령들
...
}
예를 하나 들어보자. 앞서 만든 mypower 함수는 숫자를 받아들여 세제곱값을 반환하는데, 만일 숫자가 아닌 문자를 인수로 주면 어떻게 될까? 다음의 예를 보자.
mypower("moon")
Error in x^3: non-numeric argument to binary operator
위의 함수는 에러 메시지를 내게 된다. 이 함수를 개선시키기 위해 먼저 인수가 숫자인지 아닌지 판단해서 숫자이면 원래 하는 일을 하고 숫자가 아니면 NA값을 반환하게 하려면 다음과 같이 한다. 숫자인지 아닌지 판단하는 명령(함수)은 is.numeric()인데 숫자인 경우 1이 되고 숫자가 아니면 0이 된다.
# 개선된 mypower함수
mypower=function(x){ # 새로운 함수 정의
if(is.numeric(x)) { # 숫자인지 판단
result=c(x^3) # 숫자인 경우 세제곱 값 구함
}
else{
result=NA # 숫자가 아닌 경우 실행
}
result # 결과 반환
}
mypower(3)
[1] 27
mypower("moon")
[1] NA
이와 같이 개선된 mypower 함수는 인수가 숫자가 아닌 다른 값이 오더라도 에러가 나지 않는다.
if…else… 제어문 대신 쓸 수 있는 ifelse()함수가 있다. 이 함수는 if…else…제어문과 같은 기능을 하면서 실행속도가 훨씬 빠르기 때문에 가능하면 ifelse()함수를 쓰는 것을 권장한다. 다음과 같이 쓴다.
result=ifelse(조건문,참일 경우 값,거짓일 경우 값)
위의 mypower() 함수를 ifelse() 함수로 바꾸면 다음과 같다.
# ifelse()함수로 개선된 mypower함수
mypower2=function(x){
ifelse(is.numeric(x),c(x^3),NA)
}
mypower2(3)
[1] 27
mypower2("moon")
[1] NA
하지만 if…else… 구문과 달리 ifelse 함수는 하나의 값만 반환한다. 예를 들어 mypower() 함수와 mypower2()함수에 숫자를 하나 넣을 때와 숫자의 벡터를 넣을때 값이 달라진다.
x=3
mypower(x)
[1] 27
mypower2(x)
[1] 27
x=c(2,3,4)
mypower(x)
[1] 8 27 64
mypower2(x)
[1] 8
역시 ifelse함수는 하나의 값만 반환된다. 여러 개의 값을 반환하기를 원할 경우 if…else… 구문을 써야 하고 하나의 값만 필요한 경우 ifelse함수를 쓰는 것이 좋다.
반복문 중 대표적인 것은 for를 이용한 반복문이다. 다음과 같이 쓴다.
for ( 루프변수 in 리스트) {
... # 반복할 구문
...
}
위의 for 구문은 루프변수가 리스트에 있는 첫 번째 값부터 마지막 값까지 변하면서 리스트의 길이만큼 반복된다. 예를 들어 계승(팩토리얼)을 구할 때는 다음과 같이 한다.
myfact=function(x) {
result=1
for(i in 1:x) {
result=result*i
}
result
}
myfact(3)
[1] 6
myfact(5)
[1] 120
for 루프의 리스트에는 벡터, 함수 등을 지정할 수도 있다. 다음은 for 루프를 이용하여 plot() 함수에 인수를 전달하여 그래프를 그리고 있다.
par(mfrow=c(2,2))
x=list(1:6, sin, function(x) {x^2+2*x}, dnorm)
for(i in x) plot(i,xlim=c(0,2*pi))
par(mfrow=c(1,1))
같은 반복이라도 어떤 조건이 성립할 동안 계속 어떤 계산을 반복할 때는 while() 문을 사용하는 것이 좋다. 단 조건식이 계속 TRUE인 경우 프로그램이 무한 반복되므로 주의하여야 한다. 또한 조건식이 처음부터 거짓이라면 한 번도 실행되지 않는다.
while (조건식) { # 조건이 만족할 동안
... # 반복할 구문
... # 조건식이 처음부터 거짓이라면 한 번도 실행되지 않는다.
}
다음의 예를 보자.
i=1
while(i<5) {
i=i+1
}
i
[1] 5
처음 i는 1이었으며 while 문을 안의 i=i+1이 반복되어 결국 i의 값이 5가 되었다.
for나 while 등 반복문을 실행하는 도중에 break를 만나면 강제로 반복문에서 탈출한다. 예를 들어 조금 전의 예제를 break 문을 써서 다음과 같이 할 수 있다.
i=1
while(1) { # 항상 실행
if(i==5) break # i가 5면 반복문에서 탈출
i=i+1
}
i
[1] 5
for나 while 등 반복문을 실행하는 도중에 next를 만나면 강제로 다음 반복으로 넘어간다. 예를 들어 1부터 100까지 홀수의 합을 구하고 싶으면 다음과 같이 하면 된다.
oddSum=0 # 홀수의 합을 저장할 변수, 0으로 초기화
for(i in 1:100) { # 1부터 100까지 반복
if(i%%2==0) next # 짝수(2로 나눈 나머지가 0)이면 다음 반복으로 넘어간다.
oddSum=oddSum+i
}
oddSum
[1] 2500
repeat 문을 사용하면 식이 무제한으로 반복된다. while(1)과 같다.
i=1
repeat { # 항상 실행
if(i==5) break # i가 5면 반복문에서 탈출
i=i+1
}
i
[1] 5
(문제) 학생 10명이 있다.영어 시험 결과를 eng, 수학 시험 결과를 math라고 하자. 영어시험은 2번과 9번 학생이 결시하였고 9번 학생이 수학시험을 결시하였다. R에서 결측치는 NA이다.
번호=1:10
영어=c(70,NA,80,85,90,90,60,55,NA,90)
수학=c(80,73,84,90,95,85,60,50,NA,100)
data=data.frame(번호,영어,수학)
data
번호 영어 수학
1 1 70 80
2 2 NA 73
3 3 80 84
4 4 85 90
5 5 90 95
6 6 90 85
7 7 60 60
8 8 55 50
9 9 NA NA
10 10 90 100
영어와 수학시험의 결시자가 몇 명인지 계산하고 결시자를 뺀 평균을 계산하여 다음과 같이 보고하라.
# A tibble: 2 x 5
과목 총원 결시자 총점 평균
<chr> <int> <int> <dbl> <dbl>
1 수학 10 1 717 79.66667
2 영어 10 2 620 77.50000
(답)
require(tidyverse)
data %>%
gather(key="과목",value="성적",영어,수학) %>%
group_by(과목) %>%
summarize(
총원=n(),
결시자=sum(is.na(성적)),
총점=sum(성적,na.rm=TRUE),
평균=mean(성적,na.rm=TRUE)
)
위의 성적 data를 사용하여 누락된 값을 각 과목의 평균치로 채워 넣으세요.
(힌트) is.na()와 apply()함수를 사용하세요.