Perturbation Workflows
workflows.RmdOverview
This vignette covers the detailed features of
rebalancedNoise after the basic workflow. Every feature is
shown in both interfaces: the stateful R6 engine
(rn_setup() plus $ methods) and the functional
interface (rn_init() plus piped function calls). The basics
of both styles are explained in
vignette("getting-started", package = "rebalancedNoise").
Setup
The same simulated data is used throughout this vignette. Country probabilities are unequal so that some cells hold fewer than five records.
library(rebalancedNoise)
#> Welcome to rebalancedNoise 0.3.1.
library(data.table)
#>
#> Attaching package: 'data.table'
#> The following object is masked from 'package:base':
#>
#> %notin%
library(sdcHierarchies)
#> Loading required package: shinythemes
#> Package 'sdcHierarchies' 0.23.1 has been loaded.
set.seed(1)
N <- 300
countries <- c("AT", "DE", "NL", "SE", "FR", "IT")
industries <- c("Tech", "Mfg", "Retail", "Services")
country_probs <- c(0.25, 0.25, 0.20, 0.15, 0.10, 0.05)
dt <- data.table(
country = sample(countries, N, replace = TRUE, prob = country_probs),
industry = sample(industries, N, replace = TRUE),
turnover = runif(N, 1000, 10000),
workers = sample(10:500, N, replace = TRUE),
assets = runif(N, 10000, 100000),
direction = sample(c(1, -1), N, replace = TRUE),
noise_multiplier = sample(seq(0.75, 1.25, by = 0.01), N, replace = TRUE)
)
dims_detailed <- list(
country = hier_create("Total", nodes = countries),
industry = hier_create("Total", nodes = industries)
)
dims_country_only <- list(
country = hier_create("Total", nodes = countries)
)
dims_industry_only <- list(
industry = hier_create("Total", nodes = industries)
)One Rebalancing, Many Tables
Sensitive cell detection runs once during rebalancing and is reused by every following perturbation. Rebalancing is the expensive step, so the choice of the detailed hierarchy deserves attention. Publication tables can then use any coarser hierarchy and any numeric variable without further rebalancing.
The chunks below set up the two core objects that the rest of this
vignette builds on. Both objects rebalance turnover on the
detailed structure (country x industry) and create one country-level
table.
sdc <- rn_setup(data = dt, sensitive_params = list(n_threshold = 5))
#> ℹ Initialization started...
#> ✔ Initialization complete.
sdc$rebalance(dim_list = dims_detailed, num_var = "turnover")
#> ℹ Performing rebalancing on detailed table structure...
#> ✔ Rebalancing complete. Updated "direction_rebalanced" column.
sdc$perturb(dim_list = dims_country_only, variables = "turnover", name = "table_a")
#> ✔ Created new table "table_a" with variables `turnover`.
state_fun <- dt |>
rn_init(sensitive_params = list(n_threshold = 5)) |>
rn_rebalance(dim_list = dims_detailed, num_var = "turnover")
#> ℹ Initialization started...
#> ✔ Initialization complete.
#> ℹ Performing rebalancing on detailed table structure...
#> ✔ Rebalancing complete. Updated "direction_rebalanced" column.
t_a <- rn_perturb(state_fun, dim_list = dims_country_only, variables = "turnover")Additional tables reuse the same rebalanced state. In the R6 engine every table is stored under a name inside the object:
# A second table, different hierarchy, two variables in one call
sdc$perturb(
dim_list = dims_industry_only,
variables = c("turnover", "assets"),
name = "table_b"
)
#> ✔ Created new table "table_b" with variables `turnover` and `assets`.
res_b <- sdc$get_results("table_b")
head(res_b[, list(
industry, turnover, turnover_init, turnover_pert,
assets, assets_init, assets_pert
)])
#> industry turnover turnover_init turnover_pert assets assets_init
#> <char> <num> <num> <num> <num> <num>
#> 1: Total 1651113.7 1853460.8 1679243.9 15895256 17764751
#> 2: Tech 526319.4 571895.9 539107.1 4897189 5397934
#> 3: Mfg 323879.3 405202.8 333368.4 3231023 3207915
#> 4: Retail 411855.2 471077.7 417148.2 3691171 5013046
#> 5: Services 389059.7 405284.4 389620.2 4075872 4145856
#> assets_pert
#> <num>
#> 1: 16007592
#> 2: 5283449
#> 3: 3009194
#> 4: 3846979
#> 5: 3867971
# All stored tables at once, returned as a named list
names(sdc$get_results())
#> [1] "table_a" "table_b"In the functional interface you keep the objects yourself, for
example in a named list. The rn_rebalanced object
state_fun can be perturbed as often as needed:
t_b <- rn_perturb(
state_fun,
dim_list = dims_industry_only,
variables = c("turnover", "assets")
)
tables <- list(table_a = rn_format(t_a), table_b = rn_format(t_b))
names(tables)
#> [1] "table_a" "table_b"There is no automatic caching in the functional interface. A repeated
call to rn_perturb() recomputes the table. In the R6 engine
a repeated call with the same name and the same parameters is recognized
and skipped.
Sensitive Cell Detection
The results carry the sensitivity flags that the rebalancing step
derived from sensitive_params. Cells with few contributing
records are flagged, and their noise direction stays fixed while the
algorithm optimizes all other directions.
res_a <- sdc$get_results("table_a")
res_a[, list(country, n_obs, is_internal, is_sens_turnover)][order(n_obs)]
#> country n_obs is_internal is_sens_turnover
#> <char> <num> <lgcl> <lgcl>
#> 1: IT 10 TRUE FALSE
#> 2: FR 32 TRUE FALSE
#> 3: SE 41 TRUE FALSE
#> 4: NL 57 TRUE FALSE
#> 5: DE 72 TRUE FALSE
#> 6: AT 88 TRUE FALSE
#> 7: Total 300 FALSE FALSEThe n_obs column counts the records contributing to each
cell. The is_internal column marks base cells, and
is_sens_turnover reports the sensitivity decision for that
variable. In long format the same information appears in the
standardized is_sensitive column.
Table Naming and Caching in the R6 Engine
Every R6 result table is identified by its name and by a hash of the hierarchy, the SDC parameters, and the rounding flag. The hash enables two protections.
Repeating an identical call recomputes nothing:
sdc$list_tables()
#> table_name dimensions variables
#> <char> <char> <char>
#> 1: table_a country turnover
#> 2: table_b industry turnover, assets
# Same name, same hash: the calculation is skipped
sdc$perturb(dim_list = dims_country_only, variables = "turnover", name = "table_a")
#> ℹ Table "table_a" for `turnover` already calculated. Skipping.Reusing a name for a different table structure raises an error instead of silently overwriting a stored result:
# Different hierarchy under an existing name: error
try(sdc$perturb(dim_list = dims_industry_only, variables = "turnover", name = "table_a"))
#> Error in sdc$perturb(dim_list = dims_industry_only, variables = "turnover", :
#> ✖ Table name "table_a" is already in use.
#> ℹ Use a different table name for this hierarchy definition.
# Different structures need different names
sdc$perturb(dim_list = dims_industry_only, variables = "turnover", name = "table_c")
#> ✔ Created new table "table_c" with variables `turnover`.
sdc$list_tables()
#> table_name dimensions variables
#> <char> <char> <char>
#> 1: table_a country turnover
#> 2: table_b industry turnover, assets
#> 3: table_c industry turnoverThe functional interface has no table registry. You store each
rn_perturbed object under a name of your own choosing, so
collisions cannot occur.
Rounding to Whole Numbers
Some publications require integer values. The round
argument, available in sdc$perturb() and
rn_perturb(), rounds the perturbed values on the microdata
level with R’s round() before the tables are computed.
Because rounding happens before tabulation, every published value is a
whole number.
sdc$perturb(
dim_list = dims_country_only, variables = "turnover",
name = "table_rounded", round = TRUE
)
#> ✔ Created new table "table_rounded" with variables `turnover`.
res_rounded <- sdc$get_results("table_rounded")
head(res_rounded[, list(country, turnover, turnover_pert)])
#> country turnover turnover_pert
#> <char> <num> <num>
#> 1: Total 1651113.7 1679249
#> 2: AT 544080.2 548800
#> 3: DE 335581.4 334305
#> 4: NL 313695.9 316612
#> 5: SE 216463.0 215232
#> 6: FR 197918.8 187070
all(res_rounded$turnover_pert == round(res_rounded$turnover_pert))
#> [1] TRUE
t_rounded <- rn_perturb(
state_fun,
dim_list = dims_country_only, variables = "turnover", round = TRUE
)
res_rounded_fun <- rn_format(t_rounded)
all(res_rounded_fun$turnover_pert == round(res_rounded_fun$turnover_pert))
#> [1] TRUEFour points to keep in mind:
- Only the perturbed columns (
[var]_initand[var]_pert) are rounded. The original values in[var]stay unchanged. - Rounding adds up to 0.5 per record, so cell totals can shift a little.
- R’s
round()rounds halves toward the nearest even number, so 2.5 becomes- Exact halves rarely occur in perturbed data.
- The round flag is part of the R6 table hash. Reusing a table name
with a different
roundvalue raises a collision error.
Rebalancing on Linear Combinations
A single variable is not always the right basis for direction optimization. If turnover, workers, and assets matter equally, rebalancing on only one of them protects the others less. In that case, build a weighted linear combination of standardized variables and use it as the rebalancing target. The algorithm then protects all variables together.
Missing values need attention here. A missing variable should neither add to nor subtract from the combined score, and dropping records with missing values wastes information. The recommended solution is to replace missing values with 0 before scaling. After centering, 0 equals the mean, so missing values contribute neutrally.
# Work on a copy so that dt stays complete
dt2 <- copy(dt)
dt2[sample(1:N, 20), assets := NA]
sum(is.na(dt2$assets))
#> [1] 20
# Standardize turnover and workers, replace NA with 0 before scaling assets
dt2[, turnover_std := scale(turnover)]
dt2[, workers_std := scale(workers)]
dt2[, assets_std := scale(ifelse(is.na(assets), 0, assets))]
# Weighted combination: 30 percent turnover, 30 percent workers, 40 percent assets
dt2[, rebal_combined := 0.3 * turnover_std + 0.3 * workers_std + 0.4 * assets_std]
sum(is.na(dt2$rebal_combined))
#> [1] 0The combined variable is used only in the rebalancing step. The perturbation still operates on the original variables.
sdc_lc <- rn_setup(data = dt2, sensitive_params = list(n_threshold = 5))
#> ℹ Initialization started...
#> ✔ Initialization complete.
sdc_lc$rebalance(dim_list = dims_detailed, num_var = "rebal_combined")
#> ℹ Performing rebalancing on detailed table structure...
#> ✔ Rebalancing complete. Updated "direction_rebalanced" column.
sdc_lc$perturb(
dim_list = dims_country_only,
variables = c("turnover", "workers", "assets"),
name = "multi_var_table"
)
#> ✔ Created new table "multi_var_table" with variables `turnover`, `workers`, and `assets`.
res_lc <- sdc_lc$get_results("multi_var_table")
head(res_lc[, list(country, turnover, turnover_pert, workers, workers_pert)])
#> country turnover turnover_pert workers workers_pert
#> <char> <num> <num> <num> <num>
#> 1: Total 1651113.7 1720381.2 73686 75565.00
#> 2: AT 544080.2 586227.6 22973 26204.30
#> 3: DE 335581.4 329979.7 17957 13920.51
#> 4: NL 313695.9 295206.6 12488 11133.28
#> 5: SE 216463.0 208358.1 8834 9326.71
#> 6: FR 197918.8 223378.2 8502 9135.63
res_lc_fun <- dt2 |>
rn_init(sensitive_params = list(n_threshold = 5)) |>
rn_rebalance(dim_list = dims_detailed, num_var = "rebal_combined") |>
rn_perturb(
dim_list = dims_country_only,
variables = c("turnover", "workers", "assets")
)
#> ℹ Initialization started...
#> ✔ Initialization complete.
#> ℹ Performing rebalancing on detailed table structure...
#> ✔ Rebalancing complete. Updated "direction_rebalanced" column.
head(rn_format(res_lc_fun)[, list(country, turnover, turnover_pert)])
#> country turnover turnover_pert
#> <char> <num> <num>
#> 1: Total 1651113.7 1720381.2
#> 2: AT 544080.2 586227.6
#> 3: DE 335581.4 329979.7
#> 4: NL 313695.9 295206.6
#> 5: SE 216463.0 208358.1
#> 6: FR 197918.8 223378.2Evaluating Results
Before releasing tables, inspect the perturbation impact. The R6
method $summarize() and the function
rn_summarize() report the same statistics.
sdc$summarize(table = "table_a", target_vars = "turnover")
#>
#> ── EZS Perturbation Summary: "table_a" ─────────────────────────────────────────
#>
#> ── Variable: "turnover" ──
#>
#> ── OVERALL
#> ℹ nrCells: 7 | MAPE (Initial): 25.181% | MAPE (Final): 12.57% | Noise Reduction: 50.08%
#> Percentiles - Relative (%) Initial:
#> -15.98 | -14.948 | -10.819 | 6.739 | 16.024 | 26.364 | 64.789 | 75.403 | 78.057
#> Percentiles - Relative (%) Final:
#> -5.48 | -5.185 | -4.007 | -0.475 | 0.867 | 1.316 | 55.151 | 73.476 | 78.057
#> Percentiles - Absolute (Units) Final:
#> -10846.157 | -10271.829 | -7974.517 | -1254.123 | 2913.184 | 16422.458 |
#> 32138.769 | 33513.132 | 33856.722
#>
#> ── Summary Statistics
#> • Sensitivity Rate: 0% (0/7 cells)
#> • Internal Cells: 6 (85.7%)
#> • Noise Reduction: 50.08%
rn_summarize(t_a)
#>
#> ── EZS Perturbation Summary: "rn_perturbed" ────────────────────────────────────
#>
#> ── Variable: "turnover" ──
#>
#> ── OVERALL
#> ℹ nrCells: 7 | MAPE (Initial): 25.181% | MAPE (Final): 12.57% | Noise Reduction: 50.08%
#> Percentiles - Relative (%) Initial:
#> -15.98 | -14.948 | -10.819 | 6.739 | 16.024 | 26.364 | 64.789 | 75.403 | 78.057
#> Percentiles - Relative (%) Final:
#> -5.48 | -5.185 | -4.007 | -0.475 | 0.867 | 1.316 | 55.151 | 73.476 | 78.057
#> Percentiles - Absolute (Units) Final:
#> -10846.157 | -10271.829 | -7974.517 | -1254.123 | 2913.184 | 16422.458 |
#> 32138.769 | 33513.132 | 33856.722
#>
#> ── Summary Statistics
#> • Sensitivity Rate: 0% (0/7 cells)
#> • Internal Cells: 6 (85.7%)
#> • Noise Reduction: 50.08%How to read the report:
- MAPE is the mean absolute percentage error between original and perturbed values. MAPE (Initial) refers to the noise with the original random directions, before rebalancing. MAPE (Final) refers to the published values. A lower final value shows the effect of the rebalancing.
- Noise Reduction states by how many percent the final MAPE is below the initial MAPE.
- Percentiles list nine levels of the deviation distribution: 0, 1, 5, 25, 50, 75, 95, 99, and 100 percent. The first two rows are relative deviations in percent, initial and final. The third row gives absolute deviations in the original unit of the variable. Check the 1 and 99 percent levels for extreme deviations in single cells.
- Sensitivity Rate states which share of cells was flagged as sensitive. These cells received fixed noise for disclosure protection.
Both functions return the statistics invisibly as a named list, so
you can store and process them. With a single target_vars
entry the list contains the elements overall and
meta, with one element per variable otherwise:
result_a <- sdc$summarize(table = "table_a", target_vars = "turnover")
#>
#> ── EZS Perturbation Summary: "table_a" ─────────────────────────────────────────
#>
#> ── Variable: "turnover" ──
#>
#> ── OVERALL
#> ℹ nrCells: 7 | MAPE (Initial): 25.181% | MAPE (Final): 12.57% | Noise Reduction: 50.08%
#> Percentiles - Relative (%) Initial:
#> -15.98 | -14.948 | -10.819 | 6.739 | 16.024 | 26.364 | 64.789 | 75.403 | 78.057
#> Percentiles - Relative (%) Final:
#> -5.48 | -5.185 | -4.007 | -0.475 | 0.867 | 1.316 | 55.151 | 73.476 | 78.057
#> Percentiles - Absolute (Units) Final:
#> -10846.157 | -10271.829 | -7974.517 | -1254.123 | 2913.184 | 16422.458 |
#> 32138.769 | 33513.132 | 33856.722
#>
#> ── Summary Statistics
#> • Sensitivity Rate: 0% (0/7 cells)
#> • Internal Cells: 6 (85.7%)
#> • Noise Reduction: 50.08%
names(result_a)
#> [1] "overall" "meta"
names(result_a$overall)
#> [1] "count" "mape_init" "mape_final" "q_rel_init" "q_rel_final"
#> [6] "q_abs_final"
names(result_a$meta)
#> [1] "sensitivity_rate" "n_total_cells" "n_sensitive_cells"
#> [4] "n_internal_cells" "noise_reduction_pct"
result_b <- sdc$summarize(table = "table_b", target_vars = c("turnover", "assets"))
#>
#> ── EZS Perturbation Summary: "table_b" ─────────────────────────────────────────
#>
#> ── Variable: "turnover" ──
#>
#> ── OVERALL
#> ℹ nrCells: 5 | MAPE (Initial): 12.914% | MAPE (Final): 1.699% | Noise Reduction: 86.85%
#> Percentiles - Relative (%) Initial:
#> 4.17 | 4.35 | 5.068 | 8.659 | 12.255 | 14.379 | 22.963 | 24.68 | 25.109
#> Percentiles - Relative (%) Final:
#> 0.144 | 0.19 | 0.372 | 1.285 | 1.704 | 2.43 | 2.83 | 2.91 | 2.93
#> Percentiles - Absolute (Units) Final:
#> 560.488 | 749.785 | 1506.976 | 5292.931 | 9489.092 | 12787.7 | 25061.708 |
#> 27516.509 | 28130.21
#>
#> ── Summary Statistics
#> • Sensitivity Rate: 0% (0/5 cells)
#> • Internal Cells: 4 (80%)
#> • Noise Reduction: 86.85%
#>
#> ── EZS Perturbation Summary: "table_b" ─────────────────────────────────────────
#>
#> ── Variable: "assets" ──
#>
#> ── OVERALL
#> ℹ nrCells: 5 | MAPE (Initial): 12.046% | MAPE (Final): 4.956% | Noise Reduction: 58.85%
#> Percentiles - Relative (%) Initial:
#> -0.715 | -0.618 | -0.229 | 1.717 | 10.225 | 11.761 | 31.002 | 34.85 | 35.812
#> Percentiles - Relative (%) Final:
#> -6.866 | -6.795 | -6.513 | -5.101 | 0.707 | 4.221 | 7.154 | 7.74 | 7.887
#> Percentiles - Absolute (Units) Final:
#> -221829.755 | -221272.605 | -219044.006 | -207901.009 | 112336.321 | 155807.158
#> | 340169.373 | 377041.816 | 386259.927
#>
#> ── Summary Statistics
#> • Sensitivity Rate: 0% (0/5 cells)
#> • Internal Cells: 4 (80%)
#> • Noise Reduction: 58.85%
names(result_b)
#> [1] "turnover" "assets"Export and Import
Rebalancing large datasets takes time. The rebalanced state can be stored and loaded again so that later sessions only pay for the perturbations.
Using the R6 engine
$export() writes the rebalanced microdata, the SDC
parameters, the rebalancing status, and, optionally, the cached result
tables. rn_setup() recognizes an export object or the path
to an RDS file and restores the full state.
export_file <- file.path(tempdir(), "rebalanced_data.rds")
sdc$export(file = export_file)
#> ✔ Exported to /tmp/RtmpF4ntCf/rebalanced_data.rds
# In a later session: load and continue without rebalancing again
sdc2 <- rn_setup(export_file)
#> ℹ Initialization started...
#> ✔ Initialization complete.
sdc2$list_tables()
#> table_name dimensions variables
#> <char> <char> <char>
#> 1: table_a country turnover
#> 2: table_b industry turnover, assets
#> 3: table_c industry turnover
#> 4: table_rounded country turnover
# New tables from the imported state, no recomputation of the directions
sdc2$perturb(dim_list = dims_industry_only, variables = "workers", name = "table_d")
#> ✔ Created new table "table_d" with variables `workers`.
sdc2$list_tables()
#> table_name dimensions variables
#> <char> <char> <char>
#> 1: table_a country turnover
#> 2: table_b industry turnover, assets
#> 3: table_c industry turnover
#> 4: table_rounded country turnover
#> 5: table_d industry workersPass include_results = FALSE to export only the
rebalanced microdata, which produces a smaller file. On import,
rn_setup() validates the object and rejects files with
missing columns or invalid direction values.
Using the functional interface
An rn_rebalanced object contains everything that is
needed to perturb further tables, so a plain saveRDS() is
enough:
state_file <- file.path(tempdir(), "rebalanced_state.rds")
saveRDS(state_fun, state_file)
state_re <- readRDS(state_file)
t_re <- rn_perturb(state_re, dim_list = dims_industry_only, variables = "workers")
head(rn_format(t_re))
#> industry n_obs is_internal workers workers_init workers_pert is_sens_workers
#> <char> <num> <lgcl> <num> <num> <num> <lgcl>
#> 1: Total 300 FALSE 73686 83914.68 76617.98 FALSE
#> 2: Tech 91 TRUE 20800 25302.16 21261.32 FALSE
#> 3: Mfg 62 TRUE 15925 18689.39 16782.93 FALSE
#> 4: Retail 71 TRUE 16140 17937.37 16788.53 FALSE
#> 5: Services 76 TRUE 20821 21985.76 21785.20 FALSEThe R6 export object can also be fed directly into the functional interface, which makes the two persistence formats interchangeable:
rn_init(sdc$export(include_results = FALSE))
#> ℹ Initialization started...
#> ✔ Initialization complete.
#> <rn_initialized>
#> microdata: 300 records x 10 columns
#> sensitive_params: n_threshold = 5
#> n_threads: 3
#> rebalanced: TRUEUtility Methods
Microdata with Directions
sdc$get_microdata() returns the microdata including
direction, direction_rebalanced, and
noise_multiplier. Set include_record_id = TRUE
to add the internal record identifier that the rebalancing uses for
tracking. The functional counterpart is as.data.table()
applied to an rn_rebalanced object.
head(sdc$get_microdata())
#> Key: <country, industry>
#> country industry turnover workers assets direction noise_multiplier strID
#> <char> <char> <num> <int> <num> <num> <num> <char>
#> 1: AT Mfg 6713.882 434 13327.64 1 0.78 102
#> 2: AT Mfg 8189.331 374 24854.73 1 1.17 102
#> 3: AT Mfg 8452.675 63 71095.67 -1 1.00 102
#> 4: AT Mfg 7932.028 36 56080.61 1 1.20 102
#> 5: AT Mfg 2082.540 385 16656.75 1 0.76 102
#> 6: AT Mfg 6432.361 137 57557.15 -1 0.89 102
#> direction_rebalanced
#> <int>
#> 1: -1
#> 2: 1
#> 3: -1
#> 4: -1
#> 5: -1
#> 6: 1
head(sdc$get_microdata(include_record_id = TRUE))
#> Key: <country, industry>
#> country industry turnover workers assets direction noise_multiplier
#> <char> <char> <num> <int> <num> <num> <num>
#> 1: AT Mfg 6713.882 434 13327.64 1 0.78
#> 2: AT Mfg 8189.331 374 24854.73 1 1.17
#> 3: AT Mfg 8452.675 63 71095.67 -1 1.00
#> 4: AT Mfg 7932.028 36 56080.61 1 1.20
#> 5: AT Mfg 2082.540 385 16656.75 1 0.76
#> 6: AT Mfg 6432.361 137 57557.15 -1 0.89
#> record_id strID direction_rebalanced
#> <int> <char> <int>
#> 1: 28 102 -1
#> 2: 62 102 1
#> 3: 73 102 -1
#> 4: 81 102 -1
#> 5: 113 102 -1
#> 6: 122 102 1
# Functional equivalent
head(as.data.table(state_fun))
#> Key: <country, industry>
#> country industry turnover workers assets direction noise_multiplier strID
#> <char> <char> <num> <int> <num> <num> <num> <char>
#> 1: AT Mfg 6713.882 434 13327.64 1 0.78 102
#> 2: AT Mfg 8189.331 374 24854.73 1 1.17 102
#> 3: AT Mfg 8452.675 63 71095.67 -1 1.00 102
#> 4: AT Mfg 7932.028 36 56080.61 1 1.20 102
#> 5: AT Mfg 2082.540 385 16656.75 1 0.76 102
#> 6: AT Mfg 6432.361 137 57557.15 -1 0.89 102
#> direction_rebalanced
#> <int>
#> 1: -1
#> 2: 1
#> 3: -1
#> 4: -1
#> 5: -1
#> 6: 1Listing Stored Tables
sdc$list_tables() shows the name, the dimensions, and
the variables of every table stored in the R6 object.
sdc$list_tables()
#> table_name dimensions variables
#> <char> <char> <char>
#> 1: table_a country turnover
#> 2: table_b industry turnover, assets
#> 3: table_c industry turnover
#> 4: table_rounded country turnoverStarting Over with reset()
Rebalancing can run once per R6 object and once per imported export
state. A second call raises an error. $reset() returns the
object to its initialized state: it removes the rebalancing artifacts
(direction_rebalanced, strID, and
is_sens_* columns), restores the original row order, and
clears all cached tables. Optionally it accepts new SDC parameters.
sdc_rst <- rn_setup(dt, sensitive_params = list(n_threshold = 5))
#> ℹ Initialization started...
#> ✔ Initialization complete.
sdc_rst$rebalance(dim_list = dims_detailed, num_var = "turnover")
#> ℹ Performing rebalancing on detailed table structure...
#> ✔ Rebalancing complete. Updated "direction_rebalanced" column.
sdc_rst$perturb(dim_list = dims_detailed, variables = "turnover", name = "t1")
#> ✔ Created new table "t1" with variables `turnover`.
# A second rebalance() on the same object is rejected
try(sdc_rst$rebalance(dim_list = dims_country_only, num_var = "turnover"))
#> Error in sdc_rst$rebalance(dim_list = dims_country_only, num_var = "turnover") :
#> ✖ Rebalancing has already been performed on this object.
#> ℹ Call `reset()` first to start over with a different structure or variable.
# Reset, optionally with new parameters, then start again
sdc_rst$reset(sensitive_params = list(n_threshold = 3))
#> ✔ Reset complete. Call `rebalance()` to start over.
sdc_rst$rebalance(dim_list = dims_country_only, num_var = "turnover")
#> ℹ Performing rebalancing on detailed table structure...
#> ✔ Rebalancing complete. Updated "direction_rebalanced" column.
sdc_rst$perturb(dim_list = dims_country_only, variables = "turnover", name = "t1")
#> ✔ Created new table "t1" with variables `turnover`.
sdc_rst$list_tables()
#> table_name dimensions variables
#> <char> <char> <char>
#> 1: t1 country turnoverAfter $reset() the object behaves like a freshly
initialized one. The functional interface needs no equivalent:
rn_rebalance() is stateless and can be applied to an
rn_rebalanced object again at any time.
Performance and Parallelization
The sensitivity check during rebalancing is the most time-consuming
step and runs in C++ with OpenMP. It is used in
$rebalance() and rn_rebalance(), in
$perturb() with strID, and in
rn_perturb() when the sensitivity flags of a result table
are computed. The perturbation step itself runs single-threaded.
The number of threads is resolved once in rn_setup() or
rn_init() and then stays fixed for the object or state.
Besides the n_threads argument there, the value can also be
set globally with options(rn_threads = 4) or via the
environment variable rn_threads, for example with
Sys.setenv(rn_threads = "4"). The argument takes precedence
over the option, and the option takes precedence over the environment
variable. Without any of these settings, the default is the number of
detected cores minus one, with a minimum of 1. This setting applies to
both interfaces.
Summary
Every feature of rebalancedNoise is reachable through
both interfaces, and the interfaces can be combined at any stage of a
workflow. The R6 engine adds naming, caching, and state management for
interactive table production. The functional interface provides
explicit, composable steps for scripts and reproducible pipelines.
Choose by working style, not by capability.