matplotlib로 (진짜 어렵게) 그림을 그리는 방법

에제1: 액시즈를 이용한 플랏

- 목표: plt.plot() 을 사용하지 않고 아래 그림을 그려보자.

import matplotlib.pyplot as plt 
plt.plot([1,2,3],'or')
[<matplotlib.lines.Line2D at 0x7fccb28ed6a0>]

- 구조: axis $\subset$ axes $\subset$ figure

- 전략: 그림을 만들고 (도화지를 준비) $\to$ 액시즈를 만들고 (네모틀을 만든다) $\to$ 액시즈에 그림을 그린다. (.plot()을 이용)

- 우선 그림객체를 생성한다.

fig = plt.figure() # 도화지를 준비한다. 
<Figure size 432x288 with 0 Axes>
fig # 현재 도화지상태를 체크 
<Figure size 432x288 with 0 Axes>
  • 그림객체를 출력해봐야 아무것도 나오지 않는다. (아무것도 없으니까..)
fig.add_axes() ## 액시즈를 fig에 추가하라. 
fig.axes ## 현재 fig에 있는 액시즈 정보
fig.axes # 현재 네모틀 상태를 체크 
[]
fig.add_axes([0,0,1,1]) # 도화지안에 (0,0) 위치에 길이가 (1,1) 인 네모틀을 만든다. 
<Axes:>
fig.axes # 현재 네모틀 상태를 체크 --> 네모틀이 하나 있음. 
[<Axes:>]
fig # 현재도화지 상태 체크 --> 도화지에 (하나의) 네모틀이 잘 들어가 있음 
axs1=fig.axes[0] ## 첫번째 액시즈 
axs1.plot([1,2,3],'or') # 첫번쨰 액시즈에 접근하여 그림을 그림 
[<matplotlib.lines.Line2D at 0x7fccb2a49a60>]
fig #현재 도화지 상태 체크 --> 그림이 잘 그려짐 

예제2: 액시즈를 이용한 서브플랏 (방법1)

- 목표: subplot

fig # 현재 도화지 출력

- 액시즈추가

fig.add_axes([1,0,1,1])
<Axes:>
fig.axes
[<Axes:>, <Axes:>]
fig
axs2=fig.axes[1] ## 두번째 액시즈 

- 두번째 액시즈에 그림그림

axs2.plot([1,2,3],'ok') ## 두번째 액시즈에 그림그림 
[<matplotlib.lines.Line2D at 0x7fccb2cfad60>]
fig ## 현재 도화지 체크

- 첫번째 액시즈에 그림추가

axs1.plot([1,2,3],'--') ### 액시즈1에 점선추가 
[<matplotlib.lines.Line2D at 0x7fccb2e5b250>]
fig ## 현재 도화지 체크 

예제3: 액시즈를 이용하여 서브플랏 (방법2)

- 예제2의 레이아웃이 좀 아쉽다.

- 다시 그려보자.

fig = plt.figure() 
<Figure size 432x288 with 0 Axes>
fig.axes
[]
fig.subplots(1,2)
array([<AxesSubplot:>, <AxesSubplot:>], dtype=object)
fig.axes
[<AxesSubplot:>, <AxesSubplot:>]
ax1,ax2 = fig.axes
ax1.plot([1,2,3],'or')
ax2.plot([1,2,3],'ob')
[<matplotlib.lines.Line2D at 0x7fccb30098e0>]
fig
  • 그림이 좀 좁은것 같다. (도화지를 늘려보자)
fig.set_figwidth(10)
fig
ax1.plot([1,2,3],'--')
[<matplotlib.lines.Line2D at 0x7fccb3009f10>]
fig

예제4: 액시즈를 이용하여 2$\times$2 서브플랏 그리기

fig = plt.figure()
fig.axes
[]
<Figure size 432x288 with 0 Axes>
fig.subplots(2,2) 
fig.axes
[<AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>]
ax1,ax2,ax3,ax4=fig.axes 
ax1.plot([1,2,3],'ob')
ax2.plot([1,2,3],'or')
ax3.plot([1,2,3],'ok')
ax4.plot([1,2,3],'oy')
[<matplotlib.lines.Line2D at 0x7fccb316e550>]
fig

예제5: plt.subplots()를 이용하여 2$\times$2 서브플랏 (복습)

x=[1,2,3,4]
y=[1,2,4,3]
_, axs = plt.subplots(2,2) 
axs[0,0].plot(x,y,'o:r') 
axs[0,1].plot(x,y,'Xb') 
axs[1,0].plot(x,y,'xm') 
axs[1,1].plot(x,y,'.--k') 
[<matplotlib.lines.Line2D at 0x7fccb3415220>]

- 단계적으로 코드를 실행하고 싶을때

x=[1,2,3,4]
y=[1,2,4,3]
_, axs = plt.subplots(2,2) 
axs[0,0].plot(x,y,'o:r') 
axs[0,1].plot(x,y,'Xb') 
axs[1,0].plot(x,y,'xm') 
axs[1,1].plot(x,y,'.--k') 
[<matplotlib.lines.Line2D at 0x7fccb36b3eb0>]
  • 어? 그림을 볼려면 어떻게 하지?
_
  • 이렇게 하면된다.

- 단계적으로 그림을 그릴경우에는 도화지객체를 fig라는 변수로 명시하여 받는것이 가독성이 좋다.

x=[1,2,3,4]
y=[1,2,4,3]
fig, axs = plt.subplots(2,2) 
axs[0,0].plot(x,y,'o:r') 
axs[0,1].plot(x,y,'Xb') 
axs[1,0].plot(x,y,'xm') 
axs[1,1].plot(x,y,'.--k') 
[<matplotlib.lines.Line2D at 0x7fccb38b4310>]
fig # 현재 도화지 확인

예제6: plt.subplots()를 2$\times$2 subplot 그리기 -- 액시즈를 각각 변수명으로 저장

x=[1,2,3,4]
y=[1,2,4,3]
fig, axs = plt.subplots(2,2) 
ax1,ax2,ax3,ax4 =axs
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Input In [46], in <module>
----> 1 ax1,ax2,ax3,ax4 =axs

ValueError: not enough values to unpack (expected 4, got 2)
(ax1,ax2), (ax3,ax4) = axs
ax1.plot(x,y,'o:r') 
ax2.plot(x,y,'Xb') 
ax3.plot(x,y,'xm') 
ax4.plot(x,y,'.--k') 
[<matplotlib.lines.Line2D at 0x7fccb3c5ca90>]
fig

예제7: plt.subplots()를 이용하여 2$\times$2 서브플랏 그리기 -- fig.axes에서 접근!

fig, _ = plt.subplots(2,2)
fig.axes
[<AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>]
ax1, ax2, ax3, ax4= fig.axes
ax1.plot(x,y,'o:r') 
ax2.plot(x,y,'Xb') 
ax3.plot(x,y,'xm') 
ax4.plot(x,y,'.--k') 
[<matplotlib.lines.Line2D at 0x7fccb47098e0>]
fig

- 예제7, 예제4와 비교해볼것: 거의 비슷함

- matplotlib은 그래프를 쉽게 그릴수도 있지만 어렵게 그릴수도 있다.

- 오브젝트를 컨트르로 하기 어려우므로 여러가지 축약버전이 존재함.

  • 사실 그래서 서브플랏을 그리는 방법 1,2,3... 와 같은 식으로 정리하여 암기하기에는 무리가 있다.

- 원리를 꺠우치면 다양한 방법을 자유자재로 쓸 수 있음. (자유도가 높음)

제목설정

예제1: plt.plot()

x=[1,2,3]
y=[1,2,2] 
plt.plot(x,y)
plt.title('title')
Text(0.5, 1.0, 'title')

예제2: 액시즈를 이용

fig = plt.figure()
fig.subplots()
<AxesSubplot:>
ax1=fig.axes[0]
ax1.set_title('title')
Text(0.5, 1.0, 'title')
fig

- 문법을 잘 이해했으면 각 서브플랏의 제목을 설정하는 방법도 쉽게 알 수 있다.

예제3: subplot에서 각각의 제목설정

fig, ax = plt.subplots(2,2) 
(ax1,ax2),(ax3,ax4) =ax
ax1.set_title('title1')
ax2.set_title('title2')
ax3.set_title('title3')
ax4.set_title('title4')
Text(0.5, 1.0, 'title4')
fig

- 보기싫음 $\to$ 서브플랏의 레이아웃 재정렬

fig.tight_layout() # 외우세요.. 

예제4: 액시즈의 제목 + Figure제목

fig.suptitle('sup title')
Text(0.5, 0.98, 'sup title')
fig
fig.tight_layout()
fig

축범위설정

예제1

x=[1,2,3]
y=[4,5,6]
plt.plot(x,y,'o')
[<matplotlib.lines.Line2D at 0x7fccb4ba2760>]
plt.plot(x,y,'o')
plt.xlim(-1,5)
plt.ylim(3,7)
(3.0, 7.0)

예제2

fig = plt.figure()
fig.subplots()
<AxesSubplot:>
ax1=fig.axes[0]
import numpy as np 
ax1.plot(np.random.normal(size=100),'o')
[<matplotlib.lines.Line2D at 0x7fccb4e9fa00>]
fig
ax1.set_xlim(-10,110)
ax1.set_ylim(-5,5)
(-5.0, 5.0)
fig

통계예제

- 여러가지 경우의 산점도와 표본상관계수

예제1

np.random.seed(43052)
x1=np.linspace(-1,1,100,endpoint=True)
y1=x1**2+np.random.normal(scale=0.1,size=100)
plt.plot(x1,y1,'o')
plt.title('y=x**2')
Text(0.5, 1.0, 'y=x**2')
np.corrcoef(x1,y1)
array([[1.        , 0.00688718],
       [0.00688718, 1.        ]])

- (표본)상관계수의 값이 0에 가까운 것은 두 변수의 직선관계가 약한것을 의미한 것이지 두 변수 사이에 아무런 함수관계가 없다는 것을 의미하는 것은 아니다.

예제2

- 아래와 같은 자료를 고려하자.

np.random.seed(43052)
x2=np.random.uniform(low=-1,high=1,size=100000)
y2=np.random.uniform(low=-1,high=1,size=100000)
plt.plot(x2,y2,'.')
plt.title('rect')
Text(0.5, 1.0, 'rect')
np.corrcoef(x2,y2)
array([[1.        , 0.00521001],
       [0.00521001, 1.        ]])

예제3

np.random.seed(43052)
_x3=np.random.uniform(low=-1,high=1,size=100000)
_y3=np.random.uniform(low=-1,high=1,size=100000)
plt.plot(_x3,_y3,'.')
[<matplotlib.lines.Line2D at 0x7fccb58110d0>]
radius = _x3**2+_y3**2 
x3=_x3[radius<1]
y3=_y3[radius<1]
plt.plot(_x3,_y3,'.')
plt.plot(x3,y3,'.')
[<matplotlib.lines.Line2D at 0x7fccb5f2da30>]
plt.plot(x3,y3,'.')
plt.title('circ')
Text(0.5, 1.0, 'circ')
np.corrcoef(x3,y3)
array([[ 1.        , -0.00362687],
       [-0.00362687,  1.        ]])

숙제 1

- 예제1,2,3 을 하나의 figure안에 subplot 으로 그려보기 (1$\times$3 행렬처럼 그릴것)

fig, _ = plt.subplots(1,3)
fig.axes
[<AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>]
ax1, ax2, ax3 = fig.axes
np.random.seed(43052)
x1=np.linspace(-1,1,100,endpoint=True)
y1=x1**2+np.random.normal(scale=0.1,size=100)
np.random.seed(43052)
x2=np.random.uniform(low=-1,high=1,size=100000)
y2=np.random.uniform(low=-1,high=1,size=100000)
np.random.seed(43052)
_x3=np.random.uniform(low=-1,high=1,size=100000)
_y3=np.random.uniform(low=-1,high=1,size=100000)
radius = _x3**2+_y3**2 
x3=_x3[radius<1]
y3=_y3[radius<1]
ax1.plot(x1,y1,'.') 
ax2.plot(x2,y2,'.') 
ax3.plot(_x3,_y3,'.') 
ax3.plot(x3,y3,'.') 
[<matplotlib.lines.Line2D at 0x7fccb6d510a0>]
ax1.set_title('y=x**2')
ax2.set_title('rect')
ax3.set_title('circ')
Text(0.5, 1.0, 'circ')
fig
fig.set_figwidth(14)
fig

예제2~3으로 알아보는 두 변수의 독립성

- 예제2,3에 대하여 아래와 같은 절차를 고려하여 보자.

(1) $X\in [-h,h]$일 경우 $Y$의 분포를 생각해보자. 그리고 히스토그램을 그려보자.

(2) $X\in [0.9-h,0.9+h]$일 경우 $Y$의 분포를 생각해보자. 그리고 히스토그램을 그려보자.

(3) (1)-(2)를 비교해보자.

- 그림으로 살펴보자.

h=0.05
plt.hist(y2[(x2> -h )*(x2< h )])
(array([508., 527., 450., 512., 500., 521., 500., 515., 494., 506.]),
 array([-9.99973293e-01, -7.99983163e-01, -5.99993034e-01, -4.00002904e-01,
        -2.00012774e-01, -2.26437887e-05,  1.99967486e-01,  3.99957616e-01,
         5.99947746e-01,  7.99937876e-01,  9.99928006e-01]),
 <BarContainer object of 10 artists>)
h=0.05
_,axs= plt.subplots(2,2) 
axs[0,0].hist(y2[(x2> -h )*(x2< h )])
axs[0,1].hist(y2[(x2> 0.9-h )*(x2< 0.9+h )])
axs[1,0].hist(y3[(x3> -h )*(x3< h )])
axs[1,1].hist(y3[(x3> 0.9-h )*(x3< 0.9+h )])
(array([105., 194., 256., 259., 262., 270., 244., 245., 188.,  64.]),
 array([-0.5171188 , -0.41349885, -0.30987891, -0.20625896, -0.10263902,
         0.00098093,  0.10460087,  0.20822082,  0.31184076,  0.41546071,
         0.51908066]),
 <BarContainer object of 10 artists>)

- 축의범위를 조절하여보자.

h=0.05
_,axs= plt.subplots(2,2) 
axs[0,0].hist(y2[(x2> -h )*(x2< h )])
axs[0,0].set_xlim(-1.1,1.1)
axs[0,1].hist(y2[(x2> 0.9-h )*(x2< 0.9+h )])
axs[0,1].set_xlim(-1.1,1.1)
axs[1,0].hist(y3[(x3> -h )*(x3< h )])
axs[1,0].set_xlim(-1.1,1.1)
axs[1,1].hist(y3[(x3> 0.9-h )*(x3< 0.9+h )])
axs[1,1].set_xlim(-1.1,1.1)
(-1.1, 1.1)

예제4

np.random.seed(43052)
x4=np.random.normal(size=10000)
y4=np.random.normal(size=10000)
plt.plot(x4,y4,'o')
[<matplotlib.lines.Line2D at 0x7fccb81c7880>]
plt.plot(x4,y4,'.')
[<matplotlib.lines.Line2D at 0x7fccb8331d60>]

- 디자인적인 측면에서 보면 올바른 시각화라 볼 수 없다. (이 그림이 밀도를 왜곡시킨다)

- 아래와 같은 그림이 더 우수하다. (밀도를 표현하기 위해 투명도라는 개념을 도입)

plt.scatter(x4,y4,alpha=0.01)
<matplotlib.collections.PathCollection at 0x7fccb82766d0>
np.corrcoef(x4,y4)
array([[ 1.        , -0.01007718],
       [-0.01007718,  1.        ]])
h=0.05
fig, _ = plt.subplots(3,3)
fig.tight_layout()
fig
fig.set_figwidth(10)
fig.set_figheight(10)
fig
fig.axes
[<AxesSubplot:>,
 <AxesSubplot:>,
 <AxesSubplot:>,
 <AxesSubplot:>,
 <AxesSubplot:>,
 <AxesSubplot:>,
 <AxesSubplot:>,
 <AxesSubplot:>,
 <AxesSubplot:>]
k=np.linspace(-2,2,9)
k
array([-2. , -1.5, -1. , -0.5,  0. ,  0.5,  1. ,  1.5,  2. ])
h
0.05
h=0.2
for i in range(9):
    fig.axes[i].hist(y4[(x4>k[i]-h) * (x4<k[i]+h)])
fig

숙제 2

plt.scatter(x4,y4,alpha=0.01)
<matplotlib.collections.PathCollection at 0x7fccb913c580>

- 이 그림의 색깔을 붉은색으로 바꿔서 그려보자. (주의: 수업시간에 알려주지 않은 방법임)

plt.scatter(x4,y4,alpha=0.01, color = 'r')
<matplotlib.collections.PathCollection at 0x7fccb99f0940>

maplotlib + seaborn

import matplotlib.pyplot as plt 
import numpy as np 
import seaborn as sns
x=[44,48,49,58,62,68,69,70,76,79] # 몸무게 
y=[159,160,162,165,167,162,165,175,165,172] #키 
g='F','F','F','F','F','M','M','M','M','M'
plt.plot(x,y,'o')
[<matplotlib.lines.Line2D at 0x7fcc9b514100>]
sns.scatterplot(x=x,y=y,hue=g)
<AxesSubplot:>

- 두 그림을 나란히 겹쳐 그릴수 있을까?

fig, (ax1,ax2) = plt.subplots(1,2) 
ax1.plot(x,y,'o')
[<matplotlib.lines.Line2D at 0x7fcc9b74e160>]
sns.scatterplot(x=x,y=y,hue=g,ax=ax2) 
<AxesSubplot:>
fig
fig.set_figwidth(8)
fig
ax1.set_title('matplotlib')
ax2.set_title('seaborn')
Text(0.5, 1.0, 'seaborn')
fig

- 마치 matplotlib에 seaborn을 plugin하듯이 사용할 수 있다.

matplotlib vs seaborn

- 디자인이 예쁜 패키지를 선택하여 하나만 공부하는 것은 그렇게 좋은 전략이 아니다.

sns.set_theme()
plt.plot([1,2,3],[3,4,5],'or')
[<matplotlib.lines.Line2D at 0x7fcc9c0f57c0>]

예제

- 아래와 같은 자료가 있다고 하자.

np.random.seed(43052)
x=np.random.normal(size=1000,loc=2,scale=1.5)

- 이 자료가 정규분포를 따르는지 어떻게 체크할 수 있을까?

plt.hist(x)
(array([ 10.,  24.,  99., 176., 232., 222., 165.,  53.,  16.,   3.]),
 array([-2.44398446, -1.53832428, -0.6326641 ,  0.27299608,  1.17865626,
         2.08431645,  2.98997663,  3.89563681,  4.80129699,  5.70695718,
         6.61261736]),
 <BarContainer object of 10 artists>)

- 종모양이므로 정규분포인듯 하다.

- 밀도추정곡선이 있었으면 좋겠다. (KDE로 추정) $\to$ seaborn을 활용하여 그려보자.

sns.histplot(x,kde=True)
<AxesSubplot:ylabel='Count'>

- 종모양인것 같다.

- 그렇다면 아래는 어떤가?

np.random.seed(43052)
from scipy import stats 
y=stats.t.rvs(10,size=1000)
sns.histplot(y,kde=True)
<AxesSubplot:ylabel='Count'>

- 종모양이다..?

- 비교

fig, (ax1,ax2) = plt.subplots(1,2) 
sns.histplot(x,kde=True,ax=ax1)
sns.histplot(y,kde=True,ax=ax2)
<AxesSubplot:ylabel='Count'>
xx= (x-np.mean(x)) / np.std(x,ddof=1) 
yy= (y-np.mean(y)) / np.std(y,ddof=1) 

fig, (ax1,ax2) = plt.subplots(1,2) 
sns.histplot(xx,kde=True,ax=ax1)
sns.histplot(yy,kde=True,ax=ax2)
<AxesSubplot:ylabel='Count'>
xx= (x-np.mean(x)) / np.std(x,ddof=1) 
yy= (y-np.mean(y)) / np.std(y,ddof=1) 

fig, ((ax1,ax2),(ax3,ax4)) = plt.subplots(2,2) 
ax1.boxplot(xx) 
sns.histplot(xx,kde=True,ax=ax2)
ax3.boxplot(yy)
sns.histplot(yy,kde=True,ax=ax4)
<AxesSubplot:ylabel='Count'>
fig.tight_layout()
fig

- 주의: 아래와 같이 해석하면 잘못된 해석이다.

  • $y$ 히스토그램을 그려보니 모양이 종모양이다. $\to$ $y$는 정규분포이다

- 관찰: boxplot을 그려보니 $y$의 꼬리가 정규분포보다 두꺼워 보인다.

숙제3

sns.set_theme(style="whitegrid", palette="pastel")
plt.plot([1,2,3],[3,4,5],'or')
[<matplotlib.lines.Line2D at 0x7fcc9d6ac520>]
xx= (x-np.mean(x)) / np.std(x,ddof=1) 
yy= (y-np.mean(y)) / np.std(y,ddof=1) 

fig, ((ax1,ax2),(ax3,ax4)) = plt.subplots(2,2) 
ax1.boxplot(xx) 
sns.histplot(xx,kde=True,ax=ax2)
ax3.boxplot(yy)
sns.histplot(yy,kde=True,ax=ax4)
<AxesSubplot:ylabel='Count'>
fig.tight_layout()
fig