벌써 어느새 막바지에 다다르다니, 조금 어색하다.

시간이 이렇게나 빠르게 흐르다니 항상 무언가에 집중할때는 시간감각이 없어지다가

항상 마무리단계가 다가올때가 되면, 지나간 시간에 대한 자각이 된다.

혼자 공부하는 것보다 확실히 많은 것을 배우게 되고, 생각하게 되는 그런 공부가

사실 거의 끝이 난다고 하니 조금은 아쉬운 기분이 든다.

 

5주차에는 지난시간에 전처리된 데이터의 분포를 한번에 알아볼 수 있도록 시각적으로 표현하는

기본적인 시각화에 대해서 알아보았는데, 이를 조금 더 심화하게 알아보도록 하겠다.

 

 

1. 맷플롯립 기본요소 알아보기

(1) Figure 객체 

우리는 그림을 그리기 위해서는 그림을 그릴 '종이'가 항상 필요하다. 맷플롯립에서는 그래프들을 출력하기 위한 캔버스와 같은 종이의 역할이 바로 Figure 객체라고 생각하면 된다. 하여 맷플롯립에서는 도화지를 Figure, 그래프를 Axes, 축을 Axis라고 구별하여 말한다. 이 객체는 그래프를 그릴때 어떤 사이즈로 그릴것인지를 주로 조정하는곳에 사용한다. 이번에 그래프를 그릴 다음 코드를 이용하여 파일을 다운로드 하여 그려보자.

import gdown
import pandas as pd

gdown.download('https://bit.ly/3pK7iuu', 'ns_book7.csv', quiet=False)

ns_book7 = pd.read_csv('ns_book7.csv', low_memory=False)
ns_book7.head()
더보기

[출력결과]

저번시간에 그렸던 산점도 그래프를 우선적으로 그려보자, 저번 시간 복습 겸 떠올리자면 matplotlib.pyplot 모듈을 호출하여 scatter() 함수로 그리면된다. 이때, 우리는 도서권수를 x축으로 하고, 대출건수를 y축으로 하여 그래프를 그려보자. 그리고, alpha 값을 0.1로 하여 데이터의 빈도를 투명도로 표시하여 그려보자.

import matplotlib.pyplot as plt

plt.scatter(ns_book7['도서권수'], ns_book7['대출건수'], alpha=0.1)
# scatter 함수의 매개변수는 x축, y축 순서로 넘겨주면 된다. 

plt.show()
더보기

[출력결과]

그래프를 잘 그리긴 했으나, 그래프의 크기를 자유자재로 조절하였으면 좋겠다. 보는 사람이 조금더 편하게 볼수있도록 세심한 배려를 해주기 위해서는 이러한 기능이 필요한데, 이때 사용하는 것이 figure 객체라고 보면 된다. 이를 활용하여 다시 그려보자.

plt.figure(figsize=(9,6))
# 캔버스의 크기를 지정하는 경우에 가로와 세로의 길이를 튜플로 하여 figsize 매개변수에 전달하면된다.
# 이때, 그래프의 크기를 지정하는 것이 아닌 캔버스의 크기를 지정하는 것이다.
# 캔버스의 크기가 정해지면, 기본적으로 matplotlib의 함수는 타이트레이아웃으로, 캔버스를 최대한 채워서 그래프를 출력한다.

plt.scatter(ns_book7['도서권수'], ns_book7['대출건수'], alpha=0.1)

plt.show()
# figure 객체는 show() 메서드를 호출하여 그래프를 그리면 자동으로 초기화 된다.
더보기

[출력결과]

이전보다 크게 출력되어 조금 더 가시성이 좋아진 것이 보인다. 하지만, 뭔가 이상한데 실제로 출력된 그래프의 크기가 내가 지정한 크기와는 사뭇 조금 다르다. 왜 그런 것일까?

 

이유는 조금 단순하긴 하지만, 우리가 사용하는 모니터가 다 다르기 때문이다. 게임을 하거나 화면을 조정할때 '해상도'라는 말을 한번쯤 들었을 것이다. 아무리 그래프의 크기를 지정하더라도, 모니터의 해상도와 DPI라는 값으로 인해 출력되는 크기가 다르게 나올수 밖에 없다는 것이다. 즉, 원하는 크기를 그대로 적용하고 싶다면 이를 고려하여야 한다는 것이다. 이는 figure 객체에서 설정이 가능하다.

 

- DPI(dot per inch)

: 화면을 출력할때, 1인치의 크기의 화면을 몇개의 픽셀로 표현하는지를 말하는 단위이다.  엄연히 이야기하면 '점'이라고 이야기 해야한다. 왜냐하면 기준을 픽셀로 하면 PPI라고 따로 이야기하지만, 종종 같은 의미로 사용하기도 하니 여기에선 넘어가자.

 

이제 이에 대한 이유를 알아내었으니, 이를 적용하여 그래프를 그려보자.

plt.figure(figsize=(900/72, 600/72))
# 맷플롯립의 DPI 기본값은 72 이므로, 픽셀을 기준으로 그림을 그리기 위해서는 픽셀값을 인치로 환산해야한다.
# 픽셀에서 인치로 환산하는 식은 (픽셀값)/(DPI값) 을 적용하면 된다.


plt.scatter(ns_book7['도서권수'], ns_book7['대출건수'], alpha=0.1)
plt.show()
더보기

[출력결과]

출력결과의 크기를 보았는데, 여전히 실제 크기가 다르게 나온다. 이번엔 왜그럴까?

 

이전에 맷플롯립은 타이트레이아웃을 기본으로 한다고 했었다. 기억나는가? 바로 이것이 두번째 이유이다. 타이트 레이아웃은 말 그대로 주어진 캔버스(figure) 사이즈를 공백없이 최대한 활용하여 출력한다. 그래서 일부 크기가 강제로 조절되는 것이다. 하여 이를 변경하기 위해서는 bbox_inches 매개변수를 None으로 정의해주면 해결이 된다. 하지만, 이렇게 크기에 중요시 하다보면, 우리는 코딩과정에서 코드와 함께보아야하는데 조금 불편할 수 있으니 되도록이면 크기 출력은 가시성을 떨어뜨리지 않는 선에서만 조절하면 된다.

 

%config InlineBackend.print_figure_kwargs ={'bbox_inches' : None}

plt.figure(figsize=(900/72, 600/72))
# 그래프의 크기를 figsize를 변경하지 않아도, 조정할 수 있는데 바로 dpi를 변경하면된다.
# plt.figure(dpi=144)
# 다만, 차이점은 그래프의 크기가 전부 커지는 형식이므로 마커등의 크기도 함께 커진다.

plt.scatter(ns_book7['도서권수'], ns_book7['대출건수'], alpha=0.1)
plt.show()

 

더보기

[출력결과]

 

(2) rcParams 객체

Figure 객체는 캔버스라고 하면, rcParams 객체는 그림을 그리는 방법을 담은 설명서라고 보면된다. 그런데, 설명서라고 하지만 직접 그리는 방식을 수정하고 변경할 수 있는 특징이 있다. 마커의 모양을 변경하거나, 해상도 변경 등 다양한 기능들이 있다.

#dpi 기본값 변경

plt.rcParams['figure.dpi'] = 100

#마커 모양변경하기

plt.rcParams['scatter.marker']	 	# 현재 적용된 마커의 정보를 반환하여 준다.
plt.rcParams['scatter.marker'] = '*'	# 원하는 마커의 모양을 문자로 지정하면 변경하여 적용한다.

plt.scatter(ns_book7['도서권수'], ns_book7['대출건수'], alpha=0.1)
plt.show()
더보기

[출력결과]

이외에도 '+' 등과같은 다양한 마커가 있는데, 종류가 궁금하면 맷플롯립 마커 옵션을 직접 찾아보면 나온다.

 

(3) 서브플롯

처음에 우리는 matplotlib은 그래프와 캔버스 그리고 축을 구분하여 부른다고 했다. 왜냐하면 우리가 그림을 그릴때도 한 종이안에 여러가지의 그림을 그릴 수 있지 않은가? 맷플롯립 또한 여러 그래프를 하나의 figure안에 그릴 수 있다. 이를 우리는 서브플롯이라고 이야기한다. 여러개의 그래프를 그리기 위해서는 subplot() 메서드로 그리고 싶은 그래프의 수를 지정해주면 된다.

 

서브플롯 또한 크기 조절이 가능한 figsize 매개변수가 존재한다. 그리고 서브플롯에는 그래프를 구분하기 쉽도록 제목을 달아주는 set_title() 메서드를 지원하므로 원하는 제목을 매개변수로 넘겨주면 된다.

fig, axs = plt.subplots(2, figsize=(6,8))
# 두개의 그래프(서브플롯)을 세로로 하나씩 그린다.
# 만약 가로로 그리고 싶다면, 행렬을 만들듯이 서브플롯을 정의하면된다.
# plt.subplots(1, 2, figsize=(6,8)) << 1행 2열(가로로 2개 출력)
# figsize 매개변수로 서브플롯의 크기를 지정할 수 있다.

axs[0].scatter(ns_book7['도서권수'], ns_book7['대출건수'], alpha=0.1)
axs[0].set_title('scatter plot') # 그래프의 제목을 만들어 준다.
# 첫번째 그래프 그리기
# 기본적으로 subplot은 Figure에 두개의 그래프 정보를 객체로 만들어서 그린다.

axs[1].hist(ns_book7['대출건수'], bins=100)
axs[1].set_yscale('log')
axs[1].set_title('histogram')
# 히스토그램을 두번째 서브플롯에 그린다.


# 만약 축의 이름을 지정해주고 싶다면 set_xlable, set_ylabel 를 활용하면된다

fig.show()
더보기

[출력결과]

 

2. 선그래프와 막대그래프

산점도랑 히스토그램을 그리는 것으로 어느정도 빈도에 대한 데이터의 특성은 파악했다. 이제는 데이터가 시간흐름에 따라 어떤 양상을 띄는지에 대한 '추이'가 조금 궁금하다. 추이를 파악하는데에는 선그래프나 막대그래프가 적절한데, 이를 한번 지금부터 그려보도록 하자.

 

(1) 전처리

일단 선그래프를 그리기 전에, 데이터 전처리는 필수이다. 하여 우선 데이터를 그리고자하는 그래프에 맞게 가공하자.

count_by_year = ns_book7['발행년도'].value_counts()
# 해당 열의 데이터 값별로 빈도수를 세서 반환한다.

count_by_year = count_by_year.sort_index()
# 연도별로 보는 것이 가독성에 좋으니, 이를 위해 인덱스값으로 정렬한다.
# 기본적으로 문자는 가나다 순, 숫자는 오름차순으로 정리한다.

count_by_year
더보기

[출력결과]

출력결과를 봤더니, 앞으로 발행예정일 도서들이 대출이 되었다고 나온다. 즉, 잘못된 데이터가 있다는 소리다. 하여 해당 데이터를 제거하기 위해 발행년도(index)가 2030년 이하인 데이터만 추려보자.

count_by_year = count_by_year[count_by_year.index <= 2030]
# 발행년도가 2030년 이하인 데이터만 불리언 슬라이싱을 통해 추려낸다.

count_by_year
더보기

[출력결과]

막대그래프를 그리기 위해서는 주제별로 도서를 정리하여야하는데, 도서에는 주제별로 구분하는 십진분류코드가 있다. 이는 우리 데이터에 '주제분류번호' 열에 있으니 이를 활용하여 분류를 해보자. 근데 가만보니, 해당 열에는 NaN라는 결측값도 있어 분류하는 과정에서 제외작업도 거쳐야한다. 이는 지난시간에 배웠던 넘파이에 np.nan 메서드를 활용하면 된다.

import numpy as np

def kdc_1st_char(no): #함수를 정의하여 주제분류번호의 열값이 NaN인 경우 -1, 아니면 첫글자를 반환한다.
    if no is np.nan:
        return '-1'
    else:
        return no[0]

count_by_subject = ns_book7['주제분류번호'].apply(kdc_1st_char).value_counts()
count_by_subject
더보기

[출력결과]

 

(2) 선그래프

선그래프를 그리려면, matplotlib에 plot() 메서드를 사용하면 된다. 5-1에 배웠던 내용을 적용하여, 해상도와 축이름, 그래프 제목 등을 명시하여 가독성을 조금 높여보자.

plt.plot(count_by_year.index, count_by_year.values)
# count_by_year 객체로 전달하면 굳이 x축과 y축을 따로 지정하여 보내지 않아도 알아서 구분한다.
# 만약 선종류 및 색상, 마커등을 변경하고 싶으면 marker, linestyle, color 매개변수를 지정하면된다.
# 마커,선모양,색상을 한번에 정의하고 싶다면 '.:r'처럼 한번에 문자열로 넘겨주어도 된다.
# ex) plt.plot(count_by_year, '*-g') //마커를 별로하고, 선을 실선, 색상을 초록으로 한다.

plt.title('Books by year')
plt.xlabel('year')
plt.ylabel('number of books')

plt.show()
더보기

[출력결과]

출력결과를 보니 자동으로 지정된 x축의 개수가 너무 간격이 커 분석하기에는 힘든것 같다. 하여 조금 더 구간을 세분화 해보자. 이는 xticks() 함수를 활용하면 된다. 만약 y축의 간격을 조정하고 싶다면 yticks() 함수를 사용한다.

plt.plot(count_by_year, '*-g')
plt.title('Books by year')
plt.xlabel('year')
plt.ylabel('number of books')

plt.xticks(range(1947, 2030, 10))
# x축의 간격을 1947년부터 10년 간격으로 2030까지 표시한다.

for idx, val in count_by_year[::5].items():
	plt.annotate(val, (idx, val)) 
   	 # 만약 가시성을 위해 표시된 텍스트의 위치를 조정하려면 xytext 매개변수에 좌표를 튜플로 지정해주면 된다.
 	 # 조정하였으나 별 차이가 없다면 textcoords 매개변수를 offsets points로 지정하여 포인트단위로 위치를 지정하게 바꾼다.
# 마커를 찍을 위치를 반복문으로 만든다.
# 시작부터 5개 간격으로 데이터를 하나씩 찍는다.

plt.show()
더보기

[출력결과]

 

(3) 막대그래프 그리기 

선그래프는 plot()을 활용하여 그렸다면, 막대그래프는 bar() 함수를 사용하면 쉽게 그릴수 있다. 그전에 앞서 선그래프를 그릴때 처럼 막대위에 도서권수를 찍되, 잘 보이도록 나타내자.

plt.bar(count_by_subject.index, count_by_subject.values)
# 만약 막대크기(너비)를 지정하고 싶다면 width 매개변수를 지정하면된다.
# 또한 색상을 수정하길 원한다면 color 매개변수에 원하는 색상을 지정하면된다.

plt.title('Books by subject')
plt.xlabel('subject')
plt.ylabel('number of books')

for idx, val in count_by_subject.items():
	plt.annotate(val, (idx, val), xytext=(0, 2), textcoords='offset points')

# 텍스트의 위치를 조정하고 싶다면 ha 매개변수에 위치를 지정하면 된다.
# 텍스트의 크기와 색상은 fontsize, color 매개변수를 지정하면된다.


plt.show()
더보기

[출력결과]

만약 세로로 그리고 싶은 것이 아니라 가로로 그리고 싶다면 bar() 메서드 대신, barh()메서드를 활용한다. 이때 주의할 점은 x축과 y축의 위치가 바뀌므로 이를 주의하여야 한다. 뿐만 아니라, 막대의 너비가 높이로 바뀌므로 width가 아니라 height 매개변수가 된다. 그리고 annotate() 함수 또한 x축과 y축의 좌표가 반전된다. 하여 이를 고려하여 막대그래프를 가로로 그려보자.

plt.barh(count_by_subject.index, count_by_subject.values, height=0.7, color='blue')
# 매개변수 순서가 (x, y) 에서 (y, x)로 바뀌니 주의하자.

plt.title('Books by subject')
plt.xlabel('number of books')
plt.ylabel('subject')

for idx, val in count_by_subject.items():
	plt.annotate(val, (val, idx), xytext=(2, 0), textcoords='offset points', \
    fontsize = 8, va='center', color='green')

plt.show()
더보기

[출력결과]

 

 

3. [필수숙제]

p. 314의 손코딩(맷플롯립에서 bar()함수로 막대 그래프 그리기)을 코랩에서 그래프 출력하고 화면 캡처하기

 

+ Recent posts