0

I want to make a random matrix with n rows and m columns. I want the elements of each row of the matrix to be randomly chosen from a set of numbers. I want to have at least one different number in each row. I don't want all the elements of a row to be the same. I once asked my question here, but I don't know why the function I was provided still gives me some rows with all the same elements. How can I adjust this function?

f2 <- function(x, n, m) {
if ( length(unique(x)) == 1 ) {
    stop('x has only one unique element.', call. = FALSE)
}
result <- t(replicate(n, sample(x, m, replace = TRUE)))
while ( any(apply(result, 1, function(x) length(unique(result)) == 1)) ) {
    result <- t(replicate(n, sample(x, m, replace = TRUE)))
}
return(result)}

Here is an example:

x <- c(1, 1.5, 2, 3,4)
set.seed(123456)
matall=f2(x, 1200, 4)
View(matall)

      [,1] [,2] [,3] [,4]
   [1,]  3.0  3.0  1.5  1.5
   [2,]  1.5  1.0  2.0  1.0
   [3,]  4.0  1.0  3.0  2.0
   [4,]  4.0  4.0  4.0  4.0
Fate
  • 43
  • 6
  • Please look at row number 4 in the example. All elements are 4. I don't want this to happen. I want at least one element to be different in a row. – Fate Dec 31 '17 at 10:52

2 Answers2

1

I want to make a random matrix with n rows and m columns.

cols <- 3; rows <- 3
m <- matrix(ncol = cols, nrow = rows)

I want the elements of each row of the matrix to be randomly chosen from a set of numbers.

set.seed(2)
set <- seq(ncol(m)-1L)
m[] <- sample(set, length(m), replace = T)
m
#      [,1] [,2] [,3]
# [1,]    1    1    1
# [2,]    2    2    2
# [3,]    2    2    1

I want to have at least one different number in each row.

rowRanges <- matrixStats::rowRanges(m)
(isSingle <- rowRanges[,2]-rowRanges[,1]==0)
# [1]  TRUE  TRUE FALSE

m[isSingle,1] <- vapply(rowRanges[isSingle, 1], function(x) set[set!=x][1], 0L)
m
#      [,1] [,2] [,3]
# [1,]    2    1    1
# [2,]    1    2    2
# [3,]    2    2    1

or, if you want to randomize the assignment in terms of which set value and which column to choose:

vsample <- Vectorize(function(x) sample(set[set!=x], size = 1L), "x")
idx <- cbind(row=which(isSingle), col=sample(ncol(m), sum(isSingle), replace = TRUE))
mvals <- vsample(rowRanges[isSingle, 1])
m[idx] <- mvals
lukeA
  • 53,097
  • 5
  • 97
  • 100
  • I changed set to set=c(1,2,3,4) and got this error later: "Error in vapply(rowRanges[isSingle, 1], function(x) set[set != x], 0L) : values must be length 1, but FUN(X[[1]]) result is length 3" – Fate Dec 31 '17 at 11:11
  • @Fate My bad, make it e.g. `set[set!=x][1]` to make sure `vapply` takes only 1 value (in this case the 1st one which is not the one in the row). – lukeA Dec 31 '17 at 11:45
  • Thank you very much. It works. I can't like your answer due to policies of this site – Fate Jan 02 '18 at 09:01
1

There is a typo in the function definition. The while clause should read

while ( any(apply(result, 1, function(x) length(unique(x)) == 1)) ) {

instead of

while ( any(apply(result, 1, function(x) length(unique(result)) == 1)) ) {

However, the function does not terminate quickly as it tries to create the whole matrix anew each time a row with identical values is found.

The improved version only replaces rows with identical values

f3 <- function(x, n, m) {
  if ( length(unique(x)) == 1 ) {
    stop('x has only one unique element.', call. = FALSE)
  }
  result <- replicate(m, sample(x, n, replace = TRUE))
  uni_rows <- apply(result, 1, function(x) length(unique(x)) == 1)
  while ( any(uni_rows) ) {
    result[which(uni_rows), ] <- replicate(m, sample(x, sum(uni_rows), replace = TRUE))
    uni_rows <- apply(result, 1, function(x) length(unique(x)) == 1)
  }
  return(result)
}

Now,

x <- c(1, 1.5, 2, 3, 4)
set.seed(123456)
matall <- f3(x, 1200, 4)
any(apply(matall, 1, function(x) length(unique(x)) == 1))
[1] FALSE

returns

head(matall, 11)
      [,1] [,2] [,3] [,4]
 [1,]  3.0    3  1.5  1.5
 [2,]  1.5    1  2.0  1.0
 [3,]  4.0    1  3.0  2.0
 [4,]  2.0    4  3.0  1.0
 [5,]  4.0    1  1.5  3.0
 [6,]  4.0    3  2.0  3.0
 [7,]  2.0    3  4.0  4.0
 [8,]  4.0    1  2.0  4.0
 [9,]  1.5    1  4.0  1.0
[10,]  4.0    4  3.0  3.0
[11,]  1.5    3  4.0  1.5
Uwe
  • 41,420
  • 11
  • 90
  • 134
  • Thank you very much. It works. I can't like your answer due to policies of this site. – Fate Jan 02 '18 at 09:01
  • @Fate You need a reputation of at least 15 to upvote. But you can accept one of the answers which you find the most useful to solve you problem by clicking on the check mark symbol of the selected answer. Accepting will bring you another +2 reputation points. – Uwe Jan 02 '18 at 09:17