Как создать сводную таблицу на чрезвычайно больших кадрах данных в Pandas

Мне нужно создать сводную таблицу из 2000 столбцов примерно на 30-50 миллионов строк из набора данных около 60 миллионов строк. Я пытался опрокинуть куски из 100 000 строк, и это работает, но когда я пытаюсь рекомбинировать DataFrames, выполняя .append(), за которым следует .groupby('someKey'). Sum(), вся моя память занята и python в конечном итоге сбой.

Как я могу сделать точку с данными с большим объемом ОЗУ?

EDIT: добавление примерного кода

Следующий код включает в себя различные тестовые выходы на этом пути, но последний отпечаток - это то, что нас действительно интересует. Обратите внимание, что если мы изменим segMax на 3, а вместо 4, код будет генерировать ложный положительный результат для правильного вывода, Основная проблема заключается в том, что, если запись shippingid не находится в каждом куске, на который рассчитывается сумма (wawa), она не отображается на выходе.

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')

Ответ 1

Вы можете сделать добавление с помощью HDF5/pytables. Это предотвращает его из ОЗУ.

Используйте формат :

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

Теперь вы можете прочитать его как DataFrame за один раз (при условии, что этот DataFrame может поместиться в память!):

df = store['df']

Вы также можете запросить, чтобы получить только подразделы DataFrame.

Кроме того, вы также должны купить больше оперативной памяти, это дешево.


Изменить: вы можете группировать/суммировать из магазина итеративно, поскольку эта "карта уменьшает" по кускам:

# 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: Использование суммы, как указано выше, фактически не работает в pandas 0,16 (я думал, что это было в 0.15.2), вместо этого вы можете использовать reduce с add:

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

В python 3 вы должны уменьшить импорт из functools.

Возможно, более питонно/читаемо, чтобы написать это как:

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)

Если производительность невысока/если имеется большое количество новых групп, тогда может быть предпочтительнее начать сброс как нуль нужного размера (путем получения уникальных групповых клавиш, например, путем циклического прохождения кусков), а затем добавить на месте.