지난번 포스팅에서는 NumPy 에서 제공하는

 

 - (1) 단일 배열 대상의 범용 함수

       (Unary Universal Functions)

 - (2) 2개 배열 대상의 범용 함수

       (Binary Universal Functions)

 

에 대해서 간략히 소개를 하였습니다.

 

그리고 단일 배열 대상의 범용 함수 중에서 (1-1) '올림 혹은 내림 (Rounding)' 함수들(np.around, np.round_, np.rint, np.fix, np.ceil, np.floor, np.trunc)에 대해서 알아보았습니다.

 

이번 포스팅에서는 단일 배열 대상의 범용 함수(Unary ufuncs) 중에서 (1-2) 배열 원소 간 곱(products), 합(sums), 차분(differences) 범용 함수들에 대해서 알아보겠습니다.  (함수가 많아서 한꺼번에 포스팅하기에 버거우므로 여러번 나누어서 포스팅합니다)

 

 

[ Unary ufuncs : 배열 원소 간 곱 (products), 합 (sums), 차분 (differences) ]

 

 

 

 (1-2) 배열 원소 간 곱(products), 합(sums), 차분(differences), 기울기(gradient) 범용함수

 

1차원 배열 b와 2차원 배열 c를 가지고 예를 들어 설명하겠습니다.

 

 In [1]: import numpy as np


In [2]: b = np.array([1, 2, 3, 4]) # 1 dimension


In [3]: b

Out[3]: array([1, 2, 3, 4])


In [4]: c = np.array([[1, 2], [3, 4]]) # 2 dimension


In [5]: c

Out[5]:

array([[1, 2],
        [3, 4]])

 

 

 

 

 (1-2-1) 배열 원소 간 곱 범용 함수 (products universal funcstions) : np.prod()

 

2차원 배열의 경우 axis=0 이면 같은 열(column)위*아래 방향으로 배열 원소 간 곱하며, axis=1 이면 같은 행(row)의 왼쪽*오른쪽 원소 간 곱을 합니다.

 

 

# (1-2-1) np.prod() : Return the product of array elements over a given axis

 

# 1 dimensional array

In [3]: b

Out[3]: array([1, 2, 3, 4])

 

In [6]: np.prod(b)  # 1*2*3*4

Out[6]: 24

 

# 2 dimensional array

In [5]: c

Out[5]:

array([[1, 2],
        [3, 4]])

 

In [7]: np.prod(c, axis=0# [1*3, 2*4]  ↓

Out[7]: array([3, 8])


In [8]: np.prod(c, axis=1# [1*2, 3*4]  →

Out[8]: array([ 2, 12])

 

 

 

 

 (1-2-2) 배열 원소 간 합치기 범용 함수 (sum universal functions) : np.sum()

 

keepdims=True 옵션을 설정하면 1 차원 배열로 배열 원소 간 합을 반환합니다.

 

 

# (1-2-2) np.sum() : Sum of array elements over a given axis

 

# 1 dimensional array

In [3]: b

Out[3]: array([1, 2, 3, 4])

 

In [9]: np.sum(b) # [1+2+3+4]

Out[9]: 10

 

# the axes which are reduced are left in the result as dimensions with size one

In [10]: np.sum(b, keepdims=True)

Out[10]: array([10])  # 1 dimension array

 

In [11]: np.sum(b, keepdims=True).shape  # 1 dimension array

Out[11]: (1,)

 

 

 

2차원 배열의 경우 axis=0 을 설정하면 같은 열(column)의 위+아래 원소 값을 더하며, axis=1 을 설정하면 같은 행(row)의 왼쪽+오른쪽 원소 값을 더하여 1차원 배열을 반환합니다.

 

 

# 2 dimensional array

In [5]: c

Out[5]:

array([[1, 2],
        [3, 4]])

 

In [12]: np.sum(c, axis=0)  # [1+3, 2+4]  ↓

Out[12]: array([4, 6])


In [13]: np.sum(c, axis=1)  # [1+2, 3+4]  →

Out[13]: array([3, 7])

 

 

 

 

 (1-2-3) NaN 이 포함된 배열 원소 간 곱하기 범용 함수 : np.nanprod()

 

np.nanprod() 함수는 NaN (Not a Numbers) 을 '1'(one)로 간주하고 배열 원소 간 곱을 합니다.

 

 

# (1-2-3) np.nanprod() : Return the product of array elements

#                             over a given axis treating Not a Numbers (NaNs) as ones

 

In [14]: d = np.array([[1, 2], [3, np.nan]])


In [15]: d

Out[15]:

array([[  1.,   2.],
        [  3.,  nan]])


In [16]: np.nanprod(d, axis=0)  # [1*3, 2*1]  ↓

Out[16]: array([ 3., 2.])


In [17]: np.nanprod(d, axis=1)  # [1*2, 3*1]  →

Out[17]: array([ 2., 3.])

 

 

 

 

 (1-2-4) NaN이 포함된 배열 원소 간 더하기 범용 함수 : np.nansum()

 

np.nansum() 함수는 NaN (Not a Numbers)을 '0'(zero)으로 간주하고 배열 원소 간 더하기를 합니다.

 

 

In [15]: d

Out[15]:

array([[  1.,   2.],
        [  3.,  nan]])

 

# (1-2-4) np.nansum() : Return the sum of array elements

#                             over a given axis treating Not a Numbers (NaNs) as zero

 

In [18]: np.nansum(d, axis=0)  # [1+3, 2+0]  ↓

Out[18]: array([ 4., 2.])


In [19]: np.nansum(d, axis=1)  # [1+2, 3+0]  →

Out[19]: array([ 3., 3.])

 

 

 

 

 (1-2-5) 배열 원소 간 누적 곱하기 범용 함수 : np.cumprod()

 

axis=0 이면 같은 행(column)의 위에서 아래 방향으로 배열 원소들을 누적(cumulative)으로 곱해 나가며, axis=1 이면 같은 열(row)에 있는 배열 원소 간에 왼쪽에서 오른쪽 방향으로 누적으로 곱해 나갑니다.

 

 

In [20]: e = np.array([1, 2, 3, 4])


In [21]: e

Out[21]: array([1, 2, 3, 4])


In [22]: f = np.array([[1, 2, 3], [4, 5, 6]])


In [23]: f

Out[23]:

array([[1, 2, 3],
        [4, 5, 6]])

 

 

# (1-2-5) np.cumprod() : Return the cumulative product of elements along a given axis

In [24]: np.cumprod(e)  # [1, 1*2, 1*2*3, 1*2*3*4]

Out[24]: array([ 1, 2, 6, 24], dtype=int32)


In [25]: np.cumprod(f, axis=0)  # [[1, 2, 3], [1*4, 2*5, 3*6]]  ↓

Out[25]:

array([[ 1,  2,  3],
        [ 4, 10, 18]], dtype=int32)


In [26]: np.cumprod(f, axis=1)  # [[1, 1*2, 1*2*3], [4, 4*5, 4*5*6]]  →

Out[26]:

array([[  1,   2,   6],
        [  4,  20, 120]], dtype=int32)

 

 

 

 

 (1-2-6) 배열 원소 간 누적 합 구하기 범용 함수 : np.cumsum()

 

axis=0 이면 같은 행(column)의 위에서 아래 방향으로 배열 원소들을 누적(cumulative)으로 합해 나가며, axis=1 이면 같은 열(row)에 있는 배열 원소 간에 왼쪽에서 오른쪽 방향으로 누적으로 합해 나갑니다.

 

 

In [21]: e

Out[21]: array([1, 2, 3, 4])

 

# (1-2-6) np.cumsum(a, axis) : Return the cumulative sum of the elements along a given axis

 

In [27]: np.cumsum(e)  # [1, 1+2, 1+2+3, 1+2+3+4]

Out[27]: array([ 1,  3,  6, 10], dtype=int32)

 

 

In [23]: f

Out[23]:

array([[1, 2, 3],
        [4, 5, 6]])

 

In [28]: np.cumsum(f, axis=0)  # [[1, 2, 3], [1+4, 2+5, 3+6]]  ↓

Out[28]:

array([[1, 2, 3],
        [5, 7, 9]], dtype=int32)


In [29]: np.cumsum(f, axis=1)  # [[1, 1+2, 1+2+3], [4, 4+5, 4+5+6]]  →

Out[29]:

array([[ 1,  3,  6],
        [ 4,  9, 15]], dtype=int32)

 

 

 

 

  (1-2-7) 배열 원소 간 n차 차분 구하기 : np.diff()

 

 

# (1-2-7) diff(a, n, axis) : Calculate the n-th discrete difference along given axis

 

In [30]: g = np.array([1, 2, 4, 10, 13, 20])


In [31]: g

Out[31]: array([ 1, 2, 4, 10, 13, 20])

 

 

# 1차 차분 (1st order differencing)

In [32]: np.diff(g)  # [2-1, 4-2, 10-4, 13-10, 20-13]

Out[32]: array([1, 2, 6, 3, 7])

 

 

# 2차 차분 (2nd order differencing) => 1차 차분 결과 Out[32] 를 가지고 한번 더 차분

In [33]: np.diff(g, n=2)  # [2-1, 6-2, 3-6, 7-3]  <- using Out[32] array (1st order difference)

Out[33]: array([ 1, 4, -3, 4])

 

 

# 3차 차분 (3rd order differencing) => 2차 차분 결과 Out[33] 을 가지고 한번 더 차분

In [34]: np.diff(g, n=3)  # [4-1, -3-4, 4-(-3)]  <- using Out[33] array (2nd order diffenence)

Out[34]: array([ 3, -7, 7])

 

 

 

2차원 배열의 경우 axis=0 이면 같은 열(column)의 아래에서 위 방향으로 차분(difference)을 하며,

axis=1 이면 같은 행(row)의 오른쪽에서 왼쪽 방향으로 차분을 합니다.

 

 

#---- 2 dimentional arrays

 

In [35]: h = np.array([[1, 2, 4, 8], [10, 13, 20, 15]])


In [36]: h

Out[36]:

array([[ 1,  2,  4,  8],
        [10, 13, 20, 15]])

 

In [37]: np.diff(h, axis=0)  # [10-1, 13-2, 20-4, 15-8] ↑

Out[37]: array([[ 9, 11, 16, 7]])


In [38]: np.diff(h, axis=1)  # [[2-1, 4-2, 8-4], [13-10, 20-13, 15-20]] ←

Out[38]:

array([[ 1,  2,  4],
        [ 3,  7, -5]])

 

 

# n=2 이면 1차 차분 결과인 Out[38] 배열에 대해 한번 더 차분

 

In [39]: np.diff(h, n=2, axis=1)  [[2-1, 4-2], [7-3, -5-7]] ←

Out[39]:

array([[  1,   2],
        [  4, -12]])

 

 

 

 

 (1-2-8) 차분 결과를 1차원 배열(1 dimensional array)로 반환해주는 함수 : ediff1d()

 

2차원 배열에 대한 차분인 np.diff(h, axis=1) 의 경우 Out[38] 처럼 2차원 배열을 반환합니다. 반면에 ediff1d(h) 함수를 사용하면 Out[41] 처럼 차분 결과를 1차원 배열로 반환합니다.

 

 

# (1-2-8) ediff1d(ary[, to_end, to_begin])

#           : The differences between consecutive elements of an array

 

In [31]: g

Out[31]: array([ 1, 2, 4, 10, 13, 20])

 

In [40]: np.ediff1d(g)

Out[40]: array([1, 2, 6, 3, 7])

 

 

# 2 dimensional array

 

In [36]: h

Out[36]:

array([[ 1,  2,  4,  8],
        [10, 13, 20, 15]])

 

# The returned array is always 1D

In [41]: np.ediff1d(h)

Out[41]: array([ 1, 2, 4, 2, 3, 7, -5])  # 1D array, not 2D array

 

 

 

np.ediff1d() 함수의 시작부분과 끝 부분의 값을 to_begin, to_end 로 설정해줄 수도 있습니다.

 

 

In [42]: np.ediff1d(h, to_begin=np.array([-100, -99]), to_end=np.array([99, 100]))

Out[42]: array([-100, -99, 1, 2, 4, 2, 3, 7, -5, 99, 100])

 

 

 

 

  (1-2-9) 기울기(gradient) 구하기 범용 함수 : np.gradient()

 

gradient는 1차 편미분한 값들로 구성된 배열입니다. 아래 예제에 np.gradient() 함수가 어떻게 계산되는지를 수식을 적어놓았으니 참고하시기 바랍니다.  말로 설명하기가 쉽지가 않네요. ^^; 

 

 

In [31]: g

Out[31]: array([ 1, 2, 4, 10, 13, 20])

 

 

# [(2-1), {(2-1)+(4-2)}/2, {(4-2)+(10-4)}/2, {(10-4)+(13-10)}/2, {(13-10)+(20-13)}/2, (20-13)]

In [43]: np.gradient(g)

Out[43]: array([ 1. , 1.5, 4. , 4.5, 5. , 7. ])

 


# N scalars specifying the sample distances for each dimension

# x축 1단위가 '2'이므로 양쪽 옆으로 x축 변화에 따른 y값 변화를 보는 것이므로 2(단위)*2(방향)으로 나누어 줌

# [(2-1)/2, {(2-1)+(4-2)}/2*2, {(4-2)+(10-4)}/2*2, {(10-4)+(13-10)}/2*2, {(13-10)+(20-13)}/2*2, (20-13)/2] 

In [44]: np.gradient(g, 2)

Out[44]: array([ 0.5 , 0.75, 2. , 2.25, 2.5 , 3.5 ])

 


# Gradient is calculated using N-th order accurate differences at the boundaries

# 양 옆에만 2차 차분 : 1 - (1.5 -1) = 0.5,   7 + (7-5) = 9

In [45]: np.gradient(g, edge_order=2)

Out[45]: array([ 0.5, 1.5, 4. , 4.5, 5. , 9. ])

 

 

 

아래는 2차원 배열에 대한 gradient 구하는 예제입니다. np.gradient(h, axis=0)과 np.gradient(h, axis=1)을 짬뽕해 놓은 것이 np.gradient(h) 라고 보면 되겠습니다.  gradient 방법은 위의 1차원에서 소개한 방법과 같습니다.

 

 

# 2 dimensional array

 

In [36]: h

Out[36]:

array([[ 1,  2,  4,  8],
        [10, 13, 20, 15]])

 

 

# the first array stands for the gradient in rows and the second one in columns direction

In [46]: np.gradient(h)

Out[46]:

[array([[  9.,  11.,  16.,   7.],
         [  9.,  11.,  16.,   7.]]),

 array([[ 1. ,  1.5,  3. ,  4. ],
         [ 3. ,  5. ,  1. , -5. ]])]

 


# The axis keyword can be used to specify a subset of axes of which the gradient is calculated

 

In [47]: np.gradient(h, axis=0)  # ↑

Out[47]:

array([[  9.,  11.,  16.,   7.],
        [  9.,  11.,  16.,   7.]])


In [48]: np.gradient(h, axis=1)  # ←

Out[48]:

array([[ 1. ,  1.5,  3. ,  4. ],
        [ 3. ,  5. ,  1. , -5. ]])

 

 

 

다음번 포스팅에서는 지수함수, 로그함수, 삼각함수에 대해서 다루어보겠습니다.

 

많은 도움 되었기를 바랍니다.

 

 

Posted by R Friend R_Friend

댓글을 달아 주세요

  1. 김명찬 2017.11.23 12:30  댓글주소  수정/삭제  댓글쓰기

    안녕하세요.. 정리해주신 문서를 보고, 파이썬 numpy에 대해서 알아가고 있는 중입니다.
    위 설명중에서...
    # Gradient is calculated using N-th order accurate differences at the boundaries
    # 양 옆에만 2차 차분 : 1 - (1.5 -1) = 0.5, 7 + (7-5) = 9
    In [45]: np.gradient(g, edge_order=2)
    Out[45]: array([ 0.5, 1.5, 4. , 4.5, 5. , 9. ])

    이 부분에서, 양쪽 끝에서만 2차 차분을 하면,.. 0.5(1.5-1)와 2(=7-5) 가 되는 것이 맞지 않나요?
    왜 처음은 1에서 2차 차분한 값을 빼주고, 마지막 끝에서는 1차차분값에 2차차분값을 왜 더해주는지 궁금합니다.

    • R Friend R_Friend 2017.11.24 13:53 신고  댓글주소  수정/삭제

      안녕하세요 김명찬님, 반갑습니다.
      답변이 좀 늦어서 죄송합니다.

      먼저 블로그에 설명해드린 np.gradient(g, edge_order=2) 함수의 계산 로직은 맞습니다.

      gradient() 함수로 기울기를 계산할 때 보면 거리(distance)가 1 (default)라고 했을 때, 앞/가운데/뒤의 순서로 숫자가 있는 경우에는 {(가운데 수 - 앞의 수) + (뒤의 수 - 가운데 수)}/2 처럼 가운데 숫자를 기준으로 앞과 뒤의 숫자와의 차이의 평균으로 기울기를 계산했습니다.

      그런데 양쪽 변두리의 처음과 끝의 숫자를 보면
      - 제일 앞 자리에 있는 숫자는 뒷자리 숫자만 있을 뿐 (자기 자신이 제일 앞이므로)이며,
      - 제일 뒷 자리에 있는 숫자는 그 숫자를 기준으로 앞자리 숫자만 하나 있을 뿐 (자기 사진이 제일 뒤이므로) 입니다.

      그래서 제일 앞자리와 제일 뒷자리의 변수리 숫자의 경우 나머지 다른 위치(자기 자신을 기준으로 앞과 뒤에 다른 숫자가 있어서 숫자 3개로 기울기를 구할 수 있는 위치)의 숫자들과는 다른 로직으로 계산을 해야 합니다.

      가령
      k = np.array([1st, 2nd, 3rd, 4th, 5th]) 라는 array가 있다고 했을 때,

      (1) np.gradient(k) 함수의 경우 아래 처럼 각 위치의 숫자별 기울기를 계산합니다.

      np.array([ (2nd - 1st), {(2nd-1st) + (3rd-2nd)}/2, {(3rd-2nd) + (4th-3rd)}/2, {(4th-3rd) + (5th-4th)}/2, (5th-4th)])

      변두리에 해당하는 처음과 마지막을 유의해서 보시면 그냥 뒤의 숫자에서 앞의 숫자를 뺐는데요,
      - 제일 처음 위치의 숫자는 (2nd - 1st) 처럼 1차 차분 계산의 뒤에 빼주는 숫자로 들어갔으며,
      - 제일 마지막 위치의 숫자는 (5th - 4th) 처럼 1차 차분 계산의 앞쪽에 빼짐을 당하는 숫자의 위치에 들어갔습니다.


      (2) np.gradient(k, edge_order=2) 함수의 경우는 위의 (1) np.gradient(k) 함수에서 양 변두리에 위치한 값의 계산 방식에 조금 보정을 가한 경우라고 보면 될거 같습니다.

      np.array([ {(2nd - 1st) - (((2nd-1st) + (3rd-2nd))/2 - (2nd-1st))}, {(2nd-1st) + (3rd-2nd)}/2, {(3rd-2nd) + (4th-3rd)}/2, {(4th-3rd) + (5th-4th)}/2, {(5th-4th) + ((5th-4th) - {(4th-3rd) + (5th-4th)}/2)}])

      보정을 가할 때 인근 값들의 기울기 정보를 고려해주겠다는 것인데요, 제일 앞자리에 위치한 값과 제일 뒤에 위치한 값이 np.gradient() 구할 때 각각 빼주는 위치 혹은 빼짐을 당하는 위치가 서로 다릅니다. 그것 때문에 처음 위치 숫자에서는 기울기 1차 차분을 빼주었고, 제일 마지막 위치 숫자에서는 기울기 1차 차분을 더해준 것입니다.

      제일 앞에 위치한 숫자에 대해 np.gradient(g, edge_order=2) 를 구할 때 아래처럼 계산식을 표현해주면 이해가 더 쉬울 수도 있을 것 같습니다.
      --(3) 번 표기--
      {(2nd - 1st) + ((2nd-1st) - ((2nd-1st) + (3rd-2nd))/2)}

      블로그 본문에서는 {(2nd - 1st) - (((2nd-1st) + (3rd-2nd))/2 - (2nd-1st))} 식으로 계산을 한다고 표기를 해두었는데요, 두번째 괄호 안의 빼주는 위치를 바꾸어 주고 (마치 제일 뒤에 위치한 숫자가 빼주는 순서와 같이요) 대신 첫번째 괄호와 두번째 괄호를 빼기에서 더하기로 바꿔주면 (3)번 표기식과 같이 됩니다.

      쓰다보니 설명이 길고 헷갈리게 되어버렸는데요, 요점은 제일 처음 숫자와 제일 마지막 숫자의 경우 np.gradient() 구할 때 해당 위치를 기준으로 앞/뒤 숫자를 뺄 때 하나는 빼주는 뒤의 숫자가 되고, 하나는 빼짐을 당하는 앞의 숫자가 되기 때문에 np.gradient(g, edge_order=2)로 보정을 해줄 때 보정 방법이 달라진다는 것입니다.