Skip to main content
← Back to Software

datamap

Data Preparation

Describe and check data you can't share

. net install datamap, from(...)
View on GitHub →

datamap hex logo

Version 1.10.6 | 2026-10-26

version Stata 16+ MIT license Part of Stata-Tools

Quick Start · Installation · Worked Examples · Key Options · Stata-Tools · Report a bug

datamap and its companion commands describe a dataset without exposing its rows: privacy-aware text or JSON maps, Markdown data dictionaries, console QC gates that stop a do-file when an expectation fails, a QA ledger of every gate run, and missing-value pattern tables. They are for analysts who must document or hand over data they cannot share.

Quick Start

After installing, write a Markdown data dictionary for Stata's auto data:

sysuse auto, clear
datadict, output(auto_dictionary.md) missing stats

The dictionary's Variables table, as written to the file (frequency cells below the default mincell(5) are suppressed):

Variable Label Type Missing Statistics/Values
make Make and model String 0 (0.0%) N=74; 74 unique values
price Price Numeric 0 (0.0%) N=74
Median=5,006; IQR=4,195-6,342
Mean=6,165 (SD=2,949)
Range=3,291-15,906
mpg Mileage (mpg) Numeric 0 (0.0%) Unique=21
12 (suppressed <5)
14 (6; 8.1%)
15 (suppressed <5)
16 (suppressed <5)
17 (suppressed <5)
18 (9; 12.2%)
19 (8; 10.8%)
20 (suppressed <5)
21 (5; 6.8%)
22 (5; 6.8%)
23 (suppressed <5)
24 (suppressed <5)
25 (5; 6.8%)
26 (suppressed <5)
28 (suppressed <5)
29 (suppressed <5)
30 (suppressed <5)
31 (suppressed <5)
34 (suppressed <5)
35 (suppressed <5)
41 (suppressed <5)
rep78 Repair record 1978 Numeric 5 (6.8%) Unique=5
1 (suppressed <5)
2 (suppressed, complementary)
3 (30; 43.5%)
4 (18; 26.1%)
5 (11; 15.9%)
headroom Headroom (in.) Numeric 0 (0.0%) Unique=8
1.5 (suppressed <5)
2.0 (13; 17.6%)
2.5 (14; 18.9%)
3.0 (13; 17.6%)
3.5 (15; 20.3%)
4.0 (10; 13.5%)
4.5 (suppressed <5)
5.0 (suppressed <5)
trunk Trunk space (cu. ft.) Numeric 0 (0.0%) Unique=18
5 (suppressed <5)
6 (suppressed <5)
7 (suppressed <5)
8 (5; 6.8%)
9 (suppressed <5)
10 (5; 6.8%)
11 (8; 10.8%)
12 (suppressed <5)
13 (suppressed <5)
14 (suppressed <5)
15 (5; 6.8%)
16 (12; 16.2%)
17 (8; 10.8%)
18 (suppressed <5)
20 (6; 8.1%)
21 (suppressed <5)
22 (suppressed <5)
23 (suppressed <5)
weight Weight (lbs.) Numeric 0 (0.0%) N=74
Median=3,190; IQR=2,240-3,600
Mean=3,019 (SD=777)
Range=1,760-4,840
length Length (in.) Numeric 0 (0.0%) N=74
Median=192; IQR=170-204
Mean=188 (SD=22.27)
Range=142-233
turn Turn circle (ft.) Numeric 0 (0.0%) Unique=18
31 (suppressed <5)
32 (suppressed <5)
33 (suppressed <5)
34 (6; 8.1%)
35 (6; 8.1%)
36 (9; 12.2%)
37 (suppressed <5)
38 (suppressed <5)
39 (suppressed <5)
40 (6; 8.1%)
41 (suppressed <5)
42 (7; 9.5%)
43 (12; 16.2%)
44 (suppressed <5)
45 (suppressed <5)
46 (suppressed <5)
48 (suppressed <5)
51 (suppressed <5)
displacement Displacement (cu. in.) Numeric 0 (0.0%) N=74
Median=196; IQR=119-250
Mean=197 (SD=91.84)
Range=79.00-425
gear_ratio Gear ratio Numeric 0 (0.0%) N=74
Median=2.96; IQR=2.73-3.37
Mean=3.01 (SD=0.456)
Range=2.19-3.89
foreign Car origin Numeric 0 (0.0%) Unique=2
0 Domestic (52; 70.3%)
1 Foreign (22; 29.7%)

Copied from demo/datadict_auto.md, written by demo/demo_datamap.do from a saved copy of auto through single(); the in-memory call above writes the same table, and only the file header above it differs.

Features

  • Aggregate by default. datamap and datadict write class-level summaries, suppress frequency cells below mincell(5), and cap distinct-value counts at uniqcap(1000); row-level sample values appear only when samples() asks for them, and datesafe withholds date values.
  • Your data is left as found. A successful in-memory run leaves the caller's observations, variables, sort order, labels, characteristics, and data signature unchanged; file-based routes (single(), directory(), filelist()) restore the data in memory.
  • One classifier for every command. datamap, datadict, and dataprobe classify variables the same way, with exclude(), class overrides, and a reusable config() file.
  • Gates that stop a do-file. dataprobe invariants such as expectn(), isid(), inrange(), and rule() exit with return code 9 when they fail; sanity bands() and review() items are kept apart so they can warn without halting.
  • A record of every check. dataqa keeps a ledger row per gate entry, dataqa assert halts when a gate failed or an expected dataset was never checked, and dataqa export refuses a release copy that shows a small cell.
  • Missing-data patterns. datamvp tabulates joint missingness, tests for monotone missingness, and draws bar, pattern, matrix, and correlation graphs; under mincell() or maskrare rare patterns are pooled.

Requirements

  • Stata 16 or later
  • No external package dependencies for the five commands
  • Pandoc is optional and is needed only when converting a datadict Markdown file to HTML, PDF, or Word

The repository demo uses the sibling logdoc and tc_schemes packages to regenerate console transcripts and the gallery graph. It installs them from the checkout when available and otherwise uses their public Stata-Tools sources.

Installation

capture ado uninstall datamap
net install datamap, from("https://raw.githubusercontent.com/tpcopeland/Stata-Tools/main/datamap") replace

Commands

Command Purpose
datamap Create aggregate text or JSON documentation for one dataset or a collection of .dta files
datadict Create a Markdown data dictionary with optional metadata, missingness, statistics, and separate-file output
dataprobe Profile data in the console and enforce declared invariants, sanity bands, and schema comparisons
dataqa Set session QA defaults and report, assert, compare, and export the ledger dataprobe writes
datamvp Tabulate missing-value patterns, test monotone missingness, and draw missingness graphs

How It Works

datamap, datadict, and dataprobe use the same classifier. It applies exclude() first, then explicit continuous(), categorical(), and date overrides, followed by string type, date formats, value labels or low cardinality, and continuous fallback; config() can supply reusable settings.

The default input is the dataset in memory. single() reads one .dta file, directory() scans .dta files, and filelist() reads a named list of files; add recursive for nested directories. datadict also accepts a line-delimited manifest() and can use a varlist only with in-memory data or single() input.

datamap writes text by default and switches to JSON with format(json). datadict writes Markdown. separate creates one output per input dataset, while saving() writes the variable-level metadata table used by downstream checks or other project tooling.

datamap and datadict aggregate values by default. datamap's samples() option deliberately adds row-level sample values; use exclude() and datesafe when those rows must not expose identifiers or date values. mincell(5) is the default small-cell threshold for categorical and binary frequencies, and uniqcap(1000) is the default cap for distinct-value counts.

On successful in-memory runs, datamap and datadict leave the caller's observations, variables, sort order, labels, characteristics, and data signature unchanged. File-based routes preserve and restore the caller's data; dataprobe always restores it, and datamvp is sortpreserve.

Choosing a Workflow

Goal Command Result
Build a technical inventory for a handoff or pipeline datamap Privacy-aware text or JSON map with classifications, summaries, and optional samples
Publish a readable variable reference datadict CommonMark Markdown with optional metadata, missingness, statistics, and notes
Inspect or gate a dataset before analysis dataprobe Console profile, expectation verdict, and optional profile or violation artifact
Keep a structured record of every gate run dataqa Ledger rows per gate entry, a register draft, and an assert that every expected dataset was checked
Understand joint missingness datamvp Pattern-frequency table, monotone test, generated indicators, or graph

Start with one dataset and the default output. Add exclude() and datesafe before sharing a map, then move to directory() plus recursive for nested collections; for datadict, use manifest() when the single-dataset contract is settled.

Worked Examples

The examples use only Stata's built-in auto data and temporary files created by the examples. Run each example independently unless it explicitly carries state forward.

1. Write an aggregate text map

sysuse auto, clear
datamap, output(auto_map.txt) quality missing(detail)

The output contains variable classes, aggregate summaries, value labels, missingness, and quality guidance without exporting sample rows.

2. Apply privacy controls and automatic structure detection

sysuse auto, clear
datamap, output(auto_private.txt) exclude(make) compact autodetect quality

compact keeps the disclosure summary and quick-reference content while omitting the longer guidance sections.

3. Produce JSON and save the metadata table

sysuse auto, clear
datamap, format(json) output(auto_map.json) saving(auto_metadata.dta, replace)

The JSON is suitable for programmatic consumers, and auto_metadata.dta contains one row per documented variable plus the shared classification and privacy fields.

4. Create a Markdown dictionary with statistics

sysuse auto, clear
datadict, output(auto_dictionary.md) title("Auto data dictionary") missing stats detail datasignature

Use columns() when the default dictionary columns should be replaced or reordered, and use notes() or changelog() for document-level content.

5. Document a saved dataset without changing the active data

sysuse auto, clear
quietly datasignature
local before "`r(datasignature)'"
tempfile auto_file
local auto_file "`auto_file'.dta"
save "`auto_file'", replace
datadict, single("`auto_file'") output(auto_from_file.md) missing stats
quietly datasignature
assert "`before'" == "`r(datasignature)'"

The single() route loads the file for processing and restores the caller's in-memory data afterward.

6. Build separate dictionaries from a temporary directory

tempfile marker
local inputs "`marker'_inputs"
local outputs "`marker'_outputs"
mkdir "`inputs'"
mkdir "`outputs'"

sysuse auto, clear
save "`inputs'/auto_original.dta", replace
replace mpg = mpg + 1
save "`inputs'/auto_changed.dta", replace

datadict, directory("`inputs'") recursive separate outdir("`outputs'") suffix("_dictionary")

outdir() must already exist. The command writes one Markdown file per dataset with the requested suffix.

7. Profile data and enforce expectations

sysuse auto, clear
dataprobe price mpg weight, detail outliers(3)
dataprobe, expectn(74) isid(make) notmissing(price mpg weight) inrange(mpg 10 50)

Gate failures exit with return code 9; add warn when violations should be reported without stopping the do-file.

8. Save a profile and compare a refreshed dataset

sysuse auto, clear
tempfile baseline violations
local baseline "`baseline'.dta"
local violations "`violations'.dta"
dataprobe, saving("`baseline'", replace)
generate byte readme_added = 1
dataprobe, compare("`baseline'") violations("`violations'", replace) warn

compare() reports added, dropped, type-changed, and class-changed variables; violations() can save a structured .dta artifact or write to a frame.

9. Inspect missingness patterns and draw a graph

sysuse auto, clear
datamvp price mpg rep78, percent sort monotone
datamvp price mpg rep78, graph(bar) gname(auto_missing)
graph export auto_missing.png, as(png) replace

datamvp treats empty strings as missing, returns pattern counts and monotone status, and supports generate() or save() when the pattern data should be reused.

10. Record gate results in a QA ledger

tempfile ledger
dataqa set maskrare mincell(5) ledger("`ledger'") run(demo)
sysuse auto, clear
dataprobe, gatesonly isid(make) rule("heavy": weight > 1500) bands(expectn(70 80) stat(mean mpg 18 25)) name(auto_cars)
dataqa report
dataqa assert, expect(auto_cars)
dataqa set clear

Each gate entry, passed or failed, becomes one ledger row with its kind (invariant, band, or review), masked observation, and expectation. dataqa assert exits with return code 9 if any gate in the run failed (or an invariant only warned under bare warn), the run has no rows, or a named dataset has no rows; dataqa report, markdown() drafts register rows that list the dispositions each kind allows, and dataqa report, bands lists every declared band and stat() invariant beside its observed value. A dataprobe call that exits with any error other than a gate failure leaves a call-error row, so dataqa assert cannot pass over a call that never ran. Under dataqa set ... collect a failing gate records its rows and returns 0, leaving dataqa assert as the single halting point; dataqa assert, baseline(label) halts when a dataset of the baseline run has no rows now, unless it is listed in optional(); dataqa set ... baseline() stores the baseline so compare and assert need no arguments.

Demo

From a Stata-Tools repository checkout, run the named demo script from the repository root:

stata-mp -b do datamap/demo/demo_datamap.do

The script installs the local datamap source plus logdoc and tc_schemes from the checkout or their public Stata-Tools sources, writes reproducible assets under datamap/demo/, and removes its temporary logs at the end. The generated assets are checkout documentation, not files installed by net install.

Horizontal bar chart showing the percentage missing for Biomarker A, B, C, and D (variables x1 to x4) in the shipped demo dataset

Command Reference

datamap

datamap [, options]

See datamap.sthlp for the full syntax, classification rules, output schema, privacy warnings, and examples.

datadict

datadict [varlist] [, options]

See datadict.sthlp for Markdown column contracts, manifests, metadata, conversion, and separate-output workflows.

dataprobe

dataprobe [varlist] [if] [in] [, options]

See dataprobe.sthlp for profile fields, gate syntax, reusable .dta check specifications, comparison artifacts, and return codes.

dataqa

dataqa set [options | clear]
dataqa report [using ledger] [, run() markdown() replace bands]
dataqa assert [using ledger] [, run() expect() baseline() baseledger() optional()]
dataqa export [using ledger], saving() [run() replace threshold()]
dataqa compare [using ledger] [, run() baseline() baseledger() ntol() stattol()]

See dataqa.sthlp for the session defaults, the register grammar, and the dispositions each kind allows; the ledger columns are listed under ledger() in dataprobe.sthlp.

datamvp

datamvp [varlist] [if] [in] [, options]

See datamvp.sthlp for pattern filters, monotone tests, generated indicators, graph options, and saved results.

Key Options

datamap

Options Purpose
single(), directory(), filelist(), recursive Input routes and nested-directory scanning.
output(), format(text), format(json), separate, append, saving(), config() Output format, routing, metadata, and reusable settings.
nostats, nofreq, nolabels, maxfreq(25), maxcat(25), mincell(5), uniqcap(1000), compact, noguidance Summary detail, thresholds, and report length.
exclude(), continuous(), categorical(), date(), dateformat(), detect(), autodetect, panelid(), survivalvars() Privacy exclusions, classification overrides, and structure detection.
datesafe, samples(), missing(detail/pattern), quality, quality2(strict) Sample-row privacy, missingness, and quality diagnostics.

format(text) is the default and uses datamap.txt when output() is omitted; JSON uses datamap.json. append is available for combined text output, not JSON.

datadict

Options Purpose
single(), directory(), filelist(), manifest(), recursive Input routes and nested-directory scanning.
output(), separate, outdir(), suffix(_dictionary), saving(), config() Output routing, metadata, and reusable settings.
title(), subtitle(), version(), author(), date(), notes(), changelog() Document metadata and explanatory sections.
missing, stats, detail, columns(), datasignature Dictionary content and technical metadata.
exclude(), continuous(), categorical(), datevars(), dateformat() Privacy exclusions and classification overrides.
maxcat(25), maxfreq(25), mincell(5), uniqcap(1000) Category, frequency, suppression, and distinct-count thresholds.

The default output is data_dictionary.md. date() sets document metadata, while datevars() overrides which variables are classified as dates.

dataprobe

Options Purpose
single(), id(), exclude(), continuous(), categorical(), date(), detail, maxfreq(20), rare(), outliers(0), mincell(0), maskrare Profile variables, distributions, rare cells, and outliers.
nomissing, patterns Missingness summaries and pattern analysis.
expectn(), isid(), nodups, require(), notmissing(), inrange(), allowed(), forbid(), regex(), notvalues(), rule(), byrule(), stat(), binary(), events(), intervals(), keyset(), constant(), sets(), smallcells() Invariant gates.
bands(), bandwarn, coverage() Sanity bands kept apart from invariants; bandwarn makes only bands warn.
review(), heaping(), groupstat(), complete(), jumps() Review items that print and never halt (or gate with a threshold).
warn, gatesonly, onlyflagged, show(flagged), minversion() Halting behavior, display filters, and a version probe.
by(), byfreq, over(), checks(), makespec(), compare(), saving(), violations(), ledger(), name(), signature, config() Grouped checks, reusable specs, comparisons, artifacts, the QA ledger, and settings.

maskrare masks every printed count below the threshold (gate messages, missingness and groupwise blocks, the patterns table), withholds percentages and complements that would recover one, and replaces minima and maxima with guarded p1 and p99. stat() accepts mean, sd, percentiles, sum, n, distinct, pmiss, ess (Kish effective sample size), and ratio num den, each with an optional if per entry. events() requires an event at every covariate level, intervals() checks overlap, gaps, order, and event placement on its own sort, keyset() compares keys with a saved file, constant() requires time-fixed values within a key, byrule() evaluates a rule under by byvars (sortvars): so _n, _N, and [_n-1] count within groups, and smallcells() is a publication gate on a results table (no released count or difference such as n1-e1 in 1 to m-1). coverage() takes endq() to test the delivery ends on quantiles rather than the raw extremes, and gap(none) to declare that no delivery lag was sourced; groupstat(n mean p99: x, by(g)) prints several statistics in one table, and by(g) byfreq adds frequency tables within each group. Gates inside bands() are sanity bands: with bandwarn they warn while invariants still halt. Under gatesonly the profile is skipped and only the gate columns are kept. Gate failures exit with return code 9; a run where every gate passes prints a PASS: line naming the dataset and the active masking.

datamvp

Options Purpose
minfreq(1), notable, skip, sort, nodrop, percent, cumulative, ascending, minmissing(), maxmissing(), nosummary, wide Pattern table filters, ordering, and display.
correlate, monotone, bytable(), generate(), save() Missingness analysis, percent missing by group, and reusable outputs.
mincell(), maskrare, top() Small-cell masking, pooling of rare patterns, and a cap on the pattern table.
graph(bar|patterns|matrix|correlation), scheme(), title(), subtitle(), gname(), gsaving(), nodraw, varlabels, horizontal, vertical, top(), barcolor(), misscolor(), obscolor(), textlabels, colorramp(), gby(), over(), stacked, groupgap(), legendopts(), graphoptions() Graph types, styling, grouping, and export.

Under mincell() or maskrare, patterns below the threshold are pooled into one final row, so the displayed frequencies still sum to N. top() caps the pattern table (at 20 by default under masking) and pools the rest into the same row. The default graph orientation for graph(bar) is horizontal. matrix graphs automatically sample up to 500 observations when the dataset is larger and accept only the documented sample(#) and sort suboptions. The command accepts at most 244 variables. generate() checks every output name before creating variables and refuses to overwrite existing targets.

Stored Results

The help files document the complete stored-result contracts. The following tables list the results most useful in a pipeline.

datamap

Result Meaning
r(nfiles), r(nobs), r(nvars) Input-file, observation, and variable counts; r(nobs) and r(nvars) are returned when applicable.
r(format), r(output), r(input_source), r(mincell) Output mode, output path, input route, and effective small-cell threshold.
r(n_categorical), r(n_continuous), r(n_date), r(n_string) Counts by classified variable type.
r(n_excluded), r(n_suggested_exclude), r(excluded_vars), r(suggested_exclude) Explicit exclusions and identifier-like suggestions.
r(categorical_vars), r(continuous_vars), r(date_vars), r(string_vars) Classified variable lists.
r(metadata) Saved metadata path when saving() is used.

datadict

Result Meaning
r(nfiles), r(nvars_total), r(nobs_total) Number of inputs and total variables/observations processed.
r(mode), r(files), r(outputs), r(output) Input mode, input paths, output paths, and combined output path when applicable.
r(metadata) Saved metadata path when saving() is used.

dataprobe

Result Meaning
r(N), r(complete_cases), r(complete_pct) Profile denominator and complete-case summary.
r(n_checks), r(n_passed), r(n_failed), r(n_violations), r(n_errors), r(n_warnings), r(n_reviews) Gate, violation, warning, and review counts.
r(violations), r(failed_checks), r(checks_run) Families of failed entries and of the gates that ran.
r(version), r(dataset), r(masked), r(ledger), r(ledger_seq) Installed version, dataset name, masking, and ledger position.
r(singlelevel_vars), r(n_singlelevel) Variables with exactly one observed nonmissing value (not set on the gatesonly fast path).
r(compare_added), r(compare_dropped), r(compare_type_changed), r(compare_class_changed), r(compare_changed) Schema comparison counts.

dataqa

Result Meaning
r(defaults) dataqa set: the session-default option string.
r(N), r(n_datasets), r(n_failed), r(n_warned), r(ledger), r(run) dataqa report: rows read, datasets, failed and warned rows, and the ledger and run read; r(markdown) and r(n_rows) with markdown(); r(n_bands) with bands.
r(N), r(n_failed), r(n_errors), r(missing), r(run) dataqa assert: rows, halting rows, call-error rows, and expected datasets without rows, set also when it halts with r(9); with baseline(), r(baseline), r(n_missing_base), r(missing_base), r(n_optional_absent), and r(optional_absent).
r(N), r(n_scope_dropped), r(saving) dataqa export: rows written, scope expressions blanked, and the release copy.
r(n_flags), r(baseline) dataqa compare: items to review and the baseline run.

datamvp

Result Meaning
r(N), r(N_complete), r(N_incomplete), r(N_patterns) Observation, completeness, and pattern counts.
r(N_vars), r(max_miss), r(mean_miss), r(N_mv_total) Variable and missingness summaries.
r(N_monotone), r(pct_monotone), r(monotone_status) Monotone-missingness results when monotone is requested.
r(corr_miss) Missingness correlation matrix when correlate or a correlation graph is requested.
r(N_patterns_pooled), r(mincell), r(masked), r(miss_by) Pooled patterns, mask threshold, masking flag, and the bytable() matrix.

Assumptions and Limits

  • datamap and datadict use aggregate summaries by default; samples() is an explicit row-level export option.
  • mincell() suppresses categorical and binary frequency cells below the threshold, and uniqcap() reports a lower-bound count when the distinct-value cap is exceeded.
  • datamap and datadict in-memory failures are not rolled back after partial processing; use file-based input or a copy when failure isolation is required.
  • datadict requires an existing outdir() for separate outputs, and checks(), compare(), and file-based violations()/makespec() routes use Stata datasets rather than text specifications.
  • dataprobe treats warn as a non-halting gate mode for every gate and bandwarn as one for sanity bands only; without them, failed expectations exit with return code 9.
  • Stored results are never masked; only printed output is. The working dataqa ledger and datamvp save() files hold small cells and belong with the data; dataqa export writes the copy that may leave the server and refuses one that shows a small cell.
  • datamvp is limited to 244 analyzed variables, and generated indicator names are shortened and disambiguated to stay within Stata's name limit; reserved-name or existing-target collisions stop with an error before any output variable is created.

References

datamvp is a fork of Jeroen Weesie's mvpatterns (STB-61: dm91); attribution and implementation notes are in datamvp.sthlp.

dataprobe's stat(ess ...) is the Kish effective sample size, (sum w)^2 / sum w^2: Kish, L. 1965. Survey Sampling. New York: Wiley.

QA

QA suites and how to run them are documented in qa/README.md.

Version History

1.10.6 (2026-10-21)

  • Fix: dataprobe, by() maskrare no longer hangs on outputs with many small groups and linked counts (a 3,000-row profile in 40 groups with byfreq and gates ran past 12 minutes in 1.10.5; auto by(foreign) byfreq past 10). The check that protects linked counts has one budget per call, shared by its repeats and by its linear programs; when the budget runs out before every count is shown safe, every group is pooled, a note says so, and r(linkaudit_capped) is 1. Output within the budget is unchanged, and the budget counts work, not time, so the same data and call always give the same output. Change: a few slow outputs that 1.10.5 finished now fall back to the pooled output (nlsw88 by(race) byfreq: 198 s with groups in 1.10.5, 62 s pooled in 1.10.6).
  • Fix: datamap and datadict with directory() refuse a file whose name holds $, a backtick, or a double quote, naming it, before writing anything (1.10.5 read a$b.dta as a.dta and stopped with r(601)); under recursive a subdirectory with such a name is refused the same way.
  • Fix: dataprobe, by() maskrare byfreq no longer stops with r(3301) when a group's frequency table has one nonempty level that is withheld.
  • Fix: review() counts printed per by() group under maskrare are protected with the other linked counts when the condition's total prints elsewhere (its rows are one level of a profiled variable's frequency table, or a profiled variable's missing rows): two groups printing <5 beside a level count of 8 were each 4. Such a condition's line prints 0 or [suppressed] in a pooled group, as the other gates do; a condition whose total prints nowhere is shown as in 1.10.5.
  • Fix: datadict prints values from 1e6 to below 1e15 with every digit (10,032,845, not 1.00e+07), and larger ones in %21.0g.
  • Fix: when a rerun on (modified) data leaves both its row and a full-data row of the same gate standing, dataqa compare reads the row with the worst status (then the unmodified row), so it flags a failure that report and assert count; it used the later row.
  • Faster, same output (one core, measured against 1.10.5): dataprobe builds each byfreq variable's group tables from one table, reuses the CONTINUOUS block's summaries under maskrare, and does less work per step of the linked-count check (byfreq profiles 2-4x faster); datadict builds only the columns it prints or saves (default columns 1.6x on 100,000 x 100, 6.6x on 1,000,000 x 20); datamap ... saving() writes its metadata from the report pass instead of a second pass (about 2x); datamvp computes missing counts, patterns, the monotone test, bytable() cells, and graph(bar) gby()/over() cells in one pass (1.8-4x); dataqa report counts every dataset in one pass (4-5x with markdown(), 10x without, on a 98,000-row ledger); distinct counts of variables with few levels use one tabulate.

1.10.5 (2026-10-21)

  • Fix: under maskrare with by(), a count withheld in one block can no longer be recalculated from the others in the same output. The counts a profile prints per group (missing counts, complete cases, MissVars, byfreq cells, the per-group gate counts of notmissing(), complete(), stat() n/pmiss/0-1 sum, binary(), inrange() on a profiled date, allowed()/forbid()/notvalues(), and the groupstat() n and 0/1 sum cells with the call's by()) are checked together against every relation among them and the totals printed elsewhere (a count from 1 to m-1 must keep two possible values); a group whose count would be pinned joins the pooled groups. In 1.10.4 a variable missing 48 of 50 rows in group 1 and never in groups 2 and 3 gave back group 1's 2 nonmissing rows (MISSINGNESS 48 beside MissVars 0), and two groups printing <5 beside a total of 8 were each 4.
  • Change: a pooled group's linked gate count prints [suppressed] whenever it is nonzero (no longer <5), and groupstat() with the call's by() pools the groups the profile pools.
  • Fix: without the mask, a failing stat() mean or percentile of a date that its date does not show exactly (a fraction of a day, milliseconds under a %tc format that shows seconds) prints at full precision beside the date (874.5125 (24may1962)), as do a failing inrange()'s minimum and maximum on a date and the date tested by a failing coverage() early_start or late_end; a %tc value prints all 13 digits, in groupstat() band failures too, which now add the number only when the date is not exact. Under the mask they stay at month precision.
  • Docs: dataprobe help states the by() masking guarantee, its relations, and what it does not cover.

1.10.4 (2026-10-21)

  • Fix: under maskrare with by(), a group printed "all but <m" beside a group printed exactly no longer gives its small count back as the total minus the exact count (MISSINGNESS 58 minus a group's 10 was 48, the masked group's 50 rows minus 2); every per-group count with a total printed elsewhere is tested on its complement as well, in the GROUPWISE blocks and the per-group gates.
  • Fix: groupstat(n) cells are tested against the call's N when only a review() or complete() item prints it, and against the cells' own total over the entry's if rows, so a printed scope count minus the shown cells no longer gives back a pooled-away group; a 0/1 sum column follows the same test in profile mode.
  • Fix: datamap and datadict withhold the mean, SD, and skewness of a continuous() variable with three distinct values under mincell(), as for two (mean and SD with N gave back each value's count), in text, JSON, saving(), and the stats column.
  • Fix: dataqa again lets a file fixed on disk, reloaded, and rerun supersede its earlier failure (1.10.3 required the same n_scope); only a rerun on (modified) data cannot supersede a row of the unmodified data.
  • Fix: every groupstat() cell fits its column: a withheld pooled sd reads [suppr.], and a long date format or a %tc value prints as a %td date; a small cell keeps <m rather than [suppr.], which stands only for an exact count of the mask or more.
  • Fix: an unmasked groupstat() band failure on a date prints the value beside the date (1865.5 (08feb1965)), so a value just outside the band cannot read as the bound; under the mask it stays at month precision.
  • Docs: datamap help describes what compact writes and leaves out.

1.10.3 (2026-10-20)

  • Fix: distinct counts on data with more than 50,000 rows skipped rows 50,001-200,000, so datamap and datadict could report a variable as constant (Unique 1) or misclassify it; every row is now read.
  • Fix: datamap and datadict documenting the data in memory no longer reset c(changed) or point c(filename) at a temporary file (a later use without clear silently discarded unsaved edits); they no longer write a full copy of the data either. Zero-observation data in memory are documented instead of refused.
  • Fix: dataprobe groupstat() leaves rows with a missing group out of the pooled value, so the pool matches its groups; a masked groupstat() table now sets r(masked) and the ledger's obs_masked.
  • Fix: under maskrare, stat() and groupstat() withhold sd, ess, and ratio when they would give back a small count (the ess of a 0/1 weight is its number of ones), and KEY STRUCTURE withholds a median held by few keys.
  • Fix: datamap, detect(binary) never lists a date variable, and a date forced into categorical() gets no frequency table under datesafe; saving() leaves categorical summaries missing while mincell() is above 0 and continuous and categorical summaries missing under nostats.
  • Fix: a continuous(), categorical(), date(), or datevars() name matching no variable is r(111) instead of being ignored (one bad name had also disabled every wildcard in the list), and a string variable in continuous() or date() is r(109).
  • Fix: dataprobe refuses expectn() with reversed bounds and a range bound that refers to a variable inside an expression (ub*1 read the first observation); a missing stat() lower bound is reported as missing; allowed(), forbid(), and notvalues() match a value holding a double quote.
  • Fix: a variable named nomask no longer switches a session's maskrare off in datamvp and dataprobe.
  • Fix: datadict prints dataset labels in file modes, survives notes holding a backtick or "', calls a variable a "Unique identifier" or "Year of observation" only when its values agree, no longer prints 3=3 for an unlabelled value, writes "No observations" for an empty file, and writes IQR=-224 to -73.50 rather than IQR=-224--73.50.
  • Fix: datamvp string gby()/over() levels holding a quote or backtick keep every group, a label holding "' prints, generate() names and truncated text stay valid UTF-8, wide prints patterns wider than the line in full, and notes say when gby()/over() drop rows or when no missing values leave requested outputs unproduced.
  • Fix: datamap keeps the previous output when a later dataset fails, refuses append with separate and recursive without directory(), finds .DTA files, stops a symbolic-link loop, prints %tc/%tC dates with dateformat(%d...), reports panel structure from panelid() alone, checks single() paths, and keeps long dataset names in saving().
  • Fix: datamap VALUE LABEL DEFINITIONS lists the values each label defines (with .a-style codes) instead of every data value, which printed exact dates under datesafe and unique values past maxfreq() and mincell().
  • Fix: under datesafe, a date-formatted variable forced into continuous() prints no mean, quantiles, or range in text, JSON, or saving(), and samples() suppresses every date-formatted variable.
  • Fix: while mincell() is above 0, datamap and datadict withhold the mean and SD of a two-valued variable forced into continuous() and the mean of a categorical survey weight; datadict, saving() leaves categorical summaries missing, as datamap does.
  • Fix: datamap writes the Sort Order (text and JSON), keeps a failing dataset's previous output under separate, records each dataset's own _map file in saving() under separate, and writes samples(), quality, and detect(binary) under compact.
  • Fix: datadict replaces its dictionary only when every dataset was documented; config() missing = no leaves the Missing column off; a manifest or config file with a byte-order mark is read; a repeated varlist name is documented once; Filename keeps the file's own extension; a categorical date follows dateformat(); labelled extended missing codes are listed; non-ASCII dataset names get correct headings and working anchors; a backslash before a pipe survives; e-notation ranges read "a to b".
  • Fix: a reversed range in continuous(), categorical(), date(), or datevars() is r(111) instead of being ignored; a saving() name without .dta is checked before any output is written; a non-integer threshold from config() is r(198) naming the option.
  • Fix: dataqa gate key includes the kind, and a rerun supersedes only when it tested the same rows, so neither a bands() band nor a subset rerun clears an earlier failure; expect() matches the exact name; export blanks the signature of a dataset below threshold() rows and literal values in value-gate text, and refuses the session's ledger as saving(); report prints ledger text literally; compare names the by() group, reads ledgers without a signature column, and dataqa set mincell(-1) is r(198).
  • Fix: under maskrare, dataprobe stat(sum) and groupstat(sum) cells are suppressed when their nonmissing rows, nonzero rows, or either value of a two-valued variable number 1..m-1.
  • Fix: under maskrare, dataprobe GROUPWISE SUMMARY, MISSINGNESS, and FREQUENCIES withhold a cell when the total printed elsewhere minus the exactly printed cells is 1..m-1, and groupstat(n) does the same against the printed N; in a by() group whose size is withheld, every count of m or more prints [suppressed]; Unique, unique=, and level counts are masked when N minus unique is 1..m-1.
  • Fix: under maskrare, in a dataprobe profile with by(), a count printed per group can no longer be set against its total printed elsewhere in the same output to give back 1..m-1 (GROUPWISE blocks, byfreq, notmissing(), binary(), complete(), stat(n/pmiss/sum), inrange() on a date, allowed()/forbid()/notvalues(), groupstat(n/sum)); every block withholds the same group.
  • Fix: under maskrare, the dataprobe heaping 1st-of-month share is masked when the other 1sts are 1..m-1; sd needs m rows of each value for variables with two or three values; groupstat band failures name a withheld group by number, and date cells print as dates.
  • Fix: dataprobe sets r(masked) when only frequency cells were suppressed; exclude() withholds variable values in stat(), coverage(), events(), and groupstat(); saving() writes the loaded data's datasignature; a stat() upper bound of . is open for dates too and coverage() names a missing bound; compare() with a varlist no longer reports out-of-varlist variables as dropped.

1.10.2 (2026-10-20)

  • Cleanup steps that run on the way out no longer fail silently. dataqa reports when it cannot restore the session's matastrict setting, dataprobe byrule() reports when it cannot restore the caller's row order after an error, and dataprobe reports when the error row cannot be written to the ledger. A cleanup failure on an otherwise successful call now sets the return code.

1.10.1 (2026-10-13)

  • datadict no longer prints a stray space in the heading and table-of-contents entry of a dataset whose file name begins with an underscore (_demo_cohort.dta gave ## 1. followed by two spaces and Demo Cohort, with anchor #1--demo-cohort; now ## 1. Demo Cohort and #1-demo-cohort).

1.10.0 (2026-10-12)

  • Fix: datamvp graph title() and subtitle() no longer print their own quote marks: title("Missingness by variable") drew "Missingness by variable". Each quoted string is now one line, as in Stata's title(); text with no quotes is one line; title("") draws no title; quotes, dollar signs, and backticks inside the text still print as written. Affected every graph type.
  • Fix: datamvp title() and subtitle() suboptions after a comma (size(), color(), position(), ring(), span, ...) are passed to the graph instead of being drawn as text; title(, ring(0)) restyles the default title.
  • datamvp, graph(bar) gby() draws its default title once over the facets instead of once per facet, as graph(patterns) gby() already did.
  • Fix: datamvp, graph(bar) gby() and over() keep a value label written with compound quotes literally instead of stripping them.
  • New: datamvp, varlabels labels the variable axis of graph(bar) and graph(matrix), both axes of graph(correlation), and the stacked legend with variable labels; variables without a label keep their name.

1.9.3 (2026-10-12)

  • Renamed: datacheck is now dataprobe. The old name collided with the SSC package datacheck (Bhaskaran, 2011): either install blocked the other, and net install, replace overwrote one package's datacheck.ado with the other's. Replace datacheck with dataprobe in do-files; syntax, options, stored results, and the help dataprobe file are otherwise unchanged. Ledgers and makespec() spec files written by datacheck are read unchanged. Uninstall the old version first (ado uninstall datamap) so the old datacheck.ado does not linger. Version-history entries below 1.9.3 keep the old name.
  • Fix: dataprobe inrange(), stat(), and makespec() bounds are compared at full double precision, so a makespec() spec passes checks() on the data it was written from.
  • Fix: groupstat() under maskrare masks a count cell whose complement in the group's rows or nonmissing rows is a small cell, masks a pmiss cell when the group's missing or nonmissing count is small, and withholds the pooled pmiss value when any group cell is masked.
  • Fix: datamap, missing(pattern) reports "Observations with complete data" as suppressed (complementary) when the all-observed pattern is masked.
  • Fix: dataqa report, assert, export, and compare refuse (r(610)) a ledger whose status is not a string or holds a value other than pass, fail, warn, review, or error; dataqa assert matches dataset names containing spaces.
  • Fix: datamvp, graph(patterns) no longer fails when the data hold variables named patid or patorder.
  • Fix: the dataset date span is computed within one date format family; %th and %ty are no longer mixed.
  • Fix: nofreq also suppresses the value frequencies in the detect(binary) section.

1.9.2 (2026-10-05)

  • help datamap opens with a Package commands section linking the help files for datadict, datacheck, dataqa, and datamvp.
Earlier versions

1.9.1 (2026-10-04)

  • dataqa assert, optional() with no baseline set (neither baseline() nor dataqa set baseline()) is ignored with a note instead of stopping with r(198), so a study do-file can carry optional() before its first baseline run exists. baseledger() without a baseline is still r(198).

1.9.0 (2026-10-04)

  • Fix: datacheck varname, by() on one variable with no missing values stopped with r(3202) in the GROUPWISE MISSINGNESS block.
  • datacheck calls that fail to run no longer vanish from the QA ledger: any exit other than a gate failure (a parse error, a misspelled option, a bad expression, a missing variable) appends a call-error row carrying the return code, and dataqa assert halts on it. Error rows are never superseded by a later call; dataqa export refuses a run that holds one.
  • dataqa set ... collect: under a ledger, a failing gate prints its block, writes its rows, and returns 0 with r(n_failed) set, so capture noisily is no longer needed around gate calls; dataqa assert is the halting point. Without a ledger, or when the ledger append fails, the call still exits 9.
  • dataqa assert, baseline(label) [baseledger() optional()] halts when a named dataset with rows in the baseline run has none now, unless it is listed in optional().
  • dataqa report, bands lists each band and stat() invariant of the run, passing or failing, with its declared range and observed value as the ledger stores them (masked values stay masked); with markdown() the table is added as its own section.
  • datacheck byrule(byvars (sortvars): "label": exp): a rule evaluated under by byvars (sortvars): on datacheck's copy, so _n, _N, and subscripts count within groups; the caller's data and sort order are unchanged. rule() and review() print a note when a subscripted expression runs on data with no sort order.
  • datacheck smallcells(countexp ... [if exp]): a publication gate on a results table; no released count, and no difference written as an expression such as n1-e1, may lie in 1 to m-1 on rows in scope, where m is the call's mincell() threshold. Negative and non-integer counts fail; missing values count as suppressed.
  • coverage(..., endq(#)) tests early_start and late_end on the # and 1-# quantiles, so one stray record cannot hide a truncated delivery; coverage(..., gap(none)) declares that no delivery lag was sourced and drops the two end checks.
  • groupstat(stat stat ...: var, by()) prints several statistics in one table, one column per statistic, with the same ledger rows as the single-statistic entries.
  • dataqa set ... baseline(label) [baseledger(file)] stores the baseline with the session defaults, so dataqa compare and dataqa assert need no arguments; an explicit option still wins. dataqa.sthlp gains an end-to-end pipeline example (version probe, synthetic switch, dataqa set ... collect baseline() replace, named gates, compare, report, export, assert).
  • A failing groupstat(..., band()) reports how many groups, and how many of their rows, lie above and below the band (masked under maskrare); the summary is added to the failing rows' message and observed text, and observed_num is unchanged.
  • datacheck ..., by(g) byfreq prints the categorical and string frequency tables within each group, masked cell by cell.
  • The ledger grp column records a by() group by its values (by(site year | site=3 year=2015)), and dataqa compare pairs groups on them, so a level added between extracts no longer shifts every later group; a compare of an old-format baseline against a new-format run is flagged rather than paired by number. Under maskrare a withheld group keeps its number.
  • Fix: a blank string by() group printed its group number as its label; blank and missing groups now print as (blank) or their missing code. dataqa readers resolve a ledger path with the same rule as the writers (.dta added only to a name without a suffix), so a stray file without a suffix is no longer read in place of the ledger.
  • Fix: a data value or value label holding a backtick, a quote followed by an apostrophe, $, or SMCL braces broke datacheck tables and gate messages (display text leaked into the line) and stopped datamap text and JSON output, datadict, and saving() with r(132) or r(198); such values now print and write literally.
  • Under maskrare, the above/below summary of a failing groupstat() band withholds a direction's row count when one of its groups is below the mask. smallcells() refuses a threshold below 2, and dataqa assert accepts compound-quoted dataset names with spaces in optional() and expect(). A non-integer by() value is recorded as its shortest exact decimal text.
  • Fix: a failing datacheck call whose command line held a compound quote (ledger(`"file"'), name(`"a b"'), a compound-quoted rule()) wrote no call-error row (r(132) in the writer), so dataqa assert could pass over it; the row is now written for every quoting form.
  • Fix: a rule(), review(), or byrule() value written in compound quotes, rule(`"label: make != """'), stopped with r(132) or r(109); it is unwrapped and parsed like the plain form. A byrule() spec quoted as a whole is refused with a message naming the form.
  • Fix: a line break in a variable label, value label, dataset label, or sample value broke the line structure of datamap text output; it is written as a space, as string levels already were. dataqa assert prints ledger messages with quote, macro, and SMCL characters as text, and dataqa compare its gate labels, as datacheck does.
  • datacheck.sthlp gains a registry delivery-checks table (which gate catches dates outside the window, truncation, a missing year, or a share outside the window) and an idiom map from hand-written patterns to gate families.

1.8.2 (2026-10-01)

  • Faster on large files, with identical output: datacheck, by() counts group sizes, complete cases, and per-variable missingness in one pass rather than one pass per group and variable; datadict reuses the classifier's missing and distinct counts, and computes percentiles only for the rows that report or save them; datamvp, correlate fits the tetrachoric correlations on the distinct missingness patterns with frequency weights (the same estimates).
  • datacheck frequency tables and datamvp pattern graphs order tied counts by value and pattern, so which levels show and which fall past maxfreq()/top() or into a masked pool no longer varies between runs.
  • datamap, datadict, and datacheck put the caller's S_1, S_FN, and S_FNDATE globals back exactly as they were; a value holding $name or `name' came back rewritten. All four commands (with datamvp) no longer stop with r(132) when one of those globals ends in an unmatched backtick.
  • datamap, saving() and datacheck, saving() record a variable's first note and characteristic literally; $name and `name' inside them were expanded.

1.8.1 (2026-09-30)

  • datamvp preserves the caller’s exact native reshape globals across matrix graphs and graph refusals, including opaque existing values and initially absent aliases.

  • datacheck preserves embedded filename quotes until ledger-path validation so its existing quote restriction refuses the actual path before output work.

  • datamvp refuses ordinary and extended missing groupgap() values before graph work instead of silently using native default spacing.

  • dataqa restores the caller's exact matastrict setting on success and refusal, including the first native frame-link initialization.

  • dataqa: dataqa set ..., replace removes the rows already under run() from the ledger (a note is printed when a run already has rows); report, assert, and export read only the latest call of each gate, so a fixed failure no longer halts a same-run rerun (r(n_superseded)), while a rerun with changed bounds is a new gate and leaves the old failure visible; compare keys entries the same way; export refuses a saving() path that resolves to the ledger it reads (r(602)).

  • Disclosure: datamap and datadict withhold a complementary cell when a lone suppressed cell could be recovered from N and the missing count, and apply mincell() to the survival event rate and the complete-case count; datesafe withholds date-class survival time ranges; saving() rows of excluded variables carry no value-label name, notes, or characteristics. datacheck masks a small complement in stat() n/sum/distinct/pmiss, sets(values), the DATE window, and KEY STRUCTURE, and withholds an excluded variable's Miss% and inrange() extremes. datamvp graphs follow the table's mask: graph(bar) withholds masked bars, graph(patterns) draws only table-shown patterns, and graph(matrix), which plots individual observations, is refused under masking.

  • Display: a masked share prints as [masked] (or n/a for an empty denominator) rather than .%, and an excluded variable's share as [excluded], which widens the datacheck QUICK REFERENCE Miss% column; groupstat() columns stay separated for 12-character names, a failing band value prints at full precision, and with relative the cells show the group-to-pooled ratio that the band tests.

  • exclude() in datamap and datadict expands wildcards and ranges; a range that cannot be resolved in a file is an error (r(111)) before any output is written, and a token matching no variable prints a note. filelist() keeps quoted names with spaces; a quoted whole list is split into names only when the words cannot be one path (bare names, or every word written with .dta or a path), so a mixed form such as filelist("sub/a b") must list each name separately. Zero-observation files are documented. String unique counts exclude the empty string in every command.

  • datacheck: checks() isid/expectn rows add gates instead of replacing the command-line ones, and isid() takes \-separated keys; forbid()/notvalues() with an explicit missing code (., .a-.z) now match that code, so a call that passed can halt; regex() on a numeric variable matches integers in full and other values at %16.0g; makespec() writes specs that read back as passing on the same data (a string variable with a level containing $, a quote, a backtick, or \ gets no allowed row); keyset() accepts quoted filenames with commas or spaces; heaping() and coverage() explain how to give a date variable a daily display format when they refuse it.

  • Gates that passed on data they did not test now fail: events() with an all-missing covariate, groupstat() bands with an empty scope or no group tested, and intervals() rows with a missing id; float values are compared at float precision at intervals() tol(), jumps() ratio(), and groupstat() percentile bounds.

  • datamvp restores the random-number state after sampling for graph(matrix), and preserves an existing or absent S_2 legacy global on success, the no-missing shortcut, and refusal.

1.8.0 (2026-09-30)

  • Disclosure: under maskrare, datacheck masks every printed count below the threshold, including failing-row counts in gate messages and in violations(), the MISSINGNESS and GROUPWISE blocks (small groups pooled), key structure, and profile counts, and never prints a percentage or complement that would recover one; isid() reports only the masked number of duplicated rows. datamvp gains mincell() and maskrare, pooling patterns below the threshold into one row, and datacheck, patterns maskrare passes the masking on. r(masked) reports whether anything was masked.
  • Speed: gatesonly without compare(), saving(), or makespec() skips classification and the profile and keeps only the gate columns (a 44-column, 1M-row gate call fell from 39 s to under 1 s), no longer scans groupwise missingness, and tags isid()/nodups keys once per call instead of once per by() group.
  • New invariant gates: events(), intervals(), keyset(), constant(), and sets(); stat() adds sum, n, distinct, pmiss, ess, and ratio with an optional if per entry; inrange() takes several variables per bound pair and open bounds.
  • bands() and bandwarn keep sanity bands apart from invariants in one call; new coverage() band and review(), heaping(), groupstat(), complete(), and jumps() review items.
  • The verdict names the dataset (or name()) and the active masking; minversion() and r(version); signature; ledger() appends one row per gate entry, passed or failed.
  • New command dataqa: set (session defaults read by datacheck and datamvp), report (register draft with the dispositions each kind allows), assert, export (a release copy that refuses small cells), and compare (drift against a baseline run).
  • datamvp also adds bytable(), applies top() to the pattern table, and sizes the Obs and Miss columns from the largest count with comma format throughout.
  • Fixed: range bounds written as date strings (inrange(dt 01jan2020 31dec2020)) failed with r(198) instead of being parsed as dates; ISO and slash dates (2020-01-01) are read as dates rather than as arithmetic, %tc bounds in milliseconds, and a bound naming a variable is refused.

1.7.1 (2026-09-29)

  • Apply datesafe to saved metadata as well as reports.
  • Preserve exact double category membership in dictionary frequencies and reject colliding separate dictionary destinations before writing.
  • Resolve file-owned grouping variables after loading single() inputs; accept the documented config display flags and avoid caller-variable frequency collisions.
  • Preserve the caller’s active estimation sample and legacy Stata globals during documentation and profiling.
  • Handle complete-data sorting and pattern-table name collisions in datamvp; report the tetrachoric failure code when using Pearson fallback.

1.7.0 (2026-09-29)

Added three datacheck gates: rule("label": expression) for labelled row-level rules evaluated in the data's sort order, stat() for a mean, sd, median, or percentile band, and binary() for 0/1 flags that must show both levels. Added r(singlelevel_vars) and r(n_singlelevel) for variables with one observed nonmissing value, and a PASS: line when every gate passes. rule, stat, and binary rows are accepted in checks() files. Under maskrare, the continuous and date profiles and inrange() violation messages report guarded p1/p99 instead of minima and maxima, and dates at month precision. Fixed profile lines ("single level (constant)", outlier and max-category notes, and blank lines from frequency tables) that printed under quietly; violations() and makespec() with replace now create a frame that does not yet exist instead of failing with r(111); rule() expressions containing string literals print and save intact in violation messages; and nodups compares only the dataset's own columns.

1.6.9 (2026-09-29)

Stopped exclude() variables from leaking through the dataset description's date range and the panel, survival, and survey detectors; made survivalvars() select the survival variables it names; reported event rates only for 0/1 indicators and ignored missing IDs in panel detection; classified left-justified date formats (%-td, %-tc) as dates in all four commands; escaped control characters in JSON; kept caller matrices named vals/freqs intact; printed small continuous statistics and float category levels without rounding to zero or IEEE noise; counted strL variables exactly under uniqcap(0) and panelid(); applied maxcat()/uniqcap() to saving() metadata; counted datadict string levels containing quotes or backticks exactly; compared float variables at float precision in datacheck value and range gates and reported date spans in their own time units; kept datamvp, sort ties in input order with a private matrix-graph label; drew datamvp gby()/over() bar and pattern graphs correctly for non-integer (float) group levels, which had produced empty bars or r(198); kept excluded variables out of datacheck's r(missing_vars), r(flagged_vars), and patterns table; made format(json) refuse detect(), autodetect, panelid(), survivalvars(), samples(), quality, and missing() (r(198)) instead of silently dropping them; implemented missing(pattern)'s joint missing-value pattern table (previously identical to missing(detail)); and wrote frequency tables for string variables forced into the categorical class in text and JSON output.

1.6.8 (2026-08-30)

Preserved quoted text, dollar signs, backticks, pipes, and angle brackets in datadict labels and document metadata and in datamvp labels and graph titles; restored pattern-graph facet labels and varabbrev on the in-memory file-list path; and repaired Viewer-width help tables.

1.6.7 (2026-08-19)

Formatted numeric dictionary categories with their Stata display formats, preventing binary floating-point artifacts from appearing in Markdown output while retaining exact values for frequency counts.

1.6.6 (2026-08-11)

Prevented generated-variable and stored-result name collisions, tightened matrix graph parsing, preserved analytical returns after graph failures, repaired separate in-memory output, preserved quoted text and apostrophes in dictionaries and paths, and replaced temporary in-memory identities with stable memory labels.

1.6.5 (2026-08-09)

Preserved closing parentheses in saving() metadata paths across datamap, datadict, and datacheck; expanded path and return-contract QA.

1.6.4 (2026-08-05)

Corrected input-mode, successful-state, stored-result, and generated-name help contracts.

1.6.3 (2026-08-05)

Fixed Viewer-width rendering in the command help files and corrected the documented in-memory rollback behavior.

1.6.2 (2026-07-27)

Made shipped help self-contained by removing contributor-only references.

1.6.1 (2026-07-15)

Reduced peak memory for exact distinct counts without changing user-visible counts or output.

1.6.0 (2026-07-14)

Added capped distinct-value counts, unique_values_capped metadata, and frame-based report processing for lower memory use.

1.5.4 (2026-07-10)

Restored the documented dictionary privacy default, rejected negative thresholds, and tightened datamvp option validation.

1.5.3 (2026-07-10)

Completed the datacheck option and return contract and improved help-file rendering.

1.5.2 (2026-07-09)

Fixed negative fractional values in JSON output and replaced high-cardinality distinct counting with the shared counter.

1.5.1 (2026-07-08)

Fixed long-name, high-cardinality, privacy, generated-name, and state-restoration edge cases.

1.5.0 (2026-06-19)

Added shared classifier overrides, project config(), metadata exports, and datacheck compare().

1.4.1 (2026-06-19)

Fixed floating-point formatting in text output, summaries, detection, missingness, and sample rows.

Author

Timothy P Copeland, Karolinska Institutet

Questions and bug reports: GitHub issues. Please include the first line of which datamap and your Stata version.

To cite: Copeland TP. datamap: Document and check data without exposing it. Stata-Tools, https://github.com/tpcopeland/Stata-Tools/tree/main/datamap. Please give the version you used (which datamap prints it).

License

MIT (see LICENSE)