안녕하세요~ 꽁냥이에요.
상자 수염 그림(Box and Whisker Plot 또는 Box Plot)은 데이터의 분포를 시각화하는데 많이 사용되고 있습니다. 이번 포스팅에서는 상자 수염 그림이 무엇인지 Matplotlib을 이용하여 상자 수염 그림을 어떻게 그리는지에 대해서 알아보겠습니다.
이번 포스팅에서 다루는 내용은 다음과 같습니다.
1. 상자 수염 그림이란?
상자 수염 그림은 데이터의 분포를 시각화하는 그림으로써 활용되며 데이터의 사분위 수(Quartile)를 이용한다는 특징이 있습니다. 상자 수염 그림을 설명하기 위해서는 사분위 수와 사분위 범위를 알아야합니다.
먼저 사분위 수는 데이터를 내림차순으로 정렬했을 때 데이터의 개수를 4 등분하는 점이라고 이해하시면 됩니다. 아래 그림을 참고하시면 됩니다.
사분위수는 데이터 개수를 4등분하는 점이기 때문에 3개의 사분위수가 있습니다. 이를 각각 제1 사분위 수, 제2 사분위 수, 제3 사분위 수라고 합니다. 위키백과에서 가져온 각 사분위 수 정의는 다음과 같습니다.
제1 사분위 수 (Q1) : 중앙값 기준으로 하위 50% 중의 중앙값, 전체 데이터 중 하위 25%에 해당하는 값
제2 사분위 수 (Q2) : 전체 데이터의 중앙값과 같습니다.
제3 사분위 수 (Q3) : 중앙값 기준으로 상위 50% 중의 중앙값, 전체 데이터 중 상위 25%에 해당하는 값
그리고 사분위 범위(Interquartile Range : IQR)는 제3 사분위 수에서 제1 사분위 수를 뺀 값입니다.
이제 상자 수염 그림을 그리는 방법을 설명할 수 있습니다. 먼저 아래와 같이 데이터가 있다고 해보죠.
1, 2, 7, 9, 11, 20, 33, 34, 35, 41, 66, 88
상자 수염 그림을 그리는 순서는 다음과 같습니다.
1. 주어진 데이터에서 각 사분위 수 그리고 사분위 범위를 계산합니다.
Q2(중앙값) = (20 + 33)/2 = 26.5
Q3 = (35 + 41)/2 = 38
Q1 = (7 + 9)/2 = 8
IQR = Q3 - Q1 = 38 - 8 =30
2. 그래프에서 Q1과 Q3를 가로로 하는 직사각형을 그리고, Q2(중앙값)에 해당하는 위치에 가로선을 긋습니다.
3. Q3 보다 큰 값 중에서 1.5×IQR 이내에 있는 최대값, Q1 보다 작은 값 중에서 1.5×IQR 이내에 있는 최소값에 가로선을 긋고 이를 직사각형과 선분으로 연결시켜줍니다.
38(=Q3)과 83(=Q3+1.5×IQR) 사이에 있는 값 중에서 최대값은 66이므로 이 위치에 가로선을 그려줍니다. 또한 8(=Q1)과 -37(=Q1-1.5×IQR) 사이에 있는 값 중에서 최소값은 1이므로 이 위치에 가로선을 그려줍니다.
4. Q3으로부터 1.5×IQR 초과하거나 Q1으로부터 1.5×IQR미만인 데이터는 점이나, 원 등으로 따로 표시합니다.
2. 상자 수염 그림 그리기
이제 Matplotlib을 이용하여 상자 수염 그림을 그려보겠습니다. 먼저 필요한 모듈을 임포트하고 데이터를 만들어줍니다.
import matplotlib.pyplot as plt
import numpy as np
## 데이터 만들기
spread = np.random.rand(50) * 100
center = np.ones(25) * 50
flier_high = np.random.rand(10) * 100 + 100
flier_low = np.random.rand(10) * -100
data = np.concatenate((spread, center, flier_high, flier_low))
상자 수염 그림은 Matplotlib에서 제공하는 boxplot을 이용하면 쉽게 그릴 수 있습니다.
fig = plt.figure(figsize=(8,8)) ## 캔버스 생성 사이즈 8*8
fig.set_facecolor('white') ## 캔버스 배경색 설정
plt.boxplot(data) ## 상자 수염 그림 출력
fontsize=13 ## 폰트 사이즈
plt.xticks([1],['Sample'], fontsize=fontsize) ## x축 눈금 라벨
plt.title('Box Plot',fontsize=fontsize) ## 타이틀
plt.show()
line 4
상자 수염 그림을 그리고자 하는 데이터를 boxplot에 첫 번째 인자로 넣어주면 됩니다.
boxplot에는 상자 수염 그림을 예쁘게 꾸밀 수 있는 여러 가지 인자를 제공하고 있습니다(boxplot에서 제공하는 인자가 궁금하신 분들은 여기를 참고하세요). 다음은 같은 데이터를 이용하여 상자 수염 그림을 좀 더 꾸며 보았습니다. 설명은 주석으로 대체하겠습니다.
fig = plt.figure(figsize=(8,8)) ## 캔버스 생성 사이즈 8*8
fig.set_facecolor('white') ## 캔버스 배경색 설정
plt.boxplot(data,patch_artist=True, ## 박스 안쪽 여백에 색상을 입히기 위해서 반드시 True로 해줘야함
flierprops=dict(markerfacecolor='lightgreen'),## 마커테두리 컬러
medianprops=dict(color='red',linewidth=3), ## 중앙선 테두리 컬러
capprops=dict(color='blue', linewidth=3), ## 최상단, 최하단 선과 직사각형을 잇는 선 테두리 컬러
whiskerprops=dict(color='blue', linewidth=3), ## 세로선 컬러
boxprops=dict(facecolor='lightblue', color='blue'), ## 박스테두리와 안쪽 여백 컬러
)
fontsize = 13 ## 폰트 사이즈
plt.xticks([1],['Sample'], fontsize=fontsize) ## x축 눈금 라벨
plt.title('Box Plot',fontsize=fontsize) ## 타이틀
plt.show()
3. 상자 수염 그림 여러 개 그리기
상황에 따라서 상자 수염 그림 여러 개를 한 곳에 그려야할 수 있습니다. boxplot은 한 곳에 여러개 상자 수염 그림을 그릴 수 있는 기능도 제공하는데요.
꽁냥이는 한 곳에 2개의 상자 수염 그림을 그려보려고 합니다. 이를 위해 두 개의 데이터를 만들어봅시다.
## 데이터 만들기
data1 = np.concatenate((np.random.rand(50) * 100,
np.ones(25) * 50,
np.random.rand(10) * 100 + 100,
np.random.rand(10) * -100))
data2 = np.concatenate((np.random.rand(50) * 100,
np.ones(25) * 50,
np.random.rand(10) * 100 + 100,
np.random.rand(10) * -100))
아래 코드는 2개의 상자 수염 그림을 그리는 코드입니다.
num_group = 2 ## 그룹개수
edgecolor = 'blue' ## 선 색깔
linewidth = 2 ## 선 굵기
facecolors = ['#32a852','#8e59d9'] ## 면색
medianlinecolor = 'white' ## 중앙선 색
fig = plt.figure(figsize=(8,8)) ## 캔버스 생성 사이즈 8*8
fig.set_facecolor('white') ## 캔버스 배경색 설정
box = plt.boxplot([data1, data2], patch_artist=True) ## 상자 수염 그림 출력
## 상자, 1.5*IQR 초과/미만 데이터(flier), 중앙선 넘어가는 데이터 꾸미기
for item in ['boxes', 'fliers', 'medians']:
for i, v in enumerate(box[item]):
if item == 'boxes':
plt.setp(v, facecolor=facecolors[i], edgecolor=edgecolor)
elif item == 'fliers':
plt.setp(v, markerfacecolor=facecolors[i])
else:
plt.setp(v, color=medianlinecolor)
## 최상단, 최하단 선분(caps)과 상자와 이어지는 선분(whisker) 꾸미기
for item in ['whiskers','caps']:
for i, v in enumerate(zip(box[item][::2],box[item][1::2])):
if item == 'whiskers':
plt.setp(v, color=edgecolor, linewidth=linewidth, linestyle='--')
else:
plt.setp(v, color=edgecolor, linewidth=linewidth)
fontsize = 13 ## 폰트 사이즈
plt.xticks([1,2],['Group1','Group2'], fontsize=fontsize) ## x축 눈금 라벨
plt.title('Box Plot',fontsize=fontsize) ## 타이틀
plt.show()
line 9
만들어진 데이터를 리스트로 담아 boxplot 첫 번째 인자로 넘겨주면 그룹 개수만큼 상자 수염 그림이 그려집니다. 이런 식으로 3개 이상의 상자 수염 그림을 그리고 싶다면 boxplot( [ 데이터1, 데이터2, 데이터3, ... ] )처럼 해주면 됩니다.
line 12~27
박스의 구성요소를 그룹별로 다르게 적용시켰습니다. 주목해야 할 것은 상자(boxes)와 Q3 + 1.5×IQR를 초과하거나 Q1 - 1.5×IQR 보다 작은 데이터(fliers), 중앙선(medians) 3개의 요소와 최상단, 최하단 선분(caps), 상자와 이어지는 선분(whisker) 2개의 요소는 따로 분리시켜 스타일을 적용시켜야합니다. 왜냐하면 전자는 요소별로 1개의 객체가 생기지만 후자는 요소별로 2개의 객체가 생성되기 때문입니다(후자는 위아래 두 개씩 생긴다는 것을 생각해보면 너무 당연합니다). 각 객체별로 스타일을 다르게 적용시키기 위하여 setp을 이용하였습니다. setp에 대한 설명은 여기를 참고하세요.
위 코드를 실행해보세요.
위와 같이 2개의 멋진 상자 수염 그림이 완성되었습니다.
다음 포스팅에서는 그룹 내 하위 카테고리가 있는 경우의 상자 수염 그림을 그리는 방법에 대해서 소개하겠습니다. 지금까지 꽁냥이의 글 읽어주셔서 감사합니다.
참고자료
상자 수염 그림 - 위키백과
상자 수염 그림 예제 - Boxplot Demo
'데이터 분석 > 시각화' 카테고리의 다른 글
[히스토그램(Histogram)] 1. Matplotlib을 이용하여 히스토그램 그리기. (0) | 2021.01.11 |
---|---|
[상자 수염 그림(Box and Whisker Plot)] 2. Matplotlib을 이용하여 그룹 상자 수염 그림(박스 플롯) 그리기 (0) | 2020.12.05 |
Matplotlib을 이용하여 레이더 차트(Radar chart) 그리기! (15) | 2020.09.28 |
산점도 행렬 그려보기 with Python (0) | 2020.09.25 |
[도넛 차트(Donut chart)] 2. Matplotlib을 이용하여 하위 그룹을 포함하는 도넛 차트(Nested donut chart) 그리기 (2) | 2020.09.12 |
댓글