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

[Matplotlib] 시간에 따른 선 그래프(Line Chart)와 박스 플롯(Box Plot, 상자 수염 그림) 같이 그리기

by 부자 꽁냥이 2023. 2. 10.

안녕하세요~ 꽁냥이에요. 박스 플롯이나 선 그래프 둘 다 그 자체로도 훌륭한 시각화 수단이지만 같이 사용하게 되었을 경우 더 많은 정보를 한눈에 보여줄 수 있습니다. 이번 포스팅에서는 Matplotlib을 이용하여 박스 플롯과 선 그래프를 같이 그려보는 방법에 대해서 알아보겠습니다.

 

Matplotlib을 이용한 선 그래프나 박스 플롯을 그리는 방법은 아래 포스팅을 참고해 주세요.

 

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

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

 

[선 그래프(Line graph)] 1. Matplotlib을 이용하여 선 그래프 그리기

[선 그래프(Line graph)] 2. Matplotlib을 이용하여 선 그래프 꾸미기

[선 그래프(Line graph)] 3. Matplotlib을 이용하여 여러 개 선 그래프 겹쳐 그리기


   선 그래프(Line Chart)와 박스 플롯(Box Plot, 상자 수염 그림) 같이 그리기

1) 기본

먼저 가상 데이터를 만들어보겠습니다. 해당 데이터는 2022년 1월 1일부터 2월 1일의 날짜를 생성하고 각 날짜마다 100개의 랜덤 숫자를 생성한 것입니다.

 

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['axes.unicode_minus'] = False

start_date = pd.to_datetime('2022-01-01') ## 시작 날짜
end_date = pd.to_datetime('2022-02-01') ## 마지막 날짜 
dates = pd.date_range(start_date,end_date,freq='D') ## 일단위로 생성

## 가상 데이터 생성
np.random.seed(100)
df_list = []
for i, date in enumerate(dates):
    temp_df = pd.DataFrame()
    scale = 2*np.random.rand(1)[0]
    mean = np.sin(i)
    temp_data = scale*np.random.randn(100)+mean
    temp_df['DATE'] = [date.strftime('%Y-%m-%d')]*len(temp_data)
    temp_df['VALUE'] = temp_data
    df_list.append(temp_df)
    
df = pd.concat(df_list)
df.head()

 

 

이제 boxplot을 그리는 함수를 소개하겠습니다. 해당 함수는 데이터 target_data, 박스 플롯을 그릴 x 좌표 xtick, 좌표축 ax, 선 색상 color, 박스 색상 facecolor, 박스 폭 width, 레이아웃 순서 zorder 그리고 투명도 alpha 인자를 받아서 주어진 x 좌표에 박스 플롯을 그리게 됩니다.

 

def trend_boxplot(target_data, xtick, ax, color, facecolor, width=0.5, zorder=-100, alpha=0.5):
    boxplot = ax.boxplot([target_data], positions=[xtick], widths=width,
               patch_artist=True, zorder=zorder)
    ## 박스, 아웃라이어, 중앙선 스타일
    for item in ['boxes', 'fliers', 'medians']:
        for v in boxplot[item]:
            if item == 'boxes':
                plt.setp(v, facecolor=facecolor, edgecolor=color, alpha=alpha) 
            elif item == 'fliers':
                plt.setp(v, markerfacecolor=color, 
                         markersize=2.5, markeredgecolor='none', alpha=alpha)
            else:
                plt.setp(v, color='white')
                
    ## 나머지 선분 스타일
    for item in ['whiskers', 'caps']:
        for v in zip(boxplot[item][::2], boxplot[item][1::2]):
            if item == 'whiskers':
                plt.setp(v, color=color, linewidth=1, linestyle='--', alpha=alpha)
            else:
                plt.setp(v, color=color, linewidth=1, alpha=alpha)

 

이제 선 그래프와 박스 플롯을 같이 그려보겠습니다. 선 그래프는 일자별 VALUE의 평균값을 그리고 각 일자별 Raw Data를 박스 플롯으로 그릴 겁니다.

 

## 날짜로 그룹화하여 VALUE 집계
grouped_df = df.groupby('DATE').agg({'VALUE':'mean'}).reset_index()

fig = plt.figure(figsize=(15,6))
fig.set_facecolor('white')
ax = fig.add_subplot()

xticks = range(len(grouped_df)) ## x축 눈금
ax.plot(xticks, grouped_df['VALUE'], color='r') ## 평균 선 그래프
for xtick in xticks: ## 각 눈금에 대하여 박스 플롯 두개씩 그린다.
    date = grouped_df.iloc[xtick]['DATE']
    target_df = df[df['DATE']==date]
    trend_boxplot(target_df['VALUE'], xtick, ax, color='r', facecolor='y')
    
tick_step = 5 ## 눈금 표시 간격
ax.set_xticks(xticks[::tick_step])
ax.set_xticklabels(grouped_df['DATE'].to_list()[::tick_step])
plt.show()

 


2) 응용

이번엔 일자별로 두 그룹의 데이터를 나타낼 것입니다. 즉, 일자별로 두 개의 박스 플롯이 그려지는 것이지요. 아래 함수는 두 데이터를 받아서 정해진 x좌표를 중심으로 좌우에 그려지게 됩니다. 이때 인접한 x좌표 사이의 공백은 padding을 이용하여 설정하도록 했습니다.

 

def two_trend_boxplot(data1, data2, xtick, xunit, ax, colors, facecolors, 
                      width=0.5, zorder=-100, alpha=0.5, padding=0):
    width_offset = (1-padding)*0.5
    left_end = xtick-(width_offset*xunit)
    right_end = xtick+(width_offset*xunit)
    trend_boxplot(data1, left_end, ax, colors[0], facecolors[0], width=width, zorder=zorder, alpha=alpha)
    trend_boxplot(data2, right_end, ax, colors[1], facecolors[1], width=width, zorder=zorder, alpha=alpha)

 

이제 데이터를 새롭게 생성해 보겠습니다. 두 그룹이 있어야 하므로 VALUE1과 VALUE2 칼럼에 데이터를 넣어주었습니다.

 

start_date = pd.to_datetime('2022-01-01') ## 시작 날짜
end_date = pd.to_datetime('2022-02-01') ## 마지막 날짜 
dates = pd.date_range(start_date,end_date,freq='D') ## 일단위로 생성

## 가상 데이터 생성
np.random.seed(100)
df_list = []
for i, date in enumerate(dates):
    temp_df = pd.DataFrame()
    scale1 = 2*np.random.rand(1)[0]
    scale2 = 2*np.random.rand(1)[0]
    mean1 = np.sin(i)
    mean2 = np.cos(i)
    temp_data1 = scale1*np.random.randn(100)+mean1
    temp_data2 = scale2*np.random.randn(100)+mean2
    temp_df['DATE'] = [date.strftime('%Y-%m-%d')]*len(temp_data1)
    temp_df['VALUE1'] = temp_data1
    temp_df['VALUE2'] = temp_data2
    df_list.append(temp_df)
    
df = pd.concat(df_list)
df.head()

 

 

기본 편에서 살펴보았던 것과 마찬가지로 두 그룹의 일자별 VALUE1, 2의 평균을 두 개의 선으로 그린 다음 일자별로 두 그룹의 Raw Data를 두개의 박스 플롯으로 그려나갈 것입니다.

 

## 날짜로 그룹화하여 VALUE 집계
grouped_df = df.groupby('DATE').agg({'VALUE1':'mean', 'VALUE2':'mean'}).reset_index()

fig = plt.figure(figsize=(15,6))
fig.set_facecolor('white')
ax = fig.add_subplot()

xticks = range(len(grouped_df)) ## x축 눈금
ax.plot(xticks, grouped_df['VALUE1'], color='r', marker='o') ## 평균 선 그래프
ax.plot(xticks, grouped_df['VALUE2'], color='k', marker='o') ## 평균 선 그래프
xunit = xticks[1]-xticks[0] ## 눈금 단위
for xtick in xticks: ## 각 눈금에 대하여 박스 플롯 두개씩 그린다.
    date = grouped_df.iloc[xtick]['DATE']
    target_df = df[df['DATE']==date]
    two_trend_boxplot(target_df['VALUE1'], target_df['VALUE2'], xtick, xunit, ax, 
                      colors=['r', 'k'], 
                      facecolors=['y', 'b'],
                      padding=0.6,
                      width=0.3
                     )
    
tick_step = 5 ## 눈금 표시 간격
ax.set_xticks(xticks[::tick_step])
ax.set_xticklabels(grouped_df['DATE'].to_list()[::tick_step])
plt.show()

 

 

박스 플롯과 선 그래프가 예쁘게 잘 그려진 것을 알 수 있습니다.


이번 포스팅에서는 박스 플롯과 선 그래프를 같이 그리는 방법을 알아보았습니다. 선 그래프의 점 하나가 산포를 갖고 있는 경우 박스 플롯과 같이 표현하면 더 많은 정보를 효과적으로 나타낼 수 있습니다. 오늘 배운 내용은 알아두시면 반드시 도움이 되리라 생각합니다. 

 

다음에도 좋은 주제로 찾아뵙겠습니다. 감사합니다.


댓글


맨 위로