Question

Create Vector from One-to-Many Dataframe in R

I have a dataframe with a one-to-many relationship where the one in the relationship are the column names and the many in the relationship are entries in the first column. I would like to create a vector or dataframe where the elements are selected from columns whose names match the entry in the first column. What follows is an example:

df <- data.frame(
  id = c('A','A','A','B','B','C'),
  A = c(1,2,3,4,5,6),
  B = c(7,8,9,10,11,12),
  C = c(13,14,15,16,17,18),
  D = c(19,20,21,22,23,24)
)

The resulting vector or data frame should have the following elements:

1
2
3
10
11
18

I would like a second vector or dataframe (ideally the first and second are joined) which are the neighboring values to the right. The final results should therefore be

1  7
2  8
3  9
10 16
11 17
18 24

I am hoping for a solution which doesn't involve looping. Thank you for your help.

 2  79  2
1 Jan 1970

Solution

 4

You can try

sapply(0:1, \(n, x=df[-1]) x[col(x) == match(df$id, names(x))+n])

or

t(mapply(\(i, j) t(df[i, j+0:1]), 1:nrow(df), match(df$id, names(df))))

Both gives

     [,1] [,2]
[1,]    1    7
[2,]    2    8
[3,]    3    9
[4,]   10   16
[5,]   11   17
[6,]   18   24
2024-07-23
Darren Tsai

Solution

 2

With base R, we can make use of indexing [].

unname(t(sapply(1:nrow(df), \(x) {
  ind <- which(df[x, 1] == colnames(df))
  unlist(df[x, ind:(ind+1)])
})))

     [,1] [,2]
[1,] 1    7   
[2,] 2    8   
[3,] 3    9   
[4,] 10   16  
[5,] 11   17  
[6,] 18   24  
2024-07-23
benson23

Solution

 2

here is another base R method. Should be efficient:

row_num <- seq_len(nrow(df))
col_num <- match(df$id, names(df)[-1])
matrix(df[-1][cbind(row_num, c(col_num, col_num + 1))], ncol = n)

     [,1] [,2]
[1,]    1    7
[2,]    2    8
[3,]    3    9
[4,]   10   16
[5,]   11   17
[6,]   18   24
2024-07-23
Onyambu

Solution

 2

This is not an elegant dplyr solution, but might be a fun one if you are interested

library(dplyr)

df %>%
    pivot_longer(-id) %>%
    filter((as.integer(factor(name)) - as.integer(factor(id))) %in% c(0, 1)) %>%
    pull(value) %>%
    matrix(2) %>%
    t()

which gives

     [,1] [,2]
[1,]    1    7
[2,]    2    8
[3,]    3    9
[4,]   10   16
[5,]   11   17
[6,]   18   24
2024-07-23
ThomasIsCoding