Pandas DataFrame concat/update ( "upsert" )?

Я ищу элегантный способ добавить все строки из одного DataFrame в другой DataFrame (оба DataFrames имеют одинаковый индекс и структуру столбцов), но в тех случаях, когда одно и то же значение индекса появляется в обоих DataFrames, используйте строку из второй кадр данных.

Итак, например, если я начинаю с:

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'

Я хотел бы получить результат:

                    A      B
    date
    '2015-10-01'  'A1'   'B1'
    '2015-10-02'  'a1'   'b1'
    '2015-10-03'  'a2'   'b2'
    '2015-10-04'  'a3'   'b3'

Это аналогично тому, что, как мне кажется, называется "upsert" в некоторых системах SQL - комбинация обновления и вставки в том смысле, что каждая строка из 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

который, кажется, работает, но это зависит от порядка строк в каждой группе по группе, всегда являющейся тем же самым, что и исходные DataFrames, которые я не проверял, и кажется неудовлетворительно запутанным.

Есть ли у кого-нибудь идеи для более простого решения?

Ответ 1

Одно из решений состоит в том, чтобы сопоставить 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

EDIT: По предложению @chrisb это можно упростить следующим образом:

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

Спасибо Крису!

Ответ 2

В дополнение к правильному ответу, обратите внимание на наличие столбцов, которые не существуют в обоих кадрах данных:

df1 = pd.DataFrame([['test',1, True], ['test2',2, True]])
df2 = pd.DataFrame([['test2',4], ['test3',3]])

If you just do it with the the solution from above you get:

>>>     1   2
0       
test    1   True
test2   4   NaN
test3   3   NaN

But what you expect is the following behavior:

>>>     1   2
0       
test    1   True
test2   4   True
test3   3   NaN

Just change the statement to:

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