더 이상 tistory 블로그를 운영하지 않습니다. glanceyes.github.io에서 새롭게 시작합니다.

새소식

Data Science/데이터 시각화

Matplotlib의 Pyplot 모듈로 Line Plot 그리기

  • -

 

2022년 2월 3일(목)부터 4일(금)까지 네이버 부스트캠프(boostcamp) AI Tech 강의를 들으면서 개인적으로 중요하다고 생각되거나 짚고 넘어가야 할 핵심 내용들만 간단하게 메모한 내용입니다. 틀리거나 설명이 부족한 내용이 있을 수 있으며, 이는 학습을 진행하면서 꾸준히 내용을 수정하거나 추가해 나갈 예정입니다.

 

 

Line Plot

Line Plot

Line Plot이란?

연속적으로 변화하는 값을 순서대로 점으로 나타내고, 이를 선으로 연결한 그래프이다.

꺾은선 그래프, 선 그래프, line chart, line graph 등의 이름으로 사용된다.

시간 또는 순서에 대한 변화에 적절해서 추세를 살피기 위한 시계열 분석에 특화되어 있다.

사용할 때 .line이 아니라 .plot()임을 유의한다.

 

Line Plot의 요소

5개 이하의 선을 사용하는 것을 추천하는데, 더 많은 선을 사용하면 중첩으로 인해 가독성이 하락할 수 있다.

이를 구별하는 요소로 다음과 같은 것들이 있다.

  • 색상(color)
  • 마커(marker, markersize )
    • 점을 원뿐만이 아니라 사각형, 삼각형으로 표현할 때 사용
  • 선의 종류(linestyle, linewidth)
    • 보통 가장 기본이 되는 데이터는 실선으로, 보조적이거나 또는 비교군인 데이터는 점선으로 표현한다.

 

Line Plot을 위한 전처리

시시각각 변동하는 데이터는 Noise로 인해 패턴 및 추세 파악이 어려울 수 있으므로 이러한 인지적인 방해를 줄이기 위해 MA(Moving Average) 등 smoothing을 사용한다.

 

 

Line Plot 사용 시 유의점

추세에 집중하기

추세를 보기 위한 목적이므로 Bar plot과는 다르게 꼭 축을 0에 초점을 둘 필요는 없다.

너무 구체적인 line plot보다는 생략된 line plot이 더 나을 수도 있으므로 Grid, Anootate 등을 제거하거나 디테일한 정보는 표로 제공하는 것이 좋다.

.set_ylim()을 통해 생략되지 않은 선에서 범위를 조정하여 변화율을 관찰하는 것이 권장된다.

 

 

간격

간격이 규칙적일 때는 기울기 정보를 오해할 수 있고, 간격이 모두 동일하지 않을 때는 없는 데이터에 대해 있다고 오해할 수 있다.

규칙적인 간격의 데이터가 아니라면 각 관측값에 점(marker)을 표시하여 오해를 줄일 수 있다.

 

 

보간

Line은 점을 이어 만드는 요소이므로 점과 점 사이의 데이터가 없기에 이를 잇는 방법으로 보간을 사용한다.

데이터의 error나 noise가 포함되어 있을 때 데이터의 이해를 돕는 방법이다.

  • Moving Average
  • Smooth Curve with Scipy
    • scipy.interpolate.make_interp_spline()
    • scipy.interpolate.interp1d()
    • scipy.interpolate.gaussian_filter1d()

Presentation에는 좋은 방법일지 모르지만 없는 데이터를 있다고 착각하게 만드는 오해를 발생할 여지가 있으므로 일반적인 분석에서는 지양하는 것이 바람직하다.

 

 

이중 축 사용

한 plot에 대한 $x$축 또는 $y$축을 두 개를 사용하는 것을 이중 축(Dual Axis)이라고 한다.

이를 사용하는 경우는 크게 두 가지가 있다.

  • 같은 시간 축에 대해 서로 다른 종류의 데이터를 표현할 때
    • .twinx()를 사용한다.
  • 한 데이터에 대해 다른 단위를 사용할 때
    • .secondary_xaxis(), .secondary_yaxis() 사용

첫 번째의 경우에 대해 이중 축을 사용하는 것은 지양할 필요가 있는데, 이는 가독성의 이유뿐만이 아니라 서로 다른 데이터에 관해 상관관계를 유도할 수 있는 오해를 불러일으킬 수 있어서다.

 

범례 대신 라인 끝 단에 레이블을 추가하면 식별에 도움이 될 수 있다.

Annotation 등으로 Min/Max 정보 또는 Critical Point를 추가하면 도움이 될 수 있다. Annotation을 추가한 그래프와 그렇지 않은 그래프를 같이 보여주는 것도 좋은 방법이다.

신뢰구간, 분산 등을 보다 연한 색을 사용하여 uncertainty를 표현할 수도 있다.

 

 

Line Plot 사용 예시

Line Plot 그리는 방식

plot은 $x_1, 𝑥_2, \cdots$과 $𝑦1,𝑦2,\cdots$ 데이터를 사용해서 그립니다.

line plot은 왼쪽에서 오른쪽으로 그리는 게 일반적이지만, 문법 자체는 이전 점 $(𝑥_1, 𝑦_1)$에서 $(𝑥_2,𝑦_2)$로 잇고, $(𝑥_2,𝑦_2)$에서 $(𝑥_3,𝑦_3)$로 잇는 순차적인 선으로 구성된 그래프입니다.

fig, axes = plt.subplots(1, 2, figsize=(12, 7))

x1 = [1, 2, 3, 4, 5]
x2 = [1, 3, 2, 4, 5]
y = [1, 3, 2, 1, 5]

axes[0].plot(x1, y)
axes[1].plot(x2, y)

plt.show()

1644930627921

 

np.linspace()를 사용하면 line의 수를 늘리고 line 사이의 간격을 줄여서 곡선처럼 보이게 할 수도 있다.

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

n = 1000
x = np.sin(np.linspace(0, 2*np.pi, n))
y = np.cos(np.linspace(0, 2*np.pi, n))

ax.plot(x, y)

plt.show()

1644930596822

 

Plot 변형하기

np.random.seed()는 랜덤한 수를 생성할 때 seed를 고정하면 같은 seed를 나중에 사용할 때 계속 같은 수를 얻을 수 있도록 할 수 있다.

다음과 같은 요소를 plot의 파라미터로 넘겨서 조정할 수 있다.

  • 색(color)
  • 마커(marker) : 마커의 종류
  • 선의 종류(linestyle)
    • 선의 모양을 직접 전달하는 방식 '--', '-.', ':'
    • solid, dashed, dashdot, dotted, None
fig, ax = plt.subplots(1, 1, figsize=(5, 5))

np.random.seed(97)
x = np.arange(7)
y = np.random.rand(7)

ax.plot(x, y,
        color='black',
        marker='*',
        linestyle='solid', 
       )

plt.show()

1644930646024

 

Line Plot을 위한 전처리

fig, ax = plt.subplots(1, 1, figsize=(15, 7), dpi=300)

ax.plot(google.index, google['close'])
ax.plot(apple.index, apple['close'])

plt.show()

1644930652429

 

pandasrolling을 통해 이동 평균(Moving Average)을 적용하면 다음과 같다.

# 데이터 20개씩에 대한 평균을 적용한다.
google_rolling = google.rolling(window=20).mean()
fig, axes = plt.subplots(2, 1, figsize=(12, 7), dpi=300, sharex=True)
# 파라미터에서 dpi는 해상도이다.

axes[0].plot(google.index,google['close'])
axes[1].plot(google_rolling.index,google_rolling['close'])

plt.show()

1644930660516

 

 

자세하게 표시하기 v.s. 추세에 집중하기

축의 단위를 설정할 때 MultipleLocator를 사용한다.

from matplotlib.ticker import MultipleLocator

fig = plt.figure(figsize=(12, 5))


np.random.seed(970725)

x = np.arange(20)
y = np.random.rand(20)


# Ax1 
# 정보를 좀 더 디테일하게 보고 기록하기 위한 경우
ax1 = fig.add_subplot(121)
# Marker로 점을 디테일하게 명시한다.
ax1.plot(x, y,
         marker='o',
         linewidth=2)

# 축의 단위를 설정할 때 MultipleLocator를 사용한다.
ax1.xaxis.set_major_locator(MultipleLocator(1))
ax1.yaxis.set_major_locator(MultipleLocator(0.05))    
ax1.grid(linewidth=0.3)    


# Ax2
# 추세에 집중하고자 하는 경우
ax2 = fig.add_subplot(122)
ax2.plot(x, y,
       linewidth=2,)

# 위쪽과 아래쪽의 선을 안 보이게 한다.
ax2.spines['top'].set_visible(False)
ax2.spines['right'].set_visible(False)


ax1.set_title(f"Line Plot (information)", loc='left', fontsize=12, va= 'bottom', fontweight='semibold')
ax2.set_title(f"Line Plot (clean)", loc='left', fontsize=12, va= 'bottom', fontweight='semibold')


plt.show()

1644930669266

 

간격

$x$값이 없어도 plot을 그릴 수는 있지만, $x$값과 $y$값을 모두 지정해주는 습관이 필요하다.

x = [2, 3, 5, 7, 9]
y = [2, 3, 4, 7, 10]

fig, ax = plt.subplots(1, 3, figsize=(13, 4))
ax[0].plot([str(i) for i in x], y)
ax[1].plot(x, y)
ax[2].plot(x, y, marker='o')

plt.show()

1644930677855

 

보간

이동 평균을 사용했을 때와 결과가 크게 다를 바가 없다.

from scipy.interpolate import make_interp_spline, interp1d
import matplotlib.dates as dates

fig, ax = plt.subplots(1, 2, figsize=(20, 7), dpi=300)

date_np = google.index
value_np = google['close']

date_num = dates.date2num(date_np)

# smooth
date_num_smooth = np.linspace(date_num.min(), date_num.max(), 50) 
spl = make_interp_spline(date_num, value_np, k=3)
value_np_smooth = spl(date_num_smooth)

# print
ax[0].plot(date_np, value_np)
ax[1].plot(dates.num2date(date_num_smooth), value_np_smooth)

plt.show()

1644930686143

 

이중 축 사용

twinx()로 같은 시간에 대한 서로 다른 두 개의 데이터를 표시한다.

fig, ax1 = plt.subplots(figsize=(12, 7), dpi=150)

# First Plot
color = 'royalblue'

ax1.plot(google.index, google['close'], color=color)
ax1.set_xlabel('date')
ax1.set_ylabel('close price', color=color)  
ax1.tick_params(axis='y', labelcolor=color)

# # Second Plot
ax2 = ax1.twinx()  
color = 'tomato'

ax2.plot(google.index, google['volume'], color=color)
ax2.set_ylabel('volume', color=color)  
ax2.tick_params(axis='y', labelcolor=color)

ax1.set_title('Google Close Price & Volume', loc='left', fontsize=15)
plt.show()

1644930701678

 

secondary-xaxis()로 같은 데이터에 관해 다른 단위로 표시한다.

def deg2rad(x):
    return x * np.pi / 180

def rad2deg(x):
    return x * 180 / np.pi

fig, ax = plt.subplots()
x = np.arange(0, 360)
y = np.sin(2 * x * np.pi / 180)
ax.plot(x, y)
ax.set_xlabel('angle [degrees]')
ax.set_ylabel('signal')
ax.set_title('Sine wave')
secax = ax.secondary_xaxis('top', functions=(deg2rad, rad2deg))
secax.set_xlabel('angle [rad]')
plt.show()

1644930711093

 

 

레이블 추가하기

text()로 각 group에 관해 레이블을 추가할 수 있다.

fig = plt.figure(figsize=(12, 5))

x = np.linspace(0, 2*np.pi, 1000)
y1 = np.sin(x)
y2 = np.cos(x)

ax = fig.add_subplot(111, aspect=1)
ax.plot(x, y1,
       color='#1ABDE9',
       linewidth=2,)

ax.plot(x, y2,
       color='#F36E8E',
       linewidth=2,)

ax.text(x[-1]+0.1, y1[-1], s='sin', fontweight='bold',
         va='center', ha='left', 
         bbox=dict(boxstyle='round,pad=0.3', fc='#1ABDE9', ec='black', alpha=0.3))

ax.text(x[-1]+0.1, y2[-1], s='cos', fontweight='bold',
         va='center', ha='left', 
         bbox=dict(boxstyle='round,pad=0.3', fc='#F36E8E', ec='black', alpha=0.3))


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

plt.show()

1644930723827

 

Max/Min 표시하기

fig = plt.figure(figsize=(7, 7))

np.random.seed(97)

x = np.arange(20)
y = np.random.rand(20)

ax = fig.add_subplot(111)
ax.plot(x, y,
       color='lightgray',
       linewidth=2,)

ax.set_xlim(-1, 21)

# max
# (-1, np.max(y))와 (x[np.argmax(y)], np.max(y))를 연결하는 선을 그린다.
ax.plot([-1, x[np.argmax(y)]], [np.max(y)]*2,
        linestyle='--', color='tomato'
       )

ax.scatter(x[np.argmax(y)], np.max(y), 
            c='tomato',s=50, zorder=20)

# min
ax.plot([-1, x[np.argmin(y)]], [np.min(y)]*2,
        linestyle='--', color='royalblue'
       )
ax.scatter(x[np.argmin(y)], np.min(y), 
            c='royalblue',s=50, zorder=20)

plt.show()

1644930732166

 

Contents

글 주소를 복사했습니다

부족한 글 끝까지 읽어주셔서 감사합니다.
보충할 내용이 있으면 언제든지 댓글 남겨주세요.