Add a secondary ID and use reshape
:
Here's what our second ID should look like (actually, this is generally known as your "time" variable)
with(df, ave(rep(1, nrow(df)), ID, FUN = seq_along))
# [1] 1 2 3 4 1 1 2 1 2 3
With that, base R's underappreciated reshape
takes care of this with ease:
df$ID2 <- with(df, ave(rep(1, nrow(df)), ID, FUN = seq_along))
reshape(df, direction = "wide", idvar="ID", timevar="ID2")
# ID var1.1 var2.1 var1.2 var2.2 var1.3 var2.3 var1.4 var2.4
# 1 a 10 5 20 5 10 4 40 8
# 5 b 30 9 NA NA NA NA NA NA
# 6 c 20 2 20 4 NA NA NA NA
# 8 d 10 7 10 1 40 3 NA NA
Alternatively, with "reshape2":
library(reshape2)
df$ID2 <- with(df, ave(rep(1, nrow(df)), ID, FUN = seq_along))
dfL <- melt(df, id.vars=c("ID", "ID2"))
dcast(dfL, ID ~ variable + ID2, value.var="value")
# ID var1_1 var1_2 var1_3 var1_4 var2_1 var2_2 var2_3 var2_4
# 1 a 10 20 10 40 5 5 4 8
# 2 b 30 NA NA NA 9 NA NA NA
# 3 c 20 20 NA NA 2 4 NA NA
# 4 d 10 10 40 NA 7 1 3 NA