Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I avoid a loop when replacing values in a dataframe by row-column indexing?

Tags:

r

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".

like image 261
Calum You Avatar asked Dec 13 '22 15:12

Calum You


1 Answers

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)
like image 106
Rui Barradas Avatar answered Dec 18 '22 00:12

Rui Barradas