Computer >> 컴퓨터 >  >> 프로그래밍 >> Python

NumPy에서 flatten()과 ravel()의 차이점 완벽 비교

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으로 변경하자, 원본 배열 arrarr[0][0] 값 역시 1000으로 함께 변경되었습니다. 이는 ravel()이 원본 배열의 뷰(view)를 반환하기 때문입니다. 즉, 두 배열이 같은 메모리 공간을 공유하고 있는 것입니다.

반면 flatten()으로 생성한 c_arr의 첫 번째 값을 0으로 변경했을 때는 원본 배열 arr이 전혀 영향을 받지 않았습니다. flatten()은 원본 데이터와 완전히 분리된 독립적인 복사본을 새로운 메모리에 생성하기 때문입니다.

그렇다면 어떤 것을 사용해야 할까?

  • 안전성이 중요한 경우 → flatten(): 원본 배열을 절대 수정되어서는 안 되는 상황이라면 복사본을 반환하는 flatten()을 사용하는 것이 좋습니다.

  • 성능과 메모리 효율이 중요한 경우 → ravel(): 대용량 배열을 다루거나 변환된 배열을 읽기 전용으로만 사용한다면, 추가 메모리 없이 빠르게 동작하는 ravel()이 유리합니다.

  • 참고로 두 함수 모두 order 파라미터('C', 'F', 'A', 'K')를 지원하여 배열을 펼치는 순서를 지정할 수 있습니다.

결론적으로, 두 함수의 반환 결과만 놓고 보면 동일해 보이지만 메모리 참조 방식의 차이가 있다는 점을 반드시 기억해야 합니다. 의도치 않은 원본 배열 변경을 방지하려면 flatten(), 성능 최적화가 필요하다면 ravel()을 선택하는 것이 바람직합니다.