IT/언어

[python/numpy] Numpy의 데이터형 dtype과 astype를 사용한 변경(캐스트)

개발자 두더지 2020. 6. 5. 20:45
728x90

 Numpy배열 ndarray는 dtype으로 저장되어, np.array()로 ndarray오브젝트를 생성할 때 지정하거나 astype()메소드로 변경하거나 하는 것이 가능하다. 기본적으로 하나의 ndarray오브젝트에 대해 하나의 dtype가 설정되어 있으며, 모든 요소가 같은 데이터 형이 된다.

 하나의 ndarray로 복수의 데이터형으로 다루기 위한 Structured Data(구조화 데이터)라는 것도 존재하지만 여기서는 다루지 않는다. 참고로 복수의 데이터형을 포함한 배열 (수치의 열 또는 문자열의 열을 포함한 2차원 배열 등)을 처리할 때 pandas가 편하다.

여기서는, 아래의 내용에 관련해서 설명한다.

1. Numpy의 주요 데이터형 dtype

2. 숫자형이 취할 수 있는 범위 (최소값, 최대값)의 확인

  1) np.iinfo()

  2) np.finfo()

3. 문자열의 문자수에 관련한 주의사항

4. object형 : Python오브젝트에 대한 포인터 저장

5. astype()을 사용한 데이터형 dtype 변환 (캐스트)

6. astype()으로 변환(캐스트)할 때의 주의점

7. 묵시적 형 변환


1. Numpy의 주요 데이터형 dtype

 

데이터형 dtype

코드

설명

int8

i1

부호 있는 8비트 정수형

int16

i2

부호 있는 16비트 정수형

int32

i4

부호 있는 32비트 정수형

int64

i8

부호 있는 64비트 정수형

unit8

u1

부호 없는 8비트 정수형

unit16

u2

부호 없는 16비트 정수형

unit32

u4

부호 없는 32비트 정수형

unit64

u8

부호 없는 64비트 정수형

float16

f2

실수형 ; 반 정밀도 부동소수점형

(부호 1비트, 지수 5비트, 가수 10비트)

float32

f4

실수형; 단 정밀도 부동소수점형

(부호 1비트, 지수 8비트, 가수 23비트)

float64

f8

실수형; 배 정밀도 부동소수점형

(부호 1비트, 지수 11비트, 가수 54비트)

float128

f16

실수형; 네배 정밀도 부동소수점형

(부호 1비트, 지수 15비트, 가수 112비트)

complex64

c8

복소수

(실수부, 허수부 각각 float32)

complex128

c16

복소수

(실수부, 허수부 각각 float64)

complex256

c32

복소수

(실수부, 허수부 각각 float128)

bool

?

불형 (true 또는 false)

unicode

U

unicode문자열

object

0

Python 오브젝트형

  데이터형 이름의 끝자리 숫자는 bit 로 표시되고, 코드의 끝 자리의 숫자는 btye로 표시된다. 같은 형이라도 값이 다르므로 주의하자. 또한, bool형의 코드 ?는 불명확하다는 의미가 아니라 문자대로 ?가 할당되어있다.

각 종 메소드의 인수로 데이터형 dtype를 지정할 때, 예를 들면 int64형으로 할 경우, 아래와 같이 작성하면 된다.

- np.int64

- 문자열 `int64`

- 형 코드의 문자열 `i8`

import numpy as np

a = np.array([1, 2, 3], dtype=np.int64)
print(a.dtype)
# int64

a = np.array([1, 2, 3], dtype='int64')
print(a.dtype)
# int64

a = np.array([1, 2, 3], dtype='i8')
print(a.dtype)
# int64

 비트 정밀도의 수치는 생략하여 int나 float, str처럼 Python의 형을 지정할 수 있다. 이 경우, 등가의 dtype은 자동적으로 변환되지만 어떤 dtype으로 변환되는 가는 환경에 따라 다르다. (Python2는 32비트 Python3는 64비트 등)

 아래의 표는 Python3 환경일 때의 경우이다. unit이라는 Python의 형은 없지만 편의상 함께 정리해 두었다.

Python의 형

등가의 dtype의 예

int

int 64

float

float64

str

unicode

(unit)

unit64

 

 인수로 지정한 경우는 int라도 문자열 'int'라도 가능하다.  Python의 형이 아닌 unit은 문자열 'uint'만 가능하다.

print(int is np.int)
# True

a = np.array([1, 2, 3], dtype=int)
print(a.dtype)
# int64

a = np.array([1, 2, 3], dtype='int')
print(a.dtype)
# int64

2. 숫자형이 취할 수 있는 범위 (최소값, 최대값)의 확인

정수 int, unit 부동소수점수 float, 각 데이터형의 범위는 np.iinfo(), np.finfo() 로 확인이 가능하다.

1) np.iinfo()

정수 int, unit에 대해 np.iinfo()를 사용과 관련된 매뉴얼은 아래의 링크를 참고

https://docs.scipy.org/doc/numpy/reference/generated/numpy.iinfo.html

인수형 오브젝트를 지정하자면 np.iinfo()형이 리턴된다. print()로 개요를 출력하거나, max속성이나 min속성 등으로 최대값이나 최소값 등을 수치로 취득하는 것이 된다.

ii64 = np.iinfo(np.int64)
print(type(ii64))
# <class 'numpy.iinfo'>

print(ii64)
# Machine parameters for int64
# ---------------------------------------------------------------
# min = -9223372036854775808
# max = 9223372036854775807
# ---------------------------------------------------------------
# 

print(ii64.max)
# 9223372036854775807

print(type(ii64.max))
# <class 'int'>

print(ii64.min)
# -9223372036854775808

print(ii64.bits)
# 64

 

인수 데이터형 dtype을 표시하는 문자열 등을 지정 가능하며, 부호없는 정수의 unit에도 대응하고 있다.

print(np.iinfo('int16'))
# Machine parameters for int16
# ---------------------------------------------------------------
# min = -32768
# max = 32767
# ---------------------------------------------------------------
# 

print(np.iinfo('i4'))
# Machine parameters for int32
# ---------------------------------------------------------------
# min = -2147483648
# max = 2147483647
# ---------------------------------------------------------------
# 

print(np.iinfo(int))
# Machine parameters for int64
# ---------------------------------------------------------------
# min = -9223372036854775808
# max = 9223372036854775807
# ---------------------------------------------------------------
# 

print(np.iinfo('uint64'))
# Machine parameters for uint64
# ---------------------------------------------------------------
# min = 0
# max = 18446744073709551615
# ---------------------------------------------------------------
# 

 

값 자체를 인수로 지정하는 것도 가능하다.

i = 100
print(type(i))
# <class 'int'>

print(np.iinfo(i))
# Machine parameters for int64
# ---------------------------------------------------------------
# min = -9223372036854775808
# max = 9223372036854775807
# ---------------------------------------------------------------
# 

ui = np.uint8(100)
print(type(ui))
# <class 'numpy.uint8'>

print(np.iinfo(ui))
# Machine parameters for uint8
# ---------------------------------------------------------------
# min = 0
# max = 255
# ---------------------------------------------------------------
# 

 

Numpy배열인 ndarray은 안된다. dtype속성으로 데이터형을 취득하거나, 요소를 취득해서 지정해야한다.

a = np.array([1, 2, 3], dtype=np.int8)
print(type(a))
# <class 'numpy.ndarray'>

# print(np.iinfo(a))
# ValueError: Invalid integer data type 'O'.

print(np.iinfo(a.dtype))
# Machine parameters for int8
# ---------------------------------------------------------------
# min = -128
# max = 127
# ---------------------------------------------------------------
# 

print(np.iinfo(a[0]))
# Machine parameters for int8
# ---------------------------------------------------------------
# min = -128
# max = 127
# ---------------------------------------------------------------
# 

 

2) np.finfo()

부동 소수점 float에는 np.finfo()을 사용한다. np.finfo()관련된 메뉴얼은 아래에서 확인할 수 있다.

https://docs.scipy.org/doc/numpy/reference/generated/numpy.finfo.html

 사용 방법은 np.iinfo()와 같다. 인수는 형 오브젝트(np.float64)나 문자열(float64, f8) 또는 값( 0.1 ) 등을 지정할 수 있으며, print()으로 개요 출력하거나, 각 속성의 값을 얻는 것 등이 가능하다.

fi64 = np.finfo(np.float64)
print(type(fi64))
# <class 'numpy.finfo'>

print(fi64)
# Machine parameters for float64
# ---------------------------------------------------------------
# precision =  15   resolution = 1.0000000000000001e-15
# machep =    -52   eps =        2.2204460492503131e-16
# negep =     -53   epsneg =     1.1102230246251565e-16
# minexp =  -1022   tiny =       2.2250738585072014e-308
# maxexp =   1024   max =        1.7976931348623157e+308
# nexp =       11   min =        -max
# ---------------------------------------------------------------
# 

print(fi64.max)
# 1.7976931348623157e+308

print(type(fi64.max))
# <class 'numpy.float64'>

print(fi64.min)
# -1.7976931348623157e+308

print(fi64.eps)
# 2.220446049250313e-16

print(fi64.bits)
# 64

print(fi64.iexp)
# 11

print(fi64.nmant)
# 52

 위의 예의 np.iinfo()보다 더 많은 정보,  예를 들면 eps로 엡실론, iexp, nmant으로 지수부를 포함한 가수부의 픽셀 수 등을 얻을 수 있다. 자세한 내용은 링크를 확인하기 바란다.


3. 문자열의 문자수에 관련한 주의사항

str이나 uniode등으로 문자열을 지정할 경우의 dtype은 <U1처럼 된다.

a_str = np.array([1, 2, 3], dtype=str)
print(a_str)
print(a_str.dtype)
# ['1' '2' '3']
# <U1

 앞의 <, >는 각각 리틀 엔디안, 빅 엔디안을 나타나고 있다. 대부분의 경우 이에 대해 그렇게 신경쓰지 않아도 된다. 마지막의 숫자는 문자수를 나타내며, 생성자에서 dtype을 str 또는 unicode도 지정한 경우, 요소 중 최대의 문자 수가 된다.

각각의 요소에 관련해서 문자수 만큼의 메모리가 보존되어 있기 때문에, 그 이상의 문자수의 문자열은 보존되지 못하고 잘리게 된다. 그러므로 미리 충분한 문자를 저장할 수 있는 형을 지정하는 것이 좋다.

a_str[0] = 'abcde'
print(a_str)
# ['a' '2' '3']

a_str10 = np.array([1, 2, 3], dtype='U10')
print(a_str10.dtype)
# <U10

a_str10[0] = 'abcde'
print(a_str10)
# ['abcde' '2' '3']

4. object형 : Python오브젝트에 대한 포인터 저장

 object형은 특수한 데이터 형으로, Python 객체에 대한 포인터를 저장한다. 각 요소의 데이터 자체는 각각의 메모리 영역에 저장되기 때문에, 하나의 배열 ndarray내의 여러 개 데이터형(로의 포인터)을 가질 수 있다.

a_object = np.array([1, 0.1, 'one'], dtype=object)
print(a_object)
print(a_object.dtype)
# [1 0.1 'one']
# object

print(type(a_object[0]))
print(type(a_object[1]))
print(type(a_object[2]))
# <class 'int'>
# <class 'float'>
# <class 'str'>

 

문자 수의 변경도 가능하다.

a_object[2] = 'oneONE'
print(a_object)
# [1 0.1 'oneONE']

 

복수의 데이터형을 가진 배열은 Python표준의 list 형도 가능하다. 그러나 list와 Numpy배열 ndarray는 연산자 작동 방식이 다르다. ndarray의 경우 각 요소에 대해 연산이 간단하게 가능하지만, 굳이 Numpy로 이러한 데이터를 작성하여 처리하는 메리트가 작을지도 모른다.

l = [1, 0.1, 'oneONE']
print(type(l[0]))
print(type(l[1]))
print(type(l[2]))
# <class 'int'>
# <class 'float'>
# <class 'str'>

print(a_object * 2)
# [2 0.2 'oneONEoneONE']

print(l * 2)
# [1, 0.1, 'oneONE', 1, 0.1, 'oneONE']

5. astype()을 사용한 데이터형 dtype 변환 (캐스트)

Numpy 배열 ndarray의 메소드 astype()로 데이터형 dtype을 변경(캐스트)하는 것도 가능하다.

d

type가 변경된 새로운 ndarray가 생성되어 이전의 ndarray는 변화가 없다.

import numpy as np

a = np.array([1, 2, 3])
print(a)
print(a.dtype)
# [1 2 3]
# int64

a_float = a.astype(np.float32)
print(a_float)
print(a_float.dtype)
# [1. 2. 3.]
# float32

print(a)
print(a.dtype)
# [1 2 3]
# int64

 

위에서 언급한 것 처럼, dtype은 다양한 작성 방법이 있다.

a_float = a.astype(float)
print(a_float)
print(a_float.dtype)
# [1. 2. 3.]
# float64

a_str = a.astype('str')
print(a_str)
print(a_str.dtype)
# ['1' '2' '3']
# <U21

a_int = a.astype('int32')
print(a_int)
print(a_int.dtype)
# [1 2 3]
# int32

6. astype()으로 변환(캐스트)할 때의 주의점 ; float에서 int로의 캐스트

float에서 int로 캐스트할 경우 소수점 이하와 잘리고, 마이너스값은 0으로 반올림된다.

a = np.arange(50).reshape((5, 10)) / 10 - 2
print(a)
print(a.dtype)
# [[-2.  -1.9 -1.8 -1.7 -1.6 -1.5 -1.4 -1.3 -1.2 -1.1]
#  [-1.  -0.9 -0.8 -0.7 -0.6 -0.5 -0.4 -0.3 -0.2 -0.1]
#  [ 0.   0.1  0.2  0.3  0.4  0.5  0.6  0.7  0.8  0.9]
#  [ 1.   1.1  1.2  1.3  1.4  1.5  1.6  1.7  1.8  1.9]
#  [ 2.   2.1  2.2  2.3  2.4  2.5  2.6  2.7  2.8  2.9]]
# float64

a_int = a.astype('int64')
print(a_int)
print(a_int.dtype)
# [[-2 -1 -1 -1 -1 -1 -1 -1 -1 -1]
#  [-1  0  0  0  0  0  0  0  0  0]
#  [ 0  0  0  0  0  0  0  0  0  0]
#  [ 1  1  1  1  1  1  1  1  1  1]
#  [ 2  2  2  2  2  2  2  2  2  2]]
# int64

 

np.round()을 사용하는 경우 짝수에 대한 반올림이 된다. (사사오입 원칙을 따른다) 반올림할 자리의 수가 5이면 반올림 할 때 앞자리의 숫자가 짝수면 내림하고 홀수면 올림한다. 즉 0.5가 1이 되는 것이 아니라 0이 된다.

>>> round(4.5)  #결과는 4
>>> round(3.5)  #결과는 4

 

 

Python표준의 round()도 동일하다. 위의 배열과 비교하면 -0.5가 0이 되었다.

print(np.round(a).astype(int))
# [[-2 -2 -2 -2 -2 -2 -1 -1 -1 -1]
#  [-1 -1 -1 -1 -1  0  0  0  0  0]
#  [ 0  0  0  0  0  0  1  1  1  1]
#  [ 1  1  1  1  1  2  2  2  2  2]
#  [ 2  2  2  2  2  2  3  3  3  3]]

 

-0.5가 -1로 되도록 하고 싶은 경우 아래와 같이 코드를 작성하면 된다.

def my_round(x, digit=0):
    p = 10 ** digit
    s = np.copysign(1, x)
    return (s * x * p * 2 + 1) // 2 / p * s

print(my_round(a).astype(int))
# [[-2 -2 -2 -2 -2 -2 -1 -1 -1 -1]
#  [-1 -1 -1 -1 -1 -1  0  0  0  0]
#  [ 0  0  0  0  0  1  1  1  1  1]
#  [ 1  1  1  1  1  2  2  2  2  2]
#  [ 2  2  2  2  2  3  3  3  3  3]]

7. 묵시적 형 변환

astype()를 사용한 명시적 형변환이 아닌 연산에 따라 암묵적으로 형변환이 되는 경우가 있다.

예를 들어 / 연산자를 사용한 나눗셈은 부동소수점 float를 리턴한다.

a = np.array([1, 2, 3])
print(a)
print(a.dtype)
# [1 2 3]
# int64

print((a / 1).dtype)
# float64

print((a / 1.0).dtype)
# float64

 

+, -, *, //, **의 연산자를 사용한 연산은 정수int끼리면 int정수, float가 포함되었다면 float형이 된다.

print((a + 1).dtype)
# int64

print((a + 1.0).dtype)
# float64

print((a - 1).dtype)
# int64

print((a - 1.0).dtype)
# float64

print((a * 1).dtype)
# int64

print((a * 1.0).dtype)
# float64

print((a // 1).dtype)
# int64

print((a // 1.0).dtype)
# float64

print((a ** 1).dtype)
# int64

print((a ** 1.0).dtype)
# float64

 

여기까지의 예는 numpy.ndarray와 스칼라 값과의 연산이지만, numpy.ndarray끼리의 연산에서도 동일하게 적용된다. 그리고 정수 int끼리도 비트수가 다르면 형변환이 발생한다.

ones_int16 = np.ones(3, np.int16)
print(ones_int16)
# [1 1 1]

ones_int32 = np.ones(3, np.int32)
print(ones_int32)
# [1 1 1]

print((ones_int16 + ones_int32).dtype)
# int32

 

 위의 예처럼, 기본적으로는 데이터 양이 많은 쪽의 형으로 변환된다고 생각하면 된다. 그러나 경우에 따라 원래의 numpy.ndarray는 어느 쪽으로도 형변환이 가능하다. 비트수가 중요한 처리의 경우는 astype()로 명시적으로 형태로 변환해 두는 편이 좋다.

ones_float16 = np.ones(3, np.float16)
print(ones_float16)
# [1. 1. 1.]

print((ones_int16 + ones_float16).dtype)
# float32

 또한 요소의 값 대입에서는 numpy.ndarray()의 형변환이 이루어지지 않는다. 예를 들어, 정수 int의 numpy.ndarray에 부동소수점 float 값을 대입해도 numpy.ndarray의 데이터형은 int 그대로이다. 대입한 float 값은 소수점 이하가 절삭된다.


참고자료

https://note.nkmk.me/python-numpy-dtype-astype/

728x90