Numpy
Numpy는 숫자 데이터를 효과적으로 다룰 수 있기 때문에 데이터를 주로 다루는, 데이터 과학 및 데이터 분석에 많이 사용되는 파이썬 패키지입니다.
다차원의 배열 자료구조 클래스인 ndarray 클래스를 지원하며 벡터와 행렬을 사용하는 선형대수 계산에 주로 사용됩니다.
넘파이 배열
배열(Array)이란 순서가 있는 같은 종류(type)의 데이터가 저장되는 자료형을 말합니다.
리스트도 좋은 자료형임에 틀림없지만 넘파이 배열이 구조적으로 속도가 빠르고 메모리를 더 적게 사용합니다.
출처:https://jakevdp.github.io/blog/2014/05/09/why-python-is-slow/링크
수 많은 숫자 데이터를 하나의 리스트 시퀀스로 다루게 되면 넘파이 배열에 비해 상대적으로 속도가 느리고 메모리를 많이 차지하는 단점이 있습니다.
넘파이 패키지의 배열(array)을 사용하면 리스트에 비해 적은 메모리로 많은 데이터를 빠르게 처리할 수 있습니다.
다만 넘파이 배열은 많은 데이터를 다룰 수 있다는 점에서 리스트와 비슷하지만 다음과 같은 차이점을 갖습니다.
- 모든 요소(items)가 같은 자료형(type)이어야 합니다. (리스트는 다양한 자료형을 섞어서 요소로 가질 수 있습니다.)
- 요소의 개수를 바꿀 수 없습니다. (리스트는 요소의 추가 및 삭제가 자유롭습니다.)
넘파이의 배열 연산은 C로 구현된 내부 반복문을 사용하기 때문에 파이썬 반복문에 비해 속도가 빠르며 벡터화 연산(vectorized operation)을 이용하여 간단한 코드로도 복잡한 선형 대수 연산을 수행할 수 있습니다.
또한 배열 인덱싱(array indexing)을 사용한 질의(Query) 기능을 이용하여 간단한 코드로도 복잡한 수식을 계산할 수 있습니다.
다시 정리해서 되내일 내용입니다.
- 순서가 있는 같은 종류의 데이터, 파이썬에서 순서 없이 섞어 쓰던 것과 차이점을 느껴야합니다.
- 타입이 같기 때문에 크기가 고정되어 있어 메모리 상에 연속으로 있어도 간격을 알고 있으면 다음 데이터를 찾을 수 있기 때문에 문제가 생기지 않습니다.
- 벡터화 연산, 이게 중요하다. 코드를 간결화하고 복잡한 계산을 가능하게 하는겁니다.
넘파이의 자료형
넘파이의 배열인 ndarray클래스는 요소가 모두 같은 자료형을 갖는다고 했습니다.
np.array() 함수로 넘파이 배열을 만들 때 자료형을 명시적으로 작성하려면 키워드
인자로 dtype을 사용합니다.
반대로 dtype 키워드 인수가 없으면 해당 데이터 자료형을 동적 타이핑합니다. 그리고
만들어진 넘파이 배열의 자료형을 확인하려면 dtype 속성을 활용하면 됩니다.
np.array() 메서드를 쓸 때 dtype 키워드 인수로 지정할 자료형의 값은 아래 표와
같은 dtype 접두사와 바이트(혹은 글자 수)를 나타내는 숫자를 조합한 문자열
(str) 형태로 축약해서 사용할 수 있습니다.
예를 들면 “f8”은 8바이트(64비트) 부동소수점 실수(float)를 뜻하고 U4 는
4글자 유니코드 문자열을 뜻합니다. 숫자를 생략하면 운영체제에 따라 알맞은
크기를 지정합니다.
참고: https://numpy.org/doc/stable/reference/arrays.dtypes.html링크
첫 번째 넘파이 배열에서 ‘f’를 활용하여 float로 dtype을 지정해줬습니다. dtype
속성을 활용해 타입을 확인하니 32비트의 float형으로 생성된 것을 확인할 수 있습니다.
이 타입간 덧셈 연산(+)의 결과는 숫자로 계산됨을 확인할 수 있습니다.
바로 아래 넘파이 배열에서는 유니코드 문자열을 타입으로 지정해서 생성하였습니다.
1글자 이하의 글자들로 구성된 유니코드임을 추가적으로 설명하고 있습니다. 유니코드 간
덧셈 연산(+)은 concatenate의 결과를 보여줍니다.
넘파이에서는 무한대를 표현하기 위한 np.inf(infinity)와 정의할 수 없는 숫자를
나타내는 np.nan(not a number)을 사용할 수 있습니다. 다음 예와 같이 1을 0으로
나누려고 하거나 0에 대한 로그 값을 계산하면 무한대인 np.inf이 나옵니다. 0을 0으로
나누려고 시도하면 np.nan이 나옵니다.
벡터화 연산(Vectorized Operation)
넘파이 배열 객체는 배열의 각 요소에 대한 반복 연산을 하나의 명령어로 처리하는
벡터화 연산(vectorized operation)을 지원합니다. 예를 들어 다음처럼 리스트 내 모든
요소 데이터를 모두 2배 해야 하는 경우를 생각해봅시다.
넘파이 배열 객체의 벡터화 연산을 사용하면 다음과 같이 for 반복문이 없이 간단하게 단
한번의 연산식으로 표현할 수 있습니다. 물론 계산 속도도 리스트 객체에 반복문을
사용해서 처리하는 것보다 훨씬 빠릅니다.
파이썬 리스트 객체에 정수를 곱하면(*)
객체의 크기가 곱한
숫자만큼 반복 증가합니다. 넘파이 배열 객체는 벡터화 연산이 이뤄진다고 바로 앞에서
설명 드렸습니다. 이 차이점을 분명히 기억해둬야 합니다.
벡터화 연산은 비교 연산과 논리 연산을 포함한 모든 종류의 수학 연산에 대해 적용됩니다.
배열의 슬라이싱
배열 객체로 구현한 다차원 배열의 원소 중 복수 개의 원소에 접근하려면 일반적인 파이썬 슬라이싱(slicing)과 comma(,)를 함께 사용하면 됩니다.
1
2
3
4
5
a = np.array([[0, 1, 2, 3],[4, 5, 6, 7]]) # np.array([i for i in range(8)]).reshape(2,4)
a[0, :] # 첫 번째 행 출력
a[:, 0] # 첫 번째 열 전체
a[1, 1:]
a[:2, :2]
배열 생성
NumPy는 몇가지 단순한 배열을 생성하는 메서드를 제공합니다.
- zeros, ones
- zeros_like, ones_like
- empty
- arange
- linspace, logspace
여기서 몇가지만 정리해보겠습니다. 크기가 정해져 있고 모든 값이 0인 배열을 생성하려면 zeros 메서드를 사용합니다.
첫번째 인수로는 shape 혹은 1차원 배열을 크기를 뜻하는 정수를 넣습니다.
1
2
3
4
5
6
7
8
9
10
# 크기를 뜻하는 튜플을 입력하면 다차원 배열도 만들 수 있다.
a = np.zeros(5) # .reshape()
c = np.zeros((5, 2), dtype='i') # 숫자 없이 dtype을 기입하면 운영체제에서 가장 알맞은 걸 판단해서 넣어달라는 듯
# zeros 메서드로 문자열 배열도 생성 가능하지만 모든 요소의 문자열 크기가 같아야 한다.
# 만약 더 큰 크기의 문자열을 할당하면 잘릴 수 있다.
d = np.zeros(5, dtype='U4')
#0이 아닌 1로 초기화된 배열
e = np.ones((2, 3, 4), dtype="i8") # ((깊이, 행, 렬) ...)
만약 shape를 튜플 값으로 명시하지 않고 다른 배열과 같은 shape의 배열을 생성하고 싶다면 ones_like, zeros_like 명령을 사용합니다.
첫 번째 인수로는 ‘shape를 복사하고 싶은 ndarray 객체’를 기입합니다.
1
f = np.ones_like(b, dtype="f")
전치 연산
2차원 배열의 전치(transpose) 연산은 행과 열을 바꾸는 작업입니다. 이는 배열의 T 속성으로 구할 수 있습니다. 메서드가 아닌 속성이라는 점에 유의합니다.
1
2
a = np.array([[1,2,3], [4, 5, 6]])
a.T
배열의 크기 변형
일단 만들어진 배열의 내부 데이터는 보존한 채로 형태만 바꾸려면 reshape 명령이나 메서드를 사용합니다.
예를 들어 12개의 원소를 가진 1차원 행렬은 3x4 형태의 2차원 행렬로 만들 수 있습니다.
사용하는 원소의 개수가 정해져 있기 때문에 reshape 명령의 형태 튜플의 원소 중 하나는 -1이라는 숫자로 대체할 수 있습니다. -1을 넣으면 해당 숫자는 원소 갯수에 맞게 계산되어 사용됩니다.
1
2
a = np.arange(12)
a.reshape(3, -1)
다차원 배열을 1차원으로 만들고 싶을 때는 flatten 혹은 ravel 메서드를 사용하면 가능합니다.
1
2
a.flatten()
a.ravel()
같은 배열에 대해 차원만 1차원 증가시키는 경우에는 newaxis 명령을 사용하면 됩니다.
1
a[:, np.newaxis]
주의할 점
배열 사용에서 주의할 점은 길이가 5인 1차원 배열과 행, 열의 개수가 (5, 1)인 2차원 배열 또는 행, 열의 개수가 (1, 5)인 2차원 배열은 데이터가 같아도 엄연히 다른 객체라는 점입니다.
브로드캐스팅
보통 배열끼리 산술 연산을 하려면 두 배열의 shapes가 정확히 같아야
합니다.
넘파이 배열은 모양이 다른 배열 간의 연산이 가능하도록 배열의 크기를 변환시켜주는
브로드캐스팅(broadcasting)을 지원합니다.
브로드캐스팅은 넘파이가 산술 연산 중에 모양이 다른 배열을 처리하는 방법으로
더 작은 배열이 더 큰 배열에 호환되는 모양으로 확장하는 식으로 진행됩니다.
넘파이의 브로드캐스팅 규칙 중 가장 간단한 예는 배열과 스칼라 값이 연산에서 결합될
때입니다.(개념 이해를 돕기 위한 그림입니다. 실제 연산에서는 stretch된 구조를 만들어서
하진 않습니다.)
출처: https://numpy.org/doc/stable/user/basics.broadcasting.html링크
(참고) 아래의 두 예제 중 어떤 코드가 더 효율적일까요? 스칼라 곱을 활용한 코드가 더
적은 메모리를 이동하기 때문에 배열 간의 곱보다 더 효율적입니다.
브로드캐스팅이 항상 가능하진 않습니다.
확장이 가능한 경우가 있고 반대로 불가능한 경우도 존재합니다.
확장 가능한 경우를 확인하는 규칙은 다음과 같습니다.
- 넘파이 배열의 shape을 우측 정렬하고 각 차원별로 숫자를 비교합니다.
비교하는 모든 차원이 두 조건 중 하나에 충족되어야 브로드캐스팅 가능합니다.- a. 해당 차원 간의 숫자가 동일한 경우
- b. 해당 차원 중 하나가 1인 경우
아래에 보이는 경우들이 모두 브로드 캐스팅이 가능한 경우입니다.
차원이 다른 경우 우측 정렬하여 비교하고 있는데, 이렇게 비교하면 같은 차원끼리 비교를 할 수 있게 됩니다.
- 3차원: 깊이 x 행 x 열
- 2차원: 행 x 열
- 1차원: 열
출처: https://numpy.org/doc/stable/user/basics.broadcasting.html링크
브로드캐스팅이 불가능한 경우입니다. 이 경우 파이썬은 ValueError를 발생시킵니다.
차원 축소 연산
행렬에서 하나의 행에 있는 원소들을 하나의 데이터 집합으로 보고 그 집합의 평균을 구하면 각 행에 대해 하나의 숫자가 나오게 됩니다.
예를 들어 10 x 5 크기의 2차원 배열에 대해 행-평균을 구하면 10개의 숫자를
가진 1차원 벡터가 나오게 됩니다. 이러한 연산을 차원 축소(dimension reduction)연산이라고 합니다.
넘파이는 다음과 같은 차원 축소 연산 명령 혹은 메서드를 지원합니다.
- 최대/최소: min, max, argmin, argmax
- 통계: sum, mean, median, std, var
- 불리언: all, any
차원 축소 연산 - sum()
sum() 메서드는 해당 배열의 합산 결과를 반환합니다. 연산 대상이 2차원 이상일 때에는 axis 키워드 인수를
사용할 수 있습니다.
(참고) np.sum()과 ndarray.sum()은 동등한 메서드입니다.
차원 축소 연산 - min()
min() 메서드는 해당 배열의 제일 작은 값의 결과를 반환합니다. 연산 대상이 2차원 이상일 때는 axis
키워드 인수를 사용할 수 있습니다.
차원 축소 연산 - argmin()
argmin() 메서드는 해당 배열의 제일 작은 값의 인덱스를 반환합니다. 연산 대상이 2차원
이상일 때에는 axis 키워드 인수를 사용할 수 있습니다.
차원 축소 연산 - max(), argmax()
min()과 argmin()과 반대로 제일 큰 값에 대한 것들을 반환하는 점을 제외하고는 같은 동작을 합니다.
차원 축소 연산 - mean(), median(), np.all(), np.any()
mean()은 평균을 구합니다.
median()은 중앙값을 구합니다.
np.all()은 배열 요소가 모두 True일 때 True를
반환합니다.(하나라도 False면 False입니다.)
np.any()는 배열 요소가 하나라도 True면 True를
반환합니다.(전부 False면 False입니다.)
정렬
numpy.sort() 메서드는 원래의 배열은 그대로 둔채 정렬이 된 결과를 복사본으로 반환합니다.
ndarray.sort() 메서드는 해당 객체의 자료 자체가 변화하는 자체변화(in-place)메서드입니다. 따라서 주의해야 합니다.
만약 자료를 정렬하는 것이 아니라 순서만 알고 싶다면 argsort 명령을 사용합니다.
numpy.sort()를 사용하여 배열 안의 원소를 크기에 따라 정렬하여 새로운 배열을 만들 수도 있습니다.
2차원 이상인 경우에는 행이나 열을 각각 따로따로 정렬하는데 axis 인수를 사용하여 행을 정렬할 것인지 열을 정렬한 것인지 결정합니다. axis=0이면 각각의 행을 따로따로 정렬하고 axis=1이면 각각의 열을 따로따로 정렬합니다.
디폴트 값은 -1 즉 가장 안쪽(나중)의 차원입니다.
기술 통계
넘파이는 다음과 같은 데이터 집합에 대해 간단한 통계를 계산하는 함수를 제공합니다.
이러한 값들을 통틀어 기술 통계(descriptive statistics)라고 합니다.
- 데이터의 개수(count)
- 평균(mean, average)
- 분산(variance)
- 표준 편차(standard deviation)
- 최대값(maximum)
- 최소값(minimum)
- 중앙값(median)
- 사분위수(quartile)
여기서는 표본 분산, 표본 표준편차, 중앙값, 사분위수 파트를 다뤄보겠습니다.
표본 분산
표본 분산(sample variance)은 데이터와 표본 평균간의 거리의 제곱의 평균입니다. 표본
분산이 작으면 데이터가 모여있는 것이고 크면 흩어져 있는 것입니다. 수학 기호로는
s^2이라고 표시하며 다음과 같이 계산합니다.
1
2
np.var(x) # 분산
np.var(x, ddof=1) # 비편향 분산. 추후 공부하게 될 예정
표본 표준편차
표본 표준편차(sample standard variance)는 표본 분산의 양의 제곱근 값입니다. 𝑠이라고 표시합니다.
사분위수
사분위수(quartile)는 데이터를 가장 작은 수부터 가장 큰 수까지 크기가 커지는 순서대로
정렬하였을 때 1/4, 2/4, 3/4 위치에 있는 수를 말합니다. 각각 1사분위수, 2사분위수,
3사분위수라고 합니다. 1/4의 위치란 전체 데이터의 수가 만약 100개이면 25번째 순서,
즉 하위 25%를 말합니다. 따라서 2사분위수는 중앙값과 같습니다.
때로는 위치를 1/100 단위로 나눈 백분위수 (percentile)을 사용하기도 합니다.
1사분위수는 25% 백분위수와 같습니다.
난수 발생과 카운팅
파이썬을 이용해서 데이터를 무작위로 섞거나 임의의 수, 난수(random number)를 발생시키는 방법에서 몇가지를 정리해보겠습니다. 이 기능은 주로 Numpy의 random 서브패키지에서 제공합니다. (표준 라이브러리인 random과 다릅니다.)
- rand: 0부터 1사이의 균일 분포
- randn: 표준 정규 분포 (기댓값(mean)이 0이고 표준편차가 1)
- randint: 균일 분포의 정수 난수
numpy.random.randint(low, high=None, size=None)
만약 high를 입력하지 않으면 0과 low사이의 숫자를, high를 입력하면 low와 high는 사이의 숫자를 출력합니다.
size는 난수의 개수입니다. high의 범위는 포함하지 않습니다.
데이터의 순서를 바꾸고 싶을 땐 shuffle 함수를 이용하면 가능합니다. shuffle 함수는 in-place 함수입니다.
1
2
x = np.arange(10)
np.random.shuffle(x)
데이터 샘플링
이미 있는 데이터 집합에서 일부를 무작위로 선택하는 것을 표본선택 혹은 샘플링(sampling)이라고 합니다. 샘플링에는 choice 함수를 사용합니다. choice 함수는 다음과 같은 인수를 가질 수 있습니다.
numpy.random.choice(a, size=None, replace=True, p=None)
- a : 배열이면 원래의 데이터, 정수이면 arange(a) 명령으로 데이터 생성
- size : 정수. 샘플 숫자.
- replace : 불리언. True이면 한번 선택된 데이터를 다시 선택 가능.
- p : 배열. 각 데이터가 선택될 수 있는 확률.
확률을 편향되게 해서 설정을 할 수가 있습니다.
정수 데이터 카운팅
난수가 정수값이면 unique 명령이나 bincount 명령으로 데이터 값을 분석할 수 있습니다.
unique() 함수는 데이터에서 중복된 값을 제거하고 중복되지 않는 값의 리스트를 출력합니다.
return_counts 인수를 True 로 설정하면 각 값의 데이터 개수도 출력합니다.
그러나 unique()는 데이터에 존재하는 값에 대해서만 개수를 세므로 데이터 값이 나올 수 있음에도 불구하고 데이터가 하나도 없는 경우에는 정보를 주지 않습니다.
예를 들어 주사위를 10번 던졌는데 6이 한 번도 나오지 않으면 이 값을 0으로 세어주지 않습니다.
따라서 데이터가 주사위를 던졌을 때 나오는 수처럼 특정 범위안의 수인 경우에는 bincount 함수에 minlength 인수를 설정하여 쓰는 것이 더 편리합니다.
bincount 함수는 0 부터 minlength - 1 까지의 숫자에 대해 각각 카운트를 합니다. 데이터가 없을 경우에는 카운트 값이 0이 됩니다.