3.2 배열의 생성과 변형
1. 넘파이의 자료형
- 넘파이 배열 즉, ndarrray 클래스는 운소가 모두 같은 조료형이어야 한다.
- array 명령으로 배열을 만들 때 자료형을 명시적으로 적용하려면 dtype 인수 사용.
- 만약 dtype 인수가 없으면 주어진 데이털르 저장할 수 있는 자료형을 스스로 유추한다.
- 만들어진 배열의 자료형을 알아내려면 dtype 속성을 보면 된다.
x = np.array([1, 2, 3])
x.dtype
#결과
dtype('int32')
x = np.array([1,0, 2.0, 3.0])
x.dtype
#결과
dtype('float64')
x = np.array([1, 2, 3.0])
x.dtype
#결과
dtype('float64')
- dtype 인수로 지정할 자료형은 다음 표에 보인것과 같은 "dtype 접두사"로 시작하는 문자열이고
- 이 글자 뒤에 오는 숫자는 바이트 수 혹은 글자 수를 의미한다.
- 예를 들어 f8은 8바이트(64비트) 부동소수점 실수를 뜻하고
- U4는 4r글자 유니코드 문자열을 뜻한다.
- 숫자를 생략하면 운영체제에 따라 알맞은 크기를 지정한다.
---
- dtype 접두사는 사이트 참고
x = np.array([1, 2, 3], dtype='f')
x.dtype
#결과
dtype('float32')
x[0] + x[1]
#결과
3.0
x = np.array([1, 2, 3], dtype="U")
x.dtype
#결과
dtype('<U1')
# 😂
x[0] + x[1]
#결과
'12'
2. Inf 와 NaN
- 넘파이에서는 무한대를 표현하는 np.inf(infinity)와 정의할 수 없는 숫자를 나타내는
np.nan(not a number)을 사용
- 다음 예와 같이 1을 0으로 나누려고 하거나
- 0에 대한 로그 값을 계산하면 무한대인 np.inf이 나온다.
- 0을 0으로 나누려고 시도하면 np.nan이 나온다.
np.array([0, 1, -1, 0]) / np.array([1, 0, 0, 0])
#결과
array([ 0., inf, -inf, nan])
# 😂 무슨 의미?
np.log(0)
#결과
-inf
# 😂 무슨 의미?
np.exp(-np.inf)
#결과
0.0
3. 배열 생성
- NumPy는 몇가지 단순한 배열을 생성하는 명령을 제공
- zeros, ones
- zeros_life, ones_like
- empyty
- arange
- linspace, logspace
---
- 크긱가 정해져 있고 모든 값이 0인 배열을 생성하려면 zeros 명령을 사용한다.
- 인수로는 배열을 크기를 뜻하는 정수를 넣는다.
a = np.zeros(5)
a
#결과
array([0., 0., 0., 0., 0.])
# 크게를 뜻하는 튜플로 입력하면
# 다차원 배열도 만들 수 있음
b = np.zeros((2, 3))
b
#결과
array([[0., 0., 0.],
[0., 0., 0.]])
# array 명령과 마찬가지로 dtype 인수를 명시하면
# 해당 자료형 원소를 가진 배열을 만든다.
c = np.zeros((5, 2), dtype="i")
c
#결과
array([[0, 0],
[0, 0],
[0, 0],
[0, 0],
[0, 0]], dtype=int32)
# 문자열 배열도 가능하지만
# 모든 원소의 문자열 크기가 같아야 한다.
# 만약 더 큰 크기의 문자열을 할당하면 잘릴 수 있다.
d = np.zeros(5, dtype="U4")
d
#결과
array(['', '', '', '', ''], dtype='<U4')
d[0] = 'abc'
d[1] = 'ABC'
d[2] = 'ABCDE'
d
#결과
array(['abc', 'ABC', 'ABCD', '', ''], dtype='<U4')
# 0이 아닌 1로 초기화된 배열을 생성하려면
# ones 명령을 사용한다.
e = np.ones((2, 3, 4), dtype="i8")
e
#결과
array([[[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]],
[[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]]], dtype=int64)
# 😂 무슨 차이??
# 만약 크기를 튜플로 명시하지 않고
# 다른 배열과 같은 크기의 배열을 생성하고 싶다면
# ones_like, zeros_like 명령을 사용한다.
f = np.ones_like(b, dtype="f")
f
#결과
array([[1., 1., 1.],
[1., 1., 1.]], dtype=float32)
😂
- 배열을 크기가 커지면 배열을 초기화하는데도 시간이 걸림
- 이 시간을 단축하려면 배열을 생성만 하고 특정한 값을으로 초기화를 하지 않는 empty 명령을 사용
- empty 명령으로 생성된 배열에는 기존에 메모리에 저장되어있던 값이 있으므로 배열의 원소의 값을 미리 알 수 없다.
g = np.empty((4, 3))
g
#결과
array([[0.0078125, 0.0078125, 0.0078125],
[0.0078125, 0.0078125, 0.0078125],
[0.0078125, 0.0078125, 0.0078125],
[0.0078125, 0.0078125, 0.0078125]])
# arange 명령은 NumPy 버전의 range 명령이라고 볼 수 있다.
# 특정한 규칙에 따라 증가하는 수열을 만든다.
np.arange(10)
#결과
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
np.arange(3, 21, 2) # 시작, 끝(포함x), 단계
#결과
array([ 3, 5, 7, 9, 11, 13, 15, 17, 19])
# linspace 명령이나 logspace 명령은
# 선형 구간 혹은 로그 구간을
# 지정한 구간의 수만큼 분할한다.
np.linspace(0, 100, 5) # 시작, 끝(포함0), 갯수
#결과
array([ 0., 25., 50., 75., 100.])
np.logspace(0.1, 1, 10)
#결과
array([ 1.25892541, 1.58489319, 1.99526231, 2.51188643, 3.16227766,
3.98107171, 5.01187234, 6.30957344, 7.94328235, 10. ])
4. 전치 연산
- 2차원 배열의 전치(transpose)연산은 행과 열을 바꾸는 작업
- 메서드가 아니라 속성이라는 점에 유의
A = np.array([[1, 2, 3], [4, 5, 6]])
A
#결과
array([[1, 2, 3],
[4, 5, 6]])
A.T
#결과
array([[1, 4],
[2, 5],
[3, 6]])
5. 배열의 크기 변형
- 일단 만들어진 배열의 내부 데이터를 보전한 채로 형태만 바꾸려면 reshape 명령이나 메서드를 사용한다.
- 예를 들어 12개의 원소를 가진 1차원 행렬은 3x4 형태의 2차원 행렬로 만들 수 있다.
a = np.arange(12)
a
#결과
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
b = a.reshape(3, 4)
b
#결과
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
- 😂
- 사용자가 원소의 갯수가 정해져 있기 때문에 reshape 명령의 형태 튜플의 원소 중 하나는 -1이라는 숫자로 대체할 수 있다.
- -1을 넣으면 해당 숫자는 다를 값에서 계산되어 사용된다.
a.reshape(3, -1)
#결과
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
a.reshape(2, 2, -1)
#결과
array([[[ 0, 1, 2],
[ 3, 4, 5]],
[[ 6, 7, 8],
[ 9, 10, 11]]])
a.reshape(2, -1, 2)
#결과
array([[[ 0, 1],
[ 2, 3],
[ 4, 5]],
[[ 6, 7],
[ 8, 9],
[10, 11]]])
# 다차원 배열을 무조건 1차원으로 만들기 위해서는
# flatten 이나 ravel 메서드 사용
a.flatten()
#결과
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
a.ravel()
#결과
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
- 배열 사용에서 주의할 점은
- 길이가 5인 1차원 배열과 행,
- 열의 갯수가 (5,1)인 2차원 배열 또는 행,
- 열의 갯수가 (1, 5)인 2차원 배열은 데이터가 같아도 엄연히 다른 객체라는 점이 있다.
x = np.arange(5)
x
#결과
array([0, 1, 2, 3, 4])
x.reshape(1, 5)
#결과
array([[0, 1, 2, 3, 4]])
x.reshape(5, 1)
#결과
array([[0],
[1],
[2],
[3],
[4]])
# 이렇게 같은 배열에 대한 차원만 1차원 증가시키는 경우
# newaxis 명령을 사용하기도 한다.
x[:, np.newaxis]
#결과
array([[0],
[1],
[2],
[3],
[4]])
6. 배열의 연결
- 행의 수나 열의 수가 같은 두 개 이상의 배열 연결
- hstack
- vstack
- stack
- r_
- c_
- tile
# hstack 행의 수가 같은 걸 옆으로 연결
a1 = np.ones((2, 3))
print(a1)
a2 = np.ones((2, 2))
print(a2)
np.hstack([a1, a2])
#결과
[[1. 1. 1.]
[1. 1. 1.]]
[[1. 1.]
[1. 1.]]
array([[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.]])
# vstack 열의 수가 같은 걸 위아래로 연결
b1 = np.ones((2, 3))
print(a1)
b2 = np.zeros((3, 3))
print(b2)
np.vstack([b1, b2])
#결과
[[1. 1. 1.]
[1. 1. 1.]]
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
array([[1., 1., 1.],
[1., 1., 1.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
😂
- dstack 명령은 제 3의축, 행이나 열이 아닌 깊이(depth)방향으로 배열을 합친다.
- 가장 안쪽의 차원이 증가한다.
- 즉 가장 내부의 숫자 원소가 배열이된다.
- shape 정보로 보자면 가장 끝에 값이 2인 차원이 추가되는 것.
- 아래 예제 경우 shape 변화가 2개의(3x4) -> 1개의 (3x4x2)가 된다.
c1 = np.ones((3, 4))
c1
#결과
array([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
c2 = np.zeros((3, 4))
c2
#결과
array([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
np.dstack([c1, c2])
#결과
array([[[1., 0.],
[1., 0.],
[1., 0.],
[1., 0.]],
[[1., 0.],
[1., 0.],
[1., 0.],
[1., 0.]],
[[1., 0.],
[1., 0.],
[1., 0.],
[1., 0.]]])
(np.dstack([c1, c2])).shape
#결과
(3, 4, 2)
- r_메서드는 hstack 명령과 비슷하게 배열을 좌우로 연결한다
- 다만 메서드임에도 불구하고 소괄호(())를 사용하지 않고 인덱싱과 같이 대괄호([])를 사용한다.
- 이런 특수 메서드를 인덱서(indexer)라고 한다.
np.r_[np.array([1, 2, 3]),np.array([4, 5, 6])]
#결과
array([1, 2, 3, 4, 5, 6])
# c_ 메서드는 배열의 차원을 증가시킨 후 좌우로 연결
# 만약 1차원 배열을 연결하면 2차원 배열이 된다.
np.c_[np.array([1, 2, 3]), np.array([4, 5, 6])]
#결과
array([[1, 4],
[2, 5],
[3, 6]])
# tile 명령은 동일한 배열을 반복하여 연결
a = np.array([[0, 1, 2], [3, 4, 5]])
np.tile(a, 2)
#결과
array([[0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5],
[0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5],
[0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5]])
np.tile(a, (3, 3))
#결과
array([[0, 1, 2, 0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5, 3, 4, 5],
[0, 1, 2, 0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5, 3, 4, 5],
[0, 1, 2, 0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5, 3, 4, 5]])
np.tile(a, (4, 2))
#결과
array([[0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5],
[0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5],
[0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5],
[0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5]])
7. 😂 2차원 그리드 포인트 생성
- 변수 2개인 2차운 함수의 그래프를 그리거나 표를 작성하려면 2차원 영역에 대한 (x, y)좌표값 쌍 즉, 그리드 포인트(grid point)를 생성하여 각 좌표에 대한 함수 값을 계산해야 한다.
- 예를 들어 x, y 라는 두 변수를 가진 함수에서 x가 0부터 2까지, y가 0부터 4까지의 사격형 영역에서 변화하는 과정을 보고 싶다면
- 이 사격형 영역 안의 다음과 같은 그리드 포인트들에 대해 함수르 계산해야 한다.
(x,y)=(0,0),(0,1),(0,2),(0,3),(0,4),(1,0),⋯(2,4)
- 이러한 그리드 포인트를 만드는 과정을 도와주는 것이 meshgrid 명령이다.
- meshgrid 명령은 사격 영역을 구성하는 가로축의 점들과 세로축의 점을 나타내는 두 벡터를 인수로 받아서 이 사격형 영역을 이루는 조합을 출력
- 결과는 그리드 포인트의 x 값만을 표시하는 행렬과 y 값만을 표시하는 행렬 두 개로 분리하여 출력한다.
x = np.arange(3)
x
#결과
array([0, 1, 2])
y = np.arange(5)
y
#결과
array([0, 1, 2, 3, 4])
X, Y = np.meshgrid(x, y)
X
#결과
array([[0, 1, 2],
[0, 1, 2],
[0, 1, 2],
[0, 1, 2],
[0, 1, 2]])
Y
#결과
array([[0, 0, 0],
[1, 1, 1],
[2, 2, 2],
[3, 3, 3],
[4, 4, 4]])
[list(zip(x, y)) for x, y in zip(X, Y)]
#결과
[[(0, 0), (1, 0), (2, 0)],
[(0, 1), (1, 1), (2, 1)],
[(0, 2), (1, 2), (2, 2)],
[(0, 3), (1, 3), (2, 3)],
[(0, 4), (1, 4), (2, 4)]]
'😀 기초 > 넘파이(NumPy)' 카테고리의 다른 글
3장 난수 발생과 카운팅(3.5) (0) | 2022.01.25 |
---|---|
3장 배열의 연산(3.3) (0) | 2022.01.25 |
3장 넘파이 배열 프로그래밍(3.1) (0) | 2022.01.24 |
NumPy 한번에 끝내기 (0) | 2022.01.18 |