0Pricing
Pandas & NumPy Academy · 강의

메모리 절약을 위한 효율적인 데이터 형식

수치형 열을 int32/float32로 다운캐스팅하고 문자열 열을 Categorical로 변환해 DataFrame 메모리를 최대 70% 줄입니다.

메모리 절약을 위한 효율적인 데이터 형식은(는) CoddyKit의 무료 Pandas & NumPy Academy 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Pandas & NumPy Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Pandas & NumPy Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

데이터 형식이 성능에 영향을 미치는 이유

Pandas에서 모든 열에는 값이 메모리에 저장되는 방식과 작업 실행 속도를 결정하는 dtype(데이터 형식)이 있습니다. Pandas는 데이터를 불러올 때 기본적으로 큰 형식을 사용합니다. int64(값당 8바이트), float64(8바이트), object(가변 크기이며 문자열 하나에 보통 50~200바이트) 등이 이에 해당합니다. 수백만 개의 행에서는 더 작은 형식을 선택하면 메모리를 50~80% 줄이고, 캐시 활용도가 높아져 작업 속도를 2~5배 높일 수 있습니다.

import pandas as pd
import numpy as np

np.random.seed(0)
df = pd.DataFrame({
    'id': np.arange(1000000, dtype='int64'),
    'score': np.random.uniform(0, 100, 1000000).astype('float64'),
    'category': np.random.choice(['A','B','C','D'], 1000000)
})

mem = df.memory_usage(deep=True)
print('Memory per column:')
print(mem)
print(f'Total: {mem.sum() / 1e6:.1f} MB')

정수 열 다운캐스팅

열에 더 작은 범위에 들어가는 정수 값이 있다면 int64에서 더 작은 정수 형식으로 다운캐스팅하십시오. pd.to_numeric(series, downcast='integer')는 모든 값을 저장할 수 있는 가장 작은 정수 형식을 자동으로 선택합니다. int8(–128~127, 1바이트), int16(–32768~32767, 2바이트), int32(±20억, 4바이트) 중에서 선택하며, 필요한 경우에는 int64로 유지합니다. int8 열은 int64보다 메모리를 8배 적게 사용합니다.

import pandas as pd
import numpy as np

np.random.seed(0)
df = pd.DataFrame({
    'age': np.random.randint(18, 90, 500000).astype('int64'),
    'score': np.random.randint(0, 100, 500000).astype('int64'),
    'large_id': np.random.randint(0, 2**31, 500000).astype('int64')
})

# Downcast integers
for col in df.select_dtypes('int64').columns:
    df[col] = pd.to_numeric(df[col], downcast='integer')

print('Dtypes after downcasting:')
print(df.dtypes)
print(f'\nMemory: {df.memory_usage(deep=True).sum()/1e6:.2f} MB')

실수 열 다운캐스팅

pd.to_numeric(series, downcast='float')는 정밀도가 허용되는 범위에서 float64를 float32로 변환합니다(8바이트 대신 4바이트). float32의 정밀도는 소수점 이하가 아니라 전체적으로 약 7자리이며, float64는 15자리입니다. 대부분의 분석 작업(백분율, 가격, 정규화된 특성)에서는 float32의 정밀도로 충분합니다. 높은 정밀도가 필요한 과학 계산에서는 float64를 유지하십시오. 실수 정밀도를 절반으로 줄이면 메모리 사용량도 절반이 되고 캐시 성능이 향상됩니다.

import pandas as pd
import numpy as np

np.random.seed(0)
df = pd.DataFrame({'lat': np.random.uniform(-90, 90, 1000000),
                   'lon': np.random.uniform(-180, 180, 1000000),
                   'temp': np.random.uniform(-40, 50, 1000000)})

print('Before:', df.dtypes.unique())
print(f'Memory: {df.memory_usage(deep=True).sum()/1e6:.1f} MB')

for col in df.select_dtypes('float64').columns:
    df[col] = pd.to_numeric(df[col], downcast='float')

print('After:', df.dtypes.unique())
print(f'Memory: {df.memory_usage(deep=True).sum()/1e6:.1f} MB')

Categorical 데이터 형식

Categorical dtype은 반복되는 값이 있는 문자열 열에서 메모리를 가장 크게 절약할 수 있는 방법입니다. 같은 문자열(예: 'Electronics')을 수천 번 저장하는 대신, Pandas는 고유 값의 사전과 각 행에 해당하는 작은 정수 코드를 저장합니다. 100만 개의 행과 고유 범주 50개만 있는 열은 약 50MB(object dtype)에서 약 1MB(Categorical)로 줄어들어 메모리를 50배 절약할 수 있습니다.

import pandas as pd
import numpy as np

np.random.seed(0)
categories = ['Electronics', 'Clothing', 'Books', 'Food', 'Furniture',
              'Sports', 'Toys', 'Health', 'Garden', 'Automotive']

df = pd.DataFrame({'product_cat': np.random.choice(categories, 1000000)})

mem_before = df.memory_usage(deep=True).sum()
df['product_cat'] = df['product_cat'].astype('category')
mem_after = df.memory_usage(deep=True).sum()

print(f'Object dtype: {mem_before/1e6:.1f} MB')
print(f'Categorical:  {mem_after/1e6:.2f} MB')
print(f'Reduction: {mem_before/mem_after:.0f}x')

Categorical이 메모리를 절약하는 경우

Categorical dtype은 열의 고유 값 개수가 전체 행 수보다 훨씬 적을 때만 메모리를 절약합니다. 손익분기점은 고유 값 개수와 전체 행 수의 비율(카디널리티)이 약 50%를 넘는 지점입니다. 모든 행이 서로 다른 문자열을 가진다면 Categorical은 코드 배열과 범주 배열을 모두 저장하므로 object dtype보다 오히려 메모리를 더 많이 사용합니다. 변환하기 전에 항상 df['col'].nunique() / len(df)를 확인하십시오. 비율이 0.5 미만(이상적으로는 0.05 미만)이면 Categorical이 도움이 됩니다.

import pandas as pd
import numpy as np

def memory_gain(df, col):
    n = len(df)
    n_unique = df[col].nunique()
    ratio = n_unique / n
    mem_obj = df[col].memory_usage(deep=True)
    df2 = df.copy()
    df2[col] = df2[col].astype('category')
    mem_cat = df2[col].memory_usage(deep=True)
    print(f'{col}: {n_unique} unique / {n} total (ratio={ratio:.3f})')
    print(f'  Object: {mem_obj/1e6:.2f} MB → Categorical: {mem_cat/1e6:.2f} MB')
    print(f'  {"Saves" if mem_cat < mem_obj else "Wastes"} {abs(mem_obj-mem_cat)/1e6:.2f} MB')

np.random.seed(0)
df = pd.DataFrame({
    'low_card': np.random.choice(['A','B','C'], 500000),   # low cardinality
    'high_card': [f'id_{i}' for i in range(500000)]         # high cardinality
})
memory_gain(df, 'low_card')
memory_gain(df, 'high_card')

Categorical로 GroupBy 성능 향상

메모리 절약 외에도 Categorical dtype은 groupby 작업을 빠르게 합니다. Pandas가 그룹의 경계를 찾을 때 문자열을 해시하는 대신 정수 코드를 직접 사용할 수 있기 때문입니다. 지역, 제품 범주, 상태처럼 카디널리티가 낮은 문자열 열로 수백만 개의 행을 그룹화하는 DataFrame에서는 groupby 전에 해당 열을 Categorical로 변환하면 2~5배의 속도 향상을 얻을 수 있습니다.

import pandas as pd
import numpy as np
import timeit

np.random.seed(0)
df = pd.DataFrame({
    'region': np.random.choice(['North','South','East','West'], 1000000),
    'sales': np.random.randn(1000000)
})

# Object dtype groupby
t1 = timeit.timeit(lambda: df.groupby('region')['sales'].mean(), number=50)

# Categorical dtype groupby
df2 = df.copy()
df2['region'] = df2['region'].astype('category')
t2 = timeit.timeit(lambda: df2.groupby('region')['sales'].mean(), number=50)

print(f'Object dtype groupby: {t1/50*1000:.2f} ms')
print(f'Categorical groupby:  {t2/50*1000:.2f} ms')
print(f'Speedup: {t1/t2:.1f}x')

플래그 열에 boolean dtype 사용하기

이진 열(True/False, 0/1, yes/no)은 흔히 object 또는 int64로 저장됩니다. 이를 bool dtype으로 변환하면 값 하나당 1바이트만 사용합니다(int64의 8바이트 또는 문자열 'yes'/'no'의 50바이트 이상과 비교). 열에 0/1 또는 True/False 값만 들어 있는지 확인한 후 astype(bool)을 사용하십시오. 불리언 열은 Pandas가 내부적으로 비트 연산을 사용할 수 있으므로 필터링도 더 빠르게 합니다.

import pandas as pd
import numpy as np

np.random.seed(0)
df = pd.DataFrame({
    'is_premium': np.random.choice([0, 1], 1000000).astype('int64'),
    'has_discount': np.random.choice(['yes', 'no'], 1000000)
})

print('Before:')
print(df.dtypes)
print(f'Memory: {df.memory_usage(deep=True).sum()/1e6:.1f} MB')

df['is_premium'] = df['is_premium'].astype(bool)
df['has_discount'] = df['has_discount'].map({'yes': True, 'no': False}).astype(bool)

print('\nAfter:')
print(df.dtypes)
print(f'Memory: {df.memory_usage(deep=True).sum()/1e6:.2f} MB')

형식 최적화 자동화

모든 최적화를 자동으로 적용하는 재사용 가능한 optimise_dtypes(df) 함수를 작성합니다. 정수 다운캐스팅, 실수 다운캐스팅, 카디널리티가 낮은 객체를 Categorical로 변환하기, 0/1 정수를 불리언으로 변환하기를 모두 처리해야 합니다. 데이터 로드 시 이 함수를 실행하여 처음부터 메모리 사용량을 최소화합니다. 이렇게 하면 각 팀원이 각 단계를 수동으로 적용해야 한다는 사실을 기억하지 않아도 동일한 데이터세트를 로드할 때마다 최적화된 버전을 사용하게 됩니다.

import pandas as pd
import numpy as np

def optimise_dtypes(df, cat_threshold=0.5):
    '''Reduce DataFrame memory by choosing smaller dtypes.'''
    for col in df.columns:
        col_type = df[col].dtype
        if col_type == 'int64':
            df[col] = pd.to_numeric(df[col], downcast='integer')
        elif col_type == 'float64':
            df[col] = pd.to_numeric(df[col], downcast='float')
        elif col_type == 'object':
            cardinality = df[col].nunique() / len(df)
            if cardinality < cat_threshold:
                df[col] = df[col].astype('category')
    return df

# Test
np.random.seed(0)
df = pd.DataFrame({'a': np.random.randint(0,100,500000),
                   'b': np.random.randn(500000),
                   'c': np.random.choice(['X','Y','Z'],500000)})

before = df.memory_usage(deep=True).sum()
df = optimise_dtypes(df)
after = df.memory_usage(deep=True).sum()
print(f'Before: {before/1e6:.2f} MB → After: {after/1e6:.2f} MB ({before/after:.1f}x reduction)')

음수가 아닌 데이터에 사용하는 부호 없는 정수 형식

열에 음수가 아닌 정수만 포함되어 있다면(예: ID, 개수 또는 수량) 부호 없는 정수 형식을 사용합니다. uint8(0–255), uint16(0–65535), uint32(0–40억), uint64 중에서 선택할 수 있습니다. 부호 없는 형식은 부호 있는 형식과 바이트 크기는 같지만 양수 범위에서 최대 두 배 큰 값을 저장할 수 있습니다. 예를 들어 0부터 60,000까지인 상품 ID는 int32(4바이트) 대신 uint16(2바이트)에 저장할 수 있습니다. 열에 음수 값이 없는지 확인한 후 astype('uint16')을 사용합니다.

import pandas as pd
import numpy as np

np.random.seed(0)
df = pd.DataFrame({
    'product_id': np.random.randint(0, 50000, 1000000).astype('int64'),
    'quantity': np.random.randint(0, 255, 1000000).astype('int64')
})

print('Before (int64):', df.memory_usage(deep=True).sum() / 1e6, 'MB')

# product_id fits in uint16 (0-65535)
df['product_id'] = df['product_id'].astype('uint16')
# quantity fits in uint8 (0-255)
df['quantity'] = df['quantity'].astype('uint8')

print('After (uint16+uint8):', df.memory_usage(deep=True).sum() / 1e6, 'MB')
print('\nDtypes:', df.dtypes.to_dict())

각 최적화 단계 후 메모리 확인

최적화를 한 번에 하나씩 적용하고 각 단계 후에 메모리를 확인합니다. 그러면 각 기법이 얼마나 기여했는지 정확히 파악할 수 있습니다. 일반적인 대형 DataFrame은 2GB(모든 기본 데이터 형식)에서 600MB(정수 다운캐스팅), 다시 300MB(실수 다운캐스팅), 마지막으로 200MB(문자열 열에 Categorical 적용)까지 줄어들 수 있습니다. 이러한 내역을 문서화하면 코드베이스에서 익숙하지 않은 데이터 형식을 본 팀원에게 추가적인 복잡성을 도입한 이유를 설명하는 데 도움이 됩니다.

import pandas as pd
import numpy as np

np.random.seed(0)
df = pd.DataFrame({
    'user_id': np.random.randint(0, 99999, 1000000).astype('int64'),
    'age': np.random.randint(18, 80, 1000000).astype('int64'),
    'revenue': np.random.uniform(0, 5000, 1000000).astype('float64'),
    'country': np.random.choice(['US','UK','DE','FR','JP'], 1000000),
    'tier': np.random.choice(['free','basic','pro','enterprise'], 1000000)
})

def mem_mb(df):
    return df.memory_usage(deep=True).sum() / 1e6

print(f'Start: {mem_mb(df):.1f} MB')

for c in ['user_id','age']:
    df[c] = pd.to_numeric(df[c], downcast='integer')
print(f'After int downcast: {mem_mb(df):.1f} MB')

df['revenue'] = pd.to_numeric(df['revenue'], downcast='float')
print(f'After float downcast: {mem_mb(df):.1f} MB')

for c in ['country','tier']:
    df[c] = df[c].astype('category')
print(f'After Categorical: {mem_mb(df):.1f} MB')

최적화된 형식 저장 및 다시 불러오기

CSV로 저장하면 모든 값이 다시 텍스트로 변환되므로 최적화된 데이터 형식이 사라집니다. 데이터 형식을 유지하려면 df.to_parquet('file.parquet')을 사용하여 Parquet 형식으로 저장합니다. Parquet은 int32, float32 및 Categorical 형식을 보존합니다. pd.read_parquet으로 다시 불러오면 최적화 함수를 다시 실행하지 않아도 DataFrame이 동일한 메모리 효율적 형식을 유지합니다. 또한 대용량 파일에서는 Parquet이 CSV보다 훨씬 빠르게 로드됩니다.

import pandas as pd
import numpy as np

np.random.seed(0)
df = pd.DataFrame({
    'age': np.random.randint(18, 80, 100000).astype('int8'),   # already optimised
    'score': np.random.randn(100000).astype('float32'),
    'tier': pd.Categorical(np.random.choice(['free','pro'], 100000))
})
print('Dtypes:', df.dtypes.to_dict())

# Save to Parquet (preserves dtypes)
df.to_parquet('/tmp/optimised.parquet', index=False)

# Reload — dtypes preserved!
df_loaded = pd.read_parquet('/tmp/optimised.parquet')
print('\nLoaded dtypes:', df_loaded.dtypes.to_dict())
print(f'Memory: {df_loaded.memory_usage(deep=True).sum()/1e6:.2f} MB')

빠른 확인

이 강의에서 배운 메모리 효율적인 데이터 형식에 대한 이해도를 확인합니다.

강의 요약

이 강의에서는 다음을 배웠습니다. pd.to_numeric(downcast='integer')는 정수 열을 8바이트에서 1~4바이트로 줄이고, pd.to_numeric(downcast='float')는 실수에 필요한 메모리를 절반으로 줄입니다. Categorical dtype은 카디널리티가 낮은 문자열 열의 크기를 최대 50배 줄이는 동시에 groupby도 더 빠르게 수행하도록 합니다. 또한 Parquet 형식은 파일 저장 및 로드 과정에서 최적화된 데이터 형식을 보존합니다. 다음으로는 사용 가능한 RAM을 초과하는 파일을 처리하는 청크 단위 읽기를 살펴봅니다.

자주 묻는 질문

“메모리 절약을 위한 효율적인 데이터 형식” 강의는 무료인가요?

네 — “메모리 절약을 위한 효율적인 데이터 형식” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Pandas & NumPy Academy 강의 전체를 잠금 해제할 수 있습니다. Pandas & NumPy Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“메모리 절약을 위한 효율적인 데이터 형식”에서 뭘 배우나요?

수치형 열을 int32/float32로 다운캐스팅하고 문자열 열을 Categorical로 변환해 DataFrame 메모리를 최대 70% 줄입니다. 브라우저에서 직접 실행하는 실습 코드로 Pandas & NumPy Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Pandas & NumPy Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Pandas & NumPy Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.

“메모리 절약을 위한 효율적인 데이터 형식” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Pandas & NumPy Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Pandas & NumPy Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. timeit과 memory_profiler로 프로파일링하기
  2. iterrows와 Python 반복문 피하기
  3. 메모리 절약을 위한 효율적인 데이터 형식
  4. 대용량 파일 청크 단위 읽기
← Pandas & NumPy Academy(으)로 돌아가기