본문 바로가기

데이터/데이터 분석

[혼공] ch 6. 복잡한 데이터 표현하기

1. 객체지향 API로 그래프 꾸미기 

 

1) pyplot 방식과 객체지향 API 방식 

 

- 맷플롭립으로 그래프를 그리는 방식은 2가지 이다. 

  • matplotlib.pyplot에 있는 함수를 사용하는 pyplot 방식
  • 명시적으로 피겨 객체를 만들고 이 객체의 메서드를 사용하는 객체지향 API

 

pyplot 방식으로 그래프 그리기 

 

- matplotlib.pyplot에 있는 함수를 사용하면 함수들이 하나의 피겨 객체에 대한 상태를 공유한다. 

- 다음과 같이 간단한 선 그래프를 그리면 plot() 함수와 title() 함수는 동일한 피겨 객체에 적용된다. 

- plot() 함수에 리스트 형태로 데이터를 전달하고, title() 함수에는 그래프 제목을 넣어 그리기 

 

plt.plot([1,4,9,16])
plt.title('simple line graph')
plt.show()

 

 

객체지향 API 방식으로 그래프 그리기 

 

- 동일한 그래프를 피겨 객체와 Axes 객체를 사용하는 객체지향 API 방식으로 다시 그리기 

fig,ax = plt.subplots() ##하나의 Axes 객체를 가지는 피겨 생성
ax.plot([1,4,9,16])
ax.set_title('simple line graph')
plt.show()

 

- 복잡한 그래프를 그리는 경우에는 객체지향 방식을 사용하는 것이 좋다. 

 

2) 그래프에 한글 출력하기 

 

- 폰트 설치 

# 노트북이 코랩에서 실행 중인지 체크합니다.
import sys
if 'google.colab' in sys.modules:
    !echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
    # 나눔 폰트를 설치합니다.
    !sudo apt-get -qq -y install fonts-nanum
    import matplotlib.font_manager as fm
    font_files = fm.findSystemFonts(fontpaths=['/usr/share/fonts/truetype/nanum'])
    for fpath in font_files:
        fm.fontManager.addfont(fpath)

 

 

폰트 지정하기(1) : font.family 속성 

 

- 맷플롯립의 기본 폰트는 영문 sans-serif 폰트이다. rcParmas 객체의 'font.family' 속성에 저장되어 있다. 

plt.rcParams['font.family']

 

 

- 기본 폰트를 나눔고딕 폰트를 의미하는 'NanumGothic'으로 바꾸려면 다음과 같이 작성 

# 나눔고딕 폰트를 사용합니다.
plt.rcParams['font.family'] = 'NanumGothic'

 

 

폰트 지정하기(2) :rc() 함수 

 

- 나눔바른고딕 폰트인 'NanumBarunGothic'으로 바꾸기

- rc() 함수의 첫 번째 매개변수에는 설정할 그룹을 지정한다. 

- 'font.family'의 경우 font가 그룹이고 family는 그룹의 하위속성이다. 

- 따라서 rc() 함수의 첫 번째 매개변수에는 그룹인 'font'를 지정하고 두 번째 매개변수에는 그룹 하위 속성을 지정한다. 

- rc() 함수를 사용하면 한 그룹 내의 여러 설정을 동시에 지정할 수 있다. 

 

plt.rc('font', family='NanumBarunGothic', size=11)

print(plt.rcParams['font.family'], plt.rcParams['font.size'])

 

- 그래프 제목을 한글로 출력 

plt.plot([1, 4, 9, 16])
plt.title('간단한 선 그래프')
plt.show()

 

 

 

3) 출판사별 발행 도서 개수 산점도 그리기 

 

- ns_book7 데이터프레임을 사용해서 출판사별 도서가 어떤 연도에 많이 발행되었는지 산점도 그리기 

 

import gdown

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

import pandas as pd

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

 

 

- 발행 도서가 많은 상위 30개의 출판사의 데이터 중 일부를 사용 

 

고유의 출판사 목록 만들기 

 

- value_counts() 메서드를 사용해 고유한 출판사 목록 만들기 

top30_pubs = ns_book7['출판사'].value_counts()[:30]
top30_pubs

 

 

- ns_book7 데이터프레임에서 상위 30개 출판사에 해당하는 행을 표시하는 불리언 인덱스 만들기 

- 어떤 행의 출판사가 top30_pubs 목록에 있는 출판사 중 하나에 해당하는지 검사하려면 isin() 메서드를 사용해야 한다. 

- 상위 30개에 해당하는 출판사는 True, 아닌 출판사는 False로 반환 

 

top30_pubs_idx = ns_book7['출판사'].isin(top30_pubs.index)
top30_pubs_idx

 

 

- 원본 데이터프레임의 행 길이와 동일한 불리언 인덱스를 만들었다. 

- 상위 30개 출판사의 발행 도서 개수가 총 몇개인지 세기 -> sum() 메서드를 호출하면 값을 모두 더한다. 

top30_pubs_idx.sum()

 

- 이 중 1.000개만  선택

- 데이터프레임의 행을 무작위로 선택하려면 sample() 메서드를 사용 

- random_state 매개변수에 임의의 숫자를 적는다. 

 

ns_book8 = ns_book7[top30_pubs_idx].sample(1000, random_state=42)
ns_book8.head()

 

 

산점도 그리기 

 

- x축에는 '발행년도'열, y축에는 '출판사'열을 지정한다. 

 

fig, ax = plt.subplots(figsize=(10, 8))
ax.scatter(ns_book8['발행년도'], ns_book8['출판사'])
ax.set_title('출판사별 발행도서')
fig.show()

 

 

 

 

값에 따라 마커 크기를 다르게 나타내기 

 

- scatter() 함수는 마커의 크기를 지정할 수 있는 s 매개변수를 제공한다. 

- s 매개변수의 기본값인 rcParams['lines.markersize']의 제곱을 사용한다. 

- s 매개변수의 값을 하나의 실수로 바꾸면 산점도의 모든 마커 크기가 동일하게 바뀐다. 하지만 입력 데이터와 동일한 길이의 배열을 지정하면 각 데이터마다 마커의 크기가 다른 산점도를 그릴 수 있다. 

 

- '대출건수'열을 마커 크기로 전달하여 대출건수가 많은 도서를 상대적으로 크게 그리기 

 

fig, ax = plt.subplots(figsize=(10, 8))
ax.scatter(ns_book8['발행년도'], ns_book8['출판사'], s=ns_book8['대출건수'])
ax.set_title('출판사별 발행도서')
fig.show()

 

 

 

마커 꾸미기 

 

1) 투명도 조절 

 

- alpha 매개변수는 해당 연도에 출간된 도서가 얼마나 많은지 대략 가늠할 수 있다. 

- 출간 도서가 많은 경우 마커가 많이 겹치기 때문에 alpha 매개변수를 낮게 설정해도 상대적으로 진하게 나타날 것이다. 

 

2) 마커 테두리 색 바꾸기 

 

- edgecolor 매개변수는 마커 테두리의 색을 결정한다. 

- 기본값은 'face'이다. 

- 마커 테두리를 그리면 여러 개의 마커가 겹칠 때 경계를 구분할 수 있어 유용하다. 

- 검은색을 의미하는 'k'로 지정 

 

3) 마커 테두리 선 두께 바꾸기 

 

 - linewidths 매개변수는 마커 테두리 선의 두께를 결정하며 기본값은 1.5이다. 

 - 0.5로 지정하여 조금 더 테두리를 얇게 그리기 

 

4) 산점도 색 바꾸기 

 

 - c 매개변수는 산점도의 색을 지정한다. 

 - c 매개변수에 데이터 개수와 동일한 길이의 배열을 전달하면 각 데이터를 다른 색깔로 그릴 수 있다. 

 

fig, ax = plt.subplots(figsize=(10, 8))
ax.scatter(ns_book8['발행년도'], ns_book8['출판사'],
           linewidths=0.5, edgecolors='k', alpha=0.3,
           s=ns_book8['대출건수']*2, c=ns_book8['대출건수'])
ax.set_title('출판사별 발행도서')
fig.show()

 

 

값에 따라 색상 표현하기 : 컬러맵

 

- 맷플롯립은 컬러 맵(color map)을 사용하여 값에 따른 색상을 다르게 표현하다. 

- 앞서 그린 산점도는 scatter() 함수가 사용하는 기본값인 viridis 컬러맵으로 표현되었다. 

- 자주 사용하는 컬러맵 중 하나는 jet 컬러맵인데 낮은 값일수록 짙은 파란색이고, 높은 값으로 갈수록 점차 노란색으로 바뀌었다가 붉은색이 된다. 

 

- 산점도를 jet 컬러맵으로 다시 그리기 

- 컬러맵의 색깔이 어떤 대출건수 값에 대응하는지 참조 정보를 보여주는 컬러 막대(color bar)를 그래프 옆에 그리기

- 컬러맵은 cmap 매개변수로 지정할 수 있고, 컬러막대는 scatter()함수가 반환하는 객체를 colorbar() 메서드에 전달하면 된다. 

 

fig, ax = plt.subplots(figsize=(10, 8))
sc = ax.scatter(ns_book8['발행년도'], ns_book8['출판사'],
                linewidths=0.5, edgecolors='k', alpha=0.3,
                s=ns_book8['대출건수']**1.3, c=ns_book8['대출건수'], cmap='jet')
ax.set_title('출판사별 발행도서')
fig.colorbar(sc)
fig.show()

 

 

 

 

2. 맷플롯립의 고급 기능 배우기 

 

 

1) 하나의 피겨에 여러 개의 선 그래프 그리기 

 

- plot() 함수를 여러 번 호출하기

- 상위 30위에 해당한느 출판사는 True로 표시하여 불리언 배열 반환하기 

top30_pubs = ns_book7['출판사'].value_counts()[:30]
top30_pubs_idx = ns_book7['출판사'].isin(top30_pubs.index)

 

- ns_book7 데이터프레임에서 상위 30위에 해당하는 '출판사','발행연도','대출건수'열만 추출하여 ns_book9를 만들어 준다. 

- groupby() 메서드를 사용해 '출판사'와 '발행년도 열을 기준으로 행을 모은 후 sum() 메서드로 '대출건수'열의 합을 구해준다. 

ns_book9 = ns_book7[top30_pubs_idx][['출판사', '발행년도', '대출건수']]
ns_book9 = ns_book9.groupby(by=['출판사', '발행년도']).sum()

 

- 이 데이터프레임의 인덱스는 '출판사'와 '발행년도'열이다. 인덱스를 초기화하기 위해 reset_index() 메서드를 호출한다. 

 

ex) '황금가지'라는 출판사의 데이터 

ns_book9 = ns_book9.reset_index()
ns_book9[ns_book9['출판사'] == '황금가지'].head()

 

 

 

선 그래프 2개 그리기 

 

- 2개의 출판사 데이터를 만들어 '발행년도'에 대한 '대출건수' 선 그래프 그리기 

- 출판사별로 각각 데이터프레임을 만든다. 

line1 = ns_book9[ns_book9['출판사'] == '황금가지']
line2 = ns_book9[ns_book9['출판사'] == '비룡소']

 

- line1과 line2 데이터프레임의 '발행년도'열과 '대출건수'열로 plot() 함수를 두 번 호출한다. 

fig, ax = plt.subplots(figsize=(8, 6))
ax.plot(line1['발행년도'], line1['대출건수'])
ax.plot(line2['발행년도'], line2['대출건수'])
ax.set_title('년도별 대출건수')
fig.show()

 

 

 

- plot() 함수를 호출할 때 각 선 그래프에 레이블(label)을 추가하고, 마지막에 legend() 메서드를 호출하면 범례가 추가된다. 

fig, ax = plt.subplots(figsize=(8, 6))
ax.plot(line1['발행년도'], line1['대출건수'], label='황금가지')
ax.plot(line2['발행년도'], line2['대출건수'], label='비룡소')
ax.set_title('년도별 대출건수')
ax.legend()
fig.show()

 

 

 

선 그래프 5개 그리기 

 

- top30_pubs의 인덱스에 있는 상위 5개 출판사의 '발행년도'에 대한 '대출건수' 그래프 그리기 

- 5개 출판사에 대한 데이터프레임을 각각 만들지 않고 대신 for문과 슬라이스 연산자를 사용해 선 그래프를 그린다. 

- 대부분의 대출건수가 발생한 1990년대 이후 데이터가 중점적으로 보이도로고 set_xlim() 메서드를 사용한다. 첫 번째 매개변수에는 x축의 최솟값이고 두 번째 매개변수는 x축의 최댓값이다. 

fig, ax = plt.subplots(figsize=(8, 6))
for pub in top30_pubs.index[:5]:
    line = ns_book9[ns_book9['출판사'] == pub]
    ax.plot(line['발행년도'], line['대출건수'], label=pub)
ax.set_title('년도별 대출건수')
ax.legend()
ax.set_xlim(1985, 2025) ## 1985부터 2025까지 x축의 범위를 선택
fig.show()

 

 

 

스택 영역 그래프 

 

- 스택 영역 그래프(stacked area graph)는 하나의 선 그래프 위에 다른 선 그래프를 차례대로 쌓는 것이다. 

- 그래프 사이의 간격이 y축의 값이 된다. 

- 스택 영역 그래프는 맷플롯립의 stackplot() 메서드로 그릴 수 있다. 

-  첫 번째 매개변수에 x축의 값이 '발행년도'를 전달하고, 두 번째 매개변수에는 y축 값을 2차원 배열로 전달해야 한다. 

 

- ns_book9 데이터프레임에서 '발행년도'열의 개별 값을 하나의 열로 구성한 후 상위 10개 출판사의 연도별 대출건수를 스택 영역 그래프로 구리기 

 

 

a. pivot_table() 메서드로 각 '발행년도'열의 값을 열로 바꾸기 

 

- 데이터 구조를 바꾸는 방법은 판다스의 pivot_table() 메서드로 처리할 수 있다. 

- index 매개변수columns 매개변수에 원본 데이터프레임의 열을 지정하면 각 열의 고유한 값이 피벗 테이블로 변환된 데이터프레임의 인덱스와 열이 된다. 

 

ns_book10 = ns_book9.pivot_table(index='출판사', columns='발행년도')
ns_book10.head()

 

 

 

- '발행년도' 열의 값이 하나의 열이 되었다.

- 한가지 눈 여겨 보아야할 것은 열이 다단으로 구성되어 있다는 점이다.

ns_book10.columns[:10]

 

 

b. '발행년도'열을 리스트 형태로 바꾸기 

 

-판다스 인덱스 객체에서 호출할 수 있는 get_level_values() 메서드는 다단으로 구성된 열 이름에서 선택한 항목만 가져올 수 있다. 따라서  ('대출건수',1947)로 되어있는 열 이름 중 연도로 구성된 두 번째 항목만 가져올 수 있다. 

 

top10_pubs = top30_pubs.index[:10]
year_cols = ns_book10.columns.get_level_values(1)

 

 

c. stackplot() 메서드로 스택 영역 그래프 그리기 

 

- stackplot() 메서드에 x축 값에는 year_cols를 , y축에는 ns_book10에서 상위 10개 출판사에 해당하는 행만 골라 전달한다. 

 

fig, ax = plt.subplots(figsize=(8, 6))
ax.stackplot(year_cols, ns_book10.loc[top10_pubs].fillna(0), labels=top10_pubs)
ax.set_title('년도별 대출건수')
ax.legend(loc='upper left') ## 범례를 왼쪽 상단에 표시 
ax.set_xlim(1985, 2025)
fig.show()

 

 

 

2) 하나의 피겨에 여러 개의 막대 그래프 그리기 

 

- 막대 그래프를 여러 개 그리는 방법은 bar() 함수를 여러 번 호출하면 된다. 

fig, ax = plt.subplots(figsize=(8, 6))
ax.bar(line1['발행년도'], line1['대출건수'], label='황금가지')
ax.bar(line2['발행년도'], line2['대출건수'], label='비룡소')
ax.set_title('년도별 대출건수')
ax.legend()
fig.show()

 

 

- 두 출판사의 막대를 나란히 옆으로 그리려면 막대의 기본 너비인 0.8의 절반인 0.4 너비로 두 막대 그래프를 그린 다음, x축에서 막대 하나 너비인 0.4의 절반씩 떨어지도록 그려야 한다. 

 

fig, ax = plt.subplots(figsize=(8, 6))
ax.bar(line1['발행년도']-0.2, line1['대출건수'], width=0.4, label='황금가지')
ax.bar(line2['발행년도']+0.2, line2['대출건수'], width=0.4, label='비룡소')
ax.set_title('년도별 대출건수')
ax.legend()
fig.show()

 

 

스택 막대 그래프 

 

- bar() 메서드의 bottom 매개변수를 사용하여 수동으로 막대를 쌓을 수 있다. 이 매개변수는 막대가 시작할 y 좌표를 결정한다. 

- bar() 메서드의 bottom 매개변수에 데이터를 전달하여 막대를 쌓아 올리거나, y축 방향으로 데이터를 누적하여 그려야 한다. 

 

ex)

간단한 두 개의 리스트 height1, height2를 만들어 height1 막대 그래프 위에 height2를 쌓기 

height1 = [5,4,7,9,8]
height2 = [3,2,4,1,2]

plt.bar(range(5), height1, width=0.5)
plt.bar(range(5), height2, bottom=height1, width=0.5)
plt.show()

 

 

- 그래프를 그리기 전에 아예 막대의 길이를 누적해 놓고 이 값으로 막대 그래프를 그리는 방법

- height1과 height2를 더하여 height3를 만든 후 , height3를 먼저 그리고 height1을 그린다. 

 

height3 = [a + b for a, b in zip(height1, height2)]

plt.bar(range(5), height3, width=0.5)
plt.bar(range(5), height1, width=0.5)
plt.show()

 

 

데이터값 누적하여 그리기 

 

- 판다스 데이터프레임의 cumsum() 메서드를 사용하여 값을 누적

 

- ns_book10 데이터프레임에서 상위 다섯 개의 출판사의 2013 ~2020년 대출건수를 확인 

ns_book10.loc[top10_pubs[:5], ('대출건수',2013):('대출건수',2020)]

 

 

- 동일한 데이프레임에서 cumsum() 메서드를 이어서 호출 

ns_book10.loc[top10_pubs[:5], ('대출건수',2013):('대출건수',2020)].cumsum()

 

 

- 파이썬의 range() 함수로 ns_book12 데이터프레임 행 개수만큼 인덱스 번호를 만들고, for문에 reversed() 함수를 사용해 인덱스의 역순으로 반복하여 그리기 

 

ns_book12 = ns_book10.loc[top10_pubs].cumsum()

fig, ax = plt.subplots(figsize=(8, 6))
for i in reversed(range(len(ns_book12))): # 가잗 큰 막대부터 그려야 하므로 누적 합계가 가장 큰 마지막 출판사부터 그
    bar = ns_book12.iloc[i]     # 행 추출
    label = ns_book12.index[i]  # 출판사 이름 추출
    ax.bar(year_cols, bar, label=label)
ax.set_title('년도별 대출건수')
ax.legend(loc='upper left')
ax.set_xlim(1985, 2025)
fig.show()

 

 

 

3) 원 그래프 그리기 

 

- 원 그래프는 전체 데이터에 대한 비율을 원의 부채꼴로 나타낸 그래프이다. 파이 차트(pie chart)라고도 부른다. 

- top30_pubs의 값과 인덱스에서 처음 10개만 선택하여 각각 data와 labels 변수에 저장한다. 

data = top30_pubs[:10]
labels = top30_pubs.index[:10]

 

- 원 그래프는 맷플롯립의 pie() 메서드로 그릴 수 있다. 첫 번째 매개변수에 앞서 정의한 data를 전달하면 자동으로 데이터 전체에 대한 비율을 계산하여 그래프를 그린다. 

- 부채꼴 모양 위에 표시할 출판사 이름은 labels 매개변수에 전달한다. 

 

fig, ax = plt.subplots(figsize=(8, 6))
ax.pie(data, labels=labels)
ax.set_title('출판사 도서비율')
fig.show()

 

 

 

비율 표시하고 부채꼴 강조하기 

 

- pie() 메서드의 autopct 매개변수에는 파이썬의 %연산자에 적용할 포맷팅 문자열을 전달할 수 있다. 

- 예를 들어 %d를 전달하면 각 부채꼴의 비율이 정수로 표시된다. 여기에서는 소수점 첫째 자리까지 포함한 실수로 표시 

 

- 중요한 항목의 경우 해당 부채꼴 조각을 원 그래프에서 조금 떨어뜨려 시각적으로 부각시킬 수 있다, 

- 이렇게 하려면 explode 매개변수에 떨어뜨리길 원하는 조각의 간격을 반지름의 비율로 저장한다. 

- explode 매개변수에 전달하는 리스트 길이는 data 배열의 길이와 같아야 한다

 

fig, ax = plt.subplots(figsize=(8, 6))
ax.pie(data, labels=labels, startangle=90,
       autopct='%.1f%%', explode=[0.1]+[0]*9)
ax.set_title('출판사 도서비율')
fig.show()

 

 

 

4) 여러 종류의 그래프가 있는 서브플롯 그리기 

 

- 산점도, 스택 영역 그래프, 스택 막대 그래프, 원 그래프를 하나의 피겨에 모두 그리기 

- 2차원 배열 형태로 4개의 서브플롯 그리기 

- subplots() 함수의 첫 번째 매개변수와 두 번째 매개변수에는 서브플롯의 행 개수와 열 개수를 지정할 수 있다. 

- subplots() 함수에서 반환 받은 Axes 객체를 사용할 때는 2차원 배열처럼 각 격자의 위치를 지정해야 한다. 

 

 

fig, axes = plt.subplots(2, 2, figsize=(20, 16))

# 산점도
ns_book8 = ns_book7[top30_pubs_idx].sample(1000, random_state=42)
sc = axes[0, 0].scatter(ns_book8['발행년도'], ns_book8['출판사'],
                        linewidths=0.5, edgecolors='k', alpha=0.3,
                        s=ns_book8['대출건수'], c=ns_book8['대출건수'], cmap='jet')
axes[0, 0].set_title('출판사별 발행도서')
fig.colorbar(sc, ax=axes[0, 0])

# 스택 선 그래프
axes[0, 1].stackplot(year_cols, ns_book10.loc[top10_pubs].fillna(0),
                     labels=top10_pubs)
axes[0, 1].set_title('년도별 대출건수')
axes[0, 1].legend(loc='upper left')
axes[0, 1].set_xlim(1985, 2025)

# 스택 막대 그래프
for i in reversed(range(len(ns_book12))):
    bar = ns_book12.iloc[i]     # 행 추출
    label = ns_book12.index[i]  # 출판사 이름 추출
    axes[1, 0].bar(year_cols, bar, label=label)
axes[1, 0].set_title('년도별 대출건수')
axes[1, 0].legend(loc='upper left')
axes[1, 0].set_xlim(1985, 2025)

# 원 그래프
axes[1, 1].pie(data, labels=labels, startangle=90,
               autopct='%.1f%%', explode=[0.1]+[0]*9)
axes[1, 1].set_title('출판사 도서비율')

fig.savefig('all_in_one.png')