Подсчитайте первые n элементов на строку матрицы в желаемом диапазоне - PullRequest
1 голос
/ 23 октября 2019

Я хочу посчитать, сколько раз элемент в требуемом диапазоне появляется в каждой строке matrix с дополнительным условием, которое я хочу рассмотреть только для первых n таких элементов в строке.

Похожий вопрос, без добавленного условия, появляется здесь:

подсчет N вхождений в пределах верхнего предела строки матрицы

Iнаписал код R, чтобы делать то, что я хочу, но он использует вложенный for-loops. Я также заменил вложенные for-loops на sapply операторы, но они также кажутся неэффективными.

Я надеюсь, что кто-то может предложить более эффективный подход в идеале в base R. Ниже приведен пример набора данных, желаемый результат и функциональный аннотированный код R.

Вот пример набора данных. Мои фактические наборы данных будут намного больше, и у меня их будет огромное количество. Итак, эффективность важна.

my.data  <-  matrix( c(0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                      74, 22, 12, 13, 56,  0,  0,  0,  0,  0,
                      88, 77,  5, 77, 34, 98,  0,  0,  0,  0,
                      92,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                      89,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                      86, 72, 64, 40, 75, 58, 28, 66, 13, 98,
                      18,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                       0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                      70, 51, 83, 13, 50, 30,  0,  0,  0,  0,
                       0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                      28, 54, 43, 86, 50,  0,  0,  0,  0,  0,
                      45, 83,  0,  0,  0,  0,  0,  0,  0,  0,
                      39, 57, 58, 90, 84, 47, 36,  0,  0,  0,
                      76, 14, 71, 29,  0,  0,  0,  0,  0,  0,
                      23,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                       7,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                      77, 58, 90, 91, 47, 40, 58, 89,  0,  0,
                      89, 90,  0,  0,  0,  0,  0,  0,  0,  0,
                      83, 34, 61,  0,  0,  0,  0,  0,  0,  0,
                      17,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                      62,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                      10, 42,  5, 87, 61,  0,  0,  0,  0,  0,
                      90, 39, 99, 10, 84, 90, 93, 96, 69,  0,
                      84, 40, 44, 82,  0,  0,  0,  0,  0,  0,
                       0,  0,  0,  0,  0,  0,  0,  0,  0,  0),
nrow = 25, ncol = 10, byrow = TRUE)

Вот мои желаемые результаты. Я читаю каждую строку слева направо, и 0 игнорируются.

# These are the number of elements per row that satisfy all conditions
desired.n.kept      <- c(0, 2, 3, 0, 0, 3, 0, 0, 3, 0, 3, 2, 3, 2, 0, 0, 3, 0, 3, 0, 1, 2, 3, 3, 0)

# These are the number of elements per row that do not satisfy all conditions 
# up through the specified limit on number of elements that do satisfy all conditions
desired.n.discarded <- c(0, 3, 2, 1, 1, 1, 1, 0, 0, 0, 2, 0, 0, 2, 1, 1, 2, 2, 0, 1, 0, 3, 6, 0, 0)

Здесь я объясняю несколько примеров строк.

В первой строке нет элементов, которые удовлетворяют всем условиям. Другими словами, нет элементов, которые >= 30 & <= 85. Также нет элементов, которые не удовлетворяют всем условиям, < 30 | > 85, учитывая, что 0 игнорируются. Таким образом, < 30 | > 85 лучше всего рассматривать как: (> 0 & < 30) | > 85.

В третьем ряду три элемента находятся в желаемом диапазоне. Это второй, четвертый и пятый элементы, два 77 и 34, потому что они >= 30 & <= 85. Два элемента (88 и 5) находятся за пределами желаемого диапазона [(> 0 & < 30) | > 85] слева от третьего элемента, который находится в пределах желаемого диапазона, т. Е. Слева от пятого элемента, 34. Шестой элемент, 98, возникает после того, как достигнут предел в 3 сохраненных элемента, то есть после двух 77 и 34. Итак, шестой элемент, 98, игнорируется.

В шестом ряду три элемента удовлетворяют всем условиям: 72, 64 и 40. Эти три элемента являются первыми тремя, попадающими в желаемый диапазон: >= 30 & <= 85. Один элемент, 86, не удовлетворяет всем условиям (это > 85) вверх через третий элемент, который сохраняется, то есть, вверх через 40. Поскольку 40 является третьим элементом, попадающим в требуемый диапазон (>= 30 & <= 85), все шесть элементов справа от 40 игнорируются независимо от того, находятся ли они в пределах или за пределами требуемого диапазона (75,58, 28, 66, 13 и 98 игнорируются).

Вот мой исходный код с использованием вложенного for-loops:

# specify the desired range for individual elements
my.min   <- 30
my.max   <- 85

# specify maximum number of elements to keep within desired range per row
my.limit <- 3

my.cols  <- ncol(my.data)
my.rows  <- nrow(my.data)

# indicator matrix identifies elements inside the desired range
in.range <- matrix(0, nrow = my.rows, ncol = my.cols)
in.range[my.data >= my.min & my.data <= my.max] <- 1

# indicator matrix identifies elements outside the desired range
outside.range <- matrix(0, nrow = my.rows, ncol = my.cols)
outside.range[my.data > 0 & (my.data < my.min | my.data > my.max)] <- 1

# count elements that are within the desired range
count.in.range <- t(apply(in.range, 1, cumsum))

# truncate rows after my limit is reached
truncate.rows <- matrix(1, nrow = my.rows, ncol = my.cols)
for(i in 1:my.rows) {
     for(j in 2:my.cols) {
          if((count.in.range[i,(j-1)] >= my.limit) & (count.in.range[i,j] >= my.limit)) {truncate.rows[i,j] = 0}
     }
}

# count the number of elements per row that satisfy all conditions
n.kept <- rowSums(truncate.rows * in.range)
# count the number of elements per row that do not satisfy all conditions
n.discarded <- rowSums(truncate.rows * outside.range)

# verify that my code returns the desired results
all.equal(n.kept, desired.n.kept)
#[1] TRUE
all.equal(n.discarded, desired.n.discarded)
#[1] TRUE

Вот функция sapply, которую я написал вместо вложенной for-loops. Это работает, но вы можете видеть, что это кажется слишком сложным:

# This sapply approach returns a matrix with only 9 columns and many NULL elements
truncate.rows2 <- matrix(1, nrow = my.rows, ncol = my.cols)
truncate.rows2 <- t(sapply(1:my.rows, function (i) {
                       sapply(2:my.cols, function(j) {  
                            if((count.in.range[i,(j-1)] >= my.limit) & (count.in.range[i,j] >= my.limit)) {truncate.rows2[i,j] = 0}
                       })
                  }))
truncate.rows2

# modify truncate.rows2 to eliminate NULL elements and restore the first column
truncate.rows3 <- matrix(as.numeric(as.character(truncate.rows2)), ncol = (my.cols-1), nrow = my.rows)
truncate.rows3[is.na(truncate.rows3)] <- 1
truncate.rows3 <- cbind(truncate.rows[,1], truncate.rows3)
truncate.rows3

all.equal(truncate.rows, truncate.rows3)
#[1] TRUE

1 Ответ

0 голосов
/ 23 октября 2019

Я заменил вложенный цикл for следующим кодом:

# determine last element to keep by row
last.one <- apply(count.in.range, 1, function(x) min(which(x == 3), na.rm = TRUE))
last.one[is.infinite(last.one)] <- 10

# identity matrix of elements to keep
truncate.rows <- matrix(0, nrow = my.rows, ncol = my.cols)
sapply(1:my.rows, function(x) truncate.rows[x,1:last.one[x]] <<- 1)

Итак, полный функциональный код становится:

my.data  <-  matrix( c(0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                      74, 22, 12, 13, 56,  0,  0,  0,  0,  0,
                      88, 77,  5, 77, 34, 98,  0,  0,  0,  0,
                      92,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                      89,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                      86, 72, 64, 40, 75, 58, 28, 66, 13, 98,
                      18,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                       0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                      70, 51, 83, 13, 50, 30,  0,  0,  0,  0,
                       0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                      28, 54, 43, 86, 50,  0,  0,  0,  0,  0,
                      45, 83,  0,  0,  0,  0,  0,  0,  0,  0,
                      39, 57, 58, 90, 84, 47, 36,  0,  0,  0,
                      76, 14, 71, 29,  0,  0,  0,  0,  0,  0,
                      23,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                       7,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                      77, 58, 90, 91, 47, 40, 58, 89,  0,  0,
                      89, 90,  0,  0,  0,  0,  0,  0,  0,  0,
                      83, 34, 61,  0,  0,  0,  0,  0,  0,  0,
                      17,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                      62,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                      10, 42,  5, 87, 61,  0,  0,  0,  0,  0,
                      90, 39, 99, 10, 84, 90, 93, 96, 69,  0,
                      84, 40, 44, 82,  0,  0,  0,  0,  0,  0,
                       0,  0,  0,  0,  0,  0,  0,  0,  0,  0),
nrow = 25, ncol = 10, byrow = TRUE)

desired.n.kept      <- c(0, 2, 3, 0, 0, 3, 0, 0, 3, 0, 3, 2, 3, 2, 0, 0, 3, 0, 3, 0, 1, 2, 3, 3, 0)
desired.n.discarded <- c(0, 3, 2, 1, 1, 1, 1, 0, 0, 0, 2, 0, 0, 2, 1, 1, 2, 2, 0, 1, 0, 3, 6, 0, 0)

# specify desired range for individual elements
my.min   <- 30
my.max   <- 85

# specify maximum number of elements to keep within desired range per row
my.limit <- 3

my.cols  <- ncol(my.data)
my.rows  <- nrow(my.data)

# indicator matrix identifies elements inside the desired range
in.range <- matrix(0, nrow = my.rows, ncol = my.cols)
in.range[my.data >= my.min & my.data <= my.max] <- 1

# indicator matrix identifies elements outside the desired range
outside.range <- matrix(0, nrow = my.rows, ncol = my.cols)
outside.range[my.data > 0 & (my.data < my.min | my.data > my.max)] <- 1

# count elements that are within the desired range
count.in.range <- t(apply(in.range, 1, cumsum))

# determine last element to keep by row
last.one <- apply(count.in.range, 1, function(x) min(which(x == 3), na.rm = TRUE))
last.one[is.infinite(last.one)] <- 10

# identity matrix of elements to keep
truncate.rows <- matrix(0, nrow = my.rows, ncol = my.cols)
sapply(1:my.rows, function(x) truncate.rows[x,1:last.one[x]] <<- 1)

# count the number of elements per row that satisfy all conditions
n.kept <- rowSums(truncate.rows * in.range)
# count the number of elements per row that do not satisfy all conditions
n.discarded <- rowSums(truncate.rows * outside.range)

# verify that my code returns the desired results
all.equal(n.kept, desired.n.kept)
#[1] TRUE
all.equal(n.discarded, desired.n.discarded)
#[1] TRUE
...