Вы можете использовать слияние. Это может выглядеть довольно сложно, но по сути это очень похоже на ваше решение. Маска заменяет ваш первый l oop, а merge () заменяет второй l oop. Остальное просто убирает.
mask_no_mac_stb = df_be['mac_stb'].isnull()
df_merged = pd.merge(df_be[mask_no_mac_stb], df_sa, left_on='id', right_on='sa').set_index('id').drop('mac_stb_x', axis=1).rename({'mac_stb_y': 'mac_stb'}, axis=1)
df_be = df_be.set_index('id')
df_be.loc[df_merged.index, 'mac_stb'] = df_merged.loc[df_merged.index, 'mac_stb']
df_be = df_be.reset_index()
ОБНОВЛЕНИЕ:
Если вы не можете гарантировать, что все идентификаторы уникальны, вы можете использовать dict и применять:
mask_no_mac_stb = df_be['mac_stb'].isnull()
map_id2mac = {id: mac for (id, mac) in zip(df_sa['sa'].to_list(), df_sa['mac_stb'].to_list())}
df_be.loc[mask_no_mac_stb, 'mac_stb'] = df_be.loc[mask_no_mac_stb, 'id'].apply(lambda x: map_id2mac.get(x, np.nan))
Вывод для предоставленных необработанных данных (одинаковых для обоих способов):
id timestamp time_ms id_type mac_stb
0 641269DD04B1 17:16:06.304 18.00 boxId 641269DD04B1
1 98F7D7198F88 17:16:06.291 9.00 boxId 98F7D7198F88
2 A0C5624B2D79 17:16:06.291 6.00 boxId A0C5624B2D79
3 7085C6AAB849 17:16:06.288 18.00 device 7085C6AAB849
4 S828093664 17:16:06.304 18.00 account NaN
5 707630BC92E7 17:16:06.319 4.00 boxId 707630BC92E7
6 S827336056 17:16:06.319 8.00 account 001E690D2C83
7 707630BC8FA8 17:16:06.320 9.00 device 707630BC8FA8
8 S831286437 17:16:06.340 9.00 account 001E690D2D8F
9 S841512815 17:16:06.335 13.00 account 001E690D311D
Примечание. Значение для идентификатора 7085C6AAB849 по-прежнему равно NaN, поскольку оно отсутствует в df_sa