Wireframe

Altair에서 변형 함수 사용하기

Vega-Lite는 몇 가지 간단한 데이터 변형 함수를 제공합니다. 물론 보통의 경우에 차트로 시각화하려는 원본 데이터는 DataFrame의 형태로 나와 있고, 데이터 프레임 자체를 조작하여 원하는 변형을 미리 처리하는 것이 보다 유연하고 강력합니다. (데이터 프레임은 원래 그럴려고 쓰는 것이니까요) 그렇지만 원본 데이터를 미리 조작하기 어려운 경우라면 간단한 변형은 Vega-Lite의 기능을 활용하는 방법도 고려해볼 수 있습니다. 오늘은 이 글을 통해서 Altair에서 데이터 변형을 적용하는 방법을 알아보겠습니다.

인코딩에서 집합 함수를 사용하기

가장 간단하게 쓰이는 방법으로는 차트 정보를 인코딩할 때, 필드 이름 대신에 집합 함수를 적용한 표현식의 문자열을 사용하는 것입니다. average(), count() 같은 것을 이런 식으로 표현할 수 있습니다. 다음은 vega_datasets에서 자동차에 관한 정보를 가져와서 실린더 수 별 가속력의 평균치를 표현하는 코드입니다.

import altair as alt
from vega_datasets import data

cars = data.cars()
alt.Chart(cars).mark_bar().encode(
    y='Cylinders:O',
    x='average(Acceleration):Q'
)

Altair는 가속력에 대한 데이터를 자동으로 반대쪽 축인 실린더 수별로 구분하여 그 평균을 내어 차트를 그립니다. 또 다른 예를 들어보겠습니다. 1~19사이의 정수를 무작위로 생성해서 각 값마다 몇 개씩 만들어졌는지를 살펴봅니다.

import numpy as np
import pandas as pd

ts = pd.DataFrame({'t': np.random.randint(1, 20, (80, ))})
alt.Chart(ts).mark_bar().encode(
    x='t:O', y='count()'
)

사용 가능한 집합 함수의 전체 목록은 이곳에서 확인할 수 있습니다. 집합 함수 외의 데이터 변형이 필요한 경우에는 Altair에서 제공하는 transform_*() 함수를 사용합니다. (전체 목록은 Altair 문서 페이지에서 확인할 수 있습니다.) 이글에서 모든 변형 함수에 대한 설명을 할 필요는 없을 것 같고, 대표적인 몇 가지 예를 보면서 사용 방법을 익혀보도록 하겠습니다.

변형함수 적용하기

Chart 객체에는 “transform_”으로 시작하는 여러 메소드들이 있습니다. 이 메소드들은 Vega-Lite에서 제공하는 여러가지 transformation 함수를 사용하여 기존 필드의 값을 변경하거나, 새로운 필드를 생성합니다. 먼저 앞서 예를 들었던 평균값을 생성하는 trasnformation을 적용해 보겠습니다. 집합 함수를 적용하기 위해서는 transform_aggregateion() 메소드를 사용하며, 집합 함수를 수동으로 적용한 새로운 필드를 생성합니다.

alt.Chart(cars).mark_bar().encode(
    y='mean_acc:Q', x='Cylinders:O'
).transform_aggregation(
    mean_acc='mean(Acceleration)',
    groupby=['Cylinders']
)

위 예에서 mean_acc='mean(Acceleration)' 부분을 보겠습니다. 키워드 인자로 지정된 이름은 평균값을 계산하여 새롭게 생성할 필드명이 됩니다. 그리고 group_by= 키워드 인자를 사용해서 평균을 계산할 때, 그룹핑할 기준을 지정해줄 수 있습니다.

일반적인 데이터 변형 방법

일반적인 변형함수의 사용은 키워드인자로 새로운 필드를 지정하고, 그 필드값은 보통 문자열로 표현되는 어떤 표현식이 됩니다. transform_aggregate()의 예에서 보듯이, mean_acc='mean(Acceleration)'과 같이 함수 mean()Acceleration 필드를 전달하는 표현식을 문자열 그대로 쓰게 됩니다.

이 때 사용하는 표현은 함수의 종류에 따라 조금 달라질 수 있습니다. 집합 함수에서는 주로 필드 이름을 그대로 사용하게 됩니다. 필터 함수와 같이 개별 값의 특성을 사용해야 하는 경우에는 datum이라는 용어를 사용합니다. 간단한 예로 transform_filter() 함수를 사용하는 예를 보겠습니다.

alt.Chart(data.population.url).mark_area().encode(
  x='age:O', y='people:Q'
).transform_filter(
  'datum.year == 2000 & datum.sex == 1'
)

datum은 현재 데이터를 가리키는 용어라고 생각할 수 있습니다. 문자열 내에서 datum은 마치 현재 row에 대한 javascript객체처럼 다룰 수 있습니다. datum.year라고 쓸 수 있으며, datum["year"]와 같이 쓸 수 도 있습니다. 만약 필드 이름에 공백이 있거나 한다면 후자의 표현을 써야겠죠. 문자열 내에 표현식을 쓰는 것이 부담스럽다면, altair.datum을 사용할 수 있습니다. 이는 현재 데이터에 대한 프록시처럼 작동합니다.

chart..transform_filter((alt.datum.year == 2000) & (alt.datum.sex == 1))

필터 함수에 인자가 될 수 있는 값들은 다음과 같습니다.

  1. datum을 포함하는 자바 스크립트 표현식 문자열
  2. alt.datum을 사용한 파이썬 표현식
  3. Field*Predicate를 사용한 표현

Field*Predicate는 같거나 크거나 작은 관계를 나타내는 래퍼 클래스입니다. 특정 필드의 값을 기준으로 필터링할 때는 field=, equal= 인자를 사용하여 관계식을 표현할 수 있습니다.

alt.Chart(data.population.url).mark_line().encode(
    x='year:O', y='sum(people):Q'
).transform_filter(
    alt.FieldEqualPredicate(field='sex', equal=1)
)

“OneOf”, “Range” 를 기준으로 하는 Predicate를 구성할 수도 있습니다. 다음 예시는 FieldRangePredicate를 사용하여 1920년~1990년 범위의 연령별 인구를 각각 다른 색의 선 그래프로 비교할 수 있게 해줍니다.

alt.Chart(data.population.url).mark_line().encode(
    x='age:O', y='sum(people):Q', color='year:O'
).transform_filter(
    alt.FieldRangePredicate(field='year', range=[1920, 1990])
)

여러 Predicate를 연결할 때에는 “and”, “or”, “not”을 키로 하고, 여러 Predicate의 리스트를 값으로 하는 사전을 만들어서 전달합니다. 처음 필터 예제는 연도와 성별을 and 조건으로 적용하여 필터링하였으므로, 다음과 같이 표현할 수 있습니다.

alt.Chart(data.population.url).mark_area(opacity=0.5).encode(
    x='age:O', y='people:Q'
).transform_filter(
    {'and': [
        alt.FieldEqualPredicate(field='year', equal=2000),
        alt.FieldEqualPredicate(field='sex', equal=1)
    ]}
)

매개 변수 그래프 그리기

필터의 표현식을 사용하면 매개 변수를 사용한 그래프를 그리는 것이 가능합니다.

ts = pd.DataFrame({'t': np.arange(800)})
alt.Chart(ts).mark_line(strokeWidth=0.3).encode(
    x='x:Q', y='y:Q', order='t:Q'
).transform_calculate(
    x='cos(datum.t * PI / 50)', y='sin(datum.t * PI / 30)'
)

심화 – Predicate를 사용해야 하는 경우

대부분의 경우에는 자바스크립트 표현식 문자열을 사용하는 것이 가장 간단하게 쓸 수 있기 때문에 더 선호되는 방식일 것지만, 특정 데이터 형식과 관련해서는 그렇지 못한 경우가 있습니다. 예를 들어 날짜 형식이 이와 관련이 있는 경우입니다. 예를 들어 어떤 시계열 데이터에서 2005년 이후의 데이터만 필터링하고 싶은 경우에는 "datuem.date > '2007-01-01'"과 같은 식의 표현식은 작동하지 않습니다. Vega 내부적으로 문자열과 Date 형식의 데이터를 구분하기 때문인 것으로 보입니다.

이 경우에는 alt.Datetime() 클래스와 FieldGTEPredicate()를 사용해서 기간의 비교를 구현할 수 있습니다.

# 전체 기간 영역의 그래프
base = alt.Chart(data.stocks.url,width=200,height=200).mark_line().encode(
    x='date:T',y='price:Q',color='symbol:N'
)

# 2005년 이후.  alt.DateTime(year=2005)보다 큰 데이터만으로 필터링
base | base.transform_filter(
    alt.FieldGTEPredicate('date', gte=alt.DateTime(year=2007))
)

Exit mobile version