본문 바로가기

부스트캠프 AI Tech 공부 기록/Data Visualization

[Data Viz] Text (feat.Matplotlib)

Data Visualization

3-1. Text

1) Text in Data Visualization

시각화에서의 text

- Visualization이 줄 수 없는 많은 설명을 추가해 줄 수 있다

- 잘못된 전달에서 생기는 오해를 방지할 수 있다

- 하지만 text를 과도하게 사용하면 오히려 이해에 방해가 될 수 있다

- Matplotlib에서 제공하는 API 를 바탕으로 Text를 이해하자

 

Matplotlib에서의 text API

pyplot API Object-Oriented API description
suptitle suptitle title of figure
title set_title title of subplot ax
xlabel set_xlabel x-axis label
ylabel set_ylabel y-axis label
figtext text figure text
text text Axes text
annotate annotate Axes annotation with arrow
import numpy as np
import pandas as pd
import matplotlib as mpl 
import matplotlib.pyplot as plt
fig, ax = plt.subplots()

fig.suptitle('Figure Title') # figure의 title

ax.plot([1, 3, 2], label='legend') # 범례 지정
ax.legend()

ax.set_title('Ax Title') # ax title
ax.set_xlabel('X Label') # x label 정보
ax.set_ylabel('Y Label') # y label 정보

ax.text(x=1,y=2, s='Text') # text 좌표 기반으로 지정하기
fig.text(0.5, 0.6, s='Figure Text') # text 비율기반으로 위치 지정하기 (fig의 전체 가로의 0.5, 세로의 0.6 위치에 text 지정)

plt.show()

2) Text Properties

Font Components

- Text의 가독성을 높이기 위해 가장 쉽게 바꿀 수 있는 요소들은 다음과 같다.

  • family : 글씨체
  • size or fontsize : 글씨 크기
  • style or fontstyle : 일반, 기울임 등
  • weight or fontweight : 글씨 굵기

- 글씨체에 따른 가독성과 관련된 내용은 다음을 참고하자. 

 

Material Design

Build beautiful, usable products faster. Material Design is an adaptable system—backed by open-source code—that helps teams build high quality digital experiences.

material.io

 

Is there any research with respect to how font-weight affects readability?

Is there any research with respect to how font-weight affects readability? Obviously, there is some relation there, but I'm struggling to make a decision based on any facts. I created a jsFiddle...

ux.stackexchange.com

 

- 또한 matplotlib에서 제공하는 Font Demo는 아래 링트에서 볼 수 있다.

 

Fonts demo (object-oriented style) — Matplotlib 3.5.1 documentation

Note Click here to download the full example code

matplotlib.org

 

- Font 변형 적용해보기

fig, ax = plt.subplots()
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)

ax.text(x=0.5, y=0.5, s='Text\nis Important',
         fontsize=20, # fontsize = 'large'도 가능
         fontweight='bold',
         fontfamily='serif',
       )


plt.show()

 

ETC Details

- 폰트 자체를 변형하는 것은 아니지만 커스텀할 수 있는 요소들은 다음과 같다.

  • color : 폰트 색상
  • linespacing : 줄과 줄 간의 간격
  • backgroundcolor : 폰트 배경색
  • alpha : 투명도
  • zorder : 피피티에서 맨 앞으로 가져오기 / 맨 뒤로 가져오기 기능
  • visible : 폰트 보이게 하기 / 안 보이게 하기

- 적용해보기

fig, ax = plt.subplots()
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)

ax.text(x=0.5, y=0.5, s='Text\nis Important',
        fontsize=20,
        fontweight='bold',
        fontfamily='serif',
        color='royalblue',
        linespacing=2, # 줄과 줄 간의 간격
        backgroundcolor='lightgray',
        #zorder : 맨앞으로 가져오기 맨 뒤로 가져오기
        alpha=0.5
       )


plt.show()

 

Alignment : 정렬

- 정렬과 관련하여 이런 요소들을 조정할 수 있다

  • ha : horizontal alignment
  • va : vertical alignment
  • rotation
  • multialignment

- 적용해보기

fig, ax = plt.subplots()
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)

ax.text(x=0.5, y=0.5, s='Text\nis Important',
        fontsize=20,
        fontweight='bold',
        fontfamily='serif',
        color='royalblue',
        linespacing=2,
        va='center', # 세로 축 정렬 top : text의 위가 (0.5, 0.5), bottom : text의 아래 좌표가 (0.5, 0.5), center : text의 중앙 좌표가 (0.5, 0.5)
        ha='center', # 가로 축 정렬 left, right, center
        rotation='horizontal' # vertical? # roatation = 45도 가능
       )


plt.show()

 

Advanced - bbox 그리기

- Drawing Fancy boxes 참고

- 다음과 같이 다양한 박스 스타일이 존재하므로, 적재적소에 사용하면 된다

- 적용해보기

fig, ax = plt.subplots()
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)

ax.text(x=0.5, y=0.5, s='Text\nis Important',
        fontsize=20,
        fontweight='bold',
        fontfamily='serif',
        color='black',
        linespacing=2,
        va='center', # top/bottom/center
        ha='center', # left/right/center
        rotation='horizontal', # vertical로도 바꿔보기
        bbox=dict(boxstyle='round', facecolor='wheat', ec = 'blue', alpha=0.4) # ec == edge color
       )


plt.show()

 

3) Text API 별 추가 사용법

Text를 조정 없이 기본적으로 설정하면 다음과 같이 나온다

- 데이터 가져오기

student = pd.read_csv('./StudentsPerformance.csv')
student.head()

- 플롯하기 (Scatter Plot)

fig = plt.figure(figsize=(9, 9))
ax = fig.add_subplot(111, aspect=1)

for g, c in zip(['male', 'female'], ['royalblue', 'tomato']):
    student_sub = student[student['gender']==g]
    ax.scatter(x=student_sub ['math score'], y=student_sub ['reading score'],
               c=c,
               alpha=0.5, 
               label=g)
    
ax.set_xlim(-3, 102)
ax.set_ylim(-3, 102)

ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)

ax.set_xlabel('Math Score')
ax.set_ylabel('Reading Score')

ax.set_title('Score Relation') 
ax.legend()   

plt.show()

 

Title & Legend

- 제목 위치 조정하기

- 범례에 제목, 그림자 달기, 위치 조정하기

fig = plt.figure(figsize=(9, 9))
ax = fig.add_subplot(111, aspect=1)

for g, c in zip(['male', 'female'], ['royalblue', 'tomato']):
    student_sub = student[student['gender']==g]
    ax.scatter(x=student_sub ['math score'], y=student_sub ['reading score'],
               c=c,
               alpha=0.5, 
               label=g)
    
ax.set_xlim(-3, 102)
ax.set_ylim(-3, 102)

ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)

ax.set_xlabel('Math Score', 
              fontweight='semibold')
ax.set_ylabel('Reading Score', 
              fontweight='semibold')

ax.set_title('Score Relation', 
             loc='left', va='bottom',         # 제목 왼쪽 정렬
             fontweight='bold', fontsize=15
            )

ax.legend(
    title='Gender',         		# 범례 제목
    shadow=True,
    labelspacing=1.2,           	# 범례들 간의 공간
    loc='lower right',          	# 범례 위치 lower/upper/center + right/left/center
    bbox_to_anchor=[1.2, 0.5],          # 범례 위치를 원하는 위치로 지정할 수 있음 -> 권장
    #ncol = 2          			# 범례 가로로 조정하기, 항목이 엄청 많지 않으면 잘 안 씀
)

plt.show()

  * bbox_to_anchor을 더 이해하고 싶다면 → link

 

What does a 4-element tuple argument for 'bbox_to_anchor' mean in matplotlib?

In the "Legend location" section of the "Legend guide" in the matplotlib website, there's a small script where line 9 is plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3, ncol=2, mode="expand",

stackoverflow.com

 

Ticks & Text

- tick을 없애거나 조정

- text alignment

  * math score 에 대한 bar plot에 적용하기

student['math score'].head()

# bar plot을 만들기 위해 math score 데이터를 범주형으로 변환
def score_band(x):
    tmp = (x+9)//10
    if tmp <= 1: 
        return '0 - 10'
    return f'{tmp*10-9} - {tmp*10}'

student['math-range'] = student['math score'].apply(score_band)
student['math-range'].value_counts().sort_index()

- 기본 bar plot 그리기

math_grade = student['math-range'].value_counts().sort_index()

fig, ax = plt.subplots(1, 1, figsize=(11, 7))
ax.bar(math_grade.index, math_grade,
       width=0.65, 
       color='royalblue',
       linewidth=1,
       edgecolor='black'
      )

ax.margins(0.07)
# ax.grid()		# 꼭 필요할 때만 사용

plt.show()

- ticks frame을 없애고 대신 text 추가

- 제목 추가

math_grade = student['math-range'].value_counts().sort_index()

fig, ax = plt.subplots(1, 1, figsize=(11, 7))
ax.bar(math_grade.index, math_grade,
       width=0.65, 
       color='royalblue',
       linewidth=1,
       edgecolor='black'
      )

ax.margins(0.01, 0.1)
ax.set(frame_on=False) 	# frame 끄기 - 네 변 자체를 없애기
ax.set_yticks([])    	# y축 눈금 제거
ax.set_xticks(np.arange(len(math_grade)))
ax.set_xticklabels(math_grade.index, fontsize=11)

ax.set_title('Math Score Distribution', fontsize=14, fontweight='semibold') 	# 제목 추가

for idx, val in math_grade.iteritems(): 	# bar 위에 텍스트 추가
    ax.text(x=idx, y=val+3, s=val,
             va='bottom', ha='center',
             fontsize=11, fontweight='semibold'
           )

plt.show()

 

Annotate : 화살표 사용하기

- 일반 plot ( 특정 학생의 math score 표시 )

fig = plt.figure(figsize=(9, 9))
ax = fig.add_subplot(111, aspect=1)

i = 13

ax.scatter(x=student['math score'], y=student['reading score'],
           c='lightgray',
           alpha=0.9, zorder=5)
    
ax.scatter(x=student['math score'][i], y=student['reading score'][i],
           c='tomato',
           alpha=1, zorder=10)    
    
ax.set_xlim(-3, 102)
ax.set_ylim(-3, 102)

ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)

ax.set_xlabel('Math Score')
ax.set_ylabel('Reading Score')

ax.set_title('Score Relation') 

plt.show()

- annotate 추가

- annotate은 text와 가리키려는 대상의 위치를 다르게 지정해줄 수 있음 ( 화살표로 연결 )

fig = plt.figure(figsize=(9, 9))
ax = fig.add_subplot(111, aspect=1)

i = 13

ax.scatter(x=student['math score'], y=student['reading score'],
           c='lightgray',
           alpha=0.9, zorder=5)
    
ax.scatter(x=student['math score'][i], y=student['reading score'][i],
           c='tomato',
           alpha=1, zorder=10)    
    
ax.set_xlim(-3, 102)
ax.set_ylim(-3, 102)

ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)

ax.set_xlabel('Math Score')
ax.set_ylabel('Reading Score')

ax.set_title('Score Relation') 

# x축과 평행한 선
ax.plot([-3, student['math score'][i]], [student['reading score'][i]]*2,
        color='gray', linestyle='--',
        zorder=8)

# y축과 평행한 선
ax.plot([student['math score'][i]]*2, [-3, student['reading score'][i]],
       color='gray', linestyle='--',
       zorder=8)

# bbox = dict(boxstyle="round", fc='wheat', pad=0.2)
# arrowprops = dict(
#     arrowstyle="->")

ax.annotate(s=f'This is #{i} Student', # text = f'This is #{i} Student',
            xy=(student['math score'][i], student['reading score'][i]), # 원하는 포인트 잡아주기
            xytext=[80, 40], # 텍스트의 위치
            bbox=bbox,
            arrowprops=arrowprops,
            zorder=9,
           )

plt.show()