pandas DataFrame concat/update("upsert")?

emb*_*pea 24 python pandas

我正在寻找一种优雅的方法将所有行从一个DataFrame附加到另一个DataFrame(两个DataFrame具有相同的索引和列结构),但是如果两个DataFrame中都出现相同的索引值,请使用第二个数据中的行帧.

所以,例如,如果我开始:

df1:
                    A      B
    date
    '2015-10-01'  'A1'   'B1'
    '2015-10-02'  'A2'   'B2'
    '2015-10-03'  'A3'   'B3'

df2:
    date            A      B
    '2015-10-02'  'a1'   'b1'
    '2015-10-03'  'a2'   'b2'
    '2015-10-04'  'a3'   'b3'
Run Code Online (Sandbox Code Playgroud)

我希望结果如下:

                    A      B
    date
    '2015-10-01'  'A1'   'B1'
    '2015-10-02'  'a1'   'b1'
    '2015-10-03'  'a2'   'b2'
    '2015-10-04'  'a3'   'b3'
Run Code Online (Sandbox Code Playgroud)

这类似于我认为在某些SQL系统中称为"upsert"的内容---更新和插入的组合,在某种意义上,每行来自df2(a)用于更新现有行,df1如果行键已经存在于df1,或(b)df1如果行密钥尚不存在则插入到末尾.

我想出了以下内容

pd.concat([df1, df2])     # concat the two DataFrames
    .reset_index()        # turn 'date' into a regular column
    .groupby('date')      # group rows by values in the 'date' column
    .tail(1)              # take the last row in each group
    .set_index('date')    # restore 'date' as the index
Run Code Online (Sandbox Code Playgroud)

这似乎工作,但这取决于每个groupby组中的行的顺序始终与原始DataFrames相同,我没有检查过,并且看起来令人不快复杂.

有没有人对更简单的解决方案有任何想法?

Ale*_*der 27

一种解决方案是df1与新行连接df2(即索引不匹配).然后用来自的那些更新值df2.

df = pd.concat([df1, df2[~df2.index.isin(df1.index)]])
df.update(df2)

>>> df
             A   B
2015-10-01  A1  B1
2015-10-02  a1  b1
2015-10-03  a2  b2
2015-10-04  a3  b3
Run Code Online (Sandbox Code Playgroud)

编辑: 根据@chrisb的建议,这可以进一步简化如下:

pd.concat([df1[~df1.index.isin(df2.index)], df2])
Run Code Online (Sandbox Code Playgroud)

谢谢克里斯!

  • 您可以通过以相反的顺序写入来避免更新; `pd.concat([df1 [~df1.index.isin(df2.index)],df2])` (8认同)
  • @embeepea井YMMV.但这实际上非常有效,涉及一组op(在索引上)和1 take(索引),以及一个副本(concat).例如.1MM行,在我的机器上需要150ms. (2认同)

bil*_*oie 12

从pandas 1.0.3开始,所需的功能直接由以下方式给出combine_first

combined = df2.combine_first(df1)

print(combined)
#               A   B
# 2015-10-01    A1  B1
# 2015-10-02    a1  b1
# 2015-10-03    a2  b2
# 2015-10-04    a3  b3
Run Code Online (Sandbox Code Playgroud)

为了获得这种行为,其数据具有优先级的数据帧(在本例中为更新数据帧df2)必须是调用该函数的数据帧。

它基本上:(1)协调行和列,(2)优先考虑非 NaN 数据,(3)如果两个数据帧中定义的数据点,则优先考虑 中的数据df2,这本质上就是您想要的。

编辑:我的理解是,它combine_first 确实满足了所请求的“如果存在则更新,如果不存在则插入”行为。然而,根据 Vijchti 在评论中的说法(谢谢),这并不严格对应于 SQL UPSERT 操作,因为逻辑是逐个值应用的,而不是整个行。我从我的答案中删除了对 UPSERT 的任何引用。

  • UPSERT 操作是逐行插入或替换。merge_first 操作是逐个值的。这些不是等效的操作。如果您使用 UPSERT,则新行将完全替换现有行。如果您使用combine_first,则新行的非空值将仅替换现有行的空值(并且所有现有非空值将保留在原处)。 (3认同)