--- title: "Research-management analytics: coverage, attribution, and policy evaluation" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Research-management analytics: coverage, attribution, and policy evaluation} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} resource_files: - figures/logo.svg - figures/logo-dark.svg --- [![R-CMD-check](https://github.com/CTTIR/scimapR/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/CTTIR/scimapR/actions/workflows/R-CMD-check.yaml) [![pkgdown](https://github.com/CTTIR/scimapR/actions/workflows/pkgdown.yaml/badge.svg)](https://cttir.github.io/scimapR/) [![CRAN status](https://www.r-pkg.org/badges/version/scimapR)](https://CRAN.R-project.org/package=scimapR) [![Codecov test coverage](https://codecov.io/gh/CTTIR/scimapR/branch/main/graph/badge.svg)](https://app.codecov.io/gh/CTTIR/scimapR?branch=main) [![CRAN downloads](https://cranlogs.r-pkg.org/badges/scimapR)](https://cran.r-project.org/package=scimapR) [![CRAN downloads total](https://cranlogs.r-pkg.org/badges/grand-total/scimapR)](https://cran.r-project.org/package=scimapR) [![License: MIT](https://img.shields.io/badge/license-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 6, fig.height = 4, message = FALSE ) library(scimapR) ``` This article walks an end-to-end research-management study: auditing an institution's publication output against a ground-truth tracker, capturing affiliations robustly, evaluating whether an intervention changed output, and summarising impact defensibly. Everything runs on synthetic example data. ## A reproducible starting corpus For arbitrary tabular sources, `sm_corpus_from_tables()` is the recommended ingestion path: it validates required columns, coerces types, fills missing optional tables, and returns a valid `sm_corpus`. ```{r from-tables} works <- data.frame( work_id = paste0("W", 1:4), title = c("Spatial transcriptomics in cancer", "Immune checkpoint resistance", "A trial of biomarker discovery", "Single-cell atlas of the tumour"), year = c("2018", "2019", "2020", "2021"), # character -> coerced to integer doi = paste0("10.1234/example.", 1:4), cited_by_count = c(40, 12, 5, 1) ) corpus <- sm_corpus_from_tables(list(works = works)) corpus ``` For the rest of the article we use the larger built-in synthetic corpus. ```{r corpus} corpus <- sm_example_corpus(n_works = 200, seed = 1) ``` ## 1. Coverage auditing Suppose a manual tracker lists the works the institution *should* have. We can measure recall and precision of the corpus against it, and see which years are worst covered. ```{r coverage} reference <- corpus$works[1:150, c("work_id", "doi", "title", "year")] names(reference)[1] <- "id" cov <- sm_coverage_audit(corpus, reference, by = "year", match = "doi") cov summary(cov) ``` ```{r coverage-plot} ggplot2::autoplot(cov) ``` Breakdowns are returned as a single flat tibble; use `sm_coverage_breakdowns()` to access or filter them: ```{r coverage-breakdowns} sm_coverage_breakdowns(cov, dimension = "year") ``` Source coverage can be checked against a journal master list by ISSN (`sm_journal_in_index()`), and two corpora reconciled by content with `sm_reconcile()`. ```{r journal-index} ref_index <- utils::read.csv( system.file("extdata", "example_journal_index.csv", package = "scimapR"), stringsAsFactors = FALSE ) sm_journal_in_index(c("1078-8956", "9999-9999"), index = "doaj", reference_list = ref_index) ``` Passing the same index to `sm_coverage_audit(index_table = )` assesses *record capture* and *journal indexability* together -- "did we capture this paper" and "is its journal indexed" in one pass: ```{r coverage-indexability} cov_idx <- sm_coverage_audit(corpus, reference, match = "doi", index_table = ref_index) cov_idx$indexability ``` ## 2. Affiliation capture and attribution Hand-rolled affiliation regex is brittle. `sm_affiliation_match()` uses a maintained, extensible dictionary (with multilingual variants and an email-domain fallback), and `sm_attribute_institution()` rolls matches up to a controlled vocabulary. ```{r affiliation} corpus$authorships$raw_affiliation[1:3] <- c( "Bundeswehrkrankenhaus Berlin", "Charite - Universitatsmedizin Berlin", "Walter Reed Army Institute of Research" ) corpus <- sm_affiliation_match(corpus) ror <- utils::read.csv( system.file("extdata", "example_ror.csv", package = "scimapR"), stringsAsFactors = FALSE ) corpus <- sm_attribute_institution(corpus, vocabulary = "ror", ror_table = ror) ``` `match_signal` is a factor with an exported, stable level set (`sm_affiliation_signals()`), so you can filter reliably --- for example to keep only name-token matches and inspect the evidence that triggered them: ```{r affiliation-vocab} sm_affiliation_signals() summary_tbl <- sm_affiliation_summary(corpus) subset(summary_tbl, match_signal == "name_token", select = c(institution, match_signal, n_works, example_evidence)) ``` The `example_evidence` column (and the per-authorship `match_evidence` column) give an audit trail: which signal matched, on what string. > **Stability.** Accessors like `sm_affiliation_summary()` and > `sm_coverage_breakdowns(tidy = TRUE)` follow scimapR's accessor return-type > contract (`?scimapR-stability`): their documented columns change only via a > `lifecycle` deprecation, so pipelines built on them do not break across > releases. ## 3. Policy evaluation Did an intervention in 2020 change output? An interrupted time series fits a level shift and slope change with a counterfactual. ```{r its} its <- sm_its(corpus, intervention_year = 2020, outcome = "count") its ggplot2::autoplot(its) ``` For citation-based outcomes, citation-immature recent years are excluded automatically (`sm_citation_maturity()` exposes the same flags on `works`). A treated-vs-control comparison uses difference-in-differences. Here we tag two illustrative institution groups, giving each work to one of them. ```{r did} group <- rep(c("Treated Inst", "Control Inst"), length.out = nrow(corpus$works)) corpus$authorships$institution_name <- group[match(corpus$authorships$work_id, corpus$works$work_id)] did <- sm_did(corpus, treated = "Treated Inst", control = "Control Inst", intervention_year = 2020, outcome = "count") did ``` A work co-authored by both groups belongs to both series and pulls them together. `sm_did()` warns about such works and lists them in `did$shared_works`; `shared = "exclude"` leaves them out of both series and `shared = "lead"` gives each to the group of its lead author. For `outcome = "leadership"`, `institutions` (in `sm_its()`) or `institution_aware = TRUE` (in `sm_did()` and `sm_synth()`) count only the works whose corresponding author, or the author in another `lead_role` such as `"first"`, belongs to the focal institutions. ## 4. Counting and robust impact Fractional counting attributes a multi-author paper's single unit of credit across its contributors. ```{r counting} head(sm_count(corpus, method = "fractional", level = "author"), 5) ``` Heavy-tailed metrics are summarised robustly with medians, bootstrap CIs, and a citation percentile share. This example has no reference set: its `pp_top10` equals `top_pct` by construction. Supply `top_reference` with a comparable external distribution or OpenAlex flags for an informative share. ```{r robust} sm_metric_summary(corpus, metric = "citations", seed = 1, n_boot = 500) ``` ## 5. Reproducible reporting Finally, `sm_figure_manifest()` turns a directory of exported figures into a captions / alt-text manifest for the manuscript. ```{r manifest} dir <- withr::local_tempdir() gg <- ggplot2::autoplot(its) ggplot2::ggsave(file.path(dir, "fig_its.png"), gg, width = 6, height = 4, dpi = 150) sm_figure_manifest(dir) ``` Together these functions cover the recurring tasks of a research-coverage and impact study — audit, affiliation capture, policy evaluation, robust summarisation, and reporting — on top of the reproducible `sm_corpus` foundation.