Итеративно удалите столбцы и строки из матрицы s.t. среднее значение минимумов строки и столбца сведено к минимуму

У вас есть матрица i на j. Для целей этого примера возьмем следующую (очень маленькую) матрицу. Однако алгоритм должен быть быстрым и масштабируемым.

values <- c(2,5,3,6,7,
            9,5,4,9,9,
            1,5,4,8,1,
            3,1,5,6,2,
            2,9,4,7,4)
my.mat <- matrix(values, nrow = 5, byrow = TRUE)

Цель: Итеративно удалить строки или столбцы из my.mat таким образом, чтобы значение mean(c(apply(my.mat, 1, min), apply(my.mat, 2, min))) было минимизировано с учетом количество строк и столбцов удалить. Делайте это жадно (чтобы после удаления столбца или строки они никогда не возвращались в матрицу). Другими словами, просто удалите строки или столбцы с наибольшим минимальным значением. Применяются следующие предостережения.

Во-первых, если удаление строки или столбца изменяет минимальное значение столбца или строки (т. е. если они являются минимальными значениями друг друга), удалите пару (строка, столбец). Если строка или столбец соединены с несколькими столбцами или строками, последовательно удаляйте дополнительные столбцы или строки до тех пор, пока соотношение не станет равным 1:1, а затем одновременно удалите оставшуюся пару. Во-вторых, там, где есть связи, выберите случайным образом.

Вывод: вектор, указывающий порядок удаления в соответствии с этой целью. Он может ссылаться либо на имена строк/столбцов, либо на значения ячеек, если это подразумевает правильный порядок удаления.

Таким образом, для приведенной выше матрицы правильный ответ...

(Column 4), (Row 2), (Column 3), (Either Row 1 or Row 5), (Row 5 or Row 1), (Column 1 or Column 5), (Row 4 and Column 2), (Column 5 or Column 1 AND Row 3)

Однако фактическая реализация не должна быть неопределенной. Например, он должен случайным образом выбрать строку 5 или строку 1, а затем удалить оставшуюся строку на более позднем этапе, когда это необходимо.

Очень легко представить очень корявое решение проблемы. Однако трудно представить себе быстрое векторизованное решение.

Если бы не было связей, в которых столбцы и строки не были бы связаны друг с другом, и если бы не было экземпляров нескольких строк или столбцов, соединенных с одним столбцом или строкой, вы могли бы просто отсортировать уникальные минимумы строк и столбцов, а затем итеративно удалить строки и столбцы с минимальными значениями, равными i-му значению в отсортированных минимумах. Однако, когда есть связи, как в my.mat, это ломается, потому что это приведет к ненужному удалению строк и столбцов, которые не изменяют минимумы соответствующего столбца или строки. Например, если строка соединена с двумя столбцами, все они будут иметь одинаковые минимумы, поэтому этот грубый алгоритм удалит строку и оба столбца, когда правильный ответ состоит в том, чтобы удалить наугад один из столбцов, а затем удалить оба. остальные столбец и строка. Одним из возможных решений этой проблемы является дрожание значений таким образом, чтобы подразумевался правильный порядок, но по мере того, как матрица становится большой, становится трудно гарантировать, что дрожание не приведет к неправильному порядку.

РЕДАКТИРОВАНИЕ 1: объяснение примера

Эндрю Макдональд задал вопрос о примере, поэтому я объясню порядок.

Минимумы для каждой строки и столбца следующие, где Ci, Ri — i-ые столбцы, строки.

C4 R2 C3 R1 R5 R3 R4 C1 C2 C5 
 6  4  3  2  2  1  1  1  1  1 

Первые три шага просты. C4, R2 и C3 не являются минимумами для других строк или столбцов, а также нет ничьих. Итак, шаги 1-3...

Полная матрица:

   C1 C2 C3 C4 C5
R1  2  5  3  6  7
R2  9  5  4  9  9
R3  1  5  4  8  1
R4  3  1  5  6  2
R5  2  9  4  7  4

1) Удалите C4.

   C1 C2 C3 C5
R1  2  5  3  7
R2  9  5  4  9
R3  1  5  4  1
R4  3  1  5  2
R5  2  9  4  4

2) Удалить R2

   C1 C2 C3 C5
R1  2  5  3  7
R3  1  5  4  1
R4  3  1  5  2
R5  2  9  4  4

3) Удалить C3

   C1 C2 C5
R1  2  5  7
R3  1  5  1
R4  3  1  2
R5  2  9  4

Затем есть ничья между R1 и R5 (у обоих минимум 2). Они, очевидно, не связаны друг с другом и не являются минимумами для каких-либо столбцов, поэтому мы можем удалять их по одному, не изменяя минимумы какой-либо другой строки или столбца. Мы случайным образом выбираем между двумя, чтобы определить порядок.

4) Ряд 1 или Ряд 5 (я произвольно выберу ряд 1)

   C1 C2 C5
R3  1  5  1
R4  3  1  2
R5  2  9  4

5) Ряд 5 или Ряд 1 (в зависимости от того, что не было выбрано на шаге 4)

   C1 C2 C5
R3  1  5  1
R4  3  1  2

Остальные строки и столбцы связаны = 1. Вы не можете удалить R3, потому что тогда C1 или C5 станут хуже. Но вы можете удалить либо C1, либо C5 и не сделать R3 хуже. Точно так же вы не можете удалить R4 или C2, не ухудшив другой. Так что нам придется удалить R4 и C2 одновременно.

Последними несколькими шагами являются удаление одного из C1 или C5, затем оставшихся двух пар (R4 и C2, R3 и оставшихся либо C1 ИЛИ C5).

6) C1 или C5 (я выберу C5 произвольно)

   C1 C2
R3  1  5
R4  3  1

7) R4 и C2

   C1 
R3  1 

8) R3 и остальные C1 или C5

[]

ПРИМЕЧАНИЕ. Шаги 7 и 8 на самом деле взаимозаменяемы. Опять же, случайным образом выберите между ними.


person Henry David Thorough    schedule 10.07.2014    source источник
comment
Я не понимаю. три столбца (столбцы 1, 2 и 5) и две строки (3 и 4) имеют одинаковые минимумы, 1. Как вы определили порядок удаления? не должен ли предпоследний элемент в вашем ответе выше быть (Column 1 or Column 5 or Column 2)?   -  person AndrewMacDonald    schedule 10.07.2014
comment
Удаление столбца 2 приведет к ухудшению строки 4, поэтому мы должны удалить их как пару. См. объяснение, которое я добавил в ответ на ваш вопрос. Спасибо!   -  person Henry David Thorough    schedule 10.07.2014
comment
Что вы имеете в виду, говоря, что строки или столбцы становятся хуже?   -  person AndrewMacDonald    schedule 10.07.2014
comment
а, я понимаю - когда минимумы для столбца равны минимумам строки, они оба должны уйти вместе.   -  person AndrewMacDonald    schedule 10.07.2014
comment
Правильно, если нет другого столбца или строки, с которыми он имеет то же значение (например, строка 3).   -  person Henry David Thorough    schedule 10.07.2014
comment
Можно ли также поменять местами шаги 7 и 6? другими словами 6,7 и 8 взаимозаменяемы?   -  person AndrewMacDonald    schedule 15.07.2014
comment
Да, хорошая мысль. Говоря очень неточно, основное правило должно звучать примерно так: там, где есть совпадения, разбить их на наименьшие возможные подгруппы (в примере R4 + C2, C1 и C5 + R3) и удалить эти подгруппы в любом порядке.   -  person Henry David Thorough    schedule 16.07.2014
comment
Ну, я попробовал еще раз. Что вы думаете? Код также здесь, в Gist: gist.github.com/aammd/bfd62a49d5ab687db0d1   -  person AndrewMacDonald    schedule 16.07.2014


Ответы (1)


На самом деле нет необходимости делать что-либо итеративно, поскольку минимумы вектора не могут измениться, когда что-то удаляется. Поэтому мы можем свести эту проблему к рассмотрению только минимального значения строк и столбцов. Это уменьшит размер проблемы и сделает решение более быстрым и масштабируемым.

В этом ответе я использую dplyr и tidyr, два пакета для управления данными.

шаг 1: создание фрейма(ов) данных

Первый шаг — найти минимумы каждой строки и столбца и сохранить их в data.frame. Вероятно, есть более элегантные способы сделать это, но вот один из них:

library(dplyr)
library(tidyr)


colmins <- lapply(1:ncol(my.mat),function(s){col <- my.mat[,s,drop = FALSE]
                                             which(col == min(col), arr.ind = TRUE)}
)

cs_pos <- data.frame(name = rep(paste0("c",1:ncol(my.mat)),
                                times = sapply(colmins,nrow)),
                     do.call(rbind,colmins),
                     stringsAsFactors = FALSE)

rowmins <- lapply(1:nrow(my.mat),function(s){row <- my.mat[s,,drop = FALSE]
                                             which(row == min(row), arr.ind = TRUE)}
)

rs_pos <- data.frame(name = rep(paste0("r",1:nrow(my.mat)),
                                times = sapply(rowmins,nrow)),
                     do.call(rbind,rowmins),
                     stringsAsFactors = FALSE)

cs_val <- data.frame(type = "c", name = paste0("c",1:ncol(my.mat)),
                     val = apply(my.mat,2,min),
                     stringsAsFactors = FALSE)

rs_val <- data.frame(type = "r", name = paste0("r",1:ncol(my.mat)),
                     val = apply(my.mat,1,min),
                     stringsAsFactors = FALSE)


cs <- cs_pos %>%
  mutate(col = col + (extract_numeric(name)-1)) %>%
  left_join(cs_val)

rs <- rs_pos %>%
  mutate(row = row + (extract_numeric(name)-1)) %>%
  left_join(rs_val)

my.df <- rbind(cs,rs)

Результатом является data.frame с одной строкой для каждого «минимума» строки или столбца с дополнительными строками для связей.:

my.df
   name row col type val
1    c1   3   1    c   1
2    c2   4   2    c   1
3    c3   1   3    c   3
4    c4   1   4    c   6
5    c4   4   4    c   6
6    c5   3   5    c   1
7    r1   1   1    r   2
8    r2   2   3    r   4
9    r3   3   1    r   1
10   r3   3   5    r   1
11   r4   4   2    r   1
12   r5   5   1    r   2

Идентификация «групп» минимумов:

Эти повторяющиеся строки важны, потому что, когда они присутствуют, мы знаем, что строка или столбец либо а) имеют два минимума, которые равны друг другу, либо б) строка и столбец имеют одинаковые минимумы, либо в) оба.

Мы можем сделать небольшую функцию для удобства, чтобы найти эти пары значений:

findpairs <- function(var) xor(duplicated(var,incomparables = NA),
                           duplicated(var,fromLast = TRUE,incomparables = NA))

my.df.dup <- my.df %>%
  mutate(coord = paste(row,col,sep = ",")) %>%
  select(coord,name,type) %>%
  spread(type,name) %>%
  mutate(cdup = findpairs(c),
         rdup = findpairs(r)) %>%
  group_by(coord) %>%
  mutate(nval = sum(!is.na(c),!is.na(r)),
         dup = any(cdup,rdup)) %>%
  mutate(grp = ifelse(nval == 1 & !dup, 1, 0),
         grp = ifelse(nval == 1 & dup, 2, grp),
         grp = ifelse(nval == 2 & !dup, 3, grp),
         grp = ifelse(nval == 2 & dup, 4, grp)) %>%
  arrange(grp) %>%
  select(coord,c,r,grp) 

my.df.dup
  coord  c  r grp
1   1,1 NA r1   1
2   1,3 c3 NA   1
3   2,3 NA r2   1
4   5,1 NA r5   1
5   1,4 c4 NA   2
6   4,4 c4 NA   2
7   4,2 c2 r4   3
8   3,1 c1 r3   4
9   3,5 c5 r3   4

my.df.dup имеет по одной строке для каждой позиции в матрице с минимальным значением. два столбца, c и r, содержат имена столбцов и строк (соответственно), для которых значение в этой позиции является минимальным. Обратите внимание, что сейчас мы рассматриваем отношения между минимумами, а не их фактические значения.

Столбец grp удобен для определения минимумов, которые попадают в четыре категории в зависимости от того, являются ли они «общими» или нет:

## nval = 1, dup = FALSE : unique minima
## nval = 1, dup = TRUE  : duplicated minima, unshared
## nval = 2, dup = FALSE : a row-column pair
## nval = 2, dup = TRUE  : >=2 columns share minima with a row (or vice-versa)

Только минимумы в grp = 4 потребуют «разделения» в соответствии с шагами с 6 по 8 выше. Для простоты (и скорости) я отделяю их от основных данных, редактирую, а затем заменяю:

my.df.not4 <- my.df.dup %>%
  filter(grp != 4) %>%
  ungroup %>%
  filter(!(grp == 2 & duplicated(c)))

my.df.4 <- my.df.dup %>% 
  ungroup %>%
  filter(grp == 4) %>%
  group_by(c) %>%
  mutate(c_new = ifelse(sample(!duplicated(c)),c,NA)) %>%
  ungroup %>%
  group_by(r) %>%
  mutate(r_new = ifelse(sample(!duplicated(r)),r,NA)) %>%
  ungroup %>%
  select(coord, c = c_new, r = r_new)

Последний вызов mutate заменяет любые повторяющиеся значения на «NA»; это моя интерпретация шагов 6-8 выше. Я не уверен, как это будет работать, если минимумы распределяются иногда по столбцам, а иногда по строкам. YMMV.

два кадра данных: имена и минимумы

Наконец, мы преобразуем наши ответы выше в два кадра данных: один из минимальных «имен» (фактически удаляемых строк и столбцов) и один из фактических минимумов. Последний дает порядок удаления, первый - группы, которые следует удалить:

my.df.names <- rbind(my.df.not4,my.df.4) %>% 
  gather(type,name,c:r,na.rm = TRUE) %>%
  group_by(coord) %>%
  mutate(size = n(),
         name = ifelse(size == 2, paste(name,collapse = ","), name)) %>%
  select(coord,name) %>%
  ungroup

my.df.mins <- my.df %>%
  mutate(coord = paste(row,col,sep = ",")) %>%
  select(coord,val) %>%
  arrange(val %>% desc) %>%
  ungroup


my.df.names
   coord  name
1    1,3    c3
2    1,4    c4
3    4,2 c2,r4
4    3,1    c1
5    3,5 c5,r3
6    1,1    r1
7    2,3    r2
8    5,1    r5
9    4,2 c2,r4
10   3,5 c5,r3

my.df.mins
   coord val
1    1,4   6
2    4,4   6
3    2,3   4
4    1,3   3
5    1,1   2
6    5,1   2
7    3,1   1
8    4,2   1
9    3,5   1
10   3,1   1
11   3,5   1
12   4,2   1

Последний шаг прост: объединить два кадра данных, отсортировать по val и вернуть имена строк или столбцов, которые будут удалены. Если вы хотите разорвать связи случайным образом, вы можете просто использовать sample() в каждом уникальном значении val:

output <- left_join(data.frame(my.df.names),my.df.mins) %>%
  unique %>%
  arrange(desc(val)) %>%
  group_by(val) %>%
  mutate(namesamp = sample(name))

output$namesamp
"c4"    "r2"    "c3"    "r1"    "r5"    "c5,r3" "c1"    "c2,r4"
person AndrewMacDonald    schedule 10.07.2014
comment
Спасибо. Да, хотя это не решает проблему галстука, не так ли? Например, он хочет сделать r1 и r5 вместе, когда они должны быть разделены (см. редактирование исходного вопроса). - person Henry David Thorough; 10.07.2014
comment
Нет, хотя это легко исправить. сложная часть - определить ваш алгоритм трехсторонней связи - person AndrewMacDonald; 10.07.2014
comment
Отлично! Успешно справился! - person Henry David Thorough; 19.07.2014
comment
Рад слышать, что это было полезно! Я надеюсь, что он хорошо масштабируется для больших матриц. по крайней мере, это поможет вам начать! Меня очень интересует приложение здесь. Какой смысл знать эту последовательность удаления строк или столбцов? - person AndrewMacDonald; 19.07.2014