안녕하세요. 이전 포스팅의 넘파이 알고 쓰자 - 배열 복붙하기(tiling)에서 동일한 배열을 여러번 반복해서 쓰는 tile 함수와 repeat 함수에 대해서 알아보았습니다. 그 결과도 조금씩 달랐죠. 오늘 포스팅에서는 넘파이의 원소를 직접 제거, 추가하는 함수들에 대해서 알아보도록 하겠습니다.
1. np.delete(arr, obj, axis=None)
이 함수는 생각보다 직관적인 함수입니다. 일단 "delete"는 삭제한다는 의미를 가지고 있습니다. 즉, 이 함수는 요소를 삭제하는 함수일 거 같습니다. 그리고 arr은 삭제할 배열, obj는 몇 번째를 삭제할 것인지, axis는 어느 축에서 삭제할 것인지입니다. 간단한 예제를 보도록 하겠습니다.
arr = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])
# array([[ 1, 2, 3, 4],
# [ 5, 6, 7, 8],
# [ 9, 10, 11, 12]])
np.delete(arr, 1, 0)
# array([[ 1, 2, 3, 4],
# [ 9, 10, 11, 12]])
np.delete(arr, 1, 1)
# array([[ 1, 3, 4],
# [ 5, 7, 8],
# [ 9, 11, 12]])
첫번째 delete 함수의 결과는 기존 배열에서 두번째 행을 제거하였습니다. 그리고 두번째 delete 함수의 결과는 기존 배열에서 두번째 열을 제거하였습니다. 즉, axis=0이면 행을 제거하고, axis=1이면 열을 제거하는 것입니다. 만약, axis를 지정해주지 않으면 아래와 같은 결과를 얻을 수 있습니다.
np.delete(arr, 1)
# array([ 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
결과를 보면 2차원 배열이 flatten되고 2번째 요소가 삭제된 것을 볼 수 있습니다. 이때, obj 인자에는 정수값이 들어갈 수도 있지만 배열이 들어갈 수도 있습니다. 이는 넘파이 객체를 인덱싱할 때 배열을 이용해서 인덱싱하는 것과 동일한 방법입니다.
np.delete(arr, [1,2], axis=0)
# array([[1, 2, 3, 4]])
np.delete(arr, [1,3], axis=1)
# array([[ 1, 3],
# [ 5, 7],
# [ 9, 11]])
np.delete(arr, [1,3], axis=None)
# array([ 1, 3, 5, 6, 7, 8, 9, 10, 11, 12])
첫번째 결과는 두번째, 세번째 행이 삭제되었습니다. 두번째 결과는 두번째, 네번째 열이 삭제되었습니다. 마지막으로 axis를 지정해주지 않으면 먼저 arr을 flatten하게 쭉 편 뒤 두번째, 네번째 인덱스에 해당하는 값이 삭제되었습니다.
2. np.insert(arr, obj, values, axis=None)
이번에는 insert라는 함수입니다. 이름에서도 느껴지시는 것처럼 값을 추가하는 함수입니다. 그래서 delete 함수와는 달리 어떤 값을 넣어줄지 결정해주는 values라는 인자가 추가된 것을 볼 수 있습니다. 이번에도 간단한 코드를 통해 이해해보도록 하겠습니다.
a = np.array([[1, 1], [2, 2], [3, 3]])
# array([[1, 1],
# [2, 2],
# [3, 3]])
np.insert(a, 1, 5)
# array([1, 5, 1, 2, 2, 3, 3])
np.insert(a, 1, 5, axis=0)
# array([[1, 1],
# [5, 5],
# [2, 2],
# [3, 3]])
np.insert(a, 1, 5, axis=1)
# array([[1, 5, 1],
# [2, 5, 2],
# [3, 5, 3]])
위의 결과부터 천천히 확인해보도록 하겠습니다. axis를 정해주지 않게 되면 delete 함수와 마찬가지로 a 배열을 flatten한 뒤 1번 인덱스(obj=1)에 값 5(values=5)를 삽입하여 얻은 결과를 볼 수 있습니다. 그 다음은 axis=0으로 지정하여 1번 인덱스(obj=1)의 행에 값 5(values=5)를 행 방향으로 삽입한 것을 볼 수 있습니다. 마지막 결과는 axis=1로 지정하여 1번 인덱스(obj=1)의 열에 값 5(values=5)를 열 방향으로 삽입한 것을 볼 수 있습니다!! 이와 같이 values를 단순히 스칼라 값으로 넣어주면 동일한 값으로 채우게 됩니다. 만약 서로 다른 값으로 채우고 싶다면 values를 삽입하려는 axis의 shape에 맞춰서 삽입할 배열을 넣어주면 됩니다.
np.insert(a, 1, [1, 2, 3], axis=1)
# array([[1, 1, 1],
# [2, 2, 2],
# [3, 3, 3]])
np.insert(a, 1, [1, 2, 3], axis=0)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-31-d9fc527df690> in <module>
----> 1 np.insert(a, 1, [1, 2, 3], axis=0)
<__array_function__ internals> in insert(*args, **kwargs)
~/anaconda3/lib/python3.7/site-packages/numpy/lib/function_base.py in insert(arr, obj, values, axis)
4593 new[tuple(slobj)] = arr[tuple(slobj)]
4594 slobj[axis] = slice(index, index+numnew)
-> 4595 new[tuple(slobj)] = values
4596 slobj[axis] = slice(index+numnew, None)
4597 slobj2 = [slice(None)] * ndim
ValueError: could not broadcast input array from shape (1,3) into shape (1,2)
만약, shape이 맞춰지지 않으면 아래의 오류를 얻을 수 있습니다. a 배열의 shape은 (2, 3)이기 때문에 axis=0 방향으로 삽입하기 위해서는 axis=0 방향의 shape은 2가 되어야합니다. 하지만 [1, 2, 3]은 shape이 3이기 때문에 브로드캐스팅 불가하다는 오류가 발생하는 것이죠. 이를 고치기 위해서는 아래와 같이 [1, 2, 3]이 아닌 [1, 2]로 넣어주면 됩니다.
np.insert(a, 1, [1, 2], axis=0)
# array([[1, 1],
# [1, 2],
# [2, 2],
# [3, 3]])
또한 delete와 마찬가지로 obj에 배열을 넣어 obj 배열의 인덱스에 맞게 값을 삽입해줍니다.
np.insert(a, [0, 3], [10, 20], axis=0)
# array([[10, 20],
# [ 1, 1],
# [ 2, 2],
# [ 3, 3],
# [10, 20]])
axis=0 방향으로 삽입하면 저희가 원하는 결과대로 0번째 행, 4번째 행에 각각 [10, 20]이 삽입되었습니다. 이번에는 axis=1 방향으로 삽입해보도록 하겠습니다.
np.insert(a, [0, 1, 2], [10, 20, 30], axis=1)
# array([[10, 1, 20, 1, 30],
# [10, 2, 20, 2, 30],
# [10, 3, 20, 3, 30]])
이 경우에는 저희가 원하는 결과가 나오지 않았습니다. 저희가 원하는 결과는 0번째 열, 1번째 열, 2번째 열에 각각 열 배열 [10, 20, 30]을 넣어주고 싶은 데 0번째 열에는 10만, 1번째 열에는 20만, 2번째 열에는 30만 들어갔습니다. 이러한 결과를 얻는 이유는 [10, 20, 30]이 열배열가 아닌 행배열로 인식되기 때문입니다. 따라서 이 문제를 해결하기 위해서는 [10, 20, 30]을 열배열로 변환해주면 됩니다. 그러므로 [[10], [20], [30]]으로 넘겨주면 저희가 원하는 결과를 얻을 수 있습니다.
np.insert(a, [0, 1, 2], [[10], [20], [30]], axis=1)
# array([[10, 1, 10, 1, 10],
# [20, 2, 20, 2, 20],
# [30, 3, 30, 3, 30]])
3. np.append(arr, values, axis=None)
이번에는 insert와 유사하게 배열을 삽입해주는 함수입니다. 혹시 파이썬에서 list의 삽입함수인 insert 함수와 append 함수를 기억하시나요? 그 두개의 차이와 동일합니다. insert 함수는 특정 인덱스에 원하는 배열을 삽입할 수 있습니다. append 함수는 항상 마지막 인덱스에 배열을 추가해줍니다. 그래서, insert 함수와는 다르게 obj 인자가 없는 것을 볼 수 있습니다. 이때, append 함수의 큰 특징은 axis를 명시하면 values의 차원의 수와 arr의 차원의 수가 동일해야합니다. 예를 들어서 arr의 차원의 수는 2인데, values의 차원의 수가 1이면 오류가 발생하게 됩니다. 따라서, 아래와 같이 차원을 맞추어줘야 오류없이 잘 실행됩니다.
a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# array([[1, 2, 3],
# [4, 5, 6],
# [7, 8, 9]])
np.append(a, [10, 20, 30])
# array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30])
np.append(a, [[10, 20, 30]], axis=0)
# array([[ 1, 2, 3],
# [ 4, 5, 6],
# [ 7, 8, 9],
# [10, 20, 30]])
np.append(a, [[10], [20], [30]], axis=1)
# array([[ 1, 2, 3, 10],
# [ 4, 5, 6, 20],
# [ 7, 8, 9, 30]])
append 함수도 마찬가지로 axis를 정해주지 않으면 a 배열을 flatten 한 뒤에 마지막 부분에 [10, 20, 30]을 추가하고 있습니다. 이때는 axis를 명시하지 않았기 때문에 a와 [10, 20, 30]의 차원 수가 다르더라도 오류가 발생하지 않습니다. 하지만 아래의 2개의 append 함수에서는 axis를 명시했기 때문에 a와 차원의 수를 맞춰주기 위해서 강제로 2차원으로 만들어주는 것을 볼 수 있습니다. 만약, 차원의 수를 맞추어주지 않으면 아래와 같은 오류가 발생하게 됩니다.
np.append(a, [10, 20, 30], axis=0)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-66-4d3f9599c377> in <module>
----> 1 np.append(a, [10, 20, 30], axis=0)
<__array_function__ internals> in append(*args, **kwargs)
~/anaconda3/lib/python3.7/site-packages/numpy/lib/function_base.py in append(arr, values, axis)
4691 values = ravel(values)
4692 axis = arr.ndim-1
-> 4693 return concatenate((arr, values), axis=axis)
4694
4695
<__array_function__ internals> in concatenate(*args, **kwargs)
ValueError: all the input arrays must have same number of dimensions, but the array at index 0 has 2 dimension(s) and the array at index 1 has 1 dimension(s)
4. np.trim_zeros(filt, trim='fb')
다음 함수는 조금 결이 다른 함수입니다. 가끔 배열 중에 앞뒤에 필요없는 0이 있는 것을 많이 보신분들도 있습니다. 이 trim_zeros 함수는 넘파이 배열의 앞부분, 뒷부분에 해당하는 0들을 제거해주는 함수입니다. trim은 3가지 옵션이 존재합니다. 'f'로 지정하면 front, 앞부분의 0만 제거하게 됩니다. 'b'로 지정하면 back, 뒷부분의 0만 제거하게 됩니다. 'fb'는 디폴트 값으로 지정하면 front, back, 앞, 뒤의 0을 모두 제거하게 됩니다. 넘파이 배열의 앞뒷부분이 아닌 중간에 다른 값들 사이에 섞여있는 0값은 제거되지 않습니다. 이 함수는 매우 쉽기 때문에 간단한 예제 코드만 보고 넘어가도록 하겠습니다.
a = np.array((0, 0, 0, 1, 2, 3, 0, 2, 1, 0, 0, 0))
np.trim_zeros(a, trim='f')
# array([1, 2, 3, 0, 2, 1, 0, 0, 0])
np.trim_zeros(a, trim='b')
# array([0, 0, 0, 1, 2, 3, 0, 2, 1])
np.trim_zeros(a, trim='fb')
# array([1, 2, 3, 0, 2, 1])
5.np.unique(ar, return_index=False, return_inverse=False, return_counts=False, axis=None)
개인적으로 넘파이 라이브러리에서 중요하고 유용한 함수를 선택한다면 저는 개인적으로 unique 함수를 선택할 거 같습니다. 이 함수는 여러 개의 값들이 존재하는 넘파이 배열에서 어떤 값으로만 구성되어있는 지, 최초로 등장하는 값의 인덱스(return_index), 입력 배열을 재구축할 수 있는 인덱스 배열(return_inverse), 각 값이 넘파이 패열에서 등장하는 횟수(return_counts)를 아래와 같이 반환 할 수 있습니다.
a = np.array([1, 2, 2, 3, 3, 3])
# array([1, 2, 2, 3, 3, 3])
np.unique(a)
# array([1, 2, 3])
np.unique(a, return_index=True)
# (array([1, 2, 3]), array([0, 1, 3]))
np.unique(a, return_index=True, return_inverse=True)
# (array([1, 2, 3]), array([0, 1, 3]), array([0, 1, 1, 2, 2, 2]))
np.unique(a, return_index=True, return_inverse=True, return_counts=True)
# (array([1, 2, 3]),
# array([0, 1, 3]),
# array([0, 1, 1, 2, 2, 2]),
# array([1, 2, 3]))
물론 원한다면 특정 axis에 해당하는 행이나 열을 통째로 고유한 값을 확인해 볼 수 있습니다.
a = np.array([[1, 0, 0], [1, 0, 0], [2, 3, 4]])
# array([[1, 0, 0],
# [1, 0, 0],
# [2, 3, 4]])
np.unique(a, axis=0)
# array([[1, 0, 0],
# [2, 3, 4]])
np.unique(a, axis=1)
# array([[0, 0, 1],
# [0, 0, 1],
# [3, 4, 2]])
'Programming > Python' 카테고리의 다른 글
넘파이 알고 쓰자 - Elementwise bit operations (0) | 2020.09.08 |
---|---|
넘파이 알고 쓰자 - 넘파이 원소 재배열하기 (0) | 2020.09.03 |
넘파이 알고 쓰자 - 배열 복붙하기(Tiling) (0) | 2020.08.30 |
넘파이 알고 쓰자 - split (0) | 2020.08.28 |
넘파이 알고 쓰자 - stack, hstack, vstack, dstack, column_stack (7) | 2020.08.26 |