Skip to content

Commit

Permalink
[Do not Merge] Add mixed columns to jaspTable (#149)
Browse files Browse the repository at this point in the history
* start on mixedColumns and mixedRows

* mixedRows now also working

* some updates, ready for js changes

* mostly functional

* cleanup

* finally functional

* simplify weird listlength handling
  • Loading branch information
vandenman authored Sep 4, 2024
1 parent 7de02c4 commit a3cd372
Show file tree
Hide file tree
Showing 8 changed files with 359 additions and 15 deletions.
4 changes: 4 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ S3method("[[<-",jaspContainerR)
S3method("[[<-",jaspTableR)
S3method(.RCodeInOptionsIsOk,default)
S3method(.RCodeInOptionsIsOk,list)
S3method(as.data.frame,mixed)
S3method(decodeplot,"function")
S3method(decodeplot,gDesc)
S3method(decodeplot,gTree)
Expand All @@ -18,6 +19,7 @@ S3method(decodeplot,jaspGraphsPlot)
S3method(decodeplot,patchwork)
S3method(decodeplot,qgraph)
S3method(decodeplot,recordedplot)
S3method(format,mixed)
S3method(gsubInteractionSymbol,character)
S3method(gsubInteractionSymbol,list)
S3method(ifElse,character)
Expand Down Expand Up @@ -74,6 +76,8 @@ export(createJaspQmlSource)
export(createJaspReport)
export(createJaspState)
export(createJaspTable)
export(createMixedColumn)
export(createMixedRow)
export(decodeColNames)
export(decodeName)
export(encodeColNames)
Expand Down
99 changes: 99 additions & 0 deletions R/mixedColumns.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
validateCellTypes <- function(types) {
permittedTypes <- c("integer", "number", "pvalue", "string", "separator")
if (!all(types %in% permittedTypes)) {
badTypes <- unique(setdiff(types, permittedTypes))
stop(sprintf("Valid types are `%s` but got: %s",
paste(permittedTypes, collapse = ", "),
paste(badTypes, collapse = ", ")))
}
}

validateCellFormats <- function(formats, types, lengthOfValues) {
if (is.null(formats)) {
formats <- vector("list", length(types))
if (any(types %in% c("number", "pvalue"))) {

formats[types == "number"] <- list("sf:4;dp:3")
formats[types == "pvalue"] <- list("dp:3;p:.001")

}
} else {
stopifnot(length(types) == length(formats))
}
stopifnot(lengthOfValues == length(types))
return(formats)
}

#' @export
createMixedColumn <- function(values, types, formats = NULL) {

validateCellTypes(types)
formats <- validateCellFormats(formats, types, length(values))

# NOTE: names are not actually used
data <- Map(\(v, t, f) list(value = v, type = t, format = f), values, types, formats)

result <- vctrs::new_vctr(data, column = TRUE, class = "mixed")

return(result)

}

#' @export
createMixedRow <- function(value, type, format = NULL) {
# TODO: support multiple values and types?
validateCellTypes(type)
format <- validateCellFormats(format, type, length(value))
data <- list(value = value, type = type, format = format[[1L]])
result <- vctrs::new_vctr(list(data), column = FALSE, class = "mixed")
return(result)
}

isMixedColumn <- function(x) {
isTRUE(attr(x, "column"))
}


formatMixedHelper <- function(x, showFormat = FALSE, shortTypes = TRUE, usePillar = FALSE) {

# TODO: formatting with Pillar is way nicer, but maybe not always compatible with all terminals...
# it also would require overwriting the print function, which I'm not very keen about

mixedAbbreviations <- c(
"pvalue" = "pval",
"number" = "num",
"string" = "str",
"integer" = "int"
)

value <- x[["value"]]
type <- x[["type"]]
format <- x[["format"]]

if (usePillar) {
style_num <- pillar::style_num
style_subtle <- pillar::style_subtle
} else {
style_num <- style_subtle <- function(x, ...) x
}

paste0(
if (type == "string") value else style_num(value, negative = value < 0),
style_subtle(paste0(if (showFormat || !usePillar) "<" else "", if (shortTypes) mixedAbbreviations[type] else type)),
if (showFormat) style_subtle(paste0("|", format, ">")) else if (!usePillar) ">"
)
}

#' @exportS3Method
format.mixed <- function(x, showFormat = FALSE, shortTypes = TRUE, usePillar = FALSE, ...) {

data <- vctrs::vec_data(x)
return(vapply(X = data, FUN = formatMixedHelper, FUN.VALUE = character(1L), showFormat = showFormat, shortTypes = shortTypes, usePillar = usePillar))

}

#' @export
# So we don't need I(...) when putting mixed in a data.frame
as.data.frame.mixed <- function(x, row.names = NULL, optional = FALSE, ...) {
as.data.frame.AsIs(x, row.names = row.names, optional = optional, ...)
}
4 changes: 2 additions & 2 deletions R/zzzWrappers.R
Original file line number Diff line number Diff line change
Expand Up @@ -711,9 +711,9 @@ jaspTableR <- R6::R6Class(

addColumnInfo = function(name = NULL, title = NULL, overtitle = NULL, type = NULL, format = NULL, combine = NULL) {
if (!is.null(type)) {
permittedTypes <- c("integer", "number", "pvalue", "string", "separator")
permittedTypes <- c("integer", "number", "pvalue", "string", "separator", "mixed")
if (!type %in% permittedTypes)
stop("type must be ", paste0("`", permittedTypes, "`", collapse=", "), " (provided type: `", type, "`)", domain = NA)
stop("type must be ", paste0("`", permittedTypes, "`", collapse = ", "), " (provided type: `", type, "`)", domain = NA)

if (is.null(format) && type == "number")
format <- "sf:4;dp:3"
Expand Down
165 changes: 165 additions & 0 deletions inst/examples/ex-mixedColumns.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
library(jaspBase)

# Example 1: assign columns directy using createMixedColumn (using createMixedColumn is the recommended approach) ----
tb <- createJaspTable()

data <- createMixedColumn(
values = list(1.23, 0.04, "hoi", 123),
types = c("number", "pvalue", "string", "integer")
)

tb[["col"]] <- data
tb[["col2"]] <- seq(length(data))
tb

tbR <- tb$toRObject()
all.equal(tbR$col0, data) # TRUE
all.equal(tbR$col1, seq(length(data))) # TRUE

# Example 2: use setData with createMixedColumn ----
tb <- createJaspTable()

df <- data.frame(
col = createMixedColumn(
values = list(1.23, 0.04, "hoi", 123),
types = c("number", "pvalue", "string", "integer")
),
col2 = 1:4
)


df
tb$setData(df)
tb

# Example 3a, addColumns pass data.frame ----
tb <- createJaspTable()

df <- data.frame(
col = createMixedColumn(
values = list(1.23, 0.04, "hoi", 123),
types = c("number", "pvalue", "string", "integer")
),
col2 = 1:4
)


tb$addColumns(cols = df)
tb

tbR <- tb$toRObject()
all.equal(tbR$col, df$col) # TRUE
all.equal(tbR$col2, df$col2) # TRUE
all.equal(tbR, df, check.attributes = FALSE) # TRUE


# Example 3b, addColumns pass list ----
tb <- createJaspTable()

lst <- list(
col = createMixedColumn(
values = list(1.23, 0.04, "hoi", 123),
types = c("number", "pvalue", "string", "integer")
),
col2 = 1:4
)


tb$addColumns(cols = lst)
tb

# Example 4a, addRows pass data.frame ----
tb <- createJaspTable()

df <- data.frame(
name1 = createMixedColumn(
values = list(1.23, 0.04, "hoi", 123),
types = c("number", "pvalue", "string", "integer")
),
name2 = 1:4
)

rowList <- lapply(seq_len(nrow(df)), function(i) {
list(
name1 = createMixedRow(
value = df$name1[[i]]$value,
type = df$name1[[i]]$type
),
name2 = i
)
})

# one by one
tb
tb$addRows(rows = rowList[[1]])
tb
tb$addRows(rows = rowList[[2]])
tb
tb$addRows(rows = rowList[[3]])
tb
tb$addRows(rows = rowList[[4]])
tb

# all at once
tb <- createJaspTable()
tb$addRows(rows = rowList)
tb


# Example 5a, addColumnInfo pass data.frame ----
tb <- createJaspTable()
tb$addColumnInfo(name = "m", title = "mixed Column", type = "mixed")
tb$addColumnInfo(name = "i", title = "int", type = "integer")

data <- createMixedColumn(
values = list(1.23, 0.04, "hoi", 123),
types = c("number", "pvalue", "string", "integer")
)
tb[["m"]] <- data

# temporary for comparison with master
library(jaspBase)
tb1 <- createJaspTable()
tb1$addRows(rows = list(a = 1, b = 2, c = 3))
tb1
tb1$addRows(rows = list(a = 2, b = 4, c = 6))
tb1
tb1$addRows(rows = list(d = 3, f = 5))
tb1


rowList2 <- list(
list(a = 1, b = 2),
list(a = 2, b = 4),
list(a = 3, b = 6),
list(c = 3, d = 4),
list(c = 6, d = 8)
)

tb2 <- createJaspTable()
tb2$addRows(rows = rowList2)
tb2

# this crashes!
tb1 <- createJaspTable()
for (l in letters[1:6])
tb1$addColumnInfo(name = l, title = l, type = "integer")
tb1$addRows(rows = list(a = 1, b = 2, c = 3))
tb1
tb1$addRows(rows = list(a = 2, b = 4, c = 6))
tb1
tb1$addRows(rows = list(d = 3, e = 5))
tb1


rowList2 <- list(
list(a = 1, b = 2),
list(a = 2, b = 4),
list(a = 3, b = 6),
list(c = 3, d = 4),
list(c = 6, d = 8)
)

tb2 <- createJaspTable()
tb2$addRows(rows = rowList2)
tb2
19 changes: 19 additions & 0 deletions src/jaspObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,7 @@ std::vector<Json::Value> jaspObject::RList_to_VectorJson(Rcpp::List obj)
Json::Value jaspObject::RObject_to_JsonValue(Rcpp::RObject obj)
{
if(obj.isNULL()) return Json::nullValue;
else if(isMixedRObject(obj)) return MixedRObject_to_JsonValue((Rcpp::List) obj);
else if(Rcpp::is<Rcpp::List>(obj)) return RObject_to_JsonValue((Rcpp::List) obj);
else if(Rcpp::is<Rcpp::DataFrame>(obj)) return RObject_to_JsonValue((Rcpp::List) obj);
else if(Rcpp::is<Rcpp::NumericMatrix>(obj)) return RObject_to_JsonValue<REALSXP>((Rcpp::NumericMatrix) obj);
Expand All @@ -674,6 +675,24 @@ Json::Value jaspObject::RObject_to_JsonValue(Rcpp::RObject obj)
else return "something that is not understood by jaspResults right now..";
}

Json::Value jaspObject::MixedRObject_to_JsonValue(Rcpp::List obj)
{

Json::Value value(Json::objectValue);

// sometimes we receive list(mixed) and sometimes mixed, ideally we always just get mixed but I'm not sure that's possible with addRows.
Rcpp::List data = obj.length() != 3 ? obj[0] : obj;

value["value"] = RObject_to_JsonValue((Rcpp::RObject)data["value"]);
value["type"] = RObject_to_JsonValue((Rcpp::RObject)data["type"]);
value["format"] = RObject_to_JsonValue((Rcpp::RObject)data["format"]);


return value;

}


Json::Value jaspObject::RObject_to_JsonValue(Rcpp::List obj)
{
bool atLeastOneNamed = false;
Expand Down
17 changes: 14 additions & 3 deletions src/jaspObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ void jaspPrint( std::string msg);

DECLARE_ENUM(jaspObjectType, unknown, container, table, plot, list, results, html, state, column, qmlSource, report);
DECLARE_ENUM(jaspColumnType, unknown, scale, ordinal, nominal, nominalText); //can be merged with columnType from CentralDatasetModel branch later on?
DECLARE_ENUM(jaspTableColumnType, unknown, null, string, logical, integer, number, various, composite); //can be merged with columnType from CentralDatasetModel branch later on?
DECLARE_ENUM(jaspTableColumnType, unknown, null, string, logical, integer, number, various, composite, mixed); //can be merged with columnType from CentralDatasetModel branch later on?

jaspObjectType jaspObjectTypeStringToObjectType(std::string type);

Expand Down Expand Up @@ -147,12 +147,21 @@ class jaspObject
else if(Rcpp::is<Rcpp::IntegerVector>(obj)) return RcppVector_to_VectorJson<INTSXP>((Rcpp::IntegerVector) obj);
else if(Rcpp::is<Rcpp::StringVector>(obj)) return RcppVector_to_VectorJson<STRSXP>((Rcpp::StringVector) obj);
else if(Rcpp::is<Rcpp::CharacterVector>(obj)) return RcppVector_to_VectorJson<STRSXP>((Rcpp::CharacterVector) obj);
else if(isMixedRObject(obj)) return MixedRcppVector_to_VectorJson( (Rcpp::List) obj);
else if(Rcpp::is<Rcpp::List>(obj)) return RList_to_VectorJson((Rcpp::List) obj);
else if(throwError) Rf_error("JASPjson::RcppVector_to_VectorJson received an SEXP that is not a Vector of some kind.");

return std::vector<Json::Value>({""});
}

std::vector<Json::Value> MixedRcppVector_to_VectorJson(Rcpp::List obj)
{
std::vector<Json::Value> vec;
for(int i=0; i<obj.length(); i++)
vec.push_back(MixedRObject_to_JsonValue(obj[i]));

return vec;
}

template<int RTYPE> std::vector<Json::Value> RcppVector_to_VectorJson(Rcpp::Vector<RTYPE> obj)
{
Expand Down Expand Up @@ -185,9 +194,11 @@ class jaspObject
return vecvec;
}

Json::Value RObject_to_JsonValue(Rcpp::RObject obj);
Json::Value RObject_to_JsonValue(Rcpp::List obj);
Json::Value RObject_to_JsonValue( Rcpp::RObject obj);
Json::Value RObject_to_JsonValue( Rcpp::List obj);
Json::Value MixedRObject_to_JsonValue( Rcpp::List obj);

bool isMixedRObject(Rcpp::RObject obj) const { return obj.inherits("mixed"); }

template<int RTYPE> Json::Value RObject_to_JsonValue(Rcpp::Matrix<RTYPE> obj)
{
Expand Down
Loading

0 comments on commit a3cd372

Please sign in to comment.