{smcl} {vieweralsosee "tvexpose" "help tvexpose"}{...} {vieweralsosee "tvevent" "help tvevent"}{...} {vieweralsosee "tvmerge" "help tvmerge"}{...} {vieweralsosee "tvage" "help tvage"}{...} {vieweralsosee "tvtools" "help tvtools"}{...} {viewerjumpto "Syntax" "tvpanel##syntax"}{...} {viewerjumpto "Description" "tvpanel##description"}{...} {viewerjumpto "Options" "tvpanel##options"}{...} {viewerjumpto "Active class and cumulative exposure" "tvpanel##semantics"}{...} {viewerjumpto "tvexpose vs tvpanel" "tvpanel##compare"}{...} {viewerjumpto "Stored results" "tvpanel##stored"}{...} {viewerjumpto "Examples" "tvpanel##examples"}{...} {viewerjumpto "Author" "tvpanel##author"}{...} {title:Title} {phang} {bf:tvpanel} {hline 2} Fixed-width, entry-anchored person-period panel for marginal structural models {marker syntax}{...} {title:Syntax} {p 8 17 2} {cmd:tvpanel} [{cmd:using} {it:filename}]{cmd:,} {opth id(varname)} {opth entry(varname)} {opth exit(varname)} {opth exposure(name)} [{it:options}] {pstd} The data in memory is the {bf:master}: one row per person with the study {opt entry()} and {opt exit()} dates. {it:filename} is the {bf:episode} file: exposure periods with {opt id()}, a start, a stop, and an integer exposure-class variable. The episode data may instead be supplied as a named frame via {opt frame()}. {synoptset 30 tabbed}{...} {synopthdr} {synoptline} {syntab:Main} {synopt:{opth id(varname)}}person identifier in both datasets{p_end} {synopt:{opth entry(varname)}}master study-entry date anchoring grid{p_end} {synopt:{opth exit(varname)}}study exit date in the master{p_end} {synopt:{opth exposure(name)}}integer exposure-class variable{p_end} {synopt:{opt fr:ame(name)}}read episodes from a named frame{p_end} {syntab:Grid} {synopt:{opt width(#)}}interval width in days; default {cmd:width(91)}{p_end} {synopt:{opt ref:erence(#)}}reference class; default {cmd:0}{p_end} {synopt:{opt start(name)}}episode-start variable; default {cmd:start}{p_end} {synopt:{opt stop(name)}}using-data stop date; default {cmd:stop}{p_end} {syntab:Output names} {synopt:{opt per:iod(name)}}0-based period index; default {cmd:period}{p_end} {synopt:{opt startgen(name)}}interval-start variable; default {cmd:start}{p_end} {synopt:{opt stopgen(name)}}interval stop-date variable; default {cmd:stop}{p_end} {synopt:{opt gen:erate(name)}}exposure class; default {cmd:tv_class}{p_end} {synopt:{opt cum:ulative(unit)}}per-class cumulative exposure{p_end} {synopt:{opt pre:fix(string)}}prefix cumulative-variable names{p_end} {syntab:Other} {synopt:{opth keepvars(varlist)}}master variables carried to each row{p_end} {synopt:{opt dropinvalid}}remove malformed source rows explicitly{p_end} {synopt:{opt verbose}}list malformed-row examples and counts{p_end} {synopt:{opt saveas(filename)}}save the panel to disk{p_end} {synopt:{opt replace}}allow {opt saveas()} to overwrite{p_end} {synopt:{opt noi:sily}}display a build summary{p_end} {synoptline} {marker description}{...} {title:Description} {pstd} {cmd:tvpanel} expands each person's follow-up into {bf:fixed-width intervals anchored at study entry}: interval {it:k} runs from {cmd:entry + width*k} to {cmd:entry + width*(k+1) - 1}, clipped at {opt exit()}. Every interval is emitted for all person-time, exposed and unexposed, with a 0-based integer {opt period()} index suitable for {helpb msm_prepare}'s {cmd:period()}. For each interval it reports the {bf:active exposure class at the interval start} and, optionally, the {bf:per-class cumulative exposure accrued as of the interval start}. {pstd} This is the grid that marginal structural models need: weights and the weighted outcome model key off the integer period, and the exact uniform spacing keeps any lagged cumulative-exposure derivation aligned to the day. {cmd:tvpanel} is the entry point for building that grid before {helpb msm_prepare} and {helpb msm_weight}. {pstd} {bf:Important}: {cmd:tvpanel} replaces the data in memory with the person-period grid and writes the {opt period()} output variable (default {cmd:period}), overwriting any same-named variable already in memory. Preserve your data or work with copies. {marker options}{...} {title:Options} {phang} {opth id(varname)}, {opth entry(varname)}, {opth exit(varname)} identify the person and the study window in the master data (one row per person). {opt id()} must be numeric; encode a string identifier first (e.g. {cmd:egen long id2 = group(id)}). {opt entry()} anchors the grid. The closed window {cmd:entry == exit} is valid one-day follow-up and emits one period-0 row. {phang} {opt frame(name)} reads the episode data from a named {help frame:frame} held in memory instead of from a {cmd:using} file. Supply either {cmd:using} or {opt frame()}, not both. {phang} {opth exposure(name)} is the integer exposure-class variable in the episode file. Values must be integers (they become the suffix of the cumulative variables and the values of the active-class variable); {opt cumulative()} additionally requires non-negative codes, since class values become variable-name suffixes. Any value label on this variable is carried onto {opt generate()}. Episode {opt start()}/{opt stop()} must contain integer daily-date values, not datetime ({bf:%tc}) values; interval arithmetic is in whole days. {phang} {opt width(#)} sets the interval width in days. {cmd:width(91)} (the default) gives the quarterly grid used by most MSM workflows. Unlike a calendar quarter, this is exactly 91 days, so interval starts never drift from {cmd:entry + width*k}. It must be a positive whole number of days. {phang} {opt reference(#)} is the value assigned to {opt generate()} on intervals with no covering episode (unexposed person-time). Episodes whose class equals {opt reference()} are ignored for cumulative exposure. {phang} {opt start(name)} and {opt stop(name)} name the episode start/stop dates in the using file (defaults {cmd:start} and {cmd:stop}). Episodes are taken as supplied: bake any drug-class carryover or washout into the episode stop before calling {cmd:tvpanel}. {phang} {opt cumulative(unit)} adds one variable per non-reference class, {cmd:cum_}{it:class} (or {opt prefix()}{cmd:cum_}{it:class}), holding cumulative exposure in {it:unit} accrued strictly before the interval start. Allowed units are {cmd:days}, {cmd:weeks}, {cmd:months}, {cmd:quarters}, and {cmd:years}. See {help tvpanel##semantics:below}. {phang} {opt period(name)} names the 0-based period index. {opt startgen(name)} and {opt stopgen(name)} name the generated panel bounds; their defaults are {cmd:period}, {cmd:start}, and {cmd:stop}. {phang} {opth keepvars(varlist)} carries master-data variables onto every panel row. {phang} {opt dropinvalid} removes malformed source rows and reports exact counts in {cmd:r()}. By default, any missing identifier, missing or fractional date, reversed interval, or missing/noninteger exposure class stops with {cmd:r(498)} and leaves the master unchanged. Duplicate master identifiers remain an error. {phang} {opt verbose} lists up to five malformed rows and displays counts by reason but does not enable row removal; combine it with {opt dropinvalid} to continue. {phang} {opt saveas(filename)} writes the panel to disk and restores the master data. {opt replace} permits an existing file to be overwritten. {phang} {opt noisily} displays a compact build summary. {marker semantics}{...} {title:Active class and cumulative exposure} {pstd} {bf:Active class.} At each interval start, the active class is the episode with the latest start date that covers the interval start (ties broken by the larger class value). Bake carryover into the episode stop so a drug remains "active" through its carryover window. {pstd} {bf:Cumulative exposure} is evaluated {bf:as of the interval start} (non-anticipating): for each class it first forms the union of that person's closed episode intervals, so nested, crossing, duplicate, and abutting same-class episodes never double count a day. It then sums exposure-days in {cmd:[episode start, interval start - 1]} and converts to the requested unit. The current interval's own exposure is not yet counted, so the value entering period {it:k} reflects only history before {it:k}. Each cumulative variable has {cmd:char var[tvtools_quantity] "cumulative"} and {cmd:char var[tvtools_history_point] "start"} for downstream composition. {pstd} {bf:Lagged cumulative exposure} (e.g. cumulative as of {cmd:interval start - 730 days}) is a thin downstream step, not produced here: subtract the trailing-window exposure from the running total, or difference the cumulative series. {cmd:tvexpose}'s {opt lag()} is an induction/washout lag (front of episode), not an evaluation lag, and must not be used for this. {marker compare}{...} {title:tvexpose vs tvpanel} {pstd} {helpb tvexpose} splits person-time at {bf:exposure-change boundaries} and, with {opt expandunit()}, expands using {bf:calendar-average} widths anchored at each episode (a "quarter" is ~91.31 days). That is right for episode-centric dose-response work but drifts off a fixed grid and omits a uniform integer period. {pstd} {cmd:tvpanel} lays down a {bf:uniform grid anchored at study entry} with an exact width and an integer period, over all person-time, with no exposure-change splits. Use {cmd:tvpanel} when the downstream model is an MSM keyed by period; use {helpb tvexpose} when you want one row per exposure episode or per calendar bin. {marker stored}{...} {title:Stored results} {pstd} {cmd:tvpanel} stores the following in {cmd:r()}: {synoptset 22 tabbed}{...} {p2col 5 22 26 2: Scalars}{p_end} {synopt:{cmd:r(n_persons)}}persons in the panel{p_end} {synopt:{cmd:r(n_observations)}}period rows{p_end} {synopt:{cmd:r(width)}}interval width in days{p_end} {synopt:{cmd:r(n_invalid)}}malformed master plus episode rows removed{p_end} {synopt:{cmd:r(n_invalid_master)}}malformed master rows removed{p_end} {synopt:{cmd:r(n_invalid_master_id)}}master rows with missing identifiers{p_end} {synopt:{cmd:r(n_invalid_master_dates)}}master rows with missing/fractional dates{p_end} {synopt:{cmd:r(n_invalid_master_order)}}master rows with exit before entry{p_end} {synopt:{cmd:r(n_invalid_episodes)}}malformed episode rows removed{p_end} {synopt:{cmd:r(n_invalid_episode_id)}}episode rows with missing identifiers{p_end} {synopt:{cmd:r(n_invalid_episode_dates)}}episode rows with missing/fractional dates{p_end} {synopt:{cmd:r(n_invalid_episode_order)}}episode rows with stop before start{p_end} {synopt:{cmd:r(n_invalid_episode_exposure)}}episode rows with missing/noninteger classes{p_end} {p2col 5 22 26 2: Macros}{p_end} {synopt:{cmd:r(periodvar)}}name of the period variable{p_end} {synopt:{cmd:r(startvar)}}name of the interval start variable{p_end} {synopt:{cmd:r(stopvar)}}name of the interval stop variable{p_end} {synopt:{cmd:r(classvar)}}name of the active-class variable{p_end} {synopt:{cmd:r(cumvars)}}names of the cumulative variables{p_end} {marker examples}{...} {title:Examples} {pstd} Build an inline cohort and episode tempfile: {phang2}{cmd:. clear}{p_end} {phang2}{cmd:. input long id str9(entry_s exit_s) byte(female outcome)}{p_end} {phang3}{cmd:1 "01jan2020" "31dec2020" 1 1}{p_end} {phang3}{cmd:2 "01jan2020" "31dec2020" 0 0}{p_end} {phang3}{cmd:end}{p_end} {phang2}{cmd:. generate double index_date = date(entry_s, "DMY")}{p_end} {phang2}{cmd:. generate double study_exit = date(exit_s, "DMY")}{p_end} {phang2}{cmd:. format index_date study_exit %td}{p_end} {phang2}{cmd:. drop entry_s exit_s}{p_end} {phang2}{cmd:. tempfile episodes panel}{p_end} {phang2}{cmd:. preserve}{p_end} {phang2}{cmd:. clear}{p_end} {phang2}{cmd:. input long id str9(start_s stop_s) byte dmt_class}{p_end} {phang3}{cmd:1 "15jan2020" "30jun2020" 1}{p_end} {phang3}{cmd:1 "01jul2020" "31dec2020" 1}{p_end} {phang3}{cmd:2 "01mar2020" "31may2020" 1}{p_end} {phang3}{cmd:end}{p_end} {phang2}{cmd:. generate double start = date(start_s, "DMY")}{p_end} {phang2}{cmd:. generate double stop = date(stop_s, "DMY")}{p_end} {phang2}{cmd:. format start stop %td}{p_end} {phang2}{cmd:. drop start_s stop_s}{p_end} {phang2}{cmd:. save `episodes'}{p_end} {phang2}{cmd:. restore}{p_end} {pstd}{bf:Quarterly MSM grid with cumulative exposure-years}{p_end} {phang2}{cmd:. tvpanel using `episodes', id(id) entry(index_date) exit(study_exit) ///}{p_end} {phang3}{cmd:exposure(dmt_class) reference(0) width(91) period(qtr) ///}{p_end} {phang3}{cmd:cumulative(years) keepvars(female outcome) saveas(`panel') replace}{p_end} {phang2}{cmd:. use `panel', clear}{p_end} {pstd} {opt cumulative()} also accepts {cmd:days}, {cmd:weeks}, {cmd:months}, and {cmd:quarters}. The values are histories accrued strictly before each interval start. {pstd}{bf:Optional msm workflow}{p_end} {phang2}{cmd:. net install msm, from("https://raw.githubusercontent.com/tpcopeland/Stata-Tools/main/msm") replace}{p_end} {phang2}{cmd:. msm_prepare, id(id) period(qtr) treatment(tv_class) outcome(outcome)}{p_end} {pstd} The {bf:msm} package is optional and is not required to build or inspect the panel. Consult {cmd:help msm_prepare} for its outcome and covariate contracts. {marker author}{...} {title:Author} {pstd}Timothy P Copeland, Karolinska Institutet{p_end} {hline}