计算 pandas 中的完整外部联接的大小

Calculating the size of a full outer join in pandas

tl;博士

我的问题是,当使用 Pandas DataFrames 作为组合图的一部分时,我一直在计算完整外部合并的每个部分预计有多少行。

问题(在下面重复)。

  1. 理想的解决方案是不需要合并并查询 panel 个对象。鉴于 panel 上没有查询方法,是否有更简洁的解决方案可以在不达到内存上限的情况下解决此问题?
  2. 如果对 2 的回答是否定的,我如何在不执行合并 的情况下为集合 的每个组合计算所需合并 table 的大小?这可能是一种次优方法,但在这种情况下,为了应用程序的目的,它会被接受table。
  3. Python 是正确的语言吗?或者我应该寻找一种更具统计性的语言,例如 R 还是在较低级别编写它(ccython) - 数据库是不可能的。

问题

最近我重新编写了 py-upset graphing library 以使其在计算跨 DataFrame 的组合时在时间方面更有效率。我不是要审查这段代码,它在大多数情况下都运行良好,我对这种方法很满意。我现在正在寻找的是一个非常具体的问题的答案;在处理大型数据集时发现。

我在重写时采用的方法是在完整的外部连接上对所有提供的数据帧进行内存中合并,如第 480 - 502 of pyupset.resources

行所示
        for index, key in enumerate(keys):
            frame = self._frames[key]
            frame.columns = [
                '{0}_{1}'.format(column, key)
                if column not in self._unique_keys
                else
                column
                for column in self._frames[key].columns
            ]
            if index == 0:
                self._merge = frame
            else:
                suffixes = (
                    '_{0}'.format(keys[index-1]),
                    '_{0}'.format(keys[index]),
                )
                self._merge = self._merge.merge(
                    frame,
                    on=self._unique_keys,
                    how='outer',
                    copy=False,
                    suffixes=suffixes
                )

对于中小型数据帧,使用连接的效果非常好。事实上,最近的性能测试表明,它可以在不到一分钟的时间内处理 5 或 6 个数据集,每个数据集包含 10,000 行,这对于我需要的应用程序结构来说绰绰有余。

问题现在从基于时间转移到基于内存。

给定的数据集可能有数千条记录,即使在大型服务器上,库也会很快耗尽内存。

从正确的角度来看,我的这个应用程序的测试机器是一个 8 核 VMWare 盒子,128GiB RAM 运行 Centos7。

给定以下数据集大小,当添加第 5 个数据帧时,内存使用量呈指数级增长。这在意料之中,但强调了我面临的问题的核心。

  Rows | Dataframe
------------------------
 13963 | dataframe_one
 48346 | dataframe_two
 52356 | dataframe_three
337292 | dataframe_four
 49936 | dataframe_five
 24542 | dataframe_six
258093 | dataframe_seven
 16337 | dataframe_eight

就行数而言,这些不是“small”数据帧,尽管每个数据帧的列数限制为一个唯一键 + 4 个非唯一列。 pandas中每列的大小为

column | type     | unique
--------------------------
X      | object   | Y
id     | int64    | N
A      | float64  | N
B      | float64  | N
C      | float64  | N

由于内存被耗尽,此合并可能会导致问题。有时它会因内存错误而中止(很好,我可以捕获并处理这些错误),其他时候内核会接管并在系统变为 unstable 之前简单地杀死应用程序,有时,系统只是挂起并变得无响应/ unstable 直到内核最终终止应用程序并释放内存。

示例输出(内存大小近似):

[INFO] Creating merge table
[INFO] Merging table dataframe_one
[INFO] Data index length = 13963     # approx memory <500MiB
[INFO] Merging table dataframe_two
[INFO] Data index length = 98165     # approx memory <1.8GiB
[INFO] Merging table dataframe_three
[INFO] Data index length = 1296665   # approx memory <3.0GiB
[INFO] Merging table dataframe_four
[INFO] Data index length = 244776542 # approx memory ~13GiB
[INFO] Merging table dataframe_five
Killed # > 128GiB

当合并table产生后,在集合组合中查询,产生类似于https://github.com/mproffitt/py-upset/blob/feature/ISSUE-7-Severe-Performance-Degradation/tests/generated/extra_additional_pickle.png

的图

我试图构建的解决内存问题的方法是查看为合并提供的集合,预先确定合并需要多少内存,然后如果该组合需要太多,则将其拆分为较小的组合,分别计算每个组合,然后将最终数据框放回原处(分而治之)。

我的问题是我一直在计算合并的每个部分预计有多少行。

问题(从上面重复)

  1. 理想的解决方案是不需要合并并查询 panel 个对象。鉴于 panel 上没有查询方法,是否有更简洁的解决方案可以在不达到内存上限的情况下解决此问题?
  2. 如果对 2 的回答是否定的,我如何在不执行合并 的情况下为集合 的每个组合计算所需合并 table 的大小?这可能是一种次优方法,但在这种情况下,为了应用程序的目的,它会被接受table。
  3. Python 是正确的语言吗?或者我应该寻找一种更具统计性的语言,例如 R 还是在较低级别编写它(ccython).

很抱歉这个冗长的问题。如果需要或可能的话,我很乐意提供更多信息。

任何人都可以解释一下这可能是什么原因吗?

谢谢。

问题一

Dask 通过使用 hdf5 文件作为临时存储来计算合并 table "out of memory" 显示了很多希望。

通过使用多处理创建合并,dask 还提供了比 pandas 更高的性能。不幸的是,这并没有传递到 query 方法,因此在合并时获得的性能提升在查询时会丢失。

它仍然不是一个完全可行的解决方案,因为 dask 在大型、复杂的合并中可能仍然 运行 内存不足。

问题二

使用以下方法完全可以预先计算合并的大小。

  1. 按唯一键对每个数据帧进行分组并计算大小。
  2. 为每个数据框创建一组键名。
  3. 从 2 创建交集。
  4. 为第 1 组和第 2 组创建一个组差
  5. 为了容纳 np.nan 存储在唯一键中的 select 所有 NAN 值。如果一帧包含nan而另一帧不包含,则将另一个写为1。
  6. 对于交集中的集合,将每个 groupby('...').size()
  7. 的计数相乘
  8. 从集合差异中添加计数
  9. 添加 np.nan 个值

在python中可以写成:

def merge_size(left_frame, right_frame, group_by):
    left_groups = left_frame.groupby(group_by).size()
    right_groups = right_frame.groupby(group_by).size()
    left_keys = set(left_groups.index)
    right_keys = set(right_groups.index)
    intersection = right_keys & left_keys
    left_sub_right = left_keys - intersection
    right_sub_left = right_keys - intersection

    left_nan = len(left_frame.query('{0} != {0}'.format(group_by)))
    right_nan = len(right_frame.query('{0} != {0}'.format(group_by)))
    left_nan = 1 if left_nan == 0 and right_nan != 0 else left_nan
    right_nan = 1 if right_nan == 0 and left_nan != 0 else right_nan

    sizes = [(left_groups[group_name] * right_groups[group_name]) for group_name in intersection]
    sizes += [left_groups[group_name] for group_name in left_sub_right]
    sizes += [right_groups[group_name] for group_name in right_sub_left]
    sizes += [left_nan * right_nan]
    return sum(sizes) 

问题 3

此方法计算量较大,最好写成Cython以提高性能。