Duży, trwały DataFrame w pandach

93

Odkrywam przejście na Pythona i pandy jako wieloletni użytkownik SAS.

Jednak podczas dzisiejszego przeprowadzania niektórych testów byłem zaskoczony, że Pythonowi zabrakło pamięci podczas próby pandas.read_csv()pliku CSV o wielkości 128 MB. Miał około 200 000 wierszy i 200 kolumn zawierających głównie dane liczbowe.

Dzięki SAS mogę zaimportować plik csv do zestawu danych SAS i może on mieć rozmiar odpowiadający rozmiarowi mojego dysku twardego.

Czy jest w tym coś analogicznego pandas?

Regularnie pracuję z dużymi plikami i nie mam dostępu do rozproszonej sieci komputerowej.

Żelazny7
źródło
Nie jestem zaznajomiony z pandami, ale możesz chcieć przejrzeć iterację w pliku. pandas.pydata.org/pandas-docs/stable/ ...
monkut

Odpowiedzi:

80

Zasadniczo nie powinno zabraknąć pamięci, ale obecnie występują problemy z pamięcią przy read_csvdużych plikach spowodowane przez złożone problemy wewnętrzne Pythona (jest to niejasne, ale znane jest od dawna: http://github.com/pydata / pandy / Issues / 407 ).

W tej chwili nie ma idealnego rozwiązania (tutaj jest nudne: możesz transkrybować plik wiersz po wierszu na wstępnie przydzieloną tablicę NumPy lub plik mapowany w pamięci - np.mmap), ale nad tym będę pracować w najbliższej przyszłości. Innym rozwiązaniem jest odczytanie pliku w mniejszych fragmentach (użycie iterator=True, chunksize=1000), a następnie połączenie z pd.concat. Problem pojawia się, gdy przeciągniesz cały plik tekstowy do pamięci jednym wielkim łykiem.

Wes McKinney
źródło
1
Powiedzmy, że mogę odczytać plik i połączyć je wszystkie razem w jedną ramkę DataFrame. Czy DataFrame musi znajdować się w pamięci? Dzięki SAS mogę pracować z zestawami danych dowolnej wielkości, o ile mam miejsce na dysku twardym. Czy to samo dotyczy ramek DataFrames? Mam wrażenie, że ogranicza ich pamięć RAM, a nie miejsce na dysku twardym. Przepraszamy za pytanie noob i dziękuję za pomoc. Podoba mi się twoja książka.
Zelazny7
3
Racja, ogranicza Cię pamięć RAM. SAS rzeczywiście ma znacznie lepsze wsparcie dla przetwarzania dużych zbiorów danych „poza rdzeniem”.
Wes McKinney,
5
@WesMcKinney Te obejścia nie powinny być już potrzebne, z powodu nowego modułu ładującego CSV, który wylądowałeś w wersji 0.10, prawda?
Gabriel Grant
81

Wes ma oczywiście rację! Wbijam się tylko w trochę bardziej kompletny przykładowy kod. Miałem ten sam problem z plikiem 129 Mb, który został rozwiązany przez:

import pandas as pd

tp = pd.read_csv('large_dataset.csv', iterator=True, chunksize=1000)  # gives TextFileReader, which is iterable with chunks of 1000 rows.
df = pd.concat(tp, ignore_index=True)  # df is DataFrame. If errors, do `list(tp)` instead of `tp`
fickludd
źródło
6
Myślę, że możesz po prostu zrobić df = concate(tp, ignore_index=True)?
Andy Hayden
@smci Próbowałem to szybko z tymi samymi danymi powtórzonymi x4 (550 Mb) lub x8 (1,1 Gb). Co ciekawe, z lub bez [x for x in tp], x4 przeszedł dobrze, a x8 rozbił się w MemoryError.
fickludd
3
Otrzymuję ten błąd podczas korzystania z niego: AssertionError: first argument must be a list-like of pandas objects, you passed an object of type "TextFileReader". Masz pojęcie, co się tutaj dzieje?
Prince Kumar
3
Ten błąd zostanie naprawiony w 0.14 (wydanie wkrótce), github.com/pydata/pandas/pull/6941 ; obejście dla <0.14.0 jest do zrobieniapd.concat(list(tp), ignore_index=True)
Jeff
1
co, jeśli wartości są ciągami lub kategoriami
pojawia
41

To jest starszy wątek, ale chciałem tylko zrzucić tutaj moje rozwiązanie obejścia. Początkowo próbowałem tego chunksizeparametru (nawet przy dość małych wartościach, takich jak 10000), ale niewiele to pomogło; nadal miałem problemy techniczne z rozmiarem pamięci (mój plik CSV miał ~ 7,5 Gb).

W tej chwili po prostu czytam fragmenty plików CSV w podejściu pętli for i dodaję je np. Do bazy danych SQLite krok po kroku:

import pandas as pd
import sqlite3
from pandas.io import sql
import subprocess

# In and output file paths
in_csv = '../data/my_large.csv'
out_sqlite = '../data/my.sqlite'

table_name = 'my_table' # name for the SQLite database table
chunksize = 100000 # number of lines to process at each iteration

# columns that should be read from the CSV file
columns = ['molecule_id','charge','db','drugsnow','hba','hbd','loc','nrb','smiles']

# Get number of lines in the CSV file
nlines = subprocess.check_output('wc -l %s' % in_csv, shell=True)
nlines = int(nlines.split()[0]) 

# connect to database
cnx = sqlite3.connect(out_sqlite)

# Iteratively read CSV and dump lines into the SQLite table
for i in range(0, nlines, chunksize):

    df = pd.read_csv(in_csv,  
            header=None,  # no header, define column header manually later
            nrows=chunksize, # number of rows to read at each iteration
            skiprows=i)   # skip rows that were already read

    # columns to read        
    df.columns = columns

    sql.to_sql(df, 
                name=table_name, 
                con=cnx, 
                index=False, # don't use CSV file index
                index_label='molecule_id', # use a unique column from DataFrame as index
                if_exists='append') 
cnx.close()    

źródło
4
Bardzo przydatne, aby zobaczyć realistyczny przypadek użycia funkcji fragmentarycznego czytania. Dzięki.
Alex Kestner
5
Tylko mała uwaga do tego starego tematu: pandas.read_csvbezpośrednio zwraca (przynajmniej w wersji, której obecnie używam) iterator, jeśli po prostu podasz iterator=Truei chunksize=chunksize. W związku z tym po prostu wykonasz forpętlę przez pd.read_csvwywołanie, zamiast ponownie tworzyć jego instancję za każdym razem. Jednak kosztuje to tylko koszt połączenia, może nie mieć znaczącego wpływu.
Joël
1
Cześć, Joel. Dzięki za wiadomość! iterator=TrueI chunksizeparametry istniały już wtedy, jeśli dobrze pamiętam. Może w starszej wersji był błąd, który spowodował wysadzenie pamięci - spróbuję następnym razem, gdy przeczytam dużą ramkę DataFrame w Pandach (teraz głównie używam Blaze do takich zadań)
6

Poniżej znajduje się mój przebieg pracy.

import sqlalchemy as sa
import pandas as pd
import psycopg2

count = 0
con = sa.create_engine('postgresql://postgres:pwd@localhost:00001/r')
#con = sa.create_engine('sqlite:///XXXXX.db') SQLite
chunks = pd.read_csv('..file', chunksize=10000, encoding="ISO-8859-1",
                     sep=',', error_bad_lines=False, index_col=False, dtype='unicode')

Bazując na rozmiarze pliku, lepiej zoptymalizuj rozmiar fragmentu.

 for chunk in chunks:
        chunk.to_sql(name='Table', if_exists='append', con=con)
        count += 1
        print(count)

Po umieszczeniu wszystkich danych w bazie danych możesz wyszukiwać te, których potrzebujesz, z bazy danych.

BEN_YO
źródło
3

Jeśli chcesz załadować ogromne pliki csv, dask może być dobrą opcją. Naśladuje api pandy, więc wydaje się być bardzo podobny do pandy

link do dask na githubie

user8108173
źródło
Dzięki, odkąd to opublikowałem, używam dask i formatu parkietu.
Zelazny7
1

Możesz użyć Pytable zamiast pandy df. Jest przeznaczony do dużych zbiorów danych, a format pliku to hdf5. Więc czas przetwarzania jest stosunkowo szybki.

Elm662
źródło