안녕하세요. 지난 포스팅의 넘파이 알고 쓰자 - 넘파이 객체 선언에서는 간단한 환경설정과 넘파이 벡터, 행렬을 선언하는 여러가지 방법에 대해서 알아보았습니다. 오늘은 몇 가지 넘파이 연산을 알아보도록 하겠습니다. 지난 포스팅에서 봤던 함수를 적용할 예정이니 안보신 분들은 이전 포스팅을 보고 오시는 것을 추천드립니다. 코드는 제 깃허브를 참조해주시길 바랍니다.
1. 넘파이 기본 연산
파이썬에는 사칙연산을 기본적으로 제공하고 거기에 제곱, 몫, 나머지 계산까지 총 7개의 연산을 지원하고 있습니다. 넘파이도 유사한 연산을 오버라이딩(overriding)하여 제공하고 있습니다.
1). 사칙연산(+, -, *, /)
벡터와 행렬의 사칙연산을 정의해봅시다. 가장 간단한 방법은 대응되는 인덱스에 해당하는 원소끼리의 사칙연산을 하면 될 거 같습니다. 맞습니다!! 넘파이에서의 사칙연산이 구현된 방식입니다. 아래의 코드를 보시죠.
# 벡터
v1 = np.array([1, 2, 3, 4, 5])
v2 = np.full_like(v1, 5)
print("v1 + v2 = ", v1 + v2) # v1 + v2 = [ 6 7 8 9 10]
print("v1 - v2 = ", v1 - v2) # v1 - v2 = [-4 -3 -2 -1 0]
print("v1 * v2 = ", v1 * v2) # v1 * v2 = [ 5 10 15 20 25]
print("v1 / v2 = ", v1 / v2) # v1 / v2 = [0.2 0.4 0.6 0.8 1. ]
# 행렬
m1 = np.arange(1, 10).reshape(3, 3)
m2 = np.full_like(m1, 5)
print("m1 + m2 = ", m1 + m2)
# m1 + m2 = [[ 6 7 8]
# [ 9 10 11]
# [12 13 14]]
print("m1 - m2 = ", m1 - m2)
# m1 - m2 = [[-4 -3 -2]
# [-1 0 1]
# [ 2 3 4]]
print("m1 * m2 = ", m1 * m2)
# m1 * m2 = [[ 5 10 15]
# [20 25 30]
# [35 40 45]]
print("m1 / m2 = ", m1 / m2)
# m1 / m2 = [[0.2 0.4 0.6]
# [0.8 1. 1.2]
# [1.4 1.6 1.8]]
여러분들의 생각대로 잘 나온 것을 볼 수 있습니다. 그렇다면 벡터나 행렬의 shape이 맞지 않는다면 어떻게 될까요?
v1 = np.arange(1, 10)
v2 = np.arange(1, 9)
print("v1 + v2 = ", v1 + v2)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-14-5f983c08199d> in <module>
2 v2 = np.arange(1, 9)
3
----> 4 print("v1 + v2 = ", v1 + v2)
ValueError: operands could not be broadcast together with shapes (9,) (8,)
당연한 결과입니다. 넘파이에서 벡터, 행렬의 사칙 연산은 원소별로 수행하는 것입니다. 그런데 shape이 안맞으면 연산할 때 짝이 안맞기 때문에 오류가 날 수 밖에 없겠죠? 이와 같이 벡터나 행렬끼리 연산을 하게 되면 shape을 반드시 먼저 확인해보시길 바랍니다. 보통 넘파이에서는 위와 같이 shape이 안맞아 발생하는 오류가 대부분입니다.
2). 제곱, 몫, 나머지 계산(**, //, %)
사칙연산은 원소 별 연산을 수행한다는 것을 알았습니다. 그렇다면 제곱, 몫, 나머지는 넘파이에서 수행할 수 있을까요? 정답을 할 수 있습니다. 아래의 코드를 보시죠.
# 벡터
v1 = np.arange(1, 5)
v2 = np.arange(5, 1, -1)
print("v1 ** v2 = ", v1 ** v2) # v1 ** v2 = [ 1 16 27 16]
print("v1 // v2 = ", v1 // v2) # v1 // v2 = [0 0 1 2]
print("v1 % v2 = ", v1 % v2) # v1 % v2 = [1 2 0 0]
# 행렬
m1 = np.arange(1, 10).reshape(3, 3)
m2 = np.arange(10, 1, -1).reshape(3, 3)
print("m1 ** m2 = ", m1 ** m2)
# m1 ** m2 = [[ 1 512 6561]
# [16384 15625 7776]
# [ 2401 512 81]]
print("m1 // m2 = ", m1 // m2)
# m1 // m2 = [[0 0 0]
# [0 0 1]
# [1 2 4]]
print("m1 % m2 = ", m1 % m2)
# m1 % m2 = [[1 2 3]
# [4 5 1]
# [3 2 1]]
보시면 바로 이전에 봤던 사칙 연산과 동일하게 원소별 연산을 수행하는 것을 볼 수 있습니다. 원소 별로 제곱하고, 몫을 계산하고, 나머지를 계산하게 됩니다.
2. 넘파이 범용 함수(universal function)
다음으로는 넘파이에서 사용되는 범용 함수입니다. 범용 함수란 넘파이에서 제공하는 sin, cos, exp와 같은 수학 함수를 의미합니다. 줄여서 ufunc이라고 부르기도 합니다. 넘파이에서 이러한 함수는 넘파이 객체의 각 요소를 입력으로 하는 함수의 출력값을 계산하게 됩니다. 그리고 넘파이에서 지원하는 범용 함수는 엄청나게 많습니다. 바로 위에서 언급한 sin, cos, exp 뿐만 아니라 올림 및 내림을 지원하는 함수, 절댓값을 지원하는 함수 등등이 있습니다. 오늘은 간단한 몇 가지 함수들만 설명드리도록 하겠습니다. 나머지 범용 함수는 생각날 때마다 이 포스팅에 추가하도록 하겠습니다.
1). 올림/내림/반올림 함수
넘파이에서 제공하는 올림 함수는 np.ceil, 내림 함수는 np.floor 입니다. 그리고 반올림 함수는 np.around, np.round입니다. np.around는 항상 0.5를 기준으로 정수로 값을 반환합니다. 그에 반해 np.round는 반올림할 실수와 자리수를 넘겨줍니다. 그러면 반환값이 정수가 아닌 실수가 될수도 있습니다.
r1 = np.ceil(np.random.rand(2, 5))
r2 = np.floor(np.random.rand(2, 5))
r3_int = np.round(np.random.rand(2, 5))
r3_1 = np.round(np.random.rand(2, 5), 1)
r3_2 = np.round(np.random.rand(2, 5), 2)
r4 = np.around(np.random.rand(2, 5))
print("r1 = \n", r1)
# [[1. 1. 1. 1. 1.]
# [1. 1. 1. 1. 1.]]
print("r2 = \n", r2)
# [[0. 0. 0. 0. 0.]
# [0. 0. 0. 0. 0.]]
print("r3_int = \n", r3_int)
# [[1. 0. 1. 0. 0.]
# [0. 1. 0. 1. 1.]]
print("r3_1 = \n", r3_1)
# [[0.3 1. 0.2 0.6 0.4]
# [0.5 0. 0.6 0.5 0.1]]
print("r3_2 = \n", r3_2)
# [[0.44 0.08 0.14 0.87 0.07]
# [0.95 0.85 0.87 0.23 0.19]]
print("r4 = \n", r4)
# [[1. 0. 0. 1. 0.]
# [0. 1. 0. 1. 0.]]
여기서 확인할 것은 np.round()입니다. 보시면 함수 인자에 넘파이 객체만 넘기는 경우 정수로 값을 반환하게 됩니다. 하지만, 값을 1, 2로 넣게 되면 각각 소수점 첫번째 자리, 소수점 2번째 자리까지 계산하는 것을 볼 수 있습니다. 따라서, 얻고자하는 소수점 자리의 값을 뒷 인자로 넘겨줘야 원하는 소수점에 해당하는 값을 얻을 수 있습니다.
2). 삼각함수/역삼각함수
넘파이에서 제공하는 삼각함수는 np.sin, np.cos, np.tan이 있습니다. 그리고 역삼각함수는 np.arcsin, np.arccos, np.arctan이 있습니다.
# 벡터
sin_v = np.sin(np.arange(1, 6))
cos_v = np.cos(np.arange(1, 6))
tan_v = np.tan(np.arange(1, 6))
arcsin_v = np.arcsin(np.linspace(0, 1, 5))
arccos_v = np.arccos(np.linspace(0, 1, 5))
arctan_v = np.arctan(np.linspace(0, 1, 5))
print("sin_v = \n", sin_v) # [ 0.84147098 0.90929743 0.14112001 -0.7568025 -0.95892427]
print("cos_v = \n", cos_v) # [ 0.54030231 -0.41614684 -0.9899925 -0.65364362 0.28366219]
print("tan_v = \n", tan_v) # [ 1.55740772 -2.18503986 -0.14254654 1.15782128 -3.38051501]
print("arcsin_v = \n", arcsin_v) # [0. 0.25268026 0.52359878 0.84806208 1.57079633]
print("arccos_v = \n", arccos_v) # [1.57079633 1.31811607 1.04719755 0.72273425 0. ]
print("arctan_v = \n", arctan_v) # [0. 0.24497866 0.46364761 0.64350111 0.78539816]
3). 지수 함수(exponential function)
넘파이에서 제공하는 지수 연산 함수는 3개입니다. np.exp, np.exp2, np.expm1입니다. np.exp는 각 요소에 자연상수를 밑으로 하는 지수함수 값을 반환합니다. np.exp2는 밑이 2인 경우입니다. np.expm1은 각 요소에 자연상수를 밑으로 하는 지수함수를 적용한 뒤 1을 뺀 것입니다. 수식적으로 표현하면 $f(x)=e^{x} - 1$입니다.
e1 = np.exp(np.arange(1, 6)) # np.exp([a, b, c]) = [exp(a), exp(b), exp(c)]
e2 = np.exp2(np.arange(1, 6)) # np.exp2([a, b, c]) = [2^(a), 2^(b), 2^(c)]
e3 = np.expm1(np.arange(1, 6)) # np.expm1([a, b, c]) = [e^(a)-1, e^(b)-1, e^(c)-1]
print(e1) # [ 2.71828183 7.3890561 20.08553692 54.59815003 148.4131591 ]
print(e2) # [ 2. 4. 8. 16. 32.]
print(e3) # [ 1.71828183 6.3890561 19.08553692 53.59815003 147.4131591 ]
4). 로그 함수(logarithm function)
넘파이에서 제공하는 로그함수는 4개입니다. np.log, np.log10, np.log2, np.log1p입니다. np.log는 밑이 자연상수 $e$인 자연로그 값을 반환합니다. np.log10, np.log2는 각각 밑이 10, 2인 로그값을 반환합니다. np.log1p는 밑이 자연상수 $e$면서 $1 + x$의 입력값을 반환합니다. 수식으로 적으면 $f(x) = \ln{(1 + x)}$와 동일합니다.
l1 = np.log(np.arange(1, 6)) # np.log([a, b, c]) = [ln(a), ln(b), ln(c)]
l2 = np.log10(np.arange(1, 6)) # np.log10([a, b, c]) = [log_10(a), log_10(b), log_10(c)]
l3 = np.log2(np.arange(1, 6)) # np.log2([a, b, c]) = [log_2(a), log_2(b), log_2(c)]
l4 = np.log1p(np.arange(1, 6)) # np.expm1([a, b, c]) = [ln(a+1), ln(b+1), ln(c+1)]
print(l1) # [0. 0.69314718 1.09861229 1.38629436 1.60943791]
print(l2) # [0. 0.30103 0.47712125 0.60205999 0.69897 ]
print(l3) # [0. 1. 1.5849625 2. 2.32192809]
print(l4) # [0.69314718 1.09861229 1.38629436 1.60943791 1.79175947]
5). 제곱근 함수(square function)와 제곱 함수(square function)
넘파이에서 제공하는 제곱근 함수와 제곱 함수는 각각 1개씩 존재합니다. np.sqrt, np.square입니다.
np.random.seed(42)
s1 = np.sqrt(np.arange(1, 6)) # np.log([a, b, c]) = [ln(a), ln(b), ln(c)]
s2 = np.square(np.arange(1, 6)) # np.log10([a, b, c]) = [log_10(a), log_10(b), log_10(c)]
print(s1) # [1. 1.41421356 1.73205081 2. 2.23606798]
print(s2) # [ 1 4 9 16 25]
'Programming > Python' 카테고리의 다른 글
넘파이 알고 쓰자 - Random Module (0) | 2020.08.22 |
---|---|
넘파이 알고 쓰자 - 브로드캐스팅 (0) | 2020.08.19 |
넘파이 알고 쓰자 - 넘파이 인덱싱과 슬라이싱 (0) | 2020.08.18 |
넘파이 알고 쓰자 - 넘파이 객체 선언 (0) | 2020.08.14 |
넘파이 알고 쓰자 - 소개 (0) | 2020.08.12 |