0Pricing
Pandas & NumPy Academy · Lekcja

Strumieniowy odczyt CSV z chunksize

Czytaj duży plik CSV w porcjach o stałym rozmiarze za pomocą pd.read_csv(chunksize=), przetwarzaj każdą porcję, a następnie łącz lub akumuluj wyniki.

Strumieniowy odczyt CSV z chunksize to bezpłatna lekcja Pandas & NumPy Academy na CoddyKit. To lekcja 1 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.

Problem dużych plików CSV

Gdy plik CSV jest większy niż dostępna pamięć RAM — na przykład plik dziennika o rozmiarze 50 GB na komputerze z 16 GB pamięci — wywołanie pd.read_csv('file.csv') kończy się błędem MemoryError lub powoduje intensywne użycie pamięci wymiany, przez co system działa bezużytecznie wolno. Rozwiązaniem jest odczyt fragmentami: zamiast ładować cały plik jednocześnie, przetwarza się go w fragmentach o stałym rozmiarze, gromadząc wyniki bez przechowywania wszystkich danych w pamięci naraz.

Parametr chunksize w read_csv

Przekazanie chunksize=N do pd.read_csv() zwraca iterator TextFileReader, a nie DataFrame. Każda iteracja zwraca DataFrame zawierający najwyżej N wierszy. Plik jest odczytywany leniwie — dane nie są ładowane, dopóki nie zażądają Państwo kolejnego fragmentu. Tego iteratora można użyć w pętli for lub przekazać do pd.concat(). Należy wybrać wartość chunksize wystarczająco dużą, aby zapewnić wydajne operacje wejścia-wyjścia (np. 10 000–100 000 wierszy), ale jednocześnie na tyle małą, aby fragment wygodnie mieścił się w pamięci.

import pandas as pd

# Returns a TextFileReader iterator, NOT a DataFrame
chunks = pd.read_csv('sales_data.csv', chunksize=10000)
print(type(chunks))  # <class 'pandas.io.parsers.readers.TextFileReader'>

for chunk in chunks:
    print(f'Chunk shape: {chunk.shape}')
    # process each chunk independently
    break   # just show the first chunk here

Niezależne przetwarzanie każdego fragmentu

Najczęstszy wzorzec polega na wykonaniu transformacji lub filtrowania każdego fragmentu i zebraniu wyników na liście, a następnie ich połączeniu. Można na przykład filtrować wiersze spełniające warunek, obliczać statystyki dla poszczególnych fragmentów lub wybierać tylko potrzebne kolumny. Praca na podzbiorach oznacza, że w pamięci znajduje się tylko bieżący fragment, a pozostała część pliku pozostaje nietknięta. Po zakończeniu pętli pojedyncze wywołanie pd.concat(results) tworzy końcowy DataFrame.

import pandas as pd

results = []
for chunk in pd.read_csv('orders.csv', chunksize=50000):
    # Keep only high-value orders
    filtered = chunk[chunk['amount'] > 1000]
    results.append(filtered)

# Combine all filtered chunks
high_value = pd.concat(results, ignore_index=True)
print('High-value orders:', len(high_value))

Gromadzenie agregatów z wielu fragmentów

Czasami nie trzeba przechowywać żadnych wierszy — potrzebny jest tylko agregat narastający. Należy śledzić narastającą sumę, liczbę lub minimum/maksimum dla kolejnych fragmentów bez tworzenia listy DataFrame'ów. Jest to najbardziej oszczędny pamięciowo wzorzec, ponieważ zużycie pamięci pozostaje stałe niezależnie od rozmiaru pliku. Na końcu należy obliczyć końcową statystykę na podstawie akumulatorów.

import pandas as pd

total_revenue = 0.0
total_rows = 0

for chunk in pd.read_csv('sales.csv', chunksize=100000):
    total_revenue += chunk['revenue'].sum()
    total_rows += len(chunk)

print(f'Processed {total_rows:,} rows')
print(f'Total revenue: ${total_revenue:,.2f}')

Określanie dtype w celu przyspieszenia odczytu fragmentami

Domyślnie Pandas wnioskuje typy danych kolumn na podstawie danych, co wymaga dwukrotnego skanowania każdego fragmentu (najpierw w celu określenia typów, a następnie w celu parsowania). Podanie argumentu dtype eliminuje ten narzut i zapobiega niespójności typów między fragmentami. Na przykład kolumna zawierająca głównie liczby całkowite, ale także jedną pustą komórkę, może zostać rozpoznana jako float64 w jednym fragmencie i object w innym. Jawne określenie typów danych zapewnia spójny i szybszy odczyt wszystkich fragmentów.

import pandas as pd

dtype_map = {
    'order_id': 'int32',
    'customer_id': 'int32',
    'amount': 'float32',
    'category': 'category'
}

for chunk in pd.read_csv('orders.csv',
                         chunksize=50000,
                         dtype=dtype_map,
                         parse_dates=['order_date']):
    print(chunk.dtypes)
    break

Wybieranie tylko potrzebnych kolumn

Użyj parametru usecols, aby załadować tylko kolumny potrzebne do analizy. Jeśli plik CSV ma 50 kolumn, ale agregacja korzysta tylko z 3, nie ma powodu parsować pozostałych 47. Połączenie usecols z chunksize znacznie ogranicza zarówno czas operacji wejścia-wyjścia, jak i zużycie pamięci. Jest to jedna z najprostszych i najbardziej efektywnych optymalizacji przetwarzania dużych plików CSV.

import pandas as pd

# Only read the three columns we actually need
for chunk in pd.read_csv(
    'large_transactions.csv',
    chunksize=100000,
    usecols=['date', 'amount', 'region']
):
    print(chunk.columns.tolist())
    print(chunk.memory_usage(deep=True).sum() / 1e6, 'MB per chunk')
    break

Agregacja GroupBy we fragmentach

Wykonanie agregacji groupby na wielu fragmentach wymaga gromadzenia wyników częściowych. Należy wykonać groupby w każdym fragmencie, a następnie połączyć wyniki za pomocą drugiego groupby na połączonych wynikach częściowych. Aby na przykład uzyskać całkowitą sprzedaż według regionu z pliku o rozmiarze 10 GB, należy zebrać w liście sumy dla regionów z poszczególnych fragmentów, a następnie połączyć je i ponownie wykonać grupowanie. Ten wzorzec dwuetapowej agregacji jest czasami nazywany podejściem map-reduce.

import pandas as pd

partials = []
for chunk in pd.read_csv('sales.csv',
                         chunksize=100000,
                         usecols=['region', 'revenue']):
    partial = chunk.groupby('region')['revenue'].sum()
    partials.append(partial)

# Combine partial sums
final = pd.concat(partials).groupby(level=0).sum()
print('Revenue by region:')
print(final.sort_values(ascending=False))

Obsługa błędów parsowania w wielu fragmentach

Duże pliki CSV pochodzące ze źródeł zewnętrznych często zawierają niepoprawne wiersze — dodatkowe przecinki, nieprawidłowe kodowanie lub obcięte linie. Użyj on_bad_lines='skip' (Pandas 1.3+) albo error_bad_lines=False (starsze wersje Pandas), aby pomijać niepoprawne wiersze bez zgłaszania błędu, oraz encoding='latin-1', jeśli parsowanie UTF-8 się nie powiedzie. Obsłuż każdy fragment za pomocą try-except i rejestruj fragmenty, podczas których wystąpiły błędy. Pozwoli to utworzyć odporny potok, który nie ulegnie awarii z powodu pojedynczego niepoprawnego wiersza w pliku zawierającym 10 milionów wierszy.

import pandas as pd

bad_chunks = []
all_chunks = []

for i, chunk in enumerate(pd.read_csv(
    'raw_data.csv',
    chunksize=50000,
    on_bad_lines='skip',
    encoding='utf-8',
    encoding_errors='replace'
)):
    try:
        # Your transformation here
        all_chunks.append(chunk)
    except Exception as e:
        bad_chunks.append((i, str(e)))
        print(f'Chunk {i} error: {e}')

print(f'Processed {len(all_chunks)} chunks, {len(bad_chunks)} errors')

Zapis fragmentami do plików wyjściowych

Gdy przetworzone dane wyjściowe również są duże, należy zapisywać wyniki przyrostowo, zamiast gromadzić wszystko w pamięci i zapisywać dopiero na końcu. Otwórz plik CSV i dopisuj każdy przetworzony fragment za pomocą mode='a' oraz header=False dla kolejnych fragmentów. Dzięki temu zużycie pamięci potoku wyjściowego pozostaje stałe, a częściowe wyniki można sprawdzać przed zakończeniem całego uruchomienia.

import pandas as pd

first_chunk = True
for chunk in pd.read_csv('input.csv', chunksize=100000):
    # Transform
    processed = chunk[chunk['status'] == 'active'].copy()
    processed['revenue_usd'] = processed['revenue'] * 1.10

    # Write incrementally
    mode = 'w' if first_chunk else 'a'
    processed.to_csv('output.csv',
                     mode=mode,
                     header=first_chunk,
                     index=False)
    first_chunk = False

print('Done writing output.csv')

Szacowanie optymalnego rozmiaru fragmentu

Wybór wartości chunksize jest kwestią kompromisu: zbyt mała wartość oznacza wiele iteracji pętli w Pythonie i duży narzut, a zbyt duża sprawia, że fragmenty nie mieszczą się w pamięci RAM. Praktyczne podejście polega na załadowaniu jednego fragmentu, zmierzeniu jego zużycia pamięci za pomocą chunk.memory_usage(deep=True).sum() i ustawieniu wartości chunksize tak, aby każdy fragment zajmował około 10–20% dostępnej pamięci RAM. psutil.virtual_memory().available w Pythonie zwraca ilość dostępnej pamięci RAM w czasie działania, umożliwiając adaptacyjne obliczanie wartości chunksize.

import pandas as pd

# Sample 1000 rows to estimate per-row memory
sample = pd.read_csv('big_file.csv', nrows=1000)
bytes_per_row = sample.memory_usage(deep=True).sum() / 1000
print(f'Bytes per row: {bytes_per_row:.0f}')

# Target: use at most 500 MB per chunk
target_bytes = 500 * 1024 * 1024
optimal_chunksize = int(target_bytes / bytes_per_row)
print(f'Recommended chunksize: {optimal_chunksize:,}')

Efektywne łączenie wyników fragmentów

Gdy gromadzą Państwo wiele DataFrame'ów z fragmentów na liście, a następnie je łączą, należy pamiętać, że wywołanie pd.concat dla setek małych ramek jest powolne z powodu wielokrotnego przydzielania pamięci. Lepszym wzorcem jest agregowanie w każdym fragmencie i przechowywanie tylko małego wyniku agregacji, a nie całego fragmentu. Jeśli rzeczywiście potrzebne są wszystkie wiersze, przyrostowy zapis do pliku Parquet (za pomocą pyarrow) jest szybszy niż użycie pd.concat na końcu.

import pandas as pd

# Efficient: aggregate first, small list of scalars
running_total = 0
running_count = 0

for chunk in pd.read_csv('sales.csv', chunksize=100000):
    running_total += chunk['amount'].sum()
    running_count += chunk['amount'].count()

print(f'Mean amount: {running_total / running_count:.2f}')

# Avoid: accumulating full chunk DataFrames
# results = []
# for chunk in reader:
#     results.append(chunk)   # memory grows to full file size
# df = pd.concat(results)     # slow for hundreds of chunks

Szybki test

Sprawdź swoją znajomość zagadnień analizy danych omówionych w tej lekcji.

Podsumowanie lekcji

W tej lekcji nauczyłeś się, że parametr chunksize w pd.read_csv zwraca iterator DataFrame'ów, umożliwiając oszczędne pamięciowo przetwarzanie dużych plików, argumenty usecols i dtype ograniczają zużycie pamięci dla pojedynczego fragmentu i przyspieszają parsowanie, a akumulatory narastające (suma, liczba, wyniki częściowe) eliminują konieczność tworzenia listy wszystkich fragmentów. W następnej części przyjrzymy się dokładniej wzorcom przyrostowej agregacji dla kolejnych fragmentów.

Często zadawane pytania

Czy lekcja „Strumieniowy odczyt CSV z chunksize” jest bezpłatna?

Tak — pełny tekst „Strumieniowy odczyt CSV z chunksize” 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 „Strumieniowy odczyt CSV z chunksize”?

Czytaj duży plik CSV w porcjach o stałym rozmiarze za pomocą pd.read_csv(chunksize=), przetwarzaj każdą porcję, a następnie łącz lub akumuluj 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 1 z 4.

Ile czasu zajmuje lekcja „Strumieniowy odczyt CSV z chunksize”?

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. Strumieniowy odczyt CSV z chunksize
  2. Przyrostowa agregacja między porcjami
  3. Wprowadzenie do obiektów Dask DataFrame
  4. Parquet: szybki magazyn kolumnowy
← Powrót do Pandas & NumPy Academy