Python에서 데이터 시각화하는 다양한 방법


  • Python에서 데이터 시각화할 때 사용하는 다양한 라이브러리를 정리한 글입니다
  • 데이터 분석가들은 주로 Python(또는 R, SQL)을 가지고 데이터 분석을 합니다
    • R에는 ggplot이란 시각화에 좋은 라이브러리가 있는 반면 Python에는 어느 춘추전국시대처럼 다양한 라이브러리들이 있습니다
    • 각 라이브러리들마다 특징이 있기 때문에, 자유롭게 사용하면 좋을 것 같습니다
    • Zeppelin도 시각화할 때 사용할 수 있지만, 라이브러리는 아니기 때문에 이 문서에선 다루지 않습니다
    • 데이터 시각화 관련한 꿀팁은 카일 스쿨 2주차를 참고해주세요 :)
  • 저는 Jupyter Notebook으로 레포트를 작성해, 레포트의 보는 사람이 누구인지에 따라 다르게 그래프를 그렸습니다
    • 직접 그래프를 더 탐색해보고 싶은 목적이 있는 분들에게 보내는 그래프라면 동적인(Interactive) 그래프를 그렸습니다
    • 반복적인 레포트는 정적인 그래프 기반으로 작성한 후, 추가적인 내용이 궁금하면 대시보드로 가도록 유도했습니다
  • 이 문서는 맥북에서 작성되었으며, 부족한 내용이 있으면 연락주세요 :)
  • 현재 프론트단의 문제로 자바스크립트 기반 그래프는 처음엔 보이지 않을 수 있습니다. 10초 뒤 새로고침하면 나올거에요-!
  • plot.ly, pyecharts는 웹에서 정말 강력합니다. 꼭 직접 사용해보세요!
  • 라이브러리
    • matplotlib
    • seaborn
    • plotnine
    • folium
    • plot.ly
    • pyecharts

Matplotlib


import pandas as pd
import numpy as np
import matplotlib
import matplotlib.pyplot as plt

print("Matplotlib version", matplotlib.__version__)
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
Matplotlib version 2.2.3

Figure의 구성 요소

  • 처음 matplotlib을 사용해 그래프를 그릴 때, 그래프와 관련된 명칭을 (영어 표현으로) 몰라 애를 먹었습니다
  • 여기 나온 표현을 숙지해두기만 해도 좋을 것 같습니다

Figure

  • Figure는 그림이 그려지는 도화지라고 생각할 수 있습니다
    • 우선 Figure를 그린 후, plt.subplots로 도화지를 분할해 각 부분에 그래프를 그리는 방식으로 진행합니다
    • plt.figure를 명시적으로 표현해주는 것이 좋으나, plot 함수에서 자동으로 figure를 생성하기 때문에 자주 사용하진 않습니다
    • 그러나 현재 figure에 접근해야 할 필요성이 있다면, plt.gcf()로 접근할 수 있습니다
  • size를 조절하고 싶은 경우엔 fig.set_size_inches(18.5, 10.5)
    • 또는 plt.figure(figsize=(10,5))
    • 또는 plt.rcParams['figure.figsize'] = (10,7)

Axes

  • Axes는 plot이 그려지는 공간입니다

Axis

  • plot의 축입니다

fig = plt.figure()
fig.suptitle('figure sample plots')

fig, ax_lst = plt.subplots(2, 2, figsize=(8,5))

ax_lst[0][0].plot([1,2,3,4], 'ro-')
ax_lst[0][1].plot(np.random.randn(4, 10), np.random.randn(4,10), 'bo--')
ax_lst[1][0].plot(np.linspace(0.0, 5.0), np.cos(2 * np.pi * np.linspace(0.0, 5.0)))
ax_lst[1][1].plot([3,5], [3,5], 'bo:')
ax_lst[1][1].plot([3,7], [5,4], 'kx')
plt.show()
df = pd.DataFrame(np.random.randn(4,4))
df.plot(kind='barh')
Out[4]:
  • ggplot 스타일로 그리고 싶다면 아래 옵션 추가
plt.style.use('ggplot')
pd.options.display.mpl_style = 'default'
fig = plt.figure()
fig.suptitle('ggplot style')

fig, ax_lst = plt.subplots(2, 2, figsize=(8,5))

ax_lst[0][0].plot([1,2,3,4], 'ro-')
ax_lst[0][1].plot(np.random.randn(4, 10), np.random.randn(4,10), 'bo--')
ax_lst[1][0].plot(np.linspace(0.0, 5.0), np.cos(2 * np.pi * np.linspace(0.0, 5.0)))
ax_lst[1][1].plot([3,5], [3,5], 'bo:')
ax_lst[1][1].plot([3,7], [5,4], 'kx')
plt.show()

Seaborn


  • seaborn은 matplotlib을 기반으로 다양한 색 테마, 차트 기능을 추가한 라이브러리입니다
  • matplotlib에 의존성을 가지고 있습니다
  • matplotlib에 없는 그래프(히트맵, 카운트플랏 등)을 가지고 있습니다
  • 설치

      pip3 install seaborn
  • Seaborn Tutorials
import seaborn as sns
print("Seaborn version : ", sns.__version__)
sns.set()
sns.set_style('whitegrid')
sns.set_color_codes()
Seaborn version :  0.9.0
current_palette = sns.color_palette()
sns.palplot(current_palette)

relplot

tips = sns.load_dataset("tips")
sns.relplot(x="total_bill", y="tip", hue="smoker", style="smoker",
            data=tips)
Out[11]:
df = pd.DataFrame(dict(time=np.arange(500),
                       value=np.random.randn(500).cumsum()))
g = sns.relplot(x="time", y="value", kind="line", data=df)
g.fig.autofmt_xdate()

Catplot

sns.catplot(x="day", y="total_bill", hue="smoker",
            col="time", aspect=.6,
            kind="swarm", data=tips)
Out[12]:
titanic = sns.load_dataset("titanic")
g = sns.catplot(x="fare", y="survived", row="class",
                kind="box", orient="h", height=1.5, aspect=4,
                data=titanic.query("fare > 0"))
g.set(xscale="log");

Pairplot

iris = sns.load_dataset("iris")
sns.pairplot(iris)
Out[16]:
g = sns.PairGrid(iris)
g.map_diag(sns.kdeplot)
g.map_offdiag(sns.kdeplot, n_levels=6);

Heatmap

flights = sns.load_dataset("flights")
flights = flights.pivot("month", "year", "passengers")
plt.figure(figsize=(10, 10))
ax = sns.heatmap(flights, annot=True, fmt="d")

Plotnine


  • plotnine은 R의 ggplot2에 기반해 그래프를 그려주는 라이브러리입니다
  • R로 시각화하는 것이 익숙하신 분들에게 좋을 것 같습니다. 저는 사용해보진 않았습니다!
  • 공식 문서
  • 설치

      pip3 install plotnine
import plotnine
from plotnine import *
print("plontnine version :",plotnine.__version__)
plontnine version : 0.4.0
n = 10
df = pd.DataFrame({'x': np.arange(n),
                   'y': np.arange(n),
                   'yfit': np.arange(n) + np.tile([-.2, .2], n//2),
                   'cat': ['a', 'b']*(n//2)})
(ggplot(df)
 + geom_col(aes('x', 'y', fill='cat'))
 + geom_point(aes('x', y='yfit', color='cat'))
 + geom_path(aes('x', y='yfit', color='cat'))
)
Out[39]:
df2 = pd.DataFrame({
    'letter': ['Alpha', 'Beta', 'Delta', 'Gamma'] * 2,
    'pos': [1, 2, 3, 4] * 2,
    'num_of_letters': [5, 4, 5, 5] * 2
})

(ggplot(df2)
 + geom_col(aes(x='letter',y='pos', fill='letter'))
 + geom_line(aes(x='letter', y='num_of_letters', color='letter'), size=1)
 + scale_color_hue(l=0.45)                                  # some contrast to make the lines stick out
 + ggtitle('Greek Letter Analysis')
)
Out[41]:

Folium


  • folium은 지도 데이터(Open Street Map)에 leaflet.js를 이용해 위치정보를 시각화하는 라이브러리입니다
  • 자바스크립트 기반이라 interactive하게 그래프를 그릴 수 있습니다
  • 한국 GeoJSON 데이터는 southkorea-maps에서 확인할 수 있습니다
  • 참고 자료
  • 그 외에도 pydeck, ipyleaflet 등으로 지도 시각화를 할 수 있습니다
  • 설치

      pip3 install folium
import folium
print("folium version is", folium.__version__)
folium version is 0.6.0
m = folium.Map(location=[37.5502, 126.982], zoom_start=12)
folium.Marker(location=[37.5502, 126.982], popup="Marker A",
             icon=folium.Icon(icon='cloud')).add_to(m)
folium.Marker(location=[37.5411, 127.0107], popup="한남동",
             icon=folium.Icon(color='red')).add_to(m)
m
Out[76]:

Plot.ly


  • plotly는 Interactive 그래프를 그려주는 라이브러리입니다
  • Scala, R, Python, Javascript, MATLAB 등에서 사용할 수 있습니다
  • 시각화를 위해 D3.js를 사용하고 있습니다
  • 사용해보면 사용이 쉽고, 세련된 느낌을 받습니다
  • Online과 offline이 따로 존재합니다(온라인시 api key 필요)
  • plotly cloud라는 유료 모델도 있습니다
  • 설치

      pip3 install plotly
  • 참고 자료

import plotly
print("plotly version :", plotly.__version__)
plotly version : 3.1.1
plotly.offline.init_notebook_mode()
plotly.offline.iplot({
"data": [{
    "x": [1, 2, 3],
    "y": [4, 2, 5]
}],
"layout": {
    "title": "hello world"
}
})
import plotly.figure_factory as ff
import plotly.plotly as py
import plotly.graph_objs as go

df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/school_earnings.csv")

table = ff.create_table(df)
plotly.offline.iplot(table, filename='jupyter-table1')
data = [go.Bar(x=df.School,
            y=df.Gap)]

plotly.offline.iplot(data, filename='jupyter-basic_bar')
data = [dict(
        visible = False,
        line=dict(color='#ff0000', width=6),
        name = '𝜈 = '+str(step),
        x = np.arange(0,10,0.01),
        y = np.sin(step*np.arange(0,10,0.01))) for step in np.arange(0,5,0.1)]
data[10]['visible'] = True

steps = []
for i in range(len(data)):
    step = dict(
        method = 'restyle',
        args = ['visible', [False] * len(data)],
    )
    step['args'][1][i] = True # Toggle i'th trace to "visible"
    steps.append(step)

sliders = [dict(
    active = 10,
    currentvalue = {"prefix": "Frequency: "},
    pad = {"t": 50},
    steps = steps
)]

layout = dict(sliders=sliders)
fig = dict(data=data, layout=layout)

plotly.offline.iplot(fig, filename='Sine Wave Slider')

pyecharts


  • Baidu에서 데이터 시각화를 위해 만든 Echarts.js의 파이썬 버전입니다
  • 정말 다양한 그래프들이 내장되어 있어 레포트를 작성할 때 좋습니다!
  • 자바스크립트 기반이기 때문에 Interactive한 그래프를 그려줍니다
  • 공식 문서
  • 설치

      pip3 install pyecharts
import pyecharts
print("pyecharts version : ", pyecharts.__version__)
pyecharts version :  0.5.8
attr = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
v1 = [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3]
v2 = [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3]
bar = pyecharts.Bar("Bar chart", "precipitation and evaporation one year")
bar.add("precipitation", attr, v1, mark_line=["average"], mark_point=["max", "min"])
bar.add("evaporation", attr, v2, mark_line=["average"], mark_point=["max", "min"])
bar.height = 500
bar.width = 800
bar
Out[109]:
attr = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
v1 = [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3]
v2 = [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3]
bar = pyecharts.Bar("Bar chart", "precipitation and evaporation one year")
bar.use_theme("dark")
bar.add("precipitation", attr, v1, mark_line=["average"], mark_point=["max", "min"])
bar.add("evaporation", attr, v2, mark_line=["average"], mark_point=["max", "min"])
bar.height = 500
bar.width = 800
bar
Out[110]:
title = "bar chart2"
index = pd.date_range("8/24/2018", periods=6, freq="M")
df1 = pd.DataFrame(np.random.randn(6), index=index)
df2 = pd.DataFrame(np.random.rand(6), index=index)

dfvalue1 = [i[0] for i in df1.values]
dfvalue2 = [i[0] for i in df2.values]
_index = [i for i in df1.index.format()]

bar = pyecharts.Bar(title, "Profit and loss situation")
bar.add("profit", _index, dfvalue1)
bar.add("loss", _index, dfvalue2)
bar.height = 500
bar.width = 800
bar
Out[111]:
from pyecharts import Bar, Line, Overlap

attr = ['A','B','C','D','E','F']
v1 = [10, 20, 30, 40, 50, 60]
v2 = [38, 28, 58, 48, 78, 68]
bar = Bar("Line Bar")
bar.add("bar", attr, v1)
line = Line()
line.add("line", attr, v2)

overlap = Overlap()
overlap.add(bar)
overlap.add(line)

overlap
Out[112]:
from pyecharts import Pie

attr = ['A','B','C','D','E','F']
v1 = [10, 20, 30, 40, 50, 60]
v2 = [38, 28, 58, 48, 78, 68]
pie = Pie("pie chart", title_pos="center", width=600)
pie.add("A", attr, v1, center=[25, 50], is_random=True, radius=[30, 75], rosetype='radius')
pie.add("B", attr, v2, center=[75, 50], is_randome=True, radius=[30, 75], rosetype='area', is_legend_show=False,
       is_label_show=True)
pie
Out[113]:
bar = Bar("가로 그래프")
bar.add("A", attr, v1)
bar.add("B", attr, v2, is_convert=True)
bar.width=800
bar
Out[114]:
import random

attr = ["{}th".format(i) for i in range(30)]
v1 = [random.randint(1, 30) for _ in range(30)]
bar = Bar("Bar - datazoom - slider ")
bar.add("", attr, v1, is_label_show=True, is_datazoom_show=True)
bar
Out[115]:
days = ["{}th".format(i) for i in range(30)]
days_v1 = [random.randint(1, 30) for _ in range(30)]
bar = Bar("Bar - datazoom - xaxis/yaxis")
bar.add(
    "",
    days,
    days_v1,
    is_datazoom_show=True,
    datazoom_type="slider",
    datazoom_range=[10, 25],
    is_datazoom_extra_show=True,
    datazoom_extra_type="slider",
    datazoom_extra_range=[10, 25],
    is_toolbox_show=False,
)
bar
Out[116]:

3D

from pyecharts import Bar3D

bar3d = Bar3D("3D Graph", width=1200, height=600)
x_axis = [
    "12a", "1a", "2a", "3a", "4a", "5a", "6a", "7a", "8a", "9a", "10a", "11a",
    "12p", "1p", "2p", "3p", "4p", "5p", "6p", "7p", "8p", "9p", "10p", "11p"
    ]
y_axis = [
    "Saturday", "Friday", "Thursday", "Wednesday", "Tuesday", "Monday", "Sunday"
    ]
data = [
    [0, 0, 5], [0, 1, 1], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0],
    [0, 6, 0], [0, 7, 0], [0, 8, 0], [0, 9, 0], [0, 10, 0], [0, 11, 2],
    [0, 12, 4], [0, 13, 1], [0, 14, 1], [0, 15, 3], [0, 16, 4], [0, 17, 6],
    [0, 18, 4], [0, 19, 4], [0, 20, 3], [0, 21, 3], [0, 22, 2], [0, 23, 5],
    [1, 0, 7], [1, 1, 0], [1, 2, 0], [1, 3, 0], [1, 4, 0], [1, 5, 0],
    [1, 6, 0], [1, 7, 0], [1, 8, 0], [1, 9, 0], [1, 10, 5], [1, 11, 2],
    [1, 12, 2], [1, 13, 6], [1, 14, 9], [1, 15, 11], [1, 16, 6], [1, 17, 7],
    [1, 18, 8], [1, 19, 12], [1, 20, 5], [1, 21, 5], [1, 22, 7], [1, 23, 2],
    [2, 0, 1], [2, 1, 1], [2, 2, 0], [2, 3, 0], [2, 4, 0], [2, 5, 0],
    [2, 6, 0], [2, 7, 0], [2, 8, 0], [2, 9, 0], [2, 10, 3], [2, 11, 2],
    [2, 12, 1], [2, 13, 9], [2, 14, 8], [2, 15, 10], [2, 16, 6], [2, 17, 5],
    [2, 18, 5], [2, 19, 5], [2, 20, 7], [2, 21, 4], [2, 22, 2], [2, 23, 4],
    [3, 0, 7], [3, 1, 3], [3, 2, 0], [3, 3, 0], [3, 4, 0], [3, 5, 0],
    [3, 6, 0], [3, 7, 0], [3, 8, 1], [3, 9, 0], [3, 10, 5], [3, 11, 4],
    [3, 12, 7], [3, 13, 14], [3, 14, 13], [3, 15, 12], [3, 16, 9], [3, 17, 5],
    [3, 18, 5], [3, 19, 10], [3, 20, 6], [3, 21, 4], [3, 22, 4], [3, 23, 1],
    [4, 0, 1], [4, 1, 3], [4, 2, 0], [4, 3, 0], [4, 4, 0], [4, 5, 1],
    [4, 6, 0], [4, 7, 0], [4, 8, 0], [4, 9, 2], [4, 10, 4], [4, 11, 4],
    [4, 12, 2], [4, 13, 4], [4, 14, 4], [4, 15, 14], [4, 16, 12], [4, 17, 1],
    [4, 18, 8], [4, 19, 5], [4, 20, 3], [4, 21, 7], [4, 22, 3], [4, 23, 0],
    [5, 0, 2], [5, 1, 1], [5, 2, 0], [5, 3, 3], [5, 4, 0], [5, 5, 0],
    [5, 6, 0], [5, 7, 0], [5, 8, 2], [5, 9, 0], [5, 10, 4], [5, 11, 1],
    [5, 12, 5], [5, 13, 10], [5, 14, 5], [5, 15, 7], [5, 16, 11], [5, 17, 6],
    [5, 18, 0], [5, 19, 5], [5, 20, 3], [5, 21, 4], [5, 22, 2], [5, 23, 0],
    [6, 0, 1], [6, 1, 0], [6, 2, 0], [6, 3, 0], [6, 4, 0], [6, 5, 0],
    [6, 6, 0], [6, 7, 0], [6, 8, 0], [6, 9, 0], [6, 10, 1], [6, 11, 0],
    [6, 12, 2], [6, 13, 1], [6, 14, 3], [6, 15, 4], [6, 16, 0], [6, 17, 0],
    [6, 18, 0], [6, 19, 0], [6, 20, 1], [6, 21, 2], [6, 22, 2], [6, 23, 6]
    ]
range_color = ['#313695', '#4575b4', '#74add1', '#abd9e9', '#e0f3f8', '#ffffbf',
               '#fee090', '#fdae61', '#f46d43', '#d73027', '#a50026']
bar3d.add(
    "",
    x_axis,
    y_axis,
    [[d[1], d[0], d[2]] for d in data],
    is_visualmap=True,
    visual_range=[0, 20],
    visual_range_color=range_color,
    grid3d_width=200,
    grid3d_depth=80,
)
bar3d.width=700
bar3d.height=500

bar3d
Out[117]:
from pyecharts import Boxplot

boxplot = Boxplot("Box plot")
x_axis = ['expr1', 'expr2', 'expr3', 'expr4', 'expr5']
y_axis = [
    [850, 740, 900, 1070, 930, 850, 950, 980, 980, 880,
    1000, 980, 930, 650, 760, 810, 1000, 1000, 960, 960],
    [960, 940, 960, 940, 880, 800, 850, 880, 900, 840,
    830, 790, 810, 880, 880, 830, 800, 790, 760, 800],
    [880, 880, 880, 860, 720, 720, 620, 860, 970, 950,
    880, 910, 850, 870, 840, 840, 850, 840, 840, 840],
    [890, 810, 810, 820, 800, 770, 760, 740, 750, 760,
    910, 920, 890, 860, 880, 720, 840, 850, 850, 780],
    [890, 840, 780, 810, 760, 810, 790, 810, 820, 850,
    870, 870, 810, 740, 810, 940, 950, 800, 810, 870]
]
_yaxis = boxplot.prepare_data(y_axis)
boxplot.add("boxplot", x_axis, _yaxis)
boxplot
Out[118]:
from pyecharts import Funnel

attr = ["A", "B", "C", "D", "E", "F"]
value = [20, 40, 60, 80, 100, 120]
funnel = Funnel("퍼널 그래프")
funnel.add(
    "퍼널",
    attr,
    value,
    is_label_show=True,
    label_pos="inside",
    label_text_color="#fff",
)
funnel.width=700
funnel.height=500
funnel
Out[119]:
from pyecharts import Gauge

gauge = Gauge("Gauge Graph")
gauge.add("이용률", "가운데", 66.66)
gauge
Out[120]:

카일스쿨 유튜브 채널을 만들었습니다. 데이터 사이언스, 성장, 리더십, BigQuery 등을 이야기할 예정이니, 관심 있으시면 구독 부탁드립니다 :)

PM을 위한 데이터 리터러시 강의를 만들었습니다. 문제 정의, 지표, 실험 설계, 문화 만들기, 로그 설계, 회고 등을 담은 강의입니다

이 글이 도움이 되셨거나 다양한 의견이 있다면 댓글 부탁드립니다 :)

Buy me a coffeeBuy me a coffee





© 2017. by Seongyun Byeon

Powered by zzsza