시각화를 하다보면 기존의 그래프에 가독성을 높이기 위해 여러가지 종류의 도형을 추가해야 할 때가 있습니다.  Python  matplotlib.artist 의 Patch 클래스는 아래의 화면 캡쳐에서 보는 것처럼 Shadow, Spine, Wedge, Circle, Ellipse, Arrow, Rectangle, RegularPolygon, FancyArrowPatch, PathPatch 등의 다양한 도형을 쉽게 그릴 수 있는 클래스를 지원합니다. 

 

 

[ matplotlib artist inheritance diagrams, reference for matplotlib artists ]

 

matplotlib.artist Inheritance Diagrams and Reference for Matplotlib artists (* source: matplotlib.org)

 

이들 Reference for Matplotlib artists 중에서 이번 포스팅에서는 matplotlib의 add_patch() 함수matplotlib.patches 클래스의 다양한 artists 를 사용해서 

 

(1) matplotlib 그래프에 직사각형 추가하기 (adding a Rectangle in matplotlib's plot)

(2) matplotlib 그래프에 원 추가하기 (adding a Circle in matplotlib's plot)

(3) matplotlib 그래프에 타원 추가하기 (adding a Ellipse in matplotlib's plot)

(4) matplotlib 그래프에 다각형 추가하기 (adding a Polygon in matplotlib's plot)

(5) matplotlib 그래프에 화살표 추가하기 (adding a Arrow in matplotlib's plot)

 

하는 방법을 소개하겠습니다. 

 

 

먼저, 시각화를 위해 필요한 Python 모듈을 불러오고, 예제로 사용할 간단한 샘플 데이터셋을 만들어보겠습니다. 

 

## importing modules for visualization and data generation
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np


## generating sample datasets
np.random.seed(1004)
x1 = np.random.uniform(low=1.5, high=4.5, size=100)
y1 = np.random.uniform(low=1.5, high=2.5, size=100)
x2 = np.random.normal(loc=7, scale=0.6, size=100)
y2 = np.random.normal(loc=7, scale=0.6, size=100)
x = np.concatenate((x1, x2), axis=0)
y = np.concatenate((y1, y2), axis=0)

 

 

(1) matplotlib 그래프에 직사각형 추가하기 (adding a Rectangle in matplotlib's plot)

 

코드가 추가적인 설명이 필요 없을 정도로 간단하므로 예제 코드와 그 결과로 그려진 그래프 & 추가된 도형 위에 matplotlib.matches 클래스의 각 artist 별 매개변수를 표기해놓는 것으로 설명을 갈음하겠습니다. 

 

import matplotlib.pyplot as plt
import matplotlib.patches as patches

## plotting
fig, ax = plt.subplots(figsize=(7, 7))
ax.set_title('Adding a Rectangle in plot', fontsize=16)
ax.set_xlim(0.0, 10.0)
ax.set_ylim(0.0, 10.0)
ax.set_xlabel('X')
ax.set_ylabel('Y')

ax.plot(x, y, 'ro', alpha=0.5)

## (1) a Rectangle patch
## https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Rectangle.html
ax.add_patch(
   patches.Rectangle(
       (1.0, 1.0),      # (x, y) coordinates of left-bottom corner point
       4, 2,            # width, height
       edgecolor = 'blue',
       linestyle = 'dashed', 
       fill = True,
       facecolor = 'yellow',
   ))

plt.show()

adding a patch of a rectangle in matplotlib

 

 

 

(2) matplotlib 그래프에 원 추가하기 (adding a Circle in matplotlib's plot)

 

import matplotlib.pyplot as plt
import matplotlib.patches as patches

## plotting
fig, ax = plt.subplots(figsize=(7, 7))
ax.set_title('Adding a Circle in plot', fontsize=16)
ax.set_xlim(0.0, 10.0)
ax.set_ylim(0.0, 10.0)
ax.set_xlabel('X')
ax.set_ylabel('Y')

ax.plot(x, y, 'ro', alpha=0.5)

## (2) a Circle patch
## https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Circle.html
ax.add_patch(
   patches.Circle(
       (7.0, 7.0),      # (x, y) coordinates of center point
       radius = 2.,     # radius
       edgecolor = 'black',
       linestyle = 'dotted', 
       fill = True,
       facecolor = 'lightgray',
   ))

plt.show()

adding a patch of a circle in matplotlib

 

 

 

(3) matplotlib 그래프에 타원 추가하기 (adding a Ellipse in matplotlib's plot)

 

import matplotlib.pyplot as plt
import matplotlib.patches as patches

## plotting
fig, ax = plt.subplots(figsize=(7, 7))
ax.set_title('Adding an Ellipse in plot', fontsize=16)
ax.set_xlim(0.0, 10.0)
ax.set_ylim(0.0, 10.0)
ax.set_xlabel('X')
ax.set_ylabel('Y')

ax.plot(x, y, 'ro', alpha=0.5)

## (3) A scale-free ellipse.
## https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Ellipse.html
ax.add_patch(
   patches.Ellipse(
       xy = (5, 5), # xy xy coordinates of ellipse centre.
       width = 5,   # width Total length (diameter) of horizontal axis.
       height = 10, # height Total length (diameter) of vertical axis.
       angle = -40, # angle Rotation in degrees anti-clockwise. 0 by default
       edgecolor = 'black',
       linestyle = 'solid', 
       fill = True,
       facecolor = 'yellow',
   ))

plt.show()

adding a patch of an ellipse in matplotlib

 

 

 

(4) matplotlib 그래프에 다각형 추가하기 (adding a Polygon in matplotlib's plot)

 

다각형(Polygon)을 추가하려면 다각형의 각 꼭지점의 xy 좌표의 모음을 numpy array 형태로 입력해주면 됩니다. 그리고 closed=True 를 설정해주면 xy 좌표들의 모음의 처음 좌표와 마지막 좌표를 연결해서 다각형의 닫아주어서 완성해줍니다. 

 

import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np

## plotting
fig, ax = plt.subplots(figsize=(7, 7))
ax.set_title('Adding a Polygon in plot', fontsize=16)
ax.set_xlim(0.0, 10.0)
ax.set_ylim(0.0, 10.0)
ax.set_xlabel('X')
ax.set_ylabel('Y')

ax.plot(x, y, 'ro', alpha=0.5)

## (4) a Polygon patch
## https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Polygon.html
ax.add_patch(
   patches.Polygon(
       # xy is a numpy array with shape Nx2.
       np.array([[1, 1], [5, 1], [9, 5], [9, 9], [6, 9], [1, 4]]), # xy
       closed=True,
       edgecolor = 'black',
       linestyle = 'dashdot', 
       fill = True,
       facecolor = 'lightgray',
   ))

plt.show()

adding a patch of a polygon in matplotlib

 

 

 

(5) matplotlib 그래프에 화살표 추가하기 (adding a Arrow in matplotlib's plot)

 

import matplotlib.pyplot as plt
import matplotlib.patches as patches

## plotting
fig, ax = plt.subplots(figsize=(7, 7))
ax.set_title('Adding an Arrow in plot', fontsize=16)
ax.set_xlim(0.0, 10.0)
ax.set_ylim(0.0, 10.0)
ax.set_xlabel('X')
ax.set_ylabel('Y')

ax.plot(x, y, 'ro', alpha=0.5)

## (5) a Arrow patch
## https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Arrow.html
ax.add_patch(
   patches.Arrow(
       x = 3, # x coordinate of the arrow tail.
       y = 5, # y coordinate of the arrow tail.
       dx = 0, # dx Arrow length in the x direction.
       dy = -2, # dy Arrow length in the y direction.
       # width: Scale factor for the width of the arrow. 
       width = 1, # With a default value of 1, the tail width is 0.2 and head width is 0.6.
       edgecolor = 'black',
       linestyle = 'solid', 
       fill = True,
       facecolor = 'yellow',
   ))

plt.show()

adding a patch of an arrow in matplotlib

 

 

 

[ Reference ]

* Python matplotlib's Inheritance Diagrams 

   : https://matplotlib.org/stable/api/artist_api.html#artist-api

* Reference for matplotlib artists

   : https://matplotlib.org/stable/gallery/shapes_and_collections/artist_reference.html#sphx-glr-gallery-shapes-and-collections-artist-reference-py 

* Python matplotlib's Rectangle patch
   : https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Rectangle.html

* Python matplotlib's Circle patch
   : https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Circle.html

* Python matplotlib's scale-free Ellipse
   : https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Ellipse.html

* Python matploblib's Polygon patch
   : https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Polygon.html

* Python matplotlib's Arrow patch
   : https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Arrow.html

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

반응형
Posted by Rfriend

댓글을 달아 주세요

산점도 (Scatter plot) 가 두 변수 간의 관계를 2차원(2D으로 시각화해서 볼 수 있는 그래프라면, 버블 그래프(Bubble chart)는 산점도에 버블의 크기와 색깔을 추가하여 3차원(3D) 또는 4차원(4D)의 정보를 2차원에 시각화해서 볼 수 있는 그래프입니다. 그리고 시간 축을 추가하여 애니메이션 형태의 버블 그래프를 그린다면 5차원(5D)의 시각화도 가능합니다. 

 

이번 포스팅에서는 Python의 matplotlib, seaborn, plotly, bubbly 등의 시각화 모듈을 이용해서 버블 그래프를 그리는 방법을 소개하겠습니다. 

 

(1) matplotlib 으로 버블 그래프 그리기 (Bubble chart using Matplotlib)

(2) seaborn 으로 버블 그래프 그리기 (Bubble chart using Seaborn)

(3) plotly 로 동적인 버블 그래프 그리기 (Interactive Bubble chart using Plotly)

(4) bubbly 로 시간의 흐름에 따라 변화하는 버블 그래프 그리기 (Animated Bubble chart using bubbly)

 

 

plotly 모듈에 들어있는 Gapminder  Global Indicator 데이터셋을 대상으로 버블 그래프를 그려보겠습니다. x 축에는 gpdPercap, y 축에는 lifeExp 를 사용하겠으며, pop 에 비례해서 버블의 크기를 조절하고, continent 에 따라서 버블의 색깔을 다르게 해보겠습니다. 

 

## importing modules
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
import pandas as pd
import numpy as np

## getting dataset
df = px.data.gapminder()


df.info()
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 1704 entries, 0 to 1703
# Data columns (total 8 columns):
#  #   Column     Non-Null Count  Dtype  
# ---  ------     --------------  -----  
#  0   country    1704 non-null   object 
#  1   continent  1704 non-null   object 
#  2   year       1704 non-null   int64  
#  3   lifeExp    1704 non-null   float64
#  4   pop        1704 non-null   int64  
#  5   gdpPercap  1704 non-null   float64
#  6   iso_alpha  1704 non-null   object 
#  7   iso_num    1704 non-null   int64  
# dtypes: float64(2), int64(3), object(3)
# memory usage: 106.6+ KB


df.head(3)
# 	country	continent	year	lifeExp	pop	gdpPercap	iso_alpha	iso_num
# 0	Afghanistan	Asia	1952	28.801	8425333	779.445314	AFG	4
# 1	Afghanistan	Asia	1957	30.332	9240934	820.853030	AFG	4
# 2	Afghanistan	Asia	1962	31.997	10267083	853.100710	AFG	4


df.year.max() # ==> keep year 2007
# 2007


df.gdpPercap.describe()   # right-skewed => log scaling
# count      1704.000000
# mean       7215.327081
# std        9857.454543
# min         241.165877dd
# 25%        1202.060309
# 50%        3531.846989
# 75%        9325.462346
# max      113523.132900
# Name: gdpPercap, dtype: float64

 

 

(1) matplotlib 으로 버블 그래프 그리기 (Bubble chart using Matplotlib)

 

matplotlib.pyplot.scatter() 메소드를 사용해서 산점도를 그리면서, 버블의 크기(s)와 색깔(c)을 추가로 지정해주면 됩니다. continent 별로 색깔을 다르게 지정해주었는데요, 아래의 범례(plt.legend()) 설정 방법 참고하세요. alpha=0.5 로 버블이 겹쳤을 때 파악할 수 있도록 투명도를 설정해주었습니다. 

 

x축의 gdpPercap 변수가 왼쪽으로 값이 많이 쏠려있는 형태를 띠고 있으므로 plt.xscale('log') 을 사용해서 x축을 로그 변환 (log transformation) 해주었습니다. plt.grid() 로 점선을 사용해서 그리드 선을 추가해주었습니다. 

 

## (1) Bubble chart with Matplotlib
fig = plt.figure(figsize=(16, 9))
df_2007 = df[df['year']==2007]

ax = plt.scatter(
    x = df_2007['gdpPercap'], 
    y = df_2007['lifeExp'], 
    s = df_2007['pop']/300000, 
    c = pd.Categorical(df_2007['continent']).codes, 
    cmap = "Accent", 
    alpha = 0.5, 
    edgecolors = "gray", 
    linewidth = 1);

plt.xscale('log') # log scaling
plt.xlabel("GDP per Capita")
plt.ylabel("Life Expectancy")
plt.title('Gapminder Global Indicators with Matplotlib', fontsize=16)
plt.grid(True, which='major', linestyle='--', linewidth=0.5)
## setting legend using colors in matplotlib scatter plot
plt.legend(
    handles=ax.legend_elements()[0], 
    labels=['Asia', 'Europe', 'Africa', 'Americas', 'Oceania'])

plt.show()

bubble chart using matplotlib

 

 

 

(2) seaborn 으로 버블 그래프 그리기 (Bubble chart using Seaborn)

 

위의 matplotlib 대비 seaborn.scatter() 메소드는 x축, y축, size, hue(그룹별 색깔 구분) 을 각각 지정해줄 수 있어서 비슷한데요, legend=True 로 지정해주면 size 와 hue (그룹별 색깔 구분) 에 대한 범례를 자동으로 추가해주어서 편리합니다. 

 

## (2) Bubble chart with Seaborn
fig = plt.figure(figsize=(16, 9))
df_2007 = df[df['year']==2007]

sns.scatterplot(
    data = df_2007, 
    x = "gdpPercap", 
    y = "lifeExp", 
    size = "pop", 
    hue = "continent",
    legend = True, 
    sizes = (20, 2000) ,
    alpha=0.5
)

plt.xscale('log') # log scaling
plt.xlabel("GDP per Capita")
plt.ylabel("Life Expectancy")
plt.title('Gapminder Global Indicators with Seaborn', fontsize=16)
plt.grid(True, which='major', linestyle='--', linewidth=0.5)
plt.show()

bubble chart using seaborn

 

 

 

(3) plotly 로 동적인 버블 그래프 그리기 (Interactive Bubble chart using Plotly)

 

plotly 는 위의 matplotlib, seaborn 대비 사용자가 커서를 사용해서 동적으로 버블 그래프(interactive bubble chart)를 조회할 수 있다는 점입니다. hover_name="country" 를 지정해주면 버블 위에 커서를 가져다 놓을 경우 국가 이름과 함께 x축, y축, 버블 크기, 색깔 지정 정보를 팝업 메시지로 보여줍니다. 그리고 커서로 블록을 설정해주면 해당 블록에 대해 줌인(zoom in) 해서 확대해주는 기능도 있습니다. 그 외에 첫번째 줄의 df.query("year==2007") 처럼 px.scatter() 함수 안에서 데이터의 subset 을 query 해서 쓸 수 있으며, log_x = True 를 설정하면 x축의 값을 로그 변환하도록 설정해줄 수도 있습니다. 코드가 매우 깔끔하고 그래프가 이쁜데다 동적이기까지 해서 가독성이 매우 뛰어나기 때문에 왠만하면 matplotlib 이나 seaborn 대신에 plotly 를 사용하는 것이 사용자 입장에서는 좋을 것 같아요. 

 

## (3) Bubble chart with Plotly
import plotly.express as px

fig = px.scatter(
    df.query("year==2007"), 
    x="gdpPercap", 
    y="lifeExp",
    size="pop", 
    color="continent",
    hover_name="country", 
    log_x=True, 
    title='Gapminder Global Indicators with Plotly',
    size_max=60,
    height=650)

fig.show()

 

아래의 Plotly 로 그린 버블 그래프의 버블 위에 커서를 가져가면 뜨는 캡션 정보와, 커서로 블록을 선정해서 hover 해서 zoom in 하는 모습을 화면 녹화한 것이예요. 코드 몇줄로 이게 된다는게 신기하지요?! plotly 짱! ^^

 

 

 

 

(4) bubbly 로 시간의 흐름에 따라 변화하는 버블 그래프 그리기 (Animated Bubble chart using bubbly)

 

이번에는 시간 축을 추가 (adding timestamp axis)해서 시간의 흐름에 따라서 버블 그래프가 변화하는 애니메이션을 시각화 해보겠습니다. bubbly 모듈의 bubbleplot() 메소드를 사용하며, time_column='year' 에 년도(year)를 시간 축으로 설정해주었습니다. x축은 x_column, y축은 y_column, 버블 크기는 size_column, 버블 색깔은 color_column 로 설정해주면 됩니다. x_logscale=True 로 x축 값을 로그 변환해주었으며, scale_bubble=3 으로 버블의 스케일을 지정해주었습니다. 

 

#!pip install bubbly

## (4) Animated Bubble Chart
## Using the function bubbleplot from the module bubbly(bubble charts with plotly)
## ref: https://github.com/AashitaK/bubbly

from bubbly.bubbly import bubbleplot 
from __future__ import division
from plotly.offline import init_notebook_mode, iplot
init_notebook_mode()

figure = bubbleplot(
    dataset=df, 
    x_column='gdpPercap', 
    y_column='lifeExp', 
    bubble_column='country', 
    time_column='year', 
    size_column='pop', 
    color_column='continent', 
    x_title="GDP per Capita", 
    y_title="Life Expectancy", 
    title='Animated Gapminder Global Indicators with Bubbly',
    x_logscale=True, 
    scale_bubble=3, 
    height=650)

iplot(figure, config={'scrollzoom': True})

 

 

아래는 애니메이션 버블 차트를 실행시킨 모습을 화면 녹화한 것이예요. 왼쪽 하단의 'Play' 버튼을 누르면 애니메이션이 실행이 되면서 년도의 순서대로 버블 차트가 변화는 것을 볼 수 있습니다. 신기하지요?! ^^ bubbly 짱!! (bubbly 의 백본이 plotly 이므로 역시 plotly 짱!!)

 

 

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

반응형
Posted by Rfriend

댓글을 달아 주세요

Sankey Diagram 은 화살표 너비가 흐름 속도에 비례(the width of the arrows is proportional to the flow rate)하는 흐름 다이어그램의 한 유형(a type of flow diagram)입니다. Sankey Diagram은 또한 에너지 계정, 지역 또는 국가 차원의 자재 흐름 계정 및 비용 분석을 시각화할 수 있습니다. 다이어그램은 종종 재료 흐름 분석의 시각화에 사용됩니다. 


Sankey Diagram은 시스템 내의 주요 전송 또는 흐름(major transfers or flows)을 강조합니다. 그들은 흐름에 대한 가장 중요한 기여의 위치를 찾는데 도움을 줍니다. 그것들은 종종 정의된 시스템 경계 내에서 보존된 양을 보여줍니다. [1]

 

 

Python에서 Sankey Diagram 을 그리는 모듈로 HoloViews 와 plotly 가 있는데요, HoloViews 가 구문이 좀더 직관적이어서 이해하기가 쉽습니다. 

 

이번 포스팅에서는 Python의 HoloViews 모듈을 사용해서 Jupyter Notebook에서 Sankey Diagram 을 시각화하는 방법을 소개하겠습니다. [2]

 

HoloViews 모듈은 백본으로 Matplotlib 과 Bokeh 를 사용하므로, 만약 이들 모듈이 설치되어 있지 않다면 pip install 로 먼저 이들 모듈을 설치해주어야 합니다. 

 

 

## in a command line

$ pip install bokeh
$ pip install holoviews

 

Sankey elements represent flows and their quantities in proportion to one another. The data of a Sankey element defines a directed, acyclic graph, making it a specialized subclass of the Graph element. The width of the lines in a Sankey diagram represent the magnitudes of each edge. Both the edges and nodes can be defined through any valid tabular format including pandas dataframes, dictionaries/tuples of columns, NumPy arrays and lists of tuples.

The easiest way to define a Sankey element is to define a list of edges and their associated quantities:

 

Sankey 요소는 흐름과 흐름의 양을 서로 비례하여 나타냅니다. Sankey 요소의 데이터는 방향성 있는 비순환 그래프(directed, acyclic graph)를 정의하여 그래프 요소의 특수 하위 클래스로 만듭니다. Sankey Daigram 에서 선의 너비는 각 Edge의 크기를 나타냅니다. Edge와 Node 모두 pandas DataFrame, Dictionaries/Tuples of columns, NumPy Array 및 Lists of Tuples을 포함한 유효한 표 형식을 통해 정의할 수 있습니다. 이게 바로 Holoviews 가 편리하고 직관적인 이유예요. 

 

Sankey 요소를 정의하는 가장 쉬운 방법은 list of edges과 관련 수량을 정의하는 것입니다. 아래 예에서는 A, B node에서 X, Y, Z node 로의 흐름의 관계와 양을 리스트 형식으로 정의한 것입니다. [2]

 

import holoviews as hv
from holoviews import opts, dim
hv.extension('bokeh')

# defining a list of edges and their associated quantities
sankey = hv.Sankey([
    ['A', 'X', 10],
    ['A', 'Y', 20],
    ['A', 'Z', 30],
    ['B', 'X', 60],
    ['B', 'Y', 5],
    ['B', 'Z', 40]]
)

sankey.opts(width=800, height=500)

sankey diagram using holoviews module in python

 

 

 

아래의 예시는 2010년 왕립학회 정책 보고서 "The Scientific Century: securing our future prosperity"에 설명된 영국 박사과정 학생들의 진로에 대한 간단한 데이터 세트를 Sankey Diagram으로 그려본 것입니다. 우리는 정수 지수로 열거된 노드와 각 경력 단계 사이에 흐르는 백분율을 정의합니다. 마지막으로 "To"라는 레이블을 붙이는 대상 노드에 의해 값과 색상에 대한 단위를 가진 차원을 정의합니다. [2]

 

위의 예에서는 nodes의 이름을 그대로 사용하였다면, 아래의 예에서는 nodes에 대해서 enumerate() 함수를 사용해서 index 정수를 매핑해서 holoveiws Dataset 객체로 만들어 주고, 이를 edges 의 관계를 정의할 때 index 정수를 사용하였습니다. 

 

nodes = ["PhD", "Career Outside Science",  "Early Career Researcher", "Research Staff",
         "Permanent Research Staff",  "Professor",  "Non-Academic Research"]
for i, j in enumerate(nodes):
    print(i, ':', j)
    
# 0 : PhD
# 1 : Career Outside Science
# 2 : Early Career Researcher
# 3 : Research Staff
# 4 : Permanent Research Staff
# 5 : Professor
# 6 : Non-Academic Research

 

 

Label의 위치를 오른쪽으로 바꾸어 주었으며 (label_position='right'), cmap, edge_color, node_color 로 edge와 node 의 색깔도 다르게 설정해주었습니다. 색깔이 다르니 가독성이 한결 좋아졌습니다. 

 

## the edges are expressed as integer node indexes and labels are provided separately.
## We can explicitly define the set of nodes as a Dataset of indexes and labels as key 
## and value dimensions respectively.
nodes = ["PhD", "Career Outside Science",  "Early Career Researcher", "Research Staff",
         "Permanent Research Staff",  "Professor",  "Non-Academic Research"]
nodes = hv.Dataset(enumerate(nodes), 'index', 'label')

edges = [
    (0, 1, 53), 
    (0, 2, 47), 
    (2, 6, 17), 
    (2, 3, 30), 
    (3, 1, 22.5), 
    (3, 4, 3.5), 
    (3, 6, 4.), 
    (4, 5, 0.45)   
]

value_dim = hv.Dimension('Percentage', unit='%')
careers = hv.Sankey((edges, nodes), ['From', 'To'], vdims=value_dim)

careers.opts(
    opts.Sankey(labels='label', 
                label_position='right', # adjust the label_position from "right" to "left"
                width=900, height=500, 
                cmap='Set1',
                edge_color=dim('To').str(), # setting edge color
                node_color=dim('index').str()) # setting node color
)

sankey diagram using holoviews in python

 

 

 

Interactive Diagram 이어서 커서를 가져다대면 아래의 화면 캡쳐와 같이 해당 Edge 가 하이라이드 되며 해당 Edge의 From note, To note, Percentage 등의 캡션 정보를 팝업으로 볼 수 있습니다.  (아래는 화면 캡쳐이므로 Interactive 기능 없음. Jupyter Notebook에서 실행해야지 Interactive 기능 사용 가능함)

 

 

[ Reference ]

 

[1] Introduction to Sankey Diagram: https://en.wikipedia.org/wiki/Sankey_diagram

[2] Sankey Element using HoloViews: https://holoviews.org/reference/elements/bokeh/Sankey.html

[3] R로 Sankey Diagram 시각화 하기: https://rfriend.tistory.com/220

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

반응형
Posted by Rfriend

댓글을 달아 주세요

이번 포스팅에서는 Python의 numpy 2D 행렬에서 행 기준, 열기준 백분율을 구하여 히트맵을 그리는 방법을 소개하겠습니다. 

 

(1) numpy 2D array 의 행 기준 백분율을 구해서 히트맵 그리기

     (Plotting the heatmap of percentages by row in numpy 2D array)

(2) numpy 2D array 의 열 기준 백분율을 구해서 히트맵 그리기 

     (Plotting the heatmap of percentages by column in numpy 2D array)

 

 

먼저, 예제로 사용할 numpy 2D array 를 0~4 사이의 정수 난수를 생성해서 만들어보겠습니다. 

 

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

## creating a sample dataset of numpy 2D array
np.random.seed(1004)
mat = np.random.randint(low=0, high=5, size=(5, 5))

print(mat)
# [[2 3 3 4 1]
#  [2 0 0 4 4]
#  [0 2 4 2 3]
#  [3 0 4 3 0]
#  [0 2 4 1 4]]

 

 

(1) numpy 2D array 의 행 기준 백분율을 구해서 히트맵 그리기

     (Plotting the heatmap of percentages by row in numpy 2D array)

 

 

numpy 2D array 에서 행 기준 백분율을 구하기 위해서는 행 기준의 합(summation by row, sum(axis=1, keepdims=True)) 이 필요합니다.  

 

이때 mat.sum(axis=1, keepdims=True) 처럼 keepdims=True 옵션을 표기해줌으로써 shape(5, 1) 의 각 행별 합계를 구할 수 있습니다. 만약 keepdims 옵션을 명기하지 않을 경우 디폴트로서 keepdims=False (by default) 가 적용이 되어서 shape(5,)  의 array([13, 10, 11, 10, 11]) 의 계산 결과가 나오며, 이걸로 나누어 주면 행 기준 백분율이 아니라 element-wise 나눗셈이 되어서 전혀 엉뚱한 계산결과가 나오므로 주의가 필요합니다. 

 

## (1) plotting heatmap of numpy percentages along axis 1 in 2D array

## row sums in matrix using 'keepdims=True' option
row_sum = mat.sum(axis=1, keepdims=True)

print(row_sum)
# [[13]
#  [10]
#  [11]
#  [10]
#  [11]]


## when 'keepdims=False' (by default)
mat.sum(axis=1)
# array([13, 10, 11, 10, 11])

 

 

 

위에서 구한 shape (5, 1) 의 행 기준 합계  row_sum 행렬로 원래의 2D array 행렬 mat  을 나누어주면, numpy 2D array에서 행 기준의 백분율을 구할 수 있습니다. np.round(x, decimals=2) 함수를 사용해서 백분율 계산 결과를 소수점 2째 자리까지만 나오도록 반올림을 해주었습니다. 

 

## calculating row percentage in matrix
mat_row_pct = mat / row_sum

## Round array elements to the given number of decimals
mat_row_pct = np.round(mat_row_pct, decimals=2)

print(mat_row_pct)
# [[0.15 0.23 0.23 0.31 0.08]
#  [0.2  0.   0.   0.4  0.4 ]
#  [0.   0.18 0.36 0.18 0.27]
#  [0.3  0.   0.4  0.3  0.  ]
#  [0.   0.18 0.36 0.09 0.36]]

 

 

 

이제 matplotlib 의 matshow() 함수와 colormap 을 사용해서, 위에서 구한 행 기준 백분율에 대해 히트맵을 그려보겠습니다. colormap 으로는 순차적으로 파란색이 진해지(sequential colormaps)는 camp='Blues' 를 사용하였습니다. 그리고 해석을 편리하게 할 수 있도록 plt.colorbar() 함수를 사용해서 칼라의 강도별 백분율을 legend colorbar 를 만들어주었습니다. 아래에 히트맵을 보면 한눈에 행 기준의 백분율이 어디 셀이 높고 어디 셀이 낮은지 한눈에 금방 알아볼 수 있습니다. 

 

## plotting the heatmap of matrix row percentage (axis=1) using colormaps
## ref: https://matplotlib.org/stable/tutorials/colors/colormaps.html

plt.rcParams['figure.figsize'] = (8, 8) # figure size
plt.matshow(mat_row_pct, cmap='Blues') # sequential colormaps
plt.title('Heatmap of row percentage in matrix', fontsize=16)
plt.colorbar(label='percentages along axis 1') # colorbar legend
plt.show()

heatmap of row percentage in numpy 2D array

 

 

 

 

(2) numpy 2D array 의 열 기준 백분율을 구해서 히트맵 그리기 

     (Plotting the heatmap of percentages by column in numpy 2D array)

 

이번에는 mat.sum(axis=0, keepdims=True)  로 numpy 2D array에서 열 기준으로 백분율(percentages by column in numpy 2D array)을 구해보겠습니다. 앞에서와는 다르게 열 기준 합을 구할 때는 axis = 0 을 사용하였습니다. 

(열 기준 백분율을 구할 때는 keepdims=False 로 해도 결과는 동일합니다.)

 

## (2) plotting heatmap of numpy percentages along axis 0 in 2D array
## row sums in matrix
print(mat)
# [[2 3 3 4 1]
#  [2 0 0 4 4]
#  [0 2 4 2 3]
#  [3 0 4 3 0]
#  [0 2 4 1 4]]

col_sum = mat.sum(axis=0, keepdims=True)

print(col_sum)
# [[ 7  7 15 14 12]]

 

 

 

이제 위에서 구한 열 기준 합으로 원래의 2D array 를 나누어주어서 열 기준 백분율을 구하겠습니다. 그리고 np.round(array, decimals=2) 함수를 사용해서 백분율 행렬의 원소 값을 소수점 2째 자리까지만 나오도록 반올림 해보겠습니다. 

 

## calculating row percentage in matrix
mat_col_pct = mat / col_sum

## Round array elements to the given number of decimals
mat_col_pct = np.round(mat_col_pct, decimals=2)

print(mat_col_pct)
# [[0.29 0.43 0.2  0.29 0.08]
#  [0.29 0.   0.   0.29 0.33]
#  [0.   0.29 0.27 0.14 0.25]
#  [0.43 0.   0.27 0.21 0.  ]
#  [0.   0.29 0.27 0.07 0.33]]

 

 

 

2D array 에서 열 기준 백분율이 준비가 되었으니 matplotlib의 plt.matshow() 함수에 순차적으로 빨간색의 강도가 진해지는 colormap 으로서 cmap='Reds' 를 사용하여 히트맵을 그려보겠습니다. 

 

## plotting the heatmap of matrix column percentage (axis=0) using colormaps
## ref: https://matplotlib.org/stable/tutorials/colors/colormaps.html
plt.rcParams['figure.figsize'] = (8, 8)
plt.matshow(mat_col_pct, cmap='Reds') # sequential colormaps
plt.title('Heatmap of row percentage in matrix', fontsize=16)
plt.colorbar(label='percentages along axis 0') # colorbar legend
plt.show()

colormap of percentages by column in numpy 2D array

 

 

[Reference]

* choosing colormaps in matplotlib: https://matplotlib.org/stable/tutorials/colors/colormaps.html

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

반응형
Posted by Rfriend

댓글을 달아 주세요

이번 포스팅에서는 Python의 pandas DataFrame을 대사응로 matplotlib 그래프를 그렸을 때, X축 범주형 항목의 순서(changing the order of x-axis ticks in python matplotlib plot)를 변경하는 방법을 소개하겠습니다. 

 

(1) pandas DataFrame의 x 축으로 사용되는 범주형 값을 loc 를 사용해 재정렬하기

(2) index 가 재정렬된 pandas.DataFrame에 대해서 matplotlib 으로 그래프 그리기

 

changing the order of x-axis ticks in matplotlib plot

 

먼저, 예제로 사용할 간단한 pandas DataFrame을 만들어 보겠습니다. 요일(weekday dates) 별 값(value) 으로 구성된 DataFrame에 대해서 groupby() 연산자를 사용해서 요일별 값의 합을 집계(aggregation by weekday) 한 DataFrame을 만들었습니다. 

 

import pandas as pd
import matplotlib.pyplot as plt

## making a sample pandas DataFrmae
df = pd.DataFrame({
    'weekday': ['Monday', 'Saturday', 'Tuesday', 'Sunday', 'Wednesday', 'Thursday', 
                'Friday', 'Saturday', 'Sunday', 'Friday', 'Tuesday'], 
    'value': [2, 3, 4, 2, 6, 7, 5, 2, 1, 6, 4]
})

print(df)
#       weekday  value
# 0      Monday      2
# 1    Saturday      3
# 2     Tuesday      4
# 3      Sunday      2
# 4   Wednesday      6
# 5    Thursday      7
# 6      Friday      5
# 7    Saturday      2
# 8      Sunday      1
# 9      Friday      6
# 10    Tuesday      4


## aggregation of value by weekday --> this will be used for visualization
df_agg = df.groupby('weekday').sum('value')
print(df_agg)
#            value
# weekday         
# Friday        11
# Monday         2
# Saturday       5
# Sunday         3
# Thursday       7
# Tuesday        8
# Wednesday      6

 

 

 

(1) pandas DataFrame의 x 축으로 사용되는 범주형 값을 loc 를 사용해 재정렬하기

 

위에서 df.groupby('weekday').sum('value') 로 집계한 df_agg DataFrame의 결과를 보면, 알파벳(alphabet) 순서대로 요일의 순서("Friday", "Monday", "Satruday", "Sunday", "Thursday", "Tuesday", "Wednesday")가 정해져서 집계가 되었습니다. 이 DataFrame에 대해 matplotlib 으로 막대그래프를 그리면 아래와 같이 요일이 알파벳 순서대로 정렬이 된 상태에서 그래프가 그려집니다. 

 

plt.figure(figsize=(12, 8))
plt.bar(df_agg.index, df_agg.value)
plt.title("Bar plot without reordering x-axis label", fontsize=20)
plt.xlabel("Weekday", fontsize=18)
plt.xticks(fontsize=16)
plt.show()

matplotlib: before changing the order of x-axis label

 

 

(2) index 가 재정렬된 pandas.DataFrame에 대해서 matplotlib 으로 그래프 그리기
       (matplotlib 그래프의 x-axis 의 ticks 순서 바꾸기)

 

요일의 순서가 우리가 일상적으로 사용하는 순서와는 다르기 때문에 눈에 잘 안들어오고 거슬립니다. 이럴 때는 pandas DataFrame의 index 순서를 먼저 바꾸어주고, 순서가 재정렬된 후의 DataFrame에 대해서 matplotlib 으로 그래프를 그려주면 됩니다.

 

아래 예제에서는 요일(weekday)을 알파벳 순서가 아니라, 우리가 일상적으로 사용하는 ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") 의 순서로 DataFrame의  index 를 loc 를 사용해서 바꾸어 준후에, matplotlib 막대그래프(bar graph)를 그려보았습니다. 

 

matplotlib의 X 축 레이블의 크기 

## changing the order of x-axis label using loc
weekday_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
df_agg = df_agg.loc[weekday_order]

print(df_agg)
#            value
# weekday         
# Monday         2
# Tuesday        8
# Wednesday      6
# Thursday       7
# Friday        11
# Saturday       5
# Sunday         3


## box-plot after changing the order of x-axis ticks
plt.figure(figsize=(12, 8))
plt.bar(df_agg.index, df_agg.value)
plt.title("Bar plot after reordering x-axis label", fontsize=20)
plt.xlabel("Weekday", fontsize=18)
plt.xticks(fontsize=16)
plt.show()

matplotlib: after changing the order of x-axis label

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

반응형
Posted by Rfriend

댓글을 달아 주세요

이번 포스팅에서는 Python matplotlib 모듈을 사용해서 그래프를 그렸을 때 범례를 추가(adding a legend on the Axes)하는 4가지 방법을 소개하겠습니다. 그리고 범례의 위치, 범례 글자 크기, 범례 배경색, 범례 테두리 색, 범례 그림자 등을 설정하는 방법을 소개하겠습니다. 

 

(1) 범례 추가하기 (adding a legend on the Axes)

    : legend(), legend(handles, labels), legend(handles=handles), legend(labels)

(2) 범례 스타일 설정하기
    : location, fontsize, facecolor, edgecolor, shadow

 

 

(1) 범례 추가하기 (adding a legend on the Axes)

 

matplotlib 으로 그래프를 그린 후 범례를 추가하는 방법에는 (1-1) legend(), (1-2) line.set_label(), (1-3) legend(handles, labels), (1-4) legend(handles=handles) 의 4가지 방법이 있습니다. 이중에서 그래프를 그릴 때 label 을 입력해주고, 이어서 ax.legend() 나 plt.legend() 를 실행해서 자동으로 범례를 만드는 방법이 가장 쉽습니다. 차례대로 4가지 방법을 소개하겠습니다.  결과는 모두 동일하므로 범례가 추가된 그래프는 (1-1)번에만 소개합니다. 

 

먼저, 아래의 2개 방법은 자동으로 범례를 탐지해서 범례를 생성해주는 예제입니다. 

 

(1-1) ax.plot(label='text...') --> ax.legend() 또는 plt.legend()

 

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(1)
x = np.arange(100)
y1 = np.random.normal(0, 1, 100)
y2 = np.random.normal(0, 1, 100).cumsum()

## 1. Automatic detection of elements to be shown in the legend
## 1-1. label
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(1, 1, 1)

## labels: A list of labels to show next to the artists. 
ax.plot(x, y1, 'b-', label='Company 1')
ax.plot(x, y2, 'r--', label='Company 2')
ax.legend()
plt.show()

matplotlib legend

 

 

(1-2) line.set_label('text...') --> ax.legend()

 

## 1-2. line.set_label()

fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(1, 1, 1)

line, = ax.plot(x, y1, 'b-')
line.set_label('Company 1')

line, = ax.plot(x, y2, 'r--')
line.set_label('Company 2')

ax.legend()
plt.show()

 

 

위의 두 방법은 범례를 자동 탐지해서 생성해줬다면, 아래의 방법부터는 명시적으로 범례의 handles (선 모양과 색깔 등) 와 labels (범례 이름)를 지정해줍니다.

 

(1-3) ax.legend(handles, labels)

 

## 2. Explicitly listing the artists and labels in the legend
## to pass an iterable of legend artists followed by an iterable of legend labels respectively
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(1, 1, 1)

line1, = ax.plot(x, y1, 'b-')
line2, = ax.plot(x, y2, 'r--')

ax.legend([line1, line2], ['Company 1', 'Company 2'])
plt.show()

 

 

 

(1-4) ax.legend(handles=handles)

 

## 3. Explicitly listing the artists in the legend
## the labels are taken from the artists' label properties.
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(1, 1, 1)

line1, = ax.plot(x, y1, 'b-', label='Company 1')
line2, = ax.plot(x, y2, 'r--', label='Company 2')

## handles: A list of Artists (lines, patches) to be added to the legend. 
ax.legend(handles=[line1, line2])
plt.show()

 

 

 

(2) 범례 스타일 설정하기
    : location, fontsize, facecolor, edgecolor, shadow

 

범례의 위치(location) 을 지정하려면 loc 매개변수에 location strings 인 'best', 'upper right', 'upper left', 'lower left', 'lower right', 'right', 'center left', 'center right', 'lower center', 'upper center', 'center' 중에서 하나를 선택해서 입력해주면 됩니다. 기본설정값은 loc='best' 로서 컴퓨터가 알아서 가장 적절한 여백을 가지는 위치에 범례를 생성해주는데요, 나름 똑똑하게 잘 위치를 선택해서 범례를 추가해줍니다. 

 

그밖에 범례의 폰트 크기(fontsize), 범례의 배경 색깔(facecolor, 기본설정값은 'white'), 범례의 테두리선 색깔(edgecolor), 범례 상자의 그림자 여부(shadow, 기본설정값은 'False') 등도 사용자가 직접 지정해줄 수 있습니다. (굳이 범례의 스타일을 꾸미는데 시간과 노력을 많이 쏟을 이유는 없을 것 같기는 합니다만....^^;)

 

fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(1, 1, 1)

ax.plot(x, y1, 'b-', label='Company 1')
ax.plot(x, y2, 'r--', label='Company 2')
ax.legend(
    ## location strings
    ## : 'best', 'upper right', 'upper left', 'lower left', 'lower right'
    ##   'right', 'center left', 'center right', 'lower center', 'upper center', 'center'
    loc='center left', # default: loc='best'
    ## fontsize
    ## # 'xx-small','x-small','small','medium','x-large','xx-large'
    fontsize='large', 
    facecolor='yellow', # background color
    edgecolor='black',
    shadow=True) # shadow behind the legend if True

ax.set_title('Adding a Legend with color and shadow', fontsize=18)
plt.show()

matplotlib adding a legend with color and shadow at center left location

 

 

[ Reference ]

* matplotlib.pyplot.legend() : https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.legend.html

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요. :-)

 

반응형
Posted by Rfriend

댓글을 달아 주세요

이번 포스팅에서는 Python matplotlib 모듈로 그래프를 그릴 때,  

 

(1) 눈금 설정하기 : set_xticks(), set_yticks()

(2) 눈금 이름 설정하기 : set_xticklabels(), set_yticklabels()

(3) 축 이름 설정하기 : set_xlabel(), set_ylabel()

(4) 제목 설정하기 : set_title()

 

하는 방법을 소개하겠습니다. 

 

 

먼저, numpy 모듈을 사용해서 표준정규분포 X~N(0, 1) 로부터 난수 100개를 생성해서 matplotlib의 기본 설정으로 선 그래프를 그려보겠습니다. matplotlib의 Figure 객체를 만들고, fig.add_subplot(1, 1, 1) 로 하위 플롯을 하나 만든 다음에 선 그래프를 그렸습니다. 

 

import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(1, 1, 1)

np.random.seed(1)
x=np.arange(100)
y=np.random.normal(0, 1, 100)

## plot with default setting
ax.plot(x, y)
plt.show()

 

 

(1) 눈금 설정하기 : set_xticks(), set_yticks()

 

X축의 눈금을 [0, 50, 100] 으로 설정하고, Y축의 눈금은 [-2, 0, 2] 로 설정해보겠습니다. 

 

fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(1, 1, 1)

ax.plot(x, y)

## setting xticks, yticks
ax.set_xticks([0, 50, 100])
ax.set_yticks([-2, 0, 2])

plt.show()

matplotlib.pyplot : ax.set_xticks(), ax.set_yticks()

 

 

 

(2) 눈금 이름 설정하기 : set_xticklabels(), set_yticklabels()

 

X축과 Y축에 눈금이름(xticklabel, yticklabel)을 설정해줄 수도 있습니다. 이때 set_xticks(), set_yticks() 메소드로 눈금의 위치를 먼저 설정해주고, 이어서 같은 개수의 눈금 이름을 가지고 set_xticklabels(), set_yticklabels() 메소드로 눈금 이름을 설정해주면 됩니다. 

 

fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(1, 1, 1)

ax.plot(x, y)

## setting xticks, yticks with labels
ax.set_xticks([0, 50, 100])
ax.set_yticks([-2, 0, 2])
ax.set_xticklabels(['start', 'middel', 'end'], fontsize=12)
ax.set_yticklabels(['low', 'zero', 'high'], fontsize=12)

plt.show()

 

 

 

(3) 축 이름 설정하기 : set_xlabel(), set_ylabel()

 

set_xlabel(), set_ylabel() 메소드로 축 이름(xlabel, ylabel)을 설정해 줄 때 fontsize 매개변수를 사용해서 글자 크기를 조정할 수 있습니다. 

 

fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(1, 1, 1)

ax.plot(x, y)

## setting xticks, yticks with labels
ax.set_xticks([0, 50, 100])
ax.set_yticks([-2, 0, 2])
ax.set_xticklabels(['start', 'middel', 'end'], fontsize=12)
ax.set_yticklabels(['low', 'zero', 'high'], fontsize=12)

## setting xlabel, ylabel
ax.set_xlabel('Steps', fontsize=16)
ax.set_ylabel('Value', fontsize=16)

plt.show()

 

 

 

(4) 제목 설정하기 : set_title()

 

제목의 텍스트, 폰트 크기(fontsize), 위치(loc={'center', 'left', 'right'}) 를 설정해 줄 수 있습니다. 

 

fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(1, 1, 1)

ax.plot(x, y)

## setting xticks, yticks with labels
ax.set_xticks([0, 50, 100])
ax.set_yticks([-2, 0, 2])
ax.set_xticklabels(['start', 'middel', 'end'], fontsize=12)
ax.set_yticklabels(['low', 'zero', 'high'], fontsize=12)

## xlabel, ylabel
ax.set_xlabel('Steps', fontsize=16)
ax.set_ylabel('Value', fontsize=16)

# title
ax.set_title('Plo with ticks and labels', 
             fontsize=20, 
             loc='left') # 'center', 'right'

plt.show()

matplotlib: setting title, xlabel, xticks, xticklabels

 

 

 

[ Reference ]

* ax.set_xticks(): https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.set_xticks.html

* ax.set_xticklabels(): https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.set_xticklabels.html

* ax.set_xlabel(): https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.set_xlabel.html

* ax.set_title(): https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.axes.Axes.set_title.html

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요! :-)

 

반응형
Posted by Rfriend

댓글을 달아 주세요

여러개의 그래프를 위/아래 또는 왼쪽/오른쪽으로 붙여서 시각화를 한 후에 비교를 해보고 싶을 때가 있습니다. 그리고 측정 단위가 서로 같을 경우에는 x축, y축을 여러개의 그래프와 공유하면서 동일한 scale의 눈금으로 시각화 함으로써 그래프 간 비교를 더 쉽고 정확하게 할 수 있습니다. 

 

이번 포스팅에서는

  (1) 여러개의 하위 플롯 그리기: plt.subplots(nrows=2, ncols=2)

  (2) X축과 Y축을 공유하기
        : plt.subplots(sharex=True. sharey=True)

  (3) 여러개의 하위 플롯들 간의 간격을 조절해서 붙이기

        : (3-1) plt.subplots_adjust(wspace=0, hspace=0)

        : (3-2) plt.tight_layout(h_pad=-1, w_pad=-1)

하는 방법을 소개하겠습니다. 

 

 

 

(1) 여러개의 하위 플롯 그릭: plt.subplots(nrows=2, ncols=2)

 

먼저, plt.subplots(nrows=2, ncols=2) 로 2개 행, 2개 열의 layout 을 가지는 하위 플롯을 그려보겠습니다. 평균=0, 표준편차=1을 가지는 표준정규분포로 부터 100개의 난수를 생성해서 파란색(color='b')의 약간 투명한(alpha=0.5) 히스토그램을 그려보겠습니다.

 

for loop 을 돌면서 2 x 2 의 레이아웃에 하위 플롯이 그려질때는 왼쪽 상단이 1번, 오른쪽 상단이 2번, 왼쪽 하단이 3번, 오른쪽 하단이 4번의 순서로 하위 그래프가 그려집니다.  

 

이렇게 하위 플롯이 그려질 때 자동으로 하위 플롯 간 여백(padding)을 두고 그래프가 그려지며, X축과 Y축은 공유되지 않고 각각 축을 가지고 그려집니다.  

 

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

## subplots with 2 by 2
## (1) there are paddings between subplots
## (2) x and y axes are not shared
fig, axes = plt.subplots(
    nrows=2, 
    ncols=2, 
    figsize=(10, 10))

for i in range(2):
    for j in range(2):
        axes[i, j].hist(
            np.random.normal(loc=0, scale=1, size=100), 
            color='b', 
            alpha=0.5)

subplots with padding and x, y axes

 

 

(2) X축과 Y축을 공유하기
      : plt.subplots(sharex=True. sharey=True)

(3) 여러개의 하위 플롯들 간의 간격을 조절해서 붙이기

      : (3-1) plt.subplots_adjust(wspace=0, hspace=0)

 

여러개의 하위 플롯 간에 X축과 Y축을 공유하려면 plt.subplots()의 옵션 중에서 sharex=True, sharey=True를 설정해주면 됩니다. 

 

그리고 하위 플롯들 간의 간격을 없애서 그래프를 서로 붙여서 그리고 싶다면 두가지 방법이 있습니다. 먼저, plt.subplots_adjust(wspace=0, hspace=0) 에서 wspace 는 폭의 간격(width space), hspace 는 높이의 간격(height space) 을 설정하는데 사용합니다. 

 

fig, axes = plt.subplots(
    nrows=2, ncols=2, 
    sharex=True, # sharing properties among x axes
    sharey=True, # sharing properties among y axes 
    figsize=(10, 10))

for i in range(2):
    for j in range(2):
        axes[i, j].hist(
            np.random.normal(loc=0, scale=1, size=100), 
            color='b', 
            alpha=0.5)
        
## adjust the subplot layout 
plt.subplots_adjust(
    wspace=0, # the width of the padding between subplots 
    hspace=0) # the height of the padding between subplots

matplotlib subplots adjust

 

 

아래 예제에서는 plt.subplots(sharex=False, sharey=True) 로 해서 X축은 공유하지 않고 Y축만 공유하도록 했습니다. 

그리고 plt.subplots_adjust(wspace=0, hspace=0.2) 로 해서 높이의 간격(height space)에만 0.2 만큼의 간격을 부여해주었습니다. 

 

fig, axes = plt.subplots(
    nrows=2, ncols=2, 
    sharex=False, # sharing properties among x axes
    sharey=True, # sharing properties among y axes 
    figsize=(10, 10))

for i in range(2):
    for j in range(2):
        axes[i, j].hist(
            np.random.normal(loc=0, scale=1, size=100), 
            color='b', 
            alpha=0.5)
        
## adjust the subplot layout 
plt.subplots_adjust(
    wspace=0, # the width of the padding between subplots 
    hspace=0.2) # the height of the padding between subplots

 

 

 

(3) 여러개의 하위 플롯들 간의 간격을 조절해서 붙이기

      : (3-2) plt.tight_layout(h_pad=-1, w_pad=-1)

 

하위 플롯 간 간격을 조절하는 두번째 방법으로는 plt.tight_layout(h_pad, w_pad) 을 사용하는 방법입니다. plt.tight_layout(h_pad=-1, w_pad=-1)로 설정해서 위의 2번에서 했던 것처럼 4개의 하위 플롯 간에 간격이 없이 모두 붙여서 그려보겠습니다. (참고: h_pad=0, w_pad=0 으로 설정하면 하위 플롯간에 약간의 간격이 있습니다.)

 

fig, axes = plt.subplots(
    nrows=2, ncols=2, 
    sharex=True, # sharing properties among x axes
    sharey=True, # sharing properties among y axes 
    figsize=(10, 10))

for i in range(2):
    for j in range(2):
        axes[i, j].hist(
            np.random.normal(loc=0, scale=1, size=100), 
            color='b', 
            alpha=0.5)

## adjusting the padding between and around subplots
plt.tight_layout(
    h_pad=-1, # padding height between edges of adjacent subplots
    w_pad=-1) # padding width between edges of adjacent subplots

matplotlib subplots tight_layout

 

 

X축은 공유하지 않고 Y축만 공유하며, plt.tight_layout(h_pad=3, w_pad=0) 으로 설정해서 높이 간격을 벌려보겠습니다. 그리고 하위 플롯이 그려지는 순서대로 'blue', 'red', 'yellow', 'black' 의 색깔을 입혀보겠습니다. 

 

fig, axes = plt.subplots(
    nrows=2, ncols=2, 
    sharex=False, # sharing properties among x axes
    sharey=True, # sharing properties among y axes 
    figsize=(10, 10))

color = ['blue', 'red', 'yellow', 'black']
k=0
for i in range(2):
    for j in range(2):
        axes[i, j].hist(
            np.random.normal(loc=0, scale=1, size=100), 
            color=color[k], 
            alpha=0.5)
        k +=1

## adjusting the padding between and around subplots
plt.tight_layout(
    h_pad=3, # padding height between edges of adjacent subplots
    w_pad=0) # padding width between edges of adjacent subplots

 

[ Reference ]

- plt.subplots(): https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.subplots.html
- plt.subplots_adjust(): https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.subplots_adjust.html
- plt.tight_layout(): https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.tight_layout.html

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!

 

반응형
Posted by Rfriend

댓글을 달아 주세요

이번 포스팅에서는 Python의 matplotlib 모듈을 사용해서 

 

  (1) 그래프에 수평선 추가하기 (adding horizontal lines)

  (2) 그래프에 수직선 추가하기 (adding vertical lines) 

 

하는 3가지 방법을 소개하겠습니다. 

 

예제로 사용할 샘플 데이터셋을 정규분포로부터 난수를 생성해서 100개 샘플을 추출하고, 점 그래프를 그려보겠습니다. 

이 기본 점 그래프에 수평선과 수직선을 차례대로 추가해보겠습니다. 

 

import numpy as np
import matplotlib.pyplot as plt

## generating random numbers
np.random.seed(1004)
x = np.random.normal(0, 1, 100)

## plotting the original data
plt.figure(figsize = (10, 6))
plt.plot(x, linestyle='none', marker='o', color='gray')
plt.show()

 

 

 (1) 그래프에 수평선 추가하기 (adding horizontal lines)

 

(a) plt.axhline(y, xmin, xmax) : 축을 따라서 수평선을 추가, xmin 과 xmax 는 0~1 사이의 값을 가짐

(b) plt.hlines(y, xmin, xmax) : xmin ~ xmax 까지 각 y 값의 수평선을 추가

(c) plt.plot((x1, x2), (y1, y2)) : (x1, x2), (y1, y2) 좌표를 연결하는 선 추가

 

(a) 번의 plt.axhline() 은 y축에서 부터 수평선이 시작하고, xmin~xmax 로 0~1 사이의 비율 값을 가지는 반면에, (b)번의 plt.hlines() 는 xmin 값 위치에서 부터 수평선이 시작하고, xmin~xmax 값으로 좌표값을 받는다는 차이점이 있습니다. 

(c) 번의 plt.plot() 은 단지 수평선, 수직선 뿐만이 아니라 범용적으로 두 좌표를 연결하는 선을 추가할 수 있습니다. 

 

plt.figure(figsize = (10, 6))
plt.plot(x, linestyle='none', marker='o', color='gray')
plt.title("Plot with Horizontal Lines", fontsize=16)

## (1) adding a horizontal line across the axis
## xmin and xmax should be b/w 0 and 1
plt.axhline(y=3, xmin=0, xmax=1, color='blue', linestyle='solid')
plt.axhline(y=2, xmin=0.1, xmax=0.9, color='blue', linestyle='dashed')

## (2) adding a horizontal line at each y from xmin to xmax
plt.hlines(y=0, xmin=0, xmax=50, color='red', linestyle='dotted')

## (3) adding a horizontal line using (x1, x2), (y1, y2) coordinates
plt.plot((50, 100), (-2, -2), color='black', linestyle='dashdot')

plt.show()

 

horizontal lines using matplotlib

 

 

(2) 그래프에 수직선 추가하기 (adding vertical lines) 

 

(a) plt.axvline(x, ymin, ymax) : 축을 따라서 수직선을 추가, ymin 과 ymax 는 0~1 사이의 값을 가짐

(b) plt.vlines(x, ymin, ymax) : ymin ~ ymax 까지 각 x 값의 수평선을 추가

(c) plt.plot((x1, x2), (y1, y2)) : (x1, x2), (y1, y2) 좌표를 연결하는 선 추가

 

(a) 번의 plt.axvline() 은 x축에서 부터 수평선이 시작하고, ymin~ymax 로 0~1 사이의 비율 값을 가지는 반면에, (b)번의 plt.vlines() 는 ymin 값 위치에서 부터 수평선이 시작하고, ymin~ymax 값으로 좌표값을 받는다는 차이점이 있습니다. 

(c) 번의 plt.plot() 은 단지 수평선, 수직선 뿐만이 아니라 범용적으로 두 좌표를 연결하는 선을 추가할 수 있습니다. 

 

plt.figure(figsize = (10, 6))
plt.plot(x, linestyle='none', marker='o', color='gray')
plt.title("Plot with vertical Lines", fontsize=16)

## (1) adding a vertical line across the axis
## ymin and ymax should be b/w 0 and 1
plt.axvline(x=0, ymin=0, ymax=1, color='blue', linestyle='solid')
plt.axvline(x=10, ymin=0.1, ymax=0.9, color='blue', linestyle='dashed')

## (2) adding a vertical line at each y from xmin to xmax
plt.vlines(x=50, ymin=0, ymax=3, color='red', linestyle='dotted')

## (3) adding a vertical line using (x1, x2), (y1, y2) coordinates
plt.plot((100, 100), (0, -3), color='black', linestyle='dashdot')

plt.show()

 

vertical lines using matplotlib

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

반응형
Posted by Rfriend

댓글을 달아 주세요

  1. gamma 2021.10.07 00:48 신고  댓글주소  수정/삭제  댓글쓰기

    범례를 표시하고 싶은데, 이리 저리 해봐도 범례가 표시되지 않는데,
    범례를 표시하는 방법이 있을까요?
    *아래 코딩으로 plot은 정상적으로 그려집니다.


    # """재배면적별_적합_부적합_농가수1"""로 이중축 차트

    재배면적별_적합_부적합_농가수1 <- as.data.frame(재배면적별_적합_부적합_농가수1)
    str(재배면적별_적합_부적합_농가수1)
    g1 <- ggplot2::ggplot(재배면적별_적합_부적합_농가수1, aes(x=재배면적), group=1) +
    geom_line(aes(y=Freq_적합), color = "blue", size = 2, group=1) +
    geom_line(aes(y=Freq_부적합), color = "black", size = 2, group=1) +

    # theme(legend.position = "topleft") +
    # theme(legend.title = element_text(face = "bold", size = 13, color = "darkblue")) +
    theme(legend.position = c(0.5, 0.5))
    g1

    max_ratio_적합vs부적합률 <- max(재배면적별_적합_부적합_농가수1$Freq_적합)/(max(재배면적별_적합_부적합_농가수1$부적합률))

    g2 <- g1 + geom_line(aes(y = 부적합률*max_ratio_적합vs부적합률*0.8), group=1, color="red") +
    scale_y_continuous(sec.axis = sec_axis(~./(max_ratio_적합vs부적합률), name="부적합률")) +
    ggtitle("< 적합(Blue), 부적합(Black) 농가수(좌측 y축) vs 부적합률(Red)(우측 y축) >")
    # ggplot2::theme(legend.position = "left")

    g2

    • Rfriend 2021.10.07 00:54 신고  댓글주소  수정/삭제

      https://rfriend.tistory.com/316 참고하세요.

      R 과 Python 카테고리가 따로 있습니다. R질문을 걔속 Python 포스팅한 글에 남기시고 았어요. >_<

  2. gamma 2021.10.07 00:51 신고  댓글주소  수정/삭제  댓글쓰기

    제가 전에 질문했던 것을 다시 볼려고 하는데 찾을 수가 없네요.
    https://rfriend.tistory.com//3, https://rfriend.tistory.com//648번 같았는데,
    가보니까 보이지 않는데, 찾을 수 있는 방법이 있을까요?

이번 포스팅에서는 Python의 matplotlib 모듈을 사용하여, X축의 값은 동일하지만 Y축의 값은 척도가 다르고 값이 서로 크게 차이가 나는 2개의 Y값 데이터에 대해서 이중축 그래프 (plot with 2 axes for a dataset with different scales)를 그리는 방법을 소개하겠습니다. 

 

먼저 간단한 예제 데이터셋을 만들어보겠습니다. 

 

  * x 축은 2021-10-01 ~ 2021-10-10 일까지의 10개 날짜로 만든 index 값을 동일하게 사용하겠습니다. 

  * y1 값은 0~9 까지 정수에 표준정규분포 Z~N(0, 1) 로 부터 생성한 난수를 더하여 만들었습니다. 

  * y2 값은 정수 0~9에 지수를 취하여 만들었습니다. 

 

## importing modules
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt


## generating sample dataset

np.random.seed(123) # for reproducibility
idx = pd.date_range("10 1 2021", 
                     periods=10, 
                     freq="d", 
                     name="Date")

y1 = np.arange(10) + np.random.normal(0, 1, 10)
y2 = np.exp(range(10))

df = pd.DataFrame({'y1': y1, 
                   'y2': y2}, 
                  index = idx)


print(df)
#                   y1           y2
# Date                             
# 2021-10-01 -1.085631     1.000000
# 2021-10-02  1.997345     2.718282
# 2021-10-03  2.282978     7.389056
# 2021-10-04  1.493705    20.085537
# 2021-10-05  3.421400    54.598150
# 2021-10-06  6.651437   148.413159
# 2021-10-07  3.573321   403.428793
# 2021-10-08  6.571087  1096.633158
# 2021-10-09  9.265936  2980.957987
# 2021-10-10  8.133260  8103.083928

 

 

먼저, 스케일이 다른 2개의 y값을 1개의 축을 사용하여 그렸을 때 문제점을 살펴보고, 

다음으로 이를 해결하기 위한 방법 중의 하나로서 matplotlib을 사용해 2중축 그래프를 그려보겠습니다. 

 

(* 참고로, 2중축 그래프 외에 서로 다른 척도(scale)의 두개 변수의 값을 표준화(standardization, scaling) 하여 두 변수의 척도를 비교할 수 있도록 변환해준 후에 하나의 축에 두 변수를 그리는 방법도 있습니다.)

 

 

(1)  스케일이 다른 2개의 y값 변수를 1중축 그래프에 그렸을 때 문제점

==> 스케일이 작은 쪽의 y1 값이 스케일이 큰 쪽의 y2 값에 압도되어 y1 값의 패턴을 파악할 수 없음. (스케일이 작은 y1값의 시각화가 의미 없음)

 

## scale이 다른 데이터를 1개의 y축만을 사용해 그린 그래프
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot() 

ax.plot(df.index, df.y1, marker='s', color='blue')
ax.plot(df.index, df.y2, marker='o', color='red')

plt.title('Plot with 1 Axis', fontsize=16)
plt.xlabel('Date', fontsize=14)
plt.ylabel('Value', fontsize=14)
plt.legend(['y1', 'y2'], fontsize=12, loc='best')
plt.show()

plot with 1 axis for a dataset with the different scales

 

 

(2) 스케일이 다른 2개의 변수에 대해 2중축 그래프 그렸을 때

==> 각 y1, y2 변수별 스케일에 맞게 적절하게 Y축이 조정이 되어 두 변수 값의 패턴을 파악하기가 쉬움

 

이때, 가독성을 높이기 위해서 각 Y축의 색깔, Y축 tick의 색깔과 그래프의 색깔을 동일하게 지정해주었습니다. (color 옵션 사용)

 

## plot with 2 different axes for a dataset with different scales
# left side
fig, ax1 = plt.subplots()
color_1 = 'tab:blue'
ax1.set_title('Plot with 2 Axes for a dataset with different scales', fontsize=16)
ax1.set_xlabel('Date')
ax1.set_ylabel('Y1 value (blue)', fontsize=14, color=color_1)
ax1.plot(df.index, df.y1, marker='s', color=color_1)
ax1.tick_params(axis='y', labelcolor=color_1)

# right side with different scale
ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis
color_2 = 'tab:red'
ax2.set_ylabel('Y2 value (red)', fontsize=14, color=color_2)
ax2.plot(df.index, df.y2, marker='o', color=color_2)
ax2.tick_params(axis='y', labelcolor=color_2)

fig.tight_layout()
plt.show()

plot with 2 axes for a dataset with different scales

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터과학자 되세요!   :-)

 

반응형
Posted by Rfriend

댓글을 달아 주세요