다음의 내용은 Hadley Wickahm이 쓴 Advanced R 책 중의 functional programming 챕터의 도입부에 있는 내용이다.
먼저 다음과같은 데이타가 있다고 하자. 이 들 데이타 중 누락된 값은 -99로 표기되어 있고 일부는 또 입력시 실수해서(타이프를 잘못해서) -999로 기록되어 있다고 가정한다. 데이타의 처리를 위해 -99 또는 -999의 값을 NA로 전부 바꾸고 싶다.
set.seed(1014)
df=data.frame(replicate(6,sample(c(1:10,-99,-999),6,rep=TRUE)))
names(df)=letters[1:6]
df
## a b c d e f
## 1 1 6 1 5 -999 1
## 2 -99 4 4 -999 9 3
## 3 8 9 5 4 1 4
## 4 2 10 3 9 7 8
## 5 1 -99 5 9 9 6
## 6 6 3 1 3 9 6
이런 경우 먼저 데이타 프레임의 열 데이타 중 -99를 NA 로 바꾼다면 다음과 같은 코드를 쓸수 있다.
df$a[df$a==-99]<-NA
이후 복사하여 붙여넣기(copy-and-paste) 신공을 발휘하여 다음과 같이 코드를 입력할 수 있지만
df$a[df$a==-99]<-NA
df$b[df$b==-99]<-NA
df$c[df$c==-98]<-NA
df$d[df$d==-99]<-NA
df$e[df$e==-99]<-NA
df$f[df$g==-99]<-NA
이와 같이 붙여넣기를 할때의 문제 중 하나는 실수를 할수가 있다는 것이다 위의 코드에도 두개의 실수가 있다. 하나는 -99를 -98로 입력한 것이고 또 하나는 df$f줄에 보면 df$g로 잘못입력 한 것이다. 데이타의 양이 많아질수록 이러한 실수를 하기 쉽다. 또한 missing value의 값이 -99가 아니라 9999로 바뀐다면 일일이 다시 타이핑을 해야 한다.
이러한 버그를 예방하고 보다 유연한 코드를 만들기 위해 “Do not repeat yourself” 즉 DRY 원칙을 적용해보자. 먼저 다음과 같은 함수를 하나 만들어 본다.
fix_missing=function(x){
x[x==-99]<-NA
x
}
df$a=fix_missing(df$a)
df$b=fix_missing(df$b)
df$c=fix_missing(df$c)
df$d=fix_missing(df$d)
df$e=fix_missing(df$e)
df$f=fix_missing(df$e)
이 방법을 통해 -99를 -98로 미스타이프할 우려는 줄었지만 여전히 실수할 여지가 있다. 마지막 줄에 보면 또 df$f 다음에 함수의 인자로 df$g를 잘못썼다. 실수를 줄일수 있는 다음 단계는 두개의 함수를 조합하여 쓰는 것이다. fix_missing은 한개의 열에서 에러를 고치는 함수이고 laaply함수는 데이타프레임의 전체 열에 대하여 어떤일을 하는 함수이다. 따라서 두개의 함수를 같이 사용하면 다음과 같이 코드를 만들수 있다.
fix_missing=function(x){
x[x==-99]<-NA
x
}
df[]=lapply(df,fix_missing)
위와같은 코드는 처음의 copy-paste방법에 비해 다섯가지의 잇점이 있다.
df[1:5]=lapply(df[1:5],fix_missing)
위와같이 데이타의 일부 서브셋에만 함수를 적용하는 것이 가능하다.
만일 다른 열에는 -99가 아니라 -999, 또는 -9999가 쓰였다면 어떻게 할까? 다시한번 복사신공을 발휘하면
fix_missing_99=function(x){
x[x==-99]<-NA
x
}
fix_missing_999=function(x){
x[x==-999]<-NA
x
}
fix_missing_9999=function(x){
x[x==-999]<-NA
x
}
여기서 또 버그를 만들었다. 마지막 줄에 -9999를 -999로 잘못 입력한 것이다. 이렇게 함수를 여러개 만들 것이 아니라 NA 값을 지정하는 인수를 하나 만들어 fix_missing함수에 전달해주면 해결할 수 있지 않을까?
fix_missing <- function(x,na.value){
x[x==na.value]<-NA
x
}
이와 관련된 문제를 하나 풀어보기로 하자. 데이타 cleaning이 모두 끝났다고 가정하고
각 변수에 대한 수치적인 summary를 다음과 같이 하고자 한다.
df=data.frame(replicate(6,sample(c(1:10),6,rep=TRUE)))
names(df)=letters[1:6]
df
## a b c d e f
## 1 8 7 10 5 9 10
## 2 10 7 4 8 3 6
## 3 2 1 5 3 6 6
## 4 3 8 4 8 4 3
## 5 7 8 2 1 6 5
## 6 5 10 6 4 2 4
mean(df$a)
median(df$a)
sd(df$a)
mad(df$a)
IQR(df$a)
mean(df$b)
median(df$b)
sd(df$b)
mad(df$b)
IQR(df$b)
이와같은 작업을 여러 열에 대해 계속 한다면 매우 중복된 작업이 된다. 어떻게 해결할 수 있을까? 계속 읽기 전에 1-2분만 생각해보자
num_summary <- function(x) {
c(mean(x),median(x),sd(x),mad(x),IQR(x))
}
lapply(df,num_summary)
## $a
## [1] 5.833 6.000 3.061 3.706 4.250
##
## $b
## [1] 6.8333 7.5000 3.0605 0.7413 1.0000
##
## $c
## [1] 5.167 4.500 2.714 1.483 1.750
##
## $d
## [1] 4.833 4.500 2.787 3.706 4.000
##
## $e
## [1] 5.000 5.000 2.530 2.224 2.750
##
## $f
## [1] 5.667 5.500 2.422 1.483 1.750
이와 같은 함수를 생각했다면 아주 좋은 출발이다. 하지만 아직 약간의 중복이 있다. NA값을 처리하는 na.rm=TRUE를 인수로 준다면 중복이 심하다는 것을 알 수 있다.
num_summary <- function(x) {
c(mean(x,na.rm=TRUE),
median(x,na.rm=TRUE),
sd(x,na.rm=TRUE),
mad(x,na.rm=TRUE),
IQR(x,na.rm=TRUE))
}
lapply(df,num_summary)
## $a
## [1] 5.833 6.000 3.061 3.706 4.250
##
## $b
## [1] 6.8333 7.5000 3.0605 0.7413 1.0000
##
## $c
## [1] 5.167 4.500 2.714 1.483 1.750
##
## $d
## [1] 4.833 4.500 2.787 3.706 4.000
##
## $e
## [1] 5.000 5.000 2.530 2.224 2.750
##
## $f
## [1] 5.667 5.500 2.422 1.483 1.750
다섯개의 함수가 모두 같은 인수(x,na.rm=TRUE)를 갖고 있어 다섯번 중복된다. 이와 같은 중복은 버그의 위험성이 있고 유지 보수를 어렵게 한다. 이와 같은 중복을 피하는 방법은 다음과 같다. 즉, 함수를 리스트에 저장하는 것이다.
num_summary=function(x) {
funs=c(mean,median,sd,mad,IQR)
lapply(funs,function(f) f(x,na.rm=TRUE))
}