1515# ' file:
1616# '
1717# ' * `logical` -> `integer` (`TRUE` -> `1`, `FALSE` -> `0`)
18- # ' * `data.frame` -> `matrix` (via [data.matrix()])
18+ # ' * `factor` -> `integer` (the index of each value's level)
19+ # ' * `data.frame` -> `matrix` (via [data.matrix()]); every column must be
20+ # ' numeric, integer, logical, or factor
1921# ' * `list` -> `array`
2022# ' * `table` -> `vector`, `matrix`, or `array` (depending on dimensions of table)
2123# '
24+ # ' ### Factor conversion
25+ # ' Factors are written as their level indices: the position of each value in
26+ # ' `levels(x)` rather than the value itself. The default levels are the sorted
27+ # ' unique values, so `factor(c(10, 9, 8))` has levels `8`, `9`, `10` and is
28+ # ' written as `[3, 2, 1]`, and an unused level shifts the indices of the levels
29+ # ' after it. If the original values are what you want, convert them first, e.g.
30+ # ' with `as.numeric(as.character(x))`. The fitting methods of a model compiled
31+ # ' from a Stan file error if a factor is supplied for a variable that is not
32+ # ' declared as `int`, but `write_stan_json()` has no declarations to check
33+ # ' against and so always converts.
34+ # '
35+ # '
36+ # ' ### List to array conversion
2237# ' The `list` to `array` conversion is intended to make it easier to prepare
2338# ' the data for certain Stan declarations involving arrays:
2439# '
2540# ' * `array[K] vector[J] v ` can be constructed in \R as a list with `K`
2641# ' elements where each element is a vector of length `J`
2742# ' * `array[K] matrix[I,J] m ` can be constructed in \R as a list with `K`
2843# ' elements where each element is an `IxJ` matrix
44+ # ' * `array[K,I,J] int n ` can be constructed in \R as a list with `K`
45+ # ' elements where each element is an `IxJ` matrix of integers
2946# '
3047# ' These can also be passed in from \R as arrays instead of lists but the list
31- # ' option is provided for convenience. Unfortunately for arrays with more than
32- # ' one dimension (e.g. `array[K,L] vector[J] v `) it is not possible to use an
33- # ' \R list and an array must be used instead. For this example the array in \R
34- # ' should have dimensions `KxLxJ`.
48+ # ' option is provided for convenience. A list always contributes exactly one
49+ # ' leading dimension, so `array[K,L] vector[J] v ` can be supplied either as a
50+ # ' list of `K` matrices each with dimensions `LxJ` or as a single \R array with
51+ # ' dimensions `KxLxJ`. Nested lists are not supported: every element of the list
52+ # ' must be a vector, matrix, or array.
3553# '
54+ # ' ### Scalar vs. length-1 vector
3655# ' Because \R does not distinguish between a scalar and a vector of length 1, a
3756# ' length-1 vector like `c(42)` is written to JSON as a scalar (`42`) rather
3857# ' than an array (`[42]`). If a Stan variable is declared as a vector or array
@@ -97,23 +116,12 @@ write_stan_json <- function(data, file, always_decimal = FALSE) {
97116 if (is.null(var )) {
98117 stop(" Variable '" , var_name , " ' is NULL." , call. = FALSE )
99118 }
100- if (! (is.numeric(var ) || is.factor(var ) || is.logical(var ) ||
101- is.data.frame(var ) || is.list(var ))) {
102- stop(" Variable '" , var_name , " ' is of invalid type." , call. = FALSE )
103- }
119+ validate_data_type(var , var_name )
120+ var <- convert_to_array(var , var_name )
121+ # after the conversion, so that NAs nested inside a list are also found
104122 if (anyNA(var )) {
105123 stop(" Variable '" , var_name , " ' has NA values." , call. = FALSE )
106124 }
107-
108- if (is.table(var )) {
109- var <- unclass(var )
110- } else if (is.logical(var )) {
111- mode(var ) <- " integer"
112- } else if (is.data.frame(var )) {
113- var <- data.matrix(var )
114- } else if (is.list(var )) {
115- var <- list_to_array(var , var_name )
116- }
117125 data [[var_name ]] <- var
118126 }
119127
@@ -130,6 +138,55 @@ write_stan_json <- function(data, file, always_decimal = FALSE) {
130138}
131139
132140
141+ # Types accepted for a data variable and for each column of a data frame
142+ is_valid_data_type <- function (x ) {
143+ is.numeric(x ) || is.factor(x ) || is.logical(x )
144+ }
145+
146+
147+ # TRUE for a factor, or a data frame with any factor column
148+ has_factor <- function (x ) {
149+ is.factor(x ) || (is.data.frame(x ) && any(vapply(x , is.factor , logical (1 ))))
150+ }
151+
152+
153+ # Error if a variable is not one of the types accepted in a data list. Data
154+ # frames and lists are accepted here and converted by convert_to_array().
155+ validate_data_type <- function (var , var_name ) {
156+ if (! is_valid_data_type(var ) && ! is.data.frame(var ) && ! is.list(var )) {
157+ stop(" Variable '" , var_name , " ' is of invalid type." , call. = FALSE )
158+ }
159+ invisible (NULL )
160+ }
161+
162+
163+ # Convert the R container types accepted in a data list to the atomic arrays
164+ # CmdStan's JSON reader expects. Used by both write_stan_json() and
165+ # process_data() so that the two paths agree.
166+ convert_to_array <- function (var , var_name = NULL ) {
167+ if (is.table(var )) {
168+ var <- unclass(var )
169+ } else if (is.data.frame(var )) {
170+ # data.matrix() silently coerces character columns to factor codes and
171+ # date/time columns to their numeric representation, so apply the same
172+ # type check used for the variables themselves (#817)
173+ invalid <- ! vapply(var , is_valid_data_type , logical (1 ))
174+ if (any(invalid )) {
175+ stop(" Variable '" , var_name , " ' has columns of invalid type: " ,
176+ paste(names(var )[invalid ], collapse = " , " ), " ." , call. = FALSE )
177+ }
178+ var <- data.matrix(var )
179+ } else if (is.list(var )) {
180+ var <- list_to_array(var , var_name )
181+ }
182+ # after the conversions above so that lists of logicals are also converted
183+ if (is.logical(var )) {
184+ mode(var ) <- " integer"
185+ }
186+ var
187+ }
188+
189+
133190list_to_array <- function (x , name = NULL ) {
134191 list_length <- length(x )
135192 if (list_length == 0 ) {
@@ -142,9 +199,9 @@ list_to_array <- function(x, name = NULL) {
142199 if (! all_equal_dim ) {
143200 stop(" All matrices/vectors in list '" , name , " ' must be the same size!" , call. = FALSE )
144201 }
145- all_numeric <- all(sapply(x , function (a ) is.numeric(a )))
202+ all_numeric <- all(sapply(x , function (a ) is.numeric(a ) || is.logical( a ) ))
146203 if (! all_numeric ) {
147- stop(" All elements in list '" , name , " ' must be numeric!" , call. = FALSE )
204+ stop(" All elements in list '" , name , " ' must be numeric or logical !" , call. = FALSE )
148205 }
149206 element_num_of_dim <- length(all_dims [[1 ]])
150207 x <- unlist(x )
@@ -158,12 +215,6 @@ list_to_array <- function(x, name = NULL) {
158215# ' @noRd
159216# ' @param data If not `NULL`, then either a path to a data file compatible with
160217# ' CmdStan, or a named list of \R objects to pass to [write_stan_json()].
161- # ' @param stan_file If not `NULL`, the path to the Stan model for which to
162- # ' process the named list suppiled to the `data` argument. The Stan model
163- # ' is used for checking whether the supplied named list has all the
164- # ' required elements/Stan variables and to help differentiate between a
165- # ' vector of length 1 and a scalar when genereting the JSON file. This
166- # ' argument is ignored when a path to a data file is supplied for `data`.
167218# ' @param model_variables A list of all parameters with their types and
168219# ' number of dimensions. Typically the output of model$variables().
169220# ' @return Path to data file.
@@ -192,6 +243,21 @@ process_data <- function(data, model_variables = NULL) {
192243 if (is.null(data [[var_name ]])) {
193244 stop(" Variable '" , var_name , " ' is NULL." , call. = FALSE )
194245 }
246+ validate_data_type(data [[var_name ]], var_name )
247+ # Factors are written as level indices, which are only meaningful for
248+ # variables declared as int. Handle them before the conversions below,
249+ # which replace factors with their codes and drop the factor class.
250+ if (data_variables [[var_name ]]$ type == " int" ) {
251+ if (is.factor(data [[var_name ]])) {
252+ data [[var_name ]] <- as.integer(data [[var_name ]])
253+ }
254+ } else if (has_factor(data [[var_name ]])) {
255+ stop(" A factor was supplied for '" , var_name , " ', which is declared as '" ,
256+ data_variables [[var_name ]]$ type , " '." , call. = FALSE )
257+ }
258+ # Convert lists and data frames to arrays before the checks below,
259+ # which require an atomic object (#817)
260+ data [[var_name ]] <- convert_to_array(data [[var_name ]], var_name )
195261 # distinguish between scalars and arrays/vectors of length 1
196262 if (length(data [[var_name ]]) == 1
197263 && data_variables [[var_name ]]$ dimensions == 1 ) {
@@ -201,17 +267,15 @@ process_data <- function(data, model_variables = NULL) {
201267 # generating a decimal point in write_stan_json
202268 if (data_variables [[var_name ]]$ type == " int"
203269 && ! is.integer(data [[var_name ]])) {
204- if (! is.factor(data [[var_name ]])) {
205- if (! isTRUE(all(is_wholenumber(data [[var_name ]])))) {
206- # Don't warn for NULL/NA, as different warnings are used for those
207- if (! isTRUE(anyNA(data [[var_name ]]))) {
208- warning(" A non-integer value was supplied for '" , var_name , " '!" ,
209- " It will be truncated to an integer." , call. = FALSE )
210- }
211- } else {
212- # Round before setting mode to integer to avoid floating point errors
213- data [[var_name ]] <- round(data [[var_name ]])
270+ if (! isTRUE(all(is_wholenumber(data [[var_name ]])))) {
271+ # Don't warn for NULL/NA, as different warnings are used for those
272+ if (! isTRUE(anyNA(data [[var_name ]]))) {
273+ warning(" A non-integer value was supplied for '" , var_name , " '!" ,
274+ " It will be truncated to an integer." , call. = FALSE )
214275 }
276+ } else {
277+ # Round before setting mode to integer to avoid floating point errors
278+ data [[var_name ]] <- round(data [[var_name ]])
215279 }
216280 mode(data [[var_name ]]) <- " integer"
217281 }
0 commit comments