2019년 2월 3일 일요일

[하루에 한 R] Key/value 형태의 자료를 이용한 치환

Perl에서는 hash라는 자료형을 이용하여 key/value 형태의 자료를 다룬다.


$color_of{apple} = 'red';
# initialize a hash
my %color_of = (
    "apple"  => "red",
    "orange" => "orange",
    "grape"  => "purple",
);

R에서는 이와 유사한 자료 구조로서 list가 있지만 hash보다는 훨씬 복잡한 구조의 데이터를 담을 수 있다. 예를 들어서 list_data = list("red","green",c(1,2,4), TRUE,13.43)의 사례에서 볼 수 있듯이 문자열, 숫자, 벡터, 논리값 등 어떤 타입의 원소든지 담을 수 있다. 물론 Perl의 hash에서도 참조(reference)를 이용하면 어떤 자료형이든 scalar로 전환되므로 hash의 값으로 저장할 수 있다. Perl의 hash에서는 각 원소에 접근하려면 key를 사용해야 되지만, list는 훨씬 다양한 방법으로 인덱싱하여 접근 혹은 슬라이싱이 가능하다.

이번 글에서는 R에서 key/value 목록을 사용하여 간단하게 데이터를 변환하는 방법을 알아보고자 한다. 알파벳 소문자를 NATO 음성 문자(NATO phonetic alphabet)으로 변환하는 아래의 사례에서는 list를 쓰지 않고도 named vector를 사용하고 있다. 특정 key에 해당하는 value를 확인하려면 names(var)[var=="key"] 구문을 사용하라. 언뜻 난해해 보이지만 이해하면 짧고도 아름다운(?) 구문이다.

> abcde = c("a","b","c","d","e")
> nato.abcde = c("Alpha","Bravo","Charlie","Delta","Echo")
> names(abcde) = nato.abcde
> abcde
  Alpha   Bravo Charlie   Delta    Echo 
    "a"     "b"     "c"     "d"     "e" 
> names(abcde)[abcde=="a"]
[1] "Alpha"
> data = c("e","e","a","b","c","d")
> for(i in data){print(names(abcde)[abcde==i])}
[1] "Echo"
[1] "Echo"
[1] "Alpha"
[1] "Bravo"
[1] "Charlie"
[1] "Delta"

내용상으로 value에 해당하는 것이 names() 함수로 불리워지는 것이 좀 어색하다. 왠지 names()로 지정 또는 반환되는 값은 key에 해당하는 것이 더 자연스러울 것이라는 생각이 든다. 다시 말해서 "a"를 입력했을 때 "Alpha"를 얻고자 한다면, 다음 중 어느 것이 더 자연스럽겠는가? 두 경우에서 myVar 벡터의 구조는 서로 정반대이다.

  1. result = myVar["a"] 
  2. result = names(myVar)[myVar=="a"]

두말할 나위도 없이 (1)번이 더 직관적이다. 하지만 이 글의 윗부분에서 든 사례에서는 (2)의 방식을 택하고 있다. 어떤 방식이 더 낫다고는 할 수 없다. 다만 어떤 벡터 원소의 이름은 그 원소의 속성 중 하나라고 이해하면 (2)의 방법도 수긍이 간다. 좀 더 현명한 방법은 아래와 같이 list를 사용하는 것이다. setNames() 함수를 사용하면 리스트를 생성하면서 동시에 이름을 부여할 수 있다(괄호 안에서 as.list() 함수를 쓰지 않으면 단순한 named vector가 된다). 여기에서는 names()가 가리키는 방향이 위의 named vector의 사례와는 반대임을 유의해야 한다. 즉, key에 해당하는 것을 가리킨다.

> myList = setNames(as.list(nato.abcde),abcde)
# myList = list(a="Alpha",b="Bravo",c="Charlie",d="Delta",e="Echo")
# add a new key/value element > myList$f = "Foxtrot" > myList[["h"]] = "golf" > names(myList)[names(myList)=="h"] = "g" > for (i in ls(myList)) {print(myList[[i]])} [1] "Alpha" [1] "Bravo" [1] "Charlie" [1] "Delta" [1] "Echo" [1] "Foxtrot" [1] "golf" > for (i in data) {print(myList[[i]])} [1] "Echo" [1] "Echo" [1] "Alpha" [1] "Bravo" [1] "Charlie" [1] "Delta"

'펄(Perl)'과 '알(R)'의 짤막한 비교언어학이었다.

[업데이트 2019-02-18] 실용적인 사례


위에서 보인 예문에서는 for 반복문 안에서 단지 print를 이용하여 값을 화면에 인쇄한 것에 지나지 않는다. 실제로는 data = c("e","e","a","b","c","d")라는 벡터가 주어졌을 때 "Echo"    "Echo"    "Alpha"   "Bravo"   "Charlie" "Delta"를 역시 벡터로 반환해 주어야 비로소 쓸모가 있다. Named vector를 사용하여 간단하게 처리하는 코드를 소개해 본다. Named vector abcde는 위에서 정의한 것을 그대로 사용한다. 변환 작업을 할 벡터 data가 주어졌을 때 여기에 원소와 동일한 이름을 붙이는 것이 핵심이다.

> data = c("e","e","a","b","c","d")
> names(data) = data
> for (i in data) {
+ temp = names(abcde)[abcde==i]
+ names(data)[names(data)==i] = temp
+ }
> names(data)
[1] "Echo"    "Echo"    "Alpha"   "Bravo"   "Charlie" "Delta"  

위의 방법에서는 data vector의 이름을 변경하였다. 이름과 값이 동일하다면, 값을 바꾸어도 된다. 맨 마지막 행에서는 unname() 함수를 사용하여 data 벡터의 이름을 제거해야 원하는 결과가 나온다.

> data = c("e","e","a","b","c","d")
> names(data) = data
> for (i in data) {
+ temp = names(abcde)[abcde==i]
+ data[names(data)==i] = temp
+ }
> unname(data)
[1] "Echo"    "Echo"    "Alpha"   "Bravo"   "Charlie" "Delta"  

named vector에서는 이름이 같아도 문제가 되지 않는다. 아마 data frame에서는 곤란할 것이다.

댓글 없음: