You can always tidyr::gather(), then tidyr::separate an finally tidyr::spread(). Gathering gives you the long version of a table where original colnames are now a variable, splitting splits the new column into two: original feature name and the variant .x or .y. Finally, you spread the table by the variant.
df_merged <- structure(list(blood_loss.x = c(0, 100, 150, NA, NA, NA), blood_loss.y = c(NA, NA, NA, 200, 10, 200), hospital_stay.x = c(1, 2, 3, NA, NA, NA ), hospital_stay.y = c(NA, NA, NA, 5, 8, 2)), row.names = c(NA, -6L), class = c("tbl_df", "tbl", "data.frame"))
df_merged <- df_merged %>%
tibble::rowid_to_column()
df_merged %>%
tidyr::gather("key", "value", -rowid) %>%
tidyr::separate("key", into = c("feature", "variant"), sep = "\\.") %>%
tidyr::spread(variant, value) %>%
mutate(final_value = coalesce(x, y))
Is it what You're looking for?