Условно вставьте столбцы одного фрейма данных Pandas в столбцы другого фрейма данных

#python #pandas

Вопрос:

У меня есть 2 кадра данных:

 dfA = pd.DataFrame({'label':[1,5,2,4,2,3],
                    'group':['A']*3   ['B']*3,
                    'x':[np.nan]*3   [1,2,3],
                    'y':[np.nan]*3   [1,2,3]})

dfB = pd.DataFrame({'uniqid':[1,2,3,4,5,6,7],
                    'horizontal':[34,41,23,34,23,43,22],
                    'vertical':[98,67,19,57,68,88,77]})
 

…которые выглядят как:

    label group    x    y
0      1     A  NaN  NaN
1      5     A  NaN  NaN
2      2     A  NaN  NaN
3      4     B  1.0  1.0
4      2     B  2.0  2.0
5      3     B  3.0  3.0


   uniqid  horizontal  vertical
0       1          34        98
1       2          41        67
2       3          23        19
3       4          34        57
4       5          23        68
5       6          43        88
6       7          22        77
 

В основном dfB содержит «горизонтальные» и «вертикальные» значения для всех уникальных идентификаторов. Я хочу заполнить столбцы » x » и » y «в dfA значениями» по горизонтали » и «по вертикали» в dfB, но только для группы A; данные для группы B должны оставаться неизменными.

Желаемый результат был бы:

    label group    x    y
0      1     A 34.0 98.0
1      5     A 23.0 68.0
2      2     A 41.0 67.0
3      4     B  1.0  1.0
4      2     B  2.0  2.0
5      3     B  3.0  3.0
 

Я использовал .merge (), чтобы добавить дополнительные столбцы в фрейм данных для обеих групп A и B, а затем скопировать данные в столбцы x и y только для группы A. И, наконец, удалите столбцы из dfB.

 dfA = dfA.merge(dfB, how = 'left', left_on = 'label', right_on = 'uniqid')

dfA.loc[dfA['group'] == 'A','x'] = dfA.loc[dfA['group'] == 'A','horizontal'] 
dfA.loc[dfA['group'] == 'A','y'] = dfA.loc[dfA['group'] == 'A','vertical'] 

dfA = dfA[['label','group','x','y']]
 

Будет получен правильный результат:

    label group     x     y
0      1     A  34.0  98.0
1      5     A  23.0  68.0
2      2     A  41.0  67.0
3      4     B   1.0   1.0
4      2     B   2.0   2.0
5      3     B   3.0   3.0
 

…но это действительно, действительно уродливое решение. Есть ли лучшее решение?

Ответ №1:

combine_first

 dfA.set_index(['label', 'group']).combine_first(
    dfB.set_axis(['label', 'x', 'y'], axis=1).set_index(['label'])
).reset_index()

   label group     x     y
0      1     A  34.0  98.0
1      5     A  23.0  68.0
2      2     A  41.0  67.0
3      4     B   1.0   1.0
4      2     B   2.0   2.0
5      3     B   3.0   3.0
 

fillna

Работает так же хорошо

 dfA.set_index(['label', 'group']).fillna(
    dfB.set_axis(['label', 'x', 'y'], axis=1).set_index(['label'])
).reset_index()
 

Комментарии:

1. Я раньше не сталкивался с методом .combine_first (), и поэтому это был очень полезный ответ. Спасибо за публикацию. Однако написанное решение не ограничивает обновление строк только строками группы A; если строка в группе B содержит NaN, то эта строка также будет обновлена.

Ответ №2:

Мы можем попытаться loc извлечь/обновить только ту часть, которую мы хотим. И поскольку вы объединяетесь в одном столбце, который также имеет уникальное значение dfB , вы можете использовать set_index и loc/reindex :

 mask = dfA['group']=='A'
dfA.loc[ mask, ['x','y']] = (dfB.set_index('uniqid')
                                .loc[dfA.loc[mask,'label'],
                                     ['horizontal','vertical']]
                                .values
                            )
 

Выход:

    label group     x     y
0      1     A  34.0  98.0
1      5     A  23.0  68.0
2      2     A  41.0  67.0
3      4     B   1.0   1.0
4      2     B   2.0   2.0
5      3     B   3.0   3.0
 

Обратите внимание, что вышеизложенное не сработает, если некоторые из dfA.label них не dfB.uniqueid будут включены . В этом случае нам нужно использовать reindex :

 (dfB.set_index('uniqid')
    .reindex[dfA.loc[mask,'label']
    [['horizontal','vertical']].values
)
 

Комментарии:

1. Этот метод действительно хорошо сработал для меня — мне потребовалось некоторое время, чтобы понять, как его использовать . loc с 2 различными кадрами данных будет работать, но я думаю, что это гениальное решение.