I spend sometime try to figure out how to mutate NA
values on multiple rows on row perspective in tibble
, the tibble
has 3 observations and 6 variables, generate below:
df <- data.frame(ID = c(1, 2, 3),
Score1 = c(90, 80, 70),
Score2 = c(66, 78, 86),
Score3 = c(NA, 86, 96),
Score4 = c(84, 76, 72),
Score5 = c(92, NA, 74))
sample_tibble <- as_tibble(df)
The tibble
looks as
# A tibble: 3 x 6
ID Score1 Score2 Score3 Score4 Score5
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 1 90 66 NA 84 92
2 2 80 78 86 76 NA
3 3 70 86 96 72 74
I have to use functions from tidyverse
(eg mutate
, mutate_at
, rowwise
.. etc.), the target is to replace the NA
on row 1 (in Score3
column) and row 2 (in Score5
column) with the mean
of row 1 and row 2 respectively ( mean
calculated with other values on row rather than NA
), so the ideal result should be after mutate
# A tibble: 3 x 6
ID Score1 Score2 Score3 Score4 Score5
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 1 90 66 83 84 92
2 2 80 78 86 76 80
3 3 70 86 96 72 74
The first NA
replace by mean(c(90, 66, NA, 84, 92), na.rm = TRUE)
as 83
The second NA
replace by mean(c(80, 78, 86, 76, NA), na.rm = TRUE)
as 80
Tried some code like below, and also check previous doc as Apply a function to every row of a matrix or a data frame or dplyr - using mutate() like rowmeans() , but the code never work since I am able to figure out body of mutate
function
sample_tibble[, -1] %>% rowwise() %>% mutate(...)
Not limited on rowwise
or mutate
(such as mutate_at
also good), is there any solution able to mutate row 1 and row 2 to reach the target format (Its great to mutate at same time , not as use for loop
to mutate twice), appreciate any solutions !
A slightly inefficient way would be to gather
and group_by
it:
sample_tibble %>%
tidyr::gather(k, v, -ID) %>%
group_by(ID) %>%
mutate(v = if_else(is.na(v), mean(v, na.rm = TRUE), v)) %>%
ungroup() %>%
tidyr::spread(k, v)
# # A tibble: 3 x 6
# ID Score1 Score2 Score3 Score4 Score5
# <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
# 1 1 90 66 83 84 92
# 2 2 80 78 86 76 80
# 3 3 70 86 96 72 74
As RonakShah also reminded me, gather
/ spread
can be replaced with the newer (and more featureful) cousins: pivot_longer
/ pivot_wider
.
Another technique uses apply
:
sample_tibble %>%
mutate(mu = apply(.[,-1], 1, mean, na.rm = TRUE)) %>%
### similarly, and faster, thanks RonakShah
# mutate(mu = rowMeans(.[,-1], na.rm = TRUE)) %>%
mutate_at(vars(starts_with("Score")), ~ if_else(is.na(.), mu, .)) %>%
select(-mu)
A caveat with this: the .[,-1]
is explicitly using every column except the first; if you have other columns that were not mentioned in the question, then this will certainly use more data than you intend. Unfortunately, I don't know of a way to use :
-ranging in this solution, as that would be clearer.
One approach utilizing a little bit of maths could be:
df %>%
mutate_at(vars(-1),
~ pmax(is.na(.)*rowMeans(select(df, -1), na.rm = TRUE),
(!is.na(.))*.,
na.rm = TRUE))
ID Score1 Score2 Score3 Score4 Score5
1 1 90 66 83 84 92
2 2 80 78 86 76 80
3 3 70 86 96 72 74
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.