【问题标题】:Splitting a large Pandas Dataframe with minimal memory footprint以最小的内存占用拆分大型 Pandas 数据帧
【发布时间】:2016-10-28 15:03:15
【问题描述】:

我有一个大型 DataFrame,我想将其拆分为一个测试集和一个训练集以用于模型构建。但是,我不想复制 DataFrame,因为我达到了内存限制。

是否存在类似于 pop 但针对大段的操作,该操作将同时删除 DataFrame 的一部分并允许我将其分配给新的 DataFrame?像这样的:

# Assume I have initialized a DataFrame (called "all") which contains my large dataset, 
# with a boolean column called "test" which indicates whether a record should be used for
# testing.
print len(all)
# 10000000 
test = all.pop_large_segment(all[test]) # not a real command, just a place holder
print len(all)
# 8000000
print len(test)     
# 2000000

【问题讨论】:

  • 据我所知,一旦你完成任务,pandas 就会创建一个副本。如果你只存储训练和测试的索引,它会工作吗?
  • 不回答这个问题,但也许还有其他相关的想法: - 你不能在加载时分割数据集吗? - 或者使用dask (dask.pydata.org/en/latest) 之类的东西?
  • 我知道的唯一原因是分别从 HDF5 表中加载它们并在加载时进行拆分,即首先加载一些行作为训练,然后将其余行作为拆分 - 可以提供正确的答案是这听起来很合理......
  • 在加载时拆分数据集是我在回答中建议的。唯一的障碍是您必须准确跟踪在训练集中加载了哪些行,然后加载其余的行,这会导致大量行变得笨重。你还必须确保你真的得到了一个公正的样本,否则你的训练就会被取消。这似乎是对 Pandas 机器学习能力的一个很好的补充。
  • DataFrames 没有“类似流行的东西”。 DataFrame 的大小是其身份的固有部分;没有办法让它变小或变大。您只能创建不同大小的新 DataFrame。 (看似改变DataFrame大小的操作,如drop,实际上只是创建了一个新的DataFrame。)

标签: python pandas dataframe


【解决方案1】:

由于其他答案更侧重于文件读取,我想您也可以做一些事情,如果出于任何原因没有从文件中读取您的 DataFrame。

也许您可以查看DataFrame.drop method 的代码并对其进行修改,以便就地修改您的DataFrame(drop 方法已经这样做了)返回其他原始数据:

class DF(pd.DataFrame):
    def drop(self, labels, axis=0, level=None, inplace=False, errors='raise'):
        axis = self._get_axis_number(axis)
        axis_name = self._get_axis_name(axis)
        axis, axis_ = self._get_axis(axis), axis

        if axis.is_unique:
            if level is not None:
                if not isinstance(axis, pd.MultiIndex):
                    raise AssertionError('axis must be a MultiIndex')
                new_axis = axis.drop(labels, level=level, errors=errors)
            else:
                new_axis = axis.drop(labels, errors=errors)
            dropped = self.reindex(**{axis_name: new_axis})
            try:
                dropped.axes[axis_].set_names(axis.names, inplace=True)
            except AttributeError:
                pass
            result = dropped

        else:
            labels = com._index_labels_to_array(labels)
            if level is not None:
                if not isinstance(axis, MultiIndex):
                    raise AssertionError('axis must be a MultiIndex')
                indexer = ~axis.get_level_values(level).isin(labels)
            else:
                indexer = ~axis.isin(labels)

            slicer = [slice(None)] * self.ndim
            slicer[self._get_axis_number(axis_name)] = indexer

            result = self.ix[tuple(slicer)]

        if inplace:
            dropped = self.ix[labels]
            self._update_inplace(result)
            return dropped
        else:
            return result, self.ix[labels]

这将像这样工作:

df = DF({'one':[1,2,3,4,5,4,3,2,1], 'two':[6,7,8,9,10,9,8,7,6], 'three':[11,12,13,14,15,14,13,12,11]})

dropped = df.drop(range(5), inplace=True)
# or :
# partA, partB = df.drop(range(5))

这个例子可能不是真正的内存效率,但也许你可以通过使用某种面向对象的解决方案来找出更好的东西。

【讨论】:

  • 我猜这在执行该操作时仍然需要额外的空间,但也许它可以与小块迭代使用。它可能比从文件中读取要快。
  • 这是一个有趣的方法。尽管如此,它还是按顺序拆分数据。在partApartB 结果中,您最终会得到一个数据的上半部分和另一个数据的下半部分。有偏差的样本,除非您的数据在磁盘上随机打乱。
  • @JeffL。我想通过使用小于特征数量的随机和唯一整数列表作为 drop 函数的参数来获取随机值并不难(比如random.sample(range(len(df)), n_wanted_values),而不是像这里那样的连续值范围) range(5))
  • @mgc 这个版本的 drop 没有达到 OP 的预期。例如,reindex 已经在制作数据的副本。
【解决方案2】:

我会做与@jeff-l 类似的事情,即将您的数据框存档。当您以 csv 格式读取它时,请使用 chunksize 关键字。以下脚本说明了这一点:

import pandas
import numpy

test = 5
m, n = 2*test, 3

df = pandas.DataFrame(
    data=numpy.random.random((m, n))
)

df['test'] = [0] * test + [1] * test 

df.to_csv('tmp.csv', index=False)

for chunk in pandas.read_csv('tmp.csv', chunksize=test):
    print chunk
    del chunk

【讨论】:

  • 这会按顺序从磁盘中提取数据,并且训练数据集应该是无偏样本。尽管如果您知道数据已经在磁盘上随机洗牌,这将起作用..
【解决方案3】:

如果您有空间再添加一列,您可以添加一个带有随机值的列,然后您可以对其进行过滤以进行测试。这里我使用了 0 到 1 之间的统一,但是如果你想要不同的比例,你可以使用任何东西。

df = pd.DataFrame({'one':[1,2,3,4,5,4,3,2,1], 'two':[6,7,8,9,10,9,8,7,6], 'three':[11,12,13,14,15,14,13,12,11]})
df['split'] = np.random.randint(0, 2, size=len(df))

当然,这需要您有空间来添加一个全新的列 - 特别是如果您的数据很长,也许您不需要。

例如,如果您的数据是 csv 格式并且您知道行数,则可以使用另一个选项。使用randomint 与上述类似,但将该列表传递给Pandas read_csv()skiprows 参数:

num_rows = 100000
all = range(num_rows)

some = np.random.choice(all, replace=False, size=num_rows/2)
some.sort()
trainer_df = pd.read_csv(path, skiprows=some)

rest = [i for i in all if i not in some]
rest.sort()
df = pd.read_csv(path, skiprows=rest)

前面有点笨拙,尤其是列表理解中的循环,并且在内存中创建这些列表是不幸的,但它仍然应该比只创建一半数据的完整副本更好。

为了使内存更加友好,您可以加载训练器子集,训练模型,然后用其余数据覆盖训练数据框,然后应用模型。您将无法携带somerest,但您永远不必同时加载两半数据。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-04-02
    • 2014-02-05
    • 1970-01-01
    • 1970-01-01
    • 2019-04-13
    • 2012-12-19
    • 1970-01-01
    • 2010-10-15
    相关资源
    最近更新 更多