본문 바로가기
Python/NumPy

Structured Array(구조체 배열)의 indexing(인덱싱)과 assignment(배정) - NumPy(10)

by 콩돌 2019. 2. 24.
반응형

참고 자료

https://docs.scipy.org/doc/numpy/user/basics.rec.html



파이썬 버전 3.7 기준

NumPy 버전 1.16 기준


본 포스팅에서는 NumPy에서의 Structured Array의 indexing과 assignment를 다룬다.



NumPy에서의 Structured array(구조체 배열)의 Indexing(인덱싱)과 Assignment(배정)


구조체 배열로의 배정

구조체 배열에 값을 배정하는 많은 방법이 존재한다.
① 파이썬 튜플을 활용하는 법, ② 스칼라 값을 사용하는법, 혹은 ③ 다른 구조체배열을 활용하는 방법 등이 있다.

① 파이썬의 튜플을 이용한 배정
  ○ 배정을 하고자하는 값들로 배열에 있는 필드와 같은 길이의 튜플을 구성한다.
    ▷ 리스트를 사용시 브로드캐스팅을 시도하게 되므로로 사용하지 않는게 좋다.  
  ○ 튜플의 요소들은 배열의 필드에 연속적으로 배정된다.
  ○ 값을 배정하는 가장 단순한 방법이다. 


② 스칼라를 이용한 배정

  ○ 구조체의 요소로 배정된 스칼라는 모든 필드에 배정되며 아래와 같은 상황에서 일어나게 된다. 

     구조체 배열에 스칼라가 배정되는 경우.

     구조체가 아닌 배열이 구조체 배열에 배정되는 경우.

  ○ 구조체 배열은 비구조체 배열으로 배정될 수 있으나, 구조체 자료형이 하나의 필드일 경우에 한한다.


③ 다른 구조체 배열을 이용한 배정 

  ○ 두 구조체 배열간의 배정은 입력되는 요소가 튜플로 변환되고 대상 요소에 할당되는 방식으로 진행된다.

  ○ 첫 번째 입력되는 배열은 필드 이름에 상관없이 첫번째 배열로 배정이 되며, 두 번째 이후에 입력되는 배열 역시 같은 방식으로 진행된다.

  ○ 필드의 숫자가 다른 구조체 배열들은 서로 배정을 할 수 없다. 

  ○ 임의의 필드에 종속되지 않은 대상 구조체의 바이트는 영향을 받지 않는다. 

예제)
# 배정 예제를 위한 입력
In[3]: x = np.array([(1.,1.,1.),(2.,2.,2.)], dtype=[('x01', 'f4'),('x02', 'f4'),('x03', 'f4')])

# ①번 방법을 이용한 배정
In[4]: x[1] = (1.,2.,3.)
In[5]: x
Out[5]: 
array([(1., 1., 1.), (1., 2., 3.)],
      dtype=[('x01', '<f4'), ('x02', '<f4'), ('x03', '<f4')])

# ②번 방법을 이용한 배정
In[6]: x[:] = 5.
In[7]: x
Out[7]: 
array([(5., 5., 5.), (5., 5., 5.)],
      dtype=[('x01', '<f4'), ('x02', '<f4'), ('x03', '<f4')])

# ②번 방법을 이용한 배정과 각각의 자료형이 다를 경우
In[8]: y = np.array([(0,0,0),(0,0,0)], dtype=[('x01', '<f4'), ('x02', '<i4'), ('x03', '<S3')] )
In[9]: y
Out[9]: 
array([(0., 0, b'0'), (0., 0, b'0')],
      dtype=[('x01', '<f4'), ('x02', '<i4'), ('x03', 'S3')])
In[10]: y[:] = 1
In[11]: y
Out[11]: 
array([(1., 1, b'1'), (1., 1, b'1')],
      dtype=[('x01', '<f4'), ('x02', '<i4'), ('x03', 'S3')])
In[12]: y[:] = np.arange(2)
In[13]: y
Out[13]: 
array([(0., 0, b'0'), (1., 1, b'1')],
      dtype=[('x01', '<f4'), ('x02', '<i4'), ('x03', 'S3')])

# ③번 방법을 이용한 배정
In[14]: y[:] = x[:]
In[15]: y
Out[15]: 
array([(5., 5, b'5.0'), (5., 5, b'5.0')],
      dtype=[('x01', '<f4'), ('x02', '<i4'), ('x03', 'S3')])

서브 배열(Subarray)을 포함한 배정

  ○ 서브배열의 필드를 배정할 때, 배정되는 값은 먼저 서브 배열의 형태로 브로드캐스팅 된다.



구조체 배열의 인덱싱

하나의 필드에의 접근
  ○ 필드 네임을 사용해서 배열을 인덱싱하여 구조체 배열의 하나의 필드에 접근이 가능하며 수정될 수 있다. 

  ○ 결과로 반환되는 배열은 원래 배열에 대한 뷰(view)이다. 

     동일한 메모리 위치를 공유하며 뷰에 쓰인 원래배열이 수정된다. (얕은 복사)

  ○ 인덱스된 필드와 뷰는 같은 dtype과 아이템사이즈를 가지고, 중첩된 구조체를 제외하고는 비구조체 배열이다. 


예제)

# 예제용 배열 입력

In[19]: score = np.array([(100,50),(95,20)], dtype=[('science','i4'), ('social','i4')])


# 필드 네임을 이용한 인덱싱

In[20]: score['science']

Out[20]: array([100,  95])


# 필드 네임에 대한 배정을 수행

In[21]: score['science'] = 80

In[22]: score

Out[22]: array([(80, 50), (80, 20)], dtype=[('science', '<i4'), ('social', '<i4')])


# 특정 필드에 대한 얕은 복사

In[23]: social_score = score['social']

In[24]: social_score[:] = 35

In[25]: score

Out[25]: array([(80, 35), (80, 35)], dtype=[('science', '<i4'), ('social', '<i4')])


# 얕은 복사에 대한 결과

In[26]: social_score.dtype, social_score.shape, social_score.strides

Out[26]: (dtype('int32'), (2,), (8,))


  ○ 만약 접근하는 배열이 서브배열이라면, 서브배열의 차원은 결과의 형상에 따라 확장되게 된다.


예제)

# 예제용 배열 입력

In[30]: k = np.zeros((2,2), dtype=[('x01', 'i4'), ('x02', 'f4', (4,4))])


# 일반 배열의 경우

In[31]: k['a'].shape

In[32]: k['x01'].shape

Out[32]: (2, 2)


# 서브 배열이 있는 경우

In[33]: k['x02'].shape

Out[33]: (2, 2, 4, 4)

In[34]: k['x02']

Out[34]: 

array([[[[0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.]],

        [[0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.]]],

       [[[0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.]],

        [[0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.]]]], dtype=float32)


다중 필드로의 접근
  ○ 인덱스가 필드 이름의 리스트일 경우에는, 다중 필드 인덱스를 사용하여 구조체 배열을 인덱싱과 배정을 수행할 수 있다.
  ○ 다중필드 인덱스를 이용한 인덱싱의 결과는 원본 배열의 뷰이다.
     뷰로의 배정은 원본 배열을 수정한다. 
     위의 하나의 필드에 접근하는 방식과는 다르게, 뷰의 dtype은 원본 배열과 같은 아이템 사이즈와 오프셋에서의 같은 필드를 가진다. 
      - 단지 같은 명시되지 않은 필드만 없는 형태이다.
  ○ 하나의 필드에 접근할때와 마찬가지로 마찬가지로 다중필드를 이용한 배열로의 배정은 원본 배열을 변경시킨다.
  ○ 이것은 위에서 설명한 구조체배열 할당규칙을 따른다. 즉, 적절한 다중필드 색인을 사용한다면 두개의 필드 값을 서로 바꿀 수 있다. 

※ 주의 
  - 다중 필드 인덱싱은 1.15버전에서 1.16 버전으로 전환되었다.

  - 1.15와는 다르게 1.16에서는 불러오지 않은 필드에 대해서 패딩 바이트를 삽입하므로 이 부분에 대한 것은 주의할 필요가 있다. 

  - 자세한 내용은 포스팅 상단에 링크걸어둔 참고자료를 참고하시길...


예제)
# 예제 배열의 입력
In[35]: g = np.ones(3, dtype=[('x01', 'i4'), ('x02', 'i4'), ('x03', 'f4')])
In[36]: g
Out[36]: 
array([(1, 1, 1.), (1, 1, 1.), (1, 1, 1.)],
      dtype=[('x01', '<i4'), ('x02', '<i4'), ('x03', '<f4')])

# 다중 필드로의 인덱싱 결과
In[37]: g[['x01', 'x03']]
Out[37]: array([(1, 1.), (1, 1.), (1, 1.)], dtype=[('x01', '<i4'), ('x03', '<f4')])

# 다중 필드로의 배정
In[38]: g[['x01', 'x03']] = (10, 20)
In[39]: g
Out[39]: 
array([(10, 1, 20.), (10, 1, 20.), (10, 1, 20.)],
      dtype=[('x01', '<i4'), ('x02', '<i4'), ('x03', '<f4')])

# 두 필드의 데이터 교환
In[40]: g[['x01', 'x02']] = g[['x02', 'x01']]
In[41]: g
Out[41]: 
array([(1, 10, 20.), (1, 10, 20.), (1, 10, 20.)],
      dtype=[('x01', '<i4'), ('x02', '<i4'), ('x03', '<f4')])


구조체 스칼라에 접근하기 위한 정수 인덱싱
  ○ 구조체 배열에서 하나의 요소에 대한 인덱싱은 구조체 스칼라를 반환한다.
  ○ 다른 넘파이 스칼라와는 다르게, 구조체 스칼라는 변할 수 있으며, 원본 배열로의 뷰와 같이 거동한다.
    ▷ 즉, 스칼라를 수정하는 것은 원본 배열을 수정하게된다.

  ○ 구조체 스칼라에 대해 필드네임을 사용하여 접근(Access)과 배정을 사용할 수 있다

  ○ 튜플과 유사하게 구조체 스칼라는 또한 정수를 사용하여 인덱싱될 수 있다. 

    ▷ 그러므로 튜플은 넘파이의 구조화된 타입과 동일한 본래 파이썬 자료형이라 볼 수 있다.

     구조체 스칼라는 ndarray.item을 불러와 튜플로 변환될 수 있다.


  ○ 파이썬 정수는 넘파이의 정수형식과 동일하다. 


예제)
# 예제를 위한 입력
In[8]: x = np.array([(1,2,3),(4,5,6)], dtype='i,f,f')
In[9]: x
Out[9]: 
array([(1,  2.,  3.), (4,  5.,  6.)],
      dtype=[('f0', '<i4'), ('f1', '<f4'), ('f2', '<f4')])

# 구조체 스칼라의 선언
In[10]: scalar = x[0]
In[11]: scalar
Out[11]: (1,  2.,  3.)
In[12]: type(scalar)
Out[12]: numpy.void

# 구조체 스칼라로의 배정
In[13]: scalar['f1'] = 300
In[14]: scalar
Out[14]: (1,  300.,  3.)
In[15]: x
Out[15]: 
array([(1,  300.,  3.), (4,    5.,  6.)],
      dtype=[('f0', '<i4'), ('f1', '<f4'), ('f2', '<f4')])

# 정수를 이용한 구조체 스칼라의 선언
In[16]: x
Out[16]: 
array([(1,  300.,  3.), (4,    5.,  6.)],
      dtype=[('f0', '<i4'), ('f1', '<f4'), ('f2', '<f4')])
In[17]: scalar = x[0]
In[18]: scalar
Out[18]: (1,  300.,  3.)
In[19]: scalar[2] = 400
In[20]: scalar
Out[20]: (1,  300.,  400.)
In[21]: x
Out[21]: 
array([(1,  300.,  400.), (4,    5.,    6.)],
      dtype=[('f0', '<i4'), ('f1', '<f4'), ('f2', '<f4')])

# item() 메서드를 사용하여 튜플로 변환 
In[22]: scalar.item(), type(scalar.item())
Out[22]: ((1, 300.0, 400.0), tuple)


오브젝트를 포함하는 구조체 배열의 뷰
  ○ numpy.object 타입의 필드속에서 오브젝트 포인터를 망가트리는 것을 막기 위해, 넘파이에서는 오브젝트를 포함하는 구조체 배열에 대해서 현재 뷰를 할 수 없다.


구조체 비교
  ○ 만약 두개의 void 구조체 배열의 dtype이 같을 경우, 두 배열이 동일한지 테스트 해볼 수 있는데, 이때 결과로서 불린(boolean) 배열을 출력한다.  
    ▷ 요소는 True로 설정되고 해당 구조의 모든 필드는 동일하다.
  ○ 필드네임, dtype, 타이틀이 같으며, 필드의 엔디안을 무시하고, 필드가 같은 오더에 있다면, 구조체의 dtype은 같다.

  ○ 현재, 두 void 구조체 배열의 dtype이 같지 않다면 비교는 실패하며, 스칼라 값은 False를 반환한다.

    ▷ 이 내용은 중요도가 떠러져 앞으로 사라지게 될 것이며, 에러를 일으키거나, 하나의 값을 비교하는 방식으로 될 예정이다.

  ○ 두 void 구조체 배열을 비교할 때, <와 > 연산자가 사용될 경우 항상 False를 반환한다. 

  ○ 산술연산과 비트연산 역시 지원되지 않는다.


예제)
In[23]: a = np.zeros(2, dtype=[('x1', 'i4'), ('x2', 'f4')])
In[24]: b = np.ones(2, dtype=[('x1', 'i4'), ('x2', 'f4')])
In[25]: c = np.zeros(2, dtype=[('x1', 'i4'), ('x2', 'f4')])
In[26]: a == b
Out[26]: array([False, False], dtype=bool)
In[27]: a == c
Out[27]: array([ True,  True], dtype=bool)


반응형

댓글