4

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

0

2 Answers 2

6

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)
Sign up to request clarification or add additional context in comments.

2 Comments

no need to convert df1 to a matrix imo; df1[inx] <- vals works on a dataframe as well
@Jaap You are right, I had tested it with the tibble. In the data creation, change as_tibble to as.data.frame and the matrix index works.
6

One way using mapply would be:

mapply(function(x, y, z) df[x, y] <<- z, rows, cols, vals)
df

#      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     5     0     0     0     0     0
#2     0     0     0     0     0     0     0     2     0     0
#3     0     0     0     0     0     1     0     0     0     0
#4     0     4     0     0     0     0     0     0     0     0
#5     0     0     0     0     0     0     0     0     3     0

You can read more about <<- operator here.

data

set.seed(1234)
cols <- sample(letters[1:10], 5)
vals <- sample.int(5)
rows <- sample.int(5)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.