본문 바로가기
데이터 분석/시각화

[Matplotlib] LineCollection을 이용한 박스 플롯 그려보기.

by 부자 꽁냥이 2022. 10. 23.

반갑습니다~ 꽁냥이입니다. 이번 포스팅은 Matplotlib에서 boxplot을 이용하지 않고 LineCollection 클래스를 이용하여 박스 플롯을 직접 그려보는 방법을 알아보려고 합니다. 즉, 박스 플롯에 있는 몸통을 제외한 나머지 부분을 모두 선분으로 하나하나 그려볼 거예요.

 

Matplotlib에서 제공하는 boxplot의 사용법이 궁금하신 분들은 아래 포스팅을 참고해주세요.

 

[상자 수염 그림(Box and Whisker Plot)] 1. Matplotlib을 이용하여 상자 수염 그림 그리기

[상자 수염 그림(Box and Whisker Plot)] 2. Matplotlib을 이용하여 그룹 상자 수염 그림(박스 플롯) 그리기


   LineCollection을 이용한 박스 플롯 그려보기

그럼 박스 플롯을 그려보겠습니다. 먼저 박스 플롯을 그릴 데이터를 생성했습니다. 표준 정규분포로부터 난수를 생성하고 추가적으로 이상치(1.5 Interquartile Range를 넘어가는 데이터)를 넣었습니다.

 

import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['axes.unicode_minus'] = False
from matplotlib.patches import Rectangle
from matplotlib.collections import PatchCollection, LineCollection

np.random.seed(100)
data = np.concatenate([np.random.randn(100), np.random.rand(5)+3, np.random.rand(5)-4])

 

여기서는 LineCollection 사용방법을 구체적으로 알아보겠습니다. 사실 이번 포스팅의 목적은 박스 플롯을 그리는 것이 아닌 LineCollection 사용법을 알아보기 위한 것입니다. 따라서 박스 플롯을 그리는 부분은 따로 설명하지 않고 주석을 참고해주세요.

 

def boxplot(data, x_center, width, ax, linecolor='k'):
    q1 = np.quantile(data, 0.25) ## 제 1 사분위수
    q3 = np.quantile(data, 0.75) ## 제 2 사분위수
    
    ## 좌측 하단 x, y좌표
    box_left = x_center-0.5*width 
    box_bottom = q1
    height = q3-box_bottom ## 박스 높이 = IQR
    
    ## median
    q2 = np.median(data)
    
    ## 1.5*IQR 내의 가장 끝 부분 데이터 추출
    q1_border = q1-1.5*height
    q1_left_idx = (data <= q1)&(data >= q1_border)
    q1_left = np.min(data[q1_left_idx])
    
    q3_border = q3+1.5*height
    q3_right_idx = (data >= q3)&(data <= q3_border)
    q3_right = np.max(data[q3_right_idx])
    
    ## 1.5*IQR 범위를 벗어나는 관측치
    outlier_idx = (data > q3_border)|(data < q1_border)
    outlier = data[outlier_idx]
    
    ## 선분을 리스트에 담는다
    lines = []
    lines.append([(x_center-0.25*width, q1_left), (x_center+0.25*width, q1_left)]) ## 수염 아래쪽
    lines.append([(x_center-0.5*width, q2), (x_center+0.5*width, q2)]) ## 중앙선
    lines.append([(x_center-0.25*width, q3_right), (x_center+0.25*width, q3_right)]) ## 수염 위쪽
    lines.append([(x_center, q3), (x_center, q3_right)]) ## 위쪽 세로선
    lines.append([(x_center, q1), (x_center, q1_left)]) ## 아래쪽 세로선
    
    lc = LineCollection(lines, edgecolor=linecolor)
    ax.add_collection(lc)
    
    ## 박스 몸통
    box = Rectangle((box_left, box_bottom), width, height, facecolor='none', edgecolor=linecolor)
    ax.add_patch(box)
    
    ## 이상치 마커
    if len(outlier)>0:
        ax.scatter([x_center]*len(outlier), outlier, color=linecolor)

 

line 27~35

LineCollection 클래스는 첫 번째 인자로 리스트를 받으며 리스트의 원소는 x, y 좌표를 튜플로 모아놓은 리스트입니다. 즉, 리스트의 리스트 구조로 되어 있습니다. 

 

따라서 박스를 제외한 나머지 선분을 lines 리스트에 담아주었고 이때 선분을 연결하는 두 좌표를 각각 튜플로 만들어 리스트에 담고 이를 리스트에 담아주었습니다(line 27~32). 그 다음 이 리스트를 LineCollection의 첫 번째 인자로 전달하고 추가적으로 라인 색상을 위해 edgecolor를 사용했습니다(line 34). 이 외에도 LineCollection에는 라인을 꾸밀 수 있는 여러 가지 인자를 제공하는데 이에 대한 내용은 여기를 참고해주세요.

 

LineCollection을 만들었으면 이를 add_collection 메서드를 통해 좌표축에 등록해야 합니다(line 35).


이제 실행을 해보겠습니다. 꽁냥이는 Matplotlib의 boxplot과 꽁냥이가 만든 boxplot 함수를 비교해볼 거예요.

 

fig, axes = plt.subplots(1, 2)
fig.set_facecolor('white')
fig.set_size_inches((16, 8))
ax1 = axes[0]
box = ax1.boxplot(data)
ax1.set_title('Matplotlib')

temp_x = box['boxes'][0].get_xdata()
width = np.max(temp_x)-np.min(temp_x)
x_center = 1
ax2 = axes[1]
boxplot(data, x_center, width, ax=ax2, linecolor='k')
ax2.set_xlim(ax1.get_xlim())
ax2.set_ylim(ax1.get_ylim())
ax2.set_title('꽁냥이')
plt.show()

 

코드를 실행해보면 박스 플롯이 제대로 그려진 것을 알 수 있습니다.


최근 Collection이라는 것을 공부하고 있는데 참 유용한 것들이 많더라고요. 한편으로는 아직도 모르는 것이 너무 많다는 것이 느껴져 저도 모르게 머리가 조아려지게 되네요. 이번 포스팅은 여기까지 입니다.

 

다음에도 좋은 주제로 찾아뵐 것을 약속드리며 이상 포스팅 마치겠습니다. 지금까지 꽁냥이의 글 읽어주셔서 감사합니다.


댓글


맨 위로