将一个 Pandas DataFrame 的副本合并到另一个 DataFrame 的每一行中?

6
我有一个场景,我想通过将另一个较小的表合并到数据框的每一行中来扩展数据框。
换句话说,如果大表有10行,小表有2行,则结果将是长度为20的表,其中原始表中的每一行都被复制,并合并了小表的新列。
为了实现这一点,我编写了一个小函数,它在每个表上添加一个共同的列,根据该列进行合并,然后删除该列。
def merge_expand(big, small):
    placeholder = "__placeholderstring__"
    big.insert(0, placeholder, 1)
    small.insert(0, placeholder, 1)
    merged = big.merge(small, how='left', on=placeholder)
    merged.drop(columns=placeholder, inplace=True)
    return merged

# example
big = pd.DataFrame({'a': [1,2,3], 'b': [4,5,6]})
small = pd.DataFrame({'id': ['aa','bb'], 'val':['a','b']})
merge_expand(big, small)

# output:
   a  b  id val
0  1  4  aa   a
1  1  4  bb   b
2  2  5  aa   a
3  2  5  bb   b
4  3  6  aa   a
5  3  6  bb   b

这样做可以完成任务,但我认为它有些笨拙,可能不是最有效的解决方案,因为它需要执行多个DataFrame操作。处理这个问题最有效的方法是什么?
3个回答

9

看起来你正在寻找全连接/笛卡尔积。如果我们将相同的key分配给所有观测值,可以使用pd.merge实现。

big.assign(key=1).merge(small.assign(key=1), how='outer', on='key')

输出

   a  b  key  id val
0  1  4    1  aa   a
1  1  4    1  bb   b
2  2  5    1  aa   a
3  2  5    1  bb   b
4  3  6    1  aa   a
5  3  6    1  bb   b

如果您已经有一个名为“key”的列,您实际上可以将其称为任何名称:
big['thiswontmatchanything'] = 1
small['thiswontmatchanything'] = 1

big.merge(small, how='outer', on='thiswontmatchanything').drop('thiswontmatchanything', axis=1)

输出

    a   b   id  val
0   1   4   aa  a
1   1   4   bb  b
2   2   5   aa  a
3   2   5   bb  b
4   3   6   aa  a
5   3   6   bb  b

谢谢,这是一个好的解决方案。我看到的一个问题是,如果“key”已经是一个列名,它将覆盖所有数据。我想知道是否有一种快速的方法来确保列“key”不存在。 - teepee
@teepee,你可以随便起名字,让我用另一个例子来更新。 - realr
对不起我想说的是,它并不总是很健壮,因为连接字符串可能已经在列中了。您可以通过使用晦涩难懂的列命名来将风险降至最低,但这仍然感觉像是一种hack。但是您的解决方案很棒,所以感谢您提供它。 - teepee
嗨,这对我应该有效,但在执行时出现了内存错误.. {MemoryError:无法为形状为(1960000000,)和数据类型int64的数组分配14.6 GiB}。还有其他方法吗?大df为1000000x2,小df约为1960x4。 - agr
嗨@agr,对于内存错误,有几种处理方法:(1)如果您实际上不需要int64数据类型范围,请将其更改为int32 / int16 / int8。(2)将文件拆分、合并并写入文件/流,而不是在内存中保存。理想的情况是尝试了解完整笛卡尔联接之后需要完成什么工作,并尝试按步骤执行它,或者在联接之前(如果可能的话)应用它。 - realr
1
我生成了一百万个132位数据的样本,但只有2000个实际错误向量(和其他数据)用于检查这些数据。目前我所做的是将错误向量拼接在一起,直到其大小为mil行,然后将其合并到样本 dataframe 中。我认为肯定有更好的方法来处理这个问题。谢谢。 - agr

6

我认为有一种更短的方法。 给定数据框df1和df2,您可以执行以下操作

df = df1.merge(df2, how='cross')

或者
df = df2.merge(df1, how='cross')

你可以实现一个简单的if-then-else语句来确定哪个数据框更小或更大。但这与合并操作无关。


2
这是目前最好的解决方案。它只在Pandas自1.2.0版本(2020年12月)以来才存在。 - Matthias Fripp

1
可能更不糟糕的方法是以下内容:
每个数据框通过另一个原始数据框的长度复制行,第一个数据框按“a”列排序,但您可以进行调整。然后,两个数据框沿着列轴(1)连接以实现所需的结果。
def merge_expand(*args):
    tmp_big = pd.concat([args[0]] * len(small), ignore_index=True).sort_values(by=['a']).reset_index(drop=True)
    tmp_small = pd.concat([args[1]] * len(big), ignore_index=True)
    return pd.concat([tmp_big, tmp_small], 1)

输入:

merge_expand(big, small)

输出:

   a  b  id val
0  1  4  aa   a
1  1  4  bb   b
2  2  5  aa   a
3  2  5  bb   b
4  3  6  aa   a
5  3  6  bb   b

编辑:如果您想传递一些参数,我们甚至可以使其更加通用:

def merge_expand(*args):
    if len(args) == 2:
        if len(args[0]) > len(args[1]):
            df_1 = pd.concat([args[0]] * len(args[1]), ignore_index=True).sort_values(by=[args[0].columns[0]]).reset_index(drop=True)
            df_2 = pd.concat([args[1]] * len(args[0]), ignore_index=True)
            return pd.concat([df_1, df_2], 1)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接