본문 바로가기

데이터분석

[데이터시각화] 시계열 그래프 위에 결측정보 text로 겹치지 않게 표시하기

시계열 데이터를 다루다가 데이터가 지속적으로 수집되지 않고 중간 중간 결측이 발생하고 있음이 발견되었다. 

결측이 발생하게 되면 모델 학습과 운영에 있어서 크리티컬한 문제기 때문에 결측을 채우는 것이 맞는지, 결측을 어떻게 채워야 하는지, 모든 값에 대해서 채우는 것이 맞는지 고민이 필요하다. 

결측에 대해서 단순히 전체 데이터의 결측 개수나 비율을 계산하여 판단할 수도 있겠지만, 
시계열 데이터에서는 데이터의 연속성이 중요하기 때문에 데이터의 결측이 연속적으로 얼마나 지속되었는지 살펴 볼 필요가 있다. 

그래서 결측에 대해 직관적으로 살펴보기 위해 시계열 그래프를 그려 살펴보고자 했고,
결측이 얼마나 지속되었는지에 따라 결측 보간방법을 다르게 하기 위해 특정 시간 이상 결측이 지속되는 경우는 따로 표시했다. 

[ 그래프 시각화 방법 ] 

1) 시간에 흐름에 따른 데이터 시각화 => x축을 datetime으로 
2) 결측 지속 시간 표시 => datetime 기준으로 오름차순 후, diff()를 이용해 row마다의 시간 차이를 계산
3) 특정 시간 이상 결측 강조 표시 =>  새로 데이터가 들어온 시간에 수직선으로 결측지속시간과 함께 표시 

시각화 과정에서 결측이 지속된 시간을 바로 알 수 있도록 그래프 위에 text를 표시하게 되었는데 
이때 matplotlib 패키지의 text 함수를 이용하면 된다.
주요 파라미터는 아래와 같고, 그 외에 fontsize, color, fontstyle 등 지정하고 싶다면
matplotlib.pyplot.text 설명 페이지를 참고 하면 된다. 

plt.text(x, y, s, fontdict=None, **kwargs) 
- x: x축 위치 
- y : y축 위치
- s : text 
- horizontalalignment(ha) : 수평 방향 정렬 {'left', 'center', 'right'}
- verticalalignment (va) : 수직 방향 정렬 {'baseline', 'bottom', 'center', 'center_baseline', 'top'}
import matplotlib.pyplot as plt

## 시간 컬럼 전처리 
df = df.sort_values('datetime').reset_index(drop=True)
df['datetime'] = pd.to_datetime(df['datetime'])  # 시간 컬럼 datetime 타입으로 변환 
df['time_diff'] = df['datetime'].diff()  # 시간 차이 계산 

## 시각화 
plt.figure(figsize=(15, 5))
over_2_idx = df[df['time_diff'] > pd.Timedelta(hours=2)]
plt.title(f"Over 2 hours Graph")

for i in range(len(over_2_idx)): 
    plt.axvline(over_2_idx['datetime'].iloc[i], color='red', linestyle='--', alpha=0.7)
    plt.text(over_2_idx['datetime'].iloc[i], 37, str(over_2_idx['time_diff'].iloc[i]), ha='center', va='top', rotation=30)

df.set_index("datetime")['tmpt_vl'].plot()

 

text가 겹치지 않도록 rotation으로 각도를 주었는데, 찾아보니 auto-wrapping text 기능도 제공하고 있어 
wrap=True 옵션으로 text의 길이도 자동으로 보정이 가능하다. (참고 : Auto-wrapping text 사이트)

import matplotlib.pyplot as plt

fig = plt.figure()
plt.axis((0, 10, 0, 10))
t = ("This is a really long string that I'd rather have wrapped so that it "
     "doesn't go outside of the figure, but if it's long enough it will go "
     "off the top or bottom!")
plt.text(4, 1, t, ha='left', rotation=15, wrap=True)
plt.text(6, 5, t, ha='left', rotation=15, wrap=True)
plt.text(5, 5, t, ha='right', rotation=-15, wrap=True)
plt.text(5, 10, t, fontsize=18, style='oblique', ha='center',
         va='top', wrap=True)
plt.text(3, 4, t, family='serif', style='italic', ha='right', wrap=True)
plt.text(-1, 0, t, ha='left', rotation=-15, wrap=True)

plt.show()