Python에서 데이터 시각화하는 다양한 방법
in Development on 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
- Python에서 아마 가장 많이 쓰는 라이브러리입니다
pandas
의 Dataframe을 바로 시각화할 때도 내부적으로 matplotlib을 사용합니다- 설치
pip3 install matplotlib
- Matplotlib Tutorials
- 데이터 사이언스 스쿨
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'
Figure
- Figure는 그림이 그려지는 도화지라고 생각할 수 있습니다
- 우선 Figure를 그린 후,
plt.subplots
로 도화지를 분할해 각 부분에 그래프를 그리는 방식으로 진행합니다 plt.figure
를 명시적으로 표현해주는 것이 좋으나, plot 함수에서 자동으로 figure를 생성하기 때문에 자주 사용하진 않습니다- 그러나 현재 figure에 접근해야 할 필요성이 있다면,
plt.gcf()
로 접근할 수 있습니다
- 우선 Figure를 그린 후,
- 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()
current_palette = sns.color_palette()
sns.palplot(current_palette)
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()
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");
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);
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__)
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에서 확인할 수 있습니다
- 참고 자료
- 공식 문서
- 진현수님의 Github : 서울지역 범죄 데이터를 시각화한 노트북 파일입니다
- PinkWink님의 블로그
- 그 외에도 pydeck, ipyleaflet 등으로 지도 시각화를 할 수 있습니다
설치
pip3 install folium
import folium
print("folium version is", folium.__version__)
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]:
import plotly
print("plotly version :", plotly.__version__)
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__)
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]:
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을 위한 데이터 리터러시 강의를 만들었습니다. 문제 정의, 지표, 실험 설계, 문화 만들기, 로그 설계, 회고 등을 담은 강의입니다
이 글이 도움이 되셨거나 다양한 의견이 있다면 댓글 부탁드립니다 :)