Altair로 시각화하기

AltairVegaVega-Lite에 기반한 Python용 선언적 통계 시각화 라이브러리입니다. GitHub에서 개발현황을 볼 수 있습니다.
Altair를 사용하면 데이터와 의미를 이해하는 데 더 많은 시간을 쓸 수 있습니다. Altair의 API는 간단하고 친숙하며 일관성이 있는 Vega-Lite 문법 위에 구축되었습니다. 이 단순함은 최소한의 코드로 아름답고 효과적인 시각화를 할 수 있습니다.

특징

  • 신중하게 설계된 선언적 Python API
  • 자동 생성 되는 내부 API는 Vega-Lite과 완전히 일치합니다.
  • Vega-Lite JSON 사양에 맞는 코드를 자동 생성합니다.
  • Jupyter Notebook, JupyterLab, Nteract, nbviewer에서 시각화를 표시합니다.
  • 시각화를 PNG, SVG, HTML로 내보낼수 있습니다.
  • 갤러리에서 수십 가지 예제를 제공합니다.

Altair + Jupyter notebook

Jupyter notebook을 사용하는 경우, 버전 5.3 이상에서 가장 잘 작동합니다. 또한 노트북에서 Altair를 사용하려면 vega 패키지를 추가로 설치해야 합니다.

Altair 설치방법

Conda를 이용해 Altair를 설치하려면 다음 명령을 실행하십시오.

install

conda install -c conda-forge altair vega_datasets vega

간단한 맛보기

산포도(Scatter plot)를 한 번 그려보겠습니다.

In [1]:
import altair as alt
from vega_datasets import data

alt.renderers.enable("notebook")

iris = data.iris()

alt.Chart(iris).mark_point().encode(x="petalLength", y="petalWidth", color="species")
Out[1]:

No description has been provided for this image

사용되는 데이터의 형태

Altair의 데이터는 Pandas Dataframe을 기반으로 구축되었습니다. 이 튜토리얼에서는 아래와 같은 간단한 Dataframe을 작성해 사용할 겁니다. 그리고 데이터의 레이블이있는 열은 Altair의 시각화에 필수적입니다.

In [2]:
import pandas as pd

data = pd.DataFrame({"a": list("CCCDDDEEE"), "b": [2, 7, 4, 1, 2, 6, 8, 4, 7]})

차트 개체(Chart Object)

Altair의 기본 객체는 데이터(Dataframe)를 단일 인수로 취하는 Chart입니다.

In [3]:
import altair as alt

chart = alt.Chart(data)

위에서 Chart 객체를 정의했지만 아직 차트에 데이터를 처리는 하지 않았습니다. 인코딩과 마크작업을 통해 데이터를 처리해보도록 하겠습니다.

인코딩 과 마크(Encodings and Marks)

차트 개체를 사용하여 데이터를 시각화하는 방법을 지정할 수 있습니다. 이 작업은 Chart 객체의 mark 속성을 통해 수행됩니다. Chart.mark_* 메소드를 통해 사용합니다. 예를 들어 mark_point()를 사용하여 데이터를 점으로 표시 할 수 있습니다.

In [4]:
alt.Chart(data).mark_point()
Out[4]:

No description has been provided for this image

여기서 렌더링은 데이터 세트의 한 행당 하나의 점으로 구성되며,이 점들에 대한 위치를 아직 지정하지 않았기 때문에 모두 겹쳐져서 표시됩니다.

포인트를 시각적으로 분리하기 위해 다양한 인코딩 채널을 데이터의 열에 매핑 할 수 있습니다. 예를 들어 데이터의 변수 a를 x축 위치를 나타내는 x 채널로 인코딩 할 수 있습니다. 이것은 Chart.encode() 메소드로 할 수 있습니다.

encode() 메서드는 인코딩 채널(x, y, 색상, 모양, 크기 등)을 열 이름으로 접근 할 수 있게 합니다. Pandas dataframe의 경우 Altair가 각각의 열에 적합한 데이터 유형을 자동으로 정해 줍니다.

이제 예시들를 통해 확인해 봅시다.

막대그래프(bar graph)

In [5]:
# simple barplot
import altair as alt
import pandas as pd

data = pd.DataFrame(
    {
        "a": ["A", "B", "C", "D", "E", "F", "G", "H", "I"],
        "b": [28, 55, 43, 91, 81, 53, 19, 87, 52],
    }
)

alt.Chart(data).mark_bar().encode(x="a", y="b")
Out[5]:

No description has been provided for this image

선그래프(line graph)

In [6]:
import altair as alt
import numpy as np
import pandas as pd

x = np.arange(100)
data = pd.DataFrame({"x": x, "sin(x)": np.sin(x / 5)})

alt.Chart(data).mark_line().encode(x="x", y="sin(x)")
Out[6]:

No description has been provided for this image

선그래프에 데이터를 점으로 표시하기

In [7]:
import altair as alt
import numpy as np
import pandas as pd

x = np.arange(100)
data = pd.DataFrame({"x": x, "sin(x)": np.sin(x / 5)})

alt.Chart(data).mark_line(point=True).encode(x="x", y="sin(x)")
Out[7]:

No description has been provided for this image

히트맵(heat map)

In [8]:
import altair as alt
import numpy as np
import pandas as pd

# Compute x^2 + y^2 across a 2D grid
x, y = np.meshgrid(range(-5, 5), range(-5, 5))
z = x**2 + y**2

# Convert this grid to columnar data expected by Altair
data = pd.DataFrame({"x": x.ravel(), "y": y.ravel(), "z": z.ravel()})

alt.Chart(data).mark_rect().encode(x="x:O", y="y:O", color="z:Q")
Out[8]:

No description has been provided for this image

히스토그램(histogram)

In [9]:
import altair as alt
from vega_datasets import data

movies = data.movies.url

alt.Chart(movies).mark_bar().encode(
    alt.X("IMDB_Rating:Q", bin=True),
    y="count()",
)
Out[9]:

No description has been provided for this image

면적그래프(area graph)

In [10]:
import altair as alt
from vega_datasets import data

iowa = data.iowa_electricity()

alt.Chart(iowa).mark_area().encode(x="year:T", y="net_generation:Q", color="source:N")
Out[10]:

No description has been provided for this image

스트립 플롯(strip plot)

In [11]:
import altair as alt
from vega_datasets import data

source = data.cars()

alt.Chart(source).mark_tick().encode(x="Horsepower:Q", y="Cylinders:O")
Out[11]:

No description has been provided for this image

더 복잡한 그래프 예제

In [12]:
alt.renderers.enable("notebook")
alt.data_transformers.enable("json")

data = pd.DataFrame(
    {
        "Day": range(1, 16),
        "Value": [
            54.8,
            112.1,
            63.6,
            37.6,
            79.7,
            137.9,
            120.1,
            103.3,
            394.8,
            199.5,
            72.3,
            51.1,
            112.0,
            174.5,
            130.5,
        ],
    }
)

data2 = pd.DataFrame([{"ThresholdValue": 300, "Threshold": "hazardous"}])

bar1 = alt.Chart(data).mark_bar().encode(x="Day:O", y="Value:Q")

bar2 = (
    alt.Chart(data)
    .mark_bar(color="#e45755")
    .encode(x="Day:O", y="baseline:Q", y2="Value:Q")
    .transform_filter("datum.Value >= 300")
    .transform_calculate("baseline", "300")
)

rule = alt.Chart(data2).mark_rule().encode(y="ThresholdValue:Q")

text = (
    alt.Chart(data2)
    .mark_text(align="left", dx=215, dy=-5)
    .encode(
        alt.Y("ThresholdValue:Q", axis=alt.Axis(title="PM2.5 Value")),
        text=alt.value("hazardous"),
    )
)

bar1 + text + bar2 + rule
Out[12]:

No description has been provided for this image
In [14]:
population = data.population.url

# Define aggregate fields
lower_box = 'q1(people):Q'
lower_whisker = 'min(people):Q'
upper_box = 'q3(people):Q'
upper_whisker = 'max(people):Q'

# Compose each layer individually
lower_plot = alt.Chart(population).mark_rule().encode(
    y=alt.Y(lower_whisker, axis=alt.Axis(title="population")),
    y2=lower_box,
    x='age:O'
)

middle_plot = alt.Chart(population).mark_bar(size=5.0).encode(
    y=lower_box,
    y2=upper_box,
    x='age:O'
)

upper_plot = alt.Chart(population).mark_rule().encode(
    y=upper_whisker,
    y2=upper_box,
    x='age:O'
)

middle_tick = alt.Chart(population).mark_tick(
    color='white',
    size=5.0
).encode(b
    y='median(people):Q',
    x='age:O',
)

lower_plot + middle_plot + upper_plot + middle_tick
Out[14]:

No description has been provided for this image
In [15]:
countries = alt.topo_feature(data.world_110m.url, "countries")

base = (
    alt.Chart(countries)
    .mark_geoshape(fill="#666666", stroke="white")
    .properties(width=300, height=180)
)

projections = ["equirectangular", "mercator", "orthographic", "gnomonic"]
charts = [base.project(proj).properties(title=proj) for proj in projections]

alt.vconcat(alt.hconcat(*charts[:2]), alt.hconcat(*charts[2:]))
Out[15]:

No description has been provided for this image
In [16]:
barley = data.barley()

points = (
    alt.Chart(barley)
    .mark_point(filled=True)
    .encode(
        alt.X(
            "mean(yield)",
            scale=alt.Scale(zero=False),
            axis=alt.Axis(title="Barley Yield"),
        ),
        y="variety",
        color=alt.value("black"),
    )
)

error_bars = (
    alt.Chart(barley).mark_rule().encode(x="ci0(yield)", x2="ci1(yield)", y="variety")
)

points + error_bars
Out[16]:

No description has been provided for this image

마치며

Altair의 예제를 살펴보면서 이 도구의 잠재력과 간결함을 느끼셨을 것입니다. 하지만 Altair는 아래와 같이 몇가지 주의사항이 있습니다.

  • API는 여전히 꽤 새로운 것입니다. 따라서 일부에 버그가 존재할 수 있습니다.
  • 문서화가 아직 부족합니다. 때때로 Vega-Lite 문서를보고 답을 찾아야합니다.
  • 처리 할 수있는 데이터 포인트의 수는 현재 매우 적습니다. 지금은 5,000으로 제한되어 있지만 앞으로 늘어 날 것입니다.

그러나 이런 주의사항에도 Altair은 많은 발전가능성을 가지고 있습니다. 앞으로 matplotlib의 아성을 뛰어 넘을 수 있을지 지켜보도록 하죠.