| Title: | Harmonize Confidential Firm-Level Microdata into a Common Format |
|---|---|
| Description: | Harmonizes confidential firm-level microdata held at national statistical institutes into one common structure, so that the same analysis code runs unchanged in every country. Metadata tables describe each institute's raw files and map their variables to the common ones; the functions read the raw files, apply the mapping (revaluing, recoding, reclassifying, redefining and remapping), make industry, product and regional classifications consistent over time, switch between enterprise, firm and plant identifiers, and add capital stocks computed with the perpetual inventory method. Quality checks on the harmonized data report duplicate keys, type conversion losses, breaks in yearly statistics and jumps in missing values, and outputs pass statistical disclosure control before they leave the institute. Builds on the analysis tools in 'mditools'. Capital stocks via the perpetual inventory method follow OECD (2009) <doi:10.1787/9789264068476-en>; disclosure control applies the minimum-frequency and dominance rules with secondary suppression described in Hundepool et al. (2012) <doi:10.1002/9781118348239>. |
| Authors: | Daniele Aglio [aut], Eric Bartelsman [aut], Julián Díaz-Acosta [aut], Mirja Hälbig [aut], Marco Miorandi [aut], Johanna Weiss [aut, cre], José Alberto Ferreira [ctb], Elena Sponga [ctb], Reetuparna Vishwanath [ctb], Chengzi Yi [ctb], Alessandro Zona Mattioli [ctb], European Union [fnd] (Technical Support Instrument (TSI), project ProdTool, TSI-2025-MULTIMSPROD-IBA, TSI Project Number 101261159) |
| Maintainer: | Johanna Weiss <[email protected]> |
| License: | GPL-3 |
| Version: | 0.1.0 |
| Built: | 2026-11-05 01:33:57 UTC |
| Source: | https://github.com/cran/mdiharmonize |
Hierarchy files (MD_nace_hier.csv, MD_nuts_hier.csv, MD_kic_hier.csv)
name their columns h_0, h_1, ... — the number is the level index, finest
first. Nothing about what a level means is encoded in that name;
MD_hier_levels.csv declares it. hier_col() answers "which column holds
NUTS 3", so module code asks for the level it wants instead of hard-coding
h_1.
hier_col(hier, hlevels, level_name = NULL, md_varname = NULL)hier_col(hier, hlevels, level_name = NULL, md_varname = NULL)
hier |
Character, the hierarchy's name as it appears in
|
hlevels |
The declaration table, |
level_name |
Character, the level's stable short name — |
md_varname |
Character, an MD variable whose codes sit at the wanted
level — |
One column name, e.g. "h_1".
hier_levels() for a file's columns in level order.
# in a run, MD_hier_levels is the loaded metadata file levels <- data.table::data.table( hier = "MD_nuts_hier", level = c("h_0", "h_1", "h_2"), level_name = c("lau", "nuts3", "nuts2"), md_varname = c("lau", "nuts3", "nuts2")) hier_col("MD_nuts_hier", level_name = "nuts2", hlevels = levels) # "h_2" hier_col("MD_nuts_hier", md_varname = "nuts3", hlevels = levels) # "h_1"# in a run, MD_hier_levels is the loaded metadata file levels <- data.table::data.table( hier = "MD_nuts_hier", level = c("h_0", "h_1", "h_2"), level_name = c("lau", "nuts3", "nuts2"), md_varname = c("lau", "nuts3", "nuts2")) hier_col("MD_nuts_hier", level_name = "nuts2", hlevels = levels) # "h_2" hier_col("MD_nuts_hier", md_varname = "nuts3", hlevels = levels) # "h_1"
Writes (or appends) a message to a text file, for warnings, diagnostics and
other information a run should keep, instead of printing to the console.
WarningsDescription.txt in the output folder is the run's default log;
the checkers and the post-harmonization report write their own files.
log_warnings( message, path, append = TRUE, file_name = "WarningsDescription.txt" )log_warnings( message, path, append = TRUE, file_name = "WarningsDescription.txt" )
message |
character, the message; a vector is written one element per line. |
path |
character(1), the folder the file is in, ending in |
append |
logical(1), add to the file ( |
file_name |
character(1), the file's name. |
NULL, invisibly.
dir <- paste0(tempfile("log_"), "/") dir.create(dir) log_warnings("first line", dir, append = FALSE) log_warnings("second line", dir) readLines(paste0(dir, "WarningsDescription.txt"))dir <- paste0(tempfile("log_"), "/") dir.create(dir) log_warnings("first line", dir, append = FALSE) log_warnings("second line", dir) readLines(paste0(dir, "WarningsDescription.txt"))
Gives each SBS firm-year its NACE 2-digit industry from BR (by default the
time-consistent MDnace, see nace_var), starts each
firm's capital stock from its industry's capital per employee (EU KLEMS),
accumulates it with mdi_pim_capital() from tangible investment
(ni_tan) and the industry depreciation rate, and returns SBS with a
K_pim column added.
mdi_build_pim_capital( sbs, br, capital_stocks, nace_hier, id_sbs, id_br = id_sbs, conctab = NULL, br_varnames = NULL, emp_var = "persons", nace_var = "MDnace" )mdi_build_pim_capital( sbs, br, capital_stocks, nace_hier, id_sbs, id_br = id_sbs, conctab = NULL, br_varnames = NULL, emp_var = "persons", nace_var = "MDnace" )
sbs |
A |
br |
A |
capital_stocks |
A |
nace_hier |
A |
id_sbs, id_br
|
Character, the firm identifier column of SBS and of BR
(e.g. |
conctab |
A |
br_varnames |
A |
emp_var |
Character, SBS's employment column used to scale the
initial capital. Default |
nace_var |
Character, BR's industry column. Default |
sbs with a K_pim column added, every row kept: a firm-year
with no BR match (so no industry) gets K_pim = NA. When BR's NACE
codes are all 3-digit, industries are matched at h_1 instead of h_0.
A K_pim already in sbs (from an earlier run) is replaced. sbs
itself is not modified.
yrs <- 2010:2012 sbs <- data.table::data.table(firmid = rep(c("f1", "f2"), each = 3), year = rep(yrs, 2), persons = 10L, ni_tan = 1:6) br <- data.table::data.table(firmid = rep(c("f1", "f2"), each = 3), year = rep(yrs, 2), MDnace = rep(c("0111", "1011"), each = 3)) capital_stocks <- data.table::data.table( year = rep(yrs, 4), nace = rep(c("A01", "A01", "C10", "C10"), each = 3), cc = "XX", variable = rep(c("K_GFCF_per_emp", "d_GFCF"), each = 3, times = 2), value = rep(c(100, 0.1, 200, 0.2), each = 3) ) nace_hier <- data.table::data.table(h_0 = c("0111", "1011"), h_1 = c("011", "101"), h_2 = c("01", "10")) mdi_build_pim_capital(sbs, br, capital_stocks, nace_hier, id_sbs = "firmid")yrs <- 2010:2012 sbs <- data.table::data.table(firmid = rep(c("f1", "f2"), each = 3), year = rep(yrs, 2), persons = 10L, ni_tan = 1:6) br <- data.table::data.table(firmid = rep(c("f1", "f2"), each = 3), year = rep(yrs, 2), MDnace = rep(c("0111", "1011"), each = 3)) capital_stocks <- data.table::data.table( year = rep(yrs, 4), nace = rep(c("A01", "A01", "C10", "C10"), each = 3), cc = "XX", variable = rep(c("K_GFCF_per_emp", "d_GFCF"), each = 3, times = 2), value = rep(c(100, 0.1, 200, 0.2), each = 3) ) nace_hier <- data.table::data.table(h_0 = c("0111", "1011"), h_1 = c("011", "101"), h_2 = c("01", "10")) mdi_build_pim_capital(sbs, br, capital_stocks, nace_hier, id_sbs = "firmid")
The post-harmonization quality checks for one MD dataset: whether its key
variables identify rows uniquely, whether every row has a year, whether
each variable has the class (and, for dates, the format) md_catalogue
declares, which dates the data actually covers, and whether yearly
statistics (p25, p50, p75, mean, sd) of its numeric variables change by
more than threshold from one year to the next, a possible break in the
series, and whether the share of NA values of any variable changes by
more than na_threshold from one year to the next or is 100% in a year
(the statistics only see the non-NA values, so a loss of coverage
doesn't show in them).
It checks the data against the metadata, assuming the metadata itself is well-formed; that is checked separately, on the metadata files alone (MDI's metadata checker).
mdi_check_harmonized( dt, dataset, md_catalogue, country, break_vars = NULL, threshold = 0.25, na_threshold = 0.1 )mdi_check_harmonized( dt, dataset, md_catalogue, country, break_vars = NULL, threshold = 0.25, na_threshold = 0.1 )
dt |
data.table, the harmonized dataset. |
dataset |
character(1), its MD dataset name, e.g. |
md_catalogue |
data.table, the MD catalogue: |
country |
character(1), the country code; |
break_vars |
character or |
threshold |
numeric(1), the relative year-on-year change above which a
statistic is flagged (0.25 = 25%; on real firm-level data, 10% flags
about a third of all year-on-year changes). From a previous value of 0 there is
no relative change: p25, p50 and p75 aren't flagged then (they jump
between 0 and small values for variables with many zeros), a mean or sd
that leaves 0 is, with |
na_threshold |
numeric(1), the year-on-year change in a variable's
share of |
A list:
one row: key variables, whether and how many rows are duplicated.
one row: whether there is a year column, and how many rows have no year.
expected and actual class of each non-date variable.
expected and actual class and format of each date variable.
detected format, first and last date, and expected range of each date variable.
the flagged statistics: dataset, variable, stat, year, growth, from_zero.
the flagged years: dataset, variable, year, na_share, change (from the year before), all_na.
named list, per numeric variable flagged by either check, of its yearly statistics (input to mdi_plot_breaks()).
named list, per variable flagged by either check, of its yearly share of NA values: year, na_share (input to mdi_plot_na_share()).
A table is NULL when no variable applies (e.g. breaks without a year
column or without any break). Key variables are left out of the NA
check.
dt <- data.table::data.table(firmid = c("a", "a", "b"), year = c(2020, 2020, 2021), emp = c(10, 12, 20)) catalogue <- data.table::data.table( MD_dataset = "SBS", MD_varname = c("firmid", "year", "emp"), is_key = c(1L, 1L, 0L), class = c("character", "numeric", "numeric"), domain = "", FI = "2020 - 2021") res <- mdi_check_harmonized(dt, "SBS", catalogue, "FI") res$duplicates res$breaksdt <- data.table::data.table(firmid = c("a", "a", "b"), year = c(2020, 2020, 2021), emp = c(10, 12, 20)) catalogue <- data.table::data.table( MD_dataset = "SBS", MD_varname = c("firmid", "year", "emp"), is_key = c(1L, 1L, 0L), class = c("character", "numeric", "numeric"), domain = "", FI = "2020 - 2021") res <- mdi_check_harmonized(dt, "SBS", catalogue, "FI") res$duplicates res$breaks
This tool classifies concordance edges into type 1:1, 1:m, m:1, or m:m. Optionally, the tool also creates numbered 'components' of the m:m edges. The input is a concordance data.table, with left and right columns and the function returns a data.table, possibly merged with the initial table, with conctype (and optional compnum).
mdi_classify_conc( DTconc, left = "left", right = "right", mm_groups = TRUE, mergeconc = FALSE )mdi_classify_conc( DTconc, left = "left", right = "right", mm_groups = TRUE, mergeconc = FALSE )
DTconc |
a concordance table (data.table) with two columns: given by parameters left and right. |
left |
character ('left') is the name of the left side of the concordance |
right |
character ('right') is the name of the right side of the concordance |
mm_groups |
logical (TRUE) if m:m grouping (block diagonalization) is required. |
mergeconc |
logical (FALSE) returns the conc merged with the original input |
Returns a data.table with columns left, right, conctype, outdegree,
indegree (and compnum)
where
conctype gives type of match ("1:1","m:1","1:m","m:m");
outdegree gives number of right nodes to which a left node goes;
indegree gives number of left nodes that go to a right node;
if mm_groups=TRUE, then compnum gives component number.
If mergeconc=TRUE then mdi_classify_conc() returns the result merged with the original DTconc input.
concx <- data.table::data.table( leftx = as.character(c(1, 2, 2, 3, 4, 5, 6, 6, 7, 8, 8)), rightx = c("A", "B", "C", "D", "D", "F", "E", "F", "H", "G", "H") ) classified <- mdi_classify_conc(concx, left = "leftx", right = "rightx", mm_groups = TRUE, mergeconc = TRUE)concx <- data.table::data.table( leftx = as.character(c(1, 2, 2, 3, 4, 5, 6, 6, 7, 8, 8)), rightx = c("A", "B", "C", "D", "D", "F", "E", "F", "H", "G", "H") ) classified <- mdi_classify_conc(concx, left = "leftx", right = "rightx", mm_groups = TRUE, mergeconc = TRUE)
Converts every variable of dt to the class md_vars declares for it, so
module code sees the same types in every country, whatever types came out
of import and harmonization. Conversions that fail on a value don't error:
as.numeric(), as.Date() etc. turn it into NA. The returned log
counts those, per variable, and also counts numeric values other than 0/1
converted to logical, which as.logical() turns into TRUE without an
NA to show for it.
Classes: character, integer, numeric, logical (from text:
true/t/1 and false/f/0, anything else NA), date (with the
format in domain, if any) and factor. factor is only applied where
domain is MD_codebook, the variables whose values recode/reclass
already checked against the codebook.
mdi_coerce_md_types(dt, dataset, md_vars, verbose = FALSE)mdi_coerce_md_types(dt, dataset, md_vars, verbose = FALSE)
dt |
data.table, a harmonized MD dataset. Modified by reference. |
dataset |
character(1), its MD dataset name, for the log. |
md_vars |
data.table, the variables to convert: |
verbose |
logical(1), print each conversion. |
A list: data, dt converted; log, one row per converted
variable: MD_dataset, MD_varname, desired_class, na_before,
na_after, na_introduced, logical_out_of_domain.
dt <- data.table::data.table(emp = c("1", "x"), exporter = c(0, 2)) vars <- data.table::data.table(MD_varname = c("emp", "exporter"), class = c("numeric", "logical"), domain = NA) res <- suppressWarnings(mdi_coerce_md_types(dt, "SBS", vars)) res$logdt <- data.table::data.table(emp = c("1", "x"), exporter = c(0, 2)) vars <- data.table::data.table(MD_varname = c("emp", "exporter"), class = c("numeric", "logical"), domain = NA) res <- suppressWarnings(mdi_coerce_md_types(dt, "SBS", vars)) res$log
This tool harmonizes NSI NACE classification over time. It simplify the harmonization process using the mode NACE code in the post break year as MD NACE code, and harmonizing accordingly previous year codes. It defines the break year as the year with the most changes in NACE codes.
mdi_concord_nace( DT, id_col, weight_emp, cumsh_del, sh_del, weight_col = "persons_br", seed = 1L )mdi_concord_nace( DT, id_col, weight_emp, cumsh_del, sh_del, weight_col = "persons_br", seed = 1L )
DT |
the MD dataset (e.g. |
id_col |
character, the name of |
weight_emp |
logical to decide whether or not to weight code matches by employment (instead of number of firms). |
cumsh_del |
number indicating the cumulative residual share of firms deleted for the construction of year by year concordances. |
sh_del |
number indicating the share of firms deleted for the construction of year by year concordances. |
weight_col |
character, the name of |
seed |
integer, the random seed for the firms whose code is assigned
by a random draw (weighted by how other firms' codes moved), so a
rerun gives the same |
the original MD dataset with the old NSI NACE code and the new MD NACE.
library(data.table) # four firms whose NACE codes change with the 2008 revision BR <- data.table( entid = rep(c("A", "B", "C", "D"), each = 4), year = rep(2006:2009, times = 4), nace = c("1511", "1511", "1011", "1011", "1511", "1511", "1011", "1011", "1513", "1513", "1013", "1013", "5211", "5211", "4711", "4711") ) br_harmonized <- mdi_concord_nace(BR, id_col = "entid", weight_emp = FALSE, cumsh_del = 0.95, sh_del = 0.02) br_harmonized[entid == "D"] # pre-2008 "5211" mapped to "4711"library(data.table) # four firms whose NACE codes change with the 2008 revision BR <- data.table( entid = rep(c("A", "B", "C", "D"), each = 4), year = rep(2006:2009, times = 4), nace = c("1511", "1511", "1011", "1011", "1511", "1511", "1011", "1011", "1513", "1513", "1013", "1013", "5211", "5211", "4711", "4711") ) br_harmonized <- mdi_concord_nace(BR, id_col = "entid", weight_emp = FALSE, cumsh_del = 0.95, sh_del = 0.02) br_harmonized[entid == "D"] # pre-2008 "5211" mapped to "4711"
Constructs the cfg object the pipeline functions take: the run's
directories, flags and versions. Derives the rocket/payload/metadata
directory paths from mdi_dir/country/launch_version, scans the
payload for the launch's onboarded countries and research modules, and
reads the rocket/payload version stamps. In MDI, the settings come from
the countdown script (their names there in brackets below).
mdi_config( mdi_dir, country, launch_version, input_dir, output_dir, tmp_dir, import = TRUE, clean_tmp = FALSE, phc_run = TRUE, module_run = FALSE, debug = FALSE, filtered_harmonization = FALSE, db_user = NULL, datasets = NULL, modules = NULL, modules_dir = NULL )mdi_config( mdi_dir, country, launch_version, input_dir, output_dir, tmp_dir, import = TRUE, clean_tmp = FALSE, phc_run = TRUE, module_run = FALSE, debug = FALSE, filtered_harmonization = FALSE, db_user = NULL, datasets = NULL, modules = NULL, modules_dir = NULL )
mdi_dir |
Character, the root MDI installation directory ( |
country |
Character, 2-3 letter NSI country code ( |
launch_version |
Character, e.g. |
input_dir |
Character, directory holding the NSI/mock firm-level data
files ( |
output_dir |
Character, directory generated files are exported to
( |
tmp_dir |
Character, directory for temporary MDI virtual dataset
storage ( |
import |
Logical, import raw NSI data files if |
clean_tmp |
Logical, clean |
phc_run |
Logical, |
module_run |
Logical, |
debug |
Logical, |
filtered_harmonization |
Logical, harmonize only the variables listed
in the current launch's |
db_user |
Character or |
datasets |
Character vector or |
modules |
Character vector or |
modules_dir |
Character or |
A list (cfg) with elements country, launch_version, dirs
(all derived paths: mdi, rocket, control, payload, nsimeta,
mdmeta, progs, output, tmpsave, inputdata), flags (import,
clean_tmp, phc_run, module_run, debug, filtered_harmonization),
db_user, datasets (as given), countries (onboarded country codes
found under the launch's MDmetadata/), modules (the research modules
to run: those found under the launch's Rmodules/ — a 4-letter-named
subfolder is a module — narrowed to modules if given),
rocket_version, payload_version. dirs$progs is the module folder
(modules_dir if given), dirs$main the launch's Rmodules/main.R.
mdi_dir <- paste0(tempdir(), "/") dir.create(paste0(mdi_dir, "rocket"), recursive = TRUE) dir.create(paste0(mdi_dir, "payload/Launch_v2.4/MDmetadata/FI"), recursive = TRUE) dir.create(paste0(mdi_dir, "payload/Launch_v2.4/Rmodules/CPNT"), recursive = TRUE) writeLines("commit\nabc123", paste0(mdi_dir, "rocket/rocket_github_commit.csv")) writeLines("commit\ndef456", paste0(mdi_dir, "payload/Launch_v2.4/payload_github_commit.csv")) cfg <- mdi_config( mdi_dir = mdi_dir, country = "FI", launch_version = "v2.4", input_dir = mdi_dir, output_dir = mdi_dir, tmp_dir = mdi_dir, phc_run = FALSE ) cfg$countries cfg$modulesmdi_dir <- paste0(tempdir(), "/") dir.create(paste0(mdi_dir, "rocket"), recursive = TRUE) dir.create(paste0(mdi_dir, "payload/Launch_v2.4/MDmetadata/FI"), recursive = TRUE) dir.create(paste0(mdi_dir, "payload/Launch_v2.4/Rmodules/CPNT"), recursive = TRUE) writeLines("commit\nabc123", paste0(mdi_dir, "rocket/rocket_github_commit.csv")) writeLines("commit\ndef456", paste0(mdi_dir, "payload/Launch_v2.4/payload_github_commit.csv")) cfg <- mdi_config( mdi_dir = mdi_dir, country = "FI", launch_version = "v2.4", input_dir = mdi_dir, output_dir = mdi_dir, tmp_dir = mdi_dir, phc_run = FALSE ) cfg$countries cfg$modules
Applies disclosure control rules to aggregated data based on dominance and
minimum-observation criteria. This function is typically called in
mdi_export() for export_type = "sum_stat".
Two steps are performed:
Primary disclosure:
Suppresses (-999) all aggregate cells that fail disclosure rules:
(i) do not satisfy the dominance criterion (domPerc), or
(ii) have fewer than minNumObs observations.
Flags are recorded as discflag1_* and discflag1.
Secondary disclosure:
If hierarchical aggregation (hier = TRUE) is used and exactly one child
cell is suppressed within a parent group, the smallest remaining cell in
that group is also suppressed (-999). Flags are recorded as discflag2.
mdi_disclose( DT, hhfile = NULL, hier = FALSE, minNumObs = 5, domSh = 0.85, domAllCols = 0, dom_direction = c("high", "low") )mdi_disclose( DT, hhfile = NULL, hier = FALSE, minNumObs = 5, domSh = 0.85, domAllCols = 0, dom_direction = c("high", "low") )
DT |
A |
hhfile |
A |
hier |
Logical. Set to |
minNumObs |
Numeric. Minimum number of observations required per cell.
Country-specific; set to |
domSh |
Numeric. Threshold for maximum allowed dominance share.
Country-specific; set to |
domAllCols |
Numeric
|
dom_direction |
Character. Direction of the dominance suppression rule.
|
The input must be keyed (setkeyv) on all of its grouping/label
columns. The key is the only signal separating label columns from statistic
columns; without it every label would be suppressed to -999 along with the
numbers. mdi_aggregate() sets the key, but rbindlist() drops it, so a
table assembled from several aggregations must be re-keyed before export.
Unkeyed input is an error, not silent corruption.
A table carrying mdi_hier_apply()'s node column must be passed with
hier = TRUE (and its hhfile); a table with no node column must be
passed with hier = FALSE. Either mismatch is an error — silently they
would leave a suppressed child recoverable from its parent total, or return
an empty table.
Dominance targets are resolved from the domPerc column name
(domPerc_<var> -> <agg>_<var> columns, matched by longest suffix so
emp and tot_emp do not collide), not paired positionally with
sum-columns. A bare domPerc (produced when the dominance variable is a
fixed column such as emp rather than "var") reaches every sum-column.
dom_direction = "high" suppresses when domPerc >= domSh;
dom_direction = "low" suppresses when domPerc < domSh (Germany).
Suppression affects both the target aggregate columns and, when triggered,
the NumObs column. Secondary disclosure ensures that no single cell can be
back-calculated from its parent total.
A copy of DT (the input is left unchanged), with:
Suppressed cells (-999) where disclosure criteria are not met.
Flags discflag1_* and discflag1 for primary disclosure.
Flag discflag2 for secondary disclosure (if applicable).
library(data.table) DT <- data.table( nace = c("A", "B", "C"), sum_emp = c(100, 200, 40), mean_emp = c(5, 8, 2), domPerc_emp = c(0.50, 0.92, 0.20), NumObs = c(10L, 10L, 3L) ) setkeyv(DT, "nace") # domAllCols = 0: dominance masks sum_emp only; C is below minNumObs mdi_disclose(DT, hier = FALSE, minNumObs = 5, domSh = 0.85, domAllCols = 0) # domAllCols = 1: dominance also masks mean_emp mdi_disclose(DT, hier = FALSE, minNumObs = 5, domSh = 0.85, domAllCols = 1)library(data.table) DT <- data.table( nace = c("A", "B", "C"), sum_emp = c(100, 200, 40), mean_emp = c(5, 8, 2), domPerc_emp = c(0.50, 0.92, 0.20), NumObs = c(10L, 10L, 3L) ) setkeyv(DT, "nace") # domAllCols = 0: dominance masks sum_emp only; C is below minNumObs mdi_disclose(DT, hier = FALSE, minNumObs = 5, domSh = 0.85, domAllCols = 0) # domAllCols = 1: dominance also masks mean_emp mdi_disclose(DT, hier = FALSE, minNumObs = 5, domSh = 0.85, domAllCols = 1)
Reads MD_disclosure_info.csv's disclosure_variable column and the
country's own column, and returns them as a named list. Every value is
a number, except MDIdomVar (a variable name) and MDIdomDirection
("high" or "low"), kept as text.
mdi_disclosure_config(mdmeta_dir, country)mdi_disclosure_config(mdmeta_dir, country)
mdmeta_dir |
Character, the launch's |
country |
Character, the NSI country code. |
A named list, one element per row of MD_disclosure_info.csv,
under the file's own disclosure_variable names verbatim (currently
MDIminNumObs, MDIdomVar, MDIdomSh, MDIdomNr, MDIdomAllCols,
MDIdomDirection, show_domPerc, show_values). Numbers, except
MDIdomVar and MDIdomDirection ("high", or "low" for DE).
dir <- tempfile() dir.create(dir) writeLines(c( "disclosure_variable,FI", "MDIminNumObs,3", "MDIdomVar,var" ), paste0(dir, "/MD_disclosure_info.csv")) mdi_disclosure_config(paste0(dir, "/"), "FI")dir <- tempfile() dir.create(dir) writeLines(c( "disclosure_variable,FI", "MDIminNumObs,3", "MDIdomVar,var" ), paste0(dir, "/MD_disclosure_info.csv")) mdi_disclosure_config(paste0(dir, "/"), "FI")
Exports a given data table to a specified format (csv, RDS, txt, dta, xlsx, sas) and appends an entry to an output description file.
mdi_export( DT, format, output_name, datasets_used, purpose, share_0_1, zeroes, selection, dirOUTPUT, hier = FALSE, hhfile = NULL, rel_other_output = "", export_type = "sum_stat", description = " ", CountryCode = "XX", res_group = "", show_values = 0, show_domPerc = 1, minNumObs = 5L, domSh = 0.85, domAllCols = 0, dom_direction = c("high", "low"), disc_method = c("obs_df", "firm_count") )mdi_export( DT, format, output_name, datasets_used, purpose, share_0_1, zeroes, selection, dirOUTPUT, hier = FALSE, hhfile = NULL, rel_other_output = "", export_type = "sum_stat", description = " ", CountryCode = "XX", res_group = "", show_values = 0, show_domPerc = 1, minNumObs = 5L, domSh = 0.85, domAllCols = 0, dom_direction = c("high", "low"), disc_method = c("obs_df", "firm_count") )
DT |
A |
format |
Character. Export format: |
output_name |
Character. File name to create, without extension or country code prefix. |
datasets_used |
Character vector. Name(s) of the MD datasets used. |
purpose |
Character. Research purpose of the analysis. |
share_0_1 |
Character. Explanation of any shares equal to 0 or 1. |
zeroes |
Character. Explanation of any zero values, or justification for suppressing them. |
selection |
Character. Whether results cover the full sample or a selection. |
dirOUTPUT |
Character. Path to the output directory (must end with
|
hier |
Logical. |
hhfile |
A |
rel_other_output |
Character. How this output relates to other
exported files. Default |
export_type |
Character. Type of output: |
description |
Character. Additional explanation of the output file.
Default |
CountryCode |
Character. Two-letter ISO country code used in file
naming and the output description log. Default |
res_group |
Character. Research group identifier used in file naming.
Default |
show_values |
Numeric. |
show_domPerc |
Numeric. |
minNumObs |
Integer. Minimum number of observations for disclosure
checks, country-specific ( |
domSh |
Numeric. Dominance-share threshold for |
domAllCols |
Numeric |
dom_direction |
Character. Dominance suppression direction for
|
disc_method |
Character. Disclosure rule for |
Writes files to disk; returns NULL invisibly.
# sums of employment per industry, with their dominance shares and # observation counts emp_by_nace <- data.table::data.table(nace = c("A", "B"), sum_emp = c(100, 5), domPerc_emp = c(0.5, 0.5), NumObs = c(10L, 2L)) data.table::setattr(emp_by_nace, "sumvars", "emp") data.table::setkeyv(emp_by_nace, "nace") out_dir <- paste0(tempfile("mdi_export_"), "/") dir.create(out_dir) mdi_export( DT = emp_by_nace, format = "csv", output_name = "Employment_by_industry", datasets_used = "SBS", purpose = "Employment by industry.", share_0_1 = "None", zeroes = "None", selection = "Full sample", export_type = "sum_stat", CountryCode = "XX", res_group = "ABCD", dirOUTPUT = out_dir ) # industry B has fewer than minNumObs = 5 observations: its sum is masked data.table::fread(paste0(out_dir, "XX_ABCD_Employment_by_industry.csv")) readLines(paste0(out_dir, "OutputDescription.csv"))# sums of employment per industry, with their dominance shares and # observation counts emp_by_nace <- data.table::data.table(nace = c("A", "B"), sum_emp = c(100, 5), domPerc_emp = c(0.5, 0.5), NumObs = c(10L, 2L)) data.table::setattr(emp_by_nace, "sumvars", "emp") data.table::setkeyv(emp_by_nace, "nace") out_dir <- paste0(tempfile("mdi_export_"), "/") dir.create(out_dir) mdi_export( DT = emp_by_nace, format = "csv", output_name = "Employment_by_industry", datasets_used = "SBS", purpose = "Employment by industry.", share_0_1 = "None", zeroes = "None", selection = "Full sample", export_type = "sum_stat", CountryCode = "XX", res_group = "ABCD", dirOUTPUT = out_dir ) # industry B has fewer than minNumObs = 5 observations: its sum is masked data.table::fread(paste0(out_dir, "XX_ABCD_Employment_by_industry.csv")) readLines(paste0(out_dir, "OutputDescription.csv"))
Applies a data file's rows of the NSI-to-MD concordance, method by method:
detail is an expression in x, the NSI variable, e.g.
log(x), x*1000 or format(as.Date(x, "%Y%m%d"), "%Y").
detail names the code table (codeconc or
classconc) that maps the variable's values, left to right.
builds an MD variable from several NSI variables:
detail is + or - (NA-safe sum or difference) or fn(...).
renames the NSI variable to its MD name.
It then keeps only the MD variables and sets year: from the file's year
variable if it has one (a panel), otherwise from the concordance.
mdi_harmonize( DT, concordance, country, code_tables = list(), year_var = NA_character_, verbose = FALSE )mdi_harmonize( DT, concordance, country, code_tables = list(), year_var = NA_character_, verbose = FALSE )
DT |
data.table, one NSI data file. Modified by reference. |
concordance |
data.table, the concordance rows for this file:
|
country |
character(1), the country code. A |
code_tables |
named list of the code tables recode/reclass rows can
name: the NSI and MD metadata, e.g. |
year_var |
character(1), the file's year variable, or |
verbose |
logical(1), report progress with messages. |
DT, harmonized.
raw <- data.table::data.table(TURN = c(1, 2), EMP = c(3, 4)) conc <- data.table::data.table( NSI_dataset = "sbs", year = 2020, NSI_varname = c("TURN", "TURN", "EMP"), MD_varname = c("turnover", "turnover", "emp"), method = c("revalue", "remap", "remap"), detail = c("x*1000", NA, NA) ) mdi_harmonize(raw, conc, "FI")raw <- data.table::data.table(TURN = c(1, 2), EMP = c(3, 4)) conc <- data.table::data.table( NSI_dataset = "sbs", year = 2020, NSI_varname = c("TURN", "TURN", "EMP"), MD_varname = c("turnover", "turnover", "emp"), method = c("revalue", "remap", "remap"), detail = c("x*1000", NA, NA) ) mdi_harmonize(raw, conc, "FI")
Classification codes (e.g. CN8 product codes, PRODCOM codes, NUTS
regions) change from year to year. Given a year-on-year concordance of old
to new codes, builds the mapping from each year's codes to the codes of the
last year with mdi_make_conc(), and applies it to dt: the harmonized
code replaces code_name, and the original is kept as NSI_<code_name>.
mdi_harmonize_codes_over_time(dt, conc, code_name, data_years)mdi_harmonize_codes_over_time(dt, conc, code_name, data_years)
dt |
A |
conc |
A |
code_name |
Character, the code column to harmonize (e.g. |
data_years |
Numeric, length 2: the first and last year the raw data covers. Harmonization runs over the years both the concordance and the data cover (from the year after the data's first year, since the first year has no earlier year to map from). |
A list: data, dt with the harmonized code_name and the
original as NSI_<code_name>; and codes, the mapping table
mdi_make_conc() produced (year, code_name,
<code_name>_harmonized). dt itself is not modified. NULL, with a
warning, when the concordance's years and the data's don't overlap.
# code A (2010) became B in 2011, which became C in 2012; X never changed conc <- data.table::data.table(left = c("A", "X", "B", "X"), right = c("B", "X", "C", "X"), year = c(2011, 2011, 2012, 2012)) dt <- data.table::data.table(year = c(2010, 2011, 2012, 2012), cn08 = c("A", "B", "C", "X")) out <- mdi_harmonize_codes_over_time(dt, conc, "cn08", data_years = c(2010, 2012)) out$data# code A (2010) became B in 2011, which became C in 2012; X never changed conc <- data.table::data.table(left = c("A", "X", "B", "X"), right = c("B", "X", "C", "X"), year = c(2011, 2011, 2012, 2012)) dt <- data.table::data.table(year = c(2010, 2011, 2012, 2012), cn08 = c("A", "B", "C", "X")) out <- mdi_harmonize_codes_over_time(dt, conc, "cn08", data_years = c(2010, 2012)) out$data
Performs aggregation or disaggregation of a harmonized dataset across hierarchical identifier levels using concordance tables and metadata-driven rules.
The supported identifier hierarchy is:
plantid -> firmid -> entid -> entgrp
The function relies on MD_varnames to identify keys, variable structure,
and — via its aggregation_method/disaggregation_method columns — how each
variable should be transformed, and on concordance tables (*_conc.csv) to
map between identifier levels.
mdi_key_id_switch( DT, conctab, md_varnames, method, obj_id_type, cols = NULL, key_id = "firmid", weight_data = NULL )mdi_key_id_switch( DT, conctab, md_varnames, method, obj_id_type, cols = NULL, key_id = "firmid", weight_data = NULL )
DT |
A |
conctab |
A |
md_varnames |
A |
method |
Character. |
obj_id_type |
Character. Target identifier level: |
cols |
Optional character vector of columns to process. Default
|
key_id |
Character. Name of the source identifier column in |
weight_data |
Named list of |
mdi_key_id_switch
Variable-specific transformation rules are looked up from the
aggregation_method/disaggregation_method columns in MD_varnames.
For aggregation (method = "agg"), multiple lower-level units are combined
into a higher-level unit, for example from plant to firm. Each variable is
aggregated according to its aggregation_method.
Supported aggregation methods include "sum", "weighted_avg",
"weighted_mode", "mode", "any", "all", "min", and "max".
For disaggregation (method = "disagg"), higher-level observations are split
into lower-level units using concordance mappings, for example from firm to plant.
Each variable is transformed according to its disaggregation_method.
Supported disaggregation methods include "replicate" and "weighted_alloc".
Aggregation preserves totals for additive variables such as those using "sum".
Disaggregation with "replicate" does not preserve totals. Disaggregation with
"weighted_alloc" approximately preserves totals. Factor variables are internally
converted to character to avoid unintended integer behavior.
Rows whose key_id the concordance has no target unit for are dropped,
with a message counting them per year. In aggregation, a group whose
values are all NA stays NA (not 0 for "sum", or FALSE/TRUE for
"any"/"all").
Survey-weight variables (weights_<dataset>_nsi) are dropped before the
switch, with a warning: they belong to the sample, not to the firm.
If an aggregation rule names a weight column ("weighted_avg:w",
"weighted_mode:w1|w2") and none of the named column(s) are present in
DT, a warning is raised once upfront and the variable falls back to an
unweighted mean/mode.
A "weighted_alloc" rule lists its weights in order, each as
<MD_dataset>.<MD_varname>, e.g.
"weighted_alloc:SBS.emp|PRODCOM.rev|BR.persons_br|equal". The chain is
in MD_varnames so every module splits a variable the same way. A weight
is that column of weight_data[[<MD_dataset>]], summed per target unit
and year; it can't be a column of DT itself, which holds the source
unit's value on every target row. For each source unit and year, the first
candidate with weights that aren't all missing or zero for its target
units wins; if none does, the value is split equally. A candidate whose
dataset isn't in weight_data, isn't at the target level or lacks the
column is skipped, with one warning naming it. A message reports, per
variable, how many source units used each candidate.
A data.table with variables aggregated or disaggregated to the
requested identifier level.
# how each variable is aggregated and disaggregated, as in MD_varnames md_varnames <- data.table::data.table( MD_dataset = "SBS", MD_varname = c("plantid", "firmid", "year", "emp", "sales"), is_key = c(1, 1, 1, 0, 0), aggregation_method = c(NA, NA, NA, "sum", "sum"), disaggregation_method = c(NA, NA, NA, "replicate", "weighted_alloc:BR.persons_br|equal") ) # which plants belong to which firm, per year plant_firm <- data.table::data.table(plantid = c("P1", "P2", "P3"), firmid = c("F1", "F1", "F2"), year = 2020) # plants to firms: F1 sums P1 and P2 plants <- data.table::data.table(plantid = c("P1", "P2", "P3"), year = 2020, emp = c(10, 30, 5), sales = c(100, 300, 50)) mdi_key_id_switch(DT = plants, conctab = plant_firm, md_varnames = md_varnames, method = "agg", obj_id_type = "to_firmid", key_id = "plantid") # firms to plants: F1's sales split by its plants' employment in BR, emp # copied to each plant firms <- data.table::data.table(firmid = c("F1", "F2"), year = 2020, emp = c(40, 5), sales = c(400, 50)) br <- data.table::data.table(plantid = c("P1", "P2", "P3"), year = 2020, persons_br = c(10, 30, 5)) mdi_key_id_switch(DT = firms, conctab = plant_firm, md_varnames = md_varnames, method = "disagg", obj_id_type = "to_plantid", key_id = "firmid", weight_data = list(BR = br))# how each variable is aggregated and disaggregated, as in MD_varnames md_varnames <- data.table::data.table( MD_dataset = "SBS", MD_varname = c("plantid", "firmid", "year", "emp", "sales"), is_key = c(1, 1, 1, 0, 0), aggregation_method = c(NA, NA, NA, "sum", "sum"), disaggregation_method = c(NA, NA, NA, "replicate", "weighted_alloc:BR.persons_br|equal") ) # which plants belong to which firm, per year plant_firm <- data.table::data.table(plantid = c("P1", "P2", "P3"), firmid = c("F1", "F1", "F2"), year = 2020) # plants to firms: F1 sums P1 and P2 plants <- data.table::data.table(plantid = c("P1", "P2", "P3"), year = 2020, emp = c(10, 30, 5), sales = c(100, 300, 50)) mdi_key_id_switch(DT = plants, conctab = plant_firm, md_varnames = md_varnames, method = "agg", obj_id_type = "to_firmid", key_id = "plantid") # firms to plants: F1's sales split by its plants' employment in BR, emp # copied to each plant firms <- data.table::data.table(firmid = c("F1", "F2"), year = 2020, emp = c(40, 5), sales = c(400, 50)) br <- data.table::data.table(plantid = c("P1", "P2", "P3"), year = 2020, persons_br = c(10, 30, 5)) mdi_key_id_switch(DT = firms, conctab = plant_firm, md_varnames = md_varnames, method = "disagg", obj_id_type = "to_plantid", key_id = "firmid", weight_data = list(BR = br))
Reads auxdata/deflators.csv, filtered to
the country's own rows. Every column except cc/DEFind/nace is
coerced to numeric — deflators.csv is measurement data (deflator index
values), the opposite default from the classification-file loaders
(mdi_read_meta_dir()/mdi_load_md_metadata()/mdi_load_nsi_metadata()),
so this does not reuse that primitive. Also reads
MD_nace_DEFind_conc.csv, the concordance mapping NACE codes to the
deflator's own industry grouping (DEFind).
Deliberately country-agnostic: deflators.csv carries a real row for
every country code that needs one, including variants of a country code
used for a second firm-level sample of the same country (their deflators
are the country's own – deflators are national-accounts data, independent
of the firm-level sample). No mapping between codes is hardcoded here.
mdi_load_deflators(rocket_dir, mdmeta_dir, country)mdi_load_deflators(rocket_dir, mdmeta_dir, country)
rocket_dir |
Character, the |
mdmeta_dir |
Character, the launch's |
country |
Character, the NSI country code. |
A list with elements deflators and nace_DEFind (the names
modules know them by as globals).
rocket_dir <- paste0(tempfile("rocket_"), "/") mdmeta_dir <- paste0(tempfile("mdmeta_"), "/") dir.create(paste0(rocket_dir, "auxdata"), recursive = TRUE) dir.create(mdmeta_dir) writeLines(c("cc,year,DEFind,pnv", "FI,1995,A,1.1", "PT,1995,A,2.2"), paste0(rocket_dir, "auxdata/deflators.csv")) writeLines(c("nace,DEFind", "\"0111\",A01"), paste0(mdmeta_dir, "MD_nace_DEFind_conc.csv")) mdi_load_deflators(rocket_dir, mdmeta_dir, "FI")rocket_dir <- paste0(tempfile("rocket_"), "/") mdmeta_dir <- paste0(tempfile("mdmeta_"), "/") dir.create(paste0(rocket_dir, "auxdata"), recursive = TRUE) dir.create(mdmeta_dir) writeLines(c("cc,year,DEFind,pnv", "FI,1995,A,1.1", "PT,1995,A,2.2"), paste0(rocket_dir, "auxdata/deflators.csv")) writeLines(c("nace,DEFind", "\"0111\",A01"), paste0(mdmeta_dir, "MD_nace_DEFind_conc.csv")) mdi_load_deflators(rocket_dir, mdmeta_dir, "FI")
Reads every MD classification/lookup file
(_codebook, _varnames, _catalogue, _idInfo, _hier_levels,
_classconc, _hier, _class, _indentedclass) via
mdi_read_meta_dir(). MD_datafiles.csv is read separately, plainly (no
column of it needs character coercion, and the equivalent NSI-side file
has numeric year_start/year_end columns that must not be forced to
character, so both are kept off the shared rule for consistency).
MD_disclosure_info.csv is not included here — see
mdi_disclosure_config().
mdi_load_md_metadata(mdmeta_dir)mdi_load_md_metadata(mdmeta_dir)
mdmeta_dir |
Character, the launch's |
A named list of data.tables, one per matched file, named after
the file's own basename without extension (e.g. MD_nace_hier.csv ->
MD_nace_hier), plus MD_datafiles if present.
dir <- tempfile() dir.create(dir) writeLines(c("code,description", "1,Active"), paste0(dir, "/MD_codebook.csv")) writeLines(c("name,description"), paste0(dir, "/MD_datafiles.csv")) md <- mdi_load_md_metadata(paste0(dir, "/")) names(md)dir <- tempfile() dir.create(dir) writeLines(c("code,description", "1,Active"), paste0(dir, "/MD_codebook.csv")) writeLines(c("name,description"), paste0(dir, "/MD_datafiles.csv")) md <- mdi_load_md_metadata(paste0(dir, "/")) names(md)
Reads the country's stacked NSI files
(_datafiles.csv, _varnames.csv) from nsimeta_dir, and the
NSI-to-MD concordance/classification files (_conc, _classconc,
_codeconc, plus the NSI-level and MD-level _class/_classconc
family) from nsimeta_dir and mdmeta_dir/<country>/, via
mdi_read_meta_dir(), renamed from their <country>_... filenames to
NSI_...-prefixed names. Per-classification files are included (e.g.
FI_cn08_class.csv -> NSI_cn08_class,
FI_NSI_cn08_MD_cn08_classconc.csv -> NSI_NSI_cn08_MD_cn08_classconc):
mdi_harmonize()'s reclass step looks them up by exactly these names.
NSI_datafiles.csv is read plainly (it has
year_start/year_end numeric columns that must not be forced to
character). Its path column is relative to the input data folder as
given (for mock data with one subfolder per country, that folder is the
country's).
mdi_load_nsi_metadata(nsimeta_dir, mdmeta_dir, country)mdi_load_nsi_metadata(nsimeta_dir, mdmeta_dir, country)
nsimeta_dir |
Character, the country's |
mdmeta_dir |
Character, the launch's |
country |
Character, the NSI country code. |
A named list of data.tables: NSI_datafiles, NSI_varnames,
plus every matched NSI-MD concordance/classification file (e.g.
FI_MD_conc.csv -> NSI_MD_conc, FI_classconc.csv ->
NSI_classconc).
nsimeta_dir <- paste0(tempfile("nsimeta_"), "/") mdmeta_dir <- paste0(tempfile("mdmeta_"), "/") dir.create(nsimeta_dir) dir.create(paste0(mdmeta_dir, "FI"), recursive = TRUE) writeLines(c("NSI_datafile,path,year_start,year_end", "bd2003,bd/,2003,2003"), paste0(nsimeta_dir, "FI_datafiles.csv")) writeLines(c("NSI_datafile,NSI_varname,is_key", "bd2003,firmid,1"), paste0(nsimeta_dir, "FI_varnames.csv")) writeLines(c("NSI_dataset,NSI_varname,MD_varname,method,detail,year", "bd,acq,acq,revalue,as.logical(x),2003"), paste0(mdmeta_dir, "FI/FI_MD_conc.csv")) nsi <- mdi_load_nsi_metadata(nsimeta_dir, mdmeta_dir, "FI") names(nsi)nsimeta_dir <- paste0(tempfile("nsimeta_"), "/") mdmeta_dir <- paste0(tempfile("mdmeta_"), "/") dir.create(nsimeta_dir) dir.create(paste0(mdmeta_dir, "FI"), recursive = TRUE) writeLines(c("NSI_datafile,path,year_start,year_end", "bd2003,bd/,2003,2003"), paste0(nsimeta_dir, "FI_datafiles.csv")) writeLines(c("NSI_datafile,NSI_varname,is_key", "bd2003,firmid,1"), paste0(nsimeta_dir, "FI_varnames.csv")) writeLines(c("NSI_dataset,NSI_varname,MD_varname,method,detail,year", "bd,acq,acq,revalue,as.logical(x),2003"), paste0(mdmeta_dir, "FI/FI_MD_conc.csv")) nsi <- mdi_load_nsi_metadata(nsimeta_dir, mdmeta_dir, "FI") names(nsi)
Plots p25, p50, p75 and mean of a numeric variable per year on the left
axis and sd (dashed) on the right, and labels the years
mdi_check_harmonized() flagged as breaks. Needs the ggplot2 package.
mdi_plot_breaks(series, breaks, title = NULL)mdi_plot_breaks(series, breaks, title = NULL)
series |
data.table, one variable's yearly statistics: |
breaks |
data.table, that variable's rows of |
title |
character(1) or |
A ggplot object.
if (requireNamespace("ggplot2", quietly = TRUE)) { dt <- data.table::data.table(firmid = rep(letters[1:4], 3), year = rep(2019:2021, each = 4), emp = c(1:4, 1:4, 11:14)) catalogue <- data.table::data.table( MD_dataset = "SBS", MD_varname = c("firmid", "year", "emp"), is_key = c(1L, 1L, 0L), class = c("character", "numeric", "numeric"), domain = "", FI = "2019 - 2021") res <- mdi_check_harmonized(dt, "SBS", catalogue, "FI") mdi_plot_breaks(res$series$emp, res$breaks) }if (requireNamespace("ggplot2", quietly = TRUE)) { dt <- data.table::data.table(firmid = rep(letters[1:4], 3), year = rep(2019:2021, each = 4), emp = c(1:4, 1:4, 11:14)) catalogue <- data.table::data.table( MD_dataset = "SBS", MD_varname = c("firmid", "year", "emp"), is_key = c(1L, 1L, 0L), class = c("character", "numeric", "numeric"), domain = "", FI = "2019 - 2021") res <- mdi_check_harmonized(dt, "SBS", catalogue, "FI") mdi_plot_breaks(res$series$emp, res$breaks) }
The review gate between harmonization and the modules: checks every
harmonized MD dataset in cfg$dirs$tmpsave with
mdi_check_harmonized() (duplicate keys, rows without a year, classes and
date formats, date ranges, breaks in yearly statistics, jumps in the yearly
share of NA values) and adds the
coercion fallout log (NAs introduced, or out-of-domain values force-cast to
logical, by the type conversion). Writes, in cfg$dirs$tmpsave:
<CC>_phc_results.txt, all results;
breaks_report.pdf, a page per variable either check flags: its
yearly statistics (mdi_plot_breaks(), numeric variables) above its
yearly share of NA values (mdi_plot_na_share()); needs ggplot2 and
gridExtra.
Every numeric variable a dataset has is checked, including the ones the
pipeline computes (K_pim), so run this after the whole pipeline.
Someone at the NSI reviews these; once they look fine, MDImoduleRUN = TRUE lets the modules run.
mdi_post_harmonization_checks( cfg, threshold = 0.25, na_threshold = 0.1, verbose = FALSE )mdi_post_harmonization_checks( cfg, threshold = 0.25, na_threshold = 0.1, verbose = FALSE )
cfg |
config list from |
threshold |
numeric(1), the relative year-on-year change flagged as
a break (see |
na_threshold |
numeric(1), the year-on-year change in the share of
|
verbose |
logical(1), report where the results are with a message. |
The per-dataset results of mdi_check_harmonized(), invisibly.
# a harmonized SBS with a duplicated firm-year and a row without a year tmpsave <- paste0(tempfile("phc_"), "/") dir.create(tmpsave) saveRDS(data.table::data.table(firmid = c("a", "a", "b"), year = c(2020L, 2020L, NA), emp = c(1, 1, 1)), paste0(tmpsave, "SBS.RDS")) catalogue <- data.table::data.table( MD_dataset = "SBS", MD_varname = c("firmid", "year", "emp"), is_key = c(1L, 1L, 0L), class = c("character", "integer", "numeric"), domain = "", FI = "2020 - 2021") cfg <- list(dirs = list(tmpsave = tmpsave), country = "FI", md = list(MD_catalogue = catalogue, MD_varnames = catalogue[, c("MD_dataset", "MD_varname")]), nsi = list()) res <- mdi_post_harmonization_checks(cfg) res$SBS$duplicates readLines(paste0(tmpsave, "FI_phc_results.txt"))[1:8]# a harmonized SBS with a duplicated firm-year and a row without a year tmpsave <- paste0(tempfile("phc_"), "/") dir.create(tmpsave) saveRDS(data.table::data.table(firmid = c("a", "a", "b"), year = c(2020L, 2020L, NA), emp = c(1, 1, 1)), paste0(tmpsave, "SBS.RDS")) catalogue <- data.table::data.table( MD_dataset = "SBS", MD_varname = c("firmid", "year", "emp"), is_key = c(1L, 1L, 0L), class = c("character", "integer", "numeric"), domain = "", FI = "2020 - 2021") cfg <- list(dirs = list(tmpsave = tmpsave), country = "FI", md = list(MD_catalogue = catalogue, MD_varnames = catalogue[, c("MD_dataset", "MD_varname")]), nsi = list()) res <- mdi_post_harmonization_checks(cfg) res$SBS$duplicates readLines(paste0(tmpsave, "FI_phc_results.txt"))[1:8]
This function applies one or more preprocessing operations to a data.table object.
It is designed to prepare raw microdata files for harmonization by applying a flexible
set of transformations through a structured instruction string.
mdi_preprocess(preprocessing_string, dt, read_datafile = NULL)mdi_preprocess(preprocessing_string, dt, read_datafile = NULL)
preprocessing_string |
A character string defining one or more preprocessing instructions |
dt |
A |
read_datafile |
A function |
The input preprocessing_string is a single string where:
Multiple operations are separated by double pipes (||)
Parameters within each operation are separated by pipes (|)
When a parameter contains multiple elements (e.g., multiple columns), they are separated by a hash symbol (#)
IMPORTANT: When performing more than one operation, order matters. The result of each step is fed into the next.
Supported operations:
dedup|<id_col1#id_col2#...>|<method>|[dedup_col]
Removes duplicates based on identifier columns.
Methods:
na: keep the row with the fewest missing values
max / min: keep the row with the max/min value of the specified dedup_col
meanmode: aggregate numeric columns using the mean, and categorical columns using the mode
random: keep one row among duplicates at random (the same row on every run)
filter|<col>|<operator>|<value>
Filters rows based on a logical condition.
Operators:
eq, neq: equal / not equal
gt, gte, lt, lte: greater/less than (or equal to)
in, nin: value in (or not in) a list, separated by # (e.g., US#IT#DE)
isna, notna: keep rows where the column is / is not missing. These
take no value, so the instruction has three parts, not four
(e.g., filter|FIRM_ID|notna to drop rows with no firm identifier).
agg|<group_col1#group_col2#...>|<var1:func#var2:func#...>
Aggregates rows to a lower dimension by grouping.
Functions supported: sum, mean, min, max, median, sd, mode, pickmaxby-refcol
restruct|<col_to_remove>|<col1#col2#...>
Drops a column that introduces duplication and deduplicates on a set of remaining columns.
reshape|<id_col1#id_col2#...>|<names_from>|<values_from>
Reshapes data from long to wide format using data.table::dcast.
reshapelong|<id_col1#id_col2#...>|<measure_col1#measure_col2#...>|<names_to>|<values_to>
Reshapes data from wide to long format using data.table::melt: each
measure column becomes a row. names_to is the new column holding the
original column name, values_to the new column holding its value.
Useful when a raw file stores one record per category in separate
columns but the MD schema expects one row per category.
Example (trade data with one column per direction, to one row per
direction, then drop the empty ones):
reshapelong|FIRM_ID#PRODUCT#COUNTRY|import#export|FLOW|VALUE||filter|VALUE|notna
derive|<new_col>|condmap|<cond1>:<val1>#<cond2>:<val2>#...|default:<default_val>
Creates a new column based on a sequence of conditional expressions.
Conditions are evaluated in order and the first match is used.
The condition (condX) is written in standard R syntax (e.g., colA == 1 & colB == 'X').
The value (valX) can be a column name or a static value.
A fallback value must be provided via default:<value>.
Example:
derive|new_col|condmap|type == 2:colA#year == 2008 & type == 1:colB|default:NA
scaleif|<condition_col>|<val1:factor1#val2:factor2#...>|<col1#col2#...>
Conditionally scales one or more columns by a numeric factor depending on the value of a categorical column.
The second argument is a mapping of value:factor pairs (e.g., 1:1#2:1000#5:100).
The third argument lists the columns to be scaled when the condition is met.
Example:
scaleif|unit_code|1:1#2:1000#3:0.001|value#quantity
trimchars|<col1#col2#...>|<n>
Trims the last n characters from each specified character column.
Useful for standardizing identifier or code formats.
Example:
trimchars|prodcom_code|2
parsedate|<col>|<informat>|[lang]|[outformat]
Parses a date column given its layout in strptime tokens (e.g. %d-%b-%y),
independently of the system locale. Month names (%b abbreviated / %B
full) are matched through readr's bundled CLDR month names: lang is a
two-letter language code (it, de, fr, ... - see
readr::date_names_langs() for the ~185 available); English if omitted.
Case is normalized first, so "31-OTT-12" and "31-ott-12" both parse.
By default the column is replaced by a Date; an optional outformat
(e.g. %Y%m%d) returns a formatted string instead. Values that do not
parse are set to NA with a warning; if no non-empty value parses the
operation stops.
Example (balance-sheet closing dates with Italian month names like
"31-OTT-12", then keep the latest balance sheet per firm):
parsedate|CLOSING_DATE|%d-%b-%y|it||dedup|FIRM_ID|max|CLOSING_DATE
mergefrom|<datafile_name>|<join_key1#join_key2#...>|<col1#col2#...>|[join_type]
Reads the join keys and the listed columns of another raw file,
datafile_name (an NSI_datafile of NSI_datafiles), with
read_datafile (see below), and merges the columns into the current
dataset on the join keys. The other file's own preprocessing is not run.
The join type is optional and defaults to a left join. Supported join types:
left: keep all rows from the current dataset (default)
inner: keep only rows with matching keys in both datasets
full: full outer join; keep all rows from both datasets
The join keys must uniquely identify rows in both datasets, and
be text on both sides or numbers on both sides. If a key combination
is missing on either side, the corresponding values are set to NA.
Example:
mergefrom|sbs_2020|FIRM_ID#Jahr|NACE|left
A data.table with the specified preprocessing operations applied
dt <- data.table::data.table(firm_id = c(1, 1, 2), year = 2020, country = c("IT", "IT", "FR"), emp = c(5, 6, 7)) # one row per firm and year (the one with most employment), Italian firms only mdi_preprocess("dedup|firm_id#year|max|emp||filter|country|eq|IT", dt)dt <- data.table::data.table(firm_id = c(1, 1, 2), year = 2020, country = c("IT", "IT", "FR"), emp = c(5, 6, 7)) # one row per firm and year (the one with most employment), Italian firms only mdi_preprocess("dedup|firm_id#year|max|emp||filter|country|eq|IT", dt)
One run at an NSI, up to the research modules: starts
OutputDescription.csv in cfg$dirs$output (launch, rocket and payload
versions, then a row per exported file), harmonizes the data
(mdi_harmonize_pipeline()), writes the post-harmonization report
(mdi_post_harmonization_checks()) unless the modules are to run
(cfg$flags$module_run) or the report is switched off
(cfg$flags$phc_run, e.g. for mock data), and starts
WarningsDescription.txt.
The modules themselves run in the global environment, which a package function doesn't touch: the calling script does that after this.
mdi_run(cfg, oracle_channel = NULL, verbose = TRUE)mdi_run(cfg, oracle_channel = NULL, verbose = TRUE)
cfg |
config list from |
oracle_channel |
passed on to |
verbose |
logical(1), report the run's progress with messages, including where the post-harmonization report is and what to do next. |
cfg, as mdi_harmonize_pipeline() returns it, invisibly.