Jak przechowywać ramkę danych za pomocą Pand


317

Obecnie CSVza każdym razem, gdy uruchamiam skrypt, importuję dość dużą ramkę danych. Czy istnieje dobre rozwiązanie, aby utrzymywać tę ramkę danych stale dostępną między uruchomieniami, aby nie musiałem spędzać całego czasu na czekaniu na uruchomienie skryptu?


2
Tak, jest to jedna z moich głównych skarg za pomocą Pythona - nie ma prostego sposobu zapisywania i pobierania ramek danych. R i SAS są pod tym względem znacznie bardziej przyjazne dla użytkownika.
RobertF

Odpowiedzi:


481

Najprostszym sposobem jest marynowanie go za pomocą to_pickle:

df.to_pickle(file_name)  # where to save it, usually as a .pkl

Następnie możesz go ponownie załadować, używając:

df = pd.read_pickle(file_name)

Uwaga: przed 0.11.1 savei loadbyły jedynym sposobem na to (teraz są one przestarzałe na korzyść to_picklei read_pickleodpowiednio).


Innym popularnym wyborem jest użycie HDF5 ( pytania ), który oferuje bardzo szybki czas dostępu dla dużych zestawów danych:

store = HDFStore('store.h5')

store['df'] = df  # save it
store['df']  # load it

Bardziej zaawansowane strategie są omówione w książce kucharskiej .


Od wersji 0.13 istnieje również msgpack, który może być lepszy dla interoperacyjności, jako szybsza alternatywa dla JSON, lub jeśli masz dane obiektów / tekstochłonne dla Pythona (zobacz to pytanie ).


8
Zapis @geekazoid jest przestarzały do ​​to_pickle (który tworzy piklę zamiast csv, co jest znacznie szybszym / innym obiektem).
Andy Hayden,

9
@geekazoid W przypadku, gdy dane muszą zostać przekształcone po załadowaniu (np. ciąg / obiekt do datetime64), należy to zrobić ponownie po załadowaniu zapisanego pliku csv, co spowoduje utratę wydajności. marynata zapisuje ramkę danych w jej bieżącym stanie, dzięki czemu dane i ich format zostają zachowane. Może to prowadzić do znacznego wzrostu wydajności.
harbun

4
Zarówno marynata, jak i HDFStore nie mogą zapisać ramki danych większej niż 8 GB. Czy są alternatywy?
user1700890,

1
@ user1700890 spróbuj wygenerować z losowych danych (tekst i tablice) i opublikuj nowe pytanie. Nie sądzę, że to może być słuszne / podejrzewamy, że czegoś brakuje. Nowe pytanie zyska więcej oczu, ale spróbuj dołączyć / wygenerować ramkę danych, która się odtwarza :)
Andy Hayden

1
@YixingLiu możesz zmienić tryb po fakcie stackoverflow.com/a/16249655/1240268
Andy Hayden

100

Chociaż są już pewne odpowiedzi, znalazłem ładne porównanie, w którym próbowali szeregować serializacje Pandas DataFrames: Skutecznie przechowuj Pandas DataFrames .

Porównują:

  • marynata: oryginalny format danych ASCII
  • cPickle, biblioteka C.
  • pickle-p2: używa nowszego formatu binarnego
  • json: biblioteka standardowego jsona
  • json-no-index: jak json, ale bez indeksu
  • msgpack: binarna alternatywa JSON
  • CSV
  • hdfstore: format pamięci HDF5

W swoim eksperymencie serializują ramkę danych o wartości 1 000 000 wierszy, a dwie kolumny testowane są osobno: jedna z danymi tekstowymi, druga z liczbami. Ich zastrzeżenie mówi:

Nie należy ufać, że to, co następuje, uogólnia Twoje dane. Powinieneś spojrzeć na własne dane i samemu przeprowadzić testy porównawcze

Kod źródłowy testu, do którego się odnoszą, jest dostępny online . Ponieważ ten kod nie działał bezpośrednio, wprowadziłem kilka drobnych zmian, które można uzyskać tutaj: serialize.py Otrzymałem następujące wyniki:

wyniki porównania czasu

Wspominają również, że przy konwersji danych tekstowych na dane kategoryczne serializacja jest znacznie szybsza. W ich teście około 10 razy szybciej (zobacz także kod testowy).

Edycja : Wyższy czas marynowania niż CSV można wytłumaczyć zastosowanym formatem danych. Domyślnie pickleużywa drukowanej reprezentacji ASCII, która generuje większe zestawy danych. Jak widać z wykresu, piklowanie przy użyciu nowszego formatu danych binarnych (wersja 2 pickle-p2) ma znacznie krótsze czasy ładowania.

Inne odniesienia:


1
Zaktualizowałem swoją odpowiedź, aby wyjaśnić twoje pytanie. Podsumowując: domyślnie piklina przechowuje dane w formacie ASCII.
ponownie

1
Ach, dzięki za wyjaśnienie! Uwaga: pandy DataFrame .to_pickle wydają się używać pkl.HIGHEST_PROTOCOL (powinno być 2)
ntg

2
Wygląda na to, że blog z powyższym linkiem ( Skutecznie przechowuj Pandas DataFrames został usunięty. Zrobiłem własne porównania z .to_pickle()(który używa pamięci binarnej) z .to_hdf()(bez kompresji). Celem była szybkość, rozmiar pliku dla HDF 11x Pickle i czas do załadowania było 5x Pickle. Moje dane to ~ 5k plików ~ 7k wierszy x 6 kols każdy, głównie numerycznych.
hamx0r

1
Strona nadal istnieje, wystarczy usunąć ukośnik: Skutecznie przechowuj Pandas DataFrames
IanSR

2
@Mike Williamson, w moim teście, pikle był 5 razy szybszy do załadowania niż HDF, a także zajął 1/11 miejsca na dysku (tj. Hdf był 11 razy większy na dysku i zabrał 5 razy tyle czasu do załadowania z dysku, co marynata). wszystko to dotyczyło Pythona 3 z pandami 0.22.0.
hamx0r

35

Jeśli dobrze rozumiem, już używasz, pandas.read_csv()ale chcesz przyspieszyć proces programowania, abyś nie musiał ładować pliku za każdym razem, gdy edytujesz skrypt, prawda? Mam kilka rekomendacji:

  1. możesz załadować tylko część pliku CSV, używając pandas.read_csv(..., nrows=1000)tylko do załadowania górnego bitu tabeli podczas tworzenia

  2. użyj ipython do interaktywnej sesji, tak aby zachować tabelę pand w pamięci podczas edycji i ponownego ładowania skryptu.

  3. przekonwertować plik csv na tabelę HDF5

  4. zaktualizowane użycie DataFrame.to_feather()i pd.read_feather()do przechowywania danych w formacie binarnym piórkowym kompatybilnym z R, który jest super szybki (w moich rękach, nieco szybszy niż pandas.to_pickle()w przypadku danych liczbowych i znacznie szybszy w przypadku danych ciągowych).

Możesz być także zainteresowany tą odpowiedzią na stackoverflow.


Czy wiesz, dlaczego to_feathermiałby dobrze działać na danych ciągów? Przeprowadziłem testy to_picklei to_featurena mojej numerycznej ramce danych i pikle jest około 3 razy szybsza.
zyxue,

@zyxue dobre pytanie, szczerze mówiąc, nie grałem dużo z piórami, więc nie mam odpowiedzi
Noah

20

Marynata działa dobrze!

import pandas as pd
df.to_pickle('123.pkl')    #to save the dataframe, df to 123.pkl
df1 = pd.read_pickle('123.pkl') #to load 123.pkl back to the dataframe df

8
Zauważ, że generowane pliki nie są plikami csv, być może lepiej jest użyć rozszerzenia .pklzgodnie z sugestią @Andy Haydens.
ponownie

5

Możesz użyć pliku w formacie piórkowym. To jest bardzo szybkie.

df.to_feather('filename.ft')

Dane można następnie wykorzystać bezpośrednio za Rpomocą featherbiblioteki.
James Hirschorn

4

Pandas DataFrames mają to_picklefunkcję przydatną do zapisywania DataFrame:

import pandas as pd

a = pd.DataFrame({'A':[0,1,0,1,0],'B':[True, True, False, False, False]})
print a
#    A      B
# 0  0   True
# 1  1   True
# 2  0  False
# 3  1  False
# 4  0  False

a.to_pickle('my_file.pkl')

b = pd.read_pickle('my_file.pkl')
print b
#    A      B
# 0  0   True
# 1  1   True
# 2  0  False
# 3  1  False
# 4  0  False

4

Jak już wspomniano, istnieją różne opcje i formaty plików ( HDF5 , JSON , CSV , parkiet , SQL ) do przechowywania ramki danych. Jednak picklenie jest obywatelem pierwszej klasy (w zależności od konfiguracji), ponieważ:

  1. picklejest potencjalnym zagrożeniem bezpieczeństwa. Utwórz dokumentację Pythona dla marynaty :

Ostrzeżenie The picklemoduł nie jest zabezpieczony przed błędnymi lub złośliwie skonstruowanych danych. Nigdy nie usuwaj danych otrzymanych z niezaufanego lub nieuwierzytelnionego źródła.

  1. picklejest wolny. Znajdź tutaj i tutaj testy porównawcze.

W zależności od konfiguracji / użytkowania oba ograniczenia nie mają zastosowania, ale nie zalecałbym picklejako domyślnej trwałości ramek danych pand.


1

Formaty plików Numpy są dość szybkie w przypadku danych liczbowych

Wolę używać plików numpy, ponieważ są one szybkie i łatwe w obsłudze. Oto prosty test porównawczy do zapisywania i ładowania ramki danych z 1 kolumną 1 miliona punktów.

import numpy as np
import pandas as pd

num_dict = {'voltage': np.random.rand(1000000)}
num_df = pd.DataFrame(num_dict)

za pomocą %%timeitmagicznej funkcji ipython

%%timeit
with open('num.npy', 'wb') as np_file:
    np.save(np_file, num_df)

wyjście jest

100 loops, best of 3: 5.97 ms per loop

aby załadować dane z powrotem do ramki danych

%%timeit
with open('num.npy', 'rb') as np_file:
    data = np.load(np_file)

data_df = pd.DataFrame(data)

wyjście jest

100 loops, best of 3: 5.12 ms per loop

NIE JEST ZŁY!

CONS

Występuje problem, jeśli zapiszesz plik numpy za pomocą Pythona 2, a następnie spróbujesz otworzyć go za pomocą Pythona 3 (lub odwrotnie).


6
zwróć uwagę, że to rozwiązanie usunie wszystkie nazwy kolumn i zmieni dane liczb całkowitych na zmiennoprzecinkowe :(
Joseph Garvin

0

https://docs.python.org/3/library/pickle.html

Formaty protokołu piklowania:

Wersja protokołu 0 jest oryginalnym protokołem „czytelnym dla człowieka” i jest wstecznie kompatybilna z wcześniejszymi wersjami Pythona.

Wersja protokołu 1 jest starym formatem binarnym, który jest również zgodny z wcześniejszymi wersjami Pythona.

Wersja protokołu 2 została wprowadzona w Pythonie 2.3. Zapewnia znacznie bardziej wydajne wytrawianie klas w nowym stylu. Informacje na temat ulepszeń wprowadzonych przez protokół 2 znajdują się w PEP 307.

Protokół w wersji 3 został dodany w Pythonie 3.0. Ma jawną obsługę obiektów bajtów i nie może zostać usunięty przez Python 2.x. Jest to domyślny protokół i zalecany protokół, gdy wymagana jest kompatybilność z innymi wersjami Python 3.

Wersja protokołu 4 została dodana w Pythonie 3.4. Dodaje obsługę bardzo dużych obiektów, wytrawianie większej liczby rodzajów obiektów i niektóre optymalizacje formatu danych. Informacje na temat ulepszeń wprowadzonych przez protokół 4 można znaleźć w PEP 3154.


0

kompatybilność z wróblem między wersjami

Ogólny ruch został skierowany do pióra / pióra (ostrzeżenia o wycofaniu z pand / msgpack). Mam jednak wyzwanie w przypadku kruszywa z przejściową specyfikacją Dane zserializowane w kruszywie 0.15.1 nie mogą być deserializowane za pomocą 0.16.0 ARROW-7961 . Używam serializacji, aby użyć redis, więc muszę użyć kodowania binarnego.

Przetestowałem różne opcje (za pomocą notatnika Jupyter)

import sys, pickle, zlib, warnings, io
class foocls:
    def pyarrow(out): return pa.serialize(out).to_buffer().to_pybytes()
    def msgpack(out): return out.to_msgpack()
    def pickle(out): return pickle.dumps(out)
    def feather(out): return out.to_feather(io.BytesIO())
    def parquet(out): return out.to_parquet(io.BytesIO())

warnings.filterwarnings("ignore")
for c in foocls.__dict__.values():
    sbreak = True
    try:
        c(out)
        print(c.__name__, "before serialization", sys.getsizeof(out))
        print(c.__name__, sys.getsizeof(c(out)))
        %timeit -n 50 c(out)
        print(c.__name__, "zlib", sys.getsizeof(zlib.compress(c(out))))
        %timeit -n 50 zlib.compress(c(out))
    except TypeError as e:
        if "not callable" in str(e): sbreak = False
        else: raise
    except (ValueError) as e: print(c.__name__, "ERROR", e)
    finally: 
        if sbreak: print("=+=" * 30)        
warnings.filterwarnings("default")

Z następującymi wynikami dla mojej ramki danych (w outzmiennej jupyter)

pyarrow before serialization 533366
pyarrow 120805
1.03 ms ± 43.9 µs per loop (mean ± std. dev. of 7 runs, 50 loops each)
pyarrow zlib 20517
2.78 ms ± 81.8 µs per loop (mean ± std. dev. of 7 runs, 50 loops each)
=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+=
msgpack before serialization 533366
msgpack 109039
1.74 ms ± 72.8 µs per loop (mean ± std. dev. of 7 runs, 50 loops each)
msgpack zlib 16639
3.05 ms ± 71.7 µs per loop (mean ± std. dev. of 7 runs, 50 loops each)
=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+=
pickle before serialization 533366
pickle 142121
733 µs ± 38.3 µs per loop (mean ± std. dev. of 7 runs, 50 loops each)
pickle zlib 29477
3.81 ms ± 60.4 µs per loop (mean ± std. dev. of 7 runs, 50 loops each)
=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+=
feather ERROR feather does not support serializing a non-default index for the index; you can .reset_index() to make the index into column(s)
=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+=
parquet ERROR Nested column branch had multiple children: struct<x: double, y: double>
=+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+=

pióro i parkiet nie działają w mojej ramce danych. Będę nadal używać pyarrowa. Jednak uzupełnię marynatą (bez kompresji). Pisząc do pamięci podręcznej, przechowuj puchary i marynowane zserializowane formy. Podczas odczytywania z rezerwowej pamięci podręcznej do piklowania, jeśli desekrializacja kruszyny nie powiedzie się.


To nie odpowiada na pytanie
Jason S

0

Format zależy od przypadku użycia

  • Zapisz DataFrame między sesjami notebooka - pióro , jeśli jesteś przyzwyczajony do marynowania - również w porządku.
  • Zapisz DataFrame w najmniejszym możliwym rozmiarze pliku - parkiet lub pickle.gz (sprawdź, co jest lepsze dla twoich danych)
  • Zapisz bardzo dużą ramkę danych (ponad 10 milionów wierszy) - hdf
  • Być w stanie odczytać dane na innej platformie (innej niż Python), która nie obsługuje innych formatów - csv , csv.gz , sprawdź, czy parkiet jest obsługiwany
  • Możesz recenzować na własne oczy / używając Excela / Arkuszy Google / Git diff - csv
  • Zapisz ramkę danych, która zajmuje prawie całą pamięć RAM - csv

Porównanie formatów plików pand znajduje się w tym filmie .

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.