본문 바로가기

Function

OD 시각화 1 : 여러가지 시도

지도 위에 데이터를 표현할 때 아주 까다로운 대상 중 하나는 OD 데이터다. Origin-Destination 데이터는 지도 위의 두 지점을 선으로 이어야 한다. 노드와 링크가 있는 추상공간 위의 네트워크 시각화와는 조금 다른데, 되도록이면 정점의 위치를 움직이지 않음으로써 실제 지리 공간의 거리를 유지해야 한다는 전제가 있기 때문이다.

 

 

비교적 간단한 OD 시각화

 

처음 시도해봤던 OD 시각화는 d3.js를 공부해보던 시절에 그려봤던 패스 분포도였다. 지도 위의 공간 정보는 아니었지만 선수들 포지션에 따라 대략적으로 위치가 정해졌다고 전제로 깔고 그렸다.

 

 

전후반 출전 선수를 모두 포함하여 열댓개의 노드가 있는 OD 데이터였는데, 링크의 수는 이론적으로 노드의 제곱수까지 가능하므로 사실 노드가 열개만 되어도 오고가는 선들이 매우 정신없어진다. 

 

이 때 이런저런 시도를 해보면서 가장 먼저 깨달았던건, 우선 두 점 사이에 오고가는 선을 두 개 그어주려니 약간은 곡선을 만들어야 한다는 것. 여기서는 발로 차는 공의 연결을 보여준다고 생각했기 때문에 시작하는 부분에서는 되도록 직선으로 뻗어나가게 했고, 도착점에 도달할 때 약간 꺾어서 마무리했었다. 어디서 어디로 가는지 헷갈릴 수 잇기 때문에 선의 중간에 화살표를 추가해주었다. 선이 아주 많지는 않으니 그렇게 해도 그리 어지러워 보이지는 않았다.

 

패스 횟수에 따라 선의 굵기를 다르게 했는데, 그 부분이 이 시각화에서 가장 중요한 부분이라 생각했다. 패스 분포는 결국 선수들이 연결된 정도, 혹은 경기가 운영된 흐름을 보여주는 것이라 생각했기 때문이다.

마지막에는 색상을 한번 다르게 구성해봤다. 열 댓개의 점들이었으므로 선수들을 표시한 점들을 다른 색으로 표시하고 선의 색상을 패스을 한 선수의 점 색상과 일치시켰다. 그냥 보면 알록달록해보이지만, 그래도 노란색에 집중해서 보면 노란색만 어느 정도 구분이 된다.

 

 

하나의 노드만 선택하는 방식

 

OD 시각화는 어쩔 수 없이 복잡해지기 때문에 한 장의 그림으로 데이터가 담고 있는 모든 것을 다 보여주려는 욕심은 일찌감치 포기하는게 좋다. 그래서 인터랙티브가 가능하다면, 몇 가지 옵션을 더 만들어볼 수 있다. 가장 기본적인 옵션은 하나의 노드만 선택해서 표현하도록 해주는 방법이다.

 

 

2016년에 사단법인 코드의 코드나무 소속으로 중앙일보와 작업했을 때, 위의 작업을 뚝딱(?) 고쳐서 아래와 같은 인터랙티브 버젼으로 만들어보았다. 

화살표를 좀 더 간단하게 했고, 패스횟수도 추가해보았다.  패스량이 많으면 선도 굵고 숫자도 크게 했다. 기왕이면 공이 패스한 공이 통통 튀기는 것 같은 효과도 넣어보았다. 기사의 링크는 다행히 아직 살아 있다.

 

 

중앙일보 - 데이터로 본 한국vs우즈벡 전

전반전과 180도 달랐던 후반전

news.joins.com

 

 

 

그런데 노드가 조금 더 많아지면.

 

 

같은 효과를 약간의 수정을 통해 지하철 승하차 인원 데이터에 적용시켜보았다. 

 

 

서울시에서 개방하고 있는 역간 월별 승하차량을 이용했는데, 600여개의 노드이기 때문에 한 번에 전체를 보여주는 것은 처음부터 포기했고, 특정 역을 클릭했을 때 나가는 데이터와 들어오는 데이터를 구분해서 표현해야 겨우 무언가 보이는 것 같았다. 존재하는 모든 선을 그리기에는 당시 svg방식을 사용하던 d3.js로는 퍼포먼스의 한계도 있었다.

 

아래 링크에서 시도해볼 수 있다.

 

http://bl.ocks.org/vuski/raw/f0132bcf73c31b0a8038b53b1fd8923e/?raw=true

 

bl.ocks.org

 

d3.js로는 이 정도 시도를 해봤던 것 같다. 요새는 d3.js도 canvas를 사용해서 퍼포먼스를 높일 수 있고 webgl과의 결합도 다양하게 시도되는 것 같다. 그때는 내가 잘 몰라서 저 정도가 웹브라우저 퍼포먼스의 한계라고 생각하고, 자바 기반의processing 으로 넘어갔었던 것 같다.

 

이 작업에서 느꼈던 점은, 실제 공간상에서의 이동량은 가까운 곳이 많고 먼 곳이 적다는 것. 어찌보면 당연한 이치이기도 한데, 이러한 특징은 시각화할 때 명쾌한 표현을 더더욱 어렵게 만들었다. 노드의 근처는 늘 '떡'이 되어버리곤 했다.

 

 

 

 

선의 색상과 투명도

 

 

프로세싱을 익히면서 도전해봤던 작업. 1년간의 전입신고 데이터가 모두 공개된다는 것을 알게된 직후 두근두근하는 마음으로 작업했었다. '도대체 어떤 그림이 나올까?'

 

일단 공개된 데이터의 단위인 읍면동을 살리기로 했다. 3500여개의 노드와 천만개 정도의 링크가 존재하는 데이터였다.

 

첫 시도는 아래와 같은 그림. 

사실 그냥 그리면 저렇게 나올 것이라는 점은 예상을 했었고, 일단 대략적인 내용이라도 파악해보고 싶어서, 투명도가 연한 직선으로 그렸다.

 

 

 

그래서 며칠간 여러가지 시도를 해 봤었다.

일단 선이 너무 많았기 때문에 굵기 표현은 하지 않고, 투명도를 이용해서 선의 진하기로만 이동량을 표현하기로 했다. 1년동안 A읍면동에서 B읍면동으로 한사람만 이동했다면 희미해서 거의 안 보일 것이고, 100명 이상이 이동했다면 같은 투명도의 선들이 100번 겹쳐져서 진한 선으로 표시된다고 규칙을 정했다.

 

 

이동의 방향성을 표시하면서 조금 더 이동하는 운동적인 느낌이 들게 하려고 진행하는 선들을 이렇게저렇게 굽혔다 폈다가 하다보니, 중간에는 아래처럼 기괴한 이미지도 나왔다.

저 정도까지 가다보니, 양이 많을때는 단순한 것이 가장 좋겠다 싶어 일단 평범한 호로 이동을 표현하기로 했다. 수 많은 선에 화살표를 그리는 것도 무리였으므로, '시계 방향의 이동을 순방향'으로 정의하고, 그건 그냥 이미지를 설명할 때 말이나 글로 덧붙여야겠다고 생각했다.

 

 

작업을 하다보니 생각보다 이동량이 대칭적이라는 점을 알게 되었다. 결국 오고가는 양의 차이인 순이동량이 서서히 누적되어 전국의 인구 쏠림 현상을 만들어내는 것. 그래서 순이동이 아닌 양뱡향의 표현을 그대로 드러낼 때는 수도권으로 인구가 집중되는 것은 표현하기 어렵다고 결론지었다. 

물론 OD 중 한 쪽이 수도권인 이동은 많기 때문에 위의 그림처럼, 수도권을 기종점으로 하는 이동이 지배적이라는 점은 보인다. 그렇지만 그것과, 이동의 결과로 수도권으로 인구가 점점 몰린다는 표현은 엄연히 다르다. 위 그림에서 '결과적으로 몰린다'는 사실은 알기 어렵다.

 

이동 방향을 표시하는 화살표가 없고 시작부터 끝까지 같은 진하기로 표현하니 약간 정적인 느낌이 났다. 그래서 그런지 이동의 느낌보다는 영향권을 보여준다는 느낌이 강했다. 그래서 선의 진행 정도에 따라 색상이나 투명도에 변화를 주어 정적인 느낌을 약간 줄여보려고 했다.

 

 

그렇게 해서 아래의 이미지가 완성되었다.

출발 지점에서는 약간 푸른색, 도착 지점에 가까워지면 흰색으로 했다. 사실 결과는 그러한데, 약간의 트릭을 썼다. 도착 지점에서는 RGB를 랜덤하게 발생시켰고, 결국 이 색들이 많이 겹치면서 흰색에 가까워졌는데, 그냥 흰 색을 썼을 때와는 약간 다른 느낌이 났고, 그리 나쁘지 않았다.

 

코드의 일부를 가져오자면 아래와 같다. 

for (int i=0 ; i<len ; i=i+20) {
    
    float t = (float)i/len;
    float x = curvePoint(oriX_, oriX, desX, desX_, t);
    float y = curvePoint(oriY_, oriY, desY, desY_,t);
    
    //색상
    color fromC = color(0,134,207);
    color toC = color(random(10,255),random(10,255),random(10,255));
    color interC = lerpColor(fromC, toC, t*t*t*t*t);

    //선굵기
    strokeWeight(map(t,0,1,0.005,0.015));
    stroke(interC,map(t,0,1,10,20)*weight);
    
    line(x_pre,y_pre,x,y);
    
    t = (float)(i+1)/len;
    x_pre = curvePoint(oriX_, oriX, desX, desX_, t);
    y_pre = curvePoint(oriY_, oriY, desY, desY_,t);
    
    
  }

코드를 다시 보니 strokeWeight도 약간 변화시키긴 했나보다. 

 

한 가지 커다란 개체의 색상을 정하는 것이 아니라, 개체의 굵기와 색상을 정의하면 그것들이 무수히 많이 겹치면서 결과물의 색상을 만들어 내는 상황이므로 결과의 느낌을 쉽게 예상하기 어려웠으므로, 어쩔 수 없이 색상과 선굵기 부분을 무수히 고쳐보면서 시행착오를 통해 결과를 만들었다.

 

자, 이렇게 전국을 그렸더니 무엇이 보이는가?

구체적인 내용 하나하나는 확인이 어렵다. 다만 서울과 그 주변 수도권을 기종점으로 한 이동이 지배적이라는 점이 보인다. 사실 딱 거기까지다. 부산 대구 등 지방 곳곳의 허브들이 분명히 있는데, 그것은 이 그림에서 잘 드러나지 않는다. 지역의 허브를 보고 싶으면, 수도권을 기종점으로 둔 이동을 지워보면 된다. 아래 그림처럼.

 

 

 

 

그리고 이런 표현 방식은 원거리의 이동이 어쩔 수 없이 두드러지게 나타난다는 특징도 있다. A-B 간의 거리에 비해 A-C 간의 거리가 10배라면 같은 정도의 이동이라도 선을 그리는데 필요한 픽셀이 10배가 많아진다. 그래서 그림에서도 상대적으로 영향이 크게 보일 수 밖에 없다. 지도를 왜곡시키지 않고 표현할 때 필연적으로 맞딱뜨리게 되는 딜레마이기도 하다. 위의 그림에서도 수도권 내의 이동이 당연히 훨씬 더 많음에도 불구하고 서울과 제주, 서울과 부산 등의 이동이 더 강조되어 보인다. 따라서, 역으로 얘기하자면, 위의 표현방식은 '수도권을 기종점으로 하는 이동이 전국의 이동에서 상당한 비율을 차지한다' 라는 메시지를 전달할 때 가장 적절한 경우가 된다.

 

 

저 작업은 2016년에 했는데, 몇 달 전에야 그 때 몰랐던 부분을 깨닫게 된 것이 있다.

당시에 이런저런 시행착오를 거치다가, 천만개의 선을 한번에 그리는 것과 여러 프레임에 나누어 겹쳐 그리는 결과물이 달라진다는 사실을 알게 되었다. 결국 한 프레임이 오래 걸리더라도(100초쯤 걸렸던 것 같다) 그냥 기다린 후 나온 결과물이 괜찮아서 그렇게 했었다. 하지만 이유는 몰랐었다.

 

그런데 얼마 전에 OpenGL로 당시의 작업을 재현해봤더니, 색공간을 일반적인 32비트(8비트 x 4채널)가 아닌 64비트나 128비트로 올리면 프로세싱 한 프레임으로 그렸을 때와 비슷해졌다. 즉, 프로세싱은 한 프레임을 그릴 때는 좀 더 깊은 색공간을 사용하고, 한 프레임이 끝나면 그것을 32비트로 눌러버리는 것 같다. 경험에 의한 추측이고, 확인은 해보지 못했다.

 

즉, 한 프레임 안에서는 깊은 색공간을 사용하기 때문에 아주 많이 투명한 선들을 그려도 그 값이 살아있게 되어 백개 천개 겹쳤을 때 그 만큼 누적된 효과가 비로소 드러나게 되는데, 프레임이 넘어가면 0.003 즉 1/256보다 작은 값들은 삭제되어버리므로 이전 프레임에서 미세하게 몇 개 겹쳤던 선들은 그냥 사라져버리게 된다. 

따라서, 1000만개의 선을 10만개씩 100프레임에 나누어 그릴 때와 그냥 한번에 1000만개를 그릴 때의 결과가 달라질 수 밖에 없었던 것이다.

많은 수의 선들을 겹쳐 그릴 때는 색공간이 깊으면 좋다. 일반적인 색공간 즉 한 RGB 각각의 채널당 8비트라면, 결국 아무리 연하게 그려도 255개의 선들이 겹치는 순간 가장 진하게 되어버리기 때문이다. 혹시 각각 256단계이므로 256 x 256 x 256 단계가 되는것이 아니냐 생각할 수도 있다. 물론 픽셀의 색상 수치를 숫자로만 이용해서 무언가 작업을 한다면 당연히 그렇게 된다. 하지만 눈으로 보는 결과물이라면 그렇지 않다. 눈은 그렇게 인지하지 않는다. 

 

 

 

곡선과 직선

 

요새는 여러차례의 시행착오를 거친 후라서, OD를 표현할 때 직선은 잘 쓰지 않는다. 선들이 복잡해질 때 직선과 곡선은 아래처럼 다르다.

 

 

 

 

이 작업은 시군구간의 이동량을 표현하는 일이었는데, 몇몇 대도시의 영향권이 드러날 수 있었으면 했다.  중간 과정이었고, 여러가지 표현이 달라서 물론 1:1로 비교하기는 어렵다. 그래도 살펴보면, 직선의 경우 방향성이 너무 강해서 두 개의 허브가 겹쳤을 때  그물 조직의 일부처럼 보이게 된다. 그리고, 서울에서 뻗어나가는 선들을 보면 같은 중심에서 나가는 선 중 도착지의 각이 우연히 비슷할 때 겹쳐버려서 잘 구분되지 않는다. 그렇지만 곡선을 사용하게 되면 완전히 겹치는 경우가 적어도 이론적으로는 발생하지 않는다. 크기가 다른 각 개체의 기본 형상은 동일하지만 단위 길이당 곡률 변화가 달라지게 되기 때문이다.

 

 

 

 

사실 그런데, 위와 같은 시행착오를 거쳐 아래와 같이 마무리되었다. 주요 도시에서 나가는 선들만 남기고 나머지는 모두 삭제했다.

이미지가 담고 있던 복합성은 사라졌지만, 메시지는 조금 더 명료해졌다. 아쉬운 부분이 없지않아 있지만, 사실 이 그림은 B5 크기의 보고서에 그리 크지 않게 들어가는 이미지였다. 자세히 표현해봤자 보이지도 않고 괜히 복잡해져서 읽혀야 할 부분도 읽히지 않기 때문이다.

 

시각화에서 항상 옳은 표현은 없다. 실리는 매체, 보게 되는 대상, 전체 구성에서 그 이미지의 역할 등 맥락에 따라 표현의 정도가 맞춰져야 한다.

 

 

 

형태의 변주 

 

표현해야 하는 개체 수가 아주 많지 않다면, 면의 느낌이 나는 선으로 표현해 볼 수도 있다. 

 

언제나 가까운 곳의 이동량이 많으므로 처음부터 굵으면 모든 선이 떡이 되어버린다는 문제는 위에서도 한번 언급했다. 그래서 출발할 때는 선을 많이 가늘게 하고, 마지막에만 굵어지도록 했다. 곡선으로도 표현해보고 이런저런 규칙들을 부여했다. 가까운 곳들은 조금 더 휘어가게 만들고, 먼 곳의 경우 많이 휘면 방향성이 약해지는 느낌도 있으므로 멀 때는 덜 휘어가게 해서 서울의 권역을 표시해보았다. 이 이미지가 쓰이지는 못했다. 중간 스터디 과정이다.

 

OpenGL 같이 셰이더를 이용해서 표현할 수 있다면 위와 같은 선들을 그리는게 그렇게 어렵지는 않다. 한 개체를 그리는 규칙만 잘 정해주면 되기 때문이다.

 

 

 

기본 개념은 같지만 조금 다르게 표현해보기도 하고,

 

물론 직선에 가까운 형태도 그려봤다.

 

 

 

 

사실 위의 이미지들은 3차원, 즉 z축으로 올라갔다가 내려오는 형상들이다.

z축의 값을 이용해서, 겹쳤을 때 어떤 선을 위로 보여주고 어떤 선들을 밑으로 감출지 결정할 수 있다.

 

 

 

권역의 힘을 표현할 때는 아래와 같은 표현도 나쁘지 않다. z축 방향으로만 세워 놓은 형태다.

셰이더로 그린다면 트라이앵글 하나로 선 하나를 표현할 수 있다는 장점도 있다.

 

 

위의 방식은 다른 곳에도 써 보았다.

이 이미지는 따릉이 대여 반납 데이터로 그린 그림이다. 여의나루역 근처 대여소와 관계된 곳만 그렸고, 권역을 표현할때는 그럭저럭 효과적인 것 같다.

 

 

SSAO(Screen Space Ambient Occlusion) 방식으로 겹친 선들이 구분되어 보이도록도 해봤다.

그런데 표현이 너무 생소하면 받아들이는 입장에서 익숙하지 않음에서 오는 거부감도 있는 것 같다. 다듬어지지 않아서 거친 탓도 있던 것 같다.

 

 

 

 

대표

당시에 일종의 프로토타입으로서 위 그림과 같은 화살표도 그려봤는데, 느낌이 나쁘지 않아 다른 일들에 계속 사용하고 있다. 일반적인 화살표처럼 선보다 머리의 폭이 더 넓으면 선이 많아질때는 너무 복잡해져서 명료해보이지 않았다. 그래서 이동량을 표현한 선의 굵기보다 더 굵어지지 않는 선에서 화살표처럼 보이도록 했다.

 

위 그림의 특징이라면, 출발점에서는 선이 바로 시작하되, 도착점에서는 약간의 간격을 떼어놓았다는 점.  종점까지 화살표를 이어버리면 여러 방향에서 오는 화살표 머리끼리 겹쳐서 그다지 좋지 못한 이미지가 나왔다.그래서 약간의 버퍼를 만들어 떼어 놓았고, 그렇게 생긴 틈에 지명을 표기함으로써 허전하지 않게 보이도록 했다.

 

그리고 화살표 중간을 다소 투명하게 했는데, 시작과 끝은 확실하게 보여줘야 출발과 도착을 인지할 수 있었지만, 중간에는 끊기기 않는 흐름만 보여줘도 되었기 때문이다. 중간까지 모두 진하게 하면 그림의 많은 부분에서 화살표가 서로 겹치는 탓에 알아볼 수 없게 되어버렸다.

 

 

 

문턱값(threshold)의 사용

 

 

일반적인 네트워크 시각화에서도 흔히 사용하는 방식이지만 문턱값을 이용해서 일정 정도 이상만 표현하는 방식도 시도해볼만하다.

 

따릉이 대여반납의 경우가 이 방식이 효과적이었는데, 서울 안에 몇몇 허브들을 중심으로 국부 네트워크가 이루어져 있었기 때문이다. 일정 threshold 아래의 값들은 대부분 몇 개 없는 원거리 이동이라, 이것들을 끊어냈을 때 국부 네트워크들이 효과적으로 드러났다.

 

물론 원래의 이동을 모두 그리면 저렇게 깔끔하지 않다. 

이동이 너무 많아서 시간을 z값으로 두고 들어올렸는데도 여전히 어지럽다. 공간의 네트워크가 보이는게 아니라 엉뚱하게 낮과 밤만 구분되는 정도가 되었다.

 

 

 

낯설지만 탐색용

 

 

시각화의 목적은 한눈에 정보를 전달하는 것이므로, 되도록이면 사람들에게 익숙한 표현으로 시도해야 한다. 그렇지만 사용자가 데이터에 상당히 익숙한 사람이고, 시각화의 목적 자체가 데이터 탐색이라면 조금 색다른 시도를 해볼 수 있다.

원하는 조건들이 뚜렷하게 표현되기만 한다면 방식이 낯설더라도 크게 문제되지 않는다.

 

아래의 이미지는 시군구별 인구 순이동 중 유입량을 z축 방향으로 표현한 것이다. 

어디서 얼마만큼 오는지가 확실히 드러났으면 했는데, 김해시의 경우 전체 약 3만5천명 중 약 1/3 정도가 창원시 성산구 한 곳에서만 전입했음이 드러난다. 전체의 1/2 정도는 부산 이곳저곳에서 온 듯하고, 남은 1/6이 이곳저곳에서 왔다는 사실을 대략적으로 알 수 있다. 물론 구체적인 수치는 그림에서가 아니라 다시 데이터로 돌아가 계산해야 하지만, 빠르게 탐색하는데에는 이런 표현이 효과적일 수 있다.

 

숫자가 모든 곳에 붙어 있으면 좀 더 구체적이겠지만, 그렇게 되면 그림이 어지러워지기 때문에 그림에 미세하게 표식을 넣어놓을 수 있다. 예를 들면 아래처럼 흰 기둥에 눈금을 새기는 방법이다.

대전 서구로 유입된 인구는 16,211명.  흰 색 막대의 높이가 16,211을 가리키는데, 1000명에 해당하는 높이마다 눈금을 새기고, 10,000명일 때는 눈금을 좀 더 크게 벌렸다. 이 눈금을 염두에 두고 보면 1순위인 중구에서 유입된 인구가 3,700여명임을 알 수 있다. (쌓이는 순서를 내림차순으로 미리 정렬해두었다.)

 

 

 

사실 그래도 수도권에는 답이 없다. 오밀조밀한 시군구에서 상호간의 이동량이 엄청나게 많기 때문이다. 이 그림에서 확인할 수 있는건 수도권 내적 이동이 참 많다는 사실 정도다. 그리고 서울의 인구가 경기도로 많이 빠져나간 것도 보인다. 남양주나 용인 같은 곳에 거대한 기둥이 서 있는 것이 보이는데, 대부분 서울 쪽에서 선들이 연결되어 있다. 예를 들어 용인시의 경우 4만5천명이 전입해왔는데, 전체의 80%정도가 수도권에서 왔음을 볼 수 있다. 나머지 20%정도가 전국 곳곳에서 전입해왔다는 사실이 대략적으로 보인다.

 

분명히 그림이 주는 정보는 어떤 사람에게는 효과적일 수 있지만, 이런 그림을 불특정 다수가 보는 곳에 퍼블리싱한다면 '괴상하고 어려운 시각화의 예'로 낙인찍히기 쉽다. 어느 정도 설명을 해야 하기 때문에, 아니, 그냥 그보다 딱 봤을 때 복잡해보이기 때문이다.

 

 

 

 

다음편 예

 

한 번에 모두 다 쓰려고 했더니 생각보다 내용이 다소 길어진다. 이 글은 여기까지 하고, 2편은 지난번에 만들었던 인구순이동 시각화 사용법 형식으로 써봐야겠다.

 

 

 

다음편이 추가되었다

 

OD 시각화 2 : 전국 인구 순이동

앞의 글에서 여러가지 사례를 들어 OD 시각화에서 고민했던 점들을 썼다면, 이번에는 전국 인구 순이동 시각화를 조작해보면서 설명해보겠다. 앞의 글이란 이것 OD 시각화 1 : 여러가지 시도 지도

www.vw-lab.com