데이터를 다루는 사람들은 다양한 형태로 그래픽을 활용합니다. 실시간으로 변하는 그래프를 보며 대응하기도 하고, 공들여 분석한 내용을 클라이언트에게 잘 설명하기 위해 슬라이드에 넣을 그래프를 작성하기도 합니다. 대중을 대상으로 하는 인포그래픽을 만들거나 논문에 들어갈 전문적인 도표를 만들 수도 있습니다. 목적과 대상은 모두 다릅니다. 하지만 데이터를 시각적으로 잘 표현해야 한다는 것은 동일합니다.
데이터를 시각적으로 잘 나타낼 수 있으려면 어떻게 해야 할까요? 처음에는 우리가 어떤 차트를 쓸 수 있는지 조차도 잘 모르는 상태에서 시작합니다. 엑셀이나 다른 도구들이 제공하는 간단한 차트 도구를 통해 데이터를 표현하는 방법을 익히게 될 것입니다. 하지만 어느 순간부터는, 복잡한 시각화를 위해서는 데이터를 그래픽으로 표현하는 과정에 대한 체계적인 고민이 필요해집니다. 이러한 과정은 우리가 언어를 통해 의사소통하는 것과 비슷하지 않을까요? 언어를 처음 배울 때 몇 가지 단어를 가지고 단순한 소통을 시작합니다. 그러다가 단어를 조합하고, 더 복잡한 문법을 구사할 수 있게 되면 언어를 통해 엄청나게 다양한 표현을 할 수 있게 됩니다. 다시 데이터로 돌아오면, 데이터 시각화를 위한 문법을 체계적으로 구성할 수 있다면 우리는 차트라는 파편화된 단어를 넘어서 무한에 가까운 표현력을 얻을 수 있을 겁니다.
시각화 문법은 그래프를 구성하는 요소로 어떤 것들이 필요한지 청사진을 보여주는 역할을 합니다. 이러한 문법 체계는 우리의 머리 속에 있는 것들을 실제로 구현하는 과정을 단축시키는데 도움을 줍니다. 또한 복잡한 형태의 시각화도 체계적으로 구성할 수 있습니다. 개발자의 입장에서는 새로운 기능을 일관성 있게 추가할 수 있으며, 사용자의 입장에서는 일관적인 문법 체계를 바탕으로 쉽게 그래프를 작성하거나 수정할 수 있으니 편리해집니다.
여기서는 Leland Wilkinson 의 Grammar of Graphics 와 Hadley Wicham 의 변형된 형태에 대해서 간단히 살펴보겠습니다.
Wilkinson의 Grammar of graphics 에서는 데이터를 가지고 그래픽을 만들어내는데 다음과 같은 7가지 단계가 필요하다고 말합니다.
결과적으로 우리는 6가지 항목을 통해 그래픽 스펙을 설명할 수 있습니다.
아래와 같은 데이터셋을 가지고 문법에 맞게 막대 그래프를 만들어봅시다. 세부적인 과정을 설명할 수 있도록 간단한 의사코드(pseudocode)를 표시하고, 데이터셋이 변화하는 과정을 함께 표시하겠습니다. 아래 과정에서 작성하는 의사코드는 Wilkinson의 책에서 사용하는 표기 방법을 일부 수정하여 사용했습니다.
cut color 1 Ideal E 2 Premium E 3 Good E 4 Premium I 5 Good J 6 Very Good J 7 Very Good I 8 Very Good H 9 Fair E 10 Very Good H # … with 53,930 more rows
데이터에서 필요한 변수를 추출합니다.
Cut = loadFromDataSource("Diamonds", "cut") Color = loadFromDataSource("Diamonds", "color")
다른 소스의 데이터를 JOIN 하거나 UNION 할 경우 이 단계에서 처리합니다.
카테고리 변수에 Scale을 적용합니다. 여기서는 각 항목의 인덱스에 맞는 정수값을 적용합니다.
category(Cut, values("Fair", "Good", "Very Good", "Premium", "Ideal")) category(Color, values("D", "E", "F", "G", "H", "I", "J")) ------ cut color cut_index color_index 1 Ideal E 5 2 2 Premium E 4 2 3 Good E 2 2 4 Premium I 4 6 5 Good J 2 7 6 Very Good J 3 7 7 Very Good I 3 6 8 Very Good H 3 5 9 Fair E 1 2 10 Very Good H 3 5 # … with 53,930 more rows
이 과정에서는 Variable set 하나가 그래픽 요소 하나에 매칭될 수 있도록 데이터를 변환한다. 데이터의 1행이 하나의 그래픽 요소로 표현되도록 한다고 보면 됩니다. 막대 그래프를 그리기 위해서는 cut
, color
항목의 조합에 따라 하나의 요약된 값으로 정리해야 합니다.
아래 의사코드에서는 summary.count()
라는 작업을 정의해서 두 카테고리 변수에 해당하는 데이터 수를 계산합니다.
summary.count() ------ cut color cut_index color_index count 1 Fair D 1 1 163 2 Fair E 1 2 224 3 Fair F 1 3 312 4 Fair G 1 4 314 5 Fair H 1 5 303 6 Fair I 1 6 175 7 Fair J 1 7 119 8 Good D 2 1 662 9 Good E 2 2 933 10 Good F 2 3 909 # … with 25 more rows
다양한 그래픽 표현 방식 중에서 막대 형태를 사용하겠습니다. 해당 Geometry는 0이 아닌 길이를 가지는 막대를 사용해 데이터를 표현합니다. 카테고리 변수가 2개나 있기 때문에, 막대 중에서도 누적(stack) 막대를 사용합니다. 이 경우 각 데이터의 이전 막대에 이어 붙이는 형태로 표현됩니다.
interval.stack( position(summary.count(cut*color)) ) ------ cut color color_index start_count end_count 1 Fair D 1 0 163 2 Fair E 2 163 387 3 Fair F 3 387 699 4 Fair G 4 699 1013 5 Fair H 5 1013 1316 6 Fair I 6 1316 1491 7 Fair J 7 1491 1610 8 Good D 1 0 662 9 Good E 2 662 1595 10 Good F 3 1595 2504 # … with 25 more rows
여기서는 가장 기본적인 Rectangular(Cartesian) Coordinates를 사용합니다. 만약 이 부분에서 극좌표계(Polar Coordinates)를 사용할 경우, 막대에서 파이 그래프로 변하게 됩니다.
rect(dim(1, 2))
막대 내에서 color 변수를 구분할 수 있도록 color_index
값에 맞는 색상을 부여합니다. 외곽선, 투명도 등 막대에 추가적인 시각적 속성을 적용하려면 이 단계에서 반영합니다.
cut color color_index start_count end_count bar_color 1 Fair D 1 0 163 #66C2A5 2 Fair E 2 163 387 #FC8D62 3 Fair F 3 387 699 #8DA0CB 4 Fair G 4 699 1013 #E78AC3 5 Fair H 5 1013 1316 #A6D854 6 Fair I 6 1316 1491 #FFD92F 7 Fair J 7 1491 1610 #E5C494 8 Good D 1 0 662 #66C2A5 9 Good E 2 662 1595 #FC8D62 10 Good F 3 1595 2504 #8DA0CB # … with 25 more rows
결과적으로 우리가 완성한 막대 그래프는 다음과 같은 스펙으로 표현할 수 있습니다.
DATA: - Cut: loadFromDataSource("Diamonds", "cut") - Color: loadFromDataSource("Diamonds", "color") SCALE: - category(Cut, values("Fair", "Good", "Very Good", "Premium", "Ideal")) - category(Color, values("D", "E", "F", "G", "H", "I", "J")) COORD: - rect(dim(1, 2)) ELEMENT: - interval.stack - position(summary.count(cut*color)) - label(cut) - color(color) GUIDE: - legend.color(dim(1), label("color")) - axis(dim(1), label("cut")) - axis(dim(2), label("count"))
이번에는 Hadley Wickham의 Layered grammar of graphics 를 살펴보겠습니다. 우선 Wilkinson 의 기존 문법에서 어떤 부분이 달라졌는지 가볍게 이야기 하고, 실제 구현체인 R의 ggplot2 라이브러리를 통해 어떤 방식으로 문법이 구성되어있는지 알아보겠습니다.
우선 사용자가 그래프를 구성할 때 매번 모든 구성요소를 명시하지 않아도 동작할 수 있도록, 기본값이 적용되는 계층 구조가 생겼습니다. 예를 들면 아래와 같은 항목이 기본적으로 적용됩니다. 이러한 기본값 구조를 통해 사용자가 입력해야 하는 코드의 양을 줄일 수 있습니다.
그리고 R 언어의 특성과 라이브러리 생태계에 맞도록 내부 구성이 조정되었습니다.
Layered grammar of graphics 문서에서는 Wilkinson의 문법과 Hadley Wickham 문법의 차이를 다음과 같이 설명하고 있습니다.
Hadley Wickham의 문법은 다음과 같이 레이어를 중심으로 구성됩니다.
그래프에서 데이터를 표현하기 위한 구성 요소를 하나의 그룹으로 관리합니다. 이 그룹을 레이어라고 합니다. 포토샵이나 다른 그래픽 도구를 사용하셨다면 레이어라는 개념도 익숙하실 텐데요. 여기서의 레이어도 여러 개의 레이어를 중첩하는 방식으로 그래프를 확장할 수 있습니다.
하나의 레이어는 다음과 같은 구성 요소로 이루어져 있습니다.
Data & Mapping
Stat
Geom
Position
데이터와 시각적 요소(Aesthetic Elements)가 연결되는 과정을 세부적으로 조정합니다.
오브젝트의 위치 값이 그래프에 어떻게 반영되어야 하는지 나타냅니다. 그리고 축과 그리드 선이 어떻게 그려져야 하는지 결정합니다.
전체 데이터셋을 그룹별로 나누어 보여줍니다. 어떤 변수를 기준으로 그래프가 나누어져야 하며, 어떻게 정렬해야 하는지 결정합니다.
Wilkinson의 Grammar of Graphics 는 최근 50년간 통계학의 가장 중요한 아이디어 TOP 10 에도 선정되기도 할 만큼 엄청난 영향을 미쳤습니다. 상용 시각화 서비스인 Tableau와 R의 시각화 라이브러리인 ggplot2, 그리고 Javascript의 Vega 등 많은 도구들이 앞서 설명한 문법 체계를 바탕으로 탄탄한 생태계를 쌓아 올렸습니다. 또, 데이터 과학에서 EDA 등 시각화를 기반으로 한 다양한 분석 기법들이 뿌리내리는데 큰 도움을 주었습니다.
저는 R을 공부하면서 접하게 된 ggplot2
라이브러리를 통해 처음 시각화 문법을 접하게 되었습니다. 그리고 복잡한 수학이나 개발을 어려워하던 제가 데이터에 본격적으로 재미를 붙이게 된 계기이기도 합니다. 처음에는 코드 몇 줄로 그럴싸한 그래프가 출력되는 것이 신기한 것이었지만, 얼마 지나지 않아서 잘 구성된 시각화가 가지는 힘이 엄청나게 강하다는 것을 알게 되었습니다. 제가 중요하다고 생각하는 정보를 다른 사람들도 중요하다고 느끼도록 설득하는 것이 바로 제가 하는 일이었거든요.
사실 워낙 시각화 도구들의 문서가 잘 작성되어 있어서, 이런 원론적인 내용을 모르더라도 간단한 그래프를 그리는데는 아무런 문제가 없습니다. 하지만 전달해야 하는 내용이 너무 많거나 복잡할 때는 문법 체계를 명확하게 알고 있다는 것이 큰 도움이 될 거에요. 결국 우리가 하려는 건 데이터를 기반으로 하는 의사소통이니까요.