Tehokkaat tietotyypit muistin säästämiseen
Muuntaa numeeriset sarakkeet pienemmiksi int32- ja float32-tyypeiksi ja merkkijonosarakkeet Categorical-tyypiksi vähentääksenne DataFrame-rakenteen muistinkäyttöä jopa 70 prosenttia.
Tehokkaat tietotyypit muistin säästämiseen on ilmainen Pandas & NumPy Academy-oppitunti CoddyKitissä. Tämä on oppitunti 3/4. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu Pandas & NumPy Academy-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. Pandas & NumPy Academy-kurssilla on yhteensä 4 oppituntia.
Miksi tietotyypit vaikuttavat suorituskykyyn
Pandasissa jokaisella sarakkeella on dtype (tietotyyppi), joka määrittää, miten arvot tallennetaan muistiin ja kuinka nopeasti operaatiot suoritetaan. Pandas käyttää datan lataamisen yhteydessä oletusarvoisesti leveitä tyyppejä: int64 (8 tavua arvoa kohden), float64 (8 tavua) ja object (vaihteleva määrä, usein 50–200 tavua merkkijonoa kohden). Miljoonien rivien tapauksessa pienempien tyyppien valitseminen voi vähentää muistin käyttöä 50–80 prosenttia ja nopeuttaa operaatioita 2–5-kertaisiksi paremman välimuistin hyödyntämisen ansiosta.
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')Kokonaislukusarakkeiden downcast-muunnos
Jos sarake sisältää kokonaislukuarvoja, jotka mahtuvat pienempään vaihteluväliin, muunna se tyypistä int64 pienemmäksi kokonaislukutyypiksi. pd.to_numeric(series, downcast='integer') valitsee automaattisesti pienimmän kokonaislukutyypin, johon kaikki arvot mahtuvat: int8 (–128–127, 1 tavu), int16 (–32768–32767, 2 tavua), int32 (±2 miljardia, 4 tavua), tai säilyttää tyypin int64, jos se on tarpeen. int8-sarake käyttää 8 kertaa vähemmän muistia kuin int64-sarake.
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')Liukulukusarakkeiden downcast-muunnos
pd.to_numeric(series, downcast='float') muuntaa tyypin float64 tyypiksi float32 (4 tavua 8 tavun sijaan) aina, kun tarkkuus sen sallii. float32-tyypin tarkkuus on noin 7 desimaalinumeroa, kun taas float64-tyypin tarkkuus on 15 desimaalinumeroa. Useimmissa analytiikkatehtävissä (prosenttiosuudet, hinnat ja normalisoidut piirteet) float32-tyypin tarkkuus riittää. Suurta tarkkuutta vaativassa tieteellisessä laskennassa kannattaa käyttää edelleen tyyppiä float64. Liukulukujen tarkkuuden puolittaminen puolittaa muistin käytön ja parantaa välimuistin suorituskykyä.
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-tietotyyppi
Categorical-dtype tarjoaa suurimman yksittäisen muistihyödyn merkkijonosarakkeissa, joissa arvot toistuvat. Sen sijaan että sama merkkijono (esimerkiksi 'Electronics') tallennettaisiin tuhansia kertoja, Pandas tallentaa sanaston yksilöllisistä arvoista sekä tiiviin kokonaislukukoodin jokaiselle riville. Sarake, jossa on miljoona riviä ja vain 50 yksilöllistä luokkaa, pienenee noin 50 megatavusta (object-dtype) noin 1 megatavuun (Categorical) — muistin käyttö vähenee 50-kertaisesti.
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')Milloin Categorical säästää muistia
Categorical-dtype säästää muistia vain silloin, kun sarakkeessa on huomattavasti vähemmän yksilöllisiä arvoja kuin rivejä yhteensä. Kannattavuusraja saavutetaan, kun yksilöllisten arvojen suhde rivien kokonaismäärään (kardinaliteetti) ylittää noin 50 prosenttia: jos jokaisella rivillä on yksilöllinen merkkijono, Categorical käyttää itse asiassa enemmän muistia kuin object-dtype, koska se tallentaa sekä kooditaulukon että luokkataulukon. Tarkista aina df['col'].nunique() / len(df) ennen muuntamista — alle 0,5:n suhde (ja mieluiten alle 0,05:n) tarkoittaa, että Categorical auttaa.
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 nopeuttaa GroupBy-operaatioita
Muistisäästöjen lisäksi Categorical-dtype myös nopeuttaa groupby-operaatioita, koska Pandas voi käyttää kokonaislukukoodeja suoraan sen sijaan, että se tiivistäisi merkkijonoja löytääkseen ryhmien rajat. DataFrameilla, joissa miljoonia rivejä ryhmitellään vähäkardinaalisten merkkijonosarakkeiden (kuten alueen, tuoteluokan tai tilan) perusteella, näiden sarakkeiden muuntaminen Categorical-tyyppiin ennen groupby-operaatiota voi nopeuttaa käsittelyä 2–5-kertaisesti.
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-dtypen käyttäminen lippusarakkeissa
Binaariset sarakkeet (True/False, 0/1, kyllä/ei) tallennetaan usein tyypillä object tai int64. Niiden muuntaminen bool-dtypeksi käyttää vain 1 tavun arvoa kohden (kun int64 käyttää 8 tavua ja merkkijono 'yes'/'no' vähintään 50 tavua). Käytä astype(bool)-muunnosta varmistettuasi ensin, että sarake sisältää vain arvoja 0/1 tai True/False. Boolean-sarakkeet mahdollistavat myös nopeamman suodatuksen, koska Pandas voi käyttää sisäisesti bittitason operaatioita.
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')Tietotyyppien optimoinnin automatisointi
Kirjoittakaa uudelleenkäytettävä optimise_dtypes(df)-funktio, joka ottaa automaattisesti käyttöön kaikki optimoinnit: muuntaa kokonaisluvut pienempään tyyppiin, muuntaa liukuluvut pienempään tyyppiin, muuntaa vähän eri arvoja sisältävät object-sarakkeet Categorical-tyyppisiksi ja muuntaa 0/1-kokonaisluvut bool-arvoiksi. Suorittakaa tämä funktio tietojen latauksen yhteydessä, jotta muistin käyttö pysyy mahdollisimman pienenä alusta alkaen. Näin jokainen saman aineiston lataava tiimin jäsen saa optimoidun version ilman, että hänen tarvitsee muistaa suorittaa jokaista vaihetta erikseen.
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)')Etumerkittömät kokonaislukutyypit epänegatiivisille tiedoille
Kun sarake sisältää ainoastaan epänegatiivisia kokonaislukuja, kuten tunnisteita, lukumääriä tai määriä, käyttäkää etumerkittömiä kokonaislukutyyppejä: uint8 (0–255), uint16 (0–65535), uint32 (0–4 miljardia) tai uint64. Etumerkittömät tyypit käyttävät saman verran tavuja kuin vastaavat etumerkilliset tyypit, mutta niihin voidaan tallentaa positiivisella alueella enintään kaksi kertaa suurempia arvoja. Esimerkiksi tuotetunniste, jonka arvot ovat välillä 0–60 000, mahtuu tyyppiin uint16 (2 tavua) tyypin int32 (4 tavua) sijaan. Käyttäkää astype('uint16')-muunnosta, kun olette ensin varmistaneet, ettei sarakkeessa ole negatiivisia arvoja.
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())Muistin tarkistaminen jokaisen optimointivaiheen jälkeen
Ottakaa optimoinnit käyttöön yksi kerrallaan ja tarkistakaa muistin käyttö jokaisen vaiheen jälkeen. Näin näette tarkasti, kuinka paljon kukin tekniikka vaikuttaa. Tyypillisen suuren DataFramen koko voi pienentyä 2 gigatavusta (kaikki oletustietotyypit) 600 megatavuun (kokonaislukujen muuntaminen pienempään tyyppiin), sitten 300 megatavuun (liukulukujen muuntaminen pienempään tyyppiin) ja lopulta 200 megatavuun (Categorical-merkkijonosarakkeille). Tämän erittelyn dokumentointi auttaa perustelemaan lisämonimutkaisuuden tiimin jäsenille, jotka näkevät koodikannassa ennestään tuntemattomia tietotyyppejä.
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')Optimoitujen tietotyyppien tallentaminen ja lataaminen
Optimoidut tietotyypit menetetään, jos tallennatte tiedot CSV-muotoon, koska kaikki muunnetaan takaisin tekstiksi. Säilyttäkää tietotyypit tallentamalla tiedot Parquet-muotoon komennolla df.to_parquet('file.parquet') — Parquet säilyttää tyypit int32, float32 ja Categorical. Kun tiedot ladataan uudelleen komennolla pd.read_parquet, DataFramella on samat muistia säästävät tietotyypit ilman, että optimointifunktiota tarvitsee suorittaa uudelleen. Parquet myös ladataan suurilla tiedostoilla huomattavasti CSV:tä nopeammin.
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')Pikatarkistus
Testatkaa, miten hyvin ymmärrätte tämän oppitunnin muistia säästävät tietotyypit.
Oppitunnin yhteenveto
Tässä oppitunnissa opitte, että pd.to_numeric(downcast='integer') pienentää kokonaislukusarakkeiden koon 8 tavusta 1–4 tavuun, pd.to_numeric(downcast='float') puolittaa liukulukujen muistinkulutuksen, Categorical dtype pienentää vähän eri arvoja sisältävien merkkijonosarakkeiden koon jopa 50-kertaisesti ja nopeuttaa samalla groupby-operaatioita, ja Parquet format säilyttää optimoidut tietotyypit tiedoston tallennus- ja latauskertojen välillä. Seuraavaksi tutustumme paloittaiseen lukemiseen eli tiedostojen käsittelyyn silloin, kun ne eivät mahdu käytettävissä olevaan RAM-muistiin.
Opi Python tekoälytuutorin avulla — ilmaiseksi
Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.
- Kurssit
- 30
- Oppitunnit
- 120
Usein kysytyt kysymykset
Onko oppitunti ”Tehokkaat tietotyypit muistin säästämiseen” ilmainen?
Kyllä – oppitunnin ”Tehokkaat tietotyypit muistin säästämiseen” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko Pandas & NumPy Academy-kurssin, päivitä CoddyKit PROhon. Pandas & NumPy Academy-kurssilla on yhteensä 4 oppituntia.
Mitä opin oppitunnilla ”Tehokkaat tietotyypit muistin säästämiseen”?
Muuntaa numeeriset sarakkeet pienemmiksi int32- ja float32-tyypeiksi ja merkkijonosarakkeet Categorical-tyypiksi vähentääksenne DataFrame-rakenteen muistinkäyttöä jopa 70 prosenttia. Harjoittelet Pandas & NumPy Academy-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.
Tarvitsenko kokemusta aloittaakseni Pandas & NumPy Academy-opiskelun?
Aiempi kokemus ei ole tarpeen. CoddyKitin Pandas & NumPy Academy-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 3/4.
Kuinka kauan ”Tehokkaat tietotyypit muistin säästämiseen”-oppitunnin suorittaminen kestää?
Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.
Voinko kirjoittaa ja suorittaa koodia tällä Pandas & NumPy Academy-oppitunnilla?
Kyllä. Jokainen Pandas & NumPy Academy-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.
Kaikki tämän kurssin oppitunnit
- Profilointi timeit- ja memory_profiler-työkaluilla
- iterrows-menetelmän ja Python-silmukoiden välttäminen
- Tehokkaat tietotyypit muistin säästämiseen
- Suurten tiedostojen lukeminen paloissa