Numpy
Numpy 설치 및 불러오기
1
pip install numpy
1
import numpy as np
파이썬의 리스트는 머신러닝에서 가장 많이 사용되는 구조 중 하나일 것이다. 그러나 이 리스트는 연산 속도가 느리다. 그래서 연산을 효과적으로 할 수 있도록 하기 위해 만든 것이 Numpy이다.
Numpy 연산 속도
numpy와 list의 연산 속도를 비교해보고자 한다.
1
2
3
4
5
6
7
import numpy as np
L = range(1000)
%timeit [i**2 for i in L]
N = np.arange(1000)
%timeit N**2
1
2
1000 loops, best of 5: 264 µs per loop
1000000 loops, best of 5: 1.41 µs per loop
이 때 %timeit
은 뒤의 연산에 대한 속도를 측정해준다. 시간을 보게 되면 리스트는 1000번의 루프동안 264µs 이지만, numpy의 경우 1000000번의 루프동안 1.41µs밖에 안걸린다. 동일한 루프 동안 걸리는 시간이 18만 배 정도가 차이난다.
지금은 단순히 1차원 배열이지만, 이것이 2차원, 3차원이 되면 더더욱 차이가 많이 나게 될 것이다.
리스트를 numpy로 직접 변환할 수도 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> li = [1,2,3]
> ar = np.array([1,2,3])
> arr = np.array(li)
> print(li,"\n",type(li))
[1, 2, 3]
<class 'list'>
> print(ar,"\n",type(ar))
[1 2 3]
<class 'numpy.ndarray'>
> print(arr,"\n",type(arr))
[1 2 3]
<class 'numpy.ndarray'>
Numpy 연산
vector와 scalar 연산
벡터의 각 원소에 대해 연산을 진행해보자.
\[y = \left( \begin{matrix} 1 \\ 3 \\ 5 \end{matrix} \right) \quad z = 5\]1
2
x = np.array([1,2,3])
c = 5
x각각의 원소에 c를 더하기/곱하기/나누기 등을 해주자.
1
2
3
4
5
>print("더하기 : {}\n빼기 : {}\n곱하기 : {}\n나누기 : {}".format(x+c,x-c,x*c,x/c))
더하기 : [6 7 8]
빼기 : [-4 -3 -2]
곱하기 : [ 5 10 15]
나누기 : [0.2 0.4 0.6]
vector와 vector 연산
벡터끼리는 같은 인덱스끼리 연산이 진행된다.
1
2
3
4
5
6
7
8
> y = np.array([1,3,5])
> z = np.array([2,9,20])
> print("더하기 : {}\n빼기 : {}\n곱하기 : {}\n나누기 : {}".format(y+z,y-z,y*z,y/z))
더하기 : [ 3 12 25]
빼기 : [ -1 -6 -15]
곱하기 : [ 2 27 100]
나누기 : [0.5 0.33333333 0.25 ]
numpy 인덱싱
list에서는 2차원 배열의 경우 k[a][b] 와 같이 인덱싱했다. numpy에서는 이와 유사하게 [a,b]
로 인덱싱한다. 차이점은 numpy의 경우 ,를 사용한다.
1
2
3
> l = np.array([[1,2,3],[4,5,6]])
> l[1,2]
6
slicing(:) 하는 방법도 list와 동일하다.
1
2
3
> l[0:2,1:3]
array([[2, 3],
[5, 6]])
array의 broadcasting
numpy에는 broadcasting이라는 특수한 기능이 있다. broadcasting이란 피연산자가 연산이 가능하도록 변환이 가능한 경우 변환하여 연산해주는 기능을 말한다. MxN 과 Mx1 을 연산하고자 한다면, 선형 대수 연산에서는 계산이 불가능하다.
그러나 이 때는 Mx1 배열을 복사해서 N개를 다 연산해준다.
예를 들어,
\[x = \left( \begin{matrix} 1 \ 2 \ 3 \\ 4 \ 5 \ 6 \\ 7 \ 8 \ 9 \end{matrix} \right) \quad y = \left( \begin{matrix} 0 \\ 0 \\ 1 \end{matrix} \right)\]이러한 배열이 있다. 원래 선형 대수 연산에서는 차원이 다르므로 계산이 불가능하지만, numpy에서는 y를 3x3 크기로 만들어 연산한다.
MxN, 1xN 에서도 동일하게 동작한다. 그렇다면 Mx1 과 1xN 의 연산은 어떻게 동작할까?
이 경우에는 Mx1 을 MxN으로, 1xN을 MxN으로 만들어서, MxN 두개의 행렬을 연산한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# M by N, M by 1
x1 = np.array([[1,2,3],[4,5,6],[7,8,9]])
y1 = np.array([0,1,0])
# 이 때, y는 행 벡터이다. M은 행을 나타내고 있으므로, y를 전치해줘야 한다.
# 전치하는 방법은 여러 가지가 있다.
y1 = y1[:, None]
# M by N, 1 by N
y2 = np.array([0,1,-1])
# M by 1, 1 by N
x3 = np.array([1,2,3])
x3 = x3[:,None]
y3 = np.array([2,0,-2])
1
2
3
4
5
6
7
8
9
10
11
12
> print(x1*y1, "\n\n", x1*y2, "\n\n", x3*y3)
[[0 0 0]
[4 5 6]
[0 0 0]]
[[ 0 2 -3]
[ 0 5 -6]
[ 0 8 -9]]
[[ 2 0 -2]
[ 4 0 -4]
[ 6 0 -6]]
Numpy 응용 - Linear Algebra with numpy
여러 형태의 행렬 생성 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 영행렬
> zero = np.zeros((3,3)) # zeros(dim)
> zero
[[0.,0.,0.],
[0.,0.,0.],
[0.,0.,0.]]
# 일행렬
> one = np.ones((3,3)) # ones(dim)
[[1.,1.,1.],
[1.,1.,1.],
[1.,1.,1.]]
# 대각행렬
> diag = np.diag((1,3,5)) # diag((numbers))
[[1,0,0],
[0,3,0],
[0,0,5]]
# 항등행렬
> eye = np.eye(2, dtype=int) # eye(dim, data type)
[[1,0],
[0,1]]
행렬 곱/나누기
행렬간의 곱연산은 np.dot()
또는 @
을 사용한다.
1
2
3
4
5
6
7
8
9
10
>mat1 = np.array([[1,4],[2,3]])
>mat2 = np.array([[7,9],[0,6]])
>mat1.dot(mat2)
array([[ 7, 33],
[14, 36]])
>mat1 @ mat2
array([[ 7, 33],
[14, 36]])
트레이스(trace)
트레이스란 main diagnoal의 합을 말한다. 즉, 배열 원소들 중 대각 행렬의 합이다.
1
2
3
>arr = np.array([[1,2,3],[4,5,6],[7,8,9]])
>arr.trace()
15 # 1+5+9
행렬식(determinant)
행렬을 대표하는 값들 중 하나를 말한다. 행렬을 선형 변환을 진행했을 때 얼마나 원벡터가 변하는가에 대한 척도이다. 행렬식을 구하는 방법은 2x2에서 ad - bc에 대한 식이다. 예를 들어 아래와 같은 행렬이 있다고 하자.
\[arr = \left( \begin{matrix} 1 \ 2 \\ 3 \ 4 \end{matrix} \right)\]이에 대해 행렬 식을 구하면 1x4 - 2x3 = -2
가 된다. 이 행렬식이 0이 나온다면 full rank
가 아니라는 것을 의미한다. 즉 선형 변환을 하면 차원의 손실이 일어난다.
행렬식을 구하는 함수는 np.linalag.det()
이다.
1
2
3
>arr2 = np.array([[2,3],[1,6]])
>np.linalg.det(arr2)
9.000000000000002
*선형 독립이란?
같은 수의 성분을 가진 n개의 벡터 a1,a2,…,an에 대해 벡터의 1차 결합(linear combination)인 $ C1a1 + C2a2 + … + Cnan = 0 $을 만족하는 상수 C1,C2,…,Cn이 모두 0이면 이 벡터 a1,a2,…,an은 선형 독립에 해당한다. 그러나 하나라도 0이 아닌 Ci가 존재하면 벡터 a1,a2,…,an은 선형종속 또는 1차 종속에 해당한다. 예를 들어 행렬 A,B가 있을 때, $ AC1+BC2 = 0 $에 대해 상수 C1=0,C2=0일때만 만족해야 선형 독립이고, C1=0,C2=0 이외에 C1=1,C2=-1일 때도 0이 된다면 이는 선형 종속이다.
https://rfriend.tistory.com/163
*rank란?
행렬의 일차 독립인 행 또는 열의 최대 개수를 rank라 한다. 임의의 행렬 A의 랭크는
\[A = \left( \begin{matrix} 1 \ 2 \ 1 \ 1 \\ 1 \ 1 \ -1 \ 1 \end{matrix} \right)\]rank(A)
로 표기한다. 행렬의 랭크는 그 행렬의 열벡터들에 의해 생성된 벡터 공간의 차원이다. m x n 행렬은 m개의 행과 n개의 열을 가진 행렬이다. 이 행렬의 랭크는 m개의 행 중 일차 독립인 행의 최대 개수 또는 n개의 열 중 일차 독립인 열의 최대 개수와 같다. 모든 행이 서로 일차독립이면 랭크는 m이 될 것이고, 모든 열이 서로 일차독립이면 랭크는 n이 된다. 그렇다면 m x n 행렬에서 모든 행끼리, 모든 열끼리 서로 일차독립이라면 어떻게 될까? 사실 그런 일은 존재하지 않는다. 왜냐하면 최대 일차독립인 행 또는 열의 개수는 한 행렬에서 나온다. 즉, 최대 행 개수와 최대 열 개수는 항상 동일하고, 이는 두 개의 차원이 같다는 것을 의미한다.이 행렬 A의 랭크를 구해보자. 1열과 4열은 동일하고, (1,1),(2,1)은 서로 독립이다. (1,1),(2,1),(1,-1)은 서로 종속이다. 왜냐하면 C1=0,C2=0,C3=0 이외에도, C1=3,C2=-2,C3=1일 때도 영벡터가 된다. 따라서
\[B = \left( \begin{matrix} 1 \ 4 \\ -3 \ -12 \end{matrix} \right)\]rank(A) = 2
이다. 행의 관점에서 보면 1,4열은 이미 같으므로 1행과 2행에서 각각 2,3열만 비교하면 된다. (2,1)과 (1,-1)은 일직선상에 있지 않으므로 독립이다. 따라서 1행과 2행은 독립이다. 또는 어떤 상수 a,b에 대해 $ a(1,2,1,1) + b(1,1,-1,1) = 0 $이 성립하려면 a+b=0, 2a+b=0,a-b=0이어야 하므로 이를 만족하는 유일해는 a = b = 0이므로 독립이다. 따라서rank(A) = 2
이다.행렬 B의 경우 행 관점에서 볼 때 (1,4)를 -3배 하면 2행 (-3,-12)가 얻어지므로 두 벡터는 종속이다. 열 관점에서 봐도 (1,-3)을 4배 하면 (4,-12)가 되므로 역시 종속이므로
rank(B) = 1
종속인지 독립인지를 판단할 때는 벡터를 그림으로 그리거나 정의를 생각하면 이해하기 쉽다. 두 벡터가 동일선상, 즉 같은 직선이 될 때 이 두 벡터가 종속 관계라 한다. 예를 들어 (6,7,8)과 (1,1,1)은 같은 방향에 놓여 있지 않다. 따라서 독립이다.
참고 사이트
- https://gosamy.tistory.com/16
- https://rfriend.tistory.com/163
*full rank란?
full rank는 해당 행렬의 행 또는 열 중 작은 값과 rank가 같은 경우를 말한다. 즉 3x2 행렬 A가 있을 때, 이 행렬의 rank(A) = 2라면 이는 full rank라 할 수 있다.
역행렬(inverse matrix)
행렬 A에 대해 AB = BA = I를 만족하는 행렬 B를 구할 수 있다. 즉 B = A^-1
을 만족한다.
이를 구하는 함수는 np.linalg.inv()
이다.
1
2
3
4
5
6
7
8
9
10
11
12
> arr = np.array([[1,4],[2,3]])
> arr_inv = np.linalg.inv(arr)
> arr_inv
array([[-0.6, 0.8],
[ 0.4, -0.2]])
> arr.dot(arr_inv)
array([[ 1.00000000e+00, 0.00000000e+00],
[-1.11022302e-16, 1.00000000e+00]])
# array([[1,0],
# [0,1]])
# == I
고유값과 고유벡터(eigenvalue and eienvector)
정방행렬(NxN) A에 대해 $Ax = \lambda x$를 만족하는 상수 ⋋와, 이에 대응하는 벡터이다. 즉, $ (A- \lambda I)x = 0 $에 만족해야 하는데, 이를 확인하기 위해 determinant를 사용하여 (A-⋋I)에 대해 0가 되는지 확인하면 고유값을 구할 수 있다.
그러나 numpy에 이를 구하는 함수가 존재한다. 함수는 np.linalg.eig()
이다.
1
2
3
4
5
6
> arr = np.array([[2, 0, -2],[1, 1, -2],[0, 0, 1]])
> np.linalg.eig(arr) # output : (⋋, x) 열을 기준으로 1에 대응되는 벡터는 [0,1,0]이다.
(array([1., 2., 1.]),
array([[0. , 0.70710678, 0.89442719],
[1. , 0.70710678, 0. ],
[0. , 0. , 0.4472136 ]]))
고유값이란? 행렬 A를 선형변환으로 봤을 때, 선형 변환 A에 의한 변환 결과가 자기 자신의 상수배가 되는 0이 아닌 벡터를 고유벡터라 하고, 이 상수배 값을 고유값이라 한다.
이 고유값과 고유벡터가 맞는지 확인해보자.
1
2
3
4
5
6
7
> eig_val, eig_vec = np.linalg.eig(arr)
> mat @ eig_vec[:,0] # Ax
array([0., 1., 0.])
> eig_val[0] * eig_vec[:,0] # (lambda)x
array([0., 1., 0.])
동일하게 추출되는 것을 통해 $ Ax = \lambda x $ 를 만족한다는 것을 확인할 수 있다.
Exercise
이 때까지 배운 내용들을 통해 2가지를 직접 생성해보고자 한다.
- L2 norm을 구하는 함수 : get_L2_norm()
- 어떤 행렬이 singular matrix인지 확인하는 함수 : is_singular()
1. get_L2_norm()
- 매개변수 : 1차원 벡터, np.array()
- 반환값 : 인자로 주어진 벡터의 L2 norm 값, number
2. is_singular()
- 매개변수 : 2차원 벡터, np.array()
- 반환값 : 인자로 주어진 벡터가 singular이면 true, 아니면 false
Matplotlib
파이썬의 데이터 시각화 라이브러리로 시각화할 때 자주 사용한다.
- matplotlib 설치
1
pip install matplotlib
쥬피터나 코랩에서 matplotlib을 사용하기 위해서는 %matplotlib inline
을 통해 활성화해야 한다.
1
%matplotlib inline
- Matplotlib 사용해보기
1
2
3
4
5
6
import matplotlib.pyplot as plt
%matplotlib inline
plt.plot([1,2,3,4,5]) # 실제 plotting 하는 함수
plt.show() # plt를 확인하는 명령
- figure
이 그래프의 크기를 직접 지정해줄 수 있다.
1
2
3
4
plt.figure(figsize=(6,6)) # 6x6 픽셀 크기의 도면을 선언
plt.plot([0,2,3,2,5])
plt.show()
2차함수 그리기
1
2
3
4
5
x = np.array([1,2,3,4,5]) # 정의역
y = np.array([1,4,9,16,25]) # f(x)
plt.plot(x,y)
plt.show()
위의 방법으로는 부드럽게 그려지지 않고 있다. 그래서 더 부드럽게 그리기 위해 전에 배웠던 np.arange
를 사용하거나 np.linspace
를 통해 점들의 분포를 정의역으로 선언해준다.
1
2
3
4
5
6
7
8
9
x1 = np.arange(-10,10,0.01) # arange(min,max,간격)
plt.plot(x1, x1**2)
plt.title("arange")
plt.show()
x2 = np.linspace(-10,10,2000)
plt.plot(x2, x2**2)
plt.title("linspace")
plt.show()
plot의 추가적인 기능
위에서 사용했던 title도 이에 해당되는 추가적인 기능이다. 그래프가 어떤 것을 의미하는지를 나타내기에 유용하다.
추가적으로 다양한 것들이 있다.
1.x,y 축에 설명 추가
1
2
plt.xlabel("x value")
plt.ylabel("f(x) value")
2.범위 설정
1
plt.axis([-5, 5, 0, 25]) # [x_min, x_max, y_min, y_max]
3.x,y축에 눈금 설정
1
2
plt.xticks([i for i in range(-5,6,1)]) # x축의 눈금 설정, -5,-4,-3...
plt.yticks([i for i in range(0,27,3)]) # y축의 눈금 설정
범위 설정과 눈금설정의 차이는 범위는 그래프의 범위를 설정해서 해당범위만 보겠다는 명령이고, 눈금 설정은 x,y축의 눈금 간격을 직접 세분화하거나 거시화하는 것이다.
4.title
1
plt.title("y = x^2 graph")
5.legend
1
2
plt.plot(x,x**2,label="trend") # label에 이름을 설정만 함
plt.legend() # label을 매핑해서 보여줌, plot이후에 적어줘야 함
Matplotlib의 plot 종류
1. 꺽은선 그래프(plot)
1
2
3
4
5
6
7
8
9
#random.seed(34) # 난수 일정하게 만들기 위한 설정
x = np.arange(0,20) # 0~20
y = np.random.randint(0,20,20) # 난수 20번 생성
plt.axis([0,20,0,20])
plt.yticks([0,5,10,15,20])
plt.plot(x,y)
plt.show()
이 그래프는 시계열 데이터에서 가장 많이 사용한다.
2. 산점도(scatter plot)
1
2
plt.scatter(x,y)
plt.show()
이 그래프는 x와 y가 별개의 변수일 때 많이 사용한다. 별개의 변수 사이에서 어떤 상관관계를 가질지에 대해 파악할 수 있다.
3. 박스 그래프(box plot)
단일 변수가 있을 때 이 변수의 전반적인 분포를 볼 때 많이 사용한다. 특히 수치형 데이터에 대해 많이 사용한다. 이 그래프는 단일 변수도 가능하지만, 2개 이상의 변수도 가능하다.
1
2
plt.boxplot((x,y))
plt.show()
이 그래프는 위 아래로 상한선(max)과 하한선(min)이 존재하고, 박스 그림에서 가장 밑부분, 주황색 선, 가장 윗부분은 각각 Q1(25%),Q2(50%),Q3(75%)에 해당하는 값들이다.
4. 막대 그래프(bar plot)
이 그래프는 범주형 데이터에 많이 사용한다. 범주형 데이터의 값과 그 값의 크기를 직사각형으로 나타낸 그림이다.
1
2
3
plt.bar(x,y)
plt.xticks(np.arange(0,20,1))
plt.show()
5. 히스토그램(histogram)
도수분포를 직사각형의 막대 형태로 나타낸다.0,1,2…이 아니라 0~2까지의 범주형 데이터로 구성 후 그림을 그려준다.
1
2
3
plt.xticks(np.arange(0,20,2))
plt.hist(y,bins=np.arange(0,20,2))
plt.show()
막대 그래프와의 차이점은 박스들이 이어져있다. 그 이유는 히스토그램의 경우 대체로 이어져있는 연속적 데이터를 사용하기 때문이다.
6. 원형 그래프(pie chart)
데이터에서 전체에 대한 부분의 비율을 부채꼴로 나타낸 그래프다. 다른 그래프들과는 다르게 비율을 나타낸다.
1
2
3
4
z = (100,300,200,400)
plt.pie(z, labels=["first","second","third","fourth"])
plt.show()
Seaborn
Matplotlib을 기반으로 더 다양한 시각화 방법을 제공하는 라이브러리다. 다양한 그래프들을 그려볼 수 있다.
- 커널 밀도 그래프
- 카운트그래프
- 캣그래프
- 스트립그래프
- 히트맵
- seaborn 설치 및 임포트
1
2
3
!pip install seaborn
import seaborn as sns
seaborn을 통한 시각화 그래프 종류
커널밀도그래프 (Kernel Density plot)
히스토그램과 같은 연속적인 분포를 곡선화해서 그린다.
1
2
3
4
5
6
7
8
9
x = np.arange(0,22,2)
y = np.random.randint(0,20,20)
plt.xticks(np.arange(0,20,2))
plt.hist(y,bins=x)
plt.show()
sns.kdeplot(y)
plt.show()
첫번째 그래프가 히스토그램이고, 두번째가 커널밀도 그래프이다.
이 때, kdeplot에는 shade
인자가 있다. 이는 그래프 아래의 영역을 색칠해주는 기능이다.
1
2
sns.kdeplot(y,shade=True)
plt.show()
카운트그래프(count plot)
범주형 column의 빈도수를 시각화하는 기능이다. 이는 groupby 를 한 후의 도수를 하는 것과 동일한 효과를 가진다.
1
2
vote_df = pd.DataFrame({"name":["Andy", "Bob", "Cat"], "vote":[True,True,False]})
vote_df
이를 matplotlib을 통해 생성하려면 groupby를 통해 그려야 했다,.
1
2
3
4
5
6
7
8
9
10
11
12
# in matplotlib
vote_count = vote_df.groupby('vote').count()
vote_count
plt.xlabel("vote")
plt.ylabel("count")
plt.bar(x=[False, True], height=vote_count['name'])
plt.show()
# in sns countplot
sns.countplot(x=vote_df['vote'])
plt.show()
알아서 색상을 지정해서 분류해주고, xlabel,ylabel을 자동으로 지정해준다.
캣 그래프(cat plot)
숫자형 변수와 하나 이상의 범주형 변수의 관계를 보여주는 함수다. 이 그래프는 복잡한 데이터에 적용하는 것이 좋다. cat이 concat, 여러 개를 연결해주는 그래프라는 의미이다.
그래프의 데이터로 사용할 dataset은 다음 사이트에서 다운 받길 바란다. country_wise_lastest.csv : https://www.kaggle.com/code/imdevskp/covid-19-analysis-visualization-comparisons/data?select=country_wise_latest.csv
1
2
3
4
5
6
covid = pd.read_csv("/content/covid_19_Country_Wise_Lastest.csv")
covid.head(5)
s = sns.catplot(x="WHO Region", y="Confirmed", data=covid)
s.fig.set_size_inches(10,6) # 이름들이 겹쳐 나오는 것을 보기 좋게 만들기 위해 figsize
plt.show()
원래는 2차원으로 데이터를 나타냈다면, 추가적으로 hue인자를 통해 각 점의 범주 또는 수치별 관계를 표현할 수도 있다.
또는 kind 인자를 통해 다른 형태의 그래프로도 만들 수 있다. default값은 strip
이지만, violin
으로 지정해주면 또 다른 형태의 그래프를 그릴 수 있다.
스트립 그래프(strip plot)
위의 캣 그래프의 기본 형태가 스트립 그래프이다.
1
2
sns.stripplot(x="WHO Region", y="Recovered", data=covid)
plt.show()
- swarmplot
스트립 그래프와 거의 동일한 그래프이다. 동일한 값을 가지는 데이터들이 뭉쳐져 있으면 얼마나 많은 점들이 뭉쳐져 있는지 확인하기 어렵기에 이것들을 양옆으로 분산해준다.
1
2
sns.swarmplot(x="WHO Region", y="Recovered", data=covid)
plt.show()
히트맵(heatmap)
데이터의 행렬을 색상으로 표현해주는 그래프이다.
1
covid.corr() # 행렬의 상관관계를 숫자로 표현
이처럼 숫자로 확인하면 어떤 데이터인지 확인하기 어렵다. 따라서 이를 히트맵으로 생성하면 행렬을 시각화할 수 있다.
1
2
sns.heatmap(covid.corr()) # data
plt.show()