난 ㄱㅏ끔 움짤 ㅅㅣ각화를 만든ㄷr...

Zino·2021년 12월 31일
0

이번 포스팅에서는, Python 에서 plot으로 움직이는 gif 파일 만드는 걸 간단히 다뤄보려한다.
쉽게 말해 plot으로 움짤 ¯_( ͡° ͜ʖ ͡°)_/¯ 만들기.

이번 포스팅을 쭉- 따라오신다면 얻을 gif 파일 결과부터 소개하자면,

Img 1.) KDE plot animation for Increasing band-widths

Img 2.) Scatter plot animation for Changing 'Crime Rate' thresholds

👉 이렇게 특정 plot에서 '어떤 조건 변화에 따른 시각화의 변화'를 한 눈에 볼 수 있게 된다.
👉 전체 코드 참조는 아래 링크(colab)에서도 확인 할 수 있다.
plotAnimation.ipynb (Colab)
👉 아래부터는, plot을 움짤로 만들어서 무엇이 좋은지 간단히 살펴보려한다.


1. 이미지 한 장이 답답할 때

자료 시각화는 데이터 분석 과정에서 정말 강력한 도구다. 자료를 직관적으로, 그리고 빠른 시간 내 파악하게 해주고 또 생각치 못했던 새로운 insight를 얻는데 큰 도움이 된다.

그런데 시각화를 하다보면 가끔은 '사진 한 장'으로 나오는게 답답할 때가 많다. 분석가는 직관적인 구조를 보다 효율적으로 보기 위함인데, 여러 조건을 직접 바꿔가면서 모든 경우에 plot을 만들다보면 어느새 무척 비효율적으로 (아주 가끔, 생각없이) 작업하고 있던 나의 모습을 발견하기도 한다.

이런 경우에, plot은 고정이지만 세부 조건을 변화시킬때 자료 시각화가 어떻게 달라지는지 '움짤'로 한 번에 보면 편할 때가 있다.

예를 들어 아래 경우를 보자.

필자는 Boston Housing Prices 데이터로 간단한 과제를 수행 중이었는데, 개별 속성의 분포를 간단한 시각화로 확인하던 중이었다.

속성 'INDUS' 는 (간략히 말해) '해당 지역에 전체 면적 중 산업부지의 비율' 에 해당하는 데이터다.

처음에 이런식으로 모든 속성의 분포를 간략히 훑어보고 분석을 이어갔는데, 후에 전체 산점도 행렬을 뽑아보면서 놓친 것이 발견됐다.

'INDUS' 가 약 18% 정도에 해당하는 지역이 유독 크게 (그리고 정확히 같은 수치로) 몰려있는 걸 찾게 된 건데, 이건 앞서 개별 속성을 단순히 histogram과 kde(:Kernel density estimation) plot으로 파악할 때 깊게 생각치 않아 놓쳤던 부분이다.

사실, histogram과 kde plot은 비모수 통계학적인 방법으로 unique한 분포를 '콕 집어서' 보여주는 그림이 절대 아니다. 조금만 설명을 보태자면, '주어진 자료만을 바탕으로 분포를 추정해보는 방법'으로, 세부 간격(정도..로 간략히 생각할 수 있는) parameter 들을 어떻게 정하느냐에 따라 그 분포가 매우 다르게 표현될 수 있다. 즉, 그때 그때 다른 parameter 상태에 kde density가 의존하기 때문에, 자료의 고유한 분포를 판단하는데 충분이 왜곡된 판단을 내릴 수 있다.

parameter 변화에 따라 분포가 얼마나 달라지는지 확인해보자.

kde plot은 'band-width'(이하, bw)라는 parameter를 기준으로 density를 smoothing 한다. 쉽게 보자면, 자료의 분포를 '얼마나 부드럽게 표현할 것'인가에 대한 값으로 생각할 수 있는데, 역으로 생각해보면 '부드럽게 표현할 수록, 자료의 세부적인 분포정보는 더욱 묻힐 수 있다'는 걸 알 수 있다.

위 그림에서도, bw값이 커짐(즉, 더욱 부드럽게)에 따라, 분포가 상당히 달라지는 것을 볼 수 있다.

  1. 앞서 bw값을 따로 지정하지 않고 보았을 때는, (두 번째 줄, 왼쪽에서 세 번째 plot, default 값에 해당하는) bw=0.3 으로 추정한 분포로, 두 개 봉우리가 있는 '마치 두 개의 정규분포가 겹친' 혼합분포처럼 보인다.
  2. 하지만 bw값을 극단적으로 줄일 수록 'INDUS' = 약 18%에 해당하는 record가 매우 조밀하게 모여있는 것을 알 수 있다.
  3. 극단적으로 bw=0.5까지 높인 경우(두 번째 줄, 오른쪽 첫 번째 plot)에는, smoothing이 강력하게 적용되면서 'INDUS'=18% 부분의 분포가 오히려 더 낮아지게도 보인다.

그래서, 이런 (kde)추정 분포가 bw 세부조정에 따라 어떻게 달리 표현되는지 보기 위해 '움짤'을 만들 생각에까지 이르렀다.(🙄…! )

그 결과로 나온 '움짤' kde plot은 이렇다.

Img 1.) KDE plot animation for Increasing band-widths

위 Img 1.)은, bw값은 0.01 ~ 0.5까지, 0.001간격으로 증가할 때 kde plot이 어떻게 분포를 추정하는지 보여준다. 주황색 막대 그래프는 자료의 원시 상태를 비교적으로 보기위한 histogram으로, (촘촘히 보기위해) bin=50으로 두고 표현한 그림이다.

이처럼, '움짤'로 보면 kde를 통한 분포 추정이 parameter에 따라 얼마나 분포를 다르게 나타낼 수 있는지 한 눈에 볼 수 있다.

(첫 번째 '움짤'은 kde의 성질에 관한 것으로 사실 조금 technical한 내용일 수 있다. 편하게 말하자면, '데이터 파악하는데 kde 특징을 왜 굳이?' ¯\_( ͡° ͜ʖ ͡°)_/¯..?' 라고 생각하 실 수 있다.)

두 번째 '움짤 시각화'는 보다 데이터 구조를 파악하는데 효과적인 케이스라고 할 수 있다.

이 boston housing prices 데이터는, 집 값에 영향을 많이 미치는 변수를 찾기 위해 여러 속성으로 구성되는데, 해당 지역의 범죄율도 'CRIM' 속성으로 포함시켰다.

여러 변수간 상관관계를 들여다 보던 중 'CRIM' 속성이 집 값과 매우 높은 상관관계를 갖는 것을 볼 수 있었는데, 한 가지 눈에 띄는 것이 있었다.
위 'INDUS' 속성에 약 18%에 해당하는 지역이 유독 조밀하게 관찰되는 것과 관련이 깊게, 해당 record들은 다른 속성과의 scatter plot에서도 매우 독특한 영향력을 갖고 있는 것이 보였다.

High(orange): {CRIM ≥ 1.0}, Low(blue): {CRIM < 1.0},

위 그림은, 'CRIM' 값을 임의로 1.0 이상인 경우와 그렇지 않은 경우로 나누어 산점도 행렬을 뽑은 경우다. 단순히 'CRIM' 값이 어떤 특정 임계값(Threshold) 이상/이하로만 나누어 보았는데, 다른 속성들과의 관계에도 상당히 밀접한 관계가 예상되는 패턴이 다소 보인다.

위 경우를 조금 확장해, 'CRIM' 값의 크고/작음에 따라 '범죄율 높음/낮음'으로 구분해 볼 수 있는 더미변수를 만들어 데이터 구조를 보기로 했다. 결과로 나온 '움짤'은 아래와 같다.

Img 2.) Scatter plot animation for Changing 'Crime Rate' thresholds

위 그림을 간략히 보면, 'CRIM'에 대한 임계값 변화로 다수의 변수 분포에서 '범죄율 높음'에 해당하는 record들이 형성하는 분포지역이 뚜렷이 구분되는 경우가 발견된다.

02. 아니, 그래서 '움짤 plot'은 어떻게 만든건데?!

움짤을 만든 코드는 아래와 같다.

# Import Libraries 
import imageio
import os 
from glob import glob 

# setting 
img_dir = './img/'
file_type = '.png'
save_name = 'cr_animation.gif'
speed_sec = {'duration': 0.15}

# Make .gif 
paths = glob(img_dir+'*'+file_type)
imgs = [imageio.imread(img_path) for img_path in paths ]
imageio.mimsave(img_dir+save_name, imgs, **speed_sec)

# References 
# * 파이썬 GIF 애니메이션 만들기 예제 코드(Python imageio) - [물리학과 직장인] 님의 Tistory  

(Git gist) ZinoStudio931/plot_animation.py

'움짤 코드'는 ("파이썬 GIF 애니메이션 만들기 예제 코드 (Python imageio)", '물리학과 직장인'님 Tistory)를 참조했다.

먼저 필요한 package들을 import 하고, (전부 python 내장 library이다.)
몇몇 값들을 정해준다. 필요한 값은,

  1. 움짤로 만들 이미지들이 있는 폴더 경로 ('img_path'),
  2. 이미지 확장자 명('file_type')
  3. 저장하려는 파일 명('save_name')
  4. kwarg로 반영할 사진 간 시간 간격('speed_sec') - 을 지정해주면 된다.

(위 값들은, 필자 편의대로 많이 묶었다. 조건을 다양하게 주려면, f-string(ex: f"{obj}" 같은) 을 적극 이용하면 편리하다.)

# glob를 통해서 '이미지 경로/*(:모든 이미지).png'의 이미지 경로를 담고*
paths = glob(img_dir+'\*'+file_type) 

# list comprehension으로 image read를 담은 후*
imgs = [imageio.imread(img_path) for img_path in paths ]

# 지정된 값을 바탕으로 '.gif'파일을 생성한다.* 
imageio.mimsave(img_dir+save_name, imgs, **speed_sec)

3. 마무리

움짤로 만드는 코드는 사실 복잡하지 않다. (사설이 길어 죄송합… ""ㅁ""; )

그리고, 이미 interactive한 plotting을 지원하는 좋은 package들이 많다. 예를 들면 plotly 도 상당히 다채로운 interactive 시각화 api를 제공하는데 다만, 아쉽게도 html 형식으로 내보내야 유지가 가능하다.(혹시, 좋은 파일형식을 저장방법이 있다면 공유해주세요 😭)

plot을 '움짤'로 만드는 건 오히려 수고스러운 느낌이 드는 것도 사실이지만, '이미지 파일로 변화를 담아내고 싶을 때', '이미지를 내보내야 할 필요가 있을 때' 등 제한적인 경우에 필요에 따라 사용하면 좋을 것이다.


  • 참조
  1. 파이썬 GIF 애니메이션 만들기 예제 코드 (Python imageio), '물리학과 직장인'님 Tistory
profile
Quick-start보단 Manual을 좋아하는 Data Scientist, Zino입니다 :)

0개의 댓글