IT/언어

[python/Numpy] Numpy배열 ndarray의 슬라이스를 이용한 부분 배열의 선택과 대입

개발자 두더지 2020. 5. 27. 15:59
728x90

 Python에서는 ':'(클론)을 이용한 슬라이스 [start:stop:step]를 이용하여, 리스트나 문자열, 튜플등의 시퀀스 객체의 일부를 선택하여 얻거나 다른 값을 대입하는 것이 가능하다.

 Numpy배열 ndarray에서도 슬라이로 부분 배열을 선택하여 추출하거나 다른 값을 대입하는 것이 가능하다. 이 포스팅에서 아래의 내용에 대해서 설명한다.

- 슬라이스의 기본
- 1차원의 Numpy배열 ndarray에서의 슬라이스(슬라이싱)
- 다차원의 Numpy배열 ndarray에서의 슬라이스(슬라이싱)
- 뷰(view, 참조)와 복사
- 펜시 인덱싱(; 정수 리스트 혹은 배열을 이용하여 여러 개를 동시에 선택하는 방식)과의 조합

 Numpy배열 ndarray의 요소나 부분 배열을 선택하는 방법에 대한 포스팅을 아래의 링크를 참고하길 바란다.

- 관련 포스팅 : Numpy배열 ndarray의 요소, 행, 열을 취득(추출), 대입

 혹은조건을 만족한 행이나 열을 추출하고 싶은 경우 아래의 링크를 참고하길 바란다.

- 관련 포스팅 : Numpy배열 ndarray로부터 조건을 만족한 요소, 행, 열을 추출, 삭제


1. 슬라이스의 기본

 앞서 말했듯,  Python에서는 ':'(클론)을 이용한 슬라이스 [start:stop:step]를 이용하여, 리스트나 문자열, 튜플등의 시퀀스 객체의 일부를 선택하여 얻거나 다른 값을 대입하는 것이 가능하다.

import numpy as np

l = list(range(10))
print(l)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

print(l[4:8])
# [4, 5, 6, 7]

print(l[-5:-2])
# [5, 6, 7]

print(l[::-1])
# [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

 슬라이스(슬라이싱)은 Python표준의 기능으로 자세한 내용은 아래의 포스팅을 참고하길 바란다.

- 관련 포스팅 : Python의 슬라이스(슬라이싱)을 이용한 리스트나 문자열 부분 선택, 대입


2. 1차원의 Numpy배열 ndarray에서의 슬라이스(슬라이싱)

 


1) 선택

1차원의 Numpy배열 numpy.ndarray를 슬라이스로 선택하는 경우는 Python의 기본적인 슬라이스와 동일하다.

a = np.arange(10)
print(a)
# [0 1 2 3 4 5 6 7 8 9]

print(a[4:8])
# [4 5 6 7]

print(a[-5:-2])
# [5 6 7]

print(a[::-1])
# [9 8 7 6 5 4 3 2 1 0]

2) 대입

 슬라이스를 사용한 대입은 Python의 리스트(list형)과 Numpy배열인 numpy.ndarray 각각 다르다.

 Python의 리스트에 슬라이스를 이용해 대입할 경우 위의 Python포스팅을 참고하길 바란다. 참고로 슬라이스로 선택한 요소수와 대입하는 요소수는 일치하지 않아도 된다.

 numpy.ndarray에서는 우변의 값이 브로드캐스트돼 대입된다. 즉, 우변이 스칼라 값인 경우 슬라이스로 선택된 요소 모두 그 스칼라 값으로 변환되어, 일차원 배열에 그대로 대입된다.

a[3:6] = 100
print(a)
# [  0   1   2 100 100 100   6   7   8   9]

a[3:6] = [100, 200, 300]
print(a)
# [  0   1   2 100 200 300   6   7   8   9]

 배열을 대입하는 경우 대입하는 배열의 요소 수와 슬라이스로 선택된 요소 수가 일치하지 않으면 ValueError가 발생하므로 주의해야한다.

# a[3:6] = [100, 200, 300, 400]
# ValueError: cannot copy sequence with size 4 to array axis with dimension 3

 'step'을 지정한 슬라이스에 의해 띄엄 띄엄 값이 대입되는 경우도 마찬가지이다.

a = np.arange(10)
print(a)
# [0 1 2 3 4 5 6 7 8 9]

print(a[2:8:2])
# [2 4 6]

a[2:8:2] = 100
print(a)
# [  0   1 100   3 100   5 100   7   8   9]

a[2:8:2] = [100, 200, 300]
print(a)
# [  0   1 100   3 200   5 300   7   8   9]

3. 다차원의 Numpy배열 ndarray에서의 슬라이스(슬라이싱)

 다차원의 Numpy 배열 ndarray에 대해서는 각 차원의 슬라이스를 ','(감마, 쉼표)로 구별하여 지정할 수 있다. 아래는 2차원 배열의 예이다.

a = np.arange(12).reshape((3, 4))
print(a)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

1) 선택

 각 차원의 슬라이스를 ','로 구별하여 지정한다.

print(a[1:, 1:3])
# [[ 5  6]
#  [ 9 10]]

(1) 행의 선택

전체를 나타내는 슬라이스 ':'를 사용하면 행을 선택할 수 있다. 이 경우, 뒤의 ', :'를 생략할 수 있다.

print(a[1:, :])
# [[ 4  5  6  7]
#  [ 8  9 10 11]]

print(a[1:])
# [[ 4  5  6  7]
#  [ 8  9 10 11]]

 1행을 선택하는 경우, 슬라이스가 아닌 스칼라 값으로 인덱스를 지정하면 1차원 배열이 되지만, 슬라이스로 1행만큼을 선택하면 원래 배열의 형태와 같은 2차원 배열이 된다.

print(a[1])
# [4 5 6 7]

print(a[1].shape)
# (4,)

print(a[1:2])
# [[4 5 6 7]]

print(a[1:2].shape)
# (1, 4)

 행렬연산등 형태가 중요한 경우에 주의하자.

(2) 열의 선택

 열의 선택도 동일하다. 이 경우, 처음의 ':'를 생략할 수 있다.

print(a[:, 1:3])
# [[ 1  2]
#  [ 5  6]
#  [ 9 10]]

 행과 동일하게, 1열을 선택하는 경우, 슬라이스가 아닌 스칼라값으로 인덱스를 지정하면 1차원 배열이 되지만, 슬라이스로 1열만큼을 선택하면 원래 배열의 형태와 같은 2차원 배열이 된다.

print(a[:, 1])
# [1 5 9]

print(a[:, 1].shape)
# (3,)

print(a[:, 1:2])
# [[1]
#  [5]
#  [9]]

print(a[:, 1:2].shape)
# (3, 1)

 복수의 ':'가 반복되는 경우는 '...'을 사용하는 것도 가능하다. 이와 관련된 내용은 아래의 포스팅을 참고하길 바란다.

- 관련 포스팅: Numpy배열 ndarray의 차원을 Ellipsis(...)으로 생략하여 지정


2) 대입

 다차원의 Numpy배열 ndarray에 대한 대입도 1차원의 경우와 같이 우변의 값이 브로드캐스트되어 대입된다. 배열을 대입하는 경우는 대입하는 배열의 요소수와 슬라이스로 선택된 영역에 대응하는 요소수가 일치하지 않으면 ValueError가 발생하므로 주의할 필요가 있다.

a = np.arange(12).reshape((3, 4))
print(a)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

print(a[1:, 1:3])
# [[ 5  6]
#  [ 9 10]]

a[1:, 1:3] = 100
print(a)
# [[  0   1   2   3]
#  [  4 100 100   7]
#  [  8 100 100  11]]

a[1:, 1:3] = [100, 200]
print(a)
# [[  0   1   2   3]
#  [  4 100 200   7]
#  [  8 100 200  11]]

a[1:, 1:3] = [[100, 200], [300, 400]]
print(a)
# [[  0   1   2   3]
#  [  4 100 200   7]
#  [  8 300 400  11]]

 step을 지정한 슬라이스로 띄엄 띄엄 값을 대입하는 것도 동일하다.

a = np.arange(12).reshape((3, 4))
print(a)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

print(a[1:, ::2])
# [[ 4  6]
#  [ 8 10]]

a[1:, ::2] = 100
print(a)
# [[  0   1   2   3]
#  [100   5 100   7]
#  [100   9 100  11]]

a[1:, ::2] = [100, 200]
print(a)
# [[  0   1   2   3]
#  [100   5 200   7]
#  [100   9 200  11]]

a[1:, ::2] = [[100, 200], [300, 400]]
print(a)
# [[  0   1   2   3]
#  [100   5 200   7]
#  [300   9 400  11]]

4. 뷰(view, 참조)와 복사

 슬라이스로 추출된 부분 배열은 원래 배열의 뷰(view, 참고)로써, 부분배열의 요소를 변경하면 원래 배열의 요소도 변경된다.

a = np.arange(12).reshape((3, 4))
print(a)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

a_slice = a[1:, 1:3]
print(a_slice)
# [[ 5  6]
#  [ 9 10]]

a_slice[0, 0] = 100
print(a_slice)
# [[100   6]
#  [  9  10]]

print(a)
# [[  0   1   2   3]
#  [  4 100   6   7]
#  [  8   9  10  11]]

 슬라이스로 추출한 부분배열의 복사를 작성하고 싶은 경우는 'copy()'메소드를 사용한다. copy의 요소를 변경하여도 원래 배열의 요소는 변경되지 않는다.

a = np.arange(12).reshape((3, 4))
print(a)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

a_slice_copy = a[1:, 1:3].copy()
print(a_slice_copy)
# [[ 5  6]
#  [ 9 10]]

a_slice_copy[0, 0] = 100
print(a_slice_copy)
# [[100   6]
#  [  9  10]]

print(a)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

 numpy.ndarray의 뷰와 복사에 대해 더욱 자세히 알고 싶다면 아래의 포스팅을 참고하길 바란다.

- 관련 포스팅 : Numpy배열 ndarray가 뷰인가 복사인가, 메모리를 공유하고 있는가를 판정


5.펜시 인덱싱과의 조합

 Numpy에는 인덱스의 리스트에 의해 numpy.ndarray로부터 부분 배열을 선택하는 펜시 인덱싱(; 정수 리스트 혹은 배열을 이용하여 여러 개를 동시에 선택하는 방식)이라는 기능이 있다.

- 관련 포스팅 : Numpy의 펜시익덴싱

 이것과 슬라이스를 합쳐서 부분 배열을 선택하여 취득하는 것이 가능하다.

a = np.arange(12).reshape((3, 4))
print(a)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

print(a[[0, 2], 1:3])
# [[ 1  2]
#  [ 9 10]]

 대입의 경우도 마찬가지이다.

a[[0, 2], 1:3] = 100
print(a)
# [[  0 100 100   3]
#  [  4   5   6   7]
#  [  8 100 100  11]]

a[[0, 2], 1:3] = [100, 200]
print(a)
# [[  0 100 200   3]
#  [  4   5   6   7]
#  [  8 100 200  11]]

a[[0, 2], 1:3] = [[100, 200], [300, 400]]
print(a)
# [[  0 100 200   3]
#  [  4   5   6   7]
#  [  8 300 400  11]]

펜시 인덱싱으로 추출된 부분 배열은 뷰가 아닌 복사본이므로 주의하자.

a_subset = a[[0, 2], 1:3]
print(a_subset)
# [[100 200]
#  [300 400]]

a_subset[0, 0] = -1
print(a_subset)
# [[ -1 200]
#  [300 400]]

print(a)
# [[  0 100 200   3]
#  [  4   5   6   7]
#  [  8 300 400  11]]

참고자료

https://note.nkmk.me/python-numpy-ndarray-slice/

728x90