/
tour-frozen.r
158 lines (143 loc) · 4.83 KB
/
tour-frozen.r
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#' A frozen tour path.
#'
#' A frozen tour fixes some of the values of the orthonormal projection
#' matrix and allows the others to vary freely according to any of the
#' other tour methods. This frozen tour is a frozen grand tour. See
#' \code{\link{frozen_guided_tour}} for a frozen guided tour.
#'
#' Usually, you will not call this function directly, but will pass it to
#' a method that works with tour paths like \code{\link{animate}},
#' \code{\link{save_history}} or \code{\link{render}}.
#'
#' @param d target dimensionality
#' @param frozen matrix of frozen variables, as described in
#' \code{\link{freeze}}
#' @export
#' @examples
#' frozen <- matrix(NA, nrow = 4, ncol = 2)
#' frozen[3, ] <- .5
#' animate_xy(flea[, 1:4], frozen_tour(2, frozen))
#'
#' frozen <- matrix(NA, nrow = 4, ncol = 2)
#' frozen[1, 1] <- 0.5
#' animate_xy(flea[, 1:4], frozen_tour(2, frozen))
#'
#' # Doesn't work - a bug?
#' frozen <- matrix(NA, nrow = 4, ncol = 2)
#' frozen[1:2, 1] <- 1 / 4
#' animate_xy(flea[, 1:4], frozen_tour(2, frozen))
#'
#' \dontrun{
#' # This freezes one entire direction which causes a problem,
#' # and is caught by error handling.
#' # If you want to do this it would be best with a dependence
#' # tour, with one variable set one axis, eg 3rd variable to
#' # x axis would be indicated from the code below
#' frozen <- matrix(NA, nrow = 4, ncol = 2)
#' frozen[3, ] <- c(0, 1)
#' animate_xy(flea[, 1:4], frozen_tour(2, frozen))
#' }
#'
#' # Two frozen variables in five.
#' frozen <- matrix(NA, nrow = 5, ncol = 2)
#' frozen[3, ] <- .5
#' frozen[4, ] <- c(-.2, .2)
#' animate_xy(flea[, 1:5], frozen_tour(2, frozen))
frozen_tour <- function(d = 2, frozen) {
generator <- function(current, data, ...) {
if (is.null(current)) {
# Here is a problem if the frozen part is in the
# initialised basis, because it gets zero'd out by freeze.
notfrozen <- rep(TRUE, ncol(data))
for (i in 1:ncol(data))
if (sum(frozen[i,]^2, na.rm=TRUE)>0)
notfrozen[i] <- FALSE
current <- matrix(0, nrow = ncol(data), ncol = d)
for (i in 1:d) {
current[c(1:ncol(data))[notfrozen][i],i] <- 1
}
# This will still throw an error if number of frozen is bigger than d
return(current)
}
target <- basis_random(ncol(data), d)
list(target = target)
}
check_freezer_safe(frozen)
new_geodesic_path("frozen", generator, frozen = frozen)
}
#' The frozen guided tour
#'
#' @param frozen matrix of frozen variables, as described in
#' \code{\link{freeze}}
#' @param index_f the index function to optimise.
#' @param d target dimensionality
#' @param max.tries the maximum number of unsuccessful attempts to find
#' a better projection before giving up
#' @seealso \code{\link{cmass}}, \code{\link{holes}} and \code{\link{lda_pp}}
#' for examples of index functions. The function should take a numeric
#' matrix and return a single number, preferrably between 0 and 1.
#' @export
#' @examples
#' frozen <- matrix(NA, nrow = 4, ncol = 2)
#' frozen[3, ] <- .5
#' animate_xy(flea[, 1:4], frozen_guided_tour(frozen, holes()))
frozen_guided_tour <- function(frozen, index_f, d = 2, max.tries = 25) {
generator <- function(current, data, tries, ...) {
if (is.null(current)) {
return(basis_init(ncol(data), d))
}
index <- function(proj) {
index_f(as.matrix(data) %*% proj)
}
basis <- search_frozen_geodesic(current, index, tries, max.tries, frozen = frozen)
list(target = basis$target, index = index)
}
check_freezer_safe(frozen)
new_geodesic_path("frozen-guided", generator, frozen = frozen)
}
#' Check matrix is a valid frozen matrix
#'
#' @keywords internal
#' @param frozen matrix to check for freezability
check_freezer_safe <- function(frozen) {
stopifnot(is.matrix(frozen))
lengths <- colSums(frozen^2, na.rm = TRUE)
if (any(lengths >= 1)) {
stop("Columns of frozen matrix must have squared norm < 1", call. = FALSE)
}
}
#' Freeze and thaw matrices
#'
#' Some terminology:
#' * frozen variables: the variables that have fixed values
#' * warm variables: the remaining variables that vary freely
#'
#' A frozen matrix specifies which variables to fix in a projection matrix.
#' Warm variables should be missing (\code{NA}) while frozen variables should
#' be set to their fixed values.
#'
#' @keywords internal
#' @export
#' @examples
#' frozen <- matrix(NA, nrow = 4, ncol = 2)
#' frozen[3, ] <- .5
#'
#' input <- basis_random(4, 2)
#' freeze(input, frozen)
#' thaw(input, frozen)
#' freeze(basis_random(4, 2), frozen)
freeze <- function(input, frozen) {
fixed <- !is.na(frozen)
input[fixed] <- 0
input
}
#' @export
#' @rdname freeze
thaw <- function(input, frozen) {
fixed <- !is.na(frozen)
input <- normalise(input)
frozen_lengths <- colSums(frozen^2, na.rm = TRUE)
input <- sweep(input, 2, sqrt(1 - frozen_lengths), "*")
input[fixed] <- frozen[fixed]
input
}