Block Average 를 구하는 방법(배열 다루기)

최근 Q&A 게시판에 올려져 해결된 질문은 다음과 같습니다.

특정값을 제외한 평균을 구하고자 합니다. IDL Q&A에 있는 내용보다 조금 더 복잡한 건데요,

A라는 변수가 -1, 2, 3, 1, 0, 8, -999, 1, 10, … 이렇게 300개가 있습니다.
3개씩 묶어서 평균을 구하려고 합니다. 평균값들의 개수는 100개가 되겠지요.
아무튼 중간 중간 -999라는 숫자 때문에 평균값이 이상해져서 이를 제외하려고 합니다.

질문에 대한 답변은 훌륭하고, 질분하신 분도 사실상 적절한 시도를 하시던 중에 오류가 일부 있었을 뿐이었습니다. IDL Q&A 게시판에서 한번 찾아보시면 좋겠습니다.

저는 다른 목적으로 이 질문에 눈길이 갔습니다. IDL은 배열을 만지작 거리는 여러가지 함수들을 제공하고 있는데, 이를 자유롭게 다루면 좀 편하고 빠르게 해결할 수 있는 문제였거든요.  다음 내용을 보시면서 혹시나 아직 모르고 있던 함수를 만나신다면 꼭 기억해 두십시오. IDL 생활이 훨씬 편해집니다.

질문과 같이 N개의 덩어리로 끊어서 평균을 내는 것을 Block Average라고 합니다. IDL Block Average로 검색하면 누가 이미 만들어 올린 루틴도 찾으실 수 있을 정도로, 많이들 사용하는 기법입니다.  예제에서는 300개 데이터를 사용하면 너무 눈에 안보이니, 15개 배열로 소개해 보겠습니다.

이렇게 모든 배열 요소가 정상적인 데이터라면 정말 간단합니다. REBIN을 이용하시면 됩니다. REBIN은 배열의 크기를 늘리고 줄여주는 함수입니다.(줄일 때는 해당 Block의 평균값 또는 대표값을 채택하도록 선택할 수 있습니다)

그런데, 질문하신 내용은 오류값이 -999로 기록되어 있어서 단순 평균을 내면 엉뚱한 값이 나오게 되는 것이죠. 이런 데이터로 모의해 보겠습니다.

사실 질문은 이 상황에서부터 시작됩니다.

-999 같은 숫자는 ASCII Text 파일로 저장할 때 흔히 사용되는 방법이지만 IDL 내부에서 배열로 다룰 때에는 !values.f_nan 값을 이용하는 것이 편할 때가 많습니다. 이 값은, “데이터 타입은 float 형인데, 의미 없는 값(Not a Number)”을 뜻합니다. -999를 NaN으로 대체합니다.

REBIN에 /NAN 키워드(NaN 값은 없는 셈 치고 계산하라는 옵션)가 있다면 아주 쉽게 끝날 것 같은데요, 아쉽게도 REBIN은 /NAN 키워드가 없습니다. 그래서 다음과 같이 하는 것이 한계입니다.

NaN이 끼어 있는 Block은 아예 결과를 NaN으로 냅니다. 상황에 따라서는 이 방식이 선호될 수도 있습니다. 그런데 질문의 의도는 아마도, NaN 값이 있는 경우는 다른 두 값만을 이용해 평균을 내는 방식을 찾는 것입니다. 즉, 이러한 데이터라도 5개의 평균값이 모두 산출되어야 하는 거죠. NaN을 제외하고 평균을 내는 것은 MEAN 함수에 제공되는 기능입니다. 그런데, MEAN에서 Block Average는 어떻게 구할까요? 꼼수를 하나 쓰면 됩니다. REFORM 함수는 “배열 요소의 개수를 유지하는 한 어떤 형태(N차원)의 배열로도 모양을 변형할 수 있는” 함수입니다. 예를 들어 3*4의 2차원 배열은 12개의 1차원 배열로도 바꿀 수 있고, 3*2*2의 3차원 배열로도 바꿀 수 있습니다. 이를 이용하여 다음과 같이 15개의 배열을 3*5의 2차원 배열로 바꾸어 보겠습니다.

x2 변수 출력에서 보았을 때, 줄을 바꾸지 않고 쭉 이어서 출력하면 사실상 x 변수와 다를 바 없다는 것은 확인하실 수 있습니다. 그렇지만 이렇게 모양을 바꾸어 놓고 보면 뭔가 희망이 좀 보이는 것 같습니다. 행평균(DIMENSION=1, 즉 1차원 방향)을 내면 15개 배열의 3요소씩 Block Averaging이 되는 셈이군요.

열평균도 이제 예상하듯이 DIMENSION=2로 설정하면 쉽게 구해집니다. 우리가 의도했던 것은 어쨌든 행평균이죠.

어떻습니까? 이 방식을 이용하면 반복문을 사용하지 않아도 되고, 배열의 인덱스 계산에 착오가 발생할 일도 없습니다. 배열이 매우 크다면 반복문 방식과 이 방식의 속도 차이도 날 것입니다(사실 요즘 컴퓨터들이 워낙 빨라서, 어지간한 배열이라면 그 차이를 잘 못느낄 정도입니다).

그렇지만 반복문을 사용하여 Block Average를 구하는 방법이 선호될 수도 있습니다. 이 글에 소개한 방식은 IDL에 익숙한 프로그래머라면 읽고 무슨 의도로 이렇게 만들었는지 이해하겠지만, C, JAVA 같은 다른 언어를 사용하는 프로그래머가 보았을 때는 뭘 하려는 것인지 파악하기 어려울 수 있습니다. 반복문을 사용하는 방식은 아마도 어떤 프로그래밍 언어를 사용하는 사람이라도 보면 그 의도를 이해할 수 있을 것입니다. 무엇보다, “뭔가 묘수가 있을 것 같은데…”라고 막연히 시간을 보내며 일 안하는 것 보다는, 먼저 생각 나는 방법 – 반복문을 쓰든 조건문을 쓰든 – 으로 일단 빨리 일을 처리하는 것이 더 좋은 경우가 많습니다. 어쩌다가 “묘수는 없다”라는 결론에 도달하면 시간만 낭비한 셈이 되니까요. 실행 속도 차이? 중요한 문제이긴 한데, 배열 300개면 이렇게 하나 저렇게 하나 번개같이 끝나기는 마찬가지입니다. 그래서, 게시판에 올려진 질문과 답변은 모두 모범적인 시도와 모범적인 답안이라고 생각합니다. 이 글의 방법은 사실상 “잔머리”라고 할 수 있죠.

하지만, REBIN, REFORM, MEAN 함수를 이렇게 사용한다는 사실을 모르셨다면 꼭 기억해 두세요. 정말 유용한 놈들이거든요.