ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 넘파이 기본: 배열과 벡터 연산
    Python 2023. 11. 7. 17:02

    Numerical Python의 준말로 파이썬에서 산술 계산을 위한 가장 중요한 필수 패키지 중 하나

    과학 계산을 위한 대부분의 패키지는 넘파이의 배열 객체를 데이터 교환을 위한 공통 언어처럼 사용

    > 넘파이와 관련된 대부분의 지식은 판다스에도 그대로 적용 가능

     

    넘파이 제공 기능

    · 빠른 배열 계산과 유연한 브로드캐스팅 기능을 제공하는 효율적인 다차원 배열인 ndarray

    · 반복문을 작성할 필요 없이 전체 데이터 배열을 빠르게 계산하는 표준 수학 함수

    · 배열 데이터를 디스크에 쓰거나 읽을 수 있는 도구와 메모리에 적재된 파일을 다루는 도구

    · 선형대수, 난수 생성기, 루리에 변환 가능

    · C, C++, 포트란으로 작성된 코드를 넘파이와 연결하는 C API

     

    데이터 분석 애플리케이션에서 중요하게 여겨지는 기능

    · 데이터 먼징(munging) (데이터 랭글링), 정제, 부분집합, 필터링, 변형 그리고 다른 여러 종류의 연산을 빠르게 수행하는 배열 기반 작업

    · 정렬, 유일 원소 찾기, 집합 연산 같은 일반적인 배열 처리 알고리즘

    · 효과적인 기술 통계와 데이터의 수집, 요약

    · 다양한 종류의 데이터를 병합하고 엮기 위한 데이터 정렬과 데이터 간의 관계 조작

    · if-elif-else를 사용하는 반복문 대신 배열 표현식으로 조건부 로직 표현

    · 수집, 변형, 함수 적용 같은 그룹별 데이터 조작

     

    성능 차이를 확인하기 위해 1백만 개의 정수를 저장하는 넘파이 배열과 파이썬 리스트 비교

    In [37]: import numpy as np
    
    In [38]: my_arr = np.arange(1_000_000)
    
    In [39]: my_list = list(range(1_000_000))

    각 배열과 리스트 원소에 x 2

    In [40]: %timeit my_arr2 = my_arr * 2
    1.24 ms ± 5.97 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
    
    In [41]: %timeit my_list2 = [x * 2 for x in my_list]
    51 ms ± 161 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

    넘파이를 사용한 코드가 순수 파이썬으로 작성한 코드보다 열 배에서 백 배 이상 빠르고 메모리도 적게 사용

     

    다차원 배열 객체 ndarray

    넘파이의 핵심 기능, n차원의 배열 객체

    ndarray는 파이썬에서 사용할 수 있는 대규모 데이터셋을 담을 수 있는 빠르고 유연한 자료구조

    배열을 사용하면 스칼라 원소 간의 연산에 사용하는 문법과 비슷한 방식을 사용해 전체 데이터 블록에 수학적 연산 수행 가능

    # import numpy as np
    In [42]: data = np.array([[1.5, -0.1, 3], [0, -3, 6.5]])
    
    In [43]: data
    Out[43]:
    array([[ 1.5, -0.1,  3. ],
           [ 0. , -3. ,  6.5]])
           
    In [44]: data * 10
    Out[44]:
    array([[ 15.,  -1.,  30.],
           [  0., -30.,  65.]])
    
    In [45]: data + data
    Out[45]:
    array([[ 3. , -0.2,  6. ],
           [ 0. , -6. , 13. ]])

     

    넘파이를 임포트할 경우 import numpy as np 컨벤션을 사용

    > from numpy import *를 사용해 np를 입력해도 되지만 파이썬 내장 함수와 동일한 이름을 사용하는 경우 충돌 생길 수 있음

    > 위와 같은 습관 생기지 않도록 주의할 것

    In [46]: a = [ [1, 2, 3], [4, 5, 6] ]
    
    In [47]: for i in a:
        ...:     for j in i:
        ...:         result = j * 10
        ...:         print(result)
        ...:
    10
    20
    30
    40
    50
    60
    
    In [48]: import numpy as np
    
    In [49]: a = np.array( [ [1, 2, 3], [4, 5, 6] ] )
    
    In [50]: a
    Out[50]:
    array([[1, 2, 3],
           [4, 5, 6]])
    
    In [51]: a * 10
    Out[51]:
    array([[10, 20, 30],
           [40, 50, 60]])

     

    ndarray는 같은 종류의 데이터를 담을 수 있는 포괄적인 다차원 배열, ndarray의 모든 원소는 같은 자료형이어야 함

    > 리스트는 같은 종류가 아니어도 모두 담을 수 있었음

    모든 객체는 각 차원의 크기를 알려주는 shape 튜플과 배열에 저장된 자료형을 알려주는 dtype 객체를 가짐

    In [52]: data.shape
    Out[52]: (2, 3)
    
    In [53]: data.dtype
    Out[53]: dtype('float64')

     

    ndarray 생성

    배열 생성하는 가장 쉬운 방법: array 함수 이용

    In [54]: data1 = [6, 7.5, 8, 0, 1]
    
    In [55]: arr1 = np.array(data1)
    
    In [56]: arr1
    Out[56]: array([6. , 7.5, 8. , 0. , 1. ])

     

    리스트 길이가 동일한 중첩된 순차 데이터는 다차원 배열로 변환 가능

    In [57]: data2 = [ [1, 2, 3, 4], [5, 6, 7, 8] ]
    
    In [58]: arr2 = np.array(data2)
    
    In [59]: arr2
    Out[59]:
    array([[1, 2, 3, 4],
           [5, 6, 7, 8]])

    리스트를 담고 있는 리스트이므로 넘파이 배열 arr2는 해당 데이터로부터 형태를 추론해 2차원 형태로 생성됨

    ndim과 shape 속성을 검사해 이를 확인할 수 있음

    In [60]: arr1.shape, arr2.shape
    Out[60]: ((5,), (2, 4))
    
    In [61]: arr2.ndim
    Out[61]: 2
    
    In [62]: arr2.shape
    Out[62]: (2, 4)
    
    In [63]: arr1.dtype
    Out[63]: dtype('float64')
    
    In [64]: arr2.dtype
    Out[64]: dtype('int32')

     

    numpy.array는 새로운 배열을 생성하는 여러 함수를 가지고 있음

    ex) zeros와 ones는 주어진 길이나 모양에 각각 0과 1이 들어 있는 배열 생성,

    empty 함수는 초기화되지 않은 배열을 생성(empty라고 해서 비어 있는 값만 반환하지 않음, 주의할 것)

    In [65]: np.zeros(10)
    Out[65]: array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
    
    In [66]: np.zeros((3, 6))
    Out[66]:
    array([[0., 0., 0., 0., 0., 0.],
           [0., 0., 0., 0., 0., 0.],
           [0., 0., 0., 0., 0., 0.]])
    
    In [67]: np.empty((2, 3, 2))
    Out[67]:
    array([[[0., 0.],
            [0., 0.],
            [0., 0.]],
    
           [[0., 0.],
            [0., 0.],
            [0., 0.]]])
    
    In [68]: np.ones((3, 6))
    Out[68]:
    array([[1., 1., 1., 1., 1., 1.],
           [1., 1., 1., 1., 1., 1.],
           [1., 1., 1., 1., 1., 1.]])

     

    arange는 파이썬 range함수의 배열 버전

    In [110]: np.arange(15)
    Out[110]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])

     

    ndarray의 자료형

    자료형(dtype)은 ndarray가 메모리에 있는 특정 데이터를 해석하는 데 필요한 정보(또는 메타데이터)를 담고 있는 특수한 객체

    In [3]: import numpy as np
    
    In [4]: data1 = [6, 7.5, 8, 0, 1]
    
    In [5]: arr1 = np.array(data1)
    
    In [6]: arr1
    Out[6]: array([6. , 7.5, 8. , 0. , 1. ])
    
    In [7]: data2 = [ [1, 2, 3, 4], [5, 6, 7, 8] ]
    
    In [8]: arr2 = np.array(data2)
    
    In [9]: arr2
    Out[9]:
    array([[1, 2, 3, 4],
           [5, 6, 7, 8]])
    
    In [10]: arr1 = np.array([1, 2, 3], dtype=np.float64)
    
    In [11]: arr2 = np.array([1, 2, 3], dtype=np.int32)
    
    In [12]: arr1
    Out[12]: array([1., 2., 3.])
    
    In [13]: arr2
    Out[13]: array([1, 2, 3])

     

    ndarray의 astype 메서드를 사용해 배열의 dtype을 다른 형으로 명시적 변환(cast) 가능

    In [14]: arr = np.array([1, 2, 3, 4, 5])
    
    In [15]: arr.dtype
    Out[15]: dtype('int32')
    
    In [16]: float_arr = arr.astype(np.float64)
    
    In [17]: float_arr
    Out[17]: array([1., 2., 3., 4., 5.])
    
    In [18]: float_arr.dtype
    Out[18]: dtype('float64')

     

    정수형 > 부동소수점 변환 시 소수점 아래 자리는 버려짐

    In [19]: arr = np.array([3.7, -1.2, -2.6, 0.5, 12.9, 10.1])
    
    In [20]: arr
    Out[20]: array([ 3.7, -1.2, -2.6,  0.5, 12.9, 10.1])
    
    In [21]: arr.astype(np.int32)
    Out[21]: array([ 3, -1, -2,  0, 12, 10])

     

    숫자 형식의 문자열을 담고 있는 배열이 있다면 astype을 사용해 숫자로 변환

    In [22]: numeric_strings = np.array(['1.25', '-9.6', '42'], dtype=np.string_)
    
    In [23]: numeric_strings.astype(float)
    Out[23]: array([ 1.25, -9.6 , 42.  ])
    
    In [24]: numeric_strings
    Out[24]: array([b'1.25', b'-9.6', b'42'], dtype='|S4')

     

    넘파이에서 문자열 데이터는 고정 크기를 가지며 별다른 경고를 출력하지 않고 입력을 임의로 잘라낼 수 있음

      > numpy.string_형을 이용할 때는 주의해야 함

     

    다른 배열의 dtype 속성 사용 가능

    In [25]: int_array = np.arange(10)
    
    In [26]: calibers = np.array([.22, .270, .357, .380, .44, .50], dtype=np.float64)
    
    In [27]: int_array.astype(calibers.dtype)
    Out[27]: array([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])

     

    dtype으로 사용할 수 있는 축약 코드

    In [28]: zeros_uint32 = np.zeros(8)
    
    In [30]: zeros_uint32.dtype
    Out[30]: dtype('float64')
    
    In [31]: zeros_uint32
    Out[31]: array([0., 0., 0., 0., 0., 0., 0., 0.])

     

    astype을 호출하면 새로운 dtype이 이전 dtype과 동일하더라도 항상 새로운 배열을 생성(데이터 복사)

     

    넘파이 배열의 산술 연산

    배열은 for문을 작성하지 않고 데이터를 일괄 처리할 수 있어 중요함 > 벡터화(vectorization)

    크기가 동일한 배열 간의 산술 연산은 배열의 각 원소 단위로 적용

    In [33]: arr = np.array([[1., 2., 3.,], [4., 5., 6.,]])
    
    In [34]: arr
    Out[34]:
    array([[1., 2., 3.],
           [4., 5., 6.]])
    
    In [35]: arr * arr
    Out[35]:
    array([[ 1.,  4.,  9.],
           [16., 25., 36.]])
    
    In [36]: arr - arr
    Out[36]:
    array([[0., 0., 0.],
           [0., 0., 0.]])

     

    스칼라 인수가 포함된 산술 연산의 경우 배열 내 모든 원소에 스칼라 인수가 적용됨

    In [37]: 1 / arr
    Out[37]:
    array([[1.        , 0.5       , 0.33333333],
           [0.25      , 0.2       , 0.16666667]])
    
    In [38]: arr ** 2
    Out[38]:
    array([[ 1.,  4.,  9.],
           [16., 25., 36.]])
           
    In [40]: one_arr = np.ones((2, 3))
    
    In [41]: one_arr
    Out[41]:
    array([[1., 1., 1.],
           [1., 1., 1.]])

     

    크기가 동일한 배열 간의 비교 연산은 불리언 배열 반환

    In [42]: arr2 = np.array([[0., 4., 1.], [7., 2., 12.]])
    
    In [43]: arr2
    Out[43]:
    array([[ 0.,  4.,  1.],
           [ 7.,  2., 12.]])
    
    In [44]: arr2 > arr
    Out[44]:
    array([[False,  True, False],
           [ True, False,  True]])

    크기가 다른 배열 간의 연산은 브로드캐스팅(broadcasting)이라고 함

     

    색인(index)와 슬라이싱 기초

    1차원 배열은 표면적으로 파이썬의 리스트와 유사하게 작동해 매우 간단함

    In [45]: arr = np.arange(10)
    
    In [46]: arr
    Out[46]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    
    In [47]: arr[5]
    Out[47]: 5
    
    In [48]: arr[5:8]
    Out[48]: array([5, 6, 7])
    
    In [49]: arr[5:8] = 12
    
    In [50]: arr
    Out[50]: array([ 0,  1,  2,  3,  4, 12, 12, 12,  8,  9])

    arr[5:8] = 12로 배열 슬라이스에 스칼라 값을 대입하면 12가 선택 영역 전체로 전파됨(브로드캐스팅)

     

    배열 슬라이스는 원본 배열의 view(뷰)로 데이터는 복사되지 않고 뷰에 대한 변경이 그대로 원본 배열에 반영됨

    In [51]: arr_slice = arr[5:8]
    
    In [52]: arr_slice
    Out[52]: array([12, 12, 12])
    
    In [53]: arr_slice[1] = 12345
    
    In [54]: arr
    Out[54]:
    array([    0,     1,     2,     3,     4,    12, 12345,    12,     8,
               9])

     

    단순히 [:]로 슬라이스하면 배열의 모든 값에 할당됨

    In [55]: arr_slice[:] = 64
    
    In [56]: arr
    Out[56]: array([ 0,  1,  2,  3,  4, 64, 64, 64,  8,  9])

     

    만약 뷰가 아닌 ndarray 슬라이스의 복사본을 얻고 싶다면 arr[5:8].copy()로 명시적으로 배열을 복사해야 함

    판다스도 동일하게 적용됨

     

    다차원 배열을 다룰 때 좀 더 많은 옵션이 있음, 2차원 배열에서 각 인덱스에 해당하는 원소는 스칼라 값이 아닌 1차원 배열

    In [58]: arr2d = np.array([[1, 2, 3,], [4, 5, 6], [7, 8 ,9]])
    
    In [59]: arr2d[2]
    Out[59]: array([7, 8, 9])

     

    쉼표로 구분된 인덱스 리스트를 넘기면 됨, 다음 두 표현은 동일함

    In [60]: arr2d[0][2]
    Out[60]: 3
    
    In [61]: arr2d[0, 2]
    Out[61]: 3

     

    다차원 배열에서 마지막 색인을 생략하면 반환되는 객체는 상위 차원의 데이터를 모두 포함한 한 차원 낮은 ndarray 반환

    In [65]: arr3d = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])
    
    In [66]: arr3d
    Out[66]:
    array([[[ 1,  2,  3],
            [ 4,  5,  6]],
    
           [[ 7,  8,  9],
            [10, 11, 12]]])
    In [67]: arr3d[0]
    Out[67]:
    array([[1, 2, 3],
           [4, 5, 6]])

    arr3d[0]에는 스칼라 값과 배열 모두 할당 가능

    In [68]: old_values = arr3d[0].copy()
    
    In [69]: arr3d[0] = 42
    
    In [70]: arr3d
    Out[70]:
    array([[[42, 42, 42],
            [42, 42, 42]],
    
           [[ 7,  8,  9],
            [10, 11, 12]]])
    
    In [71]: arr3d[0] = old_values
    
    In [72]: arr3d
    Out[72]:
    array([[[ 1,  2,  3],
            [ 4,  5,  6]],
    
           [[ 7,  8,  9],
            [10, 11, 12]]])

     

    arr3d[1, 0]은 (1, 0)으로 색인되는 1차원 배열과 그 값 반환

    In [73]: arr3d[1, 0]
    Out[73]: array([7, 8, 9])
    In [74]: x = arr3d[1]
    
    In [75]: x
    Out[75]:
    array([[ 7,  8,  9],
           [10, 11, 12]])
    
    In [76]: x[0]
    Out[76]: array([7, 8, 9])

    두 번에 걸쳐 인덱싱한 결과와 동일

     

    슬라이스로 선택

    · 1차원

    In [77]: arr
    Out[77]: array([ 0,  1,  2,  3,  4, 64, 64, 64,  8,  9])
    
    In [78]: arr[1:6]
    Out[78]: array([ 1,  2,  3,  4, 64])

     

    · 2차원

    In [79]: arr2d
    Out[79]:
    array([[1, 2, 3],
           [4, 5, 6],
           [7, 8, 9]])
    
    In [80]: arr2d[:2]
    Out[80]:
    array([[1, 2, 3],
           [4, 5, 6]])
    In [81]: arr2d[:2, 1:]
    Out[81]:
    array([[2, 3],
           [5, 6]])

    하나만 쓰면 행, 콤마로 구분하면 행, 열

     

    두 번째 행의 처음 두 열만 선택하고 싶은 경우

    In [82]: lower_dim_slice = arr2d[1, :2]
    
    In [83]: lower_dim_slice
    Out[83]: array([4, 5])
    
    In [84]: lower_dim_slice.shape
    Out[84]: (2,)

    arr2d는 2차원 배열이지만 lower_dim_slice는 1차원, 축 크기가 하나인 튜플 모양

     

    처음 두 행에서 세 번째 열만 선택하고 싶은 경우

    In [85]: arr2d[:2, 2]
    Out[85]: array([3, 6])

     

    열만 사용하면 전체 축을 선택한다는 의미이므로 원래 차원의 슬라이스를 얻게 됨

    In [86]: arr2d[:, :1]
    Out[86]:
    array([[1],
           [4],
           [7]])

     

    슬라이싱 구문에 값을 대입하면 선택 영역 전체에 값이 할당됨

    In [87]: arr2d[:2, 1:] = 0
    
    In [88]: arr2d
    Out[88]:
    array([[1, 0, 0],
           [4, 0, 0],
           [7, 8, 9]])

     

    불리언 값으로 선택

    In [89]: names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
    
    In [90]: data = np.array([[4, 7], [0, 2], [-5, 6], [0, 0], [1, 2], [-12, -4], [3, 4]])
    
    In [91]: names
    Out[91]: array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'], dtype='<U4')
    
    In [92]: data
    Out[92]:
    array([[  4,   7],
           [  0,   2],
           [ -5,   6],
           [  0,   0],
           [  1,   2],
           [-12,  -4],
           [  3,   4]])

    데이터를 가진 배열이 있고 이름이 중복된 배열이 있을 때, 각 이름은 data 배열의 각 행에 대응한다고 가정

    'Bob'과 동일한 이름을 선택하려면 산술 연산과 마찬가지로 배열에 대한 비교 연산(== 같은)도 벡터화

    > names를 'Bob' 문자열과 비교하면 불리언 배열이 반환됨

    In [93]: names == 'Bob'
    Out[93]: array([ True, False, False,  True, False, False, False])

    불리언 배열은 배열의 색인으로 사용할 수 있음

    In [94]: data[names == 'Bob']
    Out[94]:
    array([[4, 7],
           [0, 0]])

    불리언 배열은 반드시 색인하려는 축의 길이와 길이가 동일해야 함

    불리언 배열을 슬라이스나 정수를 선택하는 데 짜 맞출 수도 있음

    In [95]: data[[ True, False, False,  True, False, False, False]]
    Out[95]:
    array([[4, 7],
           [0, 0]])
    
    In [96]: data[names == 'Bob', 1:]
    Out[96]:
    array([[7],
           [0]])
    
    In [97]: data[names == 'Bob', 1]
    Out[97]: array([7, 0])

    색인과 열을 함께 선택

     

    'Bob'이 아닌 항목을 모두 선택하려면 != 연산자를 사용하거나 ~(부정)를 사용해 조건부를 부인

    In [98]: names != 'Bob'
    Out[98]: array([False,  True,  True, False,  True,  True,  True])
    
    In [99]: ~(names == 'Bob')
    Out[99]: array([False,  True,  True, False,  True,  True,  True])
    
    In [100]: data[~(names == 'Bob')]
    Out[100]:
    array([[  0,   2],
           [ -5,   6],
           [  1,   2],
           [-12,  -4],
           [  3,   4]])

     

    ~ 연산자는 변수가 참조하는 불리언 배열을 뒤집고 싶을 때 유용함

    In [101]: cond = names == 'Bob'
    
    In [102]: data[~cond]
    Out[102]:
    array([[  0,   2],
           [ -5,   6],
           [  1,   2],
           [-12,  -4],
           [  3,   4]])

     

    세 가지 이름 중 두 가지 이름을 선택하려면 &(and)와 |(or) 같은 논리 연산자를 사용해 여러 개의 불리언 조건을 사용

    In [103]: mask = (names == 'Bob') | (names == 'Will')
    
    In [104]:
    
    In [104]: mask
    Out[104]: array([ True, False,  True,  True,  True, False, False])
    
    In [105]: data[mask]
    Out[105]:
    array([[ 4,  7],
           [-5,  6],
           [ 0,  0],
           [ 1,  2]])

    > 파이썬 예약어인 and와 or는 사용할 수 없음(&와 |로 사용)

    불리언 색인을 이용해 배열의 데이터를 선택하면 반환되는 배열의 내용이 바뀌지 않더라도 항상 복사 데이터가 발생

     

    불리언 배열에 값을 대입하면 오른쪽에 있는 값을 불리언 배열의 값이 True인 위치로 대체해 작동

    data에 저장된 모든 음수를 0으로 대입하고 싶은 경우

    In [106]: data[data < 0] = 0
    
    In [107]: data
    Out[107]:
    array([[4, 7],
           [0, 2],
           [0, 6],
           [0, 0],
           [1, 2],
           [0, 0],
           [3, 4]])

    1차원 불리언 배열을 사용해 전체 행이나 열의 값을 대입할 수 있음

     

    In [108]: data[names != 'Joe'] = 7
    
    In [109]: data
    Out[109]:
    array([[7, 7],
           [0, 2],
           [7, 7],
           [7, 7],
           [7, 7],
           [0, 0],
           [3, 4]])

    아직 배우지 않았지만 2차원 데이터의 이러한 연산은 판다스에서 처리하는 게 편리함

     

    팬시 색인

    fancy indexing은 정수 배열을 사용한 색인을 설명하기 위해 넘파이에서 차용한 단어, 8 x 4 배열이 있다고 가정

    In [111]: arr = np.zeros((8, 4))
    
    In [112]: for i in range(8):
         ...:     arr[i] = i
         ...:
    
    In [113]: arr
    Out[113]:
    array([[0., 0., 0., 0.],
           [1., 1., 1., 1.],
           [2., 2., 2., 2.],
           [3., 3., 3., 3.],
           [4., 4., 4., 4.],
           [5., 5., 5., 5.],
           [6., 6., 6., 6.],
           [7., 7., 7., 7.]])

    특정한 순서로 행의 하위집합을 선택하고 싶다면 원하는 순서가 명시된 정수가 담긴 ndarray나 리스트를 넘기면 됨

    In [114]: arr[[4, 3, 0, 6]]
    Out[114]:
    array([[4., 4., 4., 4.],
           [3., 3., 3., 3.],
           [0., 0., 0., 0.],
           [6., 6., 6., 6.]])
    
    In [115]: # 4번 인덱스, 3번 인덱스, 0번 인덱스, 6번 인덱스를 보여 준 것
    
    In [116]: arr[[-3, -5, -7]]
    Out[116]:
    array([[5., 5., 5., 5.],
           [3., 3., 3., 3.],
           [1., 1., 1., 1.]])

    색인으로 음수를 사용하면 끝에서부터 행 선택

    다차원 색인 배열을 넘기면 조금 다르게 작동하는데, 각 색인 튜플에 대응하는 1차원 배열이 선택됨

    In [120]: arr = np.arange(8)
    
    In [121]: arr
    Out[121]: array([0, 1, 2, 3, 4, 5, 6, 7])
    
    In [122]: arr.reshape( (2, 4) )
    Out[122]:
    array([[0, 1, 2, 3],
           [4, 5, 6, 7]])

     

    In [117]: arr = np.arange(32).reshape((8, 4))
    
    In [118]: arr
    Out[118]:
    array([[ 0,  1,  2,  3],
           [ 4,  5,  6,  7],
           [ 8,  9, 10, 11],
           [12, 13, 14, 15],
           [16, 17, 18, 19],
           [20, 21, 22, 23],
           [24, 25, 26, 27],
           [28, 29, 30, 31]])
    
    In [119]: arr[[1, 5, 7, 2], [0, 3, 1, 2]]
    Out[119]: array([ 4, 23, 29, 10])

    (1, 0), (5, 3), (7, 1), (2, 2)에 대응하는 원소가 선택됨, 배열이 몇 차원이든 팬시 색인의 결과는 항상 1차원

    위 예제에서 행과 열에 대응하는 사각형 모양의 값이 선택되기를 기대했음

    > 예상했던 것처럼 만들기 위해서는 아래와 같이

    In [125]: arr[[1, 5, 7, 2]][:, [0, 3, 1, 2]]
    Out[125]:
    array([[ 4,  7,  5,  6],
           [20, 23, 21, 22],
           [28, 31, 29, 30],
           [ 8, 11,  9, 10]])

    팬시 색인은 슬라이싱과 달리 선택된 데이터를 새로운 배열로 복사, 팬시 색인으로 값을 대입하면 색인된 값이 변경됨

     

    배열 전치와 축 바꾸기

    transpose는 데이터를 복사하지 않고 데이터의 모양이 바뀐 뷰를 반환

    ndarray는 transpose 메서드와 T라는 이름의 특수한 속성을 가짐

    In [126]: arr = np.arange(15).reshape((3, 5))
    
    In [127]: arr
    Out[127]:
    array([[ 0,  1,  2,  3,  4],
           [ 5,  6,  7,  8,  9],
           [10, 11, 12, 13, 14]])
    
    In [128]: arr.T
    Out[128]:
    array([[ 0,  5, 10],
           [ 1,  6, 11],
           [ 2,  7, 12],
           [ 3,  8, 13],
           [ 4,  9, 14]])

    행렬을 계산할 때 자주 사용하게 될 행렬의 내적은 numpy.dot을 이용해 구함

    In [129]: arr = np.array([[0, 1, 0], [1, 2, -2], [6, 3, 2], [-1, 0, 1], [1, 0, 1]])
    
    In [130]: arr
    Out[130]:
    array([[ 0,  1,  0],
           [ 1,  2, -2],
           [ 6,  3,  2],
           [-1,  0,  1],
           [ 1,  0,  1]])
    
    In [131]: np.dot(arr.T, arr)
    Out[131]:
    array([[39, 20, 10],
           [20, 14,  2],
           [10,  2, 10]])

     

    @ 연산자는 행렬 곱셈을 수행하는 또 다른 방법

    In [132]: arr.T @ arr
    Out[132]:
    array([[39, 20, 10],
           [20, 14,  2],
           [10,  2, 10]])

     

    .T 속성을 이용하는 간단한 전치는 축을 뒤바꾸는 경우

    ndarray에는 swapaxes 메서드를 통해 두 개의 축 번호를 받아 배열을 뒤바꿈

    In [133]: arr
    Out[133]:
    array([[ 0,  1,  0],
           [ 1,  2, -2],
           [ 6,  3,  2],
           [-1,  0,  1],
           [ 1,  0,  1]])
    
    In [134]: arr.swapaxes(0, 1)
    Out[134]:
    array([[ 0,  1,  6, -1,  1],
           [ 1,  2,  3,  0,  0],
           [ 0, -2,  2,  1,  1]])
           
    In [135]: arr
    Out[135]:
    array([[ 0,  1,  0],
           [ 1,  2, -2],
           [ 6,  3,  2],
           [-1,  0,  1],
           [ 1,  0,  1]])
    
    In [136]: arr.swapaxes(1, 0)
    Out[136]:
    array([[ 0,  1,  6, -1,  1],
           [ 1,  2,  3,  0,  0],
           [ 0, -2,  2,  1,  1]])

    swapaxes도 데이터를 복사하지 않고 원래 데이터에 대한 뷰 반환

     

    난수 생성

    numpy.random 모듈은 파이썬 내장 random 모듈을 보강해 다양한 종류의 확률분포로부터 효과적으로 표본값을 생성하는 데 주로 사용

    np.random.standard_normal을 사용해 표준정규분포로부터 4x4 크기의 표본을 생성할 수 있음

    In [137]: samples = np.random.standard_normal(size=(4, 4))
    
    In [138]: samples
    Out[138]:
    array([[-1.10056782, -0.44984276,  0.94099913, -1.36902046],
           [ 0.40525169,  0.22437509, -0.01989877,  1.17990826],
           [ 0.81066203,  0.34056214,  0.57766456, -0.35713516],
           [-0.85313631, -0.79710324,  0.51674758, -1.5303429 ]])

    파이썬 내장 random 모듈은 한 번에 하나의 값만 생성

    numpy.random 모듈은 매우 큰 표본을 생성하지만 파이썬 내장 모듈보다 수십 배 이상 빠름

    In [139]: from random import normalvariate
    
    In [140]: N = 1_000_000
    
    In [141]: %timeit samples = [normalvariate(0, 1) for _ in range(N)]
    681 ms ± 1.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    
    In [142]: %timeit np.random.standard_normal(N)
    19.5 ms ± 97.1 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

    이러한 난수는 유사난수(pseudorandom)라고 부름

    > 난수 생성기의 시드(seed) 값에 따라 정해진 난수를 알고리즘으로 생성

    In [143]: rng = np.random.default_rng(seed=12345)
    
    In [144]: data = rng.standard_normal((2, 3))
    
    In [145]: type(rng)
    Out[145]: numpy.random._generator.Generator

     

    seed 인수는 난수 생성기의 초기 상태를 결정하며 rng 객체가 데이터를 생성할 때마다 상태가 변경됨

    생성기 객체인 rng는 numpy.random 모듈을 사용할 수 있는 다른 코드와도 분리되어 있음

     

    유니버설 함수: 배열의 각 원소를 빠르게 처리하는 함수

    ufunc라고도 부르는 universal 함수는 ndarray 안의 데이터 원소별로 연산을 수행하는 함수

    하나 이상의 스칼라 값을 받아, 하나 이상의 스칼라 결괏값을 반환하는 간단한 함수를 빠르게 수행하는 벡터화된 래퍼 함수라고 생각하면 됨

     

    많은 ufunc는 numpy.sqrt(제곱근)나 numpy.exp(지수) 같은 간단한 변형을 전체 원소 적용 가능

    In [146]: arr = np.arange(10)
    
    In [147]: arr
    Out[147]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    
    In [148]: np.sqrt(arr)
    Out[148]:
    array([0.        , 1.        , 1.41421356, 1.73205081, 2.        ,
           2.23606798, 2.44948974, 2.64575131, 2.82842712, 3.        ])
    
    In [149]: np.exp(arr)
    Out[149]:
    array([1.00000000e+00, 2.71828183e+00, 7.38905610e+00, 2.00855369e+01,
           5.45981500e+01, 1.48413159e+02, 4.03428793e+02, 1.09663316e+03,
           2.98095799e+03, 8.10308393e+03])

    위 함수들은 단항(unary) 유니버설 함수

    numpy.add나 numpy.maximum처럼 2개의 매개변수를 취해서 단일 배열을 반환하는 함수는 이항(binary) 유니버설 함수

    In [150]: x = rng.standard_normal(8)
    
    In [151]: y = rng.standard_normal(8)
    
    In [152]: x
    Out[152]:
    array([-1.3677927 ,  0.6488928 ,  0.36105811, -1.95286306,  2.34740965,
            0.96849691, -0.75938718,  0.90219827])
    
    In [153]: y
    Out[153]:
    array([-0.46695317, -0.06068952,  0.78884434, -1.25666813,  0.57585751,
            1.39897899,  1.32229806, -0.29969852])
    
    In [154]: np.maximum(x, y)
    Out[154]:
    array([-0.46695317,  0.6488928 ,  0.78884434, -1.25666813,  2.34740965,
            1.39897899,  1.32229806,  0.90219827])

    예제에서 numpy.maximum은 x와 y의 원소별로 가장 큰 값을 계산

     

    흔한 경우는 아니지만 여러 개의 배열을 반환하는 유니버설 함수도 있음

    > numpy.modf는 파이썬 내장 함수인 math.modf의 벡터화 버전, 분수를 받아 몫과 나머지를 함께 반환

    In [155]: arr = rng.standard_normal(7) * 5
    
    In [156]: arr
    Out[156]:
    array([ 4.51459671, -8.10791367, -0.7909463 ,  2.24741966, -6.71800536,
           -0.40843795,  8.62369966])
    
    In [157]: remainder, whole_part = np.modf(arr)
    
    In [158]: remainder
    Out[158]:
    array([ 0.51459671, -0.10791367, -0.7909463 ,  0.24741966, -0.71800536,
           -0.40843795,  0.62369966])
    
    In [159]: whole_part
    Out[159]: array([ 4., -8., -0.,  2., -6., -0.,  8.])

    유니버설 함수는 선택적으로 out 인수를 사용해 계산 결과를 새로운 결과로 만들지 않고 기존 배열에 할당할 수도 있음

    In [160]: arr
    Out[160]:
    array([ 4.51459671, -8.10791367, -0.7909463 ,  2.24741966, -6.71800536,
           -0.40843795,  8.62369966])
    
    In [161]: out = np.zeros_like(arr)
    
    In [162]: np.add(arr, 1)
    Out[162]:
    array([ 5.51459671, -7.10791367,  0.2090537 ,  3.24741966, -5.71800536,
            0.59156205,  9.62369966])
    
    In [163]: np.add(arr, 1, out=out)
    Out[163]:
    array([ 5.51459671, -7.10791367,  0.2090537 ,  3.24741966, -5.71800536,
            0.59156205,  9.62369966])
    
    In [164]: out
    Out[164]:
    array([ 5.51459671, -7.10791367,  0.2090537 ,  3.24741966, -5.71800536,
            0.59156205,  9.62369966])

     

    새로운 유니버설 함수는 계속 추가되므로 넘파이 온라인 문서를 참조해 최신 목록 확인할 수 있음

     

    배열을 이용한 배열 기반 프로그래밍

    넘파이 배열을 사용하면 반복문을 작성하지 않고 간결한 배열 연산을 통해 많은 종류의 데이터 처리 작업을 할 수 있음

    배열 연산을 사용해 반복문을 명시적으로 제거하는 기법: 벡터화

     

    값이 놓여 있는 그리드에서 sqrt(x^2 + y^2) 계산

    numpy.meshgrid 함수는 두 개의 1차원 배열을 받아 모든 (x, y) 짝을 만들 수 있는 2차원 배열 두 개를 반환

    In [168]: points = np.arange(-5, 5, 0.01)    # -5부터 4.99까지 0.01씩 증가하는 값의 배열
    
    In [169]: xs, ys = np.meshgrid(points, points)
    
    In [170]: ys
    Out[170]:
    array([[-5.  , -5.  , -5.  , ..., -5.  , -5.  , -5.  ],
           [-4.99, -4.99, -4.99, ..., -4.99, -4.99, -4.99],
           [-4.98, -4.98, -4.98, ..., -4.98, -4.98, -4.98],
           ...,
           [ 4.97,  4.97,  4.97, ...,  4.97,  4.97,  4.97],
           [ 4.98,  4.98,  4.98, ...,  4.98,  4.98,  4.98],
           [ 4.99,  4.99,  4.99, ...,  4.99,  4.99,  4.99]])

     

    그리드상 두 점을 가지고 계산

    In [171]: z = np.sqrt(xs ** 2 + ys ** 2)
    
    In [172]: z
    Out[172]:
    array([[7.07106781, 7.06400028, 7.05693985, ..., 7.04988652, 7.05693985,
            7.06400028],
           [7.06400028, 7.05692568, 7.04985815, ..., 7.04279774, 7.04985815,
            7.05692568],
           [7.05693985, 7.04985815, 7.04278354, ..., 7.03571603, 7.04278354,
            7.04985815],
           ...,
           [7.04988652, 7.04279774, 7.03571603, ..., 7.0286414 , 7.03571603,
            7.04279774],
           [7.05693985, 7.04985815, 7.04278354, ..., 7.03571603, 7.04278354,
            7.04985815],
           [7.06400028, 7.05692568, 7.04985815, ..., 7.04279774, 7.04985815,
            7.05692568]])

     

    여기서 맷플롯립을 이용해 2차원 배열을 시각화할 수 있음

    In [173]: import matplotlib.pyplot as plt
    
    In [174]: plt.imshow(z, cmap=plt.cm.gray, extent=[-5, 5, -5, 5])
    Installed qt5 event loop hook.
    Out[174]: <matplotlib.image.AxesImage at 0x22d22a88690>
    
    In [175]: plt.colorbar()
    Out[175]: <matplotlib.colorbar.Colorbar at 0x22d22711c50>
    
    In [176]: plt.title('Image plot of $\sqrt{x^2 + y^2}$ for a grid of values')
    Out[176]: Text(0.5, 1.0, 'Image plot of $\\sqrt{x^2 + y^2}$ for a grid of values')

     

    In [177]: plt.show()
    # 입력하면 만들어진 결과물을 보여 줌

    계산된 값이 들어 있는 2차원 배열로부터 그래프 이미지를 생성하기 위해 맷플롯립의 imshow 함수를 사용한 결과물

     

     

    배열 연산으로 조건부 표현

    In [61]: xarr = np.array([1.1, 1.2, 1.3, 1.4, 1.5])
    
    In [62]: yarr = np.array([2.1, 2.2, 2.3, 2.4, 2.5])
    
    In [63]: cond = np.array([True, False, True, True, False])
    
    In [64]: result = [(x if c else y)
        ...:         for x, y, c in zip(xarr, yarr, cond)]
    
    In [65]: result
    Out[65]: [1.1, 2.2, 1.3, 1.4, 2.5]

    위는 파이썬으로 수행된 것으로 큰 배열을 빠르게 처리하지 못하고 다차원 배열에서는 사용할 수 없음

     

    np.where 사용해 간결하게 작성

    In [68]: result = np.where(cond, xarr, yarr)
    
    In [69]: result
    Out[69]: array([1.1, 2.2, 1.3, 1.4, 2.5])

     

    임의로 생성된 데이터가 있는 행렬이 있고 모든 양수는 2로, 모든 음수는 -2로 바꾸려면 numpy.where로 쉽게 처리

    In [70]: rng = np.random.default_rng(seed=12345)
    
    In [71]: arr = rng.standard_normal((4, 4))
    
    In [72]: arr
    Out[72]:
    array([[-1.42382504,  1.26372846, -0.87066174, -0.25917323],
           [-0.07534331, -0.74088465, -1.3677927 ,  0.6488928 ],
           [ 0.36105811, -1.95286306,  2.34740965,  0.96849691],
           [-0.75938718,  0.90219827, -0.46695317, -0.06068952]])
    
    In [73]: arr > 0
    Out[73]:
    array([[False,  True, False, False],
           [False, False, False,  True],
           [ True, False,  True,  True],
           [False,  True, False, False]])
    
    In [74]: np.where(arr > 0, 2, -2)
    Out[74]:
    array([[-2,  2, -2, -2],
           [-2, -2, -2,  2],
           [ 2, -2,  2,  2],
           [-2,  2, -2, -2]])

     

    numpy.where 사용할 때 스칼라 값과 배열을 조합할 수 있음 ex) arr의 모든 양수를 2로 바꾸기

    In [75]: np.where(arr > 0, 2, arr)    # 양수인 경우에만 값을 2로 설정
    Out[75]:
    array([[-1.42382504,  2.        , -0.87066174, -0.25917323],
           [-0.07534331, -0.74088465, -1.3677927 ,  2.        ],
           [ 2.        , -1.95286306,  2.        ,  2.        ],
           [-0.75938718,  2.        , -0.46695317, -0.06068952]])

     

    수학 메서드와 통계 메서드

     

    임의의 정규분포 데이터 생성, 집계

    In [76]: arr = rng.standard_normal((5, 4))
    
    In [77]: arr
    Out[77]:
    array([[ 0.78884434, -1.25666813,  0.57585751,  1.39897899],
           [ 1.32229806, -0.29969852,  0.90291934, -1.62158273],
           [-0.15818926,  0.44948393, -1.34360107, -0.08168759],
           [ 1.72473993,  2.61815943,  0.77736134,  0.8286332 ],
           [-0.95898831, -1.20938829, -1.41229201,  0.54154683]])
    
    In [78]: arr.mean()
    Out[78]: 0.17933634979615845
    
    In [79]: np.mean(arr)
    Out[79]: 0.17933634979615845
    
    In [80]: arr.sum()
    Out[80]: 3.586726995923169

    mean이나 sum 같은 함수는 선택적으로 axis 인수를 받아 해당 axis에 대한 통계를 계산, 한 차수 낮은 배열 반환

    In [81]: arr.mean(axis=1)
    Out[81]: array([ 0.37675318,  0.07598404, -0.2834985 ,  1.48722347, -0.75978045])
    
    In [82]: arr.sum(axis=0)
    Out[82]: array([ 2.71870476,  0.30188842, -0.49975489,  1.0658887 ])
    
    In [83]: arr
    Out[83]:
    array([[ 0.78884434, -1.25666813,  0.57585751,  1.39897899],
           [ 1.32229806, -0.29969852,  0.90291934, -1.62158273],
           [-0.15818926,  0.44948393, -1.34360107, -0.08168759],
           [ 1.72473993,  2.61815943,  0.77736134,  0.8286332 ],
           [-0.95898831, -1.20938829, -1.41229201,  0.54154683]])

    arr.mean(axis=1)은 모든 열의 평균을 구하라는 의미

    arr.sum(axis=0)은 행의 합을 구하라는 의미

    cumsum과 cumprod 메서드는 중간 계산값을 담고 있는 배열을 반환

    In [84]: arr = np.array([0, 1, 2, 3, 4, 5, 6, 7])
    
    In [85]: arr.cumsum()
    Out[85]: array([ 0,  1,  3,  6, 10, 15, 21, 28])

     

    다차원 배열에서 cumsum 같은 누적 함수는 크기가 동일한 배열을 반환, 축을 지정해 부분적으로 계산하면 낮은 차수의 슬라이스를 반환

    In [86]: arr = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
    
    In [87]: arr
    Out[87]:
    array([[0, 1, 2],
           [3, 4, 5],
           [6, 7, 8]])
           
    In [90]: arr.cumsum(axis=0)    # 행의 누적합
    Out[90]:
    array([[ 0,  1,  2],
           [ 3,  5,  7],
           [ 9, 12, 15]])
    
    In [91]: arr.cumsum(axis=1)    # 열의 누적합
    Out[91]:
    array([[ 0,  1,  3],
           [ 3,  7, 12],
           [ 6, 13, 21]])

     

    표준편차(std), 분산(var)

    argmin, argmax 최소 원소의 인덱스 값, 최대 원소의 인덱스 값

     

    불리언 배열을 위한 메서드

    In [92]: arr = rng.standard_normal(100)
    
    In [93]: (arr > 0).sum()    # 양수인 값의 개수
    Out[93]: 51
    
    In [94]: (arr <= 0).sum()    # 0 이하인 값의 개수
    Out[94]: 49
    
    In [95]: bools = np.array([False, False, True, False])
    
    In [97]: bools.any()    # 하나 이상의 True 값이 있는지 검사
    Out[97]: True
    
    In [98]: bools.all()    # 모든 원소가 True인지 검사
    Out[98]: False
    
    In [99]: arr
    Out[99]:
    array([ 7.51939396e-01, -6.58760320e-01, -1.22867499e+00,  2.57557768e-01,
            3.12902918e-01, -1.30811690e-01,  1.26998312e+00, -9.29624577e-02,
           -6.61508890e-02, -1.10821447e+00,  1.35956851e-01,  1.34707776e+00,
            6.11440210e-02,  7.09146003e-02,  4.33654537e-01,  2.77483660e-01,
            5.30252387e-01,  5.36720969e-01,  6.18350015e-01, -7.95017456e-01,
            3.00030946e-01, -1.60270159e+00,  2.66798830e-01, -1.26162378e+00,
           -7.12708062e-02,  4.74049730e-01, -4.14853761e-01,  9.77165000e-02,
           -1.64041784e+00, -8.57258824e-01,  6.88281788e-01, -1.15452958e+00,
            6.50452389e-01, -1.38835995e+00, -9.07382457e-01, -1.09542531e+00,
            7.14569494e-03,  5.34359903e-01, -1.06580785e+00, -1.81472740e-01,
            1.62195180e+00, -3.17391946e-01, -8.15814967e-01,  3.86579017e-01,
           -2.23638926e-01, -7.01690809e-01, -1.79571318e+00,  8.18325622e-01,
           -5.71032902e-01,  7.85525063e-04, -1.06364272e+00,  1.30171450e+00,
            7.47872942e-01,  9.80875909e-01, -1.10418688e-01,  4.67918531e-01,
            8.90607150e-01,  1.02300937e+00,  3.12383389e-01, -6.19046857e-02,
           -3.59479647e-01, -7.48643984e-01, -9.65478907e-01,  3.60034657e-01,
           -2.44552532e-01, -1.99585661e+00, -1.55247617e-01,  1.06383087e+00,
           -2.75171567e-01, -1.85333593e+00, -1.24341928e-01,  7.84974522e-01,
            2.01998597e-01, -4.28074443e-01,  1.84828890e+00,  1.89995289e+00,
           -9.84250348e-02,  8.13445440e-01,  3.92494389e-01,  7.81442900e-01,
            1.45327152e+00,  8.20186045e-01,  8.77053446e-02, -6.53505648e-01,
           -8.11886879e-01, -2.55381724e-02,  1.15818454e+00,  3.00520870e-01,
            5.30566461e-02,  2.57271524e-01,  3.57428624e-02,  5.47236686e-01,
           -1.12296158e+00, -1.97524767e+00, -4.25150050e-01, -1.14907382e+00,
            1.61513805e+00, -1.58476860e-01, -2.52873345e-01, -1.53815403e+00])
    
    In [100]: arr[0]
    Out[100]: 0.7519393955576869
    
    In [101]: arr[ arr > 0 ]
    Out[101]:
    array([7.51939396e-01, 2.57557768e-01, 3.12902918e-01, 1.26998312e+00,
           1.35956851e-01, 1.34707776e+00, 6.11440210e-02, 7.09146003e-02,
           4.33654537e-01, 2.77483660e-01, 5.30252387e-01, 5.36720969e-01,
           6.18350015e-01, 3.00030946e-01, 2.66798830e-01, 4.74049730e-01,
           9.77165000e-02, 6.88281788e-01, 6.50452389e-01, 7.14569494e-03,
           5.34359903e-01, 1.62195180e+00, 3.86579017e-01, 8.18325622e-01,
           7.85525063e-04, 1.30171450e+00, 7.47872942e-01, 9.80875909e-01,
           4.67918531e-01, 8.90607150e-01, 1.02300937e+00, 3.12383389e-01,
           3.60034657e-01, 1.06383087e+00, 7.84974522e-01, 2.01998597e-01,
           1.84828890e+00, 1.89995289e+00, 8.13445440e-01, 3.92494389e-01,
           7.81442900e-01, 1.45327152e+00, 8.20186045e-01, 8.77053446e-02,
           1.15818454e+00, 3.00520870e-01, 5.30566461e-02, 2.57271524e-01,
           3.57428624e-02, 5.47236686e-01, 1.61513805e+00])
    
    In [102]: arr[ arr > 0 ].sum()    # 양수 합계
    Out[102]: 32.64957479778181

    불리언 배열에서 sum 메서드를 실행하면 True(1)인 원소의 개수 반환

     

    정렬

    파이썬 내장 리스트 자료형처럼 sort 메서드 이용해 정렬

    다차원 배열을 정렬할 때는 sort 메서드에 넘긴 축의 값에 따라 1차원 부분을 정렬

    In [103]: arr = rng.standard_normal(6)
    
    In [104]: arr
    Out[104]:
    array([ 0.28208603, -0.62361213,  1.12182226,  0.84122103, -0.7758961 ,
            0.41071644])
    
    In [105]: arr.sort()
    
    In [106]: arr
    Out[106]:
    array([-0.7758961 , -0.62361213,  0.28208603,  0.41071644,  0.84122103,
            1.12182226])
    
    In [107]: arr = rng.standard_normal((5, 3))
    
    In [108]: arr
    Out[108]:
    array([[-2.7224161 , -0.6733048 ,  1.24622153],
           [ 0.79020803,  0.17534089, -0.0292946 ],
           [-1.41951426, -1.35996632,  0.22341156],
           [ 1.76177943, -2.17088985,  0.62848817],
           [ 0.60119653,  0.95075786, -0.86924667]])
    
    In [109]: arr.sort(axis=0)    # 행단위로(열의 값) 정렬
    
    In [110]: arr
    Out[110]:
    array([[-2.7224161 , -2.17088985, -0.86924667],
           [-1.41951426, -1.35996632, -0.0292946 ],
           [ 0.60119653, -0.6733048 ,  0.22341156],
           [ 0.79020803,  0.17534089,  0.62848817],
           [ 1.76177943,  0.95075786,  1.24622153]])
    
    In [111]: arr.sort(axis=1)    # 행단위로(행의 값) 정렬
    
    In [112]: arr
    Out[112]:
    array([[-2.7224161 , -2.17088985, -0.86924667],
           [-1.41951426, -1.35996632, -0.0292946 ],
           [-0.6733048 ,  0.22341156,  0.60119653],
           [ 0.17534089,  0.62848817,  0.79020803],
           [ 0.95075786,  1.24622153,  1.76177943]])

     

    numpy.sort 메서드는 배열을 직접 변경하지 않고 정렬된 결과를 가지고 있는 복사본을 반환(파이썬 내장 함수 sorted와 유사)

    In [113]: arr2 = np.array( [5, -10, 7, 1, 0, -3] )
    
    In [114]: sorted_arr2 = np.sort(arr2)
    
    In [115]: sorted_arr2
    Out[115]: array([-10,  -3,   0,   1,   5,   7])
    
    In [116]: arr2
    Out[116]: array([  5, -10,   7,   1,   0,  -3])

     

    집합 관련 함수

    가장 자주 사용하는 함수는 배열 내 중복된 원소를 제거하고 남은 원소를 정렬된 형태로 반환하는 numpy.unique

    In [117]: names = np.array( ['Bob', 'Will', 'Joe', 'Bob', 'Will', 'Joe', 'Joe'] )
    
    In [118]: np.unique(names)
    Out[118]: array(['Bob', 'Joe', 'Will'], dtype='<U4')
    
    In [119]: ints = np.array( [3, 3, 3, 2, 2, 1, 1, 4, 4] )
    
    In [120]: np.unique(ints)
    Out[120]: array([1, 2, 3, 4])
    
    # numpy.unique를 순수 파이썬으로 구현
    In [121]: sorted(set(names))
    Out[121]: ['Bob', 'Joe', 'Will']

     

    numpy.in1d는 인수로 받은 배열의 원소가 기존 배열에 포함되는지 검사 후 불리언 배열로 반환

    In [122]: values = np.array([6, 0, 0, 3, 2, 5, 6])
    
    In [123]: np.in1d(values, [2, 3, 6])
    Out[123]: array([ True, False, False,  True,  True, False,  True])
    
    In [124]: 'cat' in ['cat', 'dog', 'fish']
    Out[124]: True
    
    In [125]: 'turtle' in ['cat', 'dog', 'fish']
    Out[125]: False

     

    배열 데이터의 파일 입출력

     

    넘파이는 디스크에서 텍스트나 바이너리 형식의 데이터를 불러오거나 저장 가능

    numpy.save와 numpy.load는 배열 데이터를 효과적으로 디스크에 저장하고 불러오는 함수

    배열은 기본적으로 압축되지 않은 원시(raw) 바이너리 형식의 .npy 파일로 저장됨

    In [126]: arr = np.arange(10)
    
    In [127]: np.save('some_array', arr)
    
    In [128]: np.load('some_array.npy')
    Out[128]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    
    # numpy.savez 함수를 이용하면 여러 개의 배열을 압축된 형식으로 저장
    # .npz 파일을 불러올 때는 각 배열을 필요할 때 불러올 수 있도록 딕셔너리 형식의 객체에 저장
    In [129]: np.savez('array_achive.npz', a=arr, b=arr)
    
    In [130]: arch = np.load('array_achive.npz')
    
    In [131]: arch['b']
    Out[131]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    
    In [132]: !dir *.npy
     C 드라이브의 볼륨에는 이름이 없습니다.
     볼륨 일련 번호: 52A9-0E40
    
     C:\Users\mit012 디렉터리
    
    2023-11-13  오후 03:27               168 some_array.npy
                   1개 파일                 168 바이트
                   0개 디렉터리  163,741,851,648 바이트 남음
    
    In [133]: !dir *.npz
     C 드라이브의 볼륨에는 이름이 없습니다.
     볼륨 일련 번호: 52A9-0E40
    
     C:\Users\mit012 디렉터리
    
    2023-11-13  오후 03:28               570 array_achive.npz
                   1개 파일                 570 바이트
                   0개 디렉터리  163,734,691,840 바이트 남음
    
    In [134]: np.savez_compressed('savez_compressed.npz', a=arr, b=arr)
    
    In [135]: !dir *.npz
     C 드라이브의 볼륨에는 이름이 없습니다.
     볼륨 일련 번호: 52A9-0E40
    
     C:\Users\mit012 디렉터리
    
    2023-11-13  오후 03:28               570 array_achive.npz
    2023-11-13  오후 03:38               422 savez_compressed.npz
                   2개 파일                 992 바이트
                   0개 디렉터리  163,734,319,104 바이트 남음

     

    선형대수

     

    행렬의 곱셈, 분할, 행렬식, 정사각행렬 계산 같은 선형대수는 배열을 다루는 라이브러리에서 매우 중요한 부분

    두 개의 2차원 배열을 * 연산자로 곱하면 행렬 곱셈이 아니라 대응하는 각각의 원소의 곱을 계산함

      > 배열 메서드이자 넘파이 네임스페이스 안에 있는 함수인 dot 함수를 이용해 행렬 곱셈을 계산

    2 x 3 · 3 x 2 = 2 x 2 행렬이 만들어짐

    3 · 3 부분 숫자가 같아야 곱셈 가능

    In [137]: x = np.array([[1., 2., 3.], [4., 5., 6.]])
    
    In [138]: y = np.array([[6., 23.], [-1, 7], [8, 9]])
    
    In [139]: x
    Out[139]:
    array([[1., 2., 3.],
           [4., 5., 6.]])
    
    In [140]: y
    Out[140]:
    array([[ 6., 23.],
           [-1.,  7.],
           [ 8.,  9.]])
    
    In [141]: x.dot(y)
    Out[141]:
    array([[ 28.,  64.],
           [ 67., 181.]])
    
    # x.dot(y)는 np.dot(x, y)와 동일
    In [142]: np.dot(x, y)
    Out[142]:
    array([[ 28.,  64.],
           [ 67., 181.]])
           
    # 2차원 배열과 곱셈이 가능한 크기의 1차원 배열 간 행렬 곱셈의 결과는 1차원 배열
    In [143]: x @ np.ones(3)
    Out[143]: array([ 6., 15.])

     

    numpy.linalg는 행렬의 분할과 역행렬, 행렬식과 같은 것 포함

    In [144]: from numpy.linalg import inv, qr
    
    In [145]: X = rng.standard_normal((5, 5))
    
    In [146]: mat = X.T @ X
    
    In [147]: inv(mat)
    Out[147]:
    array([[  5.4889367 ,  -0.36203745, -16.48678147,   8.5407395 ,
              3.45686547],
           [ -0.36203745,   0.25782226,   0.90291623,  -0.48032056,
             -0.2952435 ],
           [-16.48678147,   0.90291623,  54.69042898, -29.03301456,
            -11.17941712],
           [  8.5407395 ,  -0.48032056, -29.03301456,  16.15095756,
              5.64502033],
           [  3.45686547,  -0.2952435 , -11.17941712,   5.64502033,
              2.90570988]])
    
    In [148]: mat @ inv(mat)
    Out[148]:
    array([[ 1.00000000e+00, -1.40340904e-16, -2.34306920e-15,
             4.82468734e-16,  6.14918856e-17],
           [ 1.49809615e-15,  1.00000000e+00, -1.71149163e-15,
            -1.81956084e-15,  5.90939285e-16],
           [ 3.51473853e-15, -1.78073320e-16,  1.00000000e+00,
             1.28871392e-15,  6.66243152e-17],
           [ 9.10356600e-16, -3.99623916e-17,  7.19878033e-15,
             1.00000000e+00, -9.63150387e-16],
           [ 1.96716158e-15, -1.16533381e-16, -2.58304590e-15,
             1.03113277e-15,  1.00000000e+00]])

    X.T.dot(X)는 X의 전치행렬(X.T)과 X의 곱 계산

    inv = 정사각 행렬의 역행렬 계산

     

    계단 오르내리기 예제

     

    배열 연산의 활용법을 보여 주는 애플리케이션

    계단의 중간에서 같은 확률로 한 계단 올라가거나(+1) 내려간다고(-1) 가정

     

    순수 파이썬으로 내장 random 모듈을 사용해 계단 오르내리기를 1,000번 수행하는 코드

    In [149]: #! 블록 시작
    
    In [150]: import random
    
    In [151]: position = 0
    
    In [152]: walk = [position]
    
    In [153]: nsteps = 1000
    
    In [154]: for _ in range(nsteps):
         ...:     step = 1 if random.randint(0, 1) else -1
         ...:     position += step
         ...:     walk.append(position)
         ...:
    
    In [155]: #! 블록 끝
    
    In [157]: import matplotlib.pyplot as plt
    
    In [158]: plt.plot(walk[:100])
    Installed qt5 event loop hook.
    Out[158]: [<matplotlib.lines.Line2D at 0x1df8ad4bdd0>]
    
    In [159]: plt.show()

    walk는 계단을 오르거나(+1) 내려간(-1) 값의 누적 합, 배열 표현식으로 나타낼 수 있음

    numpy.random 모듈을 사용해 한 번에 1,000번 수행한 결과를 저장, 누적 합 계산

    In [160]: nsteps = 1000
    
    In [161]: rng = np.random.default_rng(seed=12345)
    
    In [162]: draws = rng.integers(0, 2, size=nsteps)
    
    In [163]: steps = np.where(draws == 0, 1, -1)
    
    In [164]: walk = steps.cumsum()

     

    계단 오르내린 위치의 최솟값/최댓값 같은 간단한 통계 구할 수 있음

    In [181]: walk.min()
    Out[181]: -8
    
    In [182]: walk.max()
    Out[182]: 50

     

    'Python' 카테고리의 다른 글

    데이터 로딩과 저장, 파일 형식  (0) 2023.11.20
    판다스  (0) 2023.11.14
    파이썬 기초와 Ipython, jupyter notebook  (0) 2023.10.31
    웹의 데이터로 그래프 그리기  (0) 2023.10.31
    모듈과 패키지 만들기  (0) 2023.10.30
Designed by Tistory.