참고 자료
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)
댓글