본문 바로가기

Software/Python

폼 나게 이쁜 그래프 그려보기 - Matplotlib 예제

Python의 Matplotlib는 꽤 강력한 그래프 그리는 도구입니다. 제 블로그 Python 카테고리에서도 자주 보여드렸습니다만... 이 아이로 그린 그래프는 MATLAB 만큼이나 이쁘면서 또 편리하거나 강력하거나, 재미있는 기능들을 많이 가지고 있더라구요^^. 그래서 멍~ 때리던 어느 날 아무 의미 없이 이쁜 그래프를 찾아볼까? 라는 시간때우기용 주제를 가지고 검색하다가 인터넷에서 아주 좋은 곳을 발견했습니다. Python 관련 다양한 설명이 있더라구요.. 그중에서 Matplotlib를 이용해서 정갈하게 그래프를 그리는 모습을 보여주고 있길래 살짝 따라해보았습니다. 즉, 오늘의 내용은 이쁜 그래프를 Python Matplotlib로 구현한 것을 잘~~~ 설명한 페이지를 그대로 따라한다~~라는 것으로 뭐 아무런 수준은 없습닏.^^

아무튼... 저는 위 페이지의 내용을 IPython Notebook으로 따라해 보았습니다.

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

X = np.linspace(-np.pi, np.pi, 256,endpoint=True)
C,S = np.cos(X), np.sin(X)

plt.plot(X,C)
plt.plot(X,S)

아주 심플한 단계입니다. 그래프를 그릴 데이터를 준비하고 그냥 그린거지요~~^^

# Create a new figure of size 8x6 points, using 100 dots per inch
plt.figure(figsize=(8,6), dpi=80)

# Create a new subplot from a grid of 1x1
plt.subplot(111)

# Plot cosine using blue color with a continuous line of width 1 (pixels)
plt.plot(X, C, color="blue", linewidth=1.0, linestyle="-")

# Plot sine using green color with a continuous line of width 1 (pixels)
plt.plot(X, S, color="green", linewidth=1.0, linestyle="-")

# Set x limits
plt.xlim(-4.0, 4.0)

# Set x ticks
plt.xticks(np.linspace(-4,4,9,endpoint=True))

# Set y limits
plt.ylim(-1.0, 1.0)

# Set y ticks
plt.yticks(np.linspace(-1,1,5,endpoint=True))

이제 grid가 1*1인 subplot에 그림을 그려봅니다. 이번에는 x / y의 tick도 정의하고 limit도 정해둔거죠.

plt.figure(figsize=(10,6), dpi=80)
plt.plot(X, C, color="blue", linewidth=2.5, linestyle="-")
plt.plot(X, S, color="red",  linewidth=2.5, linestyle="-")

# Set x limits
plt.xlim(-4.0, 4.0)

# Set x ticks
plt.xticks(np.linspace(-4,4,9,endpoint=True))

# Set y limits
plt.ylim(-1.0, 1.0)

# Set y ticks
plt.yticks(np.linspace(-1,1,5,endpoint=True))

이번에는 그림 크기(figsize)를 변경하고 있네요. 그러면서 선의 굵기도 변경하고 있습니다.

plt.figure(figsize=(10,6), dpi=80)
plt.plot(X, C, color="blue", linewidth=2.5, linestyle="-")
plt.plot(X, S, color="red",  linewidth=2.5, linestyle="-")

# Set limits
plt.xlim(X.min()*1.1, X.max()*1.1)
plt.ylim(C.min()*1.1, C.max()*1.1)

이번에는 아예 그래프의 x/y의 범위를 min/max값을 가지고 거기에 10%의 마진을 인가하도록 바꿔서 간편히 그려보고 있네요.

plt.figure(figsize=(10,6), dpi=80)
plt.plot(X, C, color="blue", linewidth=2.5, linestyle="-")
plt.plot(X, S, color="red",  linewidth=2.5, linestyle="-")

# Set limits
plt.xlim(X.min()*1.1, X.max()*1.1)
plt.ylim(C.min()*1.1, C.max()*1.1)

# Setting ticks
plt.xticks( [-np.pi, -np.pi/2, 0, np.pi/2, np.pi])
plt.yticks([-1, 0, +1])

아... 그리고.. 실제는 삼각함수의 경우는 pi를 기준으로 x축이 표현될 때가 많으니 x축의 tick을 그에 맞춰본듯 합니다.

plt.figure(figsize=(10,6), dpi=80)
plt.plot(X, C, color="blue", linewidth=2.5, linestyle="-")
plt.plot(X, S, color="red",  linewidth=2.5, linestyle="-")

# Set limits
plt.xlim(X.min()*1.1, X.max()*1.1)
plt.ylim(C.min()*1.1, C.max()*1.1)

# Setting tick labels
plt.xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi], [r'$-\pi$', r'$-\pi/2$', r'$0$', r'$+\pi/2$', r'$+\pi$'])
plt.yticks([-1, 0, +1], [r'$-1$', r'$0$', r'$+1$'])

그러나 삼각함수에서 사용되는 pi를 숫자로 보면 좀 어색하죠? 그래서 Latex 표현으로 x 축 tick을 다시 표현합니다.

plt.figure(figsize=(10,6), dpi=80)
plt.plot(X, C, color="blue", linewidth=2.5, linestyle="-")
plt.plot(X, S, color="red",  linewidth=2.5, linestyle="-")

# Set limits
plt.xlim(X.min()*1.1, X.max()*1.1)
plt.ylim(C.min()*1.1, C.max()*1.1)

# Setting tick labels
plt.xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi], [r'$-\pi$', r'$-\pi/2$', r'$0$', r'$+\pi/2$', r'$+\pi$'])
plt.yticks([-1, 0, +1], [r'$-1$', r'$0$', r'$+1$'])

# Moving spines
ax = plt.gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))

이번에는 좀 재미있는 건데요.. 바로 x / y 축을 가운데로 옮기는 것입니다. 위 코드의 Moving Spines라고 되어 있는 부분인데요... bottom과 left를 데이터 기준으로 '0'위치로 보내고, right와 top은 제거(none)하는 거죠...

이렇게만 봐도... 꽤 괜찮습니다. MATLAB에서는 직접 그렸는데[바로가기]말이죠^^

plt.figure(figsize=(10,6), dpi=80)
plt.plot(X, C, color="blue", linewidth=2.5, linestyle="-", label="cosine")
plt.plot(X, S, color="red",  linewidth=2.5, linestyle="-", label="sine")

# Set limits
plt.xlim(X.min()*1.1, X.max()*1.1)
plt.ylim(C.min()*1.1, C.max()*1.1)

# Setting tick labels
plt.xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi], [r'$-\pi$', r'$-\pi/2$', r'$0$', r'$+\pi/2$', r'$+\pi$'])
plt.yticks([-1, 0, +1], [r'$-1$', r'$0$', r'$+1$'])

# Moving spines
ax = plt.gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))

# Adding a legend
plt.legend(loc='upper left', frameon=False)

그리고 legend의 위치도 조절할 수 있다는 것을 보여주고 있습니다.

plt.figure(figsize=(10,6), dpi=80)
plt.plot(X, C, color="blue", linewidth=2.5, linestyle="-", label="cosine")
plt.plot(X, S, color="red",  linewidth=2.5, linestyle="-", label="sine")

# Set limits
plt.xlim(X.min()*1.1, X.max()*1.1)
plt.ylim(C.min()*1.1, C.max()*1.1)

# Setting tick labels
plt.xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi], [r'$-\pi$', r'$-\pi/2$', r'$0$', r'$+\pi/2$', r'$+\pi$'])
plt.yticks([-1, 0, +1], [r'$-1$', r'$0$', r'$+1$'])

# Moving spines
ax = plt.gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))

# Adding a legend
plt.legend(loc='upper left', frameon=False)

# Annotate some points
t = 2*np.pi/3
plt.plot([t,t],[0,np.cos(t)], color ='blue', linewidth=2.5, linestyle="--")
plt.scatter([t,],[np.cos(t),], 50, color ='blue')

plt.annotate(r'$\sin(\frac{2\pi}{3})=\frac{\sqrt{3}}{2}$',
             xy=(t, np.sin(t)), xycoords='data',
             xytext=(+10, +30), textcoords='offset points', fontsize=16,
             arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))

plt.plot([t,t],[0,np.sin(t)], color ='red', linewidth=2.5, linestyle="--")
plt.scatter([t,],[np.sin(t),], 50, color ='red')

plt.annotate(r'$\cos(\frac{2\pi}{3})=-\frac{1}{2}$',
             xy=(t, np.cos(t)), xycoords='data',
             xytext=(-90, -50), textcoords='offset points', fontsize=16,
             arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))

이번에는 annotate를 하는 것이 나오네요~~ 원하는 지점을 찾고... 동그라미(scatter)를 그리고, 또 기준선을 점선(--)으로 만들고, 그리고 Latex표현으로 좀 더 나이스(^^)하게 표현하고 있네요....

어떤가요? 꽤 괜찮죠??? ㅎㅎ 제가 다 알아낸것이면 좋겠지만.. 제일 처음에 이야기했듯이 전 그냥 따라하고 있을 뿐입니다. 그런데 따라기 중에 재미난 문장이 나오네요~~

Devil is in the details...

와우~~ 큰 그림도 필요하지만... 항상 뭔가 팀웍의 조율, 유지, 동기부여 등등이 모조리 실패할때는 디테일 단계에 들어가면서 자주 발생하더군요. 저 영어문장.. 너무 와닿았지요. 아무튼 제가 지금 참조하는 글의 저자는 저 멋진 문장(물론 영어 속담이지만^^)과 함께

plt.figure(figsize=(10,6), dpi=80)
plt.plot(X, C, color="blue", linewidth=2.5, linestyle="-", label="cosine")
plt.plot(X, S, color="red",  linewidth=2.5, linestyle="-", label="sine")

# Set limits
plt.xlim(X.min()*1.1, X.max()*1.1)
plt.ylim(C.min()*1.1, C.max()*1.1)

# Setting tick labels
plt.xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi], [r'$-\pi$', r'$-\pi/2$', r'$0$', r'$+\pi/2$', r'$+\pi$'])
plt.yticks([-1, 0, +1], [r'$-1$', r'$0$', r'$+1$'])

# Moving spines
ax = plt.gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))

# Adding a legend
plt.legend(loc='upper left', frameon=False)

# Annotate some points
t = 2*np.pi/3
plt.plot([t,t],[0,np.cos(t)], color ='blue', linewidth=2.5, linestyle="--")
plt.scatter([t,],[np.cos(t),], 50, color ='blue')

plt.annotate(r'$\sin(\frac{2\pi}{3})=\frac{\sqrt{3}}{2}$',
             xy=(t, np.sin(t)), xycoords='data',
             xytext=(+10, +30), textcoords='offset points', fontsize=16,
             arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))

plt.plot([t,t],[0,np.sin(t)], color ='red', linewidth=2.5, linestyle="--")
plt.scatter([t,],[np.sin(t),], 50, color ='red')

plt.annotate(r'$\cos(\frac{2\pi}{3})=-\frac{1}{2}$',
             xy=(t, np.cos(t)), xycoords='data',
             xytext=(-90, -50), textcoords='offset points', fontsize=16,
             arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))

# Devil is in the details
for label in ax.get_xticklabels() + ax.get_yticklabels():
    label.set_fontsize(16)
    label.set_bbox(dict(facecolor='white', edgecolor='None', alpha=0.65 ))

라는 코드를 제시하고 있습니다. 바로 축 라벨을 제일 위로 보이는 듯 하게 하기 위한 것이지요.

ㅎㅎ 괜찮네요~~~ 아무튼 검색중에 멋진 설명을 하고 있는 예제를 보고 그대로(정말 말 그대로 그대로) 따라 해본... 저 개인적으로는 Note의 의미밖에 없는 글 하나를 오늘도 포스팅합니다.^^

반응형