-
-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathas_draws_array.R
More file actions
252 lines (229 loc) · 6.74 KB
/
as_draws_array.R
File metadata and controls
252 lines (229 loc) · 6.74 KB
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
#' The `draws_array` format
#'
#' @name draws_array
#' @family formats
#'
#' @templateVar draws_format draws_array
#' @templateVar base_class "array"
#' @template draws_format-skeleton
#' @template args-format-nchains
#'
#' @details Objects of class `"draws_array"` are 3-D arrays with dimensions
#' `"iteration"`, `"chain"`, and `"variable"`. See **Examples**.
#'
NULL
#' @rdname draws_array
#' @export
as_draws_array <- function(x, ...) {
UseMethod("as_draws_array")
}
#' @rdname draws_array
#' @export
as_draws_array.default <- function(x, ...) {
x <- as_draws(x)
as_draws_array(x, ...)
}
#' @rdname draws_array
#' @export
as_draws_array.draws_array <- function(x, ...) {
x
}
#' @rdname draws_array
#' @export
as_draws_array.draws_matrix <- function(x, ...) {
old_dim <- dim(x)
old_dimnames <- dimnames(x)
iteration_ids <- iteration_ids(x)
chain_ids <- chain_ids(x)
dim(x) <- c(niterations(x), nchains(x), old_dim[2])
dimnames(x) <- list(
iteration = iteration_ids,
chain = chain_ids,
variable = old_dimnames[[2]]
)
class(x) <- class_draws_array()
attr(x, "nchains") <- NULL
x
}
#' @rdname draws_array
#' @export
as_draws_array.draws_df <- function(x, ...) {
if (ndraws(x) == 0) {
return(empty_draws_array(variables(x)))
}
iterations <- iteration_ids(x)
chains <- chain_ids(x)
x <- check_variables_are_numeric(x, to = "draws_array")
out <- vector("list", length(chains))
for (i in seq_along(out)) {
if (length(chains) == 1) {
out[[i]] <- x
} else {
out[[i]] <- x[x$.chain == i, ]
}
out[[i]] <- remove_reserved_df_variables(out[[i]])
out[[i]] <- as.matrix(out[[i]])
}
out <- as_array_matrix_list(out)
dimnames(out) <- list(
iteration = iterations,
chain = chains,
variable = dimnames(out)[[3]]
)
class(out) <- class_draws_array()
out
}
#' @rdname draws_array
#' @export
as_draws_array.draws_list <- function(x, ...) {
x <- as_draws_df(x)
as_draws_array(x, ...)
}
#' @rdname draws_array
#' @export
as_draws_array.draws_rvars <- function(x, ...) {
if (ndraws(x) == 0) {
return(empty_draws_array(variables(x)))
}
x <- check_variables_are_numeric(
x, to = "draws_array", is_non_numeric = is_rvar_factor, convert = FALSE
)
x <- promote_rvar_weights_to_variable(x)
# cbind discards class information when applied to vectors, which converts
# the underlying factors to numeric
draws <- do.call(cbind, lapply(seq_along(x), function(i) {
# flatten each rvar so it only has two dimensions: draws and variables
# this also collapses indices into variable names in the format "var[i,j,k,...]"
x_i <- flatten_array(x[[i]], names(x)[[i]])
draws_of(x_i)
}))
# add chain info back into the draws array
# ([draws, variables] -> [iterations, chains, variables])
.dimnames <- dimnames(draws)
dim(draws) <- c(niterations(x), nchains(x), dim(draws)[-1])
dimnames(draws) <- c(list(NULL, NULL), .dimnames[-1])
as_draws_array(draws, ...)
}
#' @rdname draws_array
#' @export
as_draws_array.mcmc <- function(x, ...) {
as_draws_array(as_draws_matrix(x), ...)
}
#' @rdname draws_array
#' @export
as_draws_array.mcmc.list <- function(x, ...) {
class(x) <- "list"
.as_draws_array(as_array_matrix_list(x))
}
# try to convert any R object into a 'draws_array' object
.as_draws_array <- function(x) {
if (is_matrix_list_like(x)) {
x <- as_array_matrix_list(x)
} else {
x <- as.array(x)
}
new_dimnames <- list(iteration = NULL, chain = NULL, variable = NULL)
if (!is.null(dimnames(x)[[3]])) {
new_dimnames[[3]] <- dimnames(x)[[3]]
} else {
new_dimnames[[3]] <- default_variables(dim(x)[3])
}
check_new_variables(new_dimnames[[3]])
new_dimnames[[1]] <- as.character(seq_rows(x))
new_dimnames[[2]] <- as.character(seq_cols(x))
dimnames(x) <- new_dimnames
class(x) <- class_draws_array()
x
}
#' @rdname draws_array
#' @export
draws_array <- function(..., .nchains = 1) {
out <- validate_draws_per_variable(...)
.nchains <- as_one_integer(.nchains)
if (.nchains < 1) {
stop_no_call("Number of chains must be positive.")
}
ndraws <- length(out[[1]])
if (ndraws %% .nchains != 0) {
stop_no_call("Number of chains does not divide the number of draws.")
}
niterations <- ndraws / .nchains
variables <- names(out)
out <- unlist(out)
out <- array(out, dim = c(niterations, .nchains, length(variables)))
dimnames(out) <- list(NULL, NULL, variables)
as_draws_array(out)
}
class_draws_array <- function() {
c("draws_array", "draws", "array")
}
#' @rdname draws_array
#' @export
is_draws_array <- function(x) {
inherits(x, "draws_array")
}
# is an object looking like a 'draws_array' object?
is_draws_array_like <- function(x) {
is.array(x) && length(dim(x)) == 3L ||
is_matrix_list_like(x)
}
# is an object likely a list of matrices?
# such an object can be easily converted to a draws_array
is_matrix_list_like <- function(x) {
is.list(x) && length(dim(x[[1]])) == 2L
}
#' Extract parts of a `draws_array` object
#'
#' Extract parts of a `draws_array` object. They are strictly defined as arrays
#' of 3 dimensions (iteration x chain x variable) so dropping any of the
#' dimensions breaks the expected structure of the object. Accordingly, no
#' dropping of dimensions is done by default even if the extracted slices are of
#' length 1. If `drop` is manually set to `TRUE` and any of the dimensions is
#' actually dropped, this will lead to dropping the `"draws_array"` class as
#' well.
#'
#' @param x,i,j,...,drop Same as in the default extraction method but with
#' `drop` being set to `FALSE` by default.
#'
#' @return An object of class `"draws_array"` unless any of the dimensions
#' was dropped during the extraction.
#'
#' @keywords internal
#' @export
`[.draws_array` <- function(x, i, j, ..., drop = FALSE) {
# TODO: allow for argument 'reserved' as in '[.draws_df'
# right now this fails because NextMethod() cannot ignore arguments
out <- NextMethod("[", drop = drop)
if (length(dim(out)) == length(dim(x))) {
class(out) <- class(x)
}
out
}
#' @export
variance.draws_array <- function(x, ...) {
var(as.vector(x))
}
# convert a list of matrices to an array
as_array_matrix_list <- function(x) {
stopifnot(is.list(x))
x <- abind::abind(x, along = 3L)
aperm(x, c(1, 3, 2))
}
# create an empty draws_array object
empty_draws_array <- function(variables = character(0), nchains = 0,
niterations = 0) {
assert_character(variables, null.ok = TRUE)
assert_number(nchains, lower = 0)
assert_number(niterations, lower = 0)
out <- array(
numeric(0),
dim = c(niterations, nchains, length(variables)),
dimnames = list(
iteration = seq_len(niterations),
chain = seq_len(nchains),
variable = variables
)
)
class(out) <- class_draws_array()
out
}