Recent Posts
Recent Comments
Link
Today
Total
02-06 20:17
관리 메뉴

Hippo's data

다운캐스팅 (Downcasting) - upgrade 본문

Python

다운캐스팅 (Downcasting) - upgrade

Hippo's data 2025. 9. 10. 00:32
728x90

안녕하세요! 오늘은 upgrade된 다운캐스팅 (Downcasting)에 대해 살펴보겠습니다!!

사실 과거에 다운캐스팅 (Downcasting)과 관련하여 포스팅을 했었는데욥 최근에 다양한 전처리 업무를 진행하며 데이터를 다룰 때, 좀 더 엔지니어틱(?)한 스킬들을 여러가지 접했는데요!

이번에는 좀 더 디벨롭하여 포스팅을 진행해 보겠습니다! 

 

https://hipposdata.tistory.com/125

 

다운캐스팅 (Downcasting)

오늘은 다운캐스팅(Downcasting)에 대해 알아보겠습니다!데이터를 불러와서 모델링을 하다보면 데이터가 너무 커서 속도가 너무 오래걸리거나, 메모리 초과(OOM: out-of-memory) 오류가 발생하는 경우가

hipposdata.tistory.com

 

# 다운캐스팅 (Downcasting) 이란?

-> DataFrame의 각 컬럼 데이터 타입더 작은(메모리 적게 쓰는) 타입으로 바꿔주는 기법

예) float64 -> float32, int64 -> int32

-> 저장 크기, 메모리 사용량(RAM 상에서 차지하는 크기), 연산 속도 면에서 효율적

 

특히 실제 현업에서는 매우 큰 크기의 데이터를 다루는 경우도 있는데요 이 경우, 대용량 데이터를 처리 하는데 매우 오랜 시간이 걸리게 됩니다! 

이 때, 다운캐스팅이 데이터 처리 효율을 크게 높여주는 대안이 될 수 있습니다! 

 

 

# 다운캐스팅 동작 원리  

-> 다운캐스팅은 해당 칼럼의 데이터 타입을 더 작은 타입으로 바꿔주는 것이었는데요 

무조건적으로 작은 타입으로 바꿔준다면, 데이터 손실이 발생할 수 있습니다! 

그래서 해당 컬럼의 모든 값이 변환하려는 타입의 표현 범위 내에 있을 때만 변환하도록 해야 하는데요!

그렇다면 표현범위라는게 무엇일까요??

 

## 표현 범위란?

특정 데이터 타입이 가질 수 있는 값의 최소~최대 범위

  - 예시:
    - int8: -128 ~ 127
    - int16: -32,768 ~ 32,767
    - float16: 약 ±6.55e-5 ~ ±65,504
    - float32: 약 ±1.18e-38 ~ ±3.4e+38

 

즉, 데이터 손실을 막기 위해 해당 칼럼의 모든 값이 해당 표현 범위를 만족하는 가장 작은 데이터 타입으로 적용하여 변환되도록 코드를 구성해야 합니다!

만약 컬럼의 값 중 하나라도 변환하려는 타입의 범위를 벗어나면, 다운캐스팅 시 데이터 손실(오버플로우, 언더플로우, 정밀도 손실 등)이 발생할 수 있습니다

 

# 다운캐스팅 구현

다운캐스팅은 Pandas 라이브러리다운캐스트 파라미터를 통해 쉽게 구현할 수 있는데요

pd.to_numeric(df[col], downcast='integer')
pd.to_numeric(df[col], downcast='float')

 

참고) Pandas 공식 문서

https://pandas.pydata.org/docs/reference/api/pandas.to_numeric.html

 

pandas.to_numeric — pandas 2.3.2 documentation

Can be ‘integer’, ‘signed’, ‘unsigned’, or ‘float’. If not None, and if the data has been successfully cast to a numerical dtype (or if the data was numeric to begin with), downcast that resulting data to the smallest numerical dtype possib

pandas.pydata.org

해당 코드는 칼럼의 데이터 타입을  표현 범위를 만족하는 가장 작은 데이터 타입으로 변환해주지만, 

주의할 점은 integer(int) 형식은 데이터 손실이 일어나지 않지만, float 형식은 손실이 발생할 수 있다는 점인데요

 

- 정수형(int): 데이터 손실 없이 다운캐스팅 가능  
- 실수형(float): 정밀도 손실(소수점 자리수 감소) 가능성 있음

 

## 정밀도(precision)와 손실  

정밀도: 소수점 이하까지 얼마나 정확하게 표현하는지  
정밀도 손실: 변환 과정에서 소수점 이하 일부잘려나가거나, 값이 미세하게 달라지는 현상

 

즉, 해당 칼럼의 값이 표현범위는 만족하여 다운캐스팅이 진행 되지만,

컴퓨터의 한계로 인해 무한정으로 소수점 아래의 범위를 표현하지 못하므로 

float 타입에서는 소수점 아래의 값이 손실되는 정밀도 손실 현상이 발생할 수 있습니다!

(int 형식은 소수점 값이 없으므로 다운캐스팅시, 표현 범위를 만족하는 데이터타입으로 다운캐스팅 가능)

 

 

예제) 

import pandas as pd

# pd.to_numeric(df[col], downcast='float')  정밀도 손실 예시
large_number = 3.129831298739616912  # 2^24 + 1
series = pd.Series([large_number], dtype='float64')

print(f"원본: {series[0]}")                                  
result = pd.to_numeric(series, downcast='float')
print(f"변환 후: {result[0]}")                                 # 값이 바뀜!
print(f"값이 같은가?: {series[0] == result[0]}")               # False

 

원본: 3.129831298739617
변환 후: 3.129831314086914
값이 같은가?: False

 

-> float64 형식을 다운캐스팅할 경우, 표현범위(정수부분은 1자리)는 만족하여 다운캐스팅이 진행 되지만, 소수점 자리수는 데이터가 잘리는 정밀도 손실이 발생하게 됨

 

## float 타입별 정밀도  

float16: 소수점 유효 자릿수 약 3~4자리  
float32: 소수점 유효 자릿수 약 6~7자리  
float64: 소수점 유효 자릿수 약 15~16자리  
→ 유효 자릿수 초과 시 해당 부분이 잘려나가 정밀도 손실 발생

 

# 기존 다운캐스팅 코드 

def downcast(df, verbose=True):
    start_mem = df.memory_usage().sum() / 1024**2
    for col in df.columns:
        dtype_name = df[col].dtype.name
        if dtype_name == 'object':
            pass
        elif dtype_name == 'category':  # Use 'category' for categorical dtype
            pass
        elif dtype_name == 'bool':
            df[col] = df[col].astype('int8')
        elif dtype_name.startswith('int') or (df[col].dtype == 'float' and (df[col] % 1 == 0).all()):
            df[col] = pd.to_numeric(df[col], downcast='integer')
        else:
            df[col] = pd.to_numeric(df[col], downcast='float')
    end_mem = df.memory_usage().sum() / 1024**2
    if verbose:
        print('{:.1f}% 압축됨'.format(100 * (start_mem - end_mem) / start_mem))
   
    return df

# downcast(df)

 

# 안전한 다운캐스팅 코드 

-> 기존 다운캐스팅 구현 코드에서 float 형식의 소수점이 잘려나가는 데이터 손실(정밀도 손실)을 막기 위해 안전한 다운캐스팅 코드를 구현하였습니다!

 

기존과 동일: object, category 형식은 변환하지 않으며, bool 형식은 int8로, int 형식은 downcast='integer' 파라미터를 이용하여 #더 작은 정수 타입으로 변환

개선된 부분: float -> downcast='float' 파라미터 이용시 → safe_float_downcast() 함수로 대체

 

## safe_float_downcast() 함수 

 float64 → float32 → float16 순서로 다운캐스팅 시도하는데,  각 타입으로 변환했을 때 모든 값이 완전히 동일(==)해야만 변환되도록 구현(정밀도 손실로 소수점이 달라진 경우에는 변환하지 않음)

 

# 기존 다운캐스팅 함수와 거의 유사()
# object, category -> 변환X
# bool -> int8
# int -> downcast='integer' # 예) int64 → int32, int16, int8 등 더 작은 정수 타입으로 변환

# float -> downcast='float' → safe_float_downcast() 함수로 대체
# 예) float64 → float32 또는 float16 등 더 작은 실수 타입으로 변환


import numpy as np

def safe_float_downcast(series):
    """
    float64 → float32 → float16 순서로 다운캐스팅 시도.
    각 타입으로 변환했을 때 모든 값이 완전히 동일(==)해야만 변환.
    """
    org = series.astype('float64')

    # float16 시도
    f16 = org.astype('float16')
    if np.array_equal(org, f16):
        return f16

    # float32 시도
    f32 = org.astype('float32')
    if np.array_equal(org, f32):
        return f32

    # float64 유지
    return org


def safe_downcast(df, verbose=True):
    """
    정밀도 손실 없는 다운캐스팅 (float은 값 변화 없는 가장 작은 타입으로만 변환)
    """
    import numpy as np

    start_mem = df.memory_usage().sum() / 1024**2

    for col in df.columns:
        dtype_name = df[col].dtype.name
        if dtype_name == 'object' or dtype_name == 'category':
            continue
        elif dtype_name == 'bool':
            df[col] = df[col].astype('int8')
        elif dtype_name.startswith('int') or (df[col].dtype == 'float' and (df[col] % 1 == 0).all()):
            df[col] = pd.to_numeric(df[col], downcast='integer')
        elif dtype_name.startswith('float'):
            df[col] = safe_float_downcast(df[col])
    end_mem = df.memory_usage().sum() / 1024**2
    if verbose:
        print('{:.1f}% 압축됨'.format(100 * (start_mem - end_mem) / start_mem))
    return df

# safe_downcast (df)

-> 비록 데이터 압축률은 감소하겠지만, 데이터 손실없이 안전한 다운캐스팅을 진행할 수 있습니다!

 

# 번외 

# Parquet 파일, 다운캐스팅

Parquet 파일이란 .parquet 확장자로 저장되는 파일을 말하는데욥

일반적인 CSV파일에 비해 적은 용량으로 데이터를 저장할 수 있으므로, 대용량 데이터 저장시에 주로 활용하곤 합니다

 

다운캐스팅 후, Parquet으로 저장한다면, 저장 크기 감소 효과는 크지 않을 수 있는데요

Parquet내부적으로 값 범위와 정밀도에 맞춰 최적 타입(int32, float32 등)으로 저장하기 때문입니다!   (Parquet의 압축/인코딩이 이미 효율적이기 때문)  

 

하지만, 데이터를 불러와서 RAM 상에서 DataFrame을 다룰 때는 다운캐스팅 효과가 확실합니다! 

Parquet파일 Pandas로 읽으면 기본적으로 int64/float64로 로딩되는데요

-> pd.read_parquet('파일경로.parquet')

읽은 후 다시 다운캐스팅해야 메모리 절약이 가능합니다!

728x90