꺼내먹는지식 준

Matplotlib Line Plot 본문

CS/데이터시각화

Matplotlib Line Plot

알 수 없는 사용자 2022. 2. 3. 23:25

Line Plot(꺾은 선 그래프, 선 그래프, line chart, line graph) 연속적으로 변화하는 값을 순서대로 점으로 나타내고, 이를 선으로 연결한 그래프이다. 

 

시간/ 순서에 대한 변화에 적합하여 추세 살피기에 용이 (시계열 특화)

 

신기하게 .line이 아니라 .plot() (가장 기본적이라 그런 듯 하다.)

 

 

코드 구현 

fig = plt.figure(figsize=(5, 5))
ax = fig.add_subplot(111, aspect=1)
#aspect 가로 세로 비율 값 

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()

aspect는 가로 세로 비율 값을 뜻한다. 

aspect = 'equal' == 1 

fig, ax = plt.subplots(1, 1, figsize=(5, 5))

np.random.seed(97)
x = np.arange(7)
y = np.random.rand(7)
#np.random.rand(m,n) : 0 ~ 1의 균일분포 표준정규분포 난수를 matrix array(m,n) 생성

ax.plot(x, y)

plt.show()

7개의 난수 array y값 생성 

Line Plot의 요소 

5개 이하의 선을 사용하는 것을 추천 (가독성)

 

구별 요소 

1) 색상 color 

2) 마커 marker merker size(점 형태)

3) 선의 종류 linestyle linewidth

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()

다음과 같이 color, marker, linestyle 지정이 가능하다. 

line plot 전처리 

시시각각 변하는 데이터는 Noise로 인해 패턴 및 추세 파악이 어렵다. 

Noise 인지적 방해를 줄이기 위해 smoothing을 사용 

 

여러 smoothing 기법이 존재(ex. moving smoothing.. 등)

 

stock = pd.read_csv('./prices.csv')
stock

시간이 있는 데이터와 없는 데이터가 혼재되어 있다. 

stock['date'] = pd.to_datetime(stock['date'], format='%Y-%m-%d', errors='raise')
#date time 자료형으로 변경 
stock.set_index("date", inplace = True)
stock

 

to_datetime

arg: int, float, str, datetime, list, tuple, 1-d array, Series, DataFrame/dict-like 

The object to convert to a datetime. If a DataFrame is provided, the method expects minimally the following columns: 

"year", "month", "day".

 

format: str, default None

The strftime to parse time, e.g. "%d/%m/%Y". 

Note that "%f" will parse all the way up to nanoseconds. See strftime documentation for more information on choices.

 

errors: {‘ignore’, ‘raise’, ‘coerce’}, default ‘raise’

  • If 'raise', then invalid parsing will raise an exception

예시

pd.to_datetime(["2016-01-05 00:00:00", "2016-12-30"])
#DatetimeIndex(['2016-01-05', '2016-12-30'], dtype='datetime64[ns]', freq=None)

pd.to_datetime(["2016-01-05 00:00:00", "2016-12-30"], format='%Y-%m-%d', errors='raise')
#DatetimeIndex(['2016-01-05', '2016-12-30'], dtype='datetime64[ns]', freq=None)

format, errors 는 default라서 사실상 선언해주지 않아도 된다. 

stock.set_index("date", inplace = True)

Set the DataFrame index (row labels) using one or more existing columns or arrays (of the correct length). The index can replace the existing index or expand on it.

설명에는 적혀져 있지 않지만, column 데이터가 index 가 되고, 사용된 column은 column에서 사라진다. 

inplace = True일 때, 원본 데이터에 영향을 준다. 

False 를 할 거면, return 값을 받아서 사용하면 된다. 

apple = stock[stock['symbol']=='AAPL']
google = stock[stock['symbol']=='GOOGL']
apple.head()
google.head()

stock['symbol'] symbol 을 iterate 하며 'AAPL' 과 동일 한 경우 반환 - pandas 게시글 참고 

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()

index 는 하루 기준으로 바뀌나, 큰 추세에서 년도 기준으로 plot이 형성되었다. 

google_rolling = google.rolling(window=20).mean()
fig, axes = plt.subplots(2, 1, figsize=(12, 7), dpi=300, sharex=True)

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

plt.show()

rolling 은 전체 데이터 집합의 여러 하위 집합에 대한 일련의 평균을 만들어 데이터 요소를 분석하는 계산, window 20이니 20개씩 가져온다. 즉, google_rolling 은 index 20 까지는 비워져 있고 21 부터 그전 20개의 데이터의 mean 으로 생성. 

dpi 는 해상도, sharex 는 x 축의 범위를 공유한다. 

 

line plot 추세

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

너무 구체적인 line plot보다 생략된 line plot이 더 나을 수도 있기에 grid, annotate 등 모두 제거, 디테일한 정보는 표로 제공한다. 

 

생략되지 않는 범위 내에서 표현 (y_lim)

 

목적이 추세면 오른쪽, 중요한 정보면 왼쪽(주식)

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)
ax1.plot(x, y,
         marker='o',
         linewidth=2)

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()

출력 그래프는 위 그래프와 같다. 

왼쪽 그래프 

multiplelocator 각 축에대한 디테일한 정보를 어떤 단위로 나타낼 것인지 결정하는 함수 

그리고 marker 와 grid 를 사용해서 최대한 많은 정보를 담았다. 

오른쪽 그래프 

최대한 추세만 본다. 

 

간격

규칙적인 간격이 중요하다. 기울기 정보(변화율)에 오해가 생길 수 있다. 

혹여나 규칙적이지 않으면 각 관측 값에 점으로 표시하자. 

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()

 

그래프 위 사진 참고 

ax[0]과 같이 x값의 간격이 제공되지 않아도 y 값 만으로 그래프를 그릴 수는 있지만(str 형태), 간격을 제공해주자. 

보간

line은 점과 점 사이에 데이터가 없기에 이를 잇는 방법(보간)

 

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

Moving Average 

Smooth Curve with Scipy (scipy.interpolate.make_interp_spline(), scipy.interpolate.interp1d(), scipy.ndimage.gaussian_filter1d())

 

Presentation에는 좋은 방법이나, 없는 데이터가 있다고 생각하게 할 수 있으며, 작은 차이를 없앨 수 있다. 일반적 분석에서는 지양해야 한다. 

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()

https://www.delftstack.com/howto/matplotlib/matplotlib-plot-smooth-curve/

 

Matplotlib Plot Smooth Curve

This tutorial explains how we can plot a smooth curve from given coordinates using the modules from the scipy and matplotlib package.

www.delftstack.com

사실상 결과를 보면 이동평균과 크게 다르지 않다. 어떠한 보간법을 쓸 것인지 결정여하에 따라 결과값이 달라질 수 있다. 

이중 축 사용 

한 plot에 대해 2개의 축을 이중축이라고 한다. 

같은 시간 축에 대해 서로 다른 종류의 데이터를 표현하기 위해서는 축이 2개 필요 

.twinx() 를 사용 

 

한 데이터에 대해 다른 단위 (radian degree)

.secondary_xaxis(), .secondary_yaxis() 사용 

2개의 plot을 그리는 것 >>> 이중 축 사용: 이중 축은 최대한 지양하자. 

웬만하면 위와같이 나눠서 그리는 것이 낫다. 

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()

tick_params() 함수를 이용해서 그래프의 틱 (tick)의 스타일을 설정할 수 있다.

twinx() 를 사용하면, 반대쪽에 다음과 같이(빨간색) 축을 생성할 수 있다. 

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()

다음과 같이 x axis label이 'top' 위에도 추가 되었다. functions 에는 label이 통과할 함수와, 그에 대한 역함수를 넣어줘야 한다. 

사실 왜 역함수가 들어가야 하는지는 잘 모르겠다. 

ETC 

라인 끝 단에 레이블을 추가하면 식별에 도움 (범례 대신)

 

위보다는 아래가 낫다. 

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, label='sin')

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

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

ax.legend(loc='upper center')

plt.show()

그래프는 위와 같다. 

legend(loc = 'upper center') 로 인해 범계가 중앙에 생성 

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))

텍스트의 위치를 다음과 같이 형성한다. 

x 좌표와 y 좌표를 지정한 후, 수직 수평 형식을 결정한다. (center, left)

 

va 와 ha:

va 는 verticalalignment 의 약자로 y축에서의 위치 [ 'center' | 'top' | 'bottom' | 'baseline' ] 

ha 는 horizontalalignment 의 약자로 x축에서의 위치 [ 'center' | 'right' | 'left' ] 

 

최대한 정보를 줄이자고 했지만, 필요한 정보 (Min/Max) 등의 정보는 추가하면 도움이 될 수 도 있다.

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
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()

점선을 plot , 점은 scatter 로 표현 

[-1, x[np.argmax(y)]]
[np.max(y)]*2

#[-1, 6]
#[0.9763350748952845, 0.9763350748952845]

즉, x 값 -1 ~ 6 까지 , y 값은 동일하게 plot을 그린다. 

 

보다 연한 색을 사용하여 uncertainty 표현 가능 (신뢰 구간, 분산 등)

 

'CS > 데이터시각화' 카테고리의 다른 글

Matplotlib 기본기에서 벗어나는 몇가지 팁  (0) 2022.02.07
Matplotlib Scatter Plot  (0) 2022.02.04
Matplotlib Bar Plot  (0) 2022.02.03
Matplotlib 기본  (0) 2022.02.03
데이터 시각화란?  (0) 2022.02.03
Comments