0Pricing
Pandas & NumPy Academy · Lekcja

Wydajne typy danych do redukcji pamięci

Zmniejszaj rozmiar typów liczbowych kolumn do int32/float32 i konwertuj kolumny tekstowe na Categorical, aby zmniejszyć zużycie pamięci przez DataFrame nawet o 70%.

Wydajne typy danych do redukcji pamięci to bezpłatna lekcja Pandas & NumPy Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Pandas & NumPy Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Pandas & NumPy Academy zawiera 4 lekcji w sumie.

Dlaczego typy danych wpływają na wydajność

W Pandas każda kolumna ma właściwość dtype (typ danych), która określa sposób przechowywania wartości w pamięci oraz szybkość wykonywania operacji. Podczas wczytywania danych Pandas domyślnie używa szerokich typów: int64 (8 bajtów na wartość), float64 (8 bajtów) oraz object (zmiennie, często 50–200 bajtów na ciąg znaków). W przypadku milionów wierszy wybór mniejszych typów może zmniejszyć zużycie pamięci o 50–80% i przyspieszyć operacje 2–5 razy dzięki lepszemu wykorzystaniu pamięci podręcznej.

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')

Zmniejszanie typu kolumn całkowitoliczbowych

Jeśli wartości całkowitoliczbowe w kolumnie mieszczą się w mniejszym zakresie, należy zmienić jej typ z int64 na mniejszy typ całkowitoliczbowy. pd.to_numeric(series, downcast='integer') automatycznie wybiera najmniejszy typ całkowitoliczbowy, który może pomieścić wszystkie wartości: int8 (–128 do 127, 1 bajt), int16 (–32768 do 32767, 2 bajty), int32 (±2 miliardy, 4 bajty) albo pozostawia int64, jeśli jest to konieczne. Kolumna typu int8 zużywa 8 razy mniej pamięci niż kolumna typu int64.

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')

Zmniejszanie typu kolumn zmiennoprzecinkowych

pd.to_numeric(series, downcast='float') konwertuje float64 na float32 (4 bajty zamiast 8), wszędzie tam, gdzie pozwala na to precyzja. float32 zapewnia około 7 cyfr znaczących po przecinku, w porównaniu z 15 dla float64. W większości zadań analitycznych (procenty, ceny, znormalizowane cechy) precyzja float32 jest wystarczająca. W obliczeniach naukowych wymagających wysokiej precyzji należy pozostać przy float64. Zmniejszenie precyzji zmiennoprzecinkowej o połowę zmniejsza zużycie pamięci o połowę i poprawia wydajność pamięci podręcznej.

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')

Typ danych Categorical

Typ Categorical zapewnia największą oszczędność pamięci w przypadku kolumn tekstowych z powtarzającymi się wartościami. Zamiast przechowywać ten sam ciąg znaków (np. 'Electronics') tysiące razy, Pandas przechowuje słownik unikatowych wartości oraz kompaktowy kod całkowitoliczbowy dla każdego wiersza. Kolumna z 1 milionem wierszy i zaledwie 50 unikatowymi kategoriami zmniejsza rozmiar z około 50 MB (typ object) do około 1 MB (Categorical) — to 50-krotne ograniczenie zużycia pamięci.

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')

Kiedy Categorical oszczędza pamięć

Typ Categorical oszczędza pamięć tylko wtedy, gdy kolumna ma znacznie mniej unikatowych wartości niż wierszy. Punkt graniczny występuje, gdy stosunek liczby unikatowych wartości do całkowitej liczby wierszy (kardynalność) przekracza około 50%: jeśli każdy wiersz zawiera unikatowy ciąg znaków, Categorical faktycznie zużywa więcej pamięci niż typ object, ponieważ przechowuje zarówno tablicę kodów, jak i tablicę kategorii. Przed konwersją należy zawsze sprawdzić df['col'].nunique() / len(df) — stosunek poniżej 0,5 (a najlepiej poniżej 0,05) oznacza, że Categorical przyniesie korzyści.

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 zwiększa wydajność GroupBy

Oprócz oszczędności pamięci typ Categorical również przyspiesza operacje groupby, ponieważ Pandas może bezpośrednio używać kodów całkowitoliczbowych zamiast haszować ciągi znaków w celu znalezienia granic grup. W przypadku DataFrame zawierających miliony wierszy, grupowanych według kolumn tekstowych o małej kardynalności (takich jak region, kategoria produktu lub status), konwersja tych kolumn na Categorical przed operacją groupby może zapewnić przyspieszenie 2–5 razy.

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')

Używanie typu boolean dla kolumn flag

Kolumny binarne (True/False, 0/1, yes/no) są często przechowywane jako object lub int64. Konwersja na typ bool zużywa tylko 1 bajt na wartość (w porównaniu z 8 bajtami dla int64 lub ponad 50 bajtami dla ciągu znaków 'yes'/'no'). Należy użyć astype(bool) po upewnieniu się, że kolumna zawiera wyłącznie wartości 0/1 lub True/False. Kolumny boolowskie umożliwiają również szybsze filtrowanie, ponieważ Pandas może wewnętrznie używać operacji bitowych.

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')

Automatyzacja optymalizacji typów

Należy napisać wielokrotnego użytku funkcję optimise_dtypes(df), która automatycznie stosuje wszystkie optymalizacje: zmniejsza typy całkowitoliczbowe, zmniejsza typy zmiennoprzecinkowe, konwertuje obiekty o małej liczbie unikalnych wartości na Categorical oraz konwertuje liczby całkowite 0/1 na bool. Funkcję należy uruchamiać podczas wczytywania danych, aby od początku minimalizować zużycie pamięci. Dzięki temu każdy członek zespołu wczytujący ten sam zbiór danych otrzyma jego zoptymalizowaną wersję, bez konieczności pamiętania o ręcznym zastosowaniu każdego kroku.

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)')

Typy liczb całkowity bez znaku dla danych nieujemnych

Jeśli kolumna zawiera wyłącznie nieujemne liczby całkowite, takie jak identyfikatory, liczności lub ilości, należy użyć typów liczb całkowity bez znaku: uint8 (0–255), uint16 (0–65535), uint32 (0–4 miliardów) lub uint64. Typy bez znaku zajmują tyle samo bajtów co odpowiadające im typy ze znakiem, ale mogą przechowywać wartości dodatnie nawet dwukrotnie większe. Na przykład identyfikator produktu z zakresu od 0 do 60 000 mieści się w typie uint16 (2 bajty), a nie int32 (4 bajty). Po upewnieniu się, że kolumna nie zawiera wartości ujemnych, należy użyć 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())

Sprawdzanie pamięci po każdym kroku optymalizacji

Optymalizacje należy stosować pojedynczo i po każdym kroku sprawdzać zużycie pamięci. Dzięki temu dokładnie widać, jaki efekt przynosi każda technika. Typowy duży DataFrame może zmniejszyć rozmiar z 2 GB (wszystkie domyślne typy danych) do 600 MB (zmniejszenie typów całkowitoliczbowych), następnie do 300 MB (zmniejszenie typów zmiennoprzecinkowych), a później do 200 MB (użycie Categorical dla kolumn tekstowych). Udokumentowanie tego podziału pomaga uzasadnić dodatkową złożoność członkom zespołu, którzy widzą w kodzie nieznane typy danych.

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')

Zapisywanie i ponowne wczytywanie zoptymalizowanych typów

Zoptymalizowane typy danych zostaną utracone podczas zapisu do CSV, ponieważ wszystkie wartości zostaną ponownie zinterpretowane jako tekst. Aby zachować typy danych, należy zapisywać dane w formacie Parquet za pomocą df.to_parquet('file.parquet') — Parquet zachowuje typy int32, float32 oraz Categorical. Po ponownym wczytaniu za pomocą pd.read_parquet DataFrame będzie zawierać te same oszczędne pod względem pamięci typy, bez konieczności ponownego uruchamiania funkcji optymalizującej. Parquet wczytuje również duże pliki znacznie szybciej niż 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')

Szybki sprawdzian

Sprawdź swoją wiedzę na temat oszczędnych pod względem pamięci typów danych z tej lekcji.

Podsumowanie lekcji

W tej lekcji nauczyłeś się, że: pd.to_numeric(downcast='integer') zmniejsza rozmiar kolumn całkowitoliczbowych z 8 bajtów do 1–4 bajtów, pd.to_numeric(downcast='float') zmniejsza o połowę zużycie pamięci przez liczby zmiennoprzecinkowe, typ danych Categorical zmniejsza rozmiar kolumn tekstowych o małej liczbie unikalnych wartości nawet 50-krotnie, a także przyspiesza operacje groupby, natomiast format Parquet zachowuje zoptymalizowane typy danych podczas zapisywania i wczytywania plików. W następnej części omówimy wczytywanie porcjami — przetwarzanie plików, które przekraczają dostępną pamięć RAM.

Często zadawane pytania

Czy lekcja „Wydajne typy danych do redukcji pamięci” jest bezpłatna?

Tak — pełny tekst „Wydajne typy danych do redukcji pamięci” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Pandas & NumPy Academy, przejdź na CoddyKit PRO. Kurs Pandas & NumPy Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Wydajne typy danych do redukcji pamięci”?

Zmniejszaj rozmiar typów liczbowych kolumn do int32/float32 i konwertuj kolumny tekstowe na Categorical, aby zmniejszyć zużycie pamięci przez DataFrame nawet o 70%. Ćwiczysz Pandas & NumPy Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Pandas & NumPy Academy?

Nie wymagamy żadnego doświadczenia. Pandas & NumPy Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Wydajne typy danych do redukcji pamięci”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Pandas & NumPy Academy?

Tak. Każda lekcja Pandas & NumPy Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Profilowanie za pomocą timeit i memory_profiler
  2. Unikanie iterrows i pętli Pythona
  3. Wydajne typy danych do redukcji pamięci
  4. Odczyt dużych plików partiami
← Powrót do Pandas & NumPy Academy