numpy ndarray 이해하기

numpy는 특히 벡터 및 행렬 연산에 있어 편의성을 제공하는 라이브러리로, 여러분들이 추후 많이 사용하게 될 pandas와 matplotlib의 기반이 되는 라이브러리라고 할 수 있습니다.

numpy에서는 기본적으로 array(어레이)라는 단위로 데이터를 관리하고, 이에 대한 연산을 수행하게 됩니다. 이 때 array는, 고등 수학에서 말하는 '행렬(matrix)'과 그 성격이 거의 유사하다고 보면 됩니다.

array 정의하기

array는 기본적으로 np.array() 함수를 사용하여 정의합니다. 이 때 Python 리스트 혹은 기존에 정의된 다른 array가 함수의 인자로 입력됩니다. array를 생성할 때 인자로 입력되는 Python 리스트는 1차원 혹은 그 이상의 차원을 가지는 것이 될 수 있습니다.

numpy에서 array를 생성하고 사용할 때는, 해당 array의 크기(혹은 모양)에 대한 정보를 항상 트래킹하고 있는 것이 중요합니다. array에 대하여 .shape 멤버 변수를 확인하면, 해당 array의 크기를 즉각적으로 확인할 수 있습니다.

한편, 몇몇 특수한 array의 경우 한 번의 함수 호출로 생성할 수 있습니다. 예를 들어 모든 성분이 0으로 되어 있는 array를 정의하고자 할 경우, np.zeros() 함수를 사용하면 한 방에 됩니다. 혹은 모든 성분이 1로 되어 있는 array를 정의하고자 할 경우 np.ones() 함수를 사용하면 됩니다.

그리고 np.arange() 함수는 일정한 차이를 갖는 연속적인 값의 성분들로 구성된 array를 생성할 때 사용합니다. 이는 Python에서의 range() 함수와 그 형태가 매우 유사합니다.

np.zeros(), np.ones(), np.arange() 등의 함수를 호출할 시, 생성할 array의 크기가 반드시 인자로 입력되어야 합니다.

array의 데이터형

모든 array는 고유한 데이터형을 가집니다. array를 정의할 때 사용자가 데이터형을 따로 명시하지 않은 경우, numpy에서는 가장 적절한 데이터형을 자동으로 인식하여 해당 array에 부여합니다.

여러분이 array를 정의할 때 데이터형을 직접 지정할 수도 있습니다. array의 데이터형을 지정하고자 할 경우, np.array() 함수를 호출할 시 아래의 예시와 같이 dtype 인자의 값을 명시하면 됩니다.

    arr = np.array(data, dtype=np.float64)

int나 float 뒤에 붙는 숫자는, 해당 array의 각 성분을 표현할 때 메모리 상에서 몇 비트를 사용할 것인지 나타내는 숫자입니다. 이 숫자가 더 클 수록, 해당 array 상에서 길이가 더 긴 정수 혹은 실수를 사용할 수 있습니다. array의 데이터형을 간단하게 나타내기 위해, numpy에서는 'i', 'u', 'f', 'c' 등과 같이 축약된 코드를 사용하기도 합니다.

여러분들이 기존에 정의한 array의 데이터형을 변환할 수도 있는데, 이 때는 .astype() 함수를 사용하면 됩니다. .astype() 함수의 인자에, 변환할 데이터형을 명시해 줍니다.

numpy에서 사용 가능한 데이터형(dtype) 정리
데이터형 데이터형 코드 설명
int8, int16, int32, int64 i1, i2, i4, i8 부호가 있는 [8, 16, 32, 64]비트 정수
uint8, uint16, uint32, uint64 u1, u2, u4, u8 부호가 없는 [8, 16, 32, 64]비트 정수
float16, float32, float64, float128 f2, f4, f8, f16 [16, 32, 64, 128]비트 실수
complex64, complex128, complex256 c8, c16, c32 [64, 128, 256]비트 복소수
bool b 불리언 (True 또는 False)
object O Python 오브젝트 형
string_ S 문자열
unicode_ U 유니코드 문자열

array 관련 연산

두 array 간에는 더하기, 빼기, 곱하기, 나누기 등의 연산을 수행할 수 있습니다. '+', '-', '*', '/' 등과 같이 여러분이 일반 숫자에 대해 사용하는 연산자를 사용하면 됩니다.

이 떄, 이러한 더하기, 빼기, 곱하기, 나누기 등의 연산은 두 array 상의 동일한 위치의 성분끼리 이루어지게 됩니다. 그러므로, 당연히 두 array의 모양이 같아야 계산이 가능합니다.

하나의 array와 일반 숫자 간에 연산을 하는 것도 가능한데, 이 경우 해당 숫자와 array 내 모든 성분 간에 해당 연산이 이루어지게 됩니다.

array 인덱싱 이해하기

기본 인덱싱

numpy array의 꽃은 인덱싱(indexing)입니다. 기본적인 인덱싱은 Python 리스트와 매우 유사합니다. arr이라는 array가 있을 때, arr[5]와 같이 특정한 인덱스를 명시할 수도 있고, arr[5:8]과 같이 범위 형태의 인덱스를 명시할 수도 있습니다. arr[:]의 경우, 해당 array의 전체 성분을 모두 선택한 결과에 해당합니다.

이러한 인덱싱 방식은 2차원 array에 대해서도 아주 유사한 방식으로 적용됩니다. arr2d라는 2차원 array를 정의한 뒤, arr2d[2, :]를 실행하게 되면 arr2d에서 인덱스가 2에 해당하는 행(3행)의 모든 성분이 1차원 array의 형태로 얻어집니다. arr2d[:, 3]을 실행하면, arr2d에서 인덱스가 3에 해당하는 열(4열)의 모든 성분이 1차원 array의 형태로 얻어집니다.

2차원 array에서는 이렇게 두 개의 인덱스를 받을 수 있는데, ','를 기준으로 앞부분에는 '행'에 대한 인덱스가, 뒷부분에는 '열'에 대한 인덱스가 입력됩니다. arr2d[1:3, :] 혹은 arr2d[:, :2]와 같이, 행 또는 열에 범위 인덱스를 적용하여 여러 개의 행 혹은 열을 얻을 수도 있습니다.

한편 2차원 array에서 4행 3열에 위치한 하나의 성분을 얻고자 하는 경우, arr2d[3, 2]를 실행하면 됩니다. 인덱싱을 통해 선택한 성분에 새로운 값을 대입하는 경우에도, arr2d[:2, 1:3] = 0과 같이 하면 됩니다.

여러 가지 방법으로 인덱싱을 시도하면서, array에 대한 인덱싱 방식에 익숙해지시길 바랍니다.

불리언 인덱싱

array와 관련하여 중요하게 사용되는 또 다른 인덱싱 방식이 불리언 인덱싱입니다.

    names = np.array(["Charles", "Kilho", "Hayoung", "Charles", "Hayoung", "Kilho", "Kilho"])
    data = np.array([[ 0.57587275, -2.84040808,  0.70568712, -0.1836896 ],
                     [-0.59389702, -1.35370379,  2.28127544,  0.03784684],
                     [-0.28854954,  0.8904534 ,  0.18153112,  0.95281901],
                     [ 0.75912188, -1.88118767, -2.37445741, -0.5908499 ],
                     [ 1.7403012 ,  1.33138843,  1.20897442, -0.58004389],
                     [ 1.11585923,  1.02466538, -0.74409379, -1.55236176],
                     [-0.45921447,  2.53114818,  0.5029578 , -0.24088216]])

위와 같은 names array와 data array가 정의되었다고 가정합시다. names array 내 각각의 성분이, data array의 각 행에 순서대로 대응된다고 가정합시다. 이러한 상황에서 이름이 "Charles"인 사람의 행 데이터만을 추출하고 싶다고 할 때, names == "Charles"를 실행하면 다음과 같은 결과를 얻을 수 있습니다.

    names == "Charles"
    >>>    array([ True, False, False,  True, False, False, False], dtype=bool)

얻어진 array를 잘 보면, 값이 "Charles"인 성분의 위치에는 True가, 그 외의 위치에는 False가 들어가 있는 것을 확인할 수 있습니다. 이렇게 names == "Charles"와 같은 조건식 형태의 코드를 실행하였을 때 생성되는 불리언 array를 다른 말로 '마스크(mask)'라고 합니다.

이런 마스크는 다른 array를 인덱싱하는 데 사용할 수 있습니다. 예를 들어 data[names == "Charles", :]를 실행하면, 위에서 보인 마스크가 True에 해당하는 행만을 data array로부터 가져오게 되고, 이들만으로 구성된 array를 얻을 수 있습니다.

    data[names == "Charles", :]
    >>> array([[ 0.57587275, -2.84040808,  0.70568712, -0.1836896 ],
               [ 0.75912188, -1.88118767, -2.37445741, -0.5908499 ]])

이러한 조건을 여러 개 추가할 수도 있습니다. 예를 들어 값이 "Charles" 혹은 "Kilho"인 성분에 대응되는 행을 얻고 싶다면, 다음과 같이 하면 됩니다.

    data[(names == "Charles") | (names == "Kilho"), :]
    >>> array([[ 0.57587275, -2.84040808,  0.70568712, -0.1836896 ],
               [-0.59389702, -1.35370379,  2.28127544,  0.03784684],
               [ 0.75912188, -1.88118767, -2.37445741, -0.5908499 ],
               [ 1.11585923,  1.02466538, -0.74409379, -1.55236176],
               [-0.45921447,  2.53114818,  0.5029578 , -0.24088216]])

혹은 기존의 data array의 각 성분의 값을 기준으로 불리언 인덱싱을 수행할 수도 있습니다. 예를 들어 data[:, 3]을 실행하여 4열만의 값을 본다고 할 때, data[:, 3] < 0을 실행하여 4열 상에서 그 값이 0보다 작은 성분을 조사하면, 다음과 같은 마스크를 얻을 수 있습니다.

    data[:, 3] < 0
    >>> array([ True, False, False,  True,  True,  True,  True], dtype=bool)

data array의 4열의 값이 0보다 작은 행에 대해서는 행 내 모든 성분에 0을 대입하도록 해 봅시다. 다음과 같이 하면 됩니다.

    data[data[:, 3] < 0, :] = 0

불리언 인덱싱은 처음 접할 때 굉장히 헷갈릴 수 있기 때문에, 여러분들이 직접 다양한 array에 대하여 반복 연습해 보면서 제대로 익힐 필요가 있습니다.

array 관련 함수 사용하기

각 성분에 적용되는 함수

numpy에서는 array에 적용할 수 있는 다양한 함수를 제공합니다. 이들 중 array의 각 성분에 대하여 특정한 계산을 일괄적으로 수행하기 위한 함수들이 있습니다.

arr array가 정의되어 있다고 할 때, np.sqrt() 함수를 적용하면 arr array의 각 성분에 대해 제곱근을 일괄적으로 계산합니다. 반면 np.log10() 함수를 적용하면 각 성분에 대해 밑이 10인 상용로그 값을 계산합니다.

numpy에서 한 개의 array의 각 성분에 적용되는 함수 정리
함수 설명
abs 각 성분의 절댓값 계산
sqrt 각 성분의 제곱근 계산 (array ** 0.5 의 결과와 동일)
square 각 성분의 제곱 계산 (array ** 2 의 결과와 동일)
exp 각 성분을 무리수 e의 지수로 삼은 값을 계산
log, log10, log2 자연로그(밑이 e), 상용로그(밑이 10), 밑이 2인 로그를 계산
sign 각 성분의 부호 계산 (+인 경우 1, -인 경우 -1, 0인 경우 0)
ceil 각 성분의 소수 첫 번째 자리에서 올림한 값을 계산
floor 각 성분의 소수 첫 번째 자리에서 내림한 값을 계산
isnan 각 성분이 NaN(Not a Number)인 경우 True를, 그렇지 않은 경우 False를 반환
isinf 각 성분이 무한대(infinity)인 경우 True를, 그렇지 않은 경우 False를 반환
cos, cosh, sin, sinh, tan, tanh 각 성분에 대한 삼각함수 값을 계산

만약 크기가 같은 두 개의 array x, y가 아래와 같이 주어졌을 때, np.maximum() 함수를 적용하면 두 array를 동일한 위치의 성분끼리 비교하여 값이 더 큰 성분을 선택, 새로운 array의 형태로 제시합니다.

    x = np.array([ 0.97121145,  1.74277758,  0.17706708, -1.14078851,  1.02197222,
                     -0.75747493,  0.4994057 , -0.03462392])
    y = np.array([ 0.91984849,  1.98745872, -0.11232596,  1.47306221,  1.24527437,
                   -0.77047603,  0.30708743, -1.76476678])
    np.maximum(x, y)
    >>> array([ 0.97121145,  1.98745872,  0.17706708,  1.47306221,  1.24527437,
                   -0.75747493,  0.4994057 , -0.03462392])
numpy에서 두 개의 array의 각 성분에 적용되는 함수 정리
함수 설명
add 두 array에서 동일한 위치의 성분끼리 더한 값을 계산 (arr1 + arr2의 결과와 동일)
subtract 두 array에서 동일한 위치의 성분끼리 뺀 값을 계산 (arr1 - arr2의 결과와 동일)
multiply 두 array에서 동일한 위치의 성분끼리 곱한 값을 계산 (arr1 * arr2의 결과와 동일)
divide 두 array에서 동일한 위치의 성분끼리 나눈 값을 계산 (arr1 / arr2의 결과와 동일)
maximum 두 array에서 동일한 위치의 성분끼리 비교하여 둘 중 최댓값을 반환
minimum 두 array에서 동일한 위치의 성분끼리 비교하여 둘 중 최솟값을 반환

통계 함수

numpy에서는 array 내 전체 성분에 대한 통계량을 계산하는 함수 또한 제공합니다. 예를 들어 arr array가 정의되어 있을 때, .sum() 함수를 적용하면 arr array 내 모든 성분의 합을 계산하며, .mean() 함수를 적용하면 arr array 내 전체 성분에 대한 평균값을 계산합니다.

array에 대하여 합이나 평균 등을 계산할 때, 전체 성분 대신 '행 방향' 혹은 '열 방향'의 성분들에 대해서만 합 혹은 평균 등을 계산하도록 할 수 있습니다. 2차원 array에 대하여 .sum(axis=0)을 실행하면 '행 방향'으로 성분들의 합을 구하게 되어, 결과적으로 각 열의 합을 계산한 결과를 산출합니다. 반면 .sum(axis=1)을 실행하면 '열 방향'으로 성분들의 합을 구하게 되여, 곧 각 행의 합을 계산한 결과를 산출합니다.

numpy의 array에 적용되는 통계 함수 정리
함수 설명
sum 전체 성분의 합을 계산
mean 전체 성분의 평균을 계산
std, var 전체 성분의 표준편차, 분산을 계산
min, max 전체 성분의 최솟값, 최댓값을 계산
argmin, argmax 전체 성분의 최솟값, 최댓값이 위치한 인덱스를 반환
cumsum 맨 첫번째 성분부터 각 성분까지의 누적합을 계산 (0에서부터 계속 더해짐)
cumprod 맨 첫번째 성분부터 각 성분까지의 누적곱을 계산 (1에서부터 계속 곱해짐)

한편 이런 통계 함수들은, 특정 조건을 만족하는 array 내 성분의 개수가 몇 개인지 셀 때도 유용하게 사용될 수 있습니다. arr array에 대하여 arr > 0을 실행하여 마스크를 얻으면, 해당 마스크에 대하여 (arr > 0).sum()을 실행하여 마스크 내 True 값의 개수를 계산함으로써, 결과적으로 arr > 0이라는 조건을 만족하는 성분의 총 개수를 산출할 수 있습니다.

정렬 함수 및 기타 함수

데이터 상에서 TOP 30 혹은 상위 5%에 해당하는 값이 무엇인지 추출해야 하는 경우가 자주 있는데, 정렬 함수는 이와 같은 작업을 위해 필수적인 함수라고 할 수 있습니다.

    arr = array([-0.21082527, -0.0396508 , -0.75771892, -1.9260892 , -0.18137694,
                   -0.44223898,  0.32745569,  0.16834256])

위와 같은 arr array가 정의되어 있다고 할 때, np.sort() 함수를 적용하면 arr array의 성분이 오름차순으로(크기가 커지는 순서대로) 정렬됩니다.

    np.sort(arr)
    >>> array([-0.21082527, -0.0396508 , -0.75771892, -1.9260892 , -0.18137694,
                 -0.44223898,  0.32745569,  0.16834256])

만약 위의 경우에서 내림차순으로 정렬하고 싶다면, np.sort(arr)[::-1]을 실행하면 됩니다. 이는 numpy 인덱싱 시 사용되는 일종의 꼼수이니 잘 기억해 두시면 유용할 것입니다.

정렬 함수는 2차원 array에도 적용할 수 있습니다.

    arr2d = np.array([[-1.25627232,  1.65117477, -0.04868035],
                       [ 0.7405744 , -0.67893699, -0.28428494],
                    [ 0.02640821, -0.29027297,  0.34441534],
                    [ 0.68394722,  0.26180229,  0.76742614],
                    [ 1.00806827,  0.77977295, -1.36273314]])

2차원 arr2d array를 하나 정의하였을 때, np.sort(arr, axis=0)을 실행하면 '행 방향'으로 오름차순 정렬이 이루어집니다. axis 인자의 값을 1로 바꾸면 '열 방향'으로 오름차순 정렬이 이루어질 것입니다.

    np.sort(arr, axis=0)
    >>> array([[-1.25627232, -0.67893699, -1.36273314],
               [ 0.02640821, -0.29027297, -0.28428494],
               [ 0.68394722,  0.26180229, -0.04868035],
               [ 0.7405744 ,  0.77977295,  0.34441534],
               [ 1.00806827,  1.65117477,  0.76742614]])

만약 길이가 아주 긴 large_arr array가 정의되어 있을 때, 해당 array 상에서 상위 5%에 위치하는 값은 다음과 같이 찾아낼 수 있습니다.

    np.sort(large_arr)[::-1][int(0.05 * len(large_arr))]

large_arr을 먼저 오름차순으로 정렬한 뒤, int(0.05 * len(large_arr))을 통해 상위 5%에 해당하는 인덱스를 계산한 후, 이를 사용하여 정렬 결과에 대한 인덱싱을 수행하는 과정으로 진행하였습니다.

마지막으로, 중복된 성분을 포함하고 있는 array에 대하여, np.unique() 함수를 사용하면 중복된 값을 제외한 유니크한 값만을 추출할 수 있습니다. 예를 들어 해당 array의 이름이 names 였다면, np.unique(names)을 실행하면 중복된 성분이 제거된 새로운 array를 얻을 수 있습니다.

numpy를 사용한 데이터 분석 맛보기

파일 읽기

[MovieLens 1M 데이터셋 다운로드]

대부분의 데이터셋은 열과 열을 구분하기 위한 구분자로 특정한 문자를 사용합니다. 예를 들어 MovieLens 1M dataset의 경우, 콜론이 두 개 붙은 문자 '::'가 구분자라고 할 수 있습니다.

구분자는 보통 콤마 ','를 많이 쓰는데, 콤마를 구분자로 사용한 파일을 특별히 'CSV(comma-separated values) 파일'이라고 부릅니다.

일정한 구분자를 사용하는 데이터셋 파일은 np.loadtxt() 함수를 사용하여 손쉽게 읽어들일 수 있습니다. 해당 함수를 호출할 때, 읽어들일 데이터셋 파일의 경로, 사용할 구분자, array의 데이터형 등이 인자로 입력됩니다.

    data = np.loadtxt("data/movielens-1m/ratings.dat", delimiter="::", dtype=np.int64)

MovieLens 1M dataset 분석하기

동영상 강의의 내용을 참조해 주시길 바랍니다.

파일 쓰기

여러분이 얻은 array 형태의 데이터 분석 결과물을 외부 파일로 쓰는 작업을 진행해보도록 합시다.

np.savetxt() 함수를 사용하면, 현재 array 형태로 들고 있는 분석 결과물을 외부 파일로 손쉽게 쓸 수 있습니다. 해당 함수를 호출할 때, 저장할 파일의 경로, 저장할 array, 저장 형태(format), 구분자 등을 인자로 같이 입력합니다.

    np.savetxt("mean_rating_by_user.csv", mean_rating_by_user_array,
               fmt='%.3f', delimiter=',')

results matching ""

    No results matching ""