ea931d27ea58a2963cd148d9cb07c11415b42dac
[morpheus.git] / pkg / R / computeMu.R
1 #' Compute μ
2 #'
3 #' Estimate the normalized columns μ of the β matrix parameter in a mixture of
4 #' logistic regressions models, with a spectral method described in the package vignette.
5 #'
6 #' @param X Matrix of input data (size nxd)
7 #' @param Y Vector of binary outputs (size n)
8 #' @param optargs List of optional argument:
9 #' \itemize{
10 #' \item 'jd_method', joint diagonalization method from the package jointDiag:
11 #' 'uwedge' (default) or 'jedi'.
12 #' \item 'jd_nvects', number of random vectors for joint-diagonalization
13 #' (or 0 for p=d, canonical basis by default)
14 #' \item 'M', moments of order 1,2,3: will be computed if not provided.
15 #' \item 'K', number of populations (estimated with rank of M2 if not given)
16 #' }
17 #'
18 #' @return The estimated normalized parameters as columns of a matrix μ of size dxK
19 #'
20 #' @seealso \code{multiRun} to estimate statistics based on μ,
21 #' and \code{generateSampleIO} for I/O random generation.
22 #'
23 #' @examples
24 #' io = generateSampleIO(10000, 1/2, matrix(c(1,0,0,1),ncol=2), c(0,0), "probit")
25 #' μ = computeMu(io$X, io$Y, list(K=2)) #or just X and Y for estimated K
26 #' @export
27 computeMu = function(X, Y, optargs=list())
28 {
29 if (!is.matrix(X) || !is.numeric(X) || any(is.na(X)))
30 stop("X: real matrix, no NA")
31 n = nrow(X)
32 d = ncol(X)
33 if (!is.numeric(Y) || length(Y)!=n || any(Y!=0 & Y!=1))
34 stop("Y: vector of 0 and 1, size nrow(X), no NA")
35 if (!is.list(optargs))
36 stop("optargs: list")
37
38 # Step 0: Obtain the empirically estimated moments tensor, estimate also K
39 M = if (is.null(optargs$M)) computeMoments(X,Y) else optargs$M
40 K = optargs$K
41 if (is.null(K))
42 {
43 # TODO: improve this basic heuristic
44 Σ = svd(M[[2]])$d
45 large_ratio <- ( abs(Σ[-d] / Σ[-1]) > 3 )
46 K <- if (any(large_ratio)) max(2, which.min(large_ratio)) else d
47 }
48
49 # Step 1: generate a family of d matrices to joint-diagonalize to increase robustness
50 d = ncol(X)
51 fixed_design = FALSE
52 jd_nvects = ifelse(!is.null(optargs$jd_nvects), optargs$jd_nvects, 0)
53 if (jd_nvects == 0)
54 {
55 jd_nvects = d
56 fixed_design = TRUE
57 }
58 M2_t = array(dim=c(d,d,jd_nvects))
59 for (i in seq_len(jd_nvects))
60 {
61 rho = if (fixed_design) c(rep(0,i-1),1,rep(0,d-i)) else normalize( rnorm(d) )
62 M2_t[,,i] = .T_I_I_w(M[[3]],rho)
63 }
64
65 # Step 2: obtain factors u_i (and their inverse) from the joint diagonalisation of M2_t
66 jd_method = ifelse(!is.null(optargs$jd_method), optargs$jd_method, "uwedge")
67 V =
68 if (jd_nvects > 1) {
69 # NOTE: increasing itermax does not help to converge, thus we suppress warnings
70 suppressWarnings({jd = jointDiag::ajd(M2_t, method=jd_method)})
71 if (jd_method=="uwedge") jd$B else MASS::ginv(jd$A)
72 }
73 else
74 eigen(M2_t[,,1])$vectors
75
76 # Step 3: obtain final factors from joint diagonalisation of T(I,I,u_i)
77 M2_t = array(dim=c(d,d,K))
78 for (i in seq_len(K))
79 M2_t[,,i] = .T_I_I_w(M[[3]],V[,i])
80 suppressWarnings({jd = jointDiag::ajd(M2_t, method=jd_method)})
81 U = if (jd_method=="uwedge") MASS::ginv(jd$B) else jd$A
82 μ = normalize(U[,1:K])
83
84 # M1 also writes M1 = sum_k coeff_k * μ_k, where coeff_k >= 0
85 # ==> search decomposition of vector M1 onto the (truncated) basis μ (of size dxK)
86 # This is a linear system μ %*% C = M1 with C of size K ==> C = psinv(μ) %*% M1
87 C = MASS::ginv(μ) %*% M[[1]]
88 μ[,C < 0] = - μ[,C < 0]
89 μ
90 }