레이블이 인덱싱인 게시물을 표시합니다. 모든 게시물 표시
레이블이 인덱싱인 게시물을 표시합니다. 모든 게시물 표시

2015년 7월 6일 월요일

넘파이(numpy) 배열의 인덱싱에서 복사와 뷰(view)

 넘파이의 ndarray(이하 배열)을 슬라이싱할 때 파이썬의 리스트(list)와 다르게 원본의 참조가 생성되는데 이를 뷰(view)라고 한다. 예를 들어서 다음과 같이 2차 배열 arr을 고려하자.

>>> arr=np.arange(8).reshape(2,4)
>>> arr
array([[0, 1, 2, 3],
      [4, 5, 6, 7]])

단순 대입문을 사용하여 arr2 = arr 이라는 명령은 arr 의 참조본이 arr2에 저장된다. 따라서 arr이 수정되면 arr2 도 같이 바뀐다. 이것은 리스트의 동작방식과 같다.
 그런데 arr[0] 라고 슬라이싱하면 첫 번째 행 전체가 선택된다.

>>> b=arr[0]
>>> b
array([0, 1, 2, 3])

여기서도 마찬가지로 b 배열은 arr 배열의 첫 행에 대한 복사가 아니고 뷰(참조의 개념)이기 때문에 원본이 바뀌면 이 참조본도 따라서 바뀌게 된다.
>>> arr[0]=10
>>> arr
array([[10, 10, 10, 10],
      [ 4,  5,  6,  7]])
>>> b
Out[127]: array([10, 10, 10, 10])

이 예에서 보면 arr 배열의 첫 행을 모두 10으로 바꾸었다. 배열의 슬라이싱에 스칼라를 대입하면 그 범위의 전체 요소에 대입된다. 그런데  b 배열도 같이 바뀐 것을 알 수 있다. 이는 리스트의 슬라이싱에서는 복사본이 생성되는 것과는 다른 동작이기 때문에 주의가 필요하다.  numpy는 대용량 데이터 처리를 염두에 두고 설계되었다. 대용량 데이터의 슬라이싱이 빈번하게 일어나는 복잡한 코드를 실행시키는데 있어서 복사가 남발되면 메모리 문제를 일으킬 소지가 많기 때문에 이렇게 설계된 것으로 짐작된다.

  만약 슬라이싱의 복사본을 생성하고 싶다면 copy() 속성을 이용하면 된다.

>>> b=arr[0].copy()
>>> b
array([0, 1, 2, 3])
이제 b 배열은 원본인 arr 과 독립적인 복사본이므로 원본이 바뀌어도 아무런 영향을 받지 않는다.
  비슷한 차이가 numpy.asarray() 함수와 numpy.array() 함수에도 존재하는데 두 함수의 동작은 거의 유사하다. 가장 큰 차이점은 copy 옵션의 초기값이 다르다는 것이다. 사실 asarray()는 내부적으로 다음과 같이 정의되어 있다.
def asarray(a, dtype=None, order=None):
   return array(a, dtype, copy=False, order=order)

참고로 array() 함수의 정의부는 다음과 같다.

numpy.array(object, dtype=None, copy=True, order=None, subok=False, ndmin=0)

따라서 만약 배열이나 행렬객체를 이용하여 다시 생성된 배열은 numpy.array()함수를 이용하였다면 복사본이, numpy.asarray()함수를 이용하였다면 참조본(전체 뷰)이 생성된다.

>>> arr = np.ones((3,4))
>>> arr
array([[ 1.,  1.,  1.,  1.],
      [ 1.,  1.,  1.,  1.],
      [ 1.,  1.,  1.,  1.]])
>>> arrB = np.asarray(arr) # 참조본 생성
>>> arrC = np.array(arr) #복사본 생성
>> arr[1]=np.pi # 원본 변경
>>> arr
array([[ 1.        ,  1.        ,  1.        ,  1.        ],
      [ 3.14159265,  3.14159265,  3.14159265,  3.14159265],
      [ 1.        ,  1.        ,  1.        ,  1.        ]])
>>> arrB # 참조본은 자동으로 변경된다.
array([[ 1.        ,  1.        ,  1.        ,  1.        ],
      [ 3.14159265,  3.14159265,  3.14159265,  3.14159265],
      [ 1.        ,  1.        ,  1.        ,  1.        ]])
>>> arrC # 복사본은 변경되지 않는다.
array([[ 1.,  1.,  1.,  1.],
      [ 1.,  1.,  1.,  1.],
      [ 1.,  1.,  1.,  1.]])


2015년 6월 24일 수요일

MATLAB 의 행렬의 인덱싱 방법 (part 3)

 행렬의 원하는 부분을 치환하기 위해서는 치환하고자 하는 행렬의 원소에 값을 입력한다. 먼저 다음과 같이 행렬 A를 생성하자


>> A = [ 1 2 3 4 5;
6 7 8 9 10;
9 9 9 9 9;
0 0 0 0 0;
1 1 1 1 1];


이제 행렬 A에 대해서 다음 예제들과 같이 조작할 수 있다.


>> A(1,2) = 100 󰎠 % (1,2) 원소가 100이 된다.
A =
1 100 3 4 5
6 7 8 9 10
9 9 9 9 9
0 0 0 0 0
1 1 1 1 1
>> A(:,2) = [1 1 1 1 1]' 󰎠 % 2열의 원소들을 치환한다.
A =
1 1 3 4 5
6 1 8 9 10
9 1 9 9 9
0 1 0 0 0
1 1 1 1 1

>> A(1:4,2) = [2 2 2 2]' 󰎠 % 1행~4행의 2열 원소들을 2로 치환
A =
1 2 3 4 5
6 2 8 9 10
9 2 9 9 9
0 2 0 0 0
1 1 1 1 1

>> A(1,2) = 100 󰎠 % (1,2) 원소가 100이 된다.
A =
1 100 3 4 5
6 7 8 9 10
9 9 9 9 9
0 0 0 0 0
1 1 1 1 1
>> A(:,2) = [1 1 1 1 1]' 󰎠 % 2열의 원소들을 치환한다.
A =
1 1 3 4 5
6 1 8 9 10
9 1 9 9 9
0 1 0 0 0
1 1 1 1 1

>> A(1:4,2) = [2 2 2 2]' 󰎠 % 1행~4행의 2열 원소들을 2로 치환
A =
1 2 3 4 5
6 2 8 9 10
9 2 9 9 9
0 2 0 0 0
1 1 1 1 1


 아직 생성되지 않은 행렬의 특정한 요소에 값을 대입할 수 있다. 이때 지정되지 않은 원소는 모두 0 으로 출력된다. 예를 들어 아직 작업 공간에 D라는 변수가 없는 경우 다음과 같이 명령을 내리면,


>> D(3,3)=3 󰎠
D =
0 0 0
0 0 0
0 0 3


와 같이 D가 새로 생성되고 (3,3)자리에 3이 대입되며 지정되지 않은 다른 자리에는 0으로 채워진다. 만약 이 상태에서 다음과 같이 추가적으로 명령을 내리면


>> D(5,5)=5 󰎠
D =
0 0 0 0 0
0 0 0 0 0
0 0 3 0 0
0 0 0 0 0
0 0 0 0 5


지정되지 않은 위치의 요소들은 0으로 초기화되는 것을 알 수 있다.


 또 다른 예로서, C라는 행렬을 새로 생성시키는데, 두 번째 행을 [1 2 3]으로 하고 싶으면 다음과 같이 한다.


>> C(2,:)=[1 2 3] 󰎠
C =
0 0 0
1 2 3


이때 지정되지 않은 첫 번째 행의 값은 0으로 초기화됨을 알 수 있다. 만약 A라는 행렬이 3X3 크기의 행렬로 작업공간에 존재한다면


>> a=A(4,4) 󰎠
??? Index exceeds matrix dimensions.


는 보다시피 에러가 발생한다. 하지만 다음 명령어는 새로운 변수가 생성되는 것과 비슷한 규칙이 적용된다.


>> A(4,4)=10 󰎠
A =
1 2 3 0
4 5 6 0
7 8 9 0
0 0 0 10


즉, 지정된 위치를 만들어 값을 대입하고 값이 없는 위치에는 0을 대입한다.



MATLAB 의 행렬의 인덱싱 방법 (part 2)

 여기에서는 이전 포스트에 이어서 행렬의 인덱싱에 대해서 설명하겠다. 행렬의 인덱스에 콜론(:) 연산자를 이용하여 여러 개의 행이나 열을 동시에 선택할 수도 있다.


>> A(2, 1:3) % 2행의 1열에서 3열까지의 원소
4 5 6


위의 예에서 콜론 연산자가 단독으로 쓰이는 경우와는 다르다는 것을 알 수 있다. 여기에서 콜론 연산자는 이전 포스트에서 설명한 수열을 생성하는 연산자로서 행벡터를 생성한다. 즉 위의 명령은 다음과 완전히 동일하다.


>> A(2, [1 2 3])


다음 명령을 보자.


>> A(1:3, :) 󰎠 % 1행(전체)부터 3행(전체)까지
>> A([1 2 3], :) 󰎠


이 명령어의 행인덱스 1:3 와 열인덱스 : 에 똑같은 콜론(:)문자가 쓰였지만 전혀 다른 연산자이다. 앞에 콜론은 수열을 생성하는 연산자이고 뒤의 연산자는 인덱스로서 단독으로 쓰였기 때문에 ‘모두’의 의미를 갖는다. 즉, 둘 다 같은 명령이다.


 행렬의 마지막 요소를 지정하는 데에는 end연산자가 사용된다. 다음 예를 보자.


>> f=1:2:10 󰎠
f =
1 3 5 7 9
>> f(end)
9


위에서는 f벡터의 끝 요소 만을 참조하려면 f(5)라고 하면 되지 않겠느냐고 생각할 수도 있으나 end연산자는 참조하려는 행렬의 크기를 모르는 경우 제일 끝의 행(또는 열)만을 지정하고 싶을 때 유용하다. MATLAB 프로그래밍을 하다 보면 참조하려는 행렬의 크기가 불확실한 경우가 자주 발생하게 된다. 다음의 예제도 잘 살펴보기 바란다.


>> F=magic(5)
F =
17 24 1 8 15
23 5 7 14 16
4 6 13 20 22
10 12 19 21 3
11 18 25 2 9

>> F(end)
9

>> F(2:end, :)
23 5 7 14 16
4 6 13 20 22
10 12 19 21 3
11 18 25 2 9

>> F(1:3, 3:end)
1 8 15
7 14 16
13 20 22


위에서 magic(n)함수는 (n은 홀수) nxn 차원의 정방 행렬을 만드는데 생성된 행렬은 특정한 행(또는 열)의 모든 요소의 합이 일정하다. 대각선 방향의 합도 역시 같다. (자세한 설명은 help magic 명령으로 살펴볼 수 있다.)


 만일 어떤 행렬에서 특정한 행 또는 열을 없애려면 다음과 같이 하면 된다.


>> A=magic(3)
A =
8 1 6
3 5 7
4 9 2

>> A(2,:)=[]
A =
8 1 6
4 9 2

>> A(:,3)=[]
A =
8 1
4 9


위에서 [ ] 명령은 공행렬(empty matrix)을 생성하는데 이는 크기가 0인 행렬을 뜻한다. 예를 들어 행렬 A에서 2행을 없앤 행렬을 B에 대입하려면 다음과 같이 하면 될 것이다.


>> A=pascal(3) 󰎠
A =
1 1 1
1 2 3
1 3 6

>> B=A([1 3],:) 󰎠
B =
1 1 1
1 3 6


공행렬과 영행렬(zero matrix)은 구별해야 하는데 공행렬은 크기가 0인 행렬을 의미하며 MATLAB에서는 ‘[]’로 생성한다. 영행렬은 행렬의 모든 요소가 0인 행렬을 의미하며 zeros()함수를 이용하면 생성할 수 있다.


 find(X)함수는 입력행렬 X의 요소 중 0이 아닌 것의 인덱스를 반환한다. 즉, 행렬의 0이 아닌 요소의 인덱스를 찾는 함수가 find()함수이다. (help find󰎠명령으로 확인해 보라.) 이 함수의 반환값은 하나, 둘 그리고 세 개가 될 수 있다.


>> X=[0 1 3; 3 0 4; 0 5 0]
X =
0 1 3
3 0 4
0 5 0

>> m=find(X)
m =
2
4
6
7
8


위의 결과를 이해하려면 MATLAB에서 이차원 행렬의 요소들은 내부적으로 일차원 배열로 저장됨을 이해해야 한다. 그 일차원 배열의 0이 아닌 요소의 인덱스를 find()함수가 만약 출력변수가 한 개라면 반환하는 것이다. 위의 경우 행렬 X의 내부 인덱스를 도시하면 다음 그림과 같다.


[그림 2] X행렬의 내부 요소 인덱스


이 그림을 보면 앞의 예제에서 find()함수의 결과값이 이해가 갈 것이다.