[Python] 데이터 사이언스 스쿨 - 3.2 배열의 생성과 변형

Updated:

데이터 사이언스 스쿨 자료를 토대로 공부한 내용입니다.

실습과정에서 필요에 따라 내용의 누락 및 추가, 수정사항이 있습니다.


3.2 배열의 생성과 변형

넘파이의 자료형

import numpy as np
# dtype을 이용해 자료형태 확인
x = np.array([0,1,2])
print(x.dtype)

# 배열 생성시 자료 형태를 지정하지 않으면 자동으로 유추
x = np.array([0,1,2.0])
print(x.dtype)

# 문자와 숫자 섞은 후 자료 형태 지정 X
x = np.array(["A",1,2.0])
print(x.dtype)
int32
float64
<U3
  • 넘파이 배열의 자료 형태를 알고 싶으면 dtype 속성을 사용한다.

  • 배열을 생성할 때 자료 형태를 지정하지 않으면 자동으로 유추한다.

dtype 종류

dtype 접두사 설명 사용 예
b 불리언 b (참 혹은 거짓)
i 정수 i8 (64비트)
u 부호 없는 정수 u8 (64비트)
f 부동소수점 f8 (64비트)
c 복소 부동소수점 c16 (128비트)
O 객체 0 (객체에 대한 포인터)
S 바이트 문자열 S24 (24 글자)
U 유니코드 문자열 U24 (24 유니코드 글자)
x = np.array([1,2,3], dtype = 'f')
print(x[0] + x[1])

x = np.array([1,2,3], dtype = 'U')
print(x[0] + x[1])
3.0
12
  • 위 예시는 똑같은 배열을 만드는데 각각 float형, 문자형으로 생성하였다.

  • float형의 경우 배열의 원소를 더하면 실제 산술 계산이 이루어 진다.

  • 반면에 문자형으로 지정한 경우는 문자로 인식하기에 두 문자를 붙여서 출력하였다.

Inf와 NaN

np.array([0, 1, -1, 0]) / np.array([1, 0, 0, 0])
<ipython-input-4-6ab12ec6e7a4>:1: RuntimeWarning: divide by zero encountered in true_divide
  np.array([0, 1, -1, 0]) / np.array([1, 0, 0, 0])
<ipython-input-4-6ab12ec6e7a4>:1: RuntimeWarning: invalid value encountered in true_divide
  np.array([0, 1, -1, 0]) / np.array([1, 0, 0, 0])





array([  0.,  inf, -inf,  nan])
print(np.log(0))
print(np.exp(-np.inf))
-inf
0.0


<ipython-input-5-0d5d201f1928>:1: RuntimeWarning: divide by zero encountered in log
  print(np.log(0))
  • 넘파이는 무한대를 의미하는 np.inf와 정의할 수 없는 숫자를 np.nan을 사용할 수 있다.

  • 위 예시들을 통해서 inf나 nan이 출력되는 것을 확인 할 수 있다.

배열 생성

# 모든 값이 0인 행렬
z1 = np.zeros(5)
z2 = np.zeros((2,2))

print(z1)
print(z2)
[0. 0. 0. 0. 0.]
[[0. 0.]
 [0. 0.]]
  • np.zeros()는 모든 원소가 0인 배열을 생성한다.

  • 생성시 배열의 크기를 지정하면 지정한 형태로 배열을 생성한다.

# 모든 값이 1인 행렬
o1 = np.ones(5)
o2 = np.ones((2,2))

print(o1)
print(o2)
[1. 1. 1. 1. 1.]
[[1. 1.]
 [1. 1.]]
  • np.ones()np.zeros()와 유사하지만 모든 원소를 1로 반환한다.
# 자료형 지정 가능
z = np.zeros((5, 2), dtype="i")
z
array([[0, 0],
       [0, 0],
       [0, 0],
       [0, 0],
       [0, 0]], dtype=int32)
  • 배열 생성시 자료 형태를 지정 가능하며 np.zeros()가 아닌 다른 배열들도 지정 가능하다.
# 문자로 지정시 크기에 유의
z = np.zeros(5, dtype="U4")

z[0] = "a"
z[1] = "abcd"
z[2] = "abcde" # 짤려서 나옴

print(z)
['a' 'abcd' 'abcd' '' '']
  • 위 예시는 np.zeros()로 문자 배열 생성 후 원소 값을 바꾸어 준다.

  • 한 가지 유의할 점은 처음 dtype에 지정한 크기보다 길이가 큰 문자를 넣으면 값이 짤려서 저장된다.

# 다른 배열과 같은 크기로 생성하기
z = np.zeros(5)
o = np.ones_like(z, dtype= "f")
o
array([1., 1., 1., 1., 1.], dtype=float32)
  • np.zeors_like, np.ones_like는 다른 배열과 같은 크기로 원소만 각각 0,1로 생성한다.
# 배열을 생성만 하고 값은 초기화 하지 않음 (가장 빠른 속도)
e = np.empty((2, 2))
e
array([[1., 1.],
       [1., 1.]])
  • 배열의 크기가 커지면 배열을 초기화하는데도 시간이 걸린다.

  • np.empty()는 배열을 생성만 하고 특정한 값으로 초기화를 하지 않는다.

  • 생성된 배열의 원소는 기존에 메모리에 저장되어 있던 값이 부여되어 값을 미리 알 수 없다.

# np.arange: 리스트의 range 역할
print(np.arange(5))

# np.arange(시작, 끝, 단계)
print(np.arange(1,11,2))
[0 1 2 3 4]
[1 3 5 7 9]
  • np.aragne()는 리스트의 range()함수와 비슷한 역할을 한다.

  • 위 예시와 같이 시작, 끝, 추출단계를 지정하여 배열을 생성할 수 있다.

# linsapce(시작, 끝(포함), 추출 갯수)
print(np.linspace(1,10,4))

# logspace(시작, 끝(포함), 추출 갯수)
print(np.logspace(0.1,1,4)) # 10**0.1 ~ 10
[ 1.  4.  7. 10.]
[ 1.25892541  2.51188643  5.01187234 10.        ]
  • np.linspace()는 시작 숫자부터 끝 숫자까지 지정한 추출 갯수만큼 출력될 수 있게 동일한 간격으로 출력한다.

  • np.logspace() 역시 유사하지만 10^시작 숫자부터 10^끝 숫자까지로 범위를 지정한다.

  • 두 함수는 range(), np.arange()와는 다르게 끝 숫자를 포함하여 출력한다.

전치 연산

M = np.array([[0,1,2],
              [3,4,5]])
print(M) # 2 x 3
print("-"*10)

print(M.T) # 3 x 2
print("-"*10)

print(np.transpose(M)) # 3 x 2
[[0 1 2]
 [3 4 5]]
----------
[[0 3]
 [1 4]
 [2 5]]
----------
[[0 3]
 [1 4]
 [2 5]]
  • 넘파이 배열은 np.transpose() 혹은 T 속성으로 전치 행렬을 출력할 수 있다.

배열의 크기 변형

# 1차원 배열 생성
x = np.arange(12)
print(x)
print("-"*20)

# 2차원으로 변환
x2 = x.reshape(3,4)
print(x2)
print("-"*20)

# 3차원으로 변환
x3 = x.reshape(2,2,3)
print(x3)
print("-"*20)

# -1을 이용하여 자동으로 지정
x4 = x.reshape(2,-1)
print(x4)
[ 0  1  2  3  4  5  6  7  8  9 10 11]
--------------------
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
--------------------
[[[ 0  1  2]
  [ 3  4  5]]

 [[ 6  7  8]
  [ 9 10 11]]]
--------------------
[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]]
  • reshape()를 사용하면 배열의 크기를 변형 가능하다.

  • 차원, 행, 열에 -1을 입력하면 나머지 숫자를 고려해서 원래 원소 갯수와 동일하게 출력될 수 있게끔 변형된다.

# 무조건 1차원으로 배열 만들기 - flatten, ravel
x = np.arange(12).reshape(3,4)
x2 = x.reshape(12)
x3 = x.flatten()
x4 = x.ravel()

print(x)
print(x2)
print(x3)
print(x4)
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
[ 0  1  2  3  4  5  6  7  8  9 10 11]
[ 0  1  2  3  4  5  6  7  8  9 10 11]
[ 0  1  2  3  4  5  6  7  8  9 10 11]
  • flatten() ravel()은 모두 1차원 배열로 크기를 변형하는 함수이다.

  • flatten()으로 생성한 배열은 원본 배열이 변경되어도 영향이 없다.

  • reshape(), ravel()으로 생성한 배열은 원본 배열이 변경되면 같이 값이 변경된다.

x[0] = 1

print("reshape:", x2)
print("faltten:", x3)
print("ravel:", x4)
reshape: [ 1  1  1  1  4  5  6  7  8  9 10 11]
faltten: [ 0  1  2  3  4  5  6  7  8  9 10 11]
ravel: [ 1  1  1  1  4  5  6  7  8  9 10 11]
  • 원본 배열을 수정하였더니 reshape(), ravel()으로 생성한 배열의 원소도 수정되었다.

  • 반대로 reshape(), ravel()으로 생성한 배열을 수정하면 원본 배열도 수정된다.

# 차원 확인하기
x = np.arange(3)
x2 = x.reshape(1,3)

print(x, "차원:", x.ndim)
print(x2, "차원:", x2.ndim)
[0 1 2] 차원: 1
[[0 1 2]] 차원: 2
  • 같은 데이터여도 1차원과 2차원 배열은 확실히 다른 객체임을 명심하여야 한다.
# 차원 증가시키기 
x = np.arange(3)
print(x[:, np.newaxis])
print(x[np.newaxis, :])
[[0]
 [1]
 [2]]
[[0 1 2]]
  • np.newaxis를 이용하면 원래 배열에서 1차원 증가시킬 수 있다.

배열 연결

# hstack: 행의 수가 같아야함
x = np.zeros((2,3))
y = np.ones((2,5))

np.hstack([x,y])
array([[0., 0., 0., 1., 1., 1., 1., 1.],
       [0., 0., 0., 1., 1., 1., 1., 1.]])
  • np.hstack()은 배열을 행 연결하는 함수로 연결하는 배열의 행의 수가 같아야 한다.
# vstack: 열의 수가 같아야함
x = np.zeros((2,4))
y = np.ones((1,4))

np.vstack([x,y])
array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [1., 1., 1., 1.]])
  • np.vstack()은 배열을 열 연결하는 함수로 연결하는 배열의 열의 수가 같아야 한다.
# dstack: 행과 열의 수가 같아야함
x = np.zeros((4,3))
y = np.ones((4,3))

# 기존 4 x 3 -> 4 x 3 x 2
np.dstack([x,y])
array([[[0., 1.],
        [0., 1.],
        [0., 1.]],

       [[0., 1.],
        [0., 1.],
        [0., 1.]],

       [[0., 1.],
        [0., 1.],
        [0., 1.]],

       [[0., 1.],
        [0., 1.],
        [0., 1.]]])
  • np.dstack()은 연결하는 배열의 행과 열의 수가 같아야 한다.

  • 만약 4 x 3 행렬 2개를 연결하면 4 x 3 x 2로 변경되어 기존의 행과 열의 수가 달라진다.

# stack: 행과 열의 수가 같아야함 - dstack의 확장버전
x = np.arange(12) ; x = x.reshape((4,3))
y = np.arange(12,24) ; y = y.reshape((4,3))

# 기존 4 x 3 -> 2 x 4 x 3 - 깊이 연결 (디폴트 axis=0)
print(np.stack([x,y]))
print("-" * 20)

# 기존 4 x 3 -> 4 x 2 x 3  - 행 연결
print(np.stack([x,y], axis = 1))
print("-" * 20)

# 기존 4 x 3 -> 4 x 3 x 2 - 행 연결 후 전치
print(np.stack([x,y], axis = 2))
[[[ 0  1  2]
  [ 3  4  5]
  [ 6  7  8]
  [ 9 10 11]]

 [[12 13 14]
  [15 16 17]
  [18 19 20]
  [21 22 23]]]
--------------------
[[[ 0  1  2]
  [12 13 14]]

 [[ 3  4  5]
  [15 16 17]]

 [[ 6  7  8]
  [18 19 20]]

 [[ 9 10 11]
  [21 22 23]]]
--------------------
[[[ 0 12]
  [ 1 13]
  [ 2 14]]

 [[ 3 15]
  [ 4 16]
  [ 5 17]]

 [[ 6 18]
  [ 7 19]
  [ 8 20]]

 [[ 9 21]
  [10 22]
  [11 23]]]
  • np.stack()np.dstack()의 확장 버전이다.

  • 지정한 axis에 따라서 연결 방식이 정해진다.

# r_: hstack과 비슷
np.r_[np.array([1, 2, 3]), np.array([4, 5, 6])]
array([1, 2, 3, 4, 5, 6])
  • np.r_[]np.hstack()과 비슷한 역할을 수행한다.

  • 한 가지 특이사항은 메소드임에도 소괄호가 아닌 중괄호를 사용한다.

# c_: 차원을 증가시킨 후 연결
np.c_[np.array([1, 2, 3]), np.array([4, 5, 6])]
array([[1, 4],
       [2, 5],
       [3, 6]])
  • np.c_[]은 차원을 1차원 증가시킨 후 행 연결한다.

  • np.r_[]처럼 메소드임에도 소괄호가 아닌 중괄호를 사용한다.

# tile: 동일한 배열을 반복하여 연결
a = np.array([[0, 1, 2],
              [3, 4, 5]])

print(np.tile(a, 3)) # 1개 지정시 hstack
print(np.tile(a, (3,2)))
[[0 1 2 0 1 2 0 1 2]
 [3 4 5 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]]
  • np.tile()은 지정한 숫자 혹은 배열크기 만큼 원래 배열을 복사해서 붙인다.

연습문제 3.2.1

지금까지 공부한 명령어를 사용하여 다음과 같은 배열을 만들어라.

array([[   0.,    0.,    0.,    1.,    1.],
       [   0.,    0.,    0.,    1.,    1.],
       [   0.,    0.,    0.,    1.,    1.],
       [  10.,   20.,   30.,   40.,   50.],
       [  60.,   70.,   80.,   90.,  100.],
       [ 110.,  120.,  130.,  140.,  150.],
       [   0.,    0.,    0.,    1.,    1.],
       [   0.,    0.,    0.,    1.,    1.],
       [   0.,    0.,    0.,    1.,    1.],
       [  10.,   20.,   30.,   40.,   50.],
       [  60.,   70.,   80.,   90.,  100.],
       [ 110.,  120.,  130.,  140.,  150.]])
z1 = np.zeros((3,3))
o1 = np.ones((3,2))

m1 = np.arange(10, 151, 10)
m2 = m1.reshape((3,5))

k1 = np.hstack([z1,o1])
k2 = np.vstack([k1,m2])

k3 = np.tile(k2,(2,1))
k3
array([[  0.,   0.,   0.,   1.,   1.],
       [  0.,   0.,   0.,   1.,   1.],
       [  0.,   0.,   0.,   1.,   1.],
       [ 10.,  20.,  30.,  40.,  50.],
       [ 60.,  70.,  80.,  90., 100.],
       [110., 120., 130., 140., 150.],
       [  0.,   0.,   0.,   1.,   1.],
       [  0.,   0.,   0.,   1.,   1.],
       [  0.,   0.,   0.,   1.,   1.],
       [ 10.,  20.,  30.,  40.,  50.],
       [ 60.,  70.,  80.,  90., 100.],
       [110., 120., 130., 140., 150.]])

2차원 그리드 포인트 생성

# 좌표 만들기
x = np.arange(3)
y = np.arange(5)

# np.meshgrid
# X: 5 x 3 - vstack / Y: 5 x 3 - hstack
X, Y = np.meshgrid(x,y)
print(X)
print("-" * 20)

print(Y)
print("-" * 20)

# zip: pair를 만들어줌
[list(zip(x, y)) for x, y in zip(X, Y)]
[[0 1 2]
 [0 1 2]
 [0 1 2]
 [0 1 2]
 [0 1 2]]
--------------------
[[0 0 0]
 [1 1 1]
 [2 2 2]
 [3 3 3]
 [4 4 4]]
--------------------





[[(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)]]
  • np.meshgrid()는 x축 벡터, y축 벡터를 인수로 받아서 두 벡터의 조합을 x축 값만 가진 행렬과 y축 값만 가진 행렬로 반환한다.

  • 위 예시에선 반환된 반환된 행렬을 zip()으로 연결해서 좌표 형태로 출력하였다.

Leave a comment