Parses a Statistics Canada Beyond 20/20 .ivt table straight from its bytes:
both the data cells and the codebook (dimension members, geographic
identifiers, footnotes). No companion CSV or metadata download is required.
Arguments
- path
Path to an
.ivtfile.- geo_attributes
For the large chunked family-2 tables only: if
TRUE, decode the full geography attribute table (names, level/type, geocodes, data-quality flag, non-response rate) from the codebook soivt_tidy()can label geographies by name. This adds a codebook block-scan (tens of seconds); the defaultFALSEkeeps those tables keyed by DGUID. Small schema'd tables and the pre-DGUID (inline-codebook) tables already carry their full attribute set on the default metadata path. Ignored for family-1 tables.- missing
If
TRUE(defaultFALSE) also return the flagged cells on their own, as amissingtibble: the same member-id columns ascells, novalue, plussymbolandstatus. On the completed table (the default) this is simply theis.na(value)rows, so it is a convenience view rather than extra work; withcomplete = FALSEit is decoded separately and is the only place the file's missing-value statement appears.- complete
If
TRUE(default) return the published table: one row per real grid coordinate, matching what StatCan's own CSV download publishes. Absent cells the file says nothing about become the published zero; cells the file flags carryvalue = NAplus thesymbolandstatusit declares for them.FALSEreturns the raw store instead – only the cells that carry a stored value, nosymbol/statuscolumns – which is smaller and faster but is not a table you can complete to a full grid yourself without turning every suppressed cell into a zero. Completion is refused abovegetOption("canivt.max_cells", 1e8)grid cells.
Value
An object of class ivt: a list with cells (a tibble of one cell
per row, keyed by 1-based member-id columns matching the StatCan metadata
Member IDs; under the default complete = TRUE it spans the whole grid and
carries symbol/status factors beside value), and metadata (table
identity, dimensions, geographies,
and footnotes). metadata$geographies carries, per member and where the
vintage stores them: the bilingual display label (geo_label,
geo_label_fr) and name (geo_name, geo_name_fr – on pre-DGUID tables
the EN/FR halves of the stored bilingual label), geo_uid (DGUID, or the
bare GEOUID on pre-DGUID tables), the aggregation level (geo_level), the
label hierarchy (geo_depth, geo_parent_id – the indentation the display
label carries, turned into a depth and the member_id of each member's
nearest shallower ancestor; both absent when the geography axis is flat),
the geography type / municipal status (geo_type, geo_type_abbr), province
abbreviation and codes (prov_abbr, alt_geo_code, pr_code), the
data-quality flag (dqf_code, with dqf_note and the table-level
dqf_legend), and the total non-response rate (tnr_short_form).
Where dqf_note is present it is accompanied by a dqf_note_truncated
logical flag: StatCan's writer stores each note in a single-byte-length
record, so notes longer than 252 characters are truncated in the source
file (2,448 of 63,404 geographies in 98-10-0129, 90 of 6,297 in
98-10-0478). The read is byte-exact – this is a container limitation, not a
decode gap, and there is no continuation to recover – but the flag marks the
affected members and a classed canivt_dqf_note_truncated warning is raised
so the loss is never silent. (Unlike a heuristic fallback it is not upgraded
to an error by options(canivt.strict = TRUE): the bytes are exactly what the
file holds.) The truncation is a container limit of the .ivt export only –
StatCan's authoritative metadata (the WDS getCubeMetadata geoAttribute or
the CSV-download metadata) stores the full untruncated note, so a consumer who
needs the complete text can recover it there.
Each footnote in metadata$footnotes carries a scope ("table",
"dimension" or "member"), the owning dimension name and, for member
notes, the member_id(s) it annotates – member_id for a single member and
member_refs for the full set (geography counts as a dimension). This matches
StatCan's own footnote linkage on the modern tables; on the pre-DGUID profiles
the same linkage is recovered from the (N) reference markers embedded in the
member labels (a note there can be cited by many members, so member_refs
lists them all).
metadata$geographies$has_data
flags which geographies carry a published non-zero value; on the pre-DGUID
tables
metadata$geographies$dqf_code (the per-geography data-quality flag from
the codebook) corroborates it (e.g. on the 2016 income table
98-400-X2016120 the flag's last digit is 9 exactly for the 888
geographies with no stored cells, which the Beyond 20/20 viewer renders
as suppressed).
Details
Every primary read is positional (header pointers, block directories, framed
value entries). When one does not resolve and a content-heuristic fallback
supplies values instead – or when directory entries point at page variants
that cannot be decoded – a classed warning (canivt_fallback /
canivt_skipped_pages) is raised naming the affected read. Set
options(canivt.strict = TRUE) to turn these into errors: on a file layout
this package has not been validated against, the fallback paths are the ones
most likely to misread silently.
Missing values
Only non-zero cells are stored, so absence covers both genuine zeros
and true missings (x suppressed, .../N not available). The two are
separated by a block each page appends after its dense value run, in one of
two forms selected by the page marker: a 1-bit absent mask – a strict
subset of the absent cells, where masked means a genuine zero and
unmasked means missing – or a self-describing reason-code array
carrying the .. / x / ... distinction itself. Both are decoded, at
every code width; only the array states a reason, so mask-derived rows
carry status = NA.
That block is what makes the default complete = TRUE output safe. The
rule it licenses, read off the file rather than off any published CSV, is:
an absent cell is the published zero unless its page's cell-status
block says otherwise. A page that writes no tail at all is a page with
nothing to flag – validated cell-for-cell against StatCan's published CSVs
on tables covering every page class – while a page whose tail cannot be
read has its absences published as zeros and counted
(canivt_absent_unclassified), never folded in silently.
The reason codes are numbered by the file, not by the format: each
table declares its own legend – symbol plus bilingual wording, in code
order – and the same symbol sits at different codes in different lineages
(x is code 2 in the NDM census tables and code 3 in the 2016
98-400-X crosstabs). status is read from that declaration, so it names
whatever the file names, including symbols outside the census vocabulary
(F too unreliable to be published, 0 s rounded to zero, ® not
released yet, z frozen series). A code the legend does not name, or a
table that declares no legend, is reported rather than guessed at
(canivt_status_code_unknown, canivt_status_legend).
Two limits are reported rather than hidden. On float64 pages a mask word of
mostly-ones is NaN-shaped and the writer's x87 quieting overwrites one
status bit per such word in the source file; those cells read as zeros
and cannot be recovered (canivt_status_nan_quieted). And on a few
2001/2006 vintages the page's word index also addresses a second, undecoded
block past the mask (canivt_status_extra_block).
The status travels out of the package with the data: ivt_tidy_missing()
labels the missing-cell table exactly as ivt_tidy() labels the values,
ivt_write_parquet() / ivt_write_csv() write it beside the data table as
a _missing sidecar, get_statcan_ivt() caches it under missing = TRUE,
and ivt_missing() gets it back from any of those forms. Under
complete = FALSE that sidecar is essential: the exported table then holds
only the cells that have a value, so reconstructing the full grid from it
alone fills every suppressed cell with a zero.
Examples
# A small real table (StatCan 98-10-0044) is bundled for examples/tests.
path <- system.file("extdata", "98100044.ivt", package = "canivt")
ivt <- read_ivt(path)
ivt
#>
#> ── IVT table 98100044 ──────────────────────────────────────────────────────────
#> Type of collective dwelling and collective dwellings occupied by usual
#> residents and population in collective dwellings: Canada, provinces and
#> territories
#> 448 cells | 14 geographies | 3 dimensions | 10 footnotes
#> geography labelled by name + uid
#> published grid: 448 values, 0 flagged as missing
head(ivt$cells)
#> # A tibble: 6 × 6
#> geo type collective value symbol status
#> <int> <int> <int> <dbl> <fct> <fct>
#> 1 1 1 1 24140 NA NA
#> 2 1 1 2 657920 NA NA
#> 3 1 2 1 13020 NA NA
#> 4 1 2 2 485320 NA NA
#> 5 1 3 1 300 NA NA
#> 6 1 3 2 11125 NA NA
