2016-04-06 54 views
12

Mam macierz, w której każdy wiersz ma co najmniej jedną komórkę NA, a każda kolumna ma również co najmniej jedną komórkę NA. Potrzebuję znaleźć największy podzbiór tej macierzy, który nie zawiera żadnych NA.Podział non-NA

Na przykład dla tej matrycy A

A <- 
    structure(c(NA, NA, NA, NA, 2L, NA, 
       1L, 1L, 1L, 0L, NA, NA, 
       1L, 8L, NA, 1L, 1L, NA, 
       NA, 1L, 1L, 6L, 1L, 3L, 
       NA, 1L, 5L, 1L, 1L, NA), 
      .Dim = c(6L, 5L), 
      .Dimnames = 
       list(paste0("R", 1:6), 
        paste0("C", 1:5))) 

A 
    C1 C2 C3 C4 C5 
R1 NA 1 1 NA NA 
R2 NA 1 8 1 1 
R3 NA 1 NA 1 5 
R4 NA 0 1 6 1 
R5 2 NA 1 1 1 
R6 NA NA NA 3 NA 

Istnieją dwa rozwiązania (8-komorowy): A[c(2, 4), 2:5] i A[2:5, 4:5], choć znalezienie tylko jedno rozwiązanie jest na tyle ważny dla moich celów. Wymiary mojej rzeczywistej macierzy to 77x132.

Będąc noobem, nie widzę żadnego oczywistego sposobu, aby to zrobić. Czy ktokolwiek mógłby mi pomóc z niektórymi pomysłami?

+2

nie jest 'A [C (2,4,5), 3: 5] 'najlepszym rozwiązaniem z 9 komórek? – bgoldst

+3

Z matrycą 77x132 rozważasz około 2^(77 + 132) ~ 8.2E62 możliwych podmatryc. Ciekawi mnie, jak można to rozwiązać ... – Frank

+2

@bgoldst lub o to chodzi 'A [2: 4, c (2, 4, 5)]' – MichaelChirico

Odpowiedz

7

1) Optim W tym podejściu możemy odpocząć problem do ciągłego problemu optymalizacji, które rozwiązujemy z optim.

Funkcja celu to f, a wprowadzeniem do niej jest wektor 0-1, którego pierwsze wpisy nrow(A) odpowiadają wierszom, a pozostałe pozycje odpowiadają kolumnom. f używa macierzy Ainf, która wywodzi się z A, zastępując węzły o dużej liczbie ujemnej i nie-NA o wartości 1. Pod względem Ainf ujemna liczba elementów w prostokącie wierszy i kolumn odpowiadająca x to -x[seq(6)] %*% Ainf %*$ x[-seq(6)] które minimalizujemy w zależności od każdego komponentu x, leżącego pomiędzy 0 a 1.

Chociaż jest to złagodzenie pierwotnego problemu do ciągłej optymalizacji, wydaje się, że otrzymujemy rozwiązanie integer, w razie potrzeby.

Właściwie większość kodu poniżej służy jedynie do uzyskania wartości początkowej. W tym celu najpierw stosujemy numerację. To przestawia wiersze i kolumny dając bardziej blokową strukturę, a następnie w permutowanej matrycy znajdujemy największą kwadratową podmacie.

W przypadku konkretnego pytania A w pytaniu największa prostokątna podmatryca ma być kwadratowa, a wartości początkowe są już wystarczająco dobre, że dają optimum, ale my wykonamy optymalizację tak czy inaczej, więc ogólnie działa. Jeśli chcesz, możesz grać z różnymi wartościami początkowymi. Na przykład zmień k z 1 na pewną wyższą liczbę w largestSquare, w którym to przypadku largestSquare zwróci kolumny k, podając k wartości początkowe, które mogą być użyte w k przebiegach optim biorąc najlepsze.

Jeśli wartości początkowe są wystarczająco dobre, powinno to dać optimum.

library(seriation) # only used for starting values 

A.na <- is.na(A) + 0 

Ainf <- ifelse(A.na, -prod(dim(A)), 1) # used by f 
nr <- nrow(A) # used by f 
f <- function(x) - c(x[seq(nr)] %*% Ainf %*% x[-seq(nr)]) 

# starting values 

# Input is a square matrix of zeros and ones. 
# Output is a matrix with k columns such that first column defines the 
# largest square submatrix of ones, second defines next largest and so on. 
# Based on algorithm given here: 
# http://www.geeksforgeeks.org/maximum-size-sub-matrix-with-all-1s-in-a-binary-matrix/ 
largestSquare <- function(M, k = 1) { 
    nr <- nrow(M); nc <- ncol(M) 
    S <- 0*M; S[1, ] <- M[1, ]; S[, 1] <- M[, 1] 
    for(i in 2:nr) 
    for(j in 2:nc) 
     if (M[i, j] == 1) S[i, j] = min(S[i, j-1], S[i-1, j], S[i-1, j-1]) + 1 
    o <- head(order(-S), k) 
    d <- data.frame(row = row(M)[o], col = col(M)[o], mx = S[o]) 
    apply(d, 1, function(x) { 
    dn <- dimnames(M[x[1] - 1:x[3] + 1, x[2] - 1:x[3] + 1]) 
    out <- c(rownames(M) %in% dn[[1]], colnames(M) %in% dn[[2]]) + 0 
    setNames(out, unlist(dimnames(M))) 
    }) 
} 
s <- seriate(A.na) 
p <- permute(A.na, s) 
# calcualte largest square submatrix in p of zeros rearranging to be in A's order 
st <- largestSquare(1-p)[unlist(dimnames(A)), 1] 

res <- optim(st, f, lower = 0*st, upper = st^0, method = "L-BFGS-B") 

podając:

> res 
$par 
R1 R2 R3 R4 R5 R6 C1 C2 C3 C4 C5 
0 1 1 1 0 0 0 1 0 1 1 

$value 
[1] -9 

$counts 
function gradient 
     1  1 

$convergence 
[1] 0 

$message 
[1] "CONVERGENCE: NORM OF PROJECTED GRADIENT <= PGTOL" 

2) GenSA Inną możliwością jest powtórzenie (1), ale zamiast używać optim korzystanie GenSA z pakietu GenSA. Nie wymaga wartości początkowych (chociaż można podać wartość początkową za pomocą argumentu par, co w niektórych przypadkach może poprawić rozwiązanie), więc kod jest znacznie krótszy, ale ponieważ używa symulowanego wyżarzania, można oczekiwać, że uruchomienie potrwa znacznie dłużej . Używanie z użyciem f (i nr i Ainf które używa się) z (1). Poniżej próbujemy bez wartości początkowej.

library(GenSA) 
resSA <- GenSA(lower = rep(0, sum(dim(A))), upper = rep(1, sum(dim(A))), fn = f) 

daje:

> setNames(resSA$par, unlist(dimnames(A))) 
R1 R2 R3 R4 R5 R6 C1 C2 C3 C4 C5 
0 1 1 1 0 0 0 1 0 1 1 

> resSA$value 
[1] -9 
+0

Czy możesz rozwinąć to, co robi "seriate"? Plik pomocy był dla mnie zbyt żargonowy. – MichaelChirico

+0

Przekazuje wiersze i kolumny, które wysyłają permutację na podstawie argumentu 'method'. Użyliśmy domyślnego. Możesz użyć tego argumentu, aby wypróbować różne metody. Działa bardzo szybko, ale niekoniecznie da ci to, czego chcesz, więc będziesz musiał trochę się z nim bawić. Jest to raczej punkt wyjścia niż gotowe rozwiązanie, chociaż wydaje się, że działa w przypadku niewielkiego problemu w pytaniu. –

+0

Dzięki! Działa to dobrze i bardzo szybko dla macierzy przykładowej i kilku macierzy testowych, ale nie dla wszystkich. Z moją rzeczywistą matrycą i wieloma losowymi macierzami testowymi zwraca ona pojedynczą komórkę. Podam przykład tego w następnym komentarzu. –

5

mam rozwiązanie, ale nie skaluje się bardzo dobrze:

findBiggestSubmatrixNonContiguous <- function(A) { 
    A <- !is.na(A); ## don't care about non-NAs 
    howmany <- expand.grid(nr=seq_len(nrow(A)),nc=seq_len(ncol(A))); 
    howmany <- howmany[order(apply(howmany,1L,prod),decreasing=T),]; 
    for (ri in seq_len(nrow(howmany))) { 
     nr <- howmany$nr[ri]; 
     nc <- howmany$nc[ri]; 
     rcom <- combn(nrow(A),nr); 
     ccom <- combn(ncol(A),nc); 
     comcom <- expand.grid(ri=seq_len(ncol(rcom)),ci=seq_len(ncol(ccom))); 
     for (comi in seq_len(nrow(comcom))) 
      if (all(A[rcom[,comcom$ri[comi]],ccom[,comcom$ci[comi]]])) 
       return(list(ri=rcom[,comcom$ri[comi]],ci=ccom[,comcom$ci[comi]])); 
    }; ## end for 
    NULL; 
}; ## end findBiggestSubmatrixNonContiguous() 

Jest on oparty na założeniu, że jeśli matryca ma wystarczająco małą gęstość NAS, a następnie wyszukując największe podrzędne najpierw, dość szybko znajdziesz rozwiązanie.

Algorytm działa poprzez obliczenie iloczynu kartezjańskiego wszystkich liczy wierszy i liczy kolumn, które mogą być indeksowane z oryginalnej matrycy do wytworzenia podmatrycę. Zbiór par zliczeń jest następnie sortowany malejąco według rozmiaru submatrix, który byłby wytwarzany przez każdą parę zliczeń; innymi słowy, uporządkowane według iloczynu dwóch liczb. Następnie wykonuje iteracje po tych parach. Dla każdej pary oblicza ona wszystkie kombinacje indeksów wierszy i indeksów kolumn, które mogą zostać pobrane dla tej pary zliczeń, i próbuje każdej kombinacji po kolei, dopóki nie znajdzie submatrix zawierającej zero NA. Po znalezieniu takiej podsieci zwraca ten zbiór indeksów wierszy i kolumn jako listę.

Wynik jest poprawny, ponieważ próbuje podmacieć rozmiarów w porządku malejącym, więc pierwszy, który znajdzie, musi być największą (lub powiązaną z największą) możliwą submatrix, która spełnia warunek.


## OP's example matrix 
A <- data.frame(C1=c(NA,NA,NA,NA,2L,NA),C2=c(1L,1L,1L,0L,NA,NA),C3=c(1L,8L,NA,1L,1L,NA),C4=c(NA,1L,1L,6L,1L,3L),C5=c(NA,1L,5L,1L,1L,NA),row.names=c('R1','R2','R3','R4','R5','R6')); 
A; 
## C1 C2 C3 C4 C5 
## R1 NA 1 1 NA NA 
## R2 NA 1 8 1 1 
## R3 NA 1 NA 1 5 
## R4 NA 0 1 6 1 
## R5 2 NA 1 1 1 
## R6 NA NA NA 3 NA 
system.time({ res <- findBiggestSubmatrixNonContiguous(A); }); 
## user system elapsed 
## 0.094 0.000 0.100 
res; 
## $ri 
## [1] 2 3 4 
## 
## $ci 
## [1] 2 4 5 
## 
A[res$ri,res$ci]; 
## C2 C4 C5 
## R2 1 1 1 
## R3 1 1 5 
## R4 0 6 1 

Widzimy, że funkcja działa bardzo szybko, na przykład matrycy op, a zwraca poprawny wynik.


randTest <- function(NR,NC,probNA,seed=1L) { 
    set.seed(seed); 
    A <- replicate(NC,sample(c(NA,0:9),NR,prob=c(probNA,rep((1-probNA)/10,10L)),replace=T)); 
    print(A); 
    print(system.time({ res <- findBiggestSubmatrixNonContiguous(A); })); 
    print(res); 
    print(A[res$ri,res$ci,drop=F]); 
    invisible(res); 
}; ## end randTest() 

pisałem wyżej funkcji, aby testowanie łatwiejsze. Możemy go nazwać, aby przetestować losową matrycę wejściową o rozmiarze NR przez NC, z prawdopodobieństwem wyboru NA w dowolnej komórce probNA.


Oto kilka trywialne testy:

randTest(8L,1L,1/3); 
##  [,1] 
## [1,] NA 
## [2,] 1 
## [3,] 4 
## [4,] 9 
## [5,] NA 
## [6,] 9 
## [7,] 0 
## [8,] 5 
## user system elapsed 
## 0.016 0.000 0.003 
## $ri 
## [1] 2 3 4 6 7 8 
## 
## $ci 
## [1] 1 
## 
##  [,1] 
## [1,] 1 
## [2,] 4 
## [3,] 9 
## [4,] 9 
## [5,] 0 
## [6,] 5 

randTest(11L,3L,4/5); 
##  [,1] [,2] [,3] 
## [1,] NA NA NA 
## [2,] NA NA NA 
## [3,] NA NA NA 
## [4,] 2 NA NA 
## [5,] NA NA NA 
## [6,] 5 NA NA 
## [7,] 8 0 4 
## [8,] NA NA NA 
## [9,] NA NA NA 
## [10,] NA 7 NA 
## [11,] NA NA NA 
## user system elapsed 
## 0.297 0.000 0.300 
## $ri 
## [1] 4 6 7 
## 
## $ci 
## [1] 1 
## 
##  [,1] 
## [1,] 2 
## [2,] 5 
## [3,] 8 

randTest(10L,10L,1/3); 
##  [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] 
## [1,] NA NA 0 3 8 3 9 1 6 NA 
## [2,] 1 NA NA 4 5 8 NA 8 2 NA 
## [3,] 4 2 5 3 7 6 6 1 1  5 
## [4,] 9 1 NA NA 4 NA NA 1 NA  9 
## [5,] NA 7 NA 8 3 NA 5 3 7  7 
## [6,] 9 3 1 2 7 NA NA 9 NA  7 
## [7,] 0 2 NA 7 NA NA 3 8 2  6 
## [8,] 5 0 1 NA 3 3 7 1 NA  6 
## [9,] 5 1 9 2 2 5 NA 7 NA  8 
## [10,] NA 7 1 6 2 6 9 0 NA  5 
## user system elapsed 
## 8.985 0.000 8.979 
## $ri 
## [1] 3 4 5 6 8 9 10 
## 
## $ci 
## [1] 2 5 8 10 
## 
##  [,1] [,2] [,3] [,4] 
## [1,] 2 7 1 5 
## [2,] 1 4 1 9 
## [3,] 7 3 3 7 
## [4,] 3 7 9 7 
## [5,] 0 3 1 6 
## [6,] 1 2 7 8 
## [7,] 7 2 0 5 

nie wiem łatwy sposób sprawdzenia, czy powyższy wynik jest prawidłowy, ale wygląda mi dobrze. Ale wygenerowanie tego wyniku zajęło prawie 9 sekund.Uruchomienie tej funkcji na umiarkowanie większych macierzach, szczególnie macierzy 77x132, jest prawdopodobnie przegraną przyczyną.

Oczekiwanie, aby zobaczyć czy ktoś może wymyślić genialny skuteczne rozwiązanie ...