datamap
Data PreparationDescribe and check data you can't share
Version 1.10.6 | 2026-10-26
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.
datamapanddatadictwrite class-level summaries, suppress frequency cells belowmincell(5), and cap distinct-value counts atuniqcap(1000); row-level sample values appear only whensamples()asks for them, anddatesafewithholds 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, anddataprobeclassify variables the same way, withexclude(), class overrides, and a reusableconfig()file. - Gates that stop a do-file.
dataprobeinvariants such asexpectn(),isid(),inrange(), andrule()exit with return code 9 when they fail; sanitybands()andreview()items are kept apart so they can warn without halting. - A record of every check.
dataqakeeps a ledger row per gate entry,dataqa asserthalts when a gate failed or an expected dataset was never checked, anddataqa exportrefuses a release copy that shows a small cell. - Missing-data patterns.
datamvptabulates joint missingness, tests for monotone missingness, and draws bar, pattern, matrix, and correlation graphs; undermincell()ormaskrarerare 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
datadictMarkdown 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.
- Privacy-safe map transcript
- JSON output transcript
- Markdown dictionary transcript
- Console QC transcript
- Missingness-pattern transcript
- Generated JSON metadata
- Clinical Markdown dictionary

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
datamapanddatadictuse aggregate summaries by default;samples()is an explicit row-level export option.mincell()suppresses categorical and binary frequency cells below the threshold, anduniqcap()reports a lower-bound count when the distinct-value cap is exceeded.datamapanddatadictin-memory failures are not rolled back after partial processing; use file-based input or a copy when failure isolation is required.datadictrequires an existingoutdir()for separate outputs, andchecks(),compare(), and file-basedviolations()/makespec()routes use Stata datasets rather than text specifications.dataprobetreatswarnas a non-halting gate mode for every gate andbandwarnas 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
dataqaledger anddatamvpsave()files hold small cells and belong with the data;dataqa exportwrites the copy that may leave the server and refuses one that shows a small cell. datamvpis 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() maskrareno longer hangs on outputs with many small groups and linked counts (a 3,000-row profile in 40 groups withbyfreqand gates ran past 12 minutes in 1.10.5;autoby(foreign) byfreqpast 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, andr(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 (nlsw88by(race) byfreq: 198 s with groups in 1.10.5, 62 s pooled in 1.10.6). - Fix:
datamapanddatadictwithdirectory()refuse a file whose name holds$, a backtick, or a double quote, naming it, before writing anything (1.10.5 reada$b.dtaasa.dtaand stopped with r(601)); underrecursivea subdirectory with such a name is refused the same way. - Fix:
dataprobe, by() maskrare byfreqno longer stops with r(3301) when a group's frequency table has one nonempty level that is withheld. - Fix:
review()counts printed perby()group undermaskrareare 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:
datadictprints values from 1e6 to below 1e15 with every digit (10,032,845, not1.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 comparereads the row with the worst status (then the unmodified row), so it flags a failure thatreportandassertcount; it used the later row. - Faster, same output (one core, measured against 1.10.5):
dataprobebuilds eachbyfreqvariable's group tables from one table, reuses the CONTINUOUS block's summaries undermaskrare, and does less work per step of the linked-count check (byfreqprofiles 2-4x faster);datadictbuilds 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);datamvpcomputes missing counts, patterns, the monotone test,bytable()cells, andgraph(bar)gby()/over()cells in one pass (1.8-4x);dataqa reportcounts every dataset in one pass (4-5x withmarkdown(), 10x without, on a 98,000-row ledger); distinct counts of variables with few levels use onetabulate.
1.10.5 (2026-10-21)
- Fix: under
maskrarewithby(), 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,byfreqcells, the per-group gate counts ofnotmissing(),complete(),stat()n/pmiss/0-1 sum,binary(),inrange()on a profiled date,allowed()/forbid()/notvalues(), and thegroupstat()n and 0/1 sum cells with the call'sby()) 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), andgroupstat()with the call'sby()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%tcformat that shows seconds) prints at full precision beside the date (874.5125 (24may1962)), as do a failinginrange()'s minimum and maximum on a date and the date tested by a failingcoverage()early_start or late_end; a%tcvalue prints all 13 digits, ingroupstat()band failures too, which now add the number only when the date is not exact. Under the mask they stay at month precision. - Docs:
dataprobehelp states the by() masking guarantee, its relations, and what it does not cover.
1.10.4 (2026-10-21)
- Fix: under
maskrarewithby(), 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 areview()orcomplete()item prints it, and against the cells' own total over the entry'sifrows, so a printed scope count minus the shown cells no longer gives back a pooled-away group; a 0/1sumcolumn follows the same test in profile mode. - Fix:
datamapanddatadictwithhold the mean, SD, and skewness of acontinuous()variable with three distinct values undermincell(), as for two (mean and SD with N gave back each value's count), in text, JSON,saving(), and thestatscolumn. - Fix:
dataqaagain lets a file fixed on disk, reloaded, and rerun supersede its earlier failure (1.10.3 required the samen_scope); only a rerun on(modified)data cannot supersede a row of the unmodified data. - Fix: every
groupstat()cell fits its column: a withheld pooledsdreads[suppr.], and a long date format or a%tcvalue prints as a%tddate; a small cell keeps<mrather 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:
datamaphelp describes whatcompactwrites 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
datamapanddatadictcould report a variable as constant (Unique 1) or misclassify it; every row is now read. - Fix:
datamapanddatadictdocumenting the data in memory no longer resetc(changed)or pointc(filename)at a temporary file (a laterusewithoutclearsilently 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:
dataprobegroupstat()leaves rows with a missing group out of the pooled value, so the pool matches its groups; a maskedgroupstat()table now setsr(masked)and the ledger'sobs_masked. - Fix: under
maskrare,stat()andgroupstat()withholdsd,ess, andratiowhen they would give back a small count (theessof 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 intocategorical()gets no frequency table underdatesafe;saving()leaves categorical summaries missing whilemincell()is above 0 and continuous and categorical summaries missing undernostats. - Fix: a
continuous(),categorical(),date(), ordatevars()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 incontinuous()ordate()is r(109). - Fix:
dataproberefusesexpectn()with reversed bounds and a range bound that refers to a variable inside an expression (ub*1read the first observation); a missingstat()lower bound is reported as missing;allowed(),forbid(), andnotvalues()match a value holding a double quote. - Fix: a variable named
nomaskno longer switches a session'smaskrareoff indatamvpanddataprobe. - Fix:
datadictprints 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 prints3=3for an unlabelled value, writes "No observations" for an empty file, and writesIQR=-224 to -73.50rather thanIQR=-224--73.50. - Fix:
datamvpstringgby()/over()levels holding a quote or backtick keep every group, a label holding"'prints,generate()names and truncated text stay valid UTF-8,wideprints patterns wider than the line in full, and notes say whengby()/over()drop rows or when no missing values leave requested outputs unproduced. - Fix:
datamapkeeps the previous output when a later dataset fails, refusesappendwithseparateandrecursivewithoutdirectory(), finds.DTAfiles, stops a symbolic-link loop, prints%tc/%tCdates withdateformat(%d...), reports panel structure frompanelid()alone, checkssingle()paths, and keeps long dataset names insaving(). - Fix:
datamapVALUE LABEL DEFINITIONS lists the values each label defines (with.a-style codes) instead of every data value, which printed exact dates underdatesafeand unique values pastmaxfreq()andmincell(). - Fix: under
datesafe, a date-formatted variable forced intocontinuous()prints no mean, quantiles, or range in text, JSON, orsaving(), andsamples()suppresses every date-formatted variable. - Fix: while
mincell()is above 0,datamapanddatadictwithhold the mean and SD of a two-valued variable forced intocontinuous()and the mean of a categorical survey weight;datadict, saving()leaves categorical summaries missing, asdatamapdoes. - Fix:
datamapwrites the Sort Order (text and JSON), keeps a failing dataset's previous output underseparate, records each dataset's own_mapfile insaving()underseparate, and writessamples(),quality, anddetect(binary)undercompact. - Fix:
datadictreplaces its dictionary only when every dataset was documented;config()missing = noleaves the Missing column off; a manifest or config file with a byte-order mark is read; a repeated varlist name is documented once;Filenamekeeps the file's own extension; a categorical date followsdateformat(); 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(), ordatevars()is r(111) instead of being ignored; asaving()name without.dtais checked before any output is written; a non-integer threshold fromconfig()is r(198) naming the option. - Fix:
dataqagate key includes the kind, and a rerun supersedes only when it tested the same rows, so neither abands()band nor a subset rerun clears an earlier failure;expect()matches the exact name;exportblanks the signature of a dataset belowthreshold()rows and literal values in value-gate text, and refuses the session's ledger assaving();reportprints ledger text literally;comparenames theby()group, reads ledgers without a signature column, anddataqa set mincell(-1)is r(198). - Fix: under
maskrare,dataprobestat(sum)andgroupstat(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,dataprobeGROUPWISE SUMMARY, MISSINGNESS, and FREQUENCIES withhold a cell when the total printed elsewhere minus the exactly printed cells is 1..m-1, andgroupstat(n)does the same against the printed N; in aby()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 adataprobeprofile withby(), 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, thedataprobeheaping 1st-of-month share is masked when the other 1sts are 1..m-1;sdneeds m rows of each value for variables with two or three values;groupstatband failures name a withheld group by number, and date cells print as dates. - Fix:
dataprobesetsr(masked)when only frequency cells were suppressed;exclude()withholds variable values instat(),coverage(),events(), andgroupstat();saving()writes the loaded data's datasignature; astat()upper bound of.is open for dates too andcoverage()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.
dataqareports when it cannot restore the session'smatastrictsetting,dataprobe byrule()reports when it cannot restore the caller's row order after an error, anddataprobereports 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)
datadictno 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.dtagave## 1.followed by two spaces andDemo Cohort, with anchor#1--demo-cohort; now## 1. Demo Cohortand#1-demo-cohort).
1.10.0 (2026-10-12)
- Fix:
datamvpgraphtitle()andsubtitle()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'stitle(); 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:
datamvptitle()andsubtitle()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, asgraph(patterns) gby()already did.- Fix:
datamvp, graph(bar) gby()andover()keep a value label written with compound quotes literally instead of stripping them. - New:
datamvp, varlabelslabels the variable axis ofgraph(bar)andgraph(matrix), both axes ofgraph(correlation), and thestackedlegend with variable labels; variables without a label keep their name.
1.9.3 (2026-10-12)
- Renamed:
datacheckis nowdataprobe. The old name collided with the SSC packagedatacheck(Bhaskaran, 2011): either install blocked the other, andnet install, replaceoverwrote one package'sdatacheck.adowith the other's. Replacedatacheckwithdataprobein do-files; syntax, options, stored results, and thehelp dataprobefile are otherwise unchanged. Ledgers andmakespec()spec files written bydatacheckare read unchanged. Uninstall the old version first (ado uninstall datamap) so the olddatacheck.adodoes not linger. Version-history entries below 1.9.3 keep the old name. - Fix:
dataprobeinrange(),stat(), andmakespec()bounds are compared at full double precision, so amakespec()spec passeschecks()on the data it was written from. - Fix:
groupstat()undermaskraremasks a count cell whose complement in the group's rows or nonmissing rows is a small cell, masks apmisscell when the group's missing or nonmissing count is small, and withholds the pooledpmissvalue 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, andcomparerefuse (r(610)) a ledger whosestatusis not a string or holds a value other than pass, fail, warn, review, or error;dataqa assertmatches dataset names containing spaces. - Fix:
datamvp, graph(patterns)no longer fails when the data hold variables namedpatidorpatorder. - Fix: the dataset date span is computed within one date format family;
%thand%tyare no longer mixed. - Fix:
nofreqalso suppresses the value frequencies in thedetect(binary)section.
1.9.2 (2026-10-05)
help datamapopens with a Package commands section linking the help files fordatadict,datacheck,dataqa, anddatamvp.
Earlier versions
1.9.1 (2026-10-04)
dataqa assert, optional()with no baseline set (neitherbaseline()nordataqa set baseline()) is ignored with a note instead of stopping with r(198), so a study do-file can carryoptional()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. datacheckcalls 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, anddataqa asserthalts on it. Error rows are never superseded by a later call;dataqa exportrefuses a run that holds one.dataqa set ... collect: under a ledger, a failing gate prints its block, writes its rows, and returns 0 withr(n_failed)set, socapture noisilyis no longer needed around gate calls;dataqa assertis 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 inoptional().dataqa report, bandslists each band andstat()invariant of the run, passing or failing, with its declared range and observed value as the ledger stores them (masked values stay masked); withmarkdown()the table is added as its own section.datacheck byrule(byvars (sortvars): "label": exp): a rule evaluated underby byvars (sortvars):on datacheck's copy, so_n,_N, and subscripts count within groups; the caller's data and sort order are unchanged.rule()andreview()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 asn1-e1, may lie in 1 to m-1 on rows in scope, where m is the call'smincell()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, sodataqa compareanddataqa assertneed no arguments; an explicit option still wins.dataqa.sthlpgains 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 undermaskrare); the summary is added to the failing rows' message andobservedtext, andobserved_numis unchanged. datacheck ..., by(g) byfreqprints the categorical and string frequency tables within each group, masked cell by cell.- The ledger
grpcolumn records aby()group by its values (by(site year | site=3 year=2015)), anddataqa comparepairs 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. Undermaskrarea 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.dataqareaders resolve a ledger path with the same rule as the writers (.dtaadded 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 brokedatachecktables and gate messages (display text leaked into the line) and stoppeddatamaptext and JSON output,datadict, andsaving()with r(132) or r(198); such values now print and write literally. - Under
maskrare, the above/below summary of a failinggroupstat()band withholds a direction's row count when one of its groups is below the mask.smallcells()refuses a threshold below 2, anddataqa assertaccepts compound-quoted dataset names with spaces inoptional()andexpect(). A non-integerby()value is recorded as its shortest exact decimal text. - Fix: a failing
datacheckcall whose command line held a compound quote (ledger(`"file"'),name(`"a b"'), a compound-quotedrule()) wrote no call-error row (r(132) in the writer), sodataqa assertcould pass over it; the row is now written for every quoting form. - Fix: a
rule(),review(), orbyrule()value written in compound quotes,rule(`"label: make != """'), stopped with r(132) or r(109); it is unwrapped and parsed like the plain form. Abyrule()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
datamaptext output; it is written as a space, as string levels already were.dataqa assertprints ledger messages with quote, macro, and SMCL characters as text, anddataqa compareits gate labels, asdatacheckdoes. datacheck.sthlpgains 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;datadictreuses the classifier's missing and distinct counts, and computes percentiles only for the rows that report or save them;datamvp, correlatefits the tetrachoric correlations on the distinct missingness patterns with frequency weights (the same estimates). datacheckfrequency tables anddatamvppattern graphs order tied counts by value and pattern, so which levels show and which fall pastmaxfreq()/top()or into a masked pool no longer varies between runs.datamap,datadict, anddatacheckput the caller'sS_1,S_FN, andS_FNDATEglobals back exactly as they were; a value holding$nameor`name'came back rewritten. All four commands (withdatamvp) no longer stop with r(132) when one of those globals ends in an unmatched backtick.datamap, saving()anddatacheck, saving()record a variable's first note and characteristic literally;$nameand`name'inside them were expanded.
1.8.1 (2026-09-30)
datamvppreserves the caller’s exact native reshape globals across matrix graphs and graph refusals, including opaque existing values and initially absent aliases.datacheckpreserves embedded filename quotes until ledger-path validation so its existing quote restriction refuses the actual path before output work.datamvprefuses ordinary and extended missinggroupgap()values before graph work instead of silently using native default spacing.dataqarestores the caller's exactmatastrictsetting on success and refusal, including the first native frame-link initialization.dataqa:dataqa set ..., replaceremoves the rows already underrun()from the ledger (a note is printed when a run already has rows);report,assert, andexportread 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;comparekeys entries the same way;exportrefuses asaving()path that resolves to the ledger it reads (r(602)).Disclosure:
datamapanddatadictwithhold a complementary cell when a lone suppressed cell could be recovered from N and the missing count, and applymincell()to the survival event rate and the complete-case count;datesafewithholds date-class survival time ranges;saving()rows of excluded variables carry no value-label name, notes, or characteristics.datacheckmasks a small complement instat()n/sum/distinct/pmiss,sets(values), the DATE window, and KEY STRUCTURE, and withholds an excluded variable's Miss% andinrange()extremes.datamvpgraphs follow the table's mask:graph(bar)withholds masked bars,graph(patterns)draws only table-shown patterns, andgraph(matrix), which plots individual observations, is refused under masking.Display: a masked share prints as
[masked](orn/afor an empty denominator) rather than.%, and an excluded variable's share as[excluded], which widens thedatacheckQUICK REFERENCE Miss% column;groupstat()columns stay separated for 12-character names, a failing band value prints at full precision, and withrelativethe cells show the group-to-pooled ratio that the band tests.exclude()indatamapanddatadictexpands 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.dtaor a path), so a mixed form such asfilelist("sub/a b")must list each name separately. Zero-observation files are documented. Stringuniquecounts exclude the empty string in every command.datacheck:checks()isid/expectnrows add gates instead of replacing the command-line ones, andisid()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()andcoverage()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, andintervals()rows with a missing id; float values are compared at float precision atintervals()tol(),jumps()ratio(), andgroupstat()percentile bounds.datamvprestores the random-number state after sampling forgraph(matrix), and preserves an existing or absentS_2legacy global on success, the no-missing shortcut, and refusal.
1.8.0 (2026-09-30)
- Disclosure: under
maskrare,datacheckmasks every printed count below the threshold, including failing-row counts in gate messages and inviolations(), 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.datamvpgainsmincell()andmaskrare, pooling patterns below the threshold into one row, anddatacheck, patterns maskrarepasses the masking on.r(masked)reports whether anything was masked. - Speed:
gatesonlywithoutcompare(),saving(), ormakespec()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 tagsisid()/nodupskeys once per call instead of once perby()group. - New invariant gates:
events(),intervals(),keyset(),constant(), andsets();stat()addssum,n,distinct,pmiss,ess, andratiowith an optionalifper entry;inrange()takes several variables per bound pair and open bounds. bands()andbandwarnkeep sanity bands apart from invariants in one call; newcoverage()band andreview(),heaping(),groupstat(),complete(), andjumps()review items.- The verdict names the dataset (or
name()) and the active masking;minversion()andr(version);signature;ledger()appends one row per gate entry, passed or failed. - New command
dataqa:set(session defaults read bydatacheckanddatamvp),report(register draft with the dispositions each kind allows),assert,export(a release copy that refuses small cells), andcompare(drift against a baseline run). datamvpalso addsbytable(), appliestop()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,%tcbounds in milliseconds, and a bound naming a variable is refused.
1.7.1 (2026-09-29)
- Apply
datesafeto 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)