0Pricing
Pandas & NumPy Academy · Lekcja

Odczyt dużych plików partiami

Przetwarzaj pliki przekraczające dostępną pamięć RAM, odczytując je partiami za pomocą chunksize w read_csv i stopniowo agregując wyniki.

Odczyt dużych plików partiami to bezpłatna lekcja Pandas & NumPy Academy na CoddyKit. To lekcja 4 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.

Gdy pliki przekraczają rozmiar pamięci RAM

Częstym wąskim gardłem w produkcyjnych potokach danych jest plik CSV większy niż dostępna pamięć RAM. Jeśli plik ma 20 GB, a komputer dysponuje 16 GB pamięci RAM, pd.read_csv('file.csv') zakończy się błędem MemoryError. Rozwiązaniem jest wczytywanie porcjami: wczytywanie pliku w porcjach o stałym rozmiarze, przetwarzanie każdej z nich i gromadzenie wyników. Dzięki temu można analizować pliki o dowolnym rozmiarze bez wczytywania ich w całości do pamięci.

import pandas as pd
import numpy as np

# Simulate a large CSV by writing one
np.random.seed(0)
sample = pd.DataFrame({
    'date': pd.date_range('2023-01-01', periods=100000, freq='h'),
    'region': np.random.choice(['North','South','East','West'], 100000),
    'sales': np.random.randint(100, 1000, 100000)
})
sample.to_csv('/tmp/large_sales.csv', index=False)
print(f'File size: {pd.io.common.get_handle("/tmp/large_sales.csv", "r").handle.seek(0, 2)/1e6:.1f} MB... (simulated)')
print('Rows:', len(sample))

Parametr chunksize w read_csv

Przekazanie chunksize=n do pd.read_csv() powoduje zwrócenie iteratora TextFileReader zamiast DataFrame. Każda iteracja zwraca kolejne n wierszy jako DataFrame. W ten sposób w pamięci znajduje się jednocześnie tylko n wierszy. Dobrym punktem wyjścia dla parametru chunksize jest 100 000 wierszy — to wystarczająco mało, aby zmieścić się w pamięci RAM, i jednocześnie wystarczająco dużo, aby ograniczyć narzut operacji wejścia-wyjścia. Wartość należy dostosować do dostępnej pamięci RAM i liczby kolumn.

import pandas as pd

# Read file in chunks of 25,000 rows
chunk_iter = pd.read_csv('/tmp/large_sales.csv', chunksize=25000)

# Peek at the first chunk
first_chunk = next(chunk_iter)
print('First chunk shape:', first_chunk.shape)
print(first_chunk.head(3))

Iterowanie po porcjach

Do przetwarzania każdej porcji należy użyć pętli for iterującej po iteratorze porcji. W przypadku prostych operacji, takich jak zliczanie wierszy, sumowanie kolumny lub filtrowanie, każdą porcję należy przetwarzać niezależnie, a wyniki gromadzić na liście lub w sumie bieżącej. Iterator należy zawsze wywoływać wewnątrz instrukcji with albo upewnić się, że jest tworzony ponownie przy każdym uruchomieniu potoku — iteratory są zużywane jednokrotnie i nie można ich uruchomić ponownie.

import pandas as pd

total_rows = 0
total_sales = 0.0
chunk_count = 0

for chunk in pd.read_csv('/tmp/large_sales.csv', chunksize=25000):
    total_rows += len(chunk)
    total_sales += chunk['sales'].sum()
    chunk_count += 1

print(f'Chunks processed: {chunk_count}')
print(f'Total rows: {total_rows:,}')
print(f'Total sales: {total_sales:,.0f}')
print(f'Avg sales per row: {total_sales/total_rows:.2f}')

Filtrowanie wierszy podczas wczytywania porcjami

Filtry wierszy należy stosować wewnątrz pętli, aby odrzucać niepotrzebne dane, zanim zostaną zgromadzone. Dzięki temu zużycie pamięci pozostaje niskie, ponieważ zachowywane są tylko wiersze spełniające określone kryteria. Na przykład, aby wyodrębnić wszystkie wiersze dotyczące regionu „North” z pliku o rozmiarze 10 GB, należy filtrować każdą porcję przed dodaniem jej do listy wyników. Końcowa operacja pd.concat będzie wtedy przetwarzać tylko przefiltrowany podzbiór, który jest znacznie mniejszy.

import pandas as pd

filtered_chunks = []

for chunk in pd.read_csv('/tmp/large_sales.csv', chunksize=25000):
    # Keep only North region rows
    north = chunk[chunk['region'] == 'North']
    if len(north) > 0:
        filtered_chunks.append(north)

north_df = pd.concat(filtered_chunks, ignore_index=True)
print(f'North region rows: {len(north_df):,}')
print(f'North total sales: {north_df["sales"].sum():,.0f}')

Przyrostowa agregacja GroupBy

Agregacje GroupBy wykonywane na porcjach są trudniejsze niż zwykłe sumowanie, ponieważ poszczególne grupy mogą obejmować wiele porcji. Schemat postępowania jest następujący: obliczyć sumy i liczności na poziomie grupy dla każdej porcji, połączyć te częściowe wyniki, a następnie wykonać końcowe groupby na zgromadzonych wynikach częściowych. Nie należy wywoływać groupby().mean() dla każdej porcji, a następnie uśredniać średnich — prowadzi to do błędnych wyników, gdy rozmiary grup różnią się między porcjami.

import pandas as pd

partials = []

for chunk in pd.read_csv('/tmp/large_sales.csv', chunksize=25000):
    # Compute sum and count per region in this chunk
    partial = chunk.groupby('region')['sales'].agg(['sum', 'count'])
    partials.append(partial)

# Combine all partial results
combined = pd.concat(partials).groupby(level=0).sum()
combined['mean'] = combined['sum'] / combined['count']

print('Regional aggregation (chunked):')
print(combined.round(2))

Wydajne pamięciowo określanie typów podczas wczytywania

Zużycie pamięci podczas wczytywania porcjami można zmniejszyć, określając z wyprzedzeniem dtypes kolumn w funkcji read_csv. Zapobiega to przydzielaniu przez Pandas szerokich typów dla każdej porcji, a następnie ich odrzucaniu. Do read_csv() należy przekazać słownik, taki jak dtype={'region': 'category', 'sales': 'int32'}. W połączeniu ze wczytywaniem porcjami może to zmniejszyć szczytowe zużycie pamięci do mniej niż 10% zużycia podczas naiwnego wczytywania całego pliku.

import pandas as pd

specified_dtypes = {
    'region': 'category',
    'sales': 'int32'
}

total_sales = 0
for chunk in pd.read_csv(
    '/tmp/large_sales.csv',
    chunksize=25000,
    dtype=specified_dtypes,
    parse_dates=['date']
):
    total_sales += chunk['sales'].sum()
    # Only 25k rows * (category + int32 + datetime) in RAM at once

print(f'Total sales (memory-efficient): {total_sales:,.0f}')

Przyrostowy zapis wyników

Jeśli wyniki przetwarzania każdej porcji również trzeba zapisać do pliku, należy zapisywać każdą przetworzoną porcję na bieżąco, zamiast gromadzić wszystko w pamięci. W funkcji to_csv() należy użyć mode='a' (dopisywanie) oraz header=False (po pierwszej porcji). Dzięki temu zarówno dane wejściowe, jak i wyjściowe zajmują w pamięci najwyżej tyle miejsca, ile wynosi rozmiar porcji, co pozwala przetwarzać pliki o dowolnym rozmiarze na komputerze z ograniczoną pamięcią.

import pandas as pd
import os

output_path = '/tmp/filtered_output.csv'

# Remove previous output if it exists
if os.path.exists(output_path):
    os.remove(output_path)

first_chunk = True
for chunk in pd.read_csv('/tmp/large_sales.csv', chunksize=25000):
    # Process: keep only high-value rows
    processed = chunk[chunk['sales'] > 800].copy()
    
    # Write: header only on first chunk, append thereafter
    processed.to_csv(output_path,
                     mode='a',
                     header=first_chunk,
                     index=False)
    first_chunk = False

result = pd.read_csv(output_path)
print(f'High-value rows written: {len(result):,}')
print(f'Average sales (>800): {result["sales"].mean():.1f}')

Wprowadzenie do Dask jako alternatywy niewymagającej zmian w kodzie

Dask udostępnia API podobne do Pandas, które automatycznie wykonuje operacje leniwie i równolegle na porcjach. Zamiast pisać ręczną pętlę do wczytywania porcjami, można napisać dask_df = dd.read_csv('file.csv'), a następnie używać tych samych operacji co w Pandas — groupby, filtrowania i scalania. Wywołanie .compute() na końcu uruchamia wykonywanie operacji. Dask jest najbardziej praktycznym rozwiązaniem, gdy potok obejmuje złożone, wieloetapowe operacje, które trudno zaimplementować ręcznie przy użyciu wczytywania porcjami.

# pip install dask
# import dask.dataframe as dd

# Read the large CSV as a Dask DataFrame (lazy — no data loaded yet)
# ddf = dd.read_csv('/tmp/large_sales.csv')

# Operations are lazy — no computation happens until .compute()
# result = ddf.groupby('region')['sales'].mean().compute()
# print(result)

# Key Dask advantages:
# - Automatic chunking (no manual chunksize loops)
# - Parallel execution (multi-core)
# - Familiar Pandas API
# - Works with files larger than RAM

print('Dask extends Pandas to datasets larger than RAM with minimal API changes.')

Wybór rozmiaru porcji

Idealny rozmiar porcji równoważy dwa sprzeczne czynniki: zbyt małe porcje (np. 1000 wierszy) powodują duży narzut przypadający na każdą porcję (konfiguracja operacji wejścia-wyjścia, tworzenie DataFrame), przez co pętla działa dłużej. Zbyt duże porcje (np. 10 mln wierszy) niweczą cel tego podejścia, ponieważ jednocześnie wczytywana jest zbyt duża ilość danych. Praktyczny punkt wyjścia to porcje zajmujące w pamięci 50–200 MB. W przypadku DataFrame z 10 kolumnami o średnim rozmiarze 8 bajtów każda 100 000 wierszy zajmuje około 8 MB — jest to bezpieślna wartość domyślna, pozostawiająca dużo wolnej pamięci.

import pandas as pd
import timeit

# Benchmark different chunk sizes
for chunksize in [10000, 50000, 100000]:
    t = timeit.timeit(
        lambda: sum(chunk['sales'].sum()
                    for chunk in pd.read_csv('/tmp/large_sales.csv', chunksize=chunksize)),
        number=3
    )
    print(f'chunksize={chunksize:>7,}: {t/3:.3f}s per pass')

Parquet a CSV w przypadku dużych zbiorów danych

Jeśli mają Państwo wpływ na format pliku, w przypadku dużych zbiorów danych należy wybrać Parquet zamiast CSV. Parquet to kolumnowy format binarny, który: wczytuje tylko żądane kolumny (przycinanie kolumn), kompresuje dane znacznie lepiej niż CSV, zachowuje typy danych (bez ponownego ich rozpoznawania podczas wczytywania) oraz wczytuje dane 5–20 razy szybciej niż równoważny plik CSV. Duży plik CSV można jednorazowo przekonwertować do formatu Parquet za pomocą pd.read_csv('file.csv', chunksize=500000) + to_parquet, a następnie używać pd.read_parquet(columns=['col1','col2']) przy kolejnych odczytach.

import pandas as pd

# Convert our CSV to Parquet (do this once)
df = pd.read_csv('/tmp/large_sales.csv', parse_dates=['date'])
df['region'] = df['region'].astype('category')
df['sales'] = df['sales'].astype('int32')
df.to_parquet('/tmp/large_sales.parquet', index=False)

# Now read only the columns needed — much faster than CSV
sales_by_region = pd.read_parquet(
    '/tmp/large_sales.parquet',
    columns=['region', 'sales']
)
print('Parquet read result:')
print(sales_by_region.groupby('region')['sales'].mean().round(1))
print('\ndtypes preserved:', sales_by_region.dtypes.to_dict())

Pełny schemat potoku porcjowego

Kompletny schemat przetwarzania dużych plików: 1) Określić typy danych podczas wczytywania. 2) Filtrować wiersze możliwie wcześnie, wewnątrz pętli. 3) Obliczać częściowe agregacje dla każdej porcji (suma + liczność, nigdy bezpośrednio średnia). 4) Po zakończeniu pętli połączyć wyniki częściowe i obliczyć statystyki końcowe. 5) Jeśli wyniki są duże, zapisywać je przyrostowo. Ten schemat umożliwia obsługę plików dowolnego rozmiaru na dowolnym komputerze, pod warunkiem odpowiedniego dostrojenia parametru chunksize.

Szybki sprawdzian

Sprawdź swoją wiedzę na temat wczytywania danych porcjami z tej lekcji.

Podsumowanie lekcji

W tej lekcji nauczyłeś się, że chunksize w read_csv zwraca iterator dostarczający jeden DataFrame dla każdej porcji, co pozwala przyrostowo przetwarzać pliki większe niż pamięć RAM, częściowe agregacje (suma + liczność) są poprawnie gromadzone między porcjami, natomiast średniej nie można bezpośrednio uśredniać, a format Parquet jest najlepszą długoterminową alternatywą dla CSV w przypadku dużych zbiorów danych ze względu na kompresję, szybkość i zachowywanie typów danych. To kończy kurs Wskazówki dotyczące wydajności Pandas — są Państwo gotowi na zaawansowane kursy B2!

Często zadawane pytania

Czy lekcja „Odczyt dużych plików partiami” jest bezpłatna?

Tak — pełny tekst „Odczyt dużych plików partiami” 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 „Odczyt dużych plików partiami”?

Przetwarzaj pliki przekraczające dostępną pamięć RAM, odczytując je partiami za pomocą chunksize w read_csv i stopniowo agregując wyniki. Ć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 4 z 4.

Ile czasu zajmuje lekcja „Odczyt dużych plików partiami”?

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