파이썬 Numpy 배열 axis개념, 합치기 나누기 (stack, concatenate, split)

지난 시간까지 파이썬 라이브러리인 넘파이에서 numpy array를 만들고 인덱싱/슬라이싱 하는 방법까지 공부하였다. 오늘은 2개의 배열을 가지고 합치고 나누는 방법에 대해 살펴본다.

배열 합치기

a = np.arange(12).reshape(4,3)
b = np.arange(10,22).reshape(4,3)

a
array([[ 0,  1,  2],
       [ 3,  4,  5],
       [ 6,  7,  8],
       [ 9, 10, 11]])

b
array([[10, 11, 12],
       [13, 14, 15],
       [16, 17, 18],
       [19, 20, 21]])

a.shape, b.shape
((4, 3), (4, 3))

🔺 이렇게 shape(4,3) 배열 두개를 생성하여 합쳐보겠다.

병합시에 합칠 어레이들은 (배열1, 배열2) 이런 형태로 튜플로 묶어서 넣어준다.

np.vstack((a, b))

array([[ 0,  1,  2],
       [ 3,  4,  5],
       [ 6,  7,  8],
       [ 9, 10, 11],
       [10, 11, 12],
       [13, 14, 15],
       [16, 17, 18],
       [19, 20, 21]])

🔺 vstack 함수는 두 어레이를 vertical 수직 방향으로 이어 붙여준다.

np.hstack((a, b))

array([[ 0,  1,  2, 10, 11, 12],
       [ 3,  4,  5, 13, 14, 15],
       [ 6,  7,  8, 16, 17, 18],
       [ 9, 10, 11, 19, 20, 21]])

🔺 hstack 함수는 horizontal 수평 방향으로 이어 붙여준다.

np.concatenate((a, b), axis=0)

array([[ 0,  1,  2],
       [ 3,  4,  5],
       [ 6,  7,  8],
       [ 9, 10, 11],
       [10, 11, 12],
       [13, 14, 15],
       [16, 17, 18],
       [19, 20, 21]])

np.concatenate((a, b), axis=1)

array([[ 0,  1,  2, 10, 11, 12],
       [ 3,  4,  5, 13, 14, 15],
       [ 6,  7,  8, 16, 17, 18],
       [ 9, 10, 11, 19, 20, 21]])

🔺 concatenate 함수는 지정한 축을 기준으로 해서 합쳐주는데, 이 때 axis=0 이라고 하면 vstack과 같이 수직으로 이어붙이고, axis=1 이라고 하면 hstack과 같이 수평으로 이어붙인다.

axis 축 방향의 개념

여기서 앞으로 자주 나올 축 설정에 대한 개념을 이해하고 넘어가야 편하다.

axis 축 방향의 개념

⚫ 왜 axis=0이 세로 방향으로 (vstack) 붙이는 것이고 axis=1이 가로 방향으로 (hstack) 붙이는 것일까❓❓

⚫ 그리고 지정하지 않으면 기본 설정은 axis=0으로 되어 있는데 그 이유는❓❓

이것은 shape에 나오는 기준으로 한다고 생각하면 편하다.

위 그림에 배열의 shape을 보면 (2, 5) 즉, 우리가 생각하는 기준으로는 2행 5열이다.

여기서 2행이라는 것은 세로 방향으로 2개가 있다는 뜻이다. 행의 개수를 적어놨지만 그 방향이 세로 방향, vertical 이라는 뜻이다.

그래서 어떤 명령을 내릴 때 axis=0 이라고 하면 행들이 있는 방향, 즉 세로 방향으로 연산을 하는 것이고 axis=1 이라고 하면 열들이 있는 방향인 가로 방향으로 연산을 하는 것이다.

엑셀에서 작업하다보면 열 (columns) 에는 보통 어떤 데이터의 종류를 써놓고 밑으로 쭉 수집하는 경우가 많다.

이름나이몸무게
홍길동3018077
임꺽정2918579
강백호1818280

이런 식으로 보통 만들지 않나? 여기서 생각해보자 다른 추가 데이터가 왔을때 어떻게 보통 합치게 되는지 말이다. 아마 새로운 데이터를 원래 있던 것의 밑에다 붙이는 경우가 많을 것이다. 실제로 실무에서 작업할 때 그런 경우가 많기 때문에 기본 설정이 axis=0 세로 방향으로 하도록 만들어놓은 것이다. 

이런걸 보면 파이썬과 라이브러리들은 실제로 어떻게 해야 작업이 효율적이고 편할지 고민해서 개발한 흔적이 느껴진다.

다시한번 암기

axis=0 : vertical, 행들이 나열된 세로 방향으로 명령수행 (기본값)

axis=1 : horizontal, 열들이 나열된 가로 방향으로 명령수행

배열 나누기

이번에는 배열을 나누는 방법에 대해 알아본다.

a = np.arange(12)
a
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

np.split(a, 2)
[array([0, 1, 2, 3, 4, 5]), array([ 6,  7,  8,  9, 10, 11])]

np.split(a, 3)
[array([0, 1, 2, 3]), array([4, 5, 6, 7]), array([ 8,  9, 10, 11])]

np.split(a, 4)
[array([0, 1, 2]), array([3, 4, 5]), array([6, 7, 8]), array([ 9, 10, 11])]

np.split(a, 5)
ValueError: array split does not result in an equal division

🔺 1차원 배열은 split(array이름, 분할개수) 형태로 함수를 이용해서 n분할 시킬 수 있다. 이 때 균등하게 분할되지 않는 수를 입력하면 마지막 예시와 같이 ValueError가 발생한다.

np.split(a, [2, 5, 8, 10])

[array([0, 1]),
 array([2, 3, 4]),
 array([5, 6, 7]),
 array([8, 9]),
 array([10, 11])]

🔺 split 할 위치를 하나의 리스트로 지정해줄 수도 있다. 이 때 순서는 항상 0번째부터 시작한다는 것을 명심하자.

위 예시처럼 하면 2, 5, 8, 10번째에서 각각 새로운 어레이로 분리하라는 의미이다. 4번 분리하니까 총 5개의 1차원 어레이로 나누어졌다.

앞서 기본적으로 axis=0이 생략된 것이라고 하였다. 

 np.split(a, [2, 5, 8, 10], axis=0)

 [array([0, 1]),
 array([2, 3, 4]),
 array([5, 6, 7]),
 array([8, 9]),
 array([10, 11])]


np.hsplit(a, [2, 5, 8, 10])

[array([0, 1]),
 array([2, 3, 4]),
 array([5, 6, 7]),
 array([8, 9]),
 array([10, 11])]

🔺 이렇게 split에 axis=0을 써주어도 결과는 같다. (기본값이니)

a.shape 해보면 (12,) 로 표시되는데, 앞에 12가 표시되어 있으니 그 자리는 axis=0이다.

그런데 vsplit으로 하면 에러가 나고 hsplit으로 해야 동일한 결과가 나온다.

아까 shape(v, h) 라서 axis=0이면 세로방향, axis=1이면 가로방향이라고 했었는데 뭔가 안맞는다. axis=1로 해야되는거 아니야?

저렇게 표시되는데 이건 배열이 1차원이라 그냥 제일 앞에 1차원 원소의 개수가 나온 것뿐이고 실제 행열을 생각해보면 1행 12열인 셈이니 이렇게 보면 hsplit으로 하는게 맞다. 이런 부분 때문에 이해하고 넘어갔다가도 또 헷갈리고 한다… 그때마다 꼼꼼하게 개념을 숙지해두는 수밖에.

[배열이 1차원 일 때] axis=0 : 1차원을 축으로 삼음

[배열이 2차원 일 때] axis=0 : 2차원을 축으로 삼음 (큰 괄호), axis=1 : 1차원을 축으로 삼음 (안쪽괄호)

[배열이 3차원 일 때] axis=0 : 3차원을 축으로 삼음 (제일바깥 큰괄호), axis=1 : 2차원을 축으로 삼음 (중간괄호), axis=2 : 1차원을 축으로 삼음 (제일 안쪽 괄호)

🔺 정확히 말하자면 차원 개념으로 위와 같이 되는게 맞다.

a = np.arange(12).reshape(3,4)
a
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])


np.split(a, 3, axis=0)
[array([[0, 1, 2, 3]]), array([[4, 5, 6, 7]]), array([[ 8,  9, 10, 11]])]


np.split(a, 2, axis=1)
[array([[0, 1],
        [4, 5],
        [8, 9]]), 
 array([[ 2,  3],
        [ 6,  7],
        [10, 11]])]

🔺 2차원 배열을 axis=0으로 3개로 나누면 행 3개가 각각 분리된 배열3개로 나뉜다. axis=1로 2개로 나누면 열 방향으로 두개로 쪼개진 각각의 배열이 생성된다.

np.vsplit(a, 3)
[array([[0, 1, 2, 3]]), array([[4, 5, 6, 7]]), array([[ 8,  9, 10, 11]])]


np.hsplit(a, 2)
[array([[0, 1],
        [4, 5],
        [8, 9]]), 
 array([[ 2,  3],
        [ 6,  7],
        [10, 11]])]

🔺 vsplit으로 하면 axis=0 과 같이 행 단위로 나눈 결과와 같고, hsplit으로 하면 axis=1 적용시처럼 열 단위로 쪼개진다.

여기까지 numpy array의 병합과 분리 방법 그리고 axis 축방향 개념에 대해 살펴보았다. 다음 시간에는 넘파이에 적용되는 여러가지 통계 함수들에 대해 공부해본다.