This turned out to be faster than I expected (though still not as fast as the obvious approach that @akrun took), so I'm going to post this (like David) "just for general knowledge". (Plus, "data.table" all the things.) :-)
Create a data.table
with three columns:
- The unlisted values of your single row.
- A grouping variable to indicate which row the value should be assigned to in the final output.
- A grouping variable to indicate which column the value should be assigned to in the final output.
Once you have that, you can use dcast.data.table
to get the output you mention (plus a bonus column).
For point number 2 above, we can easily define a function like the following to make the process of creating groups easy:
groupMaker <- function(vecLen, perGroup) {
(0:(vecLen-1) %/% perGroup) + 1
}
Then we can use it as follows:
dcast.data.table(
data.table(value = unlist(df, use.names = FALSE),
row = groupMaker(ncol(df), 3),
col = 1:3),
row ~ col)
# row 1 2 3
# 1: 1 1 2 3
# 2: 2 4 5 6
# 3: 3 7 8 9
# 4: 4 10 11 12
Now, you mention that you are actually dealing with a single-row ~ 40K column data.frame
(I'll assume it to be 39,999 columns since that's nicely divisible by 3 and I don't want to break the other answers).
Keeping that in mind, here are some (useless) benchmarks (useless because we're talking milliseconds here, really).
set.seed(1)
S <- sample(20, 39999, TRUE)
S <- data.frame(t(S))
funAM <- function(indf) {
dcast.data.table(
data.table(value = unlist(indf, use.names = FALSE),
row = groupMaker(ncol(indf), 3),
col = 1:3),
row ~ col)
}
funDA <- function(indf) {
as.data.frame(t(`dim<-`(unlist(indf), c(3, ncol(indf)/3))))
}
funAK <- function(indf) as.data.frame(matrix(indf, ncol=3, byrow=TRUE))
library(microbenchmark)
microbenchmark(funAM(S), funDA(S), funAK(S))
# Unit: milliseconds
# expr min lq mean median uq max neval
# funAM(S) 18.487001 18.813297 22.105766 18.999891 19.455812 50.25876 100
# funDA(S) 37.187177 37.450893 40.393893 37.870683 38.869726 94.20128 100
# funAK(S) 5.018571 5.149758 5.929944 5.271679 5.536449 26.93281 100
Where this might be useful would be in cases where the number of desired columns and your number of input columns are not nicely divisible by each other.
For example, try the following sample data:
set.seed(1)
S2 <- sample(20, 40000, TRUE)
S2 <- data.frame(t(S))
With this sample data:
funAM
would give you a warning
but would correctly give you the last two columns of the last row as NA
.
funAK
would give you a warning
but would (presumably) incorrectly recycle values in the last row.
funDA
would just give you an error
.
I still think you should just fix the problem at the source though :-)