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 값은 소수점 이하가 절삭된다.
참고자료
'IT > 언어' 카테고리의 다른 글
[python] python의 변수 사용 범위 정리 (1) (0) | 2020.08.18 |
---|---|
[python] python의 sort함수에 사용되는 lambda에 대한 이해 (0) | 2020.08.17 |
[python] 머신러닝을 위한 Python(8) ; Data Cleansing (0) | 2020.06.05 |
[python] 머신러닝을 위한 Python(7) ; Data Handling - Pandas # 2 (0) | 2020.06.05 |
[python] 머신러닝을 위한 Python(6); Data Handling - Pandas # 1 (0) | 2020.06.04 |