색인과 슬라이싱 기초
데이터의 부분집합이나 개별 요소를 선택하기 위한 수많은 방법이 존재한다.
1차원 배열은 단순한데, 표면적으로는 파이썬의 리스트와 유사하게 동작한다.
arr = np.arange(10)
arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
arr[5]
5
arr[5:8]
array([5, 6, 7])
#배열에 스칼라값을 대입하면 12가 선택 영역 전체로 전파(브로드 캐스팅)되는 것을 확인이 가능하다.
arr[5:8] = 12
arr
array([ 0, 1, 2, 3, 4, 12, 12, 12, 8, 9])
arr_s = arr[5:8]
arr_s[1] = 12345
arr
array([ 0, 1, 2, 3, 4, 12, 12345, 12, 8, 9])
리스트와 중요한 차이점은 배열 조각은 원본 배열의 뷰라는 점이다.
즉, 데이터는 복사되지 않고 뷰에 대한 변경은 그대로 원본 배열에 반영된다.
arr_s[:] = 64 # [:]로 슬라이싱 하면 배열의 모든 값에 할당한다.
arr_s
array([7, 8, 9])
따라서 개별 요소는 재귀적으로 접근해야 한다. 하지만 그렇게 하기는 귀찮기 때문에
콤마로 구분된 색인 리스트를 넘기면 된다. 아래 두 문제는 같은거다.
arr2d[0][2]
3
arr2d[0,2]
3
다차원 배열에서 마지막 색인을 생력하면 반환되는 객체는 상위 차원의 데이터를 포함하고 있는
한 차원 낮은 ndarray가 된다. 2 x 2 x 3크기의 배열 arr3d가 있다면
arr3d = np.array([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]])
arr3d
array([[[ 1, 2, 3],
[ 4, 5, 6]],
[[ 7, 8, 9],
[10, 11, 12]]])
arr3d[0]
array([[1, 2, 3],
[4, 5, 6]])
old_v = arr3d[0].copy()
arr3d[0] = 42
arr3d
array([[[42, 42, 42],
[42, 42, 42]],
[[ 7, 8, 9],
[10, 11, 12]]])
arr3d[0] = old_v
arr3d
array([[[ 1, 2, 3],
[ 4, 5, 6]],
[[ 7, 8, 9],
[10, 11, 12]]])
arr3d[1,0]
array([7, 8, 9])
x = arr3d[1]
x
array([[ 7, 8, 9],
[10, 11, 12]])
x[0]
array([7, 8, 9])
여기서 살펴본 선택된 배열의 부분집합은 모두 배열의 뷰를 반환한다는 점을 기억하자.
슬라이스로 선택하기
파이썬의 리스트같은 1차원 객체처럼 ndarray는 익숙한 문법으로 슬라이싱이 가능하다.
arr
array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9])
arr[1:6]
array([ 1, 2, 3, 4, 64])
arr2d
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
arr2d[:2]
array([[1, 2, 3],
[4, 5, 6]])
arr2d[:2, 1:]
array([[2, 3],
[5, 6]])
arr2d[1, :2]
array([4, 5])
arr2d[:, :1]
array([[1],
[4],
[7]])
arr2d[:2, 1:] = 0
arr2d
array([[1, 0, 0],
[4, 0, 0],
[7, 8, 9]])
불리언값으로 선택하기
중복된 이름이 포함된 배열이 있다고 하자.
그리고 numpy.random 모듈에 있는 randn 함수를 사용해서 임의의 표준 정규 분포 데이터를 생성하자.
names = np.array(['Bob','Joe','Will','Bob','Will','Joe','Joe'])
data = np.random.randn(7,4)
names
array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'], dtype='<U4')
data
array([[-0.6910874 , 0.68352034, 1.51892787, -0.28907833],
[-0.61991431, 0.65569043, -0.90567925, -0.66510301],
[-0.49578331, -0.61198523, 0.98094116, 0.99431798],
[ 1.00135045, -0.28610541, 0.617814 , -0.32404145],
[ 0.52096438, 0.18326031, -1.34134102, -1.04946538],
[-0.27833014, -0.20804052, 0.22035008, -1.455818 ],
[-0.06508606, 0.46970765, -0.17450824, -2.41112073]])
각각의 이름은 data배열의 각 로우에 대응한다고 가정하자.
만약에 전체 로우에서 'Bob'과 같은 이름을 선택하려면
산술 연산과 마찬가지로 배열에 대한 비교 연산(==같은)도
벡터화되므로 names를 'Bob' 문자열과 비교하면 불리언 배열을 반환한다.
names == 'Bob'
array([[-0.6910874 , 0.68352034, 1.51892787, -0.28907833],
[ 1.00135045, -0.28610541, 0.617814 , -0.32404145]])
불리언 배열은 반드시 색인하려는 축의 길이와 동일한 길이를 가져야 한다.
불리언 배열 색인도 슬라이스나 요소를 선택하는데 짜 맞출 수 있다
불리언값으로 배열을 선택할 때는 불리언 배열의 크기가 다르더라도 실패하지 않는다.
그래서 더 주의하자.
data[names == 'Bob', 2:]
array([[ 1.51892787, -0.28907833],
[ 0.617814 , -0.32404145]])
data[names == 'Bob', 3]
array([-0.28907833, -0.32404145])
'Bob'이 아닌 요소들은 선택하려면 != 연산자를 사용하거나 ~를 사용해서 조건절을 부인하면 된다.
names != 'Bob'
array([False, True, True, False, True, True, True])
data[~(names == 'Bob')]
array([[-0.61991431, 0.65569043, -0.90567925, -0.66510301],
[-0.49578331, -0.61198523, 0.98094116, 0.99431798],
[ 0.52096438, 0.18326031, -1.34134102, -1.04946538],
[-0.27833014, -0.20804052, 0.22035008, -1.455818 ],
[-0.06508606, 0.46970765, -0.17450824, -2.41112073]])
cond = names == 'Bob'
data[~cond]
array([[-0.61991431, 0.65569043, -0.90567925, -0.66510301],
[-0.49578331, -0.61198523, 0.98094116, 0.99431798],
[ 0.52096438, 0.18326031, -1.34134102, -1.04946538],
[-0.27833014, -0.20804052, 0.22035008, -1.455818 ],
[-0.06508606, 0.46970765, -0.17450824, -2.41112073]])
mask = (names == 'Bob') | (names == 'Will')
mask
array([ True, False, True, True, True, False, False])
data[mask]
array([[-0.6910874 , 0.68352034, 1.51892787, -0.28907833],
[-0.49578331, -0.61198523, 0.98094116, 0.99431798],
[ 1.00135045, -0.28610541, 0.617814 , -0.32404145],
[ 0.52096438, 0.18326031, -1.34134102, -1.04946538]])
배열에 불리언 색인을 이용해서 데이터를 선택하면 반환되는 배열의 내용이 바뀌지 않더라도
항상 데이터 복사가 발생한다.
불리언 배열에 값을 대인하는 것은 상식적으로 이루어진다.
data에 저장된 모든 음수를 0으로 대입하러면 다음과 같이하면 된다.
data[data<0] = 0
data
array([[0. , 0.68352034, 1.51892787, 0. ],
[0. , 0.65569043, 0. , 0. ],
[0. , 0. , 0.98094116, 0.99431798],
[1.00135045, 0. , 0.617814 , 0. ],
[0.52096438, 0.18326031, 0. , 0. ],
[0. , 0. , 0.22035008, 0. ],
[0. , 0.46970765, 0. , 0. ]])
1차 불리언 배열을 사용해서 전체 로우나 컬럼을 선택하는 것은 쉽게 할 수 있다.
data[names != 'Joe'] = 7
data
array([[7. , 7. , 7. , 7. ],
[0. , 0.65569043, 0. , 0. ],
[7. , 7. , 7. , 7. ],
[7. , 7. , 7. , 7. ],
[7. , 7. , 7. , 7. ],
[0. , 0. , 0.22035008, 0. ],
[0. , 0.46970765, 0. , 0. ]])
2차원 데이터에 대한 이런 유형의 연산은 pandas를 이용해서 처리하는 것이 편하다.
팬시 색인
팬시 색인(fanct indexing)은 정수 배열을 사용한 색인을 설명하기 위해 NumPy에서 차용한 단어이다.
8 x 4 크기의 배열이 있다고 하자
arr = np.empty((8,4))
for i in range(8):
arr[i] = i
arr
array([[0., 0., 0., 0.],
[1., 1., 1., 1.],
[2., 2., 2., 2.],
[3., 3., 3., 3.],
[4., 4., 4., 4.],
[5., 5., 5., 5.],
[6., 6., 6., 6.],
[7., 7., 7., 7.]])
# 특정한 순러로 로우를 선택하고 싶다면 그냥 원하는 순서가 명시된 정수가 담긴 ndarray나 리스트를 넘기면 된다.
arr[[4,3,0,6]]
array([[4., 4., 4., 4.],
[3., 3., 3., 3.],
[0., 0., 0., 0.],
[6., 6., 6., 6.]])
# 색인으로 음수를 사용하면 끝에서 부터 로우를 선택한다.
arr[[-3,-5,-7]]
array([[5., 5., 5., 5.],
[3., 3., 3., 3.],
[1., 1., 1., 1.]])
arr = np.arange(32).reshape((8,4))
arr
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23],
[24, 25, 26, 27],
[28, 29, 30, 31]])
arr[[1,5,7,2],[0,3,1,2]]
array([ 4, 23, 29, 10])
마지막 결과를 보면 (1,0), (5,3), (7,1), (2,2)에 대응하는 원소들이 선택되었다.
배열이 몇 차원이든지 팬시 색인의 결과는 항상 1차원이다.
arr[[1,5,7,2]][:,[0,3,1,2]]
array([[ 4, 7, 5, 6],
[20, 23, 21, 22],
[28, 31, 29, 30],
[ 8, 11, 9, 10]])
'머신러닝 > numpy' 카테고리의 다른 글
NumPy의 기초(5) (0) | 2021.10.06 |
---|---|
NumPy의 기초(4) (0) | 2021.10.06 |
NumPy의 기초(3) (0) | 2021.09.09 |
NumPy의 기초(1) (0) | 2021.09.09 |