I want to be able to replace values in a data frame by indexing by row and column, given a list of row indices, column names and values.
library(tidyverse)
cols <- sample(letters[1:10], 5)
vals <- sample.int(5)
rows <- sample.int(5)
df <- matrix(rep(0L, times = 50), ncol = 10) %>%
`colnames<-`(letters[1:10]) %>%
as_tibble
I can do this with a for loop over a list of the parameters:
items <- list(cols, vals, rows) %>%
pmap(~ list(..3, ..1, ..2))
for (i in items){
df[i[[1]], i[[2]]] <- i[[3]]
}
df
#> # A tibble: 5 x 10
#> a b c d e f g h i j
#> <int> <int> <int> <int> <int> <int> <int> <int> <int> <int>
#> 1 0 0 0 0 0 0 0 1 0 0
#> 2 0 0 5 0 0 0 0 0 0 0
#> 3 0 0 0 0 0 0 4 0 0 0
#> 4 0 0 0 0 0 0 0 0 3 0
#> 5 0 0 0 0 0 0 0 0 0 2
but I feel like there should be an easier or "tidier" way to make all of the assignments at once, especially if there are many more than 5 items. Suppose that we know we won't get the same cell overwritten or anything (index combinations won't be duplicated), so the cell being modified will not change depending on what cycle you are on. You could call this question "vectorising assignment".
This can be done with no loops at all, be them for
or *apply
loops.
The trick is to use an index matrix. But since this only works for target objects of class matrix
, coerce the tibble
or data.frame
to matrix
and then coerce back.
I will repeat the inclusion of the data creation code, with @Ronak's solution, to make the code self contained.
inx <- cbind(rows, match(cols, names(df1)))
df1 <- as.matrix(df1)
df1[inx] <- vals
df1 <- as.tibble(df1)
identical(df, df1)
#[1] TRUE
Data creation code.
set.seed(1234)
cols <- sample(letters[1:10], 5)
vals <- sample.int(5)
rows <- sample.int(5)
df <- matrix(rep(0L, times = 50), ncol = 10) %>%
`colnames<-`(letters[1:10]) %>%
as_tibble
df1 <- df
mapply(function(x, y, z) df[x, y] <<- z, rows, cols, vals)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With