Jak stworzyć tabelę przestawną na bardzo dużych ramkach danych w pandach

Muszę utworzyć tabelę przestawną 2000 kolumn o około 30-50 milionów wierszy z zestawu danych o około 60 milionów wierszy. Próbowałem obracać w kawałkach 100 000 wierszy i to działa, ale kiedy próbuję rekombinować ramki danych, wykonując .append (), po którym następuje .groupby ('someKey').sum (), cała moja pamięć jest zajęta i python w końcu się zawiesza.

Jak mogę zrobić obrót na tak dużych danych z ograniczoną ilością pamięci RAM?

EDIT: dodanie przykładowego kodu

Następujący kod zawiera różne wyniki testów po drodze, ale ostatni wydruk jest tym, co naprawdę nas interesuje. Zauważ, że jeśli zmienimy segMax na 3, zamiast 4, Kod wygeneruje fałszywie dodatni dla poprawnego wyjścia. Głównym problemem jest to, że jeśli wpis shipmentid nie znajduje się w każdym kawałku, który ogląda sum(wawa), nie pojawia się w wyjściu.

import pandas as pd
import numpy as np
import random
from pandas.io.pytables import *
import os

pd.set_option('io.hdf.default_format','table') 

# create a small dataframe to simulate the real data.
def loadFrame():
    frame = pd.DataFrame()
    frame['shipmentid']=[1,2,3,1,2,3,1,2,3] #evenly distributing shipmentid values for testing purposes
    frame['qty']= np.random.randint(1,5,9) #random quantity is ok for this test
    frame['catid'] = np.random.randint(1,5,9) #random category is ok for this test
    return frame

def pivotSegment(segmentNumber,passedFrame):
    segmentSize = 3 #take 3 rows at a time
    frame = passedFrame[(segmentNumber*segmentSize):(segmentNumber*segmentSize + segmentSize)] #slice the input DF

    # ensure that all chunks are identically formatted after the pivot by appending a dummy DF with all possible category values
    span = pd.DataFrame() 
    span['catid'] = range(1,5+1)
    span['shipmentid']=1
    span['qty']=0

    frame = frame.append(span)

    return frame.pivot_table(['qty'],index=['shipmentid'],columns='catid', \
                             aggfunc='sum',fill_value=0).reset_index()

def createStore():

    store = pd.HDFStore('testdata.h5')
    return store

segMin = 0
segMax = 4

store = createStore()
frame = loadFrame()

print('Printing Frame')
print(frame)
print(frame.info())

for i in range(segMin,segMax):
    segment = pivotSegment(i,frame)
    store.append('data',frame[(i*3):(i*3 + 3)])
    store.append('pivotedData',segment)

print('\nPrinting Store')   
print(store)
print('\nPrinting Store: data') 
print(store['data'])
print('\nPrinting Store: pivotedData') 
print(store['pivotedData'])

print('**************')
print(store['pivotedData'].set_index('shipmentid').groupby('shipmentid',level=0).sum())
print('**************')
print('$$$')
for df in store.select('pivotedData',chunksize=3):
    print(df.set_index('shipmentid').groupby('shipmentid',level=0).sum())

print('$$$')
store['pivotedAndSummed'] = sum((df.set_index('shipmentid').groupby('shipmentid',level=0).sum() for df in store.select('pivotedData',chunksize=3)))
print('\nPrinting Store: pivotedAndSummed') 
print(store['pivotedAndSummed'])

store.close()
os.remove('testdata.h5')
print('closed')
Author: TraxusIV, 2015-04-03

1 answers

Możesz zrobić dodawanie za pomocą HDF5 / pytables. To trzyma go z dala od PAMIĘCI RAM.

Użyj formatu tabeli :

store = pd.HDFStore('store.h5')
for ...:
    ...
    chunk  # the chunk of the DataFrame (which you want to append)
    store.append('df', chunk)

Teraz możesz go odczytać jako ramkę danych za jednym zamachem (zakładając, że ta ramka może zmieścić się w pamięci!):

df = store['df']

Można również odpytywać, aby uzyskać tylko podrozdziały ramki danych.

Na bok: powinieneś też kupić więcej pamięci RAM, jest to tanie.


Edit: możesz grupować / sumować ze sklepu iteracyjnie ponieważ ta "mapa-redukuje" nad "chunks": {]}

# note: this doesn't work, see below
sum(df.groupby().sum() for df in store.select('df', chunksize=50000))
# equivalent to (but doesn't read in the entire frame)
store['df'].groupby().sum()

Edit2: używanie sum jak wyżej nie działa w pandas 0.16 (myślałem, że w 0.15.2), zamiast tego można użyć reduce z add:

reduce(lambda x, y: x.add(y, fill_value=0),
       (df.groupby().sum() for df in store.select('df', chunksize=50000)))

w Pythonie 3 musisz zaimportować reduce z functools .

Być może bardziej pythoniczne / czytelne jest zapisanie tego jako:

chunks = (df.groupby().sum() for df in store.select('df', chunksize=50000))
res = next(chunks)  # will raise if there are no chunks!
for c in chunks:
    res = res.add(c, fill_value=0)

jeśli wydajność jest słaba / jeśli istnieje duża liczba nowych grup, może być lepiej rozpocząć res jako zero poprawnego rozmiar (poprzez uzyskanie unikalnych kluczy grupy np. przez zapętlenie przez kawałki), a następnie dodać w miejscu.

 11
Author: Andy Hayden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-08 02:30:43