library(tidyverse); library(data.table)
panel <- fread("grocery_panel.csv")
markets <- fread("grocery_markets.csv")
# outcome Y = footfall (use log(footfall) so effects read as %) ; treatment D = sco_live
# DERIVE go-live: per market, the first week sco_live == 1 (never-treated: none)Assignment V — Rolling Out Self-Checkout
Overview
You are a data scientist at FreshField, a regional grocery chain of about 200 stores across 40 metropolitan markets. To lift weekly footfall (customer visits), the operations team is rolling out self-checkout / scan-and-go store by store. The rollout was not simultaneous or random. One flagship market went live alone — a deliberate showcase, with no comparable cohort launching alongside it. Several waves of markets converted on a staggered schedule across the year — some launching before the flagship’s solo debut, some after. The rest of the estate has not converted yet.
The board wants two numbers and a defense of each:
- The flagship effect — how much did self-checkout lift footfall at the single flagship market, and how confident can we be given there is no cohort to compare it against?
- The portfolio effect — across the staggered pilot markets, what is the average footfall lift, net of the confounds that make a naive before/after comparison misleading?
This is the Part-V capstone. It synthesises Session 10 (Difference-in-Differences) — to estimate the portfolio effect from the staggered pilots — Session 11 (Synthetic Control) — to estimate the single-flagship effect with no natural comparison group — and Session 12 (Counterfactual Imputation) — the unifying view that every one of these estimators is imputing the same blanked-out \(Y(0)\) block, differing only in the assumption it is willing to buy. You will take one panel dataset from a set of misleading naive numbers to two defensible, estimand-annotated effects, and write a board memo that justifies which lens each question selects — from the estimand, not the toolbox.
Dataset & Codebook
The data are simulated from a calibrated panel model so that a ground-truth answer exists; focus your effort on the methodology, not on hunting for the “real” numbers. You are given two views of the same rollout — use whichever the estimand calls for:
Download grocery_panel.csv (≈ 2.4 MB, 22,048 rows) — the store-week panel (212 stores × 104 weeks). Use it for the staggered difference-in-differences and the counterfactual-imputation / factor estimators.
Download grocery_markets.csv (≈ 430 KB, 4,160 rows) — the market-week aggregate (40 markets × 104 weeks). Use it for the single-flagship synthetic control.
Save both into the same folder as your source file and read them with plain relative paths.
grocery_panel.csv — store-week
| Column | Type | Description |
|---|---|---|
store_id |
id (chr) | Store identifier (S0001..S0212). |
market_id |
id (chr) | Parent market identifier (M01..M40). |
week |
integer | Week index 1–104. |
date |
date | Calendar date (Monday) of the week; runs from 2024-03-25. |
footfall |
integer | Weekly customer visits — the OUTCOME \(Y\). Use log(footfall) so effects read as percent. |
transactions |
integer | Weekly transactions (secondary outcome, not the target). |
revenue |
numeric | Weekly revenue (secondary outcome, not the target). |
sco_live |
binary 0/1 | 1 if self-checkout is live in this store-week — the TREATMENT \(D\). Derive each market’s go-live week as the first week sco_live == 1. |
market_size |
numeric | Time-invariant market scale. |
urban_share |
numeric (0.1–0.9) | Time-invariant urban catchment share. |
region |
integer 1–4 | Market region. |
has_parking |
binary 0/1 | Store attribute (time-invariant). |
store_sqm |
integer | Store floor area in m² (time-invariant). |
competitor_density |
numeric | Nearby-competitor density (time-invariant store attribute). |
staffed_lanes |
numeric | Number of staffed checkout lanes open that week. |
temp_c |
numeric | Weekly average temperature (a weather control). |
confidence_index |
numeric | National weekly consumer-confidence index (same series for all markets). |
grocery_markets.csv — market-week
| Column | Type | Description |
|---|---|---|
market_id |
id (chr) | Market identifier (M01..M40). |
week, date |
integer, date | Week index 1–104 and its calendar date. |
footfall |
integer | Market-total weekly footfall (sum over the market’s stores). |
footfall_per_store |
numeric | Mean weekly footfall per store. Use log(footfall_per_store) as the SC outcome for the flagship. |
transactions, revenue |
integer, numeric | Market-total weekly transactions and revenue. |
staffed_lanes |
numeric | Market-total staffed checkout lanes. |
sco_live |
binary 0/1 | 1 if any store in the market is live that week. |
confidence_index |
numeric | National weekly consumer-confidence index (same series for all markets). |
temp_c |
numeric | Market mean weekly temperature. |
n_stores |
integer | Number of stores in the market. |
market_size, urban_share, region |
numeric, numeric, integer | Time-invariant market scale, urban catchment share, region. |
You may submit in R or Python — both earn full marks (see the language-parity note below). Report your results in a rendered Quarto document (.qmd → HTML/PDF) or Jupyter notebook. Show your code, your numbers, and your reasoning. Throughout: work on log(footfall) so coefficients read as percentages, and cluster standard errors at the market level (the assignment level).
The assignment is organised into eight parts A–H with the weights below (summing to 100%). Parts D, F, and H each close on a marked judgement essay — that is where the comprehension gate lives.
| Part | Focus | Weight |
|---|---|---|
| A | Reconstruct the rollout + the 2×2 | 10% |
| B | Static/dynamic TWFE · Goodman–Bacon · the bad control | 12% |
| C | The heterogeneity-robust family (CS · SA · BJS · Gardner) | 13% |
| D | Parallel-trends test I: event study · HonestDiD · Essay 1 | 15% |
| E | The single-flagship synthetic-control family | 15% |
| F | Factor imputation (IFEct & matrix completion) · naming the factors · Essay 2 | 20% |
| G | Parallel-trends test II: placebo · leave-one-out · sensitivity | 8% |
| H | Heterogeneity · bake-off · board memo · Essay 3 | 7% |
Part A — Reconstruct the rollout + the 2×2 (10%)
Before estimating anything, recover the design from the raw panel.
- Derive each market’s go-live week as the first week
sco_live == 1, and label the never-treated markets. Report the cohort structure: how many markets go live in each week, and how many never convert. Tabulate cohort sizes — you will need them in the next bullet. - Identify the flagship correctly. Exactly one market goes live alone — a go-live week with a single market in it. That lone market is the flagship. The earliest go-live week is not necessarily the flagship: check its cohort size. Explain why cohort size = 1 — one treated unit with no comparable launch group — is what forces a synthetic-control approach for its effect, while the multi-market waves can be pooled with difference-in-differences.
- Sanity-check the panel. Is it balanced (every store observed in every week)? Any missingness? How many treated vs. never-treated markets? Does the never-treated pool look like a usable comparison/donor set — does its spread of
market_sizeandurban_sharebracket the treated markets (and the flagship in particular)? - The 2×2 by hand, and its identity with TWFE. Take the earliest multi-market wave vs. the never-treated pool, collapse each to one pre- and one post-launch period, and compute the double difference from the four cell means by hand. Then show that a two-period two-way-fixed-effects regression returns the identical number. State plainly which quantity this 2×2 is (a building block for one wave) and which it is not (the portfolio ATT).
- The naive treated-only before/after. Compute the treated-only post-minus-pre difference on
log(footfall)for the wave markets. Is it biased? In which direction, and why — is it “selection on winners”, or is something more mundane at work? (Think about what a common secular trend and seasonality do to a treated-only pre/post gap.)
State, up front, the two estimands you will target — the flagship ATT (effect on the one flagship market) and the portfolio ATT (average effect across the staggered pilot markets) — and note that they are different questions with different answers. Keep them apart for the rest of the report.
Part B — Static/dynamic TWFE, Goodman–Bacon, and the bad control (12%)
Estimate the portfolio effect on the staggered pilot waves (set the flagship aside — feeding a one-market cohort into the staggered machinery would hand the estimator a single-cluster, invalid, standard error). Build the classic DiD engine and interrogate it.
- Static two-way fixed effects (TWFE). Regress
log(footfall)onsco_livewith store and week fixed effects, clustered on market. Report it. Compare to the naive number: what did the week fixed effects strip out? Compare to what you will later believe is the truth — is TWFE above or below it, and does the sign of that gap match the staggering pathology you saw in S10, or point elsewhere? - Dynamic TWFE event study, with a plot. Estimate effects by event time (weeks relative to each market’s own go-live), binning never-treated markets to a sentinel reference and the endpoints to a sensible window (e.g. \([-8, +12]\)). Plot the leads and lags. Are the leads flat? Note what you see — you will return to it with a sharper diagnostic in Part D.
- Goodman–Bacon decomposition. Decompose the static TWFE estimate into its 2×2 building blocks. Report the weight on the forbidden “later-vs-earlier-treated” comparisons and the weight on the clean “treated-vs-never-treated” comparisons, with each block’s average estimate. Then answer the diagnostic question this buys you: is staggering the whole story here, or is the inflation coming from somewhere else? (If the clean block is itself far from where the robust estimators will land, staggering is not the disease.)
- The
staffed_lanesquestion (the bad control). A colleague reviewing your DiD says: “You should control forstaffed_lanes— busier stores keep more lanes open, so it’s a confounder you’re ignoring.” Evaluate the claim rigorously before accepting it. (i) Addstaffed_lanesto the TWFE specification and report what happens to the estimate. (ii) Regressstaffed_lanesonsco_live(same fixed effects): what does self-checkout do to the number of staffed lanes? (iii) State the rule and apply it — isstaffed_lanesa pre-treatment confounder or a post-treatment consequence of the treatment you are studying? Name the bias you would introduce by conditioning on it, and add it to your assumption ledger as the thing a good specification must not condition on.
Part C — The heterogeneity-robust family (13%)
Static TWFE is contaminated by forbidden comparisons under staggered adoption. Build the modern heterogeneity-robust menu, which forms only clean comparisons. Work on the wave markets + never-treated pool, log(footfall), market-clustered. Produce a bake-off table of these estimators:
- Callaway–Sant’Anna (
did::att_gt) — the doubly-robust ATT(g,t) grid against a never-treated control group, aggregated to an overall portfolio ATT and reported per cohort. Use a universal base period (you will need it for Part D’s event study and HonestDiD). - Sun–Abraham (
fixest::sunab) — the same idea in one saturated regression. Report the overall ATT. - Borusyak–Jaravel–Spiess imputation (
didimputation) — fit two-way fixed effects on the untreated cells only, impute each treated cell’s \(\hat Y(0)\), and average the treated-cell residuals (“fit white, predict grey”). - Gardner (
did2s) — the two-stage imputation estimator (a model-answer bonus; CS/SA and BJS are the required floor).
Two things to read off the results and state explicitly:
- The per-cohort ATTs are a clue. Do the cohorts show the same effect, or does it trend with launch order? If earlier cohorts show larger effects, what might that be a footprint of? (Hold the interpretation for Parts D and F — but flag the pattern here.)
- The family will (roughly) agree — do not over-read the agreement. If four independently-implemented estimators cluster together but all sit well above the naive week-fixed-effects intuition, is that corroboration, or could it be a shared bias? Convergence across methods certifies robustness only against the failure modes those methods differ on; it says nothing about a failure mode they all share. State which shared assumption they all rest on. Do not average the ladder — the disagreement between families is a clue you chase in Parts D–F, not noise to average away.
Part D — Parallel-trends test I: event study and HonestDiD (15%)
The robust estimators in Part C rest on parallel trends, cohort by cohort. Test it — do not assume it.
- Dynamic event study with a universal base period. Plot the Callaway–Sant’Anna dynamic effects over event time (a window such as \(-8\) to \(+12\)). Are the pre-period leads flat, or do they drift? Quantify the largest pre-period deviation and the correlation between event time and pre-period coefficient. A drifting, monotone pre-trend is a parallel-trends violation in plain sight.
- How powered is the eyeball? Use a pre-trends power calculation (R
pretrends; or reason about it explicitly) to state the linear pre-trend slope a visual pre-test would catch only half the time, and compare it to the drift you actually see. The lesson: a pre-test that “passes” is weak evidence, and one that fails is decisive. - HonestDiD — report a band, not a point. Run a relative-magnitudes sensitivity analysis on the average post-launch effect: how large can a post-period trend violation be, as a fraction \(\bar M\) of the worst pre-period deviation, before the robust confidence set first covers zero? Report the breakdown value \(\bar M^{*}\) and read it precisely. Is the robust family’s number robust?
Judgement Essay 1 (part of the 15%)
Prompt. Why do the heterogeneity-robust estimators of Part C all fail together here, rather than one catching what another misses? What kind of selection produces a non-zero pre-trend that a unit fixed effect cannot remove? Contrast selection on the level of footfall (which a unit fixed effect absorbs, leaving trends parallel) with selection on the trend/slope of footfall (which it does not). Tie this to how a real operations team might have chosen where to roll out next, and to what that implies about every DiD estimate you have produced so far.
Write this as a short essay (≈ 300–500 words), grounded in this dataset — cite the pre-trend drift and the breakdown value you measured, not textbook generalities.
Part E — The single-flagship synthetic control (15%)
Now the flagship — one treated market, no cohort. Work on the market-week panel (grocery_markets.csv), outcome log(footfall_per_store), with the never-treated markets as the donor pool and the pre-period being the weeks before the flagship’s go-live. Produce a small bake-off table of the synthetic-control family:
- Classic (Abadie–Diamond–Hainmueller) synthetic control. Build a convex weighting of donors that tracks the flagship’s pre-period trajectory. Report the pre-fit RMSPE, name the donor markets carrying the most weight (is it a genuine blend, or a single-donor collapse?), and read off the post-period gap (the SC ATT).
- Is the gap more than noise? At \(N = 1\) you cannot cluster or run a \(t\)-test. Use placebo-in-space (re-run the SC pretending each donor was treated; rank the flagship’s post/pre MSPE ratio among them for a Fisher-style \(p\)) and a modern prediction interval (
scpi) for an honest effect band. Report the placebo rank as a rank, not as “significant at 5%” — with a small donor pool the Fisher \(p\) has a floor. What does each device tell you that a point estimate cannot? - Refine and stress-test. Fit an augmented (ridge) synthetic control and compare — does correcting the pre-fit move the answer, and should it, given how good the classic pre-fit already is? Optionally add synthetic difference-in-differences and read the DiD / SC / synthetic-DiD triple as three corners of one weighted two-way regression. Note honestly where estimates agree to the digit (a deterministic quadratic program) versus where they differ by an engine/predictor choice (synthetic-control weights are not unique). Land on a defensible flagship effect with a band, and say plainly how confident a single-unit estimate lets you be.
Part G — Parallel-trends test II: placebo, leave-one-out, sensitivity (8%)
Part F fixed a rank and recovered a number. Part G asks the only question Part V cannot answer with a truth line: do we believe that imputation of \(Y(0)\)? Nothing here is a pass/fail gate — each test hands back a price, and the discipline is naming the currency.
- Placebo test. Blank the last few pre-treatment periods and re-impute them out of sample (
placeboTest = TRUE). Run it on both the additive model (\(r = 0\)) and the factor model (at your CV rank). Does the placebo fire on the additive model (the blanked pre-cells come back non-zero) and pass on the factor model? If so, state the identification punchline in one sentence: parallel trends did not hold on the raw panel, but it holds once the factor structure is removed. - Relative-magnitudes sensitivity. Run a relative-magnitudes sensitivity analysis on the factor fit’s post-launch ATT (R
fect_sens, backed by the HonestDiDFEct machinery). Report the breakdown value \(\bar M^{*}\) — but never bare. \(\bar M\) is a multiple of a ruler this model cut out of its own pre-window residual; report \(\bar M^{*}\) paired with that ruler (in pp/week), and refuse to compare it “like for like” to Part D’s breakdown value (different fit, different ruler). Check the sensitivity band’s upper edge: is it a real bound or a pinned search-grid ceiling? Report the side that actually determines \(\bar M^{*}\). - Leave-one-out joint pre-trend. Re-impute every pre-period out of sample (
loo = TRUE, ~60 min with a trustworthy bootstrap count of ~200 — or usese = FALSEfor the much faster deterministic jackknife variant; cache it either way) and read the joint pre-trend / equivalence tests. Watch the stale-object trap: the numbers afectplot prints for a leave-one-out fit come from a different slot than the ones the default in-sample test returns — quote the number from where the figure got it (loo.test.out), not the cached in-sampletest.out.
Close with a four-line verdict: do we believe the factor imputation of \(Y(0)\), and what does each test cost?
Part H — Heterogeneity, the bake-off, and the board memo (7%)
Bring everything together for FreshField’s board.
- Heterogeneity from the imputation estimator. The factor fit returns an estimated effect for every treated store-week, not just a headline. Split the treated markets by
urban_share(e.g. tertiles) and report the effect by group. Is the lift uniform, or graded? Guard against two artefacts: a common-horizon check (do groups launched at different times get averaged over ramps of different lengths?) and a per-group placebo audit (is any group’s counterfactual contaminated?). - The estimand-annotated bake-off table. One row per estimator: the naive number, static TWFE, the heterogeneity-robust family, the factor/MC imputers, and the flagship synthetic-control family — each with its estimand (portfolio vs. flagship), its estimate, and the assumption it buys. Rank by assumption credibility, not by number. This table is your assumption ledger.
- The estimand-driven method choice, stated explicitly. Which lens does each of the board’s two questions select, and why the estimand — not the tool — chose it? Name the two traps you defused (a rollout selected on the outcome’s own trend; a post-treatment “control”).
- A one-page board memo. The flagship effect and the portfolio effect, each as a number plus a counterfactual and a caveat, an honest verdict on how much to trust a single-unit estimate, a clear recommendation on the rollout, and the heterogeneity finding if you trust it. Do not collapse the two effects into one headline.
Judgement Essay 3 (part of the 7%)
Prompt. Is the urban heterogeneity you measured a genuine causal effect-modification finding, or an artefact of the factor model / the rollout schedule? Separate the possible artefact channels (timing, the counterfactual model, the choice of rank), rule out what you can and bound what you cannot, and state concretely what you would need to see to believe it fully.
Write this as a short essay (≈ 300–500 words). The audit, not the anecdote, decides — do not transplant a conclusion from lecture; read this data.
Rubric
Each part is graded on the four-column scale below. Parts D, F, and H bundle their judgement essay into the stated weight.
| Part | Weight | Excellent | Good | Needs Improvement | Missing / Inadequate |
|---|---|---|---|---|---|
| A — Rollout + 2×2 | 10% | Go-live derived from sco_live; cohort structure correct; the flagship identified as the lone cohort (with the explicit note it is not the earliest wave, and that cohort-size-1 forces SC over DiD); both estimands stated as distinct; panel confirmed balanced and never-treated pool shown to bracket the treated covariates; a hand 2×2 and its identity with two-period TWFE; the naive number explained as upward bias from common time movement, not selection-on-winners. |
Structure and estimands right, but the flagship rationale is thin or the earliest wave is mistaken for the flagship; 2×2 computed but the TWFE-equivalence not shown. | Partial structure; estimands not distinct; or the flagship folded into the DiD waves. | No derivation. |
| B — TWFE, Bacon, bad control | 12% | Static + dynamic TWFE on log(footfall), market-clustered, with an event-study plot; Goodman–Bacon reported with the forbidden-comparison weight, used to argue whether staggering is the disease here; staffed_lanes shown to be caused by treatment, diagnosed as a post-treatment mediator, correctly excluded, and the over-control bias named. |
Ladder + Bacon built, but the “is staggering the whole story” reading is thin, or the bad-control mechanism/naming is loose. | TWFE reported as if unbiased; or staffed_lanes added as a “control” with no diagnosis. |
Not addressed. |
| C — Heterogeneity-robust family | 13% | CS (never-treated + DR + universal base + market clustering, overall and per-cohort) and BJS, plus SA/Gardner, in a bake-off; states the family roughly agrees yet all sit high, and warns the agreement certifies the staggering fix, not parallel trends; reads the per-cohort pattern as a selection signature; does not average the ladder. | Required menu built and clustered, but one estimator or the “shared-bias, not corroboration” reading is under-argued. | Two estimators only; or the robust number reported as the portfolio effect with no caveat that PT is untested. | No heterogeneity-robust estimator. |
| D — PT test I + Essay 1 | 15% | Universal-base event study that surfaces the drifting pre-trend (deviation + drift correlation quantified); a power read on the pre-test; HonestDiD breakdown \(\bar M^{*}\) read as fragility, not a pass; Essay 1 nails the level-vs-trend contrast (unit FE absorbs the level, not the slope) and connects the drift to a plausible ops selection story, grounded in the measured numbers. | Event study + violation found and a breakdown value, but the why (Essay 1) is qualitative or the power read is missing. | Pooled/differenced pre-trend only; violation missed or unexplained; HonestDiD read as a pass. | No PT diagnostic. |
| E — Single-flagship SC | 15% | Classic SC with pre-RMSPE + donor weights; placebo-in-space (as a rank) and an scpi band for honest inference; augmented-SC (and optionally synthetic-DiD) contrast; a defensible flagship effect with a band, plus an honest note on weight non-uniqueness / to-the-digit agreement. |
SC + one inference device + a point effect, but the honest band or the ASCM/robustness contrast is thin. | Point SC with no inference; or the donor pool mis-specified (treated or flagship markets left in). | No synthetic control. |
| F — Factor imputation + Essay 2 | 20% | IFEct vs. MC raced under CV with the pick measured across seeds (not fished); dynamic-gap plots; factors extracted, signs pinned to a proxy, and named from observable series; the time-FE “factor” trap avoided; Essay 2 convicts the guilty factor and acquits the innocent one using the loading-vs-go-live correlation for both, with a business translation of the rollout selection. | Factors named and the guilty one identified, but the naming leans on assertion over correlation, or the seed-stability / sign-pinning discipline is skipped. | Factors named from the DGP rather than the data; wrong factor convicted; the time FE read as a real factor; or the CV rank hunted rather than reported. | No factor/imputation estimator. |
| G — PT test II | 8% | Placebo on both \(r=0\) and the factor rank (fires additively, passes with factors); relative-magnitudes \(\bar M^{*}\) reported as a pair with its ruler and not compared like-for-like to Part D; the pinned-ceiling / stale-LOO-object traps avoided; treats every \(p\)-value as a price, not a gate. | Two of the three tests run and read correctly, but a bare \(\bar M^{*}\), a like-for-like comparison to Part D, or the stale LOO number slips in. | One test only, or any \(p\)-value read as a pass/fail gate. | No second PT diagnostic. |
| H — Heterogeneity, bake-off, memo + Essay 3 | 7% | Group heterogeneity with a common-horizon check and a per-group placebo audit; an estimand-annotated bake-off ranked by assumption credibility that is a real ledger; the estimand-driven lens choice made explicit; a one-page memo with both effects (number + counterfactual + caveat), an honest single-unit verdict, and an actionable recommendation; Essay 3 separates the artefact channels and states what would convince. | Memo + reconciliation present, but the ledger or the method-choice logic is thin, the two effects blur, or Essay 3 misses an artefact channel. | Numbers restated with no estimand interpretation or recommendation. | No memo. |
In addition to part-specific scoring, the general evaluation criteria apply (correctness, code quality, communication clarity, reproducibility, and AI-use disclosure per course policy — see the syllabus).
Submission
Content
- A written report of 10–12 pages (selectively displayed code, figures, references), structured per the eight parts above, including the three judgement essays and the board memo.
- Annotated, reproducible code (R or Python). The instructor must be able to open your source file, install the listed packages, and re-render your report end-to-end on a clean machine. Cache the slow
fectfits so a re-render does not take 20 minutes.
Format
- Submit one source file plus the rendered PDF. The source file may be a Quarto Markdown (
.qmd) or a Jupyter notebook (.ipynb) — both accepted, in either R or Python. - File names:
[YourLastName]_Assignment_5.{qmd|ipynb}and[YourLastName]_Assignment_5.pdf. - Bundle the two files into a single zip:
[YourLastName]_Assignment_5.zip. - Do not include the CSVs in the zip. When grading, the instructor places the two provided CSVs in the same folder as your source file before re-rendering; read them with plain relative paths (
fread("grocery_panel.csv")), not parent-directory paths. - If your code reads from any additional external file you created (e.g. a cached fit or a saved weights table), bundle it alongside the source file — but never the provided datasets.
Deadline
- Sunday, 2 August 2026, 23:59 (Hamburg local time).
- Upload to the StudIP folder (link in the course shell).