이번 포스팅에선 NumPy의 인덱싱(indexing)과 슬라이싱(slicing)에 대해 알아보자.
Numpy는 머신러닝, 딥러닝의 기본적인 자료구조가 되는 친구라고 소개를 했는데, 오늘은 응용연산에 대해 두개의 포스팅으로 나누어 소개하고자 한다.
numpy
의 ndarray
는 리스트와 굉장히 유사한 면이 많다는 것을 대부분의 사람들이 알고 있으리라 생각한다.
**!!!!기본적인 indexing과 slicing 또한 python의 list와 거의 같다고 볼 수 있다. (하지만 본질적으로 다른 기능이 있음)
우선 살펴보자!
기본 인덱싱과 슬라이싱
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
import numpy as np # numpy 모듈을 np로 alias 해서 불러주자
arr = np.arange(10, 20, 1) # ndarray를 만들건데 1씩 증가하는 10부터 시작해서
# 20미만의 수를 담은 array를 만들것임
print(arr)
# 출력물: [10 11 12 13 14 15 16 17 18 19]
# 자 이제 slicing을 해보자.
# 난 array의 0번째 1번째 2번째 세 개의 요소 값을 알고 싶어!
tmp = arr[0:3]
print(tmp) # ndarray의 특징중 하나는 원본에 대한 slicing 데이터 값이 같음. 즉 ndarray임
# 출력물: [10 11 12]
# 그렇다면 arr[0] = 100 으로 바꾸면 어떻게 될까?
arr[0] = 100
# arr: [100 11 12 13 14 15 16 17 18 19] 이런 값을 갖게 됨
# arr을 보여주고 있는 tmp의 값은 어떻게 변할까?
# tmp: [100 11 12] 이렇게 변해있다.
# 정리하자면
# slicing 하면 view가 된다. 복사본만 따로 남는게 아님. view로 됨.
# python list 는 이게 없음. 근데 numpy는 있음.
# view 를 이해하지 못하면 나중에 data handling 할 때 문제가 된다.
# numpy의 ndarray에 대해서 slicing을 한다면 view가 만들어짐!!!
음… 뭐 마지막에 “slicing 이 view 가 되는데 주저리주저리~” 이 부분만 제외하고 본다면 크게 문제가 없다.
그래? 그러면 indexing은 어떻게 되는거지?
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import numpy as np
arr = np.arange(0, 5, 1) # 0부터 4까지 (5는 미포함) 1씩 증가하는 ndarray를 만들어보자
print(arr)
# 출력물: [0 1 2 3 4]
print(arr[2])
# 출력물: 2
# 여기까지도 크게 다른 부분이 없는것 같다.
# 조금 더 상세하게 들어가보자
print(arr[0: -1]) # 0부터 인덱스 끝까지 slicing을 해보자!
# 이렇게 slicing 을 하면 제일 끝 인덱스는 미포함임
# 출력물: [0 1 2 3]
# 여기까지도 파이썬과 똑같다.
# 음... 뭐가 다르다는거지?
# 왜 굳이 ndarray를 써야하는것인가?
# ndarray는 고차원일 때 파이썬의 list와 큰 차이가 난다.
# 2차원을 살펴보자
arr = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12]])
# 이렇게 구분짓지 않고 oneline으로 작성해도 되나,
# 2차원임을 강조하는 차원에서 arr을 만들었다.
# 자 출력해보자.
print(arr)
# [[ 1 2 3]
# [ 4 5 6]
# [ 7 8 9]
# [10 11 12]] 4x3 매트릭스로 이쁘게 출력되었다.
# 여기서 5라는 값을 가져오고 싶으면 어떻게 인덱싱을 하면 될까?
print(arr[1, 1]) # (0행부터 시작함을 인지하자)
# arr의 1행에서 1번째 열을 이렇게 표기하면 된다!
# 출력물: 5
print(arr[1][1]) # 이렇게도 표기 가능
#출력물: 5
# 그러면 slicing도 같이 써볼까?
print(arr[2, :]) # 이건 뭘까?
# 파이썬 arr의 2번째 행으로가 => [7 8 9]
# 그리고 칼럼(열) 전체를 출력해! => [7 8 9]
# 출력물: [7 8 9]
# 마찬가지로 파이썬의 리스트에서 써봤던 slicing과 indexing 기법이다.
자 그러면 여기서부터 점점 머리를 복잡하게 하는 생소하고 왜 필요할까? 의문이 들게끔 하지만 정말 필수적인 numpy
만의 함수가 나오기 시작한다.
Boolean Indexing
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
32
33
34
35
36
# boolean indexing
# 처음으로 소개할 친구가 이 boolean indexing 이다.
# 이 친구는 data cleaning 시 매우 도움이 되는 친구이니 필히 기억하도록 하자.
# True 와 False 값으로 구분하여, True의 값을 가진 요소만 indexing 하는 방식이다.
arr = np.arange(0, 10, 1) # 다시 ndarray를 잡아주고...
# arr : [0 1 2 3 4 5 6 7 8 9]
# 여기서 4이상인 값만 출력해보자.
# 물론 슬라이싱으로 눈대중으로 출력이 가능할 정도로 값이 정렬이 되어있고, 데이터 개수가 적다.
# 하지만 지금은 boolean indexing 을 사용해보고자 한다.
print( arr >= 4 ) # 이렇게 치면 어떻게 출력이 되지? boolean으로 값을 받아오지!
# [False False False False True True True True True True]
print( arr[ arr >= 4 ]) # 이렇게 값을 주게 되면
print( arr[ [False False False False True True True True True True] ]) # 이것과 같은 소리임.
# 출력값: [4 5 6 7 8 9]
# 이게 바로 boolean indexing임.
# 처음엔 생소할 수 있으나, 금방 익숙해진다.
# 그리고 데이터가 많고, 순서가 뒤죽박죽일 때 굉장히 코드의 효율성과 데이터 핸들링을 도와주는 기법이다.
# 이번엔 짝수만 출력해보자
# 그래 짝수만 어떻게 출력할건가?
# for loop와 if문을 쓰는 비효율적인 방식보다도 더 좋은게
# 바로 이 boolean indexing이다.
print(arr[ arr % 2 == 0]) # 파이썬아, arr로가서 arr에서 2로 나누어지는 모든 것들을 출력해줘 라고 생각하자.
# 출력값: [0 2 4 6 8]
# 왜이렇게 까지 해야하나요?
# 데이터 처리를 결국 나중엔 빨리 해야함. 그래야 대용량 데이터를 다룰때 시간이 말도 안되게 빨라짐.
# 지금 이런 boolean 인덱싱은 포문 돌리고 루프해서 컨디션 걸고 찾는거와는 차원이 다르게 빠름.
# 확실하게 익숙해지자!
# 머신러닝에서 웬만하면 포문 이프문으로 가릴 생각을 하진 말자..
Fancy Indexing
“fancy indexing”은 ndarray에 index 배열 (list, ndarray)를 전달해서 indexing 한단 소리이다.
그게 무슨 소리야? 코드를 한번 살펴보자.
1
2
3
4
5
6
7
arr = np.arange(0, 5, 1) # [0 1 2 3 4]
print(arr[1]) # 기존에 했던 indexing
print(arr[[1, 2, 4]]) # fancy indexing
# 출력물: [1 2 4]
# 이게 무슨소리야? 리스트 안 숫자는 인덱스 번호이다.
# 1번, 2번, 4번 인덱스의 값을 ndarray로 출력한다.
음 생각보다 간단한거 같다. 리스트를 하나 더 넣어줘서 인덱싱을 하면 되는 단순한 작업이었다.
2차원은 어떨까?
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
arr = np.arange(0, 12, 1).reshape(3, 4).copy()
# 3x4 배열로 0부터 11까지 1씩 증가하는 12개의 요소를 만들어라.
print(arr)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]] 이렇게 생김.
print(arr[2, 1]) # 9
print(arr[1:2, 2]) # 파이썬 우선 arr 행을 [1:2] 로 슬라이싱해. [4 5 6 7] 이 된다
# 거기서 2번째 요소 내놔 => [6]
# 뭐야? 왜 []가 씌워져있어?
# shape 이 뭐가 나온다 이것도 명확하게 맞춰줘야함. [6] 이 나오는데 일반적인 6이랑 다른거임!!!
# slicing 이잖아 저건. 그러니깐 요소가 숫자만 나오는게 아니고 ndarray로 나옴
# 생각을 해보자. 행, 열로 움직이잖아. slicing을 하면 행 먼저 짤라야겠지?
# 그리고 slicing 은 원본과 결과가 똑같아야겠지? 그러니깐 2차원이 나오지
# 2차원에 대해서 하나를 인덱싱을 한거야. 그러면 당연히 1차원이 나오지.
# 다시 해보자.
print("arr[1:2]: ", arr[1:2]) # [[4 5 6 7]] 이렇게 나옴.
# 여기서 이제 추가적으로 열에 대한 인덱스를 줘보자
print("arr[1:2, 2]: ", arr[1:2, 2]) # 그리고 거기서 칼럼 2번을 찾아서 출력하자.
# 👇
# [[4 5 6 7]] 인덱스 2번 컬름은 [6] 이 되는거임.
# [6] 3번째 칼럼이 나오지. (인덱스로 2를 줬잖아 0 1 2)
print("arr[1:2, 2:3]", arr[1:2, 2:3])
# 자 다시 한번 분해해서 보자구
# arr[1:2] 는 [[4 5 6 7]] 이었음.
# 여기서 열에 관해서 2:3 으로 슬라이싱을 해보자.
# 👇
# [[4 5 6 7]]
# 근데 indexing 이었다면 똑같이 [6]이 나왔겠지.
# 하지만 슬라이싱을 했어 [[6]] 이 나온다.
# 크 어렵다고 느껴진다면 우선 이해보다도 이 자체로 인지하고 연습해보자.
# 그렇다면 도움이 될 지 모른다.
# 이번엔 다르게 한 번 살펴보자.
print(arr[[0,2]]) # fancy indexing으로 0번째와 2번째를 선택했다.
# 근데 뭐 행? 열? 어떤거 말하는거야?
# 1차원이야 쉬웠지 열 하나 밖에 없었으니깐.
# 지금은 2차원임을 기억하자.
# 이런 경우엔 행을 선택한거다. 0번째와 2번째 행.
# 👉 [[ 0 1 2 3]
# [ 4 5 6 7]
# 👉 [ 8 9 10 11]] 이렇게 두 친구를 선택한거임.
# 그렇다면 fancy indexing으로 선택하고, 인덱싱을 하면 어떨까?
print(arr[[0,2], 2])
# 자 바로 위에서 2개의 행이 선택이 됐잖아?
# 그 2x4 매트릭스에서 2번 인덱스를 가진 열을 선택한다는 소리이다.
# 👇
# [[ 0 1 2 3]
# [ 8 9 10 11]]
# 즉 출력물은 [2 10] 이 된다.
# 처음 접하면 우와~~~~ 어렵다. 싶을수 있다. 하지만 당신이 머신러닝을 하고자 한다면
# 무조건 무조건!! 인지해야함을 다시 강조한다
# 자 더 복잡하게 들어가서 보자.
print(arr[[0,2], 2:3]) # 여기서 이렇게 슬라이싱을 하면 어떻게 될까?
# 이번에는 [[2] [10]] 이 되는 것을 위의 코드를 통해 알 수 있을것이다.
# 그렇다면 이건 어떨까?
print(arr[[0,2], [0,2]])
# 👇 👇
# [[ 0 1 2 3]
# [ 8 9 10 11]]
# 즉,
# [[0 2]
# [8 10]] 이 되리라 믿는다
# 하지만 매우 중요하게도 출력물은...
# [ 0 10] 이 나온다. 이건 뭔 개소리야????????
# fancy indexing의 특징중 하나는 행과 열 둘중에 하나만 fancy indexing을 적용할 수 있고,
# 두개 전부 적용할 수는 없다.
# 내가 생각해도 어렵고 복잡하다.
# 그렇다면 아래와 같이 출력을 하려면 어떻게 해야할까?
# [[0 2]
# [8 10]]
# 이때 쓰는 것이 numpy가 갖고 있는 ix_ 라는 메서드이다.
print(arr[np.ix_([0,2], [0,2])]) # 비로소 우리가 원하는대로 출력이 된다.
# [[0 2]
# [8 10]]
# 다른 방법을 사용할 수도 있다.
print(arr[[0,2]][:, [0,2]])
# [[0,2]] 로 행에 대해서 fancy 인덱싱을 하고 그 결괏값에 대해서 [:, [0,2]] 모든 행에 대해서
# 열에 대해서 fancy indexing 을 한 번 더하겠다 [0, 2].
# 이런게 문제다.
# 이전에는 fancy indexing 을 행과 열 모두에 적용하였을 때는,
# 에러로 나오지 않고 정상적으로 보이는 값이 나오니깐...
# 머신러닝으로 분석을 한다면 데이터를 분석했다면
# 그렇다면 결괏값도 이상하게 나오겠죠.
# 그래서 넘파이에 대한 기초가 정말 중요하다.
# 머신러닝은 데이터 핸들링이 정말 중요하다.
# 텐서플로우 뭐 다 좋지. 근데 핸들링 못하면 아무짝에도 쓸모가 없다.
# 결론은 데이터핸들링을 잘해야함.
# 데이터 핸들링의 기본이 되는 것은 pandas겠지만, numpy는 pandas의 근간이 되는 모듈임을 잊지 말자.
음.. 정말 복잡하다고 생각한다.
하지만 익숙해지면 문제될리 없다고도 생각한다.
열심히 happy coding 하시길!
[numpy indexing and slicing] 포스팅 끄읏!