Skip to content

fix(otis): cluster-robust SE with empty cluster levels + bump to 0.9.6#9

Merged
rootcoder007 merged 2 commits into
mainfrom
fix/otis-cluster-empty-levels
Jun 29, 2026
Merged

fix(otis): cluster-robust SE with empty cluster levels + bump to 0.9.6#9
rootcoder007 merged 2 commits into
mainfrom
fix/otis-cluster-empty-levels

Conversation

@rootcoder007

Copy link
Copy Markdown
Owner

Bug

.otis_cluster_se() computed sum(grp^2) where grp = tapply(scores, cluster, sum). When the cluster factor carries levels absent from a subset (e.g. per-year analysis of an individual-clustered panel), tapply() emits NA for the empty clusters → sum becomes NANA SE → downstream if (se > 0) errors with "missing value where TRUE/FALSE needed". This broke morie_otis_irm_dml() on every by-year clustered run (pooled worked).

Fix

One line: sum(grp^2, na.rm = TRUE) — empty clusters contribute nothing, so drop them.

Verification (live OTIS public data)

All 3 by-year clustered IRM runs now return finite cluster-robust SEs matching the published values:

  • 2023 ATE 0.13435, SE 0.00975 (pub 0.00986)
  • 2024 ATE 0.15861, SE 0.01152 (pub 0.01167)
  • 2025 ATE 0.17335, SE 0.01018 (pub 0.01030)

Regression test added (test-otis-cluster-se.R): an unused factor level must not change the SE.

Version

DESCRIPTION 0.9.5.12 → 0.9.6 (matches the already-open "0.9.6 (in development)" NEWS section), with src/ UA literals + SIU parser_version bumped in lockstep. 0.9.6 > 0.9.5.12 (forward bump, no downgrade).

Isolated off main — none of the in-flight 0.9.9 ecosystem-connect work is included.

rootcoder007 and others added 2 commits June 28, 2026 23:04
.otis_cluster_se computed sum(grp^2) where grp = tapply(scores, cluster, sum).
When `cluster` is a factor carrying levels absent from a subset (e.g. per-year
analysis of an individual-clustered panel), tapply emits NA for the empty
clusters, so the sum became NA -> NA SE -> downstream `if (se > 0)` errored
("missing value where TRUE/FALSE needed"). Add na.rm = TRUE: empty clusters
contribute nothing, so drop them. Verified -- OTIS per-year clustered IRM now
returns finite cluster-robust SEs matching the published values. Regression
test added. Version unchanged (0.9.9).

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Vansh Singh Ruhela (rootcoder007) <vsruhela@proton.me>
Bump DESCRIPTION 0.9.5.12 -> 0.9.6 to match the already-open "rmorie 0.9.6
(in development)" NEWS section, and bring the src/ User-Agent literals + the
SIU parser_version macro into lockstep (AGENTS.md requires the C++ UA to track
DESCRIPTION). Ships with the cluster-robust SE empty-levels bugfix.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Vansh Singh Ruhela (rootcoder007) <vsruhela@proton.me>
@codecov-commenter

Copy link
Copy Markdown

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment

Thanks for integrating Codecov - We've got you covered ☂️

@rootcoder007 rootcoder007 merged commit a4a1a21 into main Jun 29, 2026
18 checks passed
@rootcoder007 rootcoder007 deleted the fix/otis-cluster-empty-levels branch June 29, 2026 14:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants