如何在 Pandas 中的超大数据框上创建数据透视表

Posted

技术标签:

【中文标题】如何在 Pandas 中的超大数据框上创建数据透视表【英文标题】:How to create a pivot table on extremely large dataframes in Pandas 【发布时间】:2015-06-08 23:31:52 【问题描述】:

我需要从大约 6000 万行的数据集中创建一个包含 2000 列、大约 30-5000 万行的数据透视表。我尝试过以 100,000 行为一组进行旋转,这很有效,但是当我尝试通过执行 .append() 和 .groupby('someKey').sum() 来重新组合 DataFrame 时,我的所有内存都被占用了而python最终崩溃了。

如何使用有限的 RAM 对这么大的数据进行数据透视?

编辑:添加示例代码

下面的代码包括各种测试输出,但最后的打印是我们真正感兴趣的。请注意,如果我们将 segMax 更改为 3,而不是 4,代码将产生正确输出的误报.主要问题是如果一个 shippingid 条目不在 sum(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')

【问题讨论】:

值得注意的是,不仅python会崩溃,它还会导致整个计算机崩溃。显然不是我可以让它煮几天的情况。 根据数据的性质,您可能想尝试使用sparse DataFrames。它可以为您节省大量内存。 由于我的 shippingid 值都是数字,我现在正在尝试从 pivotedData 表中手动选择一个 shippingid 的整数值,从 0 增加到 500 万左右,然后执行sum() 结果,并将其附加到存储中的结果表中。但是,每次选择都需要很长时间,尤其是当特定的shippingid 不存在条目时。将继续使用压缩设置,看看是否有帮助。 为什么不使用 RDMS 来聚合您的数据集? SQL 引擎旨在存储数百万条记录并按组处理 Sum() 等基本处理。正如您的枢轴所指出的那样,我假设是两个字节大小的字段(ids)和一个整数(qty)字段,临时数据库表不应该太广泛而无法存储和查询。考虑在 SQL Server、Oracle、mysql、PostgreSQL 或任何其他内部进行聚合,并将结果传递给 Python dataframe。 数据来自哪里?数据库(如果有,是哪个?)、.csv 文件、HDF5 等 【参考方案1】:

您可以使用 HDF5/pytables 进行附加。这使它远离 RAM。

使用table format:

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 的子部分。

另外:你还应该买更多的内存,它很便宜。


编辑:您可以从商店iteratively 中进行分组/求和,因为这个“映射减少”了块:

# 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:使用上述 sum 实际上在 pandas 0.16 中不起作用(我认为它在 0.15.2 中起作用),相反,您可以将 reduceadd 一起使用:

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

在 python 3 中你必须import reduce from functools。

也许写成这样更符合pythonic/可读性:

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)

如果性能很差/如果有大量新组,则最好将 res 作为正确大小的零开始(通过获取唯一组键,例如通过循环块),然后就地添加。

【讨论】:

@TraxusIV 嗯,如果您有少量组,您可以迭代地进行(通过选择每个组并求和) - 如果您有很多行,这会很慢。我认为这将是一个很好的(新)问题。一个小谷歌只发现this(从 2006 年开始!)建议不,你需要走迭代方式(我建议)......过去 9 年情况可能有所改善?? @TraxusIV 对于分组求和,映射/减少,从某种意义上说,您可以对块进行分组和求和,然后将结果相加。因此,通过商店中的 df 大块。类似于: sum(df.groupby().sum() for df in store.select('df', chunksize=50000)) ?见pandas.pydata.org/pandas-docs/stable/io.html#iterator @TraxusIV 注意:无论你有多少组,这都会很快,如果你必须迭代地提取 每个组,那么慢就是你想要的/快。 @TraxusIV sum 会将多个条目相加 - 这就是您想要的。 @TraxusIV 你用的是什么版本的熊猫,我以为我测试过了,它工作了

以上是关于如何在 Pandas 中的超大数据框上创建数据透视表的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Pandas 中的数据透视表计算标准差?

在 Pandas 中处理大型数据透视表

具有多索引的 Pandas 数据透视表小计

用于搜索的 Pandas 列索引?

一文搞定Pandas的透视表

如何根据条件行值对 pandas 数据框进行取消堆叠或取消透视?