4

I have a matrix like below but with n rows

set.seed(123)
mt <- replicate(5, sample(1:3, 4, replace = TRUE))
mt
#>      [,1] [,2] [,3] [,4] [,5]
#> [1,]    3    3    3    1    3
#> [2,]    3    2    1    2    3
#> [3,]    3    2    2    3    1
#> [4,]    2    2    2    1    1

To sort it row by row, I am using this code with the order function, similar to here:

od2 <- order(mt[1, ], mt[2, ], mt[3, ], mt[4, ])
mt[, od2]
#>      [,1] [,2] [,3] [,4] [,5]
#> [1,]    1    3    3    3    3
#> [2,]    2    1    2    3    3
#> [3,]    3    2    2    1    3
#> [4,]    1    2    2    1    2

Is there any way to adapt this code to n rows? I was not successful with the second version of that answer given as a comment. I am not familiar with the do.call function.

Mikael Jagan
  • 9,012
  • 2
  • 17
  • 48
Wael
  • 1,640
  • 1
  • 9
  • 20

3 Answers3

3

Adapting this answer to sorting by rows with a bunch of transpose, you can do:

t(t(mt)[do.call(order, as.data.frame(t(mt))),])

#      [,1] [,2] [,3] [,4] [,5]
# [1,]    1    3    3    3    3
# [2,]    2    1    2    3    3
# [3,]    3    2    2    1    3
# [4,]    1    2    2    1    2

Or, simpler (as per @Ronak Shah's comment):

mt[, do.call(order, data.frame(t(mt)))]
Maël
  • 45,206
  • 3
  • 29
  • 67
  • 2
    indeed! i added it in my answer but feel free to add a separate answer if you feel like it – Maël Apr 05 '23 at 09:44
3

do.call needs a list as arguments to the function call. So you need to split the rows of the matrix into a list. The function asplit can do this by split an array or matrix by its margins.

mt[,do.call(order, asplit(mt, 1))]
#     [,1] [,2] [,3] [,4] [,5]
#[1,]    1    3    3    3    3
#[2,]    2    1    2    3    3
#[3,]    3    2    2    1    3
#[4,]    1    2    2    1    2

asplit is currently (4.2.3) doing this by selceting the slices and filling up the list an a for loop. Other options can be found at converting a matrix to a list or Convert a matrix to a list of column-vectors.


Comparing some possible methods with a larger matrix:

m <- matrix(sample(0:9, 1e6, TRUE), 1e3)

bench::mark(
asplit = m[,do.call(order, asplit(m, 1))],
data.frame = m[, do.call(order, data.frame(t(m)))],
splitRow = m[, do.call(order, split(m, row(m)))],
lapply = m[,do.call(order, lapply(1:nrow(m), function(i) m[i,]))],
splitNrow = m[, do.call(order, split(m, 1:nrow(m)))],
apply = m[, do.call(order, apply(m, 1, identity, simplify = FALSE))],
tapply = m[, do.call(order, tapply(m, row(m), identity))]
)

Result

  expression     min  median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time
  <bch:expr> <bch:t> <bch:t>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm>
1 asplit      8.44ms  9.02ms     111.    19.25MB     73.8    30    20      271ms
2 data.frame  6.88ms  7.24ms     138.    15.47MB     80.3    36    21    261.4ms
3 splitRow   19.79ms 19.98ms      48.9   31.01MB     73.3    10    15    204.6ms
4 lapply      6.79ms  6.99ms     141.    11.57MB     33.8    54    13    384.2ms
5 splitNrow   8.36ms  8.55ms     117.     7.76MB     16.3    50     7    428.8ms
6 apply       7.53ms  7.74ms     128.    15.38MB     50.6    43    17    335.9ms
7 tapply     27.64ms 28.47ms      35.4   38.73MB    165.      3    14     84.7ms

The methods data.frame and lapply are the fastest and splitNrow allocates the lowest amount of additional memory but asplit is not so far behind and is more general and works also for arrays and allows easy to change the margin.

GKi
  • 37,245
  • 2
  • 26
  • 48
2

An option with split

mt[, do.call(order, split(mt, row(mt)))]
      [,1] [,2] [,3] [,4] [,5]
[1,]    1    3    3    3    3
[2,]    2    1    2    3    3
[3,]    3    2    2    1    3
[4,]    1    2    2    1    2
akrun
  • 874,273
  • 37
  • 540
  • 662