Lovetoken

저는 개발 취향을 가진 데이터 분석가 Jr. 입니다.

Navigation
 » Home
 » About Me
 » Github

R에서 all.equal() 함수를 이용한 객체의 동일성 확인하기

02 Jan 2018 » R



수학공부 중 종종 이런일이 많았다.

원 공식에 의해 계산이 불가하거나 손으로 풀기 복잡할 경우 근사치에 대한 간편공식을 배우게 되는 경우가 있다.
그 간편공식으로써 풀은 결과물이 원 공식으로 풀은 결과물과 동일한지 비교해보는 작업을 R을 이용하여 자주 하곤 했다.
만약 그 결과물이 어떤 하나의 스칼라값으로 나온다면,
금방 비교하여 동일한지 여부를 파악할 수 있다.
R을 이용한다면 논리연산자를 이용해 빠르고 정확하게 비교가 가능할 것이다.
== 의 기호는 데이터셋의 원소별로 동일하면 TRUE, 틀리면 FALSE 를 반환하게 되므로 좌변과 우변이 같은지를 아래처럼 확인할 수 있다.

2/3 == 804/1206
## [1] TRUE

위의 예시처럼 쉽게 같은지 구분하기 힘든것을 논리연산자를 통해 동일한지 파악할 수 있다.



all.equal() 함수

그런데 이러한 논리연산자 말고, R에서는 오늘 알아볼 all.equal() 이란 함수가 있다.
이 함수의 쓰임은 비교하고자 하는 2개의 R객체가 동일한지를 확인1해 준다.

가끔, 아니 곧잘 대용량의 크기가 큰 데이터셋의 동일여부를 파악해야 할 때도 있을것이다.
아래와 같은 상황을 예로 들어 보겠다.

## 일반 행렬곱셈
A <- matrix(round(rnorm(600 * 400)), nrow = 600, ncol = 400)
B <- matrix(round(rnorm(400 * 800)), nrow = 400, ncol = 800)
AB <- A %*% B

## 분할행렬 생성
A11 <- A[1:200, 1:200]
A12 <- A[1:200, 201:400]
A21 <- A[201:600, 1:200]
A22 <- A[201:600, 201:400]

B11 <- B[1:200, 1:200]
B12 <- B[1:200, 201:800]
B21 <- B[201:400, 1:200]
B22 <- B[201:400, 201:800]

partition_AB <- rbind(
  cbind(A11 %*% B11 + A12 %*% B21, A11 %*% B12 + A12 %*% B22),
  cbind(A21 %*% B11 + A22 %*% B21, A21 %*% B12 + A22 %*% B22)
  ) # 분할행렬 생성 후 행렬곱셈

위의 코드가 무엇인지 간략하게 설명하면
첫번째는 임의의 행렬 A와 행렬 B를 생성시킨 후, 두 행렬의 곱셈을 AB로 할당한 것이며,
두번째는 행렬 A, B 를 이와 같이 알맞게 분할한 후



A11, B11 은 200×200 행렬


로 분할한 행렬을 이용하여 행렬 A, B를 곱한 것 이다.



이처럼 곱한것을 객체 partition_AB로 할당하였다.

이론적으로는 첫번째 일반적인 곱셈과, 두번째 분할행렬을 이용한 곱셈과 값이 동일하다.
단 그것이 믿겨지지 않을 경우 직접 확인해 보고싶을 수 있다.
그런데 이때 애로사항이 있다.
ABpartition_AB 가 서로 동일하느냐 를 확인하기 위해선
두 행렬의 사이즈가 600×800 으로 매우 부담이 되는 크기이다.

물론 논리연산자 == 를 써도 되겠지만, 문제는 이 논리연산자를 써서 아래 코드를 실행하게 될 경우

AB == partition_AB

행렬의 원소별 값이 동일한지에 대한 논리값을 600 × 800 = 480,000 개를 행렬 타입으로 거대하게 반환할 것이다. 거대하므로 눈으로 동일한지 확인이 어려울 것이다.
480,000개의 원소가 모두 TRUE 인지를 확인하기 위해

all(AB == partition_AB)
## [1] TRUE

처럼 all() 함수의 도움을 받을 수도 있겠지만
all.equal() 함수를 사용하는것이 더 유용할 때가 있다.

all.equal(AB, partition_AB)
## [1] TRUE

함수이름 그대로 all equal 하면 TRUE
그렇지 않을 경우 어디가 어떻게 틀린지 구체적으로 설명한다.
본 예제의 경우 원래의 행렬곱과 분할행렬의 곱이 같은경우의 예제이므로 TRUE 를 반환한다.

all.equal() 함수는 객체가 동일한지의 판단하는 함수로써도 편리한 기능의 함수이지만,
기본적으로는 얼마나 가깝게 동일한지 정도를 확인할 수 있는 일종의 비교함수로 보고 사용하는것이 좋다.
함수 도움말의 제목에서도 'Test if Two Objects are (Nearly) Equal' 로 나와있고 괄호의 "Nearly" 가 눈에 띈다.



조금 더 체감하기 좋은 행렬의 연산예시를 들어보겠다.
R에서는 순환소수, 무한소수를 계산할 때, 즉 유리수가 아닌 무리수를 연산할 때 아주 미세한 값의 차이가 존재할 수 있다.2
하지만 이는 무리수를 굳이 소수점으로 표현하여 어떤 소수점자리 이후는 절단되어 버려지는 시스템 적인 문제일 뿐이다.

아래 예시는 임의행렬 X와 그의 역행렬을 곱한것을 단위행렬과 비교해본 시뮬레이션이다.
행렬 X는 5×5 행렬이며 이론상으로는



이 성립한다. 하지만

X <- matrix(rnorm(25), 5, 5)
XtX <- solve(X) %*% X

I_5 <- diag(1, 5)

XtX == I_5
##       [,1]  [,2]  [,3]  [,4]  [,5]
## [1,] FALSE FALSE FALSE FALSE FALSE
## [2,] FALSE FALSE FALSE FALSE FALSE
## [3,] FALSE FALSE FALSE  TRUE FALSE
## [4,] FALSE FALSE FALSE  TRUE  TRUE
## [5,] FALSE FALSE FALSE FALSE FALSE

결과는 여기저기서 FALSE 가 있고 실제로 값이 다르다. 전부 TRUE 이어야 하지만

table(XtX == I_5)
## 
## FALSE  TRUE 
##    22     3

이처럼 3개만 TRUE 로 인식한다.
이처럼 무리수를 컴퓨터가 정확하게 표현할 수 없기 때문에 일어나는 일로 XtX 나, I_5 를 같지 않다고 판단하는것은 상황에 따라서 용인해야할 때가 있다.
이때 방금 언급한 all.equal() 함수를 사용해 보면 어떨까?

all.equal(XtX, I_5)
## [1] TRUE

all.equal() 함수 내에는 tolerance 인자가 존재하여 어느 정도의 차이는 단어 뜻대로 관용, 아량을 베풀어주는 로직이 있다.
따라서 XtX 와, I_5 는 그정도 차이는 무시하고 서로 동일하다고 판단하여 TRUE 를 반환한다.
만약 관용, 아량을 전혀 베풀지 않는(tolerance = 0)다면

all.equal(XtX, I_5, tolerance = 0)
## [1] "Mean relative difference: 2.806783e-15"

두 객체간 차이의 아주 정확한 값을 반환한다.
분석을 하다보면 위의 예제 말고도 동등성을 확인하여야 할 때가 사뭇 많다.
이럴 때 all.equal() 함수는 유용하게 사용될 수 있을것이다.


  1. 객체간 똑같은지 확인하는 함수로는 identical() 도 있다

  2. 이는 부동소수점으로 인한 문제이다