Jak odczytać duży plik csv z pandami?

194

Próbuję odczytać duży plik csv (około 6 GB) w pandach i pojawia się błąd pamięci:

MemoryError                               Traceback (most recent call last)
<ipython-input-58-67a72687871b> in <module>()
----> 1 data=pd.read_csv('aphro.csv',sep=';')

...

MemoryError: 

Jakaś pomoc na ten temat?

Rajkumar Kumawat
źródło
3
Co ciekawe, bardzo podobne pytanie zostało zadane prawie rok przed tym ...
DarkCygnus
Czy to odpowiada na twoje pytanie? Przepływy pracy „dużych danych” przy użyciu pand
AMC

Odpowiedzi:

261

Błąd pokazuje, że komputer nie ma wystarczającej ilości pamięci, aby odczytać cały plik CSV do DataFrame jednocześnie. Zakładając, że nie potrzebujesz całego zestawu danych w pamięci jednocześnie, jednym ze sposobów uniknięcia problemu byłoby przetworzenie pliku CSV we fragmenty (poprzez określenie chunksizeparametru):

chunksize = 10 ** 6
for chunk in pd.read_csv(filename, chunksize=chunksize):
    process(chunk)

Ten chunksizeparametr określa liczbę wierszy na porcję. (Ostatni fragment może chunksizeoczywiście zawierać mniej niż wiersze).

unutbu
źródło
17
na ogół potrzebujesz 2X końcowej pamięci, aby coś wczytać (z csv, chociaż inne formaty są lepsze w przypadku niższych wymagań pamięci). Do Twojej wiadomości odnosi się to do próby zrobienia prawie wszystkiego naraz. Znacznie lepiej jest go porcjować (który ma stałe zużycie pamięci).
Jeff
24
@altabq: Problem polega na tym, że nie mamy wystarczającej ilości pamięci, aby zbudować pojedynczą ramkę danych przechowującą wszystkie dane. Powyższe rozwiązanie próbuje poradzić sobie z tą sytuacją poprzez zmniejszenie porcji (np. Poprzez agregację lub wyodrębnienie tylko pożądanych informacji) po jednej porcji - oszczędzając w ten sposób pamięć. Cokolwiek zrobisz, NIE wywoływać DF.append(chunk)wewnątrz pętli. To wykorzysta O(N^2)operacje kopiowania. Lepiej jest dołączyć zagregowane dane do listy , a następnie zbudować DataFrame z listy za pomocą jednego wywołania do pd.DataFramelub pd.concat(w zależności od rodzaju zagregowanych danych).
unutbu
12
@altabq: Wywołanie DF.append(chunk)w pętli wymaga O(N^2)operacji kopiowania, gdzie Njest wielkość porcji, ponieważ każde wywołanie DF.appendzwraca nową ramkę danych. Wywołanie pd.DataFramelub pd.concat wyjście poza pętlę zmniejsza ilość kopii do O(N).
unutbu
5
@Pyderman: Tak, chunksizeparametr odnosi się do liczby wierszy na porcję. Ostatni fragment może chunksizeoczywiście zawierać mniej niż wiersze.
unutbu
7
@Pyderman: Tak; dzwonienie pd.concat([list_of_dfs]) raz po pętli jest znacznie szybsze niż dzwonienie pd.concatlub df.appendwiele razy w pętli. Oczywiście potrzebujesz znacznej ilości pamięci, aby pomieścić całe 6 GB csv jako jedną ramkę DataFrame.
unutbu
85

Tworzenie porcji nie zawsze powinno być pierwszym punktem wyjścia dla tego problemu.

  1. Czy plik jest duży z powodu powtarzających się danych nienumerycznych lub niechcianych kolumn?

    Jeśli tak, czasami możesz zobaczyć ogromne oszczędności pamięci, czytając kolumny jako kategorie i wybierając wymagane kolumny za pomocą usecols parametru pd.read_csv .

  2. Czy Twój przepływ pracy wymaga krojenia, manipulacji, eksportowania?

    Jeśli tak, możesz użyć dask.dataframe do wycinania, wykonywania obliczeń i eksportowania iteracyjnego. Tworzenie porcji odbywa się w trybie cichym przez dask, który obsługuje również podzbiór API pand.

  3. Jeśli wszystko inne zawiedzie, przeczytaj wiersz po wierszu za pomocą porcji.

    Kawałek za pośrednictwem pand lub biblioteki csv w ostateczności.

jpp
źródło
3
Nie wiedziałem o Dasku. +100 za to!
noamtm
34

Postępowałem tak:

chunks=pd.read_table('aphro.csv',chunksize=1000000,sep=';',\
       names=['lat','long','rf','date','slno'],index_col='slno',\
       header=None,parse_dates=['date'])

df=pd.DataFrame()
%time df=pd.concat(chunk.groupby(['lat','long',chunk['date'].map(lambda x: x.year)])['rf'].agg(['sum']) for chunk in chunks)
Rajkumar Kumawat
źródło
22
Czy istnieje powód, dla którego przeniosłeś się read_csvna read_table?
Pyderman,
33

W przypadku dużych danych zalecam użycie biblioteki „dask”
np .:

# Dataframes implement the Pandas API
import dask.dataframe as dd
df = dd.read_csv('s3://.../2018-*-*.csv')

Możesz przeczytać więcej z dokumentacji tutaj .

Inną świetną alternatywą byłoby użycie modiny, ponieważ cała funkcjonalność jest identyczna z pandami, ale wykorzystuje ona rozproszone biblioteki ramek danych, takie jak dask.

Simbarashe Timothy Motsi
źródło
11
Wszelkie korzyści w stosunku do pand mogą docenić dodanie kilku dodatkowych wskazówek
PirateApp
2
Nie używałem Dask od bardzo dawna, ale główne zalety w moich przypadkach użycia polegały na tym, że Dask może działać równolegle na wielu komputerach, a także może dopasować dane jako wycinki do pamięci.
Simbarashe Timothy Motsi
2
dzięki! jest dask zamiennikiem pand lub czy działa na wierzchu pand jako warstwa
PirateApp
3
Witamy, działa jako opakowanie dla Numpy, Pand i Scikit-Learn.
Simbarashe Timothy Motsi
1
Próbowałem zmierzyć się z kilkoma problemami z Daskiem i zawsze rzuca błąd na wszystko. Nawet z kawałkami Zgłasza także błędy pamięci. Zobacz stackoverflow.com/questions/59865572/…
Genarito
10

Powyższa odpowiedź już spełnia ten temat. W każdym razie, jeśli potrzebujesz wszystkich danych w pamięci - spójrz na bcolz . Kompresuje dane w pamięci. Miałem z tym naprawdę dobre doświadczenia. Ale brakuje mu wielu funkcji pand

Edycja: Mam współczynniki kompresji około 1/10 lub rozmiar oryginału, myślę, oczywiście w zależności od rodzaju danych. Ważnymi brakującymi funkcjami były agregaty.

PlagTag
źródło
2
Popraw tę odpowiedź, informując nas: a) jakie otrzymujesz współczynniki kompresji ib) jakie główne cechy pand brakuje? Czy poradzi sobie z NA? smyczki? kategoryczne? Daktyle?
smci
Co? Czy poradzi sobie z NA? smyczki? kategoryczne? Daktyle? Są to rzeczy, które sprawiają, że czytanie pandas csv jest powolne i zwiotczałe. NA i obiekty takie jak struny (nawet krótkie) są zabójcze. Przy okazji plik .ipynb, do którego odwołuje się Twój blog, jest wyłączony.
smci
1
@smci czytałem ci notatkę. ale sugeruję zajrzeć do dokumentów. sam musiałbym je przeczytać.
PlagTag
2
Ok, więc nie może obsługiwać NA, ciągów ani dat. Wątpię, czy poradzi sobie z floatami.
smci,
1
chunksPodejrzewam, że możesz wstępnie przetworzyć pandy przy użyciu wspomnianej metody, a następnie użyć bcolz, jeśli potrzebujesz wszystkich danych w pamięci, aby wykonać analizę. Tylko myśl.
JakeCowton,
6

Możesz odczytać dane jako kawałki i zapisać każdy kawałek jako piklę.

import pandas as pd 
import pickle

in_path = "" #Path where the large file is
out_path = "" #Path to save the pickle files to
chunk_size = 400000 #size of chunks relies on your available memory
separator = "~"

reader = pd.read_csv(in_path,sep=separator,chunksize=chunk_size, 
                    low_memory=False)    


for i, chunk in enumerate(reader):
    out_file = out_path + "/data_{}.pkl".format(i+1)
    with open(out_file, "wb") as f:
        pickle.dump(chunk,f,pickle.HIGHEST_PROTOCOL)

W następnym kroku wczytujesz pikle i dołączasz każdą pikietę do żądanej ramki danych.

import glob
pickle_path = "" #Same Path as out_path i.e. where the pickle files are

data_p_files=[]
for name in glob.glob(pickle_path + "/data_*.pkl"):
   data_p_files.append(name)


df = pd.DataFrame([])
for i in range(len(data_p_files)):
    df = df.append(pd.read_pickle(data_p_files[i]),ignore_index=True)
Lukas Humpe
źródło
3
Jeśli twój finał dfmieści się całkowicie w pamięci (jak sugeruje) i zawiera taką samą ilość danych, jak twoje dane wejściowe, to na pewno nie potrzebujesz w ogóle porcji?
jpp
W takim przypadku trzeba byłoby porcjować, jeśli na przykład plik jest bardzo szeroki (na przykład ponad 100 kolumn z dużą liczbą kolumn ciągów). Zwiększa to pamięć potrzebną do przechowywania df w pamięci. Nawet taki plik 4 GB może skończyć się użyciem od 20 do 30 GB pamięci RAM na pudełku z 64 GB pamięci RAM.
cdabel
4

Funkcje read_csv i read_table są prawie takie same. Ale musisz przypisać ogranicznik „,”, gdy używasz funkcji read_table w swoim programie.

def get_from_action_data(fname, chunk_size=100000):
    reader = pd.read_csv(fname, header=0, iterator=True)
    chunks = []
    loop = True
    while loop:
        try:
            chunk = reader.get_chunk(chunk_size)[["user_id", "type"]]
            chunks.append(chunk)
        except StopIteration:
            loop = False
            print("Iteration is stopped")

    df_ac = pd.concat(chunks, ignore_index=True)
Tyrion W
źródło
Przydałoby się, gdyby w tym poście było podane pytanie. Jak „Jaka jest różnica między read_csv a read_table?” lub „Dlaczego tabela odczytu potrzebuje ogranicznika?”
nate_weldon
1
To zależy, jak wygląda twój plik. Niektóre pliki mają wspólne ograniczniki, takie jak „,” lub „|” lub „\ t”, ale możesz zobaczyć inne pliki z ogranicznikami, takie jak 0x01, 0x02 (poprawianie tego) itp. Tak więc tabela read_table jest bardziej odpowiednia dla nietypowych ograniczników, ale read_csv może wykonywać tę samą pracę równie dobrze.
Naufal
3

Rozwiązanie 1:

Korzystanie z pand z dużymi danymi

Rozwiązanie 2:

TextFileReader = pd.read_csv(path, chunksize=1000)  # the number of rows per chunk

dfList = []
for df in TextFileReader:
    dfList.append(df)

df = pd.concat(dfList,sort=False)
czarna Owca
źródło
3
Tutaj ponownie
ładujemy
6
po prostu nie rób dfList.append, po prostu przetwarzaj każdą df
porcję
3

Oto przykład:

chunkTemp = []
queryTemp = []
query = pd.DataFrame()

for chunk in pd.read_csv(file, header=0, chunksize=<your_chunksize>, iterator=True, low_memory=False):

    #REPLACING BLANK SPACES AT COLUMNS' NAMES FOR SQL OPTIMIZATION
    chunk = chunk.rename(columns = {c: c.replace(' ', '') for c in chunk.columns})

    #YOU CAN EITHER: 
    #1)BUFFER THE CHUNKS IN ORDER TO LOAD YOUR WHOLE DATASET 
    chunkTemp.append(chunk)

    #2)DO YOUR PROCESSING OVER A CHUNK AND STORE THE RESULT OF IT
    query = chunk[chunk[<column_name>].str.startswith(<some_pattern>)]   
    #BUFFERING PROCESSED DATA
    queryTemp.append(query)

#!  NEVER DO pd.concat OR pd.DataFrame() INSIDE A LOOP
print("Database: CONCATENATING CHUNKS INTO A SINGLE DATAFRAME")
chunk = pd.concat(chunkTemp)
print("Database: LOADED")

#CONCATENATING PROCESSED DATA
query = pd.concat(queryTemp)
print(query)
jonathask
źródło
2

Możesz wypróbować sframe, które mają tę samą składnię co pandy, ale pozwalają na manipulowanie plikami większymi niż twoja pamięć RAM.

nunodsousa
źródło
„Dane w SFrame są przechowywane kolumnowo po stronie serwera GraphLab” czy jest to usługa czy pakiet?
Danny Wang
2

Jeśli używasz pand, wczytaj duży plik do porcji, a następnie wydaj wiersz po rzędzie, oto co zrobiłem

import pandas as pd

def chunck_generator(filename, header=False,chunk_size = 10 ** 5):
   for chunk in pd.read_csv(filename,delimiter=',', iterator=True, chunksize=chunk_size, parse_dates=[1] ): 
        yield (chunk)

def _generator( filename, header=False,chunk_size = 10 ** 5):
    chunk = chunck_generator(filename, header=False,chunk_size = 10 ** 5)
    for row in chunk:
        yield row

if __name__ == "__main__":
filename = r'file.csv'
        generator = generator(filename=filename)
        while True:
           print(next(generator))
Paul
źródło
1

Chcę udzielić bardziej kompleksowej odpowiedzi na podstawie większości potencjalnych rozwiązań, które już zostały dostarczone. Chciałbym również wskazać jeszcze jedną potencjalną pomoc, która może pomóc w procesie czytania.

Opcja 1: dtypy

„dtypes” to dość potężny parametr, którego można użyć do zmniejszenia presji pamięci na readmetody. Zobacz i odpowiedź. Pandy domyślnie próbują wnioskować o typach danych.

Odnosząc się do struktur danych, każde zapisane dane, następuje przydział pamięci. Na poziomie podstawowym zapoznaj się z poniższymi wartościami (tabela poniżej ilustruje wartości dla języka programowania C):

The maximum value of UNSIGNED CHAR = 255                                    
The minimum value of SHORT INT = -32768                                     
The maximum value of SHORT INT = 32767                                      
The minimum value of INT = -2147483648                                      
The maximum value of INT = 2147483647                                       
The minimum value of CHAR = -128                                            
The maximum value of CHAR = 127                                             
The minimum value of LONG = -9223372036854775808                            
The maximum value of LONG = 9223372036854775807

Sprawdź stronę, aby zobaczyć dopasowanie między NumPy i typami C.

Powiedzmy, że masz tablicę liczb z cyfr . Możesz zarówno teoretycznie, jak i praktycznie przypisać, powiedzmy tablicę 16-bitowych liczb całkowitych, ale wtedy przydzielisz więcej pamięci, niż faktycznie potrzebujesz do przechowywania tej tablicy. Aby temu zapobiec, możesz ustawić dtypeopcję na read_csv. Nie chcesz przechowywać elementów tablicy jako długich liczb całkowitych, gdzie faktycznie możesz zmieścić je za pomocą 8-bitowych liczb całkowitych ( np.int8lub np.uint8).

Obserwuj poniższą mapę typu.

Źródło: https://pbpython.com/pandas_dtypes.html

Możesz przekazać dtypeparametr jako parametr w metodach pand tak readjak w przypadku {kolumna: typ}.

import numpy as np
import pandas as pd

df_dtype = {
        "column_1": int,
        "column_2": str,
        "column_3": np.int16,
        "column_4": np.uint8,
        ...
        "column_n": np.float32
}

df = pd.read_csv('path/to/file', dtype=df_dtype)

Opcja 2: odczytane przez kawałki

Odczytywanie danych we fragmentach umożliwia dostęp do części danych w pamięci, a także można zastosować wstępne przetwarzanie danych i zachować przetworzone dane zamiast danych surowych. Byłoby znacznie lepiej, gdyby połączyć tę opcję z pierwszą, dtypami .

Chciałbym wskazać sekcje książki kucharskiej pandy dotyczące tego procesu, gdzie można je znaleźć tutaj . Zwróć uwagę na te dwie sekcje;

Opcja 3: Dask

Dask to środowisko, które jest zdefiniowane na stronie internetowej Dask jako:

Dask zapewnia zaawansowaną równoległość analiz, umożliwiając skalowanie wydajności dla narzędzi, które kochasz

Urodził się, aby pokryć niezbędne części, do których nie mogą dotrzeć pandy. Dask to potężny framework, który pozwala na znacznie większy dostęp do danych poprzez przetwarzanie ich w sposób rozproszony.

Możesz użyć dask, aby wstępnie przetworzyć swoje dane jako całość, Dask zajmuje się fragmentacją, więc w przeciwieństwie do pand, możesz po prostu zdefiniować etapy przetwarzania i pozwolić Dask wykonać pracę. Dask nie dotyczy obliczeń zanim zostanie ona wyraźnie przesunąć computei / lub persist(patrz odpowiedź tutaj różnicy).

Inne pomoce (pomysły)

  • Przepływ ETL zaprojektowany dla danych. Zachowaj tylko to, co jest potrzebne z nieprzetworzonych danych.
    • Najpierw zastosuj ETL do całych danych za pomocą frameworków takich jak Dask lub PySpark i wyeksportuj przetworzone dane.
    • Następnie sprawdź, czy przetwarzane dane można zmieścić w pamięci jako całości.
  • Rozważ zwiększenie pamięci RAM.
  • Rozważ pracę z tymi danymi na platformie chmurowej.
zero
źródło
0

Oprócz powyższych odpowiedzi, dla tych, którzy chcą przetworzyć CSV, a następnie wyeksportować do formatu csv, parquet lub SQL, d6tstack to kolejna dobra opcja. Możesz załadować wiele plików i dotyczy to zmian schematu danych (dodanych / usuniętych kolumn). Wydzielone z podstawowego wsparcia jest już wbudowane.

def apply(dfg):
    # do stuff
    return dfg

c = d6tstack.combine_csv.CombinerCSV([bigfile.csv], apply_after_read=apply, sep=',', chunksize=1e6)

# or
c = d6tstack.combine_csv.CombinerCSV(glob.glob('*.csv'), apply_after_read=apply, chunksize=1e6)

# output to various formats, automatically chunked to reduce memory consumption
c.to_csv_combine(filename='out.csv')
c.to_parquet_combine(filename='out.pq')
c.to_psql_combine('postgresql+psycopg2://usr:pwd@localhost/db', 'tablename') # fast for postgres
c.to_mysql_combine('mysql+mysqlconnector://usr:pwd@localhost/db', 'tablename') # fast for mysql
c.to_sql_combine('postgresql+psycopg2://usr:pwd@localhost/db', 'tablename') # slow but flexible
citynorman
źródło
0

W przypadku, gdy ktoś wciąż szuka czegoś takiego, odkryłem, że ta nowa biblioteka o nazwie modin może pomóc. Wykorzystuje przetwarzanie rozproszone, które może pomóc w czytaniu. Oto fajny artykuł porównujący jego funkcjonalność z pandami. Zasadniczo wykorzystuje te same funkcje, co pandy.

import modin.pandas as pd
pd.read_csv(CSV_FILE_NAME)
Jaskaran
źródło
Czy możesz skomentować, jak ten nowy moduł modinwypada w porównaniu z dobrze ugruntowaną pozycją dask.dataframe? Na przykład zobacz przenoszenie z pand do dask, aby wykorzystać wszystkie lokalne rdzenie procesora .
JPP
0

Przed użyciem opcji chunksize, jeśli chcesz być pewien funkcji procesu, którą chcesz zapisać w pętli for chunking, jak wspomniano w @unutbu, możesz po prostu użyć opcji nrows.

small_df = pd.read_csv(filename, nrows=100)

Gdy masz pewność, że blok procesu jest gotowy, możesz umieścić go w pętli łączenia dla całej ramki danych.

sam
źródło