{"id":33187122,"url":"https://r-causal.github.io/halfmoon/","last_synced_at":"2025-11-25T18:00:41.339Z","repository":{"id":92817336,"uuid":"539177750","full_name":"r-causal/halfmoon","owner":"r-causal","description":"Techniques to Build Better Balance in Propensity Score Models","archived":false,"fork":false,"pushed_at":"2025-11-04T18:02:03.000Z","size":13043,"stargazers_count":20,"open_issues_count":1,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-11-04T18:06:48.583Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://r-causal.github.io/halfmoon/","language":"R","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/r-causal.png","metadata":{"files":{"readme":"README.Rmd","changelog":"NEWS.md","contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-09-20T20:22:37.000Z","updated_at":"2025-11-04T17:54:20.000Z","dependencies_parsed_at":"2025-02-27T19:52:59.078Z","dependency_job_id":"ca55f564-6a2f-4039-92df-d2625fa5585d","html_url":"https://github.com/r-causal/halfmoon","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/r-causal/halfmoon","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/r-causal%2Fhalfmoon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/r-causal%2Fhalfmoon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/r-causal%2Fhalfmoon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/r-causal%2Fhalfmoon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/r-causal","download_url":"https://codeload.github.com/r-causal/halfmoon/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/r-causal%2Fhalfmoon/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286079811,"owners_count":27282121,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-25T02:00:05.816Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-11-16T05:00:30.374Z","updated_at":"2025-11-25T18:00:41.322Z","avatar_url":"https://github.com/r-causal.png","language":"R","funding_links":[],"categories":["Data and models"],"sub_categories":[],"readme":"---\noutput: github_document\n---\n\n\u003c!-- README.md is generated from README.Rmd. Please edit that file --\u003e\n\n```{r, include = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  comment = \"#\u003e\",\n  fig.path = \"man/figures/README-\",\n  out.width = \"100%\"\n)\n```\n\n# halfmoon \u003cimg src=\"man/figures/logo.png\" align=\"right\" height=\"138\" /\u003e\n\n\u003c!-- badges: start --\u003e\n[![R-CMD-check](https://github.com/r-causal/halfmoon/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-causal/halfmoon/actions/workflows/R-CMD-check.yaml)\n[![Codecov test coverage](https://codecov.io/gh/r-causal/halfmoon/branch/main/graph/badge.svg)](https://app.codecov.io/gh/r-causal/halfmoon?branch=main)\n[![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental)\n[![CRAN status](https://www.r-pkg.org/badges/version/halfmoon)](https://CRAN.R-project.org/package=halfmoon)\n\u003c!-- badges: end --\u003e\n\n\u003e Within light there is darkness,\nbut do not try to understand that darkness.\nWithin darkness there is light,\nbut do not look for that light.\nLight and darkness are a pair\nlike the foot before and the foot behind in walking.\n\n-- From the Zen teaching poem [Sandokai](https://en.wikipedia.org/wiki/Sandokai).\n\nThe goal of halfmoon is to cultivate balance in propensity score models.\n\n## Installation\n\nYou can install the most recent version of halfmoon from CRAN with:\n\n``` r\ninstall.packages(\"halfmoon\")\n```\n\nYou can also install the development version of halfmoon from [GitHub](https://github.com/) with:\n\n``` r\n# install.packages(\"devtools\")\ndevtools::install_github(\"r-causal/halfmoon\")\n```\n\n## Example: Weighting\n\nhalfmoon includes several techniques for assessing the balance created by propensity score weights.\n\n```{r example}\nlibrary(halfmoon)\nlibrary(ggplot2)\n\n# weighted mirrored histograms\nggplot(nhefs_weights, aes(.fitted)) +\n  geom_mirror_histogram(\n    aes(group = qsmk),\n    bins = 50\n  ) +\n  geom_mirror_histogram(\n    aes(fill = qsmk, weight = w_ate),\n    bins = 50,\n    alpha = 0.5\n  ) + scale_y_continuous(labels = abs)\n\n# weighted ecdf\nggplot(\n  nhefs_weights,\n  aes(x = smokeyrs, color = qsmk)\n) +\n  geom_ecdf(aes(weights = w_ato)) +\n  xlab(\"Smoking Years\") +\n  ylab(\"Proportion \u003c= x\")\n\n# weighted SMDs\nplot_df \u003c- check_balance(\n  nhefs_weights,\n  race:active,\n  .exposure = qsmk,\n  .weights = c(w_ate, w_att, w_atm, w_ato),\n  .metrics = \"smd\"\n)\n\nggplot(\n  plot_df,\n  aes(\n    x = abs(estimate),\n    y = variable,\n    group = method,\n    color = method\n  )\n) +\n  geom_love()\n```\n\n## Propensity Score Diagnostics\n\nhalfmoon provides comprehensive tools for assessing propensity score model quality through ROC curves, calibration plots, and distributional diagnostics.\n\n### ROC Curves\n\nAssess how well your propensity score model discriminates between treatment groups, as well as whether or not the weights create an AUC of about 0.5 (what you would observe from a randomized experiment):\n\n```{r roc-example}\n# Check AUC across different weighting methods\nroc_results \u003c- check_model_roc_curve(\n  nhefs_weights,\n  .exposure = qsmk,\n  .fitted = .fitted,\n  .weights = c(w_ate, w_att, w_atm, w_ato)\n)\n\nauc_results \u003c- check_model_auc(\n  nhefs_weights,\n  .exposure = qsmk,\n  .fitted = .fitted,\n  .weights = c(w_ate, w_att, w_atm, w_ato)\n)\n\n# Plot ROC curves\nplot_model_roc_curve(roc_results)\n\n# Display AUC values\nplot_model_auc(auc_results)\n```\n\n### Calibration Assessment\n\nEvaluate whether predicted probabilities align with observed treatment frequencies:\n\n```{r calibration-example}\n#| warning: false\nplot_model_calibration(nhefs_weights, .fitted, qsmk)\n```\n\n### Comprehensive Balance Checking\n\nAssess balance across multiple metrics simultaneously:\n\n```{r balance-example}\n# Check balance using multiple metrics\nbalance_results \u003c- check_balance(\n  nhefs_weights,\n  .vars = race:active,\n  .exposure = qsmk,\n  .weights = c(w_ate, w_att, w_atm, w_ato),\n  .metrics = c(\"smd\", \"vr\", \"ks\", \"energy\")\n)\n\n# Visualize balance across metrics\nggplot(balance_results, aes(x = abs(estimate), y = variable)) +\n  geom_point(aes(color = method)) +\n  facet_wrap(~ metric, scales = \"free_x\") +\n  labs(x = \"Balance Statistic\", y = \"Variable\")\n```\n\n### Distributional Balance with QQ Plots\n\nAssess distributional balance between treatment groups:\n\n```{r qq-example}\nplot_qq(nhefs_weights, age, qsmk, .weights = c(w_ate, w_att))\n```\n\n## Example: Matching\n\nhalfmoon also has support for working with matched datasets. Consider these two objects from the [MatchIt](https://github.com/kosukeimai/MatchIt) documentation:\n\n```{r}\nlibrary(MatchIt)\n# Default: 1:1 NN PS matching w/o replacement\nm.out1 \u003c- matchit(treat ~ age + educ + race + nodegree +\n                   married + re74 + re75, data = lalonde)\n\n# 1:1 NN Mahalanobis distance matching w/ replacement and\n# exact matching on married and race\nm.out2 \u003c- matchit(treat ~ age + educ + race + nodegree +\n                   married + re74 + re75, data = lalonde,\n                   distance = \"mahalanobis\", replace = TRUE,\n                  exact = ~ married + race)\n```\n\nOne option is to just look at the matched dataset with halfmoon:\n\n```{r}\nmatched_data \u003c- get_matches(m.out1)\n\nmatch_smd \u003c- check_balance(\n  matched_data,\n  c(age, educ, race, nodegree, married, re74, re75),\n  .exposure = treat,\n  .metrics = \"smd\"\n)\n\nplot_balance(match_smd)\n```\n\nThe downside here is that you can't compare multiple matching strategies to the observed dataset; the label on the plot is also wrong. halfmoon comes with a helper function, `bind_matches()`, that creates a dataset more appropriate for this task:\n\n```{r}\nmatches \u003c- bind_matches(lalonde, m.out1, m.out2)\nhead(matches)\n```\n\n`matches` includes an binary variable for each `matchit` object which indicates if the row was included in the match or not. Since downweighting to 0 is equivalent to filtering the datasets to the matches, we can more easily compare multiple matched datasets with `.wts`:\n\n```{r}\nmany_matched_smds \u003c- check_balance(\n  matches,\n  c(age, educ, race, nodegree, married, re74, re75),\n  .exposure = treat,\n  .weights = c(m.out1, m.out2),\n  .metrics = \"smd\"\n)\n\nplot_balance(many_matched_smds)\n```\n\nWe can also extend the idea that matching indicators are weights to weighted mirrored histograms, giving us a good idea of the range of propensity scores that are being removed from the dataset.\n\n```{r}\n# use the distance as the propensity score\nmatches$ps \u003c- m.out1$distance\n\nggplot(matches, aes(ps)) +\n    geom_mirror_histogram(\n        aes(group = factor(treat)),\n        bins = 50\n    ) +\n    geom_mirror_histogram(\n        aes(fill = factor(treat), weight = m.out1),\n        bins = 50,\n        alpha = 0.5\n    ) + scale_y_continuous(labels = abs)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/r-causal.github.io%2Fhalfmoon%2F","html_url":"https://awesome.ecosyste.ms/projects/r-causal.github.io%2Fhalfmoon%2F","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/r-causal.github.io%2Fhalfmoon%2F/lists"}