NumPy 배열을 다루다 보면 다차원 배열(ndarray)을 1차원 배열로 변환해야 하는 경우가 자주 발생합니다. NumPy는 이를 위해 두 가지 방법을 제공하는데, 하나는 flatten() 메서드이고 다른 하나는 ravel() 함수입니다. 흥미롭게도 두 방법은 동일한 결과를 반환하지만, 내부적으로 매우 중요한 차이점을 가지고 있습니다.
기본 사용 예제
# 필요한 라이브러리 numpy 임포트 import numpy as np # 리스트로부터 배열 생성 arr = np.array([(2, 7, 3, 4), (5, 6, 9, 1)]) # flatten() 출력 print(arr.flatten()) # ravel() 출력 print(arr.ravel())
실행 결과
[2 7 3 4 5 6 9 1] [2 7 3 4 5 6 9 1]
위 실행 결과에서 볼 수 있듯이 두 함수는 완전히 동일한 1차원 배열을 반환합니다. 그렇다면 같은 작업을 위해 왜 두 가지 메서드가 존재하는 걸까요? 핵심은 '뷰(view)'와 '복사본(copy)'의 차이에 있습니다.
arr.ravel()의 특징
원본 배열의 참조(뷰)를 반환합니다.
ravel()로 만든 배열을 수정하면 원본 배열의 값도 함께 변경됩니다.
새로운 메모리를 할당하지 않으므로 flatten()보다 속도가 빠릅니다.
라이브러리 레벨의 함수로,
np.ravel()형태로도 호출할 수 있습니다.
arr.flatten()의 특징
원본 배열의 복사본(copy)을 반환합니다.
복사본을 수정해도 원본 배열의 값은 전혀 변하지 않습니다.
새로운 메모리를 차지하기 때문에 ravel()보다 약간 느립니다.
ndarray 객체의 메서드로,
arr.flatten()형태로만 호출할 수 있습니다.
실습 예제로 확인하는 차이점
# 필요한 라이브러리 numpy 임포트
import numpy as np
# 2차원 numpy 배열 생성
arr = np.array([(1, 2, 3, 4), (3, 1, 4, 2)])
# 원본 배열 출력
print("Original array:\n", arr)
# 배열의 차원(2)과 타입 확인
print("Dimension of original array: %d \n Type of original array: %s" % (arr.ndim, type(arr)))
print("\nOutput from ravel method: \n")
# ndarray를 1차원 배열로 변환
b_arr = arr.ravel()
# ravel은 원본 배열의 '뷰'를 b_arr에 전달
print(b_arr)
b_arr[0] = 1000
print(b_arr)
# 주목: 원본 배열 arr의 arr[0][0] 값도 1000으로 변경됨
print(arr)
# 차원(1)과 타입 확인
print("Dimension of array: %d \n Type of array: %s" % (b_arr.ndim, type(b_arr)))
print("\nOutput from flatten method: \n")
# ndarray를 1차원 배열로 변환
c_arr = arr.flatten()
# flatten은 원본 배열의 '복사본'을 c_arr에 전달
print(c_arr)
c_arr[0] = 0
print(c_arr)
# 주목: c_arr의 값을 변경해도 원본 배열 arr에는 아무런 영향이 없음
print(arr)
print("Dimension of array->%d \n Type of array->%s" % (c_arr.ndim, type(c_arr)))
실행 결과
Original array: [[1 2 3 4] [3 1 4 2]] Dimension of original array: 2 Type of original array: <class 'numpy.ndarray'> Output from ravel method: [1 2 3 4 3 1 4 2] [1000 2 3 4 3 1 4 2] [[1000 2 3 4] [ 3 1 4 2]] Dimension of array: 1 Type of array: <class 'numpy.ndarray'> Output from flatten method: [1000 2 3 4 3 1 4 2] [0 2 3 4 3 1 4 2] [[1000 2 3 4] [ 3 1 4 2]] Dimension of array->1 Type of array-><class 'numpy.ndarray'>
실행 결과를 자세히 살펴보겠습니다. ravel()로 생성한 b_arr의 첫 번째 값을 1000으로 변경하자, 원본 배열 arr의 arr[0][0] 값 역시 1000으로 함께 변경되었습니다. 이는 ravel()이 원본 배열의 뷰(view)를 반환하기 때문입니다. 즉, 두 배열이 같은 메모리 공간을 공유하고 있는 것입니다.
반면 flatten()으로 생성한 c_arr의 첫 번째 값을 0으로 변경했을 때는 원본 배열 arr이 전혀 영향을 받지 않았습니다. flatten()은 원본 데이터와 완전히 분리된 독립적인 복사본을 새로운 메모리에 생성하기 때문입니다.
그렇다면 어떤 것을 사용해야 할까?
안전성이 중요한 경우 → flatten(): 원본 배열을 절대 수정되어서는 안 되는 상황이라면 복사본을 반환하는 flatten()을 사용하는 것이 좋습니다.
성능과 메모리 효율이 중요한 경우 → ravel(): 대용량 배열을 다루거나 변환된 배열을 읽기 전용으로만 사용한다면, 추가 메모리 없이 빠르게 동작하는 ravel()이 유리합니다.
참고로 두 함수 모두
order파라미터('C', 'F', 'A', 'K')를 지원하여 배열을 펼치는 순서를 지정할 수 있습니다.
결론적으로, 두 함수의 반환 결과만 놓고 보면 동일해 보이지만 메모리 참조 방식의 차이가 있다는 점을 반드시 기억해야 합니다. 의도치 않은 원본 배열 변경을 방지하려면 flatten(), 성능 최적화가 필요하다면 ravel()을 선택하는 것이 바람직합니다.