19

似乎我可以通过创建 mmap'd ndarray 并使用它来初始化系列来为 python 系列的基础数据进行内存映射。

        def assert_readonly(iloc):
           try:
               iloc[0] = 999 # Should be non-editable
               raise Exception("MUST BE READ ONLY (1)")
           except ValueError as e:
               assert "read-only" in e.message

        # Original ndarray
        n = 1000
        _arr = np.arange(0,1000, dtype=float)

        # Convert it to a memmap
        mm = np.memmap(filename, mode='w+', shape=_arr.shape, dtype=_arr.dtype)
        mm[:] = _arr[:]
        del _arr
        mm.flush()
        mm.flags['WRITEABLE'] = False  # Make immutable!

        # Wrap as a series
        s = pd.Series(mm, name="a")
        assert_readonly(s.iloc)

成功!它似乎s由只读的 mem 映射的 ndarray 支持。我可以对 DataFrame 做同样的事情吗?以下失败

        df = pd.DataFrame(s, copy=False, columns=['a'])
        assert_readonly(df["a"]) # Fails

以下成功,但仅针对一列:

        df = pd.DataFrame(mm.reshape(len(mm,1)), columns=['a'], copy=False)
        assert_readonly(df["a"]) # Succeeds

...所以我可以在不复制的情况下制作 DF。但是,这只适用于一列,我想要很多。我发现用于组合 1 列 DF 的方法: pd.concat(..copy=False), pd.merge(copy=False), ... 导致副本。

我有数千个大列作为数据文件,我一次只需要几个。我希望我能够将他们的 mmap 表示放在上面的 DataFrame 中。是否可以?

Pandas 文档让人有点难以猜测这里到底发生了什么——尽管它确实说 DataFrame “可以被认为是 Series 对象的类似 dict 的容器”。. 我开始这不再是这种情况。

我宁愿不需要 HD5 来解决这个问题。

4

2 回答 2

25

好的......经过大量挖掘后,这里发生了什么事。PandasDataFrame使用BlockManager该类在内部组织数据。与文档相反,DataFrame不是系列的集合,而是类似 dtyped 矩阵的集合。BlockManger将所有 float 列组合在一起,将所有 int 列组合在一起,等等......,并且它们的内存(据我所知)保持在一起。

ndarray仅当提供单个矩阵(单一类型)时,它才可以在不复制内存的情况下做到这一点。请注意,BlockManager(理论上)在其构造中也支持不复制混合类型数据,因为可能不需要将此输入复制到相同类型的分块中。但是,DataFrame仅当单个矩阵是数据参数时,构造函数才不会进行复制。

简而言之,如果您将混合类型或多个数组作为构造函数的输入,或者提供带有单个数组的 dict,那么您在 Pandas 中就不走运了,并且DataFrame' 的默认值BlockManager将复制您的数据。

在任何情况下,解决此问题的一种方法是强制BlockManager不按类型合并,而是将每列保留为单独的“块”。所以,用猴子修补魔法......

from pandas.core.internals import BlockManager
class BlockManagerUnconsolidated(BlockManager):
    def __init__(self, *args, **kwargs):
        BlockManager.__init__(self, *args, **kwargs)
        self._is_consolidated = False
        self._known_consolidated = False

    def _consolidate_inplace(self): pass
    def _consolidate(self): return self.blocks


def df_from_arrays(arrays, columns, index):
    from pandas.core.internals import make_block
    def gen():
        _len = None
        p = 0
        for a in arrays:
            if _len is None:
                _len = len(a)
                assert len(index) == _len
            assert _len == len(a)
            yield make_block(values=a.reshape((1,_len)), placement=(p,))
            p+=1

    blocks = tuple(gen())
    mgr = BlockManagerUnconsolidated(blocks=blocks, axes=[columns, index])
    return pd.DataFrame(mgr, copy=False)

DataFrame如果指定或BlockManger具有consolidate=False(或假设这种行为)如果copy=False被指定会更好。

去测试:

def assert_readonly(iloc):
    try:
        iloc[0] = 999 # Should be non-editable
        raise Exception("MUST BE READ ONLY (1)")
    except ValueError as e:
        assert "read-only" in e.message

# Original ndarray
n = 1000
_arr = np.arange(0,1000, dtype=float)

# Convert it to a memmap
mm = np.memmap(filename, mode='w+', shape=_arr.shape, dtype=_arr.dtype)
mm[:] = _arr[:]
del _arr
mm.flush()
mm.flags['WRITEABLE'] = False  # Make immutable!

df = df_from_arrays(
    [mm, mm, mm],
    columns=['a', 'b', 'c'],
    index=range(len(mm)))
assert_read_only(df["a"].iloc)
assert_read_only(df["b"].iloc)
assert_read_only(df["c"].iloc)

对我来说,要求将相似类型的数据保存在一起是否真的有实际好处似乎有点疑问——Pandas 中的大多数操作都是按标签行或按列进行的——这是从异构结构中BlockManager得出的DataFrame通常仅通过其索引关联的列。尽管他们为每个“块”保留一个索引是可行的,但如果索引保持在块中的偏移量(如果是这种情况,那么他们应该按 分组sizeof(dtype),我不认为是这种情况)。哼哼……

有一些关于 PR提供非复制构造函数的讨论,但被放弃了。

看起来有合理的计划逐步淘汰BlockManager,所以你的里程数会有所不同。

另请参阅Pandas under the hood,这对我帮助很大。

于 2017-12-19T20:34:45.123 回答
3

如果您更改 DataFrame 构造函数以添加参数 copy=False 您将拥有您想要的行为。 https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.html

编辑:另外,您想使用底层的 ndarray (而不是 pandas 系列)。

于 2017-10-09T20:07:46.320 回答