IDL에서 플롯 계열의 표출을 하는데 있어서 축(Axis)의 눈금을 로그(Log) 스케일로 표시하는 기능은 이미 예전부터 지원되어오고 있습니다. NG 체계의 PLOT 함수 및 DG 체계의 PLOT 프로시저 모두 /XLOG 및 /YLOG 키워드를 사용하여 설정하면 됩니다. 간단한 예제를 하나 본다면 다음과 같습니다.
x = FINDGEN(11)
y = x*450+500
win = WINDOW(DIMENSIONS=[600, 500], /NO_TOOLBAR)
p = PLOT(x, y, YRANGE=[100, 10000], /YLOG, YTICKUNITS='exponent', $
SYMBOL='circle', /SYM_FILLED, COLOR='crimson', CLIP=0, FONT_SIZE=10, $
MARGIN=0.1, /CURRENT)
여기서는 예제 데이터 x, y를 PLOT 함수로 표출하면서 Y축만 로그 스케일이 되도록 하였습니다. 표출된 그림의 모습은 다음과 같습니다.

위의 예제를 보면 Y축 방향의 값 범위가 500~5000이고 Y축 자체는 100~10000의 범위로 로그 스케일로 잘 표시된 것을 볼 수 있습니다. 그런데 만약 로그 스케일로 나타내고 싶은데 그 범위가 (-) 영역으로까지 가야할 경우에는 어떻게 될까요? 예를 들어 로그 스케일로 나타내고자 하는 값의 -5000~5000가 되는 경우를 가정해보는 것입니다. 사실 수학적으로 본다면 (-)인 값에 대해서는 로그 결과값이 나올 수가 없습니다. 만약 억지로 (-) 값에 대하여 로그 값을 산출하도록 하더라도 그 결과는 다음과 같이 NaN(Not A Number) 값으로만 얻어지게 됩니다.
IDL> PRINT, ALOG10(-37.5)
-NaN
% Program caused arithmetic error: Floating illegal operand
따라서 이러한 논의 자체가 말이 안된다고 보는 것이 정상이긴 합니다. 앞서 제시된 예제에서 Y 데이터의 값 범위만 다음과 같이 -3000~3000이 되도록 살짝 바꿔준 다음 전반적인 표출 과정을 다시 실행해봅시다.
x = FINDGEN(11)
y = x*600-3000
win = WINDOW(DIMENSIONS=[600, 500], /NO_TOOLBAR)
p = PLOT(x, y, XRANGE=[0, 10], YRANGE=[-5000, 5000], /YLOG, YTICKUNITS='exponent', $
SYMBOL='circle', /SYM_FILLED, COLOR='crimson', CLIP=0, FONT_SIZE=10, $
MARGIN=0.1, /CURRENT)
결과는 다음 그림과 같습니다.

이 그림을 보면 y의 값이 0 또는 (-)가 되는 부분은 아예 표시되지 않는다는 것을 확인할 수 있습니다. 게다가 Y축의 경우 축 범위를 일부러 -5000~5000으로 설정을 하였음에도 불구하고 실제 그림에서는 1~5000의 범위로 표시되었습니다. 수학적으로 로그 값이 나올 수 없는 부분들을 IDL이 알아서 생략해버린 것입니다. 사실 원칙적으로는 이렇게 처리되는 것이 전혀 이상한 것이 아닙니다. 오히려 이런 식으로 처리가 되어야 제대로 처리하는 것이라고 볼 수 있습니다.
하지만 그럼에도 불구하고 데이터의 특성에 따라서는 값 범위가 (-) 영역까지 넘나듬에도 불구하고 로그 스케일로 표시하고 싶은 경우가 실제로 있습니다. 예를 들어 고도가 0인 해수면을 기준으로 그 위/아래를 커버하는 데이터를 표출하는데 있어서 축을 로그 스케일로 표시하고자 하는 경우를 생각해볼 수 있습니다. 바로 앞서 예제로 사용했던 데이터 즉 y의 범위가 -3000~3000인 데이터도 비슷한 경우라고 봐도 됩니다. 어쨌든 (-) 영역까지 커버하는 데이터를 로그 스케일로 표출하고 싶다면 직접적인 방법으로는 어려울 것이고 뭔가 우회적인 방법을 동원해야 합니다. 그 핵심은 데이터의 값 범위가 (+)인 부분과 (-)인 부분을 따로 처리하는 것입니다. 물론 이렇게 하려면 코딩 과정이 좀 복잡해질 수 밖에 없긴 하지만, 어쨌든 그러한 방법을 지금부터 살펴보기로 하겠습니다. 먼저 예제로 사용할 x, y 데이터는 앞선 예제와 동일한 방식으로 다음과 같이 생성합니다.
x = FINDGEN(11)
y = x*600-3000
다만 앞선 예제에서 x, y를 PLOT 함수에 바로 투입하면서 /YLOG 키워드를 사용했던 방식은 여기서는 사용하지 않습니다. 대신 y에 대한 상용로그 값들로 구성된 ylog라는 배열을 다음과 같이 얻어야 합니다.
ylog = ALOG10(y)
PRINT, ylog
물론 원래 y의 값들 중 (+) 범위가 아닌 값들에 대해서만 수학적으로 의미있는 값들이 산출되었을 것입니다. 실제로 출력된 ylog의 값들을 보면 금방 알 수 있습니다.
-NaN -NaN -NaN -NaN -NaN -Inf 2.77815 3.07918 3.25527 3.38021 3.47712
따라서 원래 y의 값들 중 0 이하인 값들에 대해서만 선별적인 처리를 해줘야 합니다. 이러한 작업은 WHERE 함수를 사용하여 다음과 같이 처리해봅시다.
ww = WHERE(y LT 0)
ylog[ww] = -ALOG10(-y[ww])
ww = WHERE(y EQ 0)
ylog[ww] = !values.f_nan
여기서는 먼저 y의 값이 (-)인 경우에 대해서는 의도적으로 (+)로 변경하여 로그 값을 취해준 다음 그 로그 값에 대해서 (-)가 되도록 처리하여 그러한 값이 ylog에 대응되도록 하였습니다. 그리고 y의 값이 0인 경우에 대해서는 원래 수학적으로는 로그를 취하면 (-) 방향의 무한대(Infinity) 값이 되겠지만, 일단 여기서는 무의미한 값으로 간주하여 NaN 값이 되도록 하여 ylog에 반영되도록 하였습니다. 이러한 처리를 거친 후 출력된 ylog의 값들은 다음과 같습니다.
-3.47712 -3.38021 -3.25527 -3.07918 -2.77815 NaN 2.77815 3.07918 3.25527 3.38021 3.47712
이제 표출 과정에 있어서 x, y를 대상으로 하는 대신 다음과 같이 x, ylog를 대상으로 해봅시다.
win = WINDOW(DIMENSIONS=[600, 500], /NO_TOOLBAR)
p = PLOT(x, ylog, SYMBOL='circle', /SYM_FILLED, COLOR='crimson', $
CLIP=0, FONT_SIZE=10, MARGIN=0.1, /CURRENT)
이 과정에 의한 표출 결과는 다음 그림과 같습니다.

이 그림을 보면 Y축의 (-) 영역에서의 값의 변화도 로그 스케일로 표현이 된 것을 볼 수 있습니다. 일단 우리가 의도했던 결과는 얻은 것 같습니다. 다만 추가적인 처리가 필요한 부분이 있는데요. 바로 Y축의 눈금 숫자들입니다. 아무래도 로그를 취한 후의 값들이 반영된 것이기 때문에 -4~4의 범위가 될 수 밖에 없습니다. 또한 마이너 눈금들을 보면 로그 스케일에서처럼 처음에는 듬성듬성하다가 나중에 좁아지는 그런 형태가 아니고 그냥 균일한 간격입니다. 역시 로그를 취한 후의 값들이 축에 반영된 것이기 때문에 이렇게 나올 수 밖에 없습니다. 따라서 Y축의 눈금 숫자들 및 마이너 눈금들의 분포가 Y축 방향의 원래 데이터 값에 충실하도록 그리고 마이너 눈금들의 분포도 로그 스케일의 느낌이 나도록 처리하고 싶다는 생각이 듭니다. 물론 이러한 처리도 가능은 합니다. 다만 약간 귀찮은 과정들이 좀 더 추가되어야 합니다. 일단 먼저 눈금 숫자들부터 바꿔봅시다.
p.YTICKNAME = STRING([-10000, -1000, -100, -10, 0, 10, 100, 1000, 10000])
이와 같이 처리를 한 결과는 다음 그림과 같습니다.

그 다음은 마이너 눈금들에 대한 처리입니다. 일단 원래의 마이너 눈금들은 보이지 않도록 처리합시다.
p.YMINOR = 0
이 결과는 다음 그림과 같습니다.

그 다음에는 새로운 마이너 눈금들을 추가하는 과정입니다. 다소 무식해 보이지만 그냥 다음과 같이 처리해봅니다.
ytks = [FINDGEN(9)+1, FINDGEN(9)*10+10, FINDGEN(9)*100+100, FINDGEN(9)*1000+1000]
pyax1 = AXIS('Y', LOCATION='left', TICKVALUES=[-ALOG10(ytks),ALOG10(ytks)], $
TICKLEN=0.02, MINOR=0, SHOWTEXT=0)
pyax2 = AXIS('Y', LOCATION='right', TICKVALUES=[-ALOG10(ytks),ALOG10(ytks)], $
TICKLEN=0.02, MINOR=0, SHOWTEXT=0)
이와 같이 마이너 눈금이 매겨질 값들로 구성된 배열 ytks를 직접 정의해준 다음, 이 눈금들로 구성되는 새로운 Y축을 AXIS 함수로 정의하여 중첩하는 방식입니다. 그러면서 마이너 눈금들의 길이는 약간 짧게 처리합니다. 이러한 방식으로 좌측 및 우측에 새로운 Y축을 추가하면 됩니다. 이 과정에 의한 결과는 다음 그림과 같습니다.

이 정도면 나름 괜찮은 결과를 얻은 것 같습니다. 물론 세부적인 과정들이 다소 복잡하고 귀찮은 느낌도 있지만 제 생각에는 이게 최선인 것 같습니다. 여기서 한가지만 더 짚어봅시다. Y축의 눈금들을 보면 값들이 액면 그대로의 값들로 표시되어 있는데, 로그 스케일이라는 특성에 맞게 지수 형태같은 느낌으로 표시해주는 것도 좋을 것 같습니다. 이를 위해서는 앞서 YTICKNAME 속성을 설정했던 부분을 약간 수정해야 하는데요. 일단 다음과 같이 바꿔봅시다.
p.YTICKNAME = STRING([-10000, -1000, -100, -10, 0, 10, 100, 1000, 10000], FORMAT='(G0.1)')
여기서는 눈금 숫자들이 표시될 때의 포맷(Format)을 G 기반으로 변경한 것입니다. 이러한 변화가 반영된 결과는 다음 그림과 같습니다.

그런데 이러한 방식말고 진짜 지수 형태처럼 표시되도록 하려면 어떻게 해야 할까요? 이러한 처리도 아마 그냥 좀 무식하게 하는 수 밖에는 없을 것 같습니다. 즉 다음과 같이 눈금 숫자들을 직접 적어주는 방식으로 처리해봅시다.
p.YTICKNAME = ['-10!u4', '-10!u3', '-10!u2', '-10!u1', '0', '10!u1', '10!u2', '10!u3', '10!u4']
어쨌든 이러한 변화가 반영된 결과는 다음 그림과 같습니다.

아마 이 정도면 우리가 의도했던 결과를 얻은 것으로 보입니다. 한가지만 더 짚어보자면, 정가운데 즉 y의 값이 0인 부분에 대한 처리 문제입니다. 이미 언급했듯이 0에 대한 로그값은 (-) 방향의 무한대이기 때문에 수학적으로 의미없는 값인 것은 맞습니다. 따라서 NaN으로 처리를 했고 그러다보니 가운데 부분의 데이터 포인트가 생략되면서 가운데가 비어있는 그림이 된 것인데요. 하지만 이렇게 그림으로 표출하는데 있어서는 수학적인 의미와는 별도로 그냥 데이터 포인트들이 모두 선으로 이어지도록 보이는 것이 중요할 경우도 있습니다. 그래서 이러한 시각적인 목적을 위해서 y가 0일 때의 로그값을 그냥 0이라고 간주해버리는 경우도 생각해볼 수 있습니다. 즉 앞선 과정에서는 y가 0인 경우의 ylog 값을 NaN으로 처리했었지만 이번에는 다음과 같이 0으로 간주하는 것입니다.
ylog[ww] = 0
수학적으로는 말이 안되긴 하지만 어쨌든 이렇게 처리할 경우의 결과는 다음 그림과 같습니다.

물론 이렇게 처리하는 것이 과연 괜찮을 것이냐에 대해서는 데이터의 특성 및 관례를 감안하여 프로그래머가 판단하는 것이 좋을 것 같습니다. 지금까지 데이터의 범위가 (-) 영역까지 넘나드는 경우 로그 스케일로 표시하는 방법을 살펴보았는데요. 전반부에서 언급했듯이, (-)인 값에 대해서는 수학적으로 로그 값이 존재할 수 없기 때문에, IDL에서도 이러한 데이터를 그림으로 표출할 때 (-) 영역에 대한 로그 스케일 처리 및 표시가 생략되는 것은 전혀 이상한 것이 아닙니다. 하지만 데이터의 특성에 따라서는 어쩔 수 없이 (-) 영역에 대한 로그 스케일 처리 및 표시가 필요한 경우도 있을 수 있습니다. 따라서 굳이 그러한 처리가 필요할 경우에는 조금 복잡하고 귀찮긴 하지만 오늘 소개한 방법을 참조해두시면 어떨까 합니다.
'IDL > Programming' 카테고리의 다른 글
| IDL 8.9에서 추가된 상수 정의 기능 (1) | 2023.06.29 |
|---|---|
| IDL 8.9의 Literal Strings 문법 (0) | 2023.06.28 |
| WHILE 및 REPEAT 구문의 이해 (0) | 2023.02.10 |
| 주프로그램과 부프로그램의 구성 및 운용 방식 (0) | 2022.04.01 |
| 작업을 위한 디렉토리의 설정 방법들 (0) | 2022.03.07 |