The SynthesizerPlus copula uses a single latent coordinate per
categorical variable. This is natural for ordinal responses. For
unordered categories the copula places them on that coordinate in an
order estimated from the data (by their association with the other
columns), not in the order of their labels, but one coordinate cannot
represent every pattern of association a nominal variable can have.
Since version 0.4.0, nominal_target instead fits a
conditional multinomial logistic model of one unordered
response. The other columns are generated using the usual fitted copula,
then the response is drawn from predicted category probabilities. The
model is
\[P(Y = k \mid x) = \frac{\exp\{\eta_k(x)\}}{\sum_l \exp\{\eta_l(x)\}}, \qquad \eta_k(x) = \beta_{k0} + \sum_j f_{kj}(x_j),\]
with \(\eta\) of the first category
fixed at zero. For each numeric predictor with at least five distinct
values, \(f_{kj}\) is a natural cubic
spline with nominal_spline_df degrees of freedom (default
3, knots at quantiles of the training values), so the category
probabilities can depend nonlinearly and non-monotonically on it; other
predictors enter linearly or as category dummies, with a missingness
indicator for numeric predictors. The model is fitted by
nnet::multinom() with weight decay
nominal_decay. The purely linear logit is fitted as well,
and the model with the smaller AIC is used; the comparison is stored in
fit$pooled$nominal$selection and the choice in
fit$pooled$nominal$spline_df. Splines are thus used when
the extra parameters are supported by the data; with (nearly) separated
classes, as in iris, the linear logit is better determined
and is kept. Setting nominal_spline_df = 0 fits only the
linear logit.
library(SynthesizerPlus)
set.seed(204)
input <- data.frame(
age = rnorm(500, 35, 10),
income = rgamma(500, shape = 4, scale = 1000)
)
logits <- cbind(0, (input$age - 35)/15, log(input$income/4000))
p <- exp(logits - apply(logits, 1L, max))
p <- p/rowSums(p)
classes <- c("bus", "car", "rail")
input$transport <- factor(classes[apply(p, 1, function(q)
sample.int(3, 1, prob = q))])
fit <- fit_synthesizer(input, nominal_target = "transport", seed = 11)
synthetic <- generate(fit, n = 500, seed = 12)
prop.table(table(synthetic$transport))
#>
#> bus car rail
#> 0.344 0.346 0.310
fit$pooled$nominal$selection # AIC of the spline and linear models
#> spline_df converged aic
#> 1 3 TRUE 1034.474
#> 2 0 TRUE 1033.022Nominal category labels have no ordinal interpretation. Reordering or renaming those labels represents the same statistical outcome categories; the multinomial model is approximately equivariant to such relabelling (up to numerical optimisation and Monte Carlo variability). Reproducibility of the factor-level metadata is preserved by the generated table.
At least 40 observed categorical responses and two to twenty distinct
categories are required. Sparse strata fall back to the conventional
copula model, including the nominal target;
fit_synthesizer() warns when this happens and lists the
strata in fit$strata$conditional_fallback. The multinomial
model has additive spline effects of the numeric predictors and no
interactions, so class boundaries that depend on interactions between
predictors remain a limitation. Fitted model objects include information
about the training data and should not be publicly released as if
privacy-safe.