콘텐츠로 건너뛰기
Home » Jupyter용 시각화 라이브러리 Altair

Jupyter용 시각화 라이브러리 Altair

파이썬 생태계에서 데이터 시각화 분야에서는 matplotlib이 사실상 독보적인 위치에 있음은 반론의 여지가 별로 없긴합니다. 하지만 일관성이 부족한 API나 그래프의 외관을 예쁘게 만드려면 과다한(?) 노동이 필요한 점은 단점으로 지적받기는 합니다. 이런 문제를 극복하기 위해서, 기본적으로 예쁜 그래프를 만들 수 있도록 해주는 Seaborn이나, R의 ggplot의 인터페이스를 이식해온 plotnine 같은 대안이 존재합니다.

데이터 시각화에 대한 니즈는 유독 파이썬에서만 있는 것은 아니기에, 다른 많은 언어들에서도 훌륭한 데이터 시각화 도구들이 나오고 있습니다. jupyter의 경우 웹 기반의 노트북을 지원하고 있기 때문에 자바 스크립트로 만들어진 프론트엔드 시각화 도구를 접목하여 사용하기가 용이합니다. 자바 스크립트 쪽에서 선언적인 API, 높은 자유도 등의 요구조건을 만족하는 시각화 프론트엔드로 Vega가 있습니다.

Altair는 pandas의 DataFrame으로 된 데이터로부터 Vega에서 차트를 그리는데 필요한 기능들을 위주로 정리된 Vega-Lite 형식의 데이터를 생성해주는 파이썬 라이브러리입니다. jupyter 노트북 상에서 사용하는 것을 전제로 하지만, altair-saver, altair-viewer와 같은 패키지를 사용하면 노트북을 사용하지 않아도 브라우저로 그래프를 띄우거나, 생성한 그래프를 이미지 파일로 저장할 수 있습니다. 게다가 선언적인 문법을 채택하고 있어서 사용하기 쉽고 직관적이라는 장점을 갖고 있습니다.

설치

Altair는 pip로 설치할 수 있습니다. 보통 vega_datasets 패키지를 함께 설치하는데, 이 패키지는 샘플 데이터셋을 제공하거나, 원격에서 다운로드 할 수 있게 해줍니다. 그 외에 altair-saver, altaire-viewer도 함께 설치합니다.

$ pip install altair vega_datasets vega altair-saver altair-viewer

conda를 통해서도 설치할 수 있습니다. conda의 경우 altair가 살짝 구 버전일 수 있습니다. 이 경우, conda-forge 채널에서 다운로드 받으면 최신 버전을 설치할 수 있습니다.

$ conda install -c conda-forge altair vega_datasets


간단한 차트 만들기

이 글의 예제는 Altair의 공식 문서를 참고하고 있습니다. (https://altair-viz.github.io/getting_started/starting.html)

Altair를 사용해서 간단한 차트를 생성하는 방법에 대해서 알아보겠습니다. 앞에서도 말했지만, Altair는 차트 자체를 렌더링하는 기능을 포함하지 않습니다. 실제로 노트북에서 표현되는 모든 차트 이미지는 Vega에 의해 웹페이지에서 실시간으로 렌더링되며, Altair는 Vega가 사용할 차트 정보를 담은 JSON 데이터를 생성해줍니다.

R의 유명한 시각화 라이브러리인 ggplot과 비슷하게 Altair에서도 Grammar of Graphics와 비슷한 형태로 차트에 대한 데이터를 구성합니다. 대략의 절차는 다음과 같습니다.

  1. 시각화에 사용할 데이터를 근간으로 하여 새로운 차트 객체를 생성합니다. 이 때 사용하는 데이터는 기본적으로 pandas.DataFrame 입니다. 미리 전처리를 해 둘 수도 있고, 필요한 경우 Altair에서 제공하는 함수들을 사용해서 몇 가지 변형(transform)을 적용할 수 있습니다.
  2. 차트를 어떤 형식으로 표현할 것인지를 결정해서 mark_*()류 메소드를 호출합니다. 이렇게하면 해당 타입의 그래프를 그릴 준비가 완료됩니다.
  3. 차트의 모양을 구성할 정보를 인코드합니다. encode() 메소드를 사용합니다. 필요에 따라서는 2, 3의 순서가 변경될 수 있고, encode() 메소드는 여러 번 호출될 수 있습니다.

각각의 절차에서 처음 생성한 차트 객체는 내부가 변경되지 않습니다. 차트의 메소드를 사용해서, 정보를 추가하면 해당 변경이 적용된 차트 객체가 리턴되므로, 메소드 체인과 같은 방법으로 그래프를 다듬어 나갈 수 있습니다.

그럼 간단한 차트를 생성하는 과정을 살펴보겠습니다. Jupyter 노트북에서 실행하면, 각 과정의 결과를 바로바로 볼 수 있습니다. 먼저 아래와 같이 필요한 라이브러리를 가져오고, 데이터를 만들어봅니다.

import altair as alt
import pandas as pd
import numpy as np

df = pd.DataFrame({
        'a': lits("ABCDE"), 
        'b': np.random.randint(1, 7, (5,), dtype=np.int8)
    })

chart = alt.Chart(df)

생성된 차트를 보려고 c를 입력하고 셀을 평가하면 에러가 납니다. 아직 어떤 형태로 렌더링할 것인지를 모르기 때문입니다.

SchemaValidationError: Invalid specification
        altair.vegalite.v4.api.Chart, validating 'required'
        'mark' is a required property

여기서 ‘mark’는 바로 차트의 유형입니다. 산포도를 그려보겠습니다. 마크의 유형을 지정하는 메소드는 mark_*()의 형태로 되어 있습니다. 다양한 마크의 유형은 여기서 확인할 수 있습니다.

chart.mark_point()
mark_point()의 결과

엥… 그런데 그려지는 것이라고는 이렇게 작은 점 하나 밖에 없습니다. 현재까지 이 차트 객체는 소스 데이터를 가지고 있고, 점 차트를 그릴 것이라는 정보만 알고 있습니다. 데이터 프레임에서 x 축으로는 어떤 정보를, y축으로는 어떤 정보를 사용할 것인지 하는 정보를 전혀 알지 못합니다. 따라서 프론트엔드 렌더러는 그저 알고 있는 정보에 충실하여 점으로 데이터를 표시했습니다. 이 점은 각각의 x, y 좌표가 없기 때문에 아마도 5개의 점의 한 곳에 중첩되어 찍혀 있는 것으로 추측할 수 있습니다.

시각화에 필요한 다른 정보는 encode() 메소드를 통해서 이루어집니다. 데이터 프레임의 ‘a’ 칼럼을 X축으로 사용할 것입니다. 다음과 같이 입력해보겠습니다.

chart.mark_point().encode(x='a')

왼쪽이 그 결과입니다. x 축에 대한 정보는 이제 구성이 되었습니다. 아직, y 축에 대한 정보가 없으니, Y축은 축 자체가 그려지지 않고 모든 점이 나란히 배치됩니다. .encode(y='b')라고 붙이면 어떻게 될까요? 아, 세로로 길쭉한 그래프 모양이 포스트에 깔끔하게 들어오게 하기가 어려워서 축 방향을 아예 바꿔보겠습니다.

# 세로 방향으로 그래프가 길어서 x, y축을 바꿨음
chart.mark_point().encode(y='a').encode(x='b')

# 다음과 같이 두 번 호출할 것을 한 번 호출의 키워드 인자로 구분할 수 있습니다.
chart.mark_point().encode(
  y='a',
  x='b'
)
축 정보를 인코드한 결과

여기까지는 별로 어려운 게 없습니다. 물론 matplotlib을 사용해도 사실 여기까지는 별로 어려운게 없을 겁니다. 그럼 그래프를 막대 그래프로 바꿔보겠습니다. 중간에 사용했던 mark_point()mark_bar()로 바꿔주면 됩니다. 요건 matplotlib을 쓸 때보다 훨씬 간편합니다.

chart.mark_bar().encode(y='a', x='b')

# 혹은 점 차트를 막대 차트로 바로 변경하는 것도 가능합니다.
chart.mark_point().encode(y='a', x='b').mark_bar()
간단하게 막대 차트로 변경!

차트가 그려질 크기를 변경한다거나, 타이틀을 붙인다거나 하는 것은 차트 자체의 프로퍼티를 조정하는 것이며, 이는 properties() 메소드를 통해서 조정할 수 있습니다. 코드를 다시 정리해보겠습니다.

import altair as alt
import pandas as pd
import numpy as np

df = pd.DataFrame({
        'a': lits("ABCDE"), 
        'b': np.random.randint(1, 7, (5,), dtype=np.int8)
    })

c.mark_point().encode(x='b:Q', y='a:O')\
    .properties(title="Sample", width=400, height=120)\
    .mark_bar()

인코딩 데이터 타입

Altair는 프론트엔드의 렌더러가 사용하는 JSON 포맷의 데이터를 생성한다고 했습니다. 실제로 생성될 데이터는 Chart 객체의 to_json() 메소드를 통해서 확인할 수 있습니다. 이 때 생성된 데이터는 Vega-Lite에서 확인해볼 수 있습니다. (https://vega.github.io/editor/#/)

생성된 JSON 정보에서 차트를 위해 인코딩된 데이터는 "encoding" 키에 맵핑됩니다. 아래는 해당 데이터 부분을 가져온 것입니다. 그래프의 각 축에 대응하는 필드와 그 타입이 표시됩니다. 그런데 우리는 따로 타입이라는 걸 지정한 적이 없습니다. 이 값은 Altair에서 자동으로 파악해서 삽입한 값이며, 우리가 수동으로 변경해줄 수 있습니다.

"encoding": {
   "x": {
         "field": "b",
         "type": "quantitative"
   },
   "y": {
         "field": "a",
         "type": "nominal"
   }
}
  • “quantitative” : Q로 줄여쓰며, 연속적인 실수값
  • “ordinal” : O로 줄여쓰며, 순서가 있는 수량
  • “norminal” : N으로 줄여쓰며, 순서가 없는 카테고리
  • “temporal” : T로 줄여쓰며 시간이나 날짜값
  • “geojson” : J로 줄여쓰며 기하학적인 모양

축에 쓰이는 필드 이름 뒤에 :Q와 같은 식으로 데이터의 타입을 직접 지정해주거나, altair.X, altair.Y를 사용해서 축의 인코딩 속성을 적용할 수 있습니다. 다른 예를 위해서 코드를 다시 작성해보겠습니다.

l = 30
df2 = pd.DataFrame({
    'x': 1 + np.arange(0, l),
    'y': np.random.randint(1, 10, (l, ), dtype=np.int8),
    'w': [1,2,3] * 10
})

alt.Chart(df2).mark_point()\
        .encode(x='x', y='y')\
        .encode(color='w:O')

x, y 두 개의 필드 외에 w라는 필드를 만들어서 각각의 개별 데이터가 1, 2, 3 중 하나의 그룹에 속하게 됩니다. 그리고 색상 정보를 인코딩할 때, color="w:O"라고 했습니다. 각 포인트의 색은 w 필드에 의해 결정되며, 이 값은 순서가 있는 정수입니다. 이 때 색상은 w값에 따라 진하거나 옅은 식으로 표시됩니다.

만약 ‘w’의 타입을 O로 둔다면, 1, 2, 3 이라는 순서에 무방하게 서로 다른 카테고리로 보게 됩니다. 이 경우 각 그룹의 색상은 완전히 다른 색으로 구분되어 아래와 같이 표시될 것입니다.

예제 데이터 사용해보기

vegas_datasets 패키지는 VEGA에서 제공하는 예제 데이터 모음을 사용할 수 있게 해줍니다.

from vegas_datasets import data

cars = data.cars()
cars.head(4)

불러온 데이터는 여러 자동차들에 대한 정보를 담고 있습니다. 이 데이터들을 가지고 다양한 차트를 만들어볼 수 있습니다. 예를 들어, 아래와 같은 코드는 국가별 자동차들의 차체중량과 연비의 관계를 보여줄겁니다.

base = alt.Chart(cars).mark_point().encode(
    x='Weight_in_lbs', y='Miles_per_Gallon', color='Origin'
)

실제로 연비와 중량은 반비례하는 경향이 강하며, 일본차들이 가벼운쪽에, 미국차들이 무거운쪽에서 더 많은 비중을 보이고 있음을 볼 수 있습니다.

row, column, facet 인코딩을 사용하면, 분류별로 구분한 데이터의 차트를 일목요연하게 표시할 수 있습니다. 위에서 정의한 base에 대해서 추가로 column 인코딩을 추가해봅니다.

base.encode(column="Origin:N")

이처럼 Altair의 차트 객체는 생성된 후에도 추가적인 encode() 메소드 호출을 통해서 조정이 가능하며, mark_*() 메소드를 다시 호출하여 그래프의 타입을 바꾼 차트를 생성하거나, properties() 메소드를 통해서 여러 옵션을 조정할 수 있습니다.


이상으로 Altair의 기본적인 사용법에 대해서 살펴봤습니다. 단순 그래프보다는 차트에 더 적합하고, 웹상에서 렌더링된다는 제약이 있긴 하지만, 아주 간단하게 데이터 프레임의 내용을 시각화할 수 있고 여기서 소개하지 않은 더 많은 기능도 있기 때문에 관심을 갖고 지켜볼만한 도구인 것 같습니다. 다음 기회에 좀 더 세부적인 커스터 마이징이나 필터링 등에 대한 내용을 정리해서, 더 많은 내용을 소개하도록 하겠습니다. 그럼 안녕~