직전 포스팅에 이어, 데이터를 보다 효율적으로 다룰 수 있는 또 다른 방법을 알아보자. int와 float을 활용한 최적화이다.
int(정수형)와 float(부동 소수점) 데이터 타입은 여러 종류가 있고, 각각 메모리 크기와 표현할 수 있는 값의 범위는 다르다.
예를 들어, int8은 메모리 사용량이 1byte인 반면 int16은 메모리 사용량이 2byte이다. 두 배 정도 차이난다. 같은 원리로, int8과 int64의 메모리 사용량은 각각 1byte와 8byte로 8배 정도 차이가 난다. 만약 int8로도 충분히 표현 가능한 데이터임에도 불구하고 int64를 사용하고 있다면 쓸데없이 메모리를 낭비하고 있는 것이다. 이는 float도 마찬가지이다. float16은 2byte, float32는 4byte, float64는 8byte를 차지한다.
각 데이터 타입은 메모리 사용량과 값의 범위 사이에서 적절한 균형을 잡기 위해 선택된다. 매우 큰 정수를 처리하거나 높은 정밀도가 필요한 계산에서는 int64나 float64를 사용할 수 있으나, 메모리를 절약하고자 할때는 int16이나 float16을 사용하는게 적절할 것이다. 불필요하게 높은 메모리를 사용하고 있는 경우 Type Casting을 통해 메모리를 절약해보자.
꽤나 간단하다.
이전 포스팅과 같은 데이터를 사용하겠다.
데이터 및 참고 소스코드
https://github.com/s-heisler/pycon2017-optimizing-pandas/tree/master/pyCon%20materials
import pandas as pd
import numpy as np
df = pd.read_csv('new_york_hotels.csv', encoding='cp1252')
print(df.shape)
df.head()
object와 같은 데이터 타입은 그냥 넘기고, int와 float만 건들여준다. 원리는 현재 우리가 가진 데이터 내 최댓값과 최솟값을 구하고, 해당 범위에 해당하는 최적의 dtype을 적용시키는 것이다.
def reduce_mem_usage(df):
# 1MB = 1024KB = 1024Byte -> 1M = 1024**2Byte
start_mem = df.memory_usage().sum() / 1024**2
print('Memory usage of dataframe is {:.2f} MB'.format(start_mem)) # 초기 메모리 기록
for col in df.columns: # 각 열을 순회
col_type = df[col].dtype # 각 열의 데이터 유형 가져옴
if str(col_type) in ["category", "object"]: # category라면 다음 열로 넘어감
continue
else: # 수치형일 경우 -> 최적화 진행
# 해당 열의 최솟값과 최댓값 계산
c_min = df[col].min()
c_max = df[col].max()
# 정수형일 경우(dtype의 첫 세 글자가 int)
if str(col_type)[:3] == 'int':
# int8 범위 내에 들어온다면 -> 데이터 타입 int8로 변경
if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
df[col] = df[col].astype(np.int8)
# int16 범위 내에 들어온다면 -> 데이터 타입 int16로 변경
elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
df[col] = df[col].astype(np.int16)
# int32 범위 내에 들어온다면 -> 데이터 타입 int32로 변경
elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
df[col] = df[col].astype(np.int32)
# 어떠한 범위도 해당 안된다면 -> 데이터 타입 int64로 변경
else:
df[col] = df[col].astype(np.int64)
else:
# float16 범위 내에 들어온다면 -> 데이터 타입 float16로 변경
if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
df[col] = df[col].astype(np.float16)
# float32 범위 내에 들어온다면 -> 데이터 타입 float32로 변경
elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
df[col] = df[col].astype(np.float32)
# 어떠한 범위도 해당 안된다면 -> 데이터 타입 float64로 변경
else:
df[col] = df[col].astype(np.float64)
end_mem = df.memory_usage().sum() / 1024**2 # Memory usage after optimization
print('Memory usage after optimization is: {:.2f} MB'.format(end_mem)) # 변경 후 메모리 기록
print('Decreased by {:.1f}%'.format(100 * (start_mem - end_mem) / start_mem))
reduce_mem_usage(df)
>>> Memory usage of dataframe is 0.14 MB
>>> Memory usage after optimization is: 0.08 MB
>>> Decreased by 38.6%
0.14MB를 차지하던 데이터가 0.08MB로 줄어들었다. 메모리를 38.6%나 절약한 상태로 데이터 분석을 진행할 수 있으니, 대용량 데이터를 다룬다면 이러한 방식의 데이터 최적화도 유용할 것이다.