@@ -282,6 +282,104 @@ genCluster <- function(dtClust,
282282 return (dt [])
283283}
284284
285+ # ' @title Generate crossed data
286+ # '
287+ # ' @description Create the Cartesian product of two or more data sets,
288+ # ' preserving all variables from each input data set.
289+ # '
290+ # ' @param ... Two or more data sets to be crossed. Each input may be a
291+ # ' `data.frame` or `data.table`.
292+ # ' @param id Name of the crossed id field. Defaults to `"cross_id"`.
293+ # '
294+ # ' @return A `data.table` containing all combinations of rows from the input
295+ # ' data sets. The crossed id is placed first and used as the key.
296+ # '
297+ # ' @examples
298+ # ' region_def <- defData(varname = "r_effect", formula = 0, variance = 1)
299+ # ' mouse_def <- defData(varname = "m_effect", formula = 0, variance = 1)
300+ # '
301+ # ' dd_region <- genData(20, region_def, id = "region")
302+ # ' dd_mouse <- genData(8, mouse_def, id = "mouse")
303+ # '
304+ # ' dd <- genCrossed(
305+ # ' dd_mouse,
306+ # ' dd_region,
307+ # ' id = "mouse_region_id"
308+ # ' )
309+ # '
310+ # ' @export
311+ # ' @concept group_data
312+ genCrossed <- function (... , id = " cross_id" ) {
313+
314+ # to "declare" variable
315+
316+ .cross_join_id <- NULL
317+
318+ # ###
319+
320+ dts <- list (... )
321+
322+ # ### Check arguments
323+
324+ if (length(dts ) < 2 ) {
325+ stop(" at least two data sets must be provided" , call. = FALSE )
326+ }
327+
328+ if (! is.character(id ) || length(id ) != 1 ) {
329+ stop(" argument 'id' must be a single character string" , call. = FALSE )
330+ }
331+
332+ # ### Convert inputs to data.tables without modifying originals
333+
334+ dts <- lapply(dts , function (x ) {
335+ data.table :: copy(data.table :: as.data.table(x ))
336+ })
337+
338+ # ### Check for duplicate column names across inputs
339+
340+ all_names <- unlist(lapply(dts , names ), use.names = FALSE )
341+
342+ dup_names <- unique(all_names [duplicated(all_names )])
343+
344+ if (length(dup_names ) > 0 ) {
345+ stop(
346+ " input data sets must not share column names: " ,
347+ paste(dup_names , collapse = " , " ),
348+ call. = FALSE
349+ )
350+ }
351+
352+ if (id %in% all_names ) {
353+ stop(" argument 'id' already exists as a column name" , call. = FALSE )
354+ }
355+
356+ # ### Create Cartesian product
357+
358+ for (i in seq_along(dts )) {
359+ dts [[i ]][, .cross_join_id : = 1L ]
360+ }
361+
362+ # Sequentially merge all data sets to create Cartesian product
363+
364+ dt <- Reduce(
365+ function (x , y ) {
366+ merge(x , y , by = " .cross_join_id" , allow.cartesian = TRUE )
367+ },
368+ dts
369+ )
370+
371+ dt [, .cross_join_id : = NULL ]
372+
373+ # ### Add crossed id, move it to first column, and set key
374+
375+ dt [, eval(id ) : = .I ]
376+
377+ data.table :: setcolorder(dt , c(id , setdiff(names(dt ), id )))
378+ data.table :: setkeyv(dt , id )
379+
380+ return (dt [])
381+ }
382+
285383# ' Generate event data using longitudinal data, and restrict output to time
286384# ' until the nth event.
287385# '
0 commit comments