From a872b5ae5921b974976cdd055b5ff954ac837db6 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Thu, 21 Nov 2024 15:28:05 -0500 Subject: [PATCH 01/33] Add googlesheets functions to set validation Rely on INTERNAL googlesheets4 functions. --- R/googlesheets.R | 87 +++++++++++++++++++++++++++++++++++++ man/range_add_validation.Rd | 64 +++++++++++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 R/googlesheets.R create mode 100644 man/range_add_validation.Rd diff --git a/R/googlesheets.R b/R/googlesheets.R new file mode 100644 index 00000000..c5aa4985 --- /dev/null +++ b/R/googlesheets.R @@ -0,0 +1,87 @@ +# For information about data validation see, +# https://github.com/tidyverse/googlesheets4/blob/main/R/range_add_validation.R + +#' Add Data Validation to Google Sheet Range +#' +#' Add data validation to a Google Sheet range. +#' +#' @inheritParams googlesheets4::range_write +#' @param range Cells to apply data validation to. This `range` argument has +#' important similarities and differences to `range` elsewhere (e.g. +#' [range_read()]): +#' * Similarities: Can be a cell range, using A1 notation ("A1:D3") or using +#' the helpers in [`cell-specification`]. Can combine sheet name and cell +#' range ("Sheet1!A5:A") or refer to a sheet by name (`range = "Sheet1"`, +#' although `sheet = "Sheet1"` is preferred for clarity). +#' * Difference: Can NOT be a named range. +#' @param msg The message to display when the user types in a value that +#' violates the data validation rule. For `range_add_dropdown()`, only displayed +#' if `reject_input` is `TRUE`. +#' @param values The values to use for the dropdown list, as a character vector. +#' @param reject_input Whether to "Reject the input" (default: `TRUE`) if a +#' value violates the data validation rule or "Show a warning" (`FALSE`). +#' @param display_arrow Whether to display a dropdown arrow next to the cell +#' (default: `TRUE`) or not (`FALSE`). +#' +#' @rdname range_add_validation + +#' @rdname range_add_validation +#' @keywords internal +range_add_checkbox <- function(ss, range, msg = "Value must be TRUE or FALSE", + quiet = TRUE) { + rule <- googlesheets4:::new( + "DataValidationRule", + condition = googlesheets4:::new_BooleanCondition(type = "BOOLEAN"), + inputMessage = msg, + strict = TRUE, # same as in range_add_dropdown(), FALSE doesn't make sense + showCustomUi = TRUE # seems to be ignored + ) + + if (quiet) { + .fn <- function(...) { + googlesheets4::with_gs4_quiet(googlesheets4:::range_add_validation(...)) + } + } else { + .fn <- function(...) { + googlesheets4:::range_add_validation(...) + } + } + + .fn(ss = ss, range = range, rule = rule) +} + +#' @rdname range_add_validation +#' @keywords internal +range_add_dropdown <- function(ss, range, values, msg = "Choose a valid value", + reject_input = TRUE, display_arrow = TRUE, + quiet = TRUE) { + rule <- googlesheets4:::new( + "DataValidationRule", + condition = googlesheets4:::new_BooleanCondition( + type = "ONE_OF_LIST", + values = values + ), + inputMessage = msg, + strict = reject_input, + showCustomUi = display_arrow + ) + + if (quiet) { + .fn <- function(...) { + googlesheets4::with_gs4_quiet(googlesheets4:::range_add_validation(...)) + } + } else { + .fn <- function(...) { + googlesheets4:::range_add_validation(...) + } + } + + .fn(ss = ss, range = range, rule = rule) +} + + +# microbenchmark( +# cur_cols %>% purrr::map_dfc(setNames, object = list(logical())), +# cur_cols %>% purrr::map_dfc(~ tibble::tibble(!!.x := logical())), + +# ) diff --git a/man/range_add_validation.Rd b/man/range_add_validation.Rd new file mode 100644 index 00000000..d5a16253 --- /dev/null +++ b/man/range_add_validation.Rd @@ -0,0 +1,64 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/googlesheets.R +\name{range_add_checkbox} +\alias{range_add_checkbox} +\alias{range_add_dropdown} +\title{Add Data Validation to Google Sheet Range} +\usage{ +range_add_checkbox( + ss, + range, + msg = "Value must be TRUE or FALSE", + quiet = TRUE +) + +range_add_dropdown( + ss, + range, + values, + msg = "Choose a valid value", + reject_input = TRUE, + display_arrow = TRUE, + quiet = TRUE +) +} +\arguments{ +\item{ss}{Something that identifies a Google Sheet: +\itemize{ +\item its file id as a string or \code{\link[googledrive:drive_id]{drive_id}} +\item a URL from which we can recover the id +\item a one-row \code{\link[googledrive:dribble]{dribble}}, which is how googledrive +represents Drive files +\item an instance of \code{googlesheets4_spreadsheet}, which is what \code{\link[googlesheets4:gs4_get]{gs4_get()}} +returns +} + +Processed through \code{\link[googlesheets4:as_sheets_id]{as_sheets_id()}}.} + +\item{range}{Cells to apply data validation to. This \code{range} argument has +important similarities and differences to \code{range} elsewhere (e.g. +\code{\link[=range_read]{range_read()}}): +\itemize{ +\item Similarities: Can be a cell range, using A1 notation ("A1:D3") or using +the helpers in \code{\link{cell-specification}}. Can combine sheet name and cell +range ("Sheet1!A5:A") or refer to a sheet by name (\code{range = "Sheet1"}, +although \code{sheet = "Sheet1"} is preferred for clarity). +\item Difference: Can NOT be a named range. +}} + +\item{msg}{The message to display when the user types in a value that +violates the data validation rule. For \code{range_add_dropdown()}, only displayed +if \code{reject_input} is \code{TRUE}.} + +\item{values}{The values to use for the dropdown list, as a character vector.} + +\item{reject_input}{Whether to "Reject the input" (default: \code{TRUE}) if a +value violates the data validation rule or "Show a warning" (\code{FALSE}).} + +\item{display_arrow}{Whether to display a dropdown arrow next to the cell +(default: \code{TRUE}) or not (\code{FALSE}).} +} +\description{ +Add data validation to a Google Sheet range. +} +\keyword{internal} From e683e00f8cd72e3c5d45294164d588ccfe318270 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Thu, 21 Nov 2024 15:36:14 -0500 Subject: [PATCH 02/33] Add curation_template() and spreadsheet_range() - curation_template() creates a curation template in a Google sheet... can't write data yet - spreadhseet_range() calculates and returns a range as expected by Excel or Google Sheets --- NAMESPACE | 1 + R/curation.R | 92 ++++++++++++++++++++++++++++++++++++++++ man/curation_template.Rd | 37 ++++++++++++++++ man/spreadsheet_range.Rd | 27 ++++++++++++ 4 files changed, 157 insertions(+) create mode 100644 R/curation.R create mode 100644 man/curation_template.Rd create mode 100644 man/spreadsheet_range.Rd diff --git a/NAMESPACE b/NAMESPACE index 729e87b9..d6c1c589 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -56,6 +56,7 @@ export(confine_list) export(count_alliance_records) export(count_delim) export(cur_yr) +export(curation_template) export(download_alliance_tsv) export(download_file) export(download_obo_ontology) diff --git a/R/curation.R b/R/curation.R new file mode 100644 index 00000000..fe92d856 --- /dev/null +++ b/R/curation.R @@ -0,0 +1,92 @@ + +#' Create a Curation Template +#' +#' Create a curation template in a Google Sheet, optionally including data. +#' +#' @inheritParams googlesheets4::range_write +#' @param sheet (OPTIONAL) The sheet name, as a string. If `NULL` (default), the +#' sheet name will default to "curation-" with today's date appended (formatted +#' as "%Y%m%d"; see [format.Date()]). +#' @param .data Data to add to the curation sheet. If `NULL` (default), an empty +#' curation sheet will be created. +#' @param nrow The number of rows to create in the curation template when +#' `data = NULL` (default: `50`). +#' +#' @returns The Google Sheet info (`ss`), as a [googlesheets4::sheets_id]. +#' +#' @export +curation_template <- function(ss = NULL, sheet = NULL, .data = NULL, + nrow = 50) { + cur_cols <- c( + "iri/curie", "annotation", "value", "remove", "curation_notes", "links", + "action_notes", "status" + ) + + if (is.null(.data)) { # create empty curation sheet + val <- rep(NA, nrow) + + # inspired by https://stackoverflow.com/a/60495352/6938922 + cur_df <- tibble::as_tibble(rlang::rep_named(cur_cols, list(val))) + } else { + cur_df <- .data # NEED TO ADD REFORMATTING HERE!!! + } + + if (is.null(sheet)) sheet <- paste0("curation-", format(Sys.Date(), "%Y%m%d")) + gs_info <- googlesheets4::write_sheet(cur_df, ss, sheet) + + # add curation template data validation + gs_range <- spreadsheet_range(cur_df, "annotation", sheet = sheet) + range_add_dropdown(ss, gs_range, values = .curation_opts$header) + + # freeze first two columns + googlesheets4::with_gs4_quiet( + googlesheets4:::sheet_freeze(ss, sheet = sheet, ncol = 2) + ) + + invisible(gs_info) +} + + +# helpers -------------------------------------------------------------------- + +#' Calculate a Spreadsheet Range +#' +#' Calculate a range for a spreadsheet program (Google Sheets or Excel). +#' +#' @inheritParams curation_template +#' @param .data A tibble. +#' @param .col The column to use for the range, as a string. +#' @param rows (OPTIONAL) The rows to use for the range, either as a continous +#' integer vector or as a string (i.e. "1:10"). If `NULL` (default), the entire +#' column will be used. +#' @param n_header The number of header rows to skip (default: `1`). +#' +#' @keywords internal +spreadsheet_range <- function(.data, .col, sheet = NULL, rows = NULL, + n_header = 1) { + col_letter <- LETTERS[which(names(.data) == .col)] + if (length(col_letter) != 1) { + rlange::abort("Exactly one column must be specified in `.col`") + } + + if (is.null(rows)) { + row_ends <- c(1, nrow(.data)) + n_header + } else if (is.numeric(rows)) { + # check one continuous range + collapsed_range <- to_range(rows, sep = c(",", ":")) + if (stringr::str_count(collapsed_range, "[,:]") > 1) { + rlang::abort( + c("`rows` must be one continuous range", x = collapsed_range) + ) + } + row_ends <- c(rows[1], tail(rows, 1)) + n_header + } else { + row_ends <- as.integer(stringr::str_split(row_ends, ":")[[1]]) + n_header + } + + range <- paste0(col_letter, row_ends, collapse = ":") + if (!is.null(sheet)) { + range <- paste0(sheet, "!", range) + } + range +} \ No newline at end of file diff --git a/man/curation_template.Rd b/man/curation_template.Rd new file mode 100644 index 00000000..c3f10cdf --- /dev/null +++ b/man/curation_template.Rd @@ -0,0 +1,37 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/curation.R +\name{curation_template} +\alias{curation_template} +\title{Create a Curation Template} +\usage{ +curation_template(ss = NULL, sheet = NULL, .data = NULL, nrow = 50) +} +\arguments{ +\item{ss}{Something that identifies a Google Sheet: +\itemize{ +\item its file id as a string or \code{\link[googledrive:drive_id]{drive_id}} +\item a URL from which we can recover the id +\item a one-row \code{\link[googledrive:dribble]{dribble}}, which is how googledrive +represents Drive files +\item an instance of \code{googlesheets4_spreadsheet}, which is what \code{\link[googlesheets4:gs4_get]{gs4_get()}} +returns +} + +Processed through \code{\link[googlesheets4:as_sheets_id]{as_sheets_id()}}.} + +\item{sheet}{(OPTIONAL) The sheet name, as a string. If \code{NULL} (default), the +sheet name will default to "curation-" with today's date appended (formatted +as "\%Y\%m\%d"; see \code{\link[=format.Date]{format.Date()}}).} + +\item{.data}{Data to add to the curation sheet. If \code{NULL} (default), an empty +curation sheet will be created.} + +\item{nrow}{The number of rows to create in the curation template when +\code{data = NULL} (default: \code{50}).} +} +\value{ +The Google Sheet info (\code{ss}), as a \link[googlesheets4:sheets_id]{googlesheets4::sheets_id}. +} +\description{ +Create a curation template in a Google Sheet, optionally including data. +} diff --git a/man/spreadsheet_range.Rd b/man/spreadsheet_range.Rd new file mode 100644 index 00000000..f2e5629a --- /dev/null +++ b/man/spreadsheet_range.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/curation.R +\name{spreadsheet_range} +\alias{spreadsheet_range} +\title{Calculate a Spreadsheet Range} +\usage{ +spreadsheet_range(.data, .col, sheet = NULL, rows = NULL, n_header = 1) +} +\arguments{ +\item{.data}{A tibble.} + +\item{.col}{The column to use for the range, as a string.} + +\item{sheet}{(OPTIONAL) The sheet name, as a string. If \code{NULL} (default), the +sheet name will default to "curation-" with today's date appended (formatted +as "\%Y\%m\%d"; see \code{\link[=format.Date]{format.Date()}}).} + +\item{rows}{(OPTIONAL) The rows to use for the range, either as a continous +integer vector or as a string (i.e. "1:10"). If \code{NULL} (default), the entire +column will be used.} + +\item{n_header}{The number of header rows to skip (default: \code{1}).} +} +\description{ +Calculate a range for a spreadsheet program (Google Sheets or Excel). +} +\keyword{internal} From 41876fe3fc5ed2d287e783e9a6dcf47254a85c55 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Fri, 22 Nov 2024 16:10:10 -0500 Subject: [PATCH 03/33] Fix googlesheets range_add_validation() documentation --- R/googlesheets.R | 4 ++-- man/range_add_validation.Rd | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/R/googlesheets.R b/R/googlesheets.R index c5aa4985..f97e7d59 100644 --- a/R/googlesheets.R +++ b/R/googlesheets.R @@ -22,8 +22,8 @@ #' value violates the data validation rule or "Show a warning" (`FALSE`). #' @param display_arrow Whether to display a dropdown arrow next to the cell #' (default: `TRUE`) or not (`FALSE`). -#' -#' @rdname range_add_validation +#' @name range_add_validation +NULL #' @rdname range_add_validation #' @keywords internal diff --git a/man/range_add_validation.Rd b/man/range_add_validation.Rd index d5a16253..0dc29777 100644 --- a/man/range_add_validation.Rd +++ b/man/range_add_validation.Rd @@ -1,6 +1,7 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/googlesheets.R -\name{range_add_checkbox} +\name{range_add_validation} +\alias{range_add_validation} \alias{range_add_checkbox} \alias{range_add_dropdown} \title{Add Data Validation to Google Sheet Range} From b0eb55c38b00cc4df5bd92ae9e8e7e7a2a28fa82 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Mon, 25 Nov 2024 11:24:36 -0500 Subject: [PATCH 04/33] Fix curation_template() to work with no inputs PROBLEM: Adding validation fails when trying to create a new Google Sheet from scratch. FIX: Pass newly created ss identifier to validation & freeze functions. --- R/curation.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/R/curation.R b/R/curation.R index fe92d856..3b14ad6e 100644 --- a/R/curation.R +++ b/R/curation.R @@ -34,6 +34,8 @@ curation_template <- function(ss = NULL, sheet = NULL, .data = NULL, if (is.null(sheet)) sheet <- paste0("curation-", format(Sys.Date(), "%Y%m%d")) gs_info <- googlesheets4::write_sheet(cur_df, ss, sheet) + if (is.null(ss)) ss <- gs_info + # add curation template data validation gs_range <- spreadsheet_range(cur_df, "annotation", sheet = sheet) range_add_dropdown(ss, gs_range, values = .curation_opts$header) From 75ca680cdf03e764c2bf97ec61e79ada220cb359 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Wed, 4 Dec 2024 10:40:44 -0500 Subject: [PATCH 05/33] Update spanish headers for .curation_opts Shorten and standardize so future languages are easily managed. Using IETF BCP 47 language tags to identify different languages. --- data-raw/curation_opts.csv | 99 +++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 45 deletions(-) diff --git a/data-raw/curation_opts.csv b/data-raw/curation_opts.csv index 46a16c17..5f6bf889 100644 --- a/data-raw/curation_opts.csv +++ b/data-raw/curation_opts.csv @@ -1,57 +1,66 @@ -header,template,type,example,optional_values,alternate_title,alternate_format,notes -iri/curie,ID,required manual,DOID:0080943,IRI or CURIE,NA,NA,NA -label,AL rdfs:label@en,required manual,"46,XX sex reversal 5",NA,NA,NA,NA -parent iri/curie,SC % SPLIT=|,required manual,DOID:0111760,disease by infectious agent,CI,IRI or CURIE; CI means Class IRI --> type will be CLASS_TYPE,NA -definition,AL obo:IAO_0000115@en,required manual,"A 46,XX sex reversal that is characterized by genital virilization in 46,XX individuals, associated with congenital heart disease and variable somatic anomalies including blepharophimosis-ptosis-epicanthus inversus syndrome and congenital diaphragmatic hernia and that has_material_basis_in heterozygous mutation in the NR2F2 gene on chromosome 15q26.",NA,NA,NA,NA -definition source(s),>A oboInOwl:hasDbXref SPLIT=|,required manual,url:https://pubmed.ncbi.nlm.nih.gov/29478779/,NA,NA,NA,NA -definition source type(s),>AI dc11:type SPLIT=|,optional manual,curator inference from journal publication,"ECO codes, e.g. ECO:0007645",NA,NA,do not quote!!! -synonym(s): exact,AL oboInOwl:hasExactSynonym@en SPLIT=|,optional manual,hemangiosarcoma,NA,NA,NA,do not quote!!! -synonym(s): broad,AL oboInOwl:hasBroadSynonym@en SPLIT=|,optional manual,NA,NA,NA,NA,NA -synonym(s): narrow,AL oboInOwl:hasNarrowSynonym@en SPLIT=|,optional manual,NA,NA,NA,NA,NA -synonym(s): related,AL oboInOwl:hasRelatedSynonym@en SPLIT=|,optional manual,NA,NA,NA,NA,NA -acronym(s): exact,AL oboInOwl:hasExactSynonym@en SPLIT=|,optional manual,CAMRQ,NA,NA,NA,"must be accompanied by ""acronym annotation"" header/template in the adjacent column to the rigth in robot template" -acronym(s): broad,AL oboInOwl:hasBroadSynonym@en SPLIT=|,optional manual,NA,NA,NA,NA,NA -acronym(s): narrow,AL oboInOwl:hasNarrowSynonym@en SPLIT=|,optional manual,NA,NA,NA,NA,NA -acronym(s): related,AL oboInOwl:hasRelatedSynonym@en SPLIT=|,optional manual,DES,NA,NA,NA,"must be accompanied by ""acronym annotation"" header/template in the adjacent column to the rigth in robot template" -acronym annotation,>AI oboInOwl:hasSynonymType,optional auto,acronym,NA,NA,NA,NA -xref(s),A oboInOwl:hasDbXref SPLIT=|,optional manual,OMIM:618901,NA,NA,NA,NA +header,template,type,example,optional_values,alternate_title,alternate_format,sparql,notes +id,ID,required manual,DOID:0080943,IRI or CURIE,NA,NA,?iri a owl:Class .,replaced older 'iri/curie' header for simplicity +iri/curie,ID,required manual,DOID:0080943,IRI or CURIE,NA,NA,?iri a owl:Class .,deprecated +label,AL rdfs:label@en,required manual,"46,XX sex reversal 5",NA,NA,NA,?iri rdfs:label ?label .,NA +parent iri/curie,SC % SPLIT=|,required manual,DOID:0111760,disease by infectious agent,CI,IRI or CURIE; CI means Class IRI --> type will be CLASS_TYPE,"?iri rdfs:subClassOf ?parent . +FILTER(!isBlank(?parent))",NA +definition,AL obo:IAO_0000115@en,required manual,"A 46,XX sex reversal that is characterized by genital virilization in 46,XX individuals, associated with congenital heart disease and variable somatic anomalies including blepharophimosis-ptosis-epicanthus inversus syndrome and congenital diaphragmatic hernia and that has_material_basis_in heterozygous mutation in the NR2F2 gene on chromosome 15q26.",NA,NA,NA,?iri obo:IAO_0000115 ?definition .,NA +definition source(s),>A oboInOwl:hasDbXref SPLIT=|,required manual,url:https://pubmed.ncbi.nlm.nih.gov/29478779/,NA,NA,NA,"!<<.definition_axiom>>! + oboInOwl:hasDbXref ?def_src .",NA +definition source type(s),>AI dc11:type SPLIT=|,optional manual,curator inference from journal publication,"ECO codes, e.g. ECO:0007645",NA,NA,"!<<.definition_axiom>>! + dc:type ?src_type .",do not quote!!! +synonym(s): exact,AL oboInOwl:hasExactSynonym@en SPLIT=|,optional manual,hemangiosarcoma,NA,NA,NA,"glueV: .synonym_stmt, syn_scope = ""Exact""",do not quote!!! +synonym(s): broad,AL oboInOwl:hasBroadSynonym@en SPLIT=|,optional manual,NA,NA,NA,NA,"glueV: .synonym_stmt, syn_scope = ""Broad""",NA +synonym(s): narrow,AL oboInOwl:hasNarrowSynonym@en SPLIT=|,optional manual,NA,NA,NA,NA,"glueV: .synonym_stmt, syn_scope = ""Narrow""",NA +synonym(s): related,AL oboInOwl:hasRelatedSynonym@en SPLIT=|,optional manual,NA,NA,NA,NA,"glueV: .synonym_stmt, syn_scope = ""Related""",NA +acronym(s): exact,AL oboInOwl:hasExactSynonym@en SPLIT=|,optional manual,CAMRQ,NA,NA,NA,"glueV: .acronym_stmt, acronym_scope = ""Exact""","must be accompanied by ""acronym annotation"" header/template in the adjacent column to the rigth in robot template" +acronym(s): broad,AL oboInOwl:hasBroadSynonym@en SPLIT=|,optional manual,NA,NA,NA,NA,"glueV: .acronym_stmt, acronym_scope = ""Broad""",NA +acronym(s): narrow,AL oboInOwl:hasNarrowSynonym@en SPLIT=|,optional manual,NA,NA,NA,NA,"glueV: .acronym_stmt, acronym_scope = ""Narrow""",NA +acronym(s): related,AL oboInOwl:hasRelatedSynonym@en SPLIT=|,optional manual,DES,NA,NA,NA,"glueV: .acronym_stmt, acronym_scope = ""Related""","must be accompanied by ""acronym annotation"" header/template in the adjacent column to the rigth in robot template" +acronym annotation,>AI oboInOwl:hasSynonymType,optional auto,acronym,NA,NA,NA,NA,NA +xref(s),A oboInOwl:hasDbXref SPLIT=|,optional manual,OMIM:618901,NA,NA,NA,?iri oboInOwl:hasDbXref ?xref .,NA skos mapping(s): exact,A skos:exactMatch SPLIT=|,optional manual,OMIM:618901,NA,NA,"should use IRIs and be as follows: AI skos:exactMatch SPLIT=| - - example input: https://omim.org/MIM:618901",adds skos mappings as strings; current INCORRECT DO format + - example input: https://omim.org/MIM:618901",?iri skos:exactMatch ?skos_exact .,adds skos mappings as strings; current INCORRECT DO format skos mapping(s): broad,A skos:broadMatch SPLIT=|,optional manual,OMIM:PS613135,NA,NA,"should use IRIs and be as follows: AI skos:exactMatch SPLIT=| - - example input: https://omim.org/MIM:618901",adds skos mappings as strings; current INCORRECT DO format + - example input: https://omim.org/MIM:618901",?iri skos:broadMatch ?skos_broad .,adds skos mappings as strings; current INCORRECT DO format skos mapping(s): narrow,A skos:narrowMatch SPLIT=|,optional manual,OMIM:618901,NA,NA,"should use IRIs and be as follows: AI skos:exactMatch SPLIT=| - - example input: https://omim.org/MIM:618901",adds skos mappings as strings; current INCORRECT DO format + - example input: https://omim.org/MIM:618901",?iri skos:narrowMatch ?skos_narrow .,adds skos mappings as strings; current INCORRECT DO format skos mapping(s): related,A skos:relatedMatch SPLIT=|,optional manual,NA,NA,NA,"should use IRIs and be as follows: AI skos:exactMatch SPLIT=| - - example input: https://omim.org/MIM:618901",adds skos mappings as strings; current INCORRECT DO format -equivalent class,EC %,optional manual,disease and ('has material basis in' some (Viruses or Bacteria or Eukaryota)),NA,NA,NA,NA -sc axiom: inheritance,SC 'has material basis in' some % SPLIT=|,optional manual,NA,NA,NA,NA,NA -sc axiom: anatomical location,SC 'disease has location' some %,optional manual,NA,NA,NA,NA,NA -sc axiom: onset,SC 'existence starts during' some %,optional manual,NA,NA,NA,NA,NA -sc axiom: has_material_basis_in,SC has_material_basis_in some %,optional manual,autosomal dominant inheritance,NA,NA,NA,do not quote!!! -sc axiom: located_in,SC located_in some %,optional manual,NA,rdfs:label (preferred); IRI or CURIE (possible),NA,NA,NA -disjoint class,DC %,optional manual,NA,NA,NA,NA,NA -subset(s),AI oboInOwl:inSubset SPLIT=|,optional manual,DO_AGR_slim,any subset (aka 'slim') defined in doid-edit.owl,NA,NA,NA -deprecate,AT owl:deprecated^^xsd:boolean,optional manual,true,NA,NA,NA,NA -alternate id(s),A oboInOwl:hasAlternativeId SPLIT=|,optional manual,DOID:4,CURIE of deprecated term,NA,NA,NA -term replaced by,AI obo:IAO_0100001,optional manual,DOID:4,IRI or CURIE of term to replace by,NA,NA,NA -comment,AL rdfs:comment@en,optional manual,This is a comment. There should only be one per term.,NA,NA,NA,NA -obo id,A oboInOwl:id,required auto,DOID:0080943,OBO CURIE,NA,NA,"required data, but not necessary to include in manual curation; will be inferred from iri/curie + - example input: https://omim.org/MIM:618901",?iri skos:relatedMatch ?skos_related .,adds skos mappings as strings; current INCORRECT DO format +equivalent class,EC %,optional manual,disease and ('has material basis in' some (Viruses or Bacteria or Eukaryota)),NA,NA,NA,NA,NA +sc axiom: inheritance,SC 'has material basis in' some % SPLIT=|,optional manual,NA,NA,NA,NA,NA,NA +sc axiom: anatomical location,SC 'disease has location' some %,optional manual,NA,NA,NA,NA,NA,NA +sc axiom: onset,SC 'existence starts during' some %,optional manual,NA,NA,NA,NA,NA,NA +sc axiom: has_material_basis_in,SC has_material_basis_in some %,optional manual,autosomal dominant inheritance,NA,NA,NA,NA,do not quote!!! +sc axiom: located_in,SC located_in some %,optional manual,NA,rdfs:label (preferred); IRI or CURIE (possible),NA,NA,NA,NA +disjoint class,DC %,optional manual,NA,NA,NA,NA,NA,NA +subset(s),AI oboInOwl:inSubset SPLIT=|,optional manual,DO_AGR_slim,any subset (aka 'slim') defined in doid-edit.owl,NA,NA,"?iri oboInOwl:inSubset ?subset_iri . +?subset_iri rdfs:label ?subset .",NA +deprecate,AT owl:deprecated^^xsd:boolean,optional manual,true,NA,NA,NA,?iri owl:deprecated ?deprecate .,NA +alternate id(s),A oboInOwl:hasAlternativeId SPLIT=|,optional manual,DOID:4,CURIE of deprecated term,NA,NA,?iri oboInOwl:hasAlternativeId ?alt_id .,NA +term replaced by,AI obo:IAO_0100001,optional manual,DOID:4,IRI or CURIE of term to replace by,NA,NA,?iri obo:IAO_0100001 ?term_replaced_by .,NA +comment,AL rdfs:comment@en,optional manual,This is a comment. There should only be one per term.,NA,NA,NA,?iri rdfs:comment ?comment .,NA +obo id,A oboInOwl:id,required auto,DOID:0080943,OBO CURIE,NA,NA,?iri oboInOwl:id ?id .,"required data, but not necessary to include in manual curation; will be inferred from iri/curie if manually entered it must match the CURIE form of iri/curie" -obo namespace,A oboInOwl:hasOBONamespace,required auto,disease_ontology,"OBO namespace of ontology: disease_ontology, symptoms, transmission_process",NA,NA,"required data, but not necessary to include in manual curation; will be automatically added for any new disease +obo namespace,A oboInOwl:hasOBONamespace,required auto,disease_ontology,"OBO namespace of ontology: disease_ontology, symptoms, transmission_process",NA,NA,?iri oboInOwl:hasOBONamespace ?obo_namespace .,"required data, but not necessary to include in manual curation; will be automatically added for any new disease if manually entered it must be ""disease_ontology"" (without quotes)" -español - label,AL rdfs:label@es,optional manual,NA,NA,NA,NA,NA -español - definition,AL obo:IAO_0000115@es,optional manual,NA,NA,NA,NA,NA -español - synonym(s): exact,AL oboInOwl:hasExactSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,NA -español - synonym(s): broad,AL oboInOwl:hasBroadSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,NA -español - synonym(s): narrow,AL oboInOwl:hasNarrowSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,NA -español - synonym(s): related,AL oboInOwl:hasRelatedSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,NA -español - acronym(s): exact,AL oboInOwl:hasExactSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,NA -español - acronym(s): broad,AL oboInOwl:hasBroadSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,NA -español - acronym(s): narrow,AL oboInOwl:hasNarrowSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,NA -español - acronym(s): related,AL oboInOwl:hasRelatedSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,NA +label - es,AL rdfs:label@es,optional manual,NA,NA,NA,NA,"label + glueV: .lang_stmt, object = ""?label"", lang = ""es""",NA +definition - es,AL obo:IAO_0000115@es,optional manual,NA,NA,NA,NA,"definition + glueV: .lang_stmt, object = ""?definition"", lang = ""es""",NA +synonym(s): exact - es,AL oboInOwl:hasExactSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,"1. glueV: .lang_stmt, object = ""?!<>!Synonym"", lang = ""es"" +2. +glueV: .synonym_stmt, syn_scope = ""Exact""",NA +synonym(s): broad - es,AL oboInOwl:hasBroadSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,"1. glueV: .lang_stmt, object = ""?!<>!Synonym"", lang = ""es"" +2. +glueV: .synonym_stmt, syn_scope = ""Broad""",NA +synonym(s): narrow - es,AL oboInOwl:hasNarrowSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,"1. glueV: .lang_stmt, object = ""?!<>!Synonym"", lang = ""es"" +2. +glueV: .synonym_stmt, syn_scope = ""Narrow""",NA +synonym(s): related - es,AL oboInOwl:hasRelatedSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,"1. glueV: .lang_stmt, object = ""?!<>!Synonym"", lang = ""es"" +2. +glueV: .synonym_stmt, syn_scope = ""Related""",NA +acronym(s): exact - es,AL oboInOwl:hasExactSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,NA,NA +acronym(s): broad - es,AL oboInOwl:hasBroadSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,NA,NA +acronym(s): narrow - es,AL oboInOwl:hasNarrowSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,NA,NA +acronym(s): related - es,AL oboInOwl:hasRelatedSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,NA,NA From 33eed32c94d44268ca57387f13609d59bd3c76c3 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Thu, 18 Dec 2025 17:42:19 -0500 Subject: [PATCH 06/33] Fix issues identified by lintr No effect on outputs --- R/googlesheets.R | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/R/googlesheets.R b/R/googlesheets.R index f97e7d59..ed72b168 100644 --- a/R/googlesheets.R +++ b/R/googlesheets.R @@ -30,11 +30,11 @@ NULL range_add_checkbox <- function(ss, range, msg = "Value must be TRUE or FALSE", quiet = TRUE) { rule <- googlesheets4:::new( - "DataValidationRule", - condition = googlesheets4:::new_BooleanCondition(type = "BOOLEAN"), - inputMessage = msg, - strict = TRUE, # same as in range_add_dropdown(), FALSE doesn't make sense - showCustomUi = TRUE # seems to be ignored + "DataValidationRule", + condition = googlesheets4:::new_BooleanCondition(type = "BOOLEAN"), + inputMessage = msg, + strict = TRUE, # same as in range_add_dropdown(), FALSE doesn't make sense + showCustomUi = TRUE # seems to be ignored ) if (quiet) { From 9144734949e67652a3a269090b2c881893a3845b Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Thu, 12 Dec 2024 17:12:40 -0500 Subject: [PATCH 07/33] Add write_gs() curation_template method --- NAMESPACE | 1 + R/write.R | 28 ++++++++++++++++++++++++++++ man/write_gs.Rd | 3 +++ 3 files changed, 32 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index d6c1c589..9a016e4d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -34,6 +34,7 @@ S3method(tidy_pub_records,scopus_search_list) S3method(to_character,data.frame) S3method(to_character,default) S3method(to_character,list) +S3method(write_gs,curation_template) S3method(write_gs,data.frame) S3method(write_gs,omim_inventory) export("%>%") diff --git a/R/write.R b/R/write.R index 926d1e20..97a78137 100644 --- a/R/write.R +++ b/R/write.R @@ -121,3 +121,31 @@ write_gs.data.frame <- function(data, ss, sheet = "data-%Y%m%d", invisible(gs_info) } + +#' @rdname write_gs +#' @export +write_gs.curation_template <- function(data, ss = NULL, + sheet = "curation-%Y%m%d", ...) { + data <- dplyr::mutate(data, links = format_hyperlink(data$links, as = "gs")) + + if (is.null(sheet)) { + sheet_nm <- "curation" + } else { + sheet_nm <- format(Sys.Date(), sheet) + } + + gs_info <- googlesheets4::write_sheet(data, ss, sheet) + + if (is.null(ss)) ss <- gs_info + + # add curation template data validation + gs_range <- spreadsheet_range(data, "annotation", sheet = sheet) + range_add_dropdown(ss, gs_range, values = .curation_opts$header) + + # freeze first two columns + googlesheets4::with_gs4_quiet( + googlesheets4:::sheet_freeze(ss, sheet = sheet, ncol = 2) + ) + + invisible(gs_info) +} diff --git a/man/write_gs.Rd b/man/write_gs.Rd index 1f309de1..91e6d237 100644 --- a/man/write_gs.Rd +++ b/man/write_gs.Rd @@ -4,6 +4,7 @@ \alias{write_gs} \alias{write_gs.omim_inventory} \alias{write_gs.data.frame} +\alias{write_gs.curation_template} \title{Write Data to a Google Sheet} \usage{ write_gs(data, ss, sheet = NULL, hyperlink_curie = NULL, ...) @@ -17,6 +18,8 @@ write_gs(data, ss, sheet = NULL, hyperlink_curie = NULL, ...) ) \method{write_gs}{data.frame}(data, ss, sheet = "data-\%Y\%m\%d", hyperlink_curie = NULL, ...) + +\method{write_gs}{curation_template}(data, ss = NULL, sheet = "curation-\%Y\%m\%d", ...) } \arguments{ \item{data}{A data.frame, possibly with a defined method.} From a58bcf90890a2066f78295fdb6de061d74ba0db1 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Thu, 26 Jun 2025 10:08:15 -0400 Subject: [PATCH 08/33] Switch curation_template() to s3 generic .data argument moved to first for improved use in function pipelines. Includes only NULL method currently, which creates a blank curation template with a specified number of rows. --- NAMESPACE | 1 + R/curation.R | 61 ++++++++++++++++++++++------------------ man/curation_template.Rd | 13 +++++---- 3 files changed, 43 insertions(+), 32 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 9a016e4d..cc0a9b68 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -6,6 +6,7 @@ S3method(as_tibble,esummary_list) S3method(as_tibble,esummary_list_nested) S3method(as_tibble,scopus_search) S3method(as_tibble,scopus_search_list) +S3method(curation_template,"NULL") S3method(drop_blank,character) S3method(drop_blank,list) S3method(elucidate,omim_inventory) diff --git a/R/curation.R b/R/curation.R index 3b14ad6e..0fda2cc1 100644 --- a/R/curation.R +++ b/R/curation.R @@ -1,49 +1,38 @@ - #' Create a Curation Template #' #' Create a curation template in a Google Sheet, optionally including data. #' #' @inheritParams googlesheets4::range_write +#' @param .data Data to add to the curation sheet. If `NULL` (default), an empty +#' curation sheet will be created. #' @param sheet (OPTIONAL) The sheet name, as a string. If `NULL` (default), the #' sheet name will default to "curation-" with today's date appended (formatted #' as "%Y%m%d"; see [format.Date()]). -#' @param .data Data to add to the curation sheet. If `NULL` (default), an empty -#' curation sheet will be created. -#' @param nrow The number of rows to create in the curation template when -#' `data = NULL` (default: `50`). #' #' @returns The Google Sheet info (`ss`), as a [googlesheets4::sheets_id]. #' #' @export -curation_template <- function(ss = NULL, sheet = NULL, .data = NULL, - nrow = 50) { - cur_cols <- c( - "iri/curie", "annotation", "value", "remove", "curation_notes", "links", - "action_notes", "status" - ) +curation_template <- function(.data = NULL, ss = NULL, sheet = NULL, ...) { + UseMethod("curation_template", .data) +} - if (is.null(.data)) { # create empty curation sheet - val <- rep(NA, nrow) +#' @param nrow The number of rows to create in the curation template when +#' `.data = NULL` (default: `50`). +#' +#' @export +#' @rdname curation_template +curation_template.NULL <- function(ss = NULL, sheet = NULL, ..., nrow = 50) { + val <- rep(NA, nrow) - # inspired by https://stackoverflow.com/a/60495352/6938922 - cur_df <- tibble::as_tibble(rlang::rep_named(cur_cols, list(val))) - } else { - cur_df <- .data # NEED TO ADD REFORMATTING HERE!!! - } + # inspired by https://stackoverflow.com/a/60495352/6938922 + cur_df <- tibble::as_tibble(rlang::rep_named(curation_cols, list(val))) + class(cur_df) <- c("curation_template", class(cur_df)) if (is.null(sheet)) sheet <- paste0("curation-", format(Sys.Date(), "%Y%m%d")) gs_info <- googlesheets4::write_sheet(cur_df, ss, sheet) if (is.null(ss)) ss <- gs_info - - # add curation template data validation - gs_range <- spreadsheet_range(cur_df, "annotation", sheet = sheet) - range_add_dropdown(ss, gs_range, values = .curation_opts$header) - - # freeze first two columns - googlesheets4::with_gs4_quiet( - googlesheets4:::sheet_freeze(ss, sheet = sheet, ncol = 2) - ) + set_curation_validation(cur_df, ss, sheet) invisible(gs_info) } @@ -51,6 +40,24 @@ curation_template <- function(ss = NULL, sheet = NULL, .data = NULL, # helpers -------------------------------------------------------------------- +# define expected columns for curation template (in order) +curation_cols <- c( + "id", "annotation", "value", "remove", "curation_notes", "links", + "action_notes", "status" +) + +#' Set Data Validation for Curation Templates +set_curation_validation <- function(cur_df, ss, sheet) { + # add curation template data validation + gs_range <- spreadsheet_range(cur_df, "annotation", sheet = sheet) + range_add_dropdown(ss, gs_range, values = .curation_opts$header) + + # freeze first two columns + googlesheets4::with_gs4_quiet( + googlesheets4:::sheet_freeze(ss, sheet = sheet, ncol = 2) + ) +} + #' Calculate a Spreadsheet Range #' #' Calculate a range for a spreadsheet program (Google Sheets or Excel). diff --git a/man/curation_template.Rd b/man/curation_template.Rd index c3f10cdf..7d5a46ac 100644 --- a/man/curation_template.Rd +++ b/man/curation_template.Rd @@ -2,11 +2,17 @@ % Please edit documentation in R/curation.R \name{curation_template} \alias{curation_template} +\alias{curation_template.NULL} \title{Create a Curation Template} \usage{ -curation_template(ss = NULL, sheet = NULL, .data = NULL, nrow = 50) +curation_template(.data = NULL, ss = NULL, sheet = NULL, ...) + +\method{curation_template}{`NULL`}(ss = NULL, sheet = NULL, ..., nrow = 50) } \arguments{ +\item{.data}{Data to add to the curation sheet. If \code{NULL} (default), an empty +curation sheet will be created.} + \item{ss}{Something that identifies a Google Sheet: \itemize{ \item its file id as a string or \code{\link[googledrive:drive_id]{drive_id}} @@ -23,11 +29,8 @@ Processed through \code{\link[googlesheets4:as_sheets_id]{as_sheets_id()}}.} sheet name will default to "curation-" with today's date appended (formatted as "\%Y\%m\%d"; see \code{\link[=format.Date]{format.Date()}}).} -\item{.data}{Data to add to the curation sheet. If \code{NULL} (default), an empty -curation sheet will be created.} - \item{nrow}{The number of rows to create in the curation template when -\code{data = NULL} (default: \code{50}).} +\code{.data = NULL} (default: \code{50}).} } \value{ The Google Sheet info (\code{ss}), as a \link[googlesheets4:sheets_id]{googlesheets4::sheets_id}. From 2904b2972f70d1c38636ed809bb487a3c6c00463 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Fri, 14 Nov 2025 11:43:26 -0500 Subject: [PATCH 09/33] Add extract_obo_anon() To extract all anonymous relations from an OBO Foundry ontology in Manchester format. --- NAMESPACE | 1 + R/extract.R | 104 ++++++++++++++++++++++++++++++++++++++++ man/extract_obo_anon.Rd | 40 ++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 man/extract_obo_anon.Rd diff --git a/NAMESPACE b/NAMESPACE index cc0a9b68..ff5237cf 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -68,6 +68,7 @@ export(elucidate) export(extract_as_tidygraph) export(extract_class_axiom) export(extract_eq_axiom) +export(extract_obo_anon) export(extract_obo_mappings) export(extract_ordo_mappings) export(extract_pm_date) diff --git a/R/extract.R b/R/extract.R index 05464696..1098e0a2 100644 --- a/R/extract.R +++ b/R/extract.R @@ -611,3 +611,107 @@ extract_obo_mappings <- function(onto_path, id = NULL, version_as = "release", out } + + +#' Extract Anonymous Relationships from OBO Foundry Ontology +#' +#' Extracts all anonymous relations (logical/complex) from any OBO Foundry +#' Ontology in Manchester format, including equivalent classes, subclasses, and +#' disjoint classes. `extract_obo_anon()` is designed to supplement SPARQL +#' queries that generally cannot return anonymous relationships. +#' +#' @param obo_ont The path to an ontology file, as a string. +#' @param prefix A character vector of OBO prefixes (aka ID spaces) to filter +#' results to, or `NULL` (default) to return all axioms. _Ignored if `id` is_ +#' _provided._ +#' @param id A character vector of OBO IDs (CURIEs) to filter results to or +#' `NULL` (default) to return all entities with logical relations. +#' @param render The format for rendering classes & properties, as a string. +#' One of: +#' * `"label"` (default): Use labels, quoting as needed. +#' * `"id"`: Use OBO IDs (CURIEs). +#' +#' @returns A tibble with the columns: `id`, `data_type`, and `value`, where `value` +#' is the axiom in Manchester syntax rendered according to `format`. +#' +#' @section NOTES: +#' Uses [ROBOT export](https://robot.obolibrary.org/export) internally. +#' +#' @export +extract_obo_anon <- function(obo_ont, prefix = NULL, id = NULL, + render = "label") { + render <- match.arg(render, c("label", "id")) + + temp <- tempfile(fileext = ".tsv") + robot( + "export", + i = obo_ont, + header = '"ID|LABEL|Equivalent Class [ANON ID]|SubClass Of [ANON ID]|Disjoint With [ANON ID]"', + include = '"classes properties"', + export = temp + ) + .df <- readr::read_tsv( + temp, + col_names = c("id", "label", "eq class anon", "subclass anon", "disjoint class anon"), + skip = 1, + show_col_types = FALSE + ) + + if (!is.null(id)) { + out <- dplyr::filter(.df, .data$id %in% id) + } else if (!is.null(prefix)) { + pattern <- paste0(prefix, collapse = "|") + out <- dplyr::filter( + .df, + stringr::str_detect(.data$id, pattern) + ) + } else { + out <- .df + } + + out <- tidyr::pivot_longer( + out, + cols = c("eq class anon", "subclass anon", "disjoint class anon"), + names_to = "data_type", + values_to = "value", + values_drop_na = TRUE + ) + + if (render == "label") { + # subset to IDs actually in axioms + id_keep <- stringr::str_extract_all( + out$value, + "[A-Za-z0-9_]+:[A-Za-z0-9_#]+" + ) |> + unlist() |> + unique() + label_df <- .df |> + dplyr::select("id", "label") |> + dplyr::filter(.data$id %in% id_keep & !is.na(.data$label)) |> + dplyr::mutate( + label = dplyr::case_when( + stringr::str_detect(.data$label, "'") ~ sandwich_text(.data$label, '"'), + stringr::str_detect(.data$label, "[^[:alnum:]]") ~ sandwich_text(.data$label, "'"), + TRUE ~ .data$label + ) + ) |> + dplyr::arrange(-stringr::str_length(.data$id), .data$id) + label_replace <- purrr::set_names( + label_df$label, + label_df$id + ) + out <- dplyr::mutate( + out, + value = stringr::str_replace_all( + .data$value, + "[A-Za-z0-9_]+:[A-Za-z0-9_#]+", + # direct selection via replace fxn ~200x faster than + # label_replace used directly; returns input if ID not found + function(x) dplyr::coalesce(label_replace[x], x) + ) + ) + } + + ### FUTURE WORK -- identify subclass anon subtypes? ### + lengthen_col(out, value) +} diff --git a/man/extract_obo_anon.Rd b/man/extract_obo_anon.Rd new file mode 100644 index 00000000..4492af8c --- /dev/null +++ b/man/extract_obo_anon.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/extract.R +\name{extract_obo_anon} +\alias{extract_obo_anon} +\title{Extract Anonymous Relationships from OBO Foundry Ontology} +\usage{ +extract_obo_anon(obo_ont, prefix = NULL, id = NULL, render = "label") +} +\arguments{ +\item{obo_ont}{The path to an ontology file, as a string.} + +\item{prefix}{A character vector of OBO prefixes (aka ID spaces) to filter +results to, or \code{NULL} (default) to return all axioms. \emph{Ignored if \code{id} is} +\emph{provided.}} + +\item{id}{A character vector of OBO IDs (CURIEs) to filter results to or +\code{NULL} (default) to return all entities with logical relations.} + +\item{render}{The format for rendering classes & properties, as a string. +One of: +\itemize{ +\item \code{"label"} (default): Use labels, quoting as needed. +\item \code{"id"}: Use OBO IDs (CURIEs). +}} +} +\value{ +A tibble with the columns: \code{id}, \code{data_type}, and \code{value}, where \code{value} +is the axiom in Manchester syntax rendered according to \code{format}. +} +\description{ +Extracts all anonymous relations (logical/complex) from any OBO Foundry +Ontology in Manchester format, including equivalent classes, subclasses, and +disjoint classes. \code{extract_obo_anon()} is designed to supplement SPARQL +queries that generally cannot return anonymous relationships. +} +\section{NOTES}{ + +Uses \href{https://robot.obolibrary.org/export}{ROBOT export} internally. +} + From 2ef7aa410e6f3e301d9ab482287004211811a003 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Mon, 24 Nov 2025 13:14:50 -0500 Subject: [PATCH 10/33] Add obo-data.rq --- inst/sparql/obo-data.rq | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 inst/sparql/obo-data.rq diff --git a/inst/sparql/obo-data.rq b/inst/sparql/obo-data.rq new file mode 100644 index 00000000..af177a41 --- /dev/null +++ b/inst/sparql/obo-data.rq @@ -0,0 +1,29 @@ +# returns all class annotations, with any axiom annotations, and axioms to +# named classes (no anonymous classes); with optional VALUES or FILTER clauses +PREFIX rdf: +PREFIX owl: +PREFIX obo: +PREFIX oboInOwl: + +SELECT ?id ?predicate ?value ?axiom_predicate ?axiom_value +WHERE { + #@values# + ?id a owl:Class ; + ?predicate ?value . + FILTER(!isBlank(?id) && !isBlank(?value)) + FILTER (?predicate NOT IN (oboInOwl:id, rdf:type)) + #@filter# + + OPTIONAL { + ?axiom a owl:Axiom ; + owl:annotatedSource ?id ; + owl:annotatedProperty ?predicate ; + owl:annotatedTarget ?value ; + ?axiom_predicate ?axiom_value . + FILTER ( + ?axiom_predicate NOT IN ( + rdf:type, owl:annotatedSource, owl:annotatedProperty, owl:annotatedTarget + ) + ) + } +} From 79ef260ba1cd5392af3aa59e52203f61ff5e6a69 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Mon, 24 Nov 2025 13:17:32 -0500 Subject: [PATCH 11/33] Update extract_obo_anon() --- R/extract.R | 16 +++++++++------- man/extract_obo_anon.Rd | 14 +++++++++++++- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/R/extract.R b/R/extract.R index 1098e0a2..7c822a8c 100644 --- a/R/extract.R +++ b/R/extract.R @@ -630,6 +630,7 @@ extract_obo_mappings <- function(onto_path, id = NULL, version_as = "release", #' One of: #' * `"label"` (default): Use labels, quoting as needed. #' * `"id"`: Use OBO IDs (CURIEs). +#' @inheritParams robot #' #' @returns A tibble with the columns: `id`, `data_type`, and `value`, where `value` #' is the axiom in Manchester syntax rendered according to `format`. @@ -639,7 +640,7 @@ extract_obo_mappings <- function(onto_path, id = NULL, version_as = "release", #' #' @export extract_obo_anon <- function(obo_ont, prefix = NULL, id = NULL, - render = "label") { + render = "label", .robot_path = NULL) { render <- match.arg(render, c("label", "id")) temp <- tempfile(fileext = ".tsv") @@ -648,19 +649,20 @@ extract_obo_anon <- function(obo_ont, prefix = NULL, id = NULL, i = obo_ont, header = '"ID|LABEL|Equivalent Class [ANON ID]|SubClass Of [ANON ID]|Disjoint With [ANON ID]"', include = '"classes properties"', - export = temp + export = temp, + .robot_path = .robot_path ) .df <- readr::read_tsv( temp, - col_names = c("id", "label", "eq class anon", "subclass anon", "disjoint class anon"), + col_names = c("id", "label", "owl:equivalentClass", "rdfs:subClassOf", "owl:disjointWith"), skip = 1, show_col_types = FALSE ) if (!is.null(id)) { - out <- dplyr::filter(.df, .data$id %in% id) + out <- dplyr::filter(.df, .data$id %in% .env$id) } else if (!is.null(prefix)) { - pattern <- paste0(prefix, collapse = "|") + pattern <- sandwich_text(paste0(prefix, collapse = "|"), c("^(", ")")) out <- dplyr::filter( .df, stringr::str_detect(.data$id, pattern) @@ -671,8 +673,8 @@ extract_obo_anon <- function(obo_ont, prefix = NULL, id = NULL, out <- tidyr::pivot_longer( out, - cols = c("eq class anon", "subclass anon", "disjoint class anon"), - names_to = "data_type", + cols = c("owl:equivalentClass", "rdfs:subClassOf", "owl:disjointWith"), + names_to = "predicate", values_to = "value", values_drop_na = TRUE ) diff --git a/man/extract_obo_anon.Rd b/man/extract_obo_anon.Rd index 4492af8c..ef3ace2a 100644 --- a/man/extract_obo_anon.Rd +++ b/man/extract_obo_anon.Rd @@ -4,7 +4,13 @@ \alias{extract_obo_anon} \title{Extract Anonymous Relationships from OBO Foundry Ontology} \usage{ -extract_obo_anon(obo_ont, prefix = NULL, id = NULL, render = "label") +extract_obo_anon( + obo_ont, + prefix = NULL, + id = NULL, + render = "label", + .robot_path = NULL +) } \arguments{ \item{obo_ont}{The path to an ontology file, as a string.} @@ -22,6 +28,12 @@ One of: \item \code{"label"} (default): Use labels, quoting as needed. \item \code{"id"}: Use OBO IDs (CURIEs). }} + +\item{.robot_path}{The path to a ROBOT executable or .jar file, as a string. +When \code{NULL} (default), if a system ROBOT executable is available it will +be used, otherwise an error will be signaled. + +\strong{NOTE:} \code{DO.utils} caches the last ROBOT used for future use.} } \value{ A tibble with the columns: \code{id}, \code{data_type}, and \code{value}, where \code{value} From 668021dd5d2abfb7d9e5406ac64cc99ac3342a68 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Mon, 24 Nov 2025 13:29:34 -0500 Subject: [PATCH 12/33] Add extract_obo_data() --- NAMESPACE | 1 + R/extract.R | 86 +++++++++++++++++++++++++++++++++++++++++ man/extract_obo_data.Rd | 59 ++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 man/extract_obo_data.Rd diff --git a/NAMESPACE b/NAMESPACE index ff5237cf..3414a2f2 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -69,6 +69,7 @@ export(extract_as_tidygraph) export(extract_class_axiom) export(extract_eq_axiom) export(extract_obo_anon) +export(extract_obo_data) export(extract_obo_mappings) export(extract_ordo_mappings) export(extract_pm_date) diff --git a/R/extract.R b/R/extract.R index 7c822a8c..d411c8d7 100644 --- a/R/extract.R +++ b/R/extract.R @@ -717,3 +717,89 @@ extract_obo_anon <- function(obo_ont, prefix = NULL, id = NULL, ### FUTURE WORK -- identify subclass anon subtypes? ### lengthen_col(out, value) } + +#' Extract OBO Foundry Ontology Data +#' +#' Extracts data from an OBO Foundry ontology. +#' +#' @param obo_ont The path to an ontology file, as a string. +#' @param prefix A character vector of OBO prefixes (aka ID spaces) to filter +#' results to, or `NULL` (default) to return all axioms. _Ignored if `id` is_ +#' _provided._ +#' @param id A character vector of OBO IDs (CURIEs) to filter results to or +#' `NULL` (default) to return all entities with logical relations. +#' @param include_anon Whether to include anonymous relationships +#' (logical/complex) in the output, as a boolean (default: `TRUE`). See +#' [extract_obo_anon()]. +#' @inheritDotParams extract_obo_anon +#' @inheritParams robot +#' +#' @returns A tibble of class `obo_data` with the columns: `id`, `predicate`, +#' `value`, `axiom predicate`, and `axiom value`. An additional class is added +#' to indicate the file the data came from (without extension or directories). +#' +#' @section NOTES: +#' Uses [ROBOT query](https://robot.obolibrary.org/query) internally. +#' +#' @export +extract_obo_data <- function(obo_ont, prefix = NULL, id = NULL, + include_anon = TRUE, ..., .robot_path = NULL) { + query <- system.file( + "sparql", + "obo-data.rq", + package = "DO.utils", + mustWork = TRUE + ) |> + readr::read_file() + + if (!is.null(id)) { + iri <- to_uri(id) |> + sandwich_text(c("<", ">")) + values_stmt <- paste0(iri, collapse = " ") |> + sandwich_text(c("VALUES ?id { ", " }")) + query <- glue::glue( + query, + values = values_stmt, + filter = "", + .open = "#@", + .close = "#" + ) + } else if (!is.null(prefix)) { + filter_stmt <- paste0(prefix, collapse = "|") |> + sandwich_text(c('FILTER(CONTAINS(str(?id), "', '_")')) + query <- glue::glue( + query, + values = "", + filter = filter_stmt, + .open = "#@", + .close = "#" + ) + } + + qres <- robot_query( + input = obo_ont, + query = query, + tidy_what = c("header", "uri_to_curie"), + col_types = readr::cols(.default = readr::col_character()), + .robot_path = .robot_path + ) + + if (!include_anon) { + out <- qres + } else { + anon <- extract_obo_anon( + obo_ont, + prefix = prefix, + id = id, + ..., + .robot_path = .robot_path + ) |> + dplyr::select("id", "predicate", "value") + + out <- dplyr::bind_rows(qres, anon) + } + + ont_src <- tools::file_path_sans_ext(basename(obo_ont)) + class(out) <- c(ont_src, "obo_data", class(out)) + out +} diff --git a/man/extract_obo_data.Rd b/man/extract_obo_data.Rd new file mode 100644 index 00000000..1b0fba19 --- /dev/null +++ b/man/extract_obo_data.Rd @@ -0,0 +1,59 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/extract.R +\name{extract_obo_data} +\alias{extract_obo_data} +\title{Extract OBO Foundry Ontology Data} +\usage{ +extract_obo_data( + obo_ont, + prefix = NULL, + id = NULL, + include_anon = TRUE, + ..., + .robot_path = NULL +) +} +\arguments{ +\item{obo_ont}{The path to an ontology file, as a string.} + +\item{prefix}{A character vector of OBO prefixes (aka ID spaces) to filter +results to, or \code{NULL} (default) to return all axioms. \emph{Ignored if \code{id} is} +\emph{provided.}} + +\item{id}{A character vector of OBO IDs (CURIEs) to filter results to or +\code{NULL} (default) to return all entities with logical relations.} + +\item{include_anon}{Whether to include anonymous relationships +(logical/complex) in the output, as a boolean (default: \code{TRUE}). See +\code{\link[=extract_obo_anon]{extract_obo_anon()}}.} + +\item{...}{ + Arguments passed on to \code{\link[=extract_obo_anon]{extract_obo_anon}} + \describe{ + \item{\code{render}}{The format for rendering classes & properties, as a string. +One of: +\itemize{ +\item \code{"label"} (default): Use labels, quoting as needed. +\item \code{"id"}: Use OBO IDs (CURIEs). +}} + }} + +\item{.robot_path}{The path to a ROBOT executable or .jar file, as a string. +When \code{NULL} (default), if a system ROBOT executable is available it will +be used, otherwise an error will be signaled. + +\strong{NOTE:} \code{DO.utils} caches the last ROBOT used for future use.} +} +\value{ +A tibble of class \code{obo_data} with the columns: \code{id}, \code{predicate}, +\code{value}, \verb{axiom predicate}, and \verb{axiom value}. An additional class is added +to indicate the file the data came from (without extension or directories). +} +\description{ +Extracts data from an OBO Foundry ontology. +} +\section{NOTES}{ + +Uses \href{https://robot.obolibrary.org/query}{ROBOT query} internally. +} + From 92359eb4edfbceb5ab025dbc4d2d1661c453d781 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Thu, 18 Dec 2025 17:43:37 -0500 Subject: [PATCH 13/33] Fix spreadsheet_range() code error --- R/curation.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/curation.R b/R/curation.R index 0fda2cc1..c0f21158 100644 --- a/R/curation.R +++ b/R/curation.R @@ -75,7 +75,7 @@ spreadsheet_range <- function(.data, .col, sheet = NULL, rows = NULL, n_header = 1) { col_letter <- LETTERS[which(names(.data) == .col)] if (length(col_letter) != 1) { - rlange::abort("Exactly one column must be specified in `.col`") + rlang::abort("Exactly one column must be specified in `.col`") } if (is.null(rows)) { From e4d8c36afc6586a0ff79b8efc1ac9043d3d0c8f0 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Thu, 18 Dec 2025 17:44:23 -0500 Subject: [PATCH 14/33] Update obo-data.rq to return value labels as 'extra' To make values which are IRIs human understandable with less work --- inst/sparql/obo-data.rq | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/inst/sparql/obo-data.rq b/inst/sparql/obo-data.rq index af177a41..b56f09e8 100644 --- a/inst/sparql/obo-data.rq +++ b/inst/sparql/obo-data.rq @@ -1,11 +1,12 @@ # returns all class annotations, with any axiom annotations, and axioms to # named classes (no anonymous classes); with optional VALUES or FILTER clauses PREFIX rdf: +PREFIX rdfs: PREFIX owl: PREFIX obo: PREFIX oboInOwl: -SELECT ?id ?predicate ?value ?axiom_predicate ?axiom_value +SELECT ?id ?predicate ?value ?axiom_predicate ?axiom_value ?extra WHERE { #@values# ?id a owl:Class ; @@ -26,4 +27,8 @@ WHERE { ) ) } + + OPTIONAL { + ?value rdfs:label ?extra . + } } From f141cb9f5a8884e957e3143b07c5bab2cc23a7a8 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Thu, 18 Dec 2025 17:48:03 -0500 Subject: [PATCH 15/33] Update curation template schema - Rename 'annotation' to 'data_type' to reflect that not all data are annotations - Drop 'remove' column and use dropdown-validated 'status' instead for more flexible & powerful data handling - 'status' validation options are all documented in curation_status and include'retain', 'add', 'remove', 'exclude', 'ignore', 'restore' - Add new 'sparql_dt_motif' column to schema and ingest as internal data to support full conversion of obo-data.rq output to 'data_type's --- R/curation.R | 38 +++++++-- data-raw/curation_opts.csv | 130 ++++++++++++++++-------------- data-raw/internal-curation_opts.R | 19 ++++- 3 files changed, 118 insertions(+), 69 deletions(-) diff --git a/R/curation.R b/R/curation.R index c0f21158..b6d10986 100644 --- a/R/curation.R +++ b/R/curation.R @@ -42,15 +42,43 @@ curation_template.NULL <- function(ss = NULL, sheet = NULL, ..., nrow = 50) { # define expected columns for curation template (in order) curation_cols <- c( - "id", "annotation", "value", "remove", "curation_notes", "links", - "action_notes", "status" + "id", "data_type", "value", "status", "curation_notes", "links", + "status_notes" ) +#' Curation 'Status' +#' +#' Values used to establish `status` data validation in Google Sheets +#' [curation templates][curation_template()]. +#' +#' * `retain`: data already in ontology that should be kept; this is the default +#' `status` for existing data when creating a [curation_template()] +#' +#' * `add`: new data that should be added +#' +#' * `remove`: existing ontology data that should be removed +#' +#' * `exclude`: data relevant to the ontology that should be actively excluded +#' (e.g. an incorrect mapping) -- details should be included in `status_notes` +#' +#' * `ignore`: data not for active inclusion or exclusion that should be ignored +#' (e.g. dubious synonyms, incomplete curation data) +#' +#' * `restore`: data that was removed from the ontology and should be added back +#' +#' @keywords internal +curation_status <- c("retain", "add", "remove", "exclude", "ignore", "restore") + + #' Set Data Validation for Curation Templates set_curation_validation <- function(cur_df, ss, sheet) { - # add curation template data validation - gs_range <- spreadsheet_range(cur_df, "annotation", sheet = sheet) - range_add_dropdown(ss, gs_range, values = .curation_opts$header) + # add data_type validation + dt_range <- spreadsheet_range(cur_df, "data_type", sheet = sheet) + range_add_dropdown(ss, dt_range, values = .curation_opts$data_type) + + # add status validation + status_range <- spreadsheet_range(cur_df, "status", sheet = sheet) + range_add_dropdown(ss, status_range, values = curation_status) # freeze first two columns googlesheets4::with_gs4_quiet( diff --git a/data-raw/curation_opts.csv b/data-raw/curation_opts.csv index 5f6bf889..5e7a457a 100644 --- a/data-raw/curation_opts.csv +++ b/data-raw/curation_opts.csv @@ -1,66 +1,76 @@ -header,template,type,example,optional_values,alternate_title,alternate_format,sparql,notes -id,ID,required manual,DOID:0080943,IRI or CURIE,NA,NA,?iri a owl:Class .,replaced older 'iri/curie' header for simplicity -iri/curie,ID,required manual,DOID:0080943,IRI or CURIE,NA,NA,?iri a owl:Class .,deprecated -label,AL rdfs:label@en,required manual,"46,XX sex reversal 5",NA,NA,NA,?iri rdfs:label ?label .,NA -parent iri/curie,SC % SPLIT=|,required manual,DOID:0111760,disease by infectious agent,CI,IRI or CURIE; CI means Class IRI --> type will be CLASS_TYPE,"?iri rdfs:subClassOf ?parent . -FILTER(!isBlank(?parent))",NA -definition,AL obo:IAO_0000115@en,required manual,"A 46,XX sex reversal that is characterized by genital virilization in 46,XX individuals, associated with congenital heart disease and variable somatic anomalies including blepharophimosis-ptosis-epicanthus inversus syndrome and congenital diaphragmatic hernia and that has_material_basis_in heterozygous mutation in the NR2F2 gene on chromosome 15q26.",NA,NA,NA,?iri obo:IAO_0000115 ?definition .,NA -definition source(s),>A oboInOwl:hasDbXref SPLIT=|,required manual,url:https://pubmed.ncbi.nlm.nih.gov/29478779/,NA,NA,NA,"!<<.definition_axiom>>! - oboInOwl:hasDbXref ?def_src .",NA -definition source type(s),>AI dc11:type SPLIT=|,optional manual,curator inference from journal publication,"ECO codes, e.g. ECO:0007645",NA,NA,"!<<.definition_axiom>>! - dc:type ?src_type .",do not quote!!! -synonym(s): exact,AL oboInOwl:hasExactSynonym@en SPLIT=|,optional manual,hemangiosarcoma,NA,NA,NA,"glueV: .synonym_stmt, syn_scope = ""Exact""",do not quote!!! -synonym(s): broad,AL oboInOwl:hasBroadSynonym@en SPLIT=|,optional manual,NA,NA,NA,NA,"glueV: .synonym_stmt, syn_scope = ""Broad""",NA -synonym(s): narrow,AL oboInOwl:hasNarrowSynonym@en SPLIT=|,optional manual,NA,NA,NA,NA,"glueV: .synonym_stmt, syn_scope = ""Narrow""",NA -synonym(s): related,AL oboInOwl:hasRelatedSynonym@en SPLIT=|,optional manual,NA,NA,NA,NA,"glueV: .synonym_stmt, syn_scope = ""Related""",NA -acronym(s): exact,AL oboInOwl:hasExactSynonym@en SPLIT=|,optional manual,CAMRQ,NA,NA,NA,"glueV: .acronym_stmt, acronym_scope = ""Exact""","must be accompanied by ""acronym annotation"" header/template in the adjacent column to the rigth in robot template" -acronym(s): broad,AL oboInOwl:hasBroadSynonym@en SPLIT=|,optional manual,NA,NA,NA,NA,"glueV: .acronym_stmt, acronym_scope = ""Broad""",NA -acronym(s): narrow,AL oboInOwl:hasNarrowSynonym@en SPLIT=|,optional manual,NA,NA,NA,NA,"glueV: .acronym_stmt, acronym_scope = ""Narrow""",NA -acronym(s): related,AL oboInOwl:hasRelatedSynonym@en SPLIT=|,optional manual,DES,NA,NA,NA,"glueV: .acronym_stmt, acronym_scope = ""Related""","must be accompanied by ""acronym annotation"" header/template in the adjacent column to the rigth in robot template" -acronym annotation,>AI oboInOwl:hasSynonymType,optional auto,acronym,NA,NA,NA,NA,NA -xref(s),A oboInOwl:hasDbXref SPLIT=|,optional manual,OMIM:618901,NA,NA,NA,?iri oboInOwl:hasDbXref ?xref .,NA -skos mapping(s): exact,A skos:exactMatch SPLIT=|,optional manual,OMIM:618901,NA,NA,"should use IRIs and be as follows: +data_type,template,inclusion,example,deprecated,notes,optional_values,alternate_title,alternate_format,sparql,export_header,sparql_dt_motif +id,ID,required manual,DOID:0080943,FALSE,replaced older 'iri/curie' header for simplicity; id now covered by obo id field,IRI or CURIE,CI,IRI or CURIE; CI means Class IRI --> type will be CLASS_TYPE,?iri a owl:Class .,ID,NA +label,A rdfs:label,required manual,"46,XX sex reversal 5",FALSE,NA,NA,NA,NA,?iri rdfs:label ?label .,LABEL,rdfs:label +parent id,SC % SPLIT=|,required manual,DOID:0111760,FALSE,"accepts CURIE or IRI; intended for only asserted subclass relationships between named classes +--> separated from subclass anon for practical purposes - ROBOT template is the same, ROBOT export differs",disease by infectious agent,NA,NA,"?iri rdfs:subClassOf ?parent . +FILTER(!isBlank(?parent))",SubClass Of [NAMED ID],rdfs:subClassOf +definition,A IAO:0000115,required manual,"A 46,XX sex reversal that is characterized by genital virilization in 46,XX individuals, associated with congenital heart disease and variable somatic anomalies including blepharophimosis-ptosis-epicanthus inversus syndrome and congenital diaphragmatic hernia and that has_material_basis_in heterozygous mutation in the NR2F2 gene on chromosome 15q26.",FALSE,NA,NA,NA,NA,?iri obo:IAO_0000115 ?definition .,obo:IAO_0000115,IAO:0000115 +definition source(s),>A oboInOwl:hasDbXref SPLIT=|,required manual,url:https://pubmed.ncbi.nlm.nih.gov/29478779/,FALSE,NA,NA,NA,NA,"!<<.definition_axiom>>! + oboInOwl:hasDbXref ?def_src .",NA,IAO:0000115-oboInOwl:hasDbXref +definition source type(s),>AI dc11:type SPLIT=|,optional manual,curator inference from journal publication,FALSE,do not quote!!!,"ECO codes, e.g. ECO:0007645",NA,NA,"!<<.definition_axiom>>! + dc:type ?src_type .",NA,IAO:0000115-dc:type +synonym(s): exact,A oboInOwl:hasExactSynonym SPLIT=|,optional manual,hemangiosarcoma,FALSE,do not quote!!!,NA,NA,NA,"glueV: .synonym_stmt, syn_scope = ""Exact""",oboInOwl:hasExactSynonym,oboInOwl:hasExactSynonym +synonym(s): broad,A oboInOwl:hasBroadSynonym SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,"glueV: .synonym_stmt, syn_scope = ""Broad""",oboInOwl:hasBroadSynonym,oboInOwl:hasBroadSynonym +synonym(s): narrow,A oboInOwl:hasNarrowSynonym SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,"glueV: .synonym_stmt, syn_scope = ""Narrow""",oboInOwl:hasNarrowSynonym,oboInOwl:hasNarrowSynonym +synonym(s): related,A oboInOwl:hasRelatedSynonym SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,"glueV: .synonym_stmt, syn_scope = ""Related""",oboInOwl:hasRelatedSynonym,oboInOwl:hasRelatedSynonym +acronym(s): exact,A oboInOwl:hasExactSynonym SPLIT=|,optional manual,CAMRQ,FALSE,"must be accompanied by ""acronym annotation"" header/template in the adjacent column to the rigth in robot template",NA,NA,NA,"glueV: .acronym_stmt, acronym_scope = ""Exact""",NA,oboInOwl:hasExactSynonym-OMO:0003012 +acronym(s): broad,A oboInOwl:hasBroadSynonym SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,"glueV: .acronym_stmt, acronym_scope = ""Broad""",NA,oboInOwl:hasBroadSynonym-OMO:0003012 +acronym(s): narrow,A oboInOwl:hasNarrowSynonym SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,"glueV: .acronym_stmt, acronym_scope = ""Narrow""",NA,oboInOwl:hasNarrowSynonym-OMO:0003012 +acronym(s): related,A oboInOwl:hasRelatedSynonym SPLIT=|,optional manual,DES,FALSE,"must be accompanied by ""acronym annotation"" header/template in the adjacent column to the rigth in robot template",NA,NA,NA,"glueV: .acronym_stmt, acronym_scope = ""Related""",NA,oboInOwl:hasRelatedSynonym-OMO:0003012 +acronym annotation,>AI oboInOwl:hasSynonymType,optional auto,acronym,FALSE,NA,NA,NA,NA,NA,NA,NA +xref(s),A oboInOwl:hasDbXref SPLIT=|,optional manual,OMIM:618901,FALSE,NA,NA,NA,NA,?iri oboInOwl:hasDbXref ?xref .,oboInOwl:hasDbXref,oboInOwl:hasDbXref +skos mapping(s): exact,A skos:exactMatch SPLIT=|,optional manual,OMIM:618901,FALSE,adds skos mappings as strings; current INCORRECT DO format,NA,NA,"should use IRIs and be as follows: AI skos:exactMatch SPLIT=| - - example input: https://omim.org/MIM:618901",?iri skos:exactMatch ?skos_exact .,adds skos mappings as strings; current INCORRECT DO format -skos mapping(s): broad,A skos:broadMatch SPLIT=|,optional manual,OMIM:PS613135,NA,NA,"should use IRIs and be as follows: + - example input: https://omim.org/MIM:618901",?iri skos:exactMatch ?skos_exact .,skos:exactMatch,skos:exactMatch +skos mapping(s): broad,A skos:broadMatch SPLIT=|,optional manual,OMIM:PS613135,FALSE,adds skos mappings as strings; current INCORRECT DO format,NA,NA,"should use IRIs and be as follows: AI skos:exactMatch SPLIT=| - - example input: https://omim.org/MIM:618901",?iri skos:broadMatch ?skos_broad .,adds skos mappings as strings; current INCORRECT DO format -skos mapping(s): narrow,A skos:narrowMatch SPLIT=|,optional manual,OMIM:618901,NA,NA,"should use IRIs and be as follows: + - example input: https://omim.org/MIM:618901",?iri skos:broadMatch ?skos_broad .,skos:broadMatch,skos:broadMatch +skos mapping(s): narrow,A skos:narrowMatch SPLIT=|,optional manual,OMIM:618901,FALSE,adds skos mappings as strings; current INCORRECT DO format,NA,NA,"should use IRIs and be as follows: AI skos:exactMatch SPLIT=| - - example input: https://omim.org/MIM:618901",?iri skos:narrowMatch ?skos_narrow .,adds skos mappings as strings; current INCORRECT DO format -skos mapping(s): related,A skos:relatedMatch SPLIT=|,optional manual,NA,NA,NA,"should use IRIs and be as follows: + - example input: https://omim.org/MIM:618901",?iri skos:narrowMatch ?skos_narrow .,skos:narrowMatch,skos:narrowMatch +skos mapping(s): related,A skos:relatedMatch SPLIT=|,optional manual,NA,FALSE,adds skos mappings as strings; current INCORRECT DO format,NA,NA,"should use IRIs and be as follows: AI skos:exactMatch SPLIT=| - - example input: https://omim.org/MIM:618901",?iri skos:relatedMatch ?skos_related .,adds skos mappings as strings; current INCORRECT DO format -equivalent class,EC %,optional manual,disease and ('has material basis in' some (Viruses or Bacteria or Eukaryota)),NA,NA,NA,NA,NA -sc axiom: inheritance,SC 'has material basis in' some % SPLIT=|,optional manual,NA,NA,NA,NA,NA,NA -sc axiom: anatomical location,SC 'disease has location' some %,optional manual,NA,NA,NA,NA,NA,NA -sc axiom: onset,SC 'existence starts during' some %,optional manual,NA,NA,NA,NA,NA,NA -sc axiom: has_material_basis_in,SC has_material_basis_in some %,optional manual,autosomal dominant inheritance,NA,NA,NA,NA,do not quote!!! -sc axiom: located_in,SC located_in some %,optional manual,NA,rdfs:label (preferred); IRI or CURIE (possible),NA,NA,NA,NA -disjoint class,DC %,optional manual,NA,NA,NA,NA,NA,NA -subset(s),AI oboInOwl:inSubset SPLIT=|,optional manual,DO_AGR_slim,any subset (aka 'slim') defined in doid-edit.owl,NA,NA,"?iri oboInOwl:inSubset ?subset_iri . -?subset_iri rdfs:label ?subset .",NA -deprecate,AT owl:deprecated^^xsd:boolean,optional manual,true,NA,NA,NA,?iri owl:deprecated ?deprecate .,NA -alternate id(s),A oboInOwl:hasAlternativeId SPLIT=|,optional manual,DOID:4,CURIE of deprecated term,NA,NA,?iri oboInOwl:hasAlternativeId ?alt_id .,NA -term replaced by,AI obo:IAO_0100001,optional manual,DOID:4,IRI or CURIE of term to replace by,NA,NA,?iri obo:IAO_0100001 ?term_replaced_by .,NA -comment,AL rdfs:comment@en,optional manual,This is a comment. There should only be one per term.,NA,NA,NA,?iri rdfs:comment ?comment .,NA -obo id,A oboInOwl:id,required auto,DOID:0080943,OBO CURIE,NA,NA,?iri oboInOwl:id ?id .,"required data, but not necessary to include in manual curation; will be inferred from iri/curie + - example input: https://omim.org/MIM:618901",?iri skos:relatedMatch ?skos_related .,skos:relatedMatch,skos:relatedMatch +eq class,EC % SPLIT=|,optional manual,disease and ('has material basis in' some (Viruses or Bacteria or Eukaryota)),FALSE,NA,NA,NA,NA,"?iri owl:equivalentClass ?eq . +FILTER(!isBlank(?eq))",Equivalent Class [NAMED ID],owl:equivalentClass +eq class anon,EC % SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,NA,Equivalent Class [ANON ID],NA +subclass anon,SC % SPLIT=|,optional manual,'disease has feature' some cherubism,FALSE,"intended for only subclass of anonymous logical expressions +--> separated from parent id for practical purposes - ROBOT template is the same, ROBOT export differs",NA,NA,NA,NA,SubClass Of [ANON ID],NA +subclass anon: inheritance,SC 'has material basis in' some % SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,NA,NA,NA +subclass anon: anatomical location,SC 'disease has location' some % SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,NA,NA,NA +subclass anon: onset,SC 'existence starts during' some % SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,NA,NA,NA +subclass anon: has_material_basis_in,SC has_material_basis_in some % SPLIT=|,optional manual,autosomal dominant inheritance,FALSE,do not quote standalone terms!!!,NA,NA,NA,NA,NA,NA +subclass anon: located_in,SC located_in some % SPLIT=|,optional manual,NA,FALSE,NA,rdfs:label (preferred); IRI or CURIE (possible),NA,NA,NA,NA,NA +disjoint class,DC % SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,"?iri owl:disjointClass ?disjoint . +FILTER(!isBlank(?disjoint))",Disjoint With [NAMED ID],owl:disjointWith +disjoint class anon,DC % SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,NA,Disjoint With [ANON ID],NA +subset(s),AI oboInOwl:inSubset SPLIT=|,optional manual,DO_AGR_slim,FALSE,NA,any subset (aka 'slim') defined in doid-edit.owl,NA,NA,"?iri oboInOwl:inSubset ?subset_iri . +?subset_iri rdfs:label ?subset .",oboInOwl:inSubset,oboInOwl:inSubset +alternate id(s),A oboInOwl:hasAlternativeId SPLIT=|,optional manual,DOID:4,FALSE,NA,CURIE of deprecated term,NA,NA,?iri oboInOwl:hasAlternativeId ?alt_id .,oboInOwl:hasAlternativeId,oboInOwl:hasAlternativeId +deprecated,AT owl:deprecated^^xsd:boolean,optional manual,true,FALSE,NA,NA,NA,NA,?iri owl:deprecated ?deprecate .,owl:deprecated,owl:deprecated +obsolescence reason,AI IAO:0000231,optional manual,terms merged,FALSE,must be IRI or label of child of 'obsolescence reason specification' (IAO:0000225),NA,NA,NA,?iri IAO:0000231 ?obs_reason .,NA,IAO:0000231 +term replaced by,AI IAO:0100001,optional manual,DOID:4,FALSE,NA,IRI or CURIE of term to replace by,NA,NA,?iri obo:IAO_0100001 ?term_replaced_by .,IAO:0100001,IAO:0100001 +consider instead,oboInOwl:consider,optional manual,NA,FALSE,NA,NA,NA,NA,?iri oboInOwl:consider ?consider .,oboInOwl:consider,oboInOwl:consider +comment,A rdfs:comment,optional manual,This is a comment. There should only be one per term.,FALSE,NA,NA,NA,NA,?iri rdfs:comment ?comment .,rdfs:comment,rdfs:comment +created by,A oboInOwl:created_by,optional manual,NA,FALSE,NA,NA,NA,NA,?iri oboInOwl:created_by ?created_by .,oboInOwl:created_by,oboInOwl:created_by +creation date,A oboInOwl:creation_date,optional manual,NA,FALSE,NA,NA,NA,NA,?iri oboInOwl:creation_date ?creation_date .,oboInOwl:creation_date,oboInOwl:creation_date +obo id,A oboInOwl:id,required auto,DOID:0080943,FALSE,"required data, but not necessary to include in manual curation; will be inferred from iri/curie -if manually entered it must match the CURIE form of iri/curie" -obo namespace,A oboInOwl:hasOBONamespace,required auto,disease_ontology,"OBO namespace of ontology: disease_ontology, symptoms, transmission_process",NA,NA,?iri oboInOwl:hasOBONamespace ?obo_namespace .,"required data, but not necessary to include in manual curation; will be automatically added for any new disease +if manually entered it must match the CURIE form of iri/curie",OBO CURIE,NA,NA,?iri oboInOwl:id ?id .,NA,oboInOwl:id +obo namespace,A oboInOwl:hasOBONamespace,required auto,disease_ontology,FALSE,"required data, but not necessary to include in manual curation; will be automatically added for any new disease -if manually entered it must be ""disease_ontology"" (without quotes)" -label - es,AL rdfs:label@es,optional manual,NA,NA,NA,NA,"label + glueV: .lang_stmt, object = ""?label"", lang = ""es""",NA -definition - es,AL obo:IAO_0000115@es,optional manual,NA,NA,NA,NA,"definition + glueV: .lang_stmt, object = ""?definition"", lang = ""es""",NA -synonym(s): exact - es,AL oboInOwl:hasExactSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,"1. glueV: .lang_stmt, object = ""?!<>!Synonym"", lang = ""es"" -2. +glueV: .synonym_stmt, syn_scope = ""Exact""",NA -synonym(s): broad - es,AL oboInOwl:hasBroadSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,"1. glueV: .lang_stmt, object = ""?!<>!Synonym"", lang = ""es"" -2. +glueV: .synonym_stmt, syn_scope = ""Broad""",NA -synonym(s): narrow - es,AL oboInOwl:hasNarrowSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,"1. glueV: .lang_stmt, object = ""?!<>!Synonym"", lang = ""es"" -2. +glueV: .synonym_stmt, syn_scope = ""Narrow""",NA -synonym(s): related - es,AL oboInOwl:hasRelatedSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,"1. glueV: .lang_stmt, object = ""?!<>!Synonym"", lang = ""es"" -2. +glueV: .synonym_stmt, syn_scope = ""Related""",NA -acronym(s): exact - es,AL oboInOwl:hasExactSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,NA,NA -acronym(s): broad - es,AL oboInOwl:hasBroadSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,NA,NA -acronym(s): narrow - es,AL oboInOwl:hasNarrowSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,NA,NA -acronym(s): related - es,AL oboInOwl:hasRelatedSynonym@es SPLIT=|,optional manual,NA,NA,NA,NA,NA,NA +if manually entered it must be ""disease_ontology"" (without quotes)","OBO namespace of ontology: disease_ontology, symptoms, transmission_process",NA,NA,?iri oboInOwl:hasOBONamespace ?obo_namespace .,oboInOwl:hasOBONamespace,oboInOwl:hasOBONamespace +label - es,AL rdfs:label@es,optional manual,NA,FALSE,NA,NA,NA,NA,"label + glueV: .lang_stmt, object = ""?label"", lang = ""es""",NA,NA +definition - es,AL obo:IAO_0000115@es,optional manual,NA,FALSE,NA,NA,NA,NA,"definition + glueV: .lang_stmt, object = ""?definition"", lang = ""es""",NA,NA +synonym(s): exact - es,AL oboInOwl:hasExactSynonym@es SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,"1. glueV: .lang_stmt, object = ""?!<>!Synonym"", lang = ""es"" +2. +glueV: .synonym_stmt, syn_scope = ""Exact""",NA,NA +synonym(s): broad - es,AL oboInOwl:hasBroadSynonym@es SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,"1. glueV: .lang_stmt, object = ""?!<>!Synonym"", lang = ""es"" +2. +glueV: .synonym_stmt, syn_scope = ""Broad""",NA,NA +synonym(s): narrow - es,AL oboInOwl:hasNarrowSynonym@es SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,"1. glueV: .lang_stmt, object = ""?!<>!Synonym"", lang = ""es"" +2. +glueV: .synonym_stmt, syn_scope = ""Narrow""",NA,NA +synonym(s): related - es,AL oboInOwl:hasRelatedSynonym@es SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,"1. glueV: .lang_stmt, object = ""?!<>!Synonym"", lang = ""es"" +2. +glueV: .synonym_stmt, syn_scope = ""Related""",NA,NA +acronym(s): exact - es,AL oboInOwl:hasExactSynonym@es SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,NA,NA,NA +acronym(s): broad - es,AL oboInOwl:hasBroadSynonym@es SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,NA,NA,NA +acronym(s): narrow - es,AL oboInOwl:hasNarrowSynonym@es SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,NA,NA,NA +acronym(s): related - es,AL oboInOwl:hasRelatedSynonym@es SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,NA,NA,NA diff --git a/data-raw/internal-curation_opts.R b/data-raw/internal-curation_opts.R index 4b887a31..0bfcbfc3 100644 --- a/data-raw/internal-curation_opts.R +++ b/data-raw/internal-curation_opts.R @@ -7,13 +7,24 @@ curation_opts <- googlesheets4::read_sheet( sheet = "template_options", col_types = "c" ) |> - dplyr::filter(!is.na(.data$template)) + dplyr::mutate(deprecated = readr::parse_logical(.data$deprecated)) |> + dplyr::filter(!is.na(.data$template) & !.data$deprecated) + + +# SPARQL set identifying curation data types ------------------------------ + +.sparql_dt_motif <- curation_opts |> + dplyr::filter(!is.na(.data$sparql_dt_motif)) |> + with( + purrr::set_names(data_type, sparql_dt_motif) + ) |> + length_sort(by_name = TRUE, decreasing = TRUE) readr::write_csv(curation_opts, "data-raw/curation_opts.csv") .curation_opts <- dplyr::select( - curation_opts, - tidyselect::all_of(c("header", "template", "type")) + curation_opts, + tidyselect::all_of(c("data_type", "template", "inclusion")) ) -use_data_internal(.curation_opts, overwrite = TRUE) \ No newline at end of file +use_data_internal(.sparql_dt_motif, .curation_opts, overwrite = TRUE) From 80e8086137993915ea19646cc82dc58bc9639b1b Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Thu, 18 Dec 2025 17:53:56 -0500 Subject: [PATCH 16/33] Add curation_template() obo_data method To create curation templates with input data from an OBO ontology --- NAMESPACE | 1 + R/curation.R | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index 3414a2f2..cab5d628 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -7,6 +7,7 @@ S3method(as_tibble,esummary_list_nested) S3method(as_tibble,scopus_search) S3method(as_tibble,scopus_search_list) S3method(curation_template,"NULL") +S3method(curation_template,obo_data) S3method(drop_blank,character) S3method(drop_blank,list) S3method(elucidate,omim_inventory) diff --git a/R/curation.R b/R/curation.R index b6d10986..e4494e97 100644 --- a/R/curation.R +++ b/R/curation.R @@ -37,6 +37,70 @@ curation_template.NULL <- function(ss = NULL, sheet = NULL, ..., nrow = 50) { invisible(gs_info) } +#' @export +curation_template.obo_data <- function(.data, ss = NULL, sheet = NULL, ..., + n_max = 20) { + cur_df <- .data |> + # need smarter indexing... I think + dplyr::mutate( + index = dplyr::dense_rank(paste0(.data$predicate, .data$value)), + .by = "id", + .before = "id" + ) |> + dplyr::mutate( + predicate = dplyr::if_else( + !is.na(.data$axiom_predicate) & .data$axiom_predicate == "oboInOwl:hasSynonymType", + paste0(.data$predicate, "-", .data$axiom_value), + .data$predicate + ), + axiom_predicate = dplyr::if_else( + !is.na(.data$axiom_predicate) & .data$axiom_predicate != "oboInOwl:hasSynonymType", + paste0(.data$predicate, "-", .data$axiom_predicate), + NA_character_ + ), + # removes axiom value where predicate is updated (redundant) + axiom_value = dplyr::if_else( + is.na(.data$axiom_predicate), + NA_character_, + .data$axiom_value + ) + ) |> + tidyr::pivot_longer( + cols = -c("index", "id"), + names_to = ".value", + names_prefix = "^axiom_", + values_drop_na = TRUE + ) |> + dplyr::arrange( + .data$id, + .data$index, + stringr::str_length(.data$predicate) + ) |> + dplyr::rename(data_type = "predicate", "curation_notes" = "extra") |> + # for now, just remove index --> need to use for sorting at some point + dplyr::select(-"index") |> + unique() |> + # collapse_col(value) |> # does nothing... probably don't want to collapse + dplyr::mutate( + data_type = dplyr::coalesce( + .sparql_dt_motif[.data$data_type], + .data$data_type + ), + id = dplyr::if_else(duplicated(.data$id), NA_character_, .data$id) + ) |> + append_empty_col(curation_cols, order = TRUE) + + + class(cur_df) <- c("curation_template", class(cur_df)) + if (is.null(sheet)) sheet <- paste0("curation-", format(Sys.Date(), "%Y%m%d")) + gs_info <- googlesheets4::write_sheet(cur_df, ss, sheet) + + if (is.null(ss)) ss <- gs_info + set_curation_validation(cur_df, ss, sheet) + + invisible(gs_info) +} + # helpers -------------------------------------------------------------------- From 264c3b4e2229612f666725901bfec9e4194a24dc Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Wed, 17 Dec 2025 10:40:35 -0500 Subject: [PATCH 17/33] Rename 'status' to 'action' To better reflect that this column and included notes indicate how the data should be acted upon and not its present status. --- R/curation.R | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/R/curation.R b/R/curation.R index e4494e97..14e59083 100644 --- a/R/curation.R +++ b/R/curation.R @@ -106,24 +106,24 @@ curation_template.obo_data <- function(.data, ss = NULL, sheet = NULL, ..., # define expected columns for curation template (in order) curation_cols <- c( - "id", "data_type", "value", "status", "curation_notes", "links", - "status_notes" + "id", "data_type", "value", "action", "curation_notes", "links", + "action_notes" ) -#' Curation 'Status' +#' Curation Action #' -#' Values used to establish `status` data validation in Google Sheets +#' Values used to establish `action` data validation in Google Sheets #' [curation templates][curation_template()]. #' #' * `retain`: data already in ontology that should be kept; this is the default -#' `status` for existing data when creating a [curation_template()] +#' `action` for existing data when creating a [curation_template()] #' #' * `add`: new data that should be added #' #' * `remove`: existing ontology data that should be removed #' #' * `exclude`: data relevant to the ontology that should be actively excluded -#' (e.g. an incorrect mapping) -- details should be included in `status_notes` +#' (e.g. an incorrect mapping) -- details should be included in `action_notes` #' #' * `ignore`: data not for active inclusion or exclusion that should be ignored #' (e.g. dubious synonyms, incomplete curation data) @@ -131,7 +131,7 @@ curation_cols <- c( #' * `restore`: data that was removed from the ontology and should be added back #' #' @keywords internal -curation_status <- c("retain", "add", "remove", "exclude", "ignore", "restore") +curation_action <- c("retain", "add", "remove", "exclude", "ignore", "restore") #' Set Data Validation for Curation Templates @@ -140,9 +140,9 @@ set_curation_validation <- function(cur_df, ss, sheet) { dt_range <- spreadsheet_range(cur_df, "data_type", sheet = sheet) range_add_dropdown(ss, dt_range, values = .curation_opts$data_type) - # add status validation - status_range <- spreadsheet_range(cur_df, "status", sheet = sheet) - range_add_dropdown(ss, status_range, values = curation_status) + # add action validation + action_range <- spreadsheet_range(cur_df, "action", sheet = sheet) + range_add_dropdown(ss, action_range, values = curation_action) # freeze first two columns googlesheets4::with_gs4_quiet( From 1d8d27265ac907f8a9c84257e00b3375353ca0a0 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Wed, 17 Dec 2025 10:42:07 -0500 Subject: [PATCH 18/33] Add missing 'sheet' param to GS validation functions --- R/googlesheets.R | 13 +++++++++---- man/range_add_validation.Rd | 12 ++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/R/googlesheets.R b/R/googlesheets.R index ed72b168..8e306e07 100644 --- a/R/googlesheets.R +++ b/R/googlesheets.R @@ -27,7 +27,8 @@ NULL #' @rdname range_add_validation #' @keywords internal -range_add_checkbox <- function(ss, range, msg = "Value must be TRUE or FALSE", +range_add_checkbox <- function(ss, sheet = NULL, range, + msg = "Value must be TRUE or FALSE", quiet = TRUE) { rule <- googlesheets4:::new( "DataValidationRule", @@ -47,12 +48,16 @@ range_add_checkbox <- function(ss, range, msg = "Value must be TRUE or FALSE", } } - .fn(ss = ss, range = range, rule = rule) + .fn(ss = ss, sheet = sheet, range = range, rule = rule) } #' @rdname range_add_validation +#' @section Limitations of the Google Sheets API/`googlesheets4`: +#' - The API does not support chipset multi-selection in dropdowns: +#' https://stackoverflow.com/questions/79653536/how-to-enable-multiple-selection-in-data-validation-dropdown-using-google-sheets #' @keywords internal -range_add_dropdown <- function(ss, range, values, msg = "Choose a valid value", +range_add_dropdown <- function(ss, sheet = NULL, range, values, + msg = "Choose a valid value", reject_input = TRUE, display_arrow = TRUE, quiet = TRUE) { rule <- googlesheets4:::new( @@ -76,7 +81,7 @@ range_add_dropdown <- function(ss, range, values, msg = "Choose a valid value", } } - .fn(ss = ss, range = range, rule = rule) + .fn(ss = ss, sheet = sheet, range = range, rule = rule) } diff --git a/man/range_add_validation.Rd b/man/range_add_validation.Rd index 0dc29777..4520ed53 100644 --- a/man/range_add_validation.Rd +++ b/man/range_add_validation.Rd @@ -8,6 +8,7 @@ \usage{ range_add_checkbox( ss, + sheet = NULL, range, msg = "Value must be TRUE or FALSE", quiet = TRUE @@ -15,6 +16,7 @@ range_add_checkbox( range_add_dropdown( ss, + sheet = NULL, range, values, msg = "Choose a valid value", @@ -36,6 +38,8 @@ returns Processed through \code{\link[googlesheets4:as_sheets_id]{as_sheets_id()}}.} +\item{sheet}{Sheet to write into, in the sense of "worksheet" or "tab". You can identify a sheet by name, with a string, or by position, with a number. Ignored if the sheet is specified via \code{range}. If neither argument specifies the sheet, defaults to the first visible sheet.} + \item{range}{Cells to apply data validation to. This \code{range} argument has important similarities and differences to \code{range} elsewhere (e.g. \code{\link[=range_read]{range_read()}}): @@ -62,4 +66,12 @@ value violates the data validation rule or "Show a warning" (\code{FALSE}).} \description{ Add data validation to a Google Sheet range. } +\section{Limitations of the Google Sheets API/\code{googlesheets4}}{ + +\itemize{ +\item The API does not support chipset multi-selection in dropdowns: +https://stackoverflow.com/questions/79653536/how-to-enable-multiple-selection-in-data-validation-dropdown-using-google-sheets +} +} + \keyword{internal} From 2cab2f785afbfc58cecec869b2cd7d9285b29ddd Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Wed, 17 Dec 2025 15:45:55 -0500 Subject: [PATCH 19/33] Create colnum_to_sht_letter() to fix spreadsheet_range() Original letter calculation would only have worked for first 26 columns. colnum_to_sht_letter() works for any number of columns. --- R/curation.R | 2 +- R/utils.R | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/R/curation.R b/R/curation.R index 14e59083..9fb3eb7e 100644 --- a/R/curation.R +++ b/R/curation.R @@ -165,7 +165,7 @@ set_curation_validation <- function(cur_df, ss, sheet) { #' @keywords internal spreadsheet_range <- function(.data, .col, sheet = NULL, rows = NULL, n_header = 1) { - col_letter <- LETTERS[which(names(.data) == .col)] + col_letter <- colnum_to_sht_letter(which(names(.data) == .col)) if (length(col_letter) != 1) { rlang::abort("Exactly one column must be specified in `.col`") } diff --git a/R/utils.R b/R/utils.R index a32bc52b..3008c9c1 100644 --- a/R/utils.R +++ b/R/utils.R @@ -406,6 +406,39 @@ match_arg_several <- function(arg, choices) { } +#' Convert Column Number to Spreadsheet Column Letter +#' +#' Convert a column numeric position into the corresponding spreadsheet column +#' letter reference (e.g. 1 -> "A", 27 -> "AA"). +#' +#' @param x The column number, as a single, positive whole number. +#' @return An spreadsheet column letter, as a string. +#' +#' @section Notes: +#' Modified from a vectorized version created by GPT-5 mini (2025-12-17). +#' +#' @examples +#' colnum_to_sht_letter(1) +#' sapply(c(26, 27, 52, 703), colnum_to_sht_letter) +#' +#' @noRd +colnum_to_sht_letter <- function(x) { + if (!is_scalar_whole_number(x) || !is_positive(x) || is.na(x)) { + rlang::abort("`x` must be a single, positive whole number") + } + + x <- as.integer(x) + pieces <- character() + i <- 1 + while (x > 0L) { + r <- (x - 1L) %% 26L + pieces <- c(LETTERS[r + 1L], pieces) + x <- (x - 1L) %/% 26L + } + paste0(pieces, collapse = "") +} + + # For producing messages -------------------------------------------------- # concatenates vector input `x` replacing values with the quantity dropped From 92bc76fa20f6b65f4dbe46cd2708a5760b50ddf4 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Mon, 22 Dec 2025 10:50:25 -0500 Subject: [PATCH 20/33] Import .env from rlang --- NAMESPACE | 1 + R/utils-tidyverse.R | 1 + 2 files changed, 2 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index cab5d628..a326e735 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -169,5 +169,6 @@ importFrom(methods,new) importFrom(rentrez,set_entrez_key) importFrom(rlang,":=") importFrom(rlang,.data) +importFrom(rlang,.env) importFrom(tibble,as_tibble) importFrom(tidyr,replace_na) diff --git a/R/utils-tidyverse.R b/R/utils-tidyverse.R index a293d2ae..be70f7d9 100644 --- a/R/utils-tidyverse.R +++ b/R/utils-tidyverse.R @@ -29,6 +29,7 @@ NULL #' @name rlang_imports #' @noRd #' @importFrom rlang .data +#' @importFrom rlang .env #' @importFrom rlang := NULL From 5eb291a086b6ea959ee209c122d88e51083304c1 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Mon, 22 Dec 2025 10:52:06 -0500 Subject: [PATCH 21/33] Fixes to pass R CMD CHECK --- R/curation.R | 10 ++++++---- R/extract.R | 2 +- R/googlesheets.R | 15 ++++----------- man/curation_template.Rd | 4 +++- man/range_add_validation.Rd | 8 ++++---- 5 files changed, 18 insertions(+), 21 deletions(-) diff --git a/R/curation.R b/R/curation.R index 9fb3eb7e..43b8d57b 100644 --- a/R/curation.R +++ b/R/curation.R @@ -8,6 +8,7 @@ #' @param sheet (OPTIONAL) The sheet name, as a string. If `NULL` (default), the #' sheet name will default to "curation-" with today's date appended (formatted #' as "%Y%m%d"; see [format.Date()]). +#' @param ... Additional arguments passed to methods. #' #' @returns The Google Sheet info (`ss`), as a [googlesheets4::sheets_id]. #' @@ -21,7 +22,8 @@ curation_template <- function(.data = NULL, ss = NULL, sheet = NULL, ...) { #' #' @export #' @rdname curation_template -curation_template.NULL <- function(ss = NULL, sheet = NULL, ..., nrow = 50) { +curation_template.NULL <- function(.data = NULL, ss = NULL, sheet = NULL, ..., + nrow = 50) { val <- rep(NA, nrow) # inspired by https://stackoverflow.com/a/60495352/6938922 @@ -134,7 +136,7 @@ curation_cols <- c( curation_action <- c("retain", "add", "remove", "exclude", "ignore", "restore") -#' Set Data Validation for Curation Templates +# Set Data Validation for Curation Templates set_curation_validation <- function(cur_df, ss, sheet) { # add data_type validation dt_range <- spreadsheet_range(cur_df, "data_type", sheet = sheet) @@ -180,7 +182,7 @@ spreadsheet_range <- function(.data, .col, sheet = NULL, rows = NULL, c("`rows` must be one continuous range", x = collapsed_range) ) } - row_ends <- c(rows[1], tail(rows, 1)) + n_header + row_ends <- c(rows[1], utils::tail(rows, 1)) + n_header } else { row_ends <- as.integer(stringr::str_split(row_ends, ":")[[1]]) + n_header } @@ -190,4 +192,4 @@ spreadsheet_range <- function(.data, .col, sheet = NULL, rows = NULL, range <- paste0(sheet, "!", range) } range -} \ No newline at end of file +} diff --git a/R/extract.R b/R/extract.R index d411c8d7..0ab7af0c 100644 --- a/R/extract.R +++ b/R/extract.R @@ -715,7 +715,7 @@ extract_obo_anon <- function(obo_ont, prefix = NULL, id = NULL, } ### FUTURE WORK -- identify subclass anon subtypes? ### - lengthen_col(out, value) + lengthen_col(out, "value") } #' Extract OBO Foundry Ontology Data diff --git a/R/googlesheets.R b/R/googlesheets.R index 8e306e07..5242f750 100644 --- a/R/googlesheets.R +++ b/R/googlesheets.R @@ -8,11 +8,11 @@ #' @inheritParams googlesheets4::range_write #' @param range Cells to apply data validation to. This `range` argument has #' important similarities and differences to `range` elsewhere (e.g. -#' [range_read()]): +#' [googlesheets4::range_read()]): #' * Similarities: Can be a cell range, using A1 notation ("A1:D3") or using -#' the helpers in [`cell-specification`]. Can combine sheet name and cell -#' range ("Sheet1!A5:A") or refer to a sheet by name (`range = "Sheet1"`, -#' although `sheet = "Sheet1"` is preferred for clarity). +#' the helpers in [googlesheets4::cell-specification]. Can combine sheet +#' name and cell range ("Sheet1!A5:A") or refer to a sheet by name +#' (`range = "Sheet1"`, although `sheet = "Sheet1"` is preferred for clarity). #' * Difference: Can NOT be a named range. #' @param msg The message to display when the user types in a value that #' violates the data validation rule. For `range_add_dropdown()`, only displayed @@ -83,10 +83,3 @@ range_add_dropdown <- function(ss, sheet = NULL, range, values, .fn(ss = ss, sheet = sheet, range = range, rule = rule) } - - -# microbenchmark( -# cur_cols %>% purrr::map_dfc(setNames, object = list(logical())), -# cur_cols %>% purrr::map_dfc(~ tibble::tibble(!!.x := logical())), - -# ) diff --git a/man/curation_template.Rd b/man/curation_template.Rd index 7d5a46ac..7e33ecab 100644 --- a/man/curation_template.Rd +++ b/man/curation_template.Rd @@ -7,7 +7,7 @@ \usage{ curation_template(.data = NULL, ss = NULL, sheet = NULL, ...) -\method{curation_template}{`NULL`}(ss = NULL, sheet = NULL, ..., nrow = 50) +\method{curation_template}{`NULL`}(.data = NULL, ss = NULL, sheet = NULL, ..., nrow = 50) } \arguments{ \item{.data}{Data to add to the curation sheet. If \code{NULL} (default), an empty @@ -29,6 +29,8 @@ Processed through \code{\link[googlesheets4:as_sheets_id]{as_sheets_id()}}.} sheet name will default to "curation-" with today's date appended (formatted as "\%Y\%m\%d"; see \code{\link[=format.Date]{format.Date()}}).} +\item{...}{Additional arguments passed to methods.} + \item{nrow}{The number of rows to create in the curation template when \code{.data = NULL} (default: \code{50}).} } diff --git a/man/range_add_validation.Rd b/man/range_add_validation.Rd index 4520ed53..e017e5e0 100644 --- a/man/range_add_validation.Rd +++ b/man/range_add_validation.Rd @@ -42,12 +42,12 @@ Processed through \code{\link[googlesheets4:as_sheets_id]{as_sheets_id()}}.} \item{range}{Cells to apply data validation to. This \code{range} argument has important similarities and differences to \code{range} elsewhere (e.g. -\code{\link[=range_read]{range_read()}}): +\code{\link[googlesheets4:range_read]{googlesheets4::range_read()}}): \itemize{ \item Similarities: Can be a cell range, using A1 notation ("A1:D3") or using -the helpers in \code{\link{cell-specification}}. Can combine sheet name and cell -range ("Sheet1!A5:A") or refer to a sheet by name (\code{range = "Sheet1"}, -although \code{sheet = "Sheet1"} is preferred for clarity). +the helpers in \link[googlesheets4:cell-specification]{googlesheets4::cell-specification}. Can combine sheet +name and cell range ("Sheet1!A5:A") or refer to a sheet by name +(\code{range = "Sheet1"}, although \code{sheet = "Sheet1"} is preferred for clarity). \item Difference: Can NOT be a named range. }} From d5f572050bd8d62e9cd650cadf9b89d92b439696 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Mon, 22 Dec 2025 10:10:15 -0500 Subject: [PATCH 22/33] Add missing changes to curation schema - Rename 'type' column in schema sheet to 'inclusion' - Remove 'deprecated' column from schema and use "deprecated" in inclusion instead - Add .sparql_dt_motif as internal data --- R/sysdata.rda | Bin 10220 -> 10391 bytes data-raw/curation_opts.csv | 101 +++++++++++++----------------- data-raw/internal-curation_opts.R | 3 +- 3 files changed, 46 insertions(+), 58 deletions(-) diff --git a/R/sysdata.rda b/R/sysdata.rda index 8fbd5b3672b231937033a93a5deaa6614564adb0..7d80e8abc1783109b1343c4b6a6809d31aeee0d2 100644 GIT binary patch literal 10391 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�%VQ+_V!wr0tI}9*W6I*h>qt`gxD34nInL80MQKqA_Fd1 zsYr*P=e&O9@bdZc4Qf3xJpO)}JNmu3n7*=A`i256$L-+~XA-i~kz`03WDX{7nb$5UX z*mb50_C7*`rvx%C8mgSOGzb|I%u^V~E^2^k zaLN0b2wQYbaUo3^Q5ca($ZudAx`6n=l#uw_1I5yWBA3W6ZWDe$7$C+$h)!1H%vXq9 z6e%(I=?XZSJP1`h8sY(V(uj!Oq?E`c3ZYK`V+gQ)6n+xvh;TtTSTeM*N=g$wn{UOY z51<;0V-Y1nQ3Q(ij$X5H+rO8<+}PvjuO3{sMiZd3+oTc!5ihFv~-lu3}QwY9PPIgd&JbT00pc8!YwI;$|6R#l^rAt%Ta2! zI6YVki5Wcjb)OO@8~0$9$@u?Q=s$Cx+aGs&@bkMjF73psE(0>nwkM1hQ4bmjS~dr; zlc1g1Q25+WZIOK zJclM&o&QGzx5K{ud9a*g-2|{7Ep_=t^jB!z>aAX&9+$NsC}M|4P)6>g330TwwJf9= z>Uw`RVGVeC}cLfq++4A*}|=D zv;x#$DoETS+h9f%a858@t|mcXzzZ1(!7XF&(Y)hw=%Ila06?-WFxf=ETHiMI_oZSu zLg`Q6O{reA?@IHhnQNeuCy4-)XW!^T|Ds6x>iJ%8t?m0h6U*gAy>Gd_r&qtK~=ethU6{T63kb))0qe+=16eJR8K=SON?^aFiOc2*E zi?^y&Wy*sRj7#cEID!&nU6yRY^|cz8D=kcF@{(a^W@`>+TK09a5Sk+Bpq;70UT{S? z?RWUbl3dp=koFy%aCcZ~-pyelaRb$m*pLXoTNa@uhJ`m%Lf>M^Cxrlm17E)#?947M zXHq9Ims#4C!ORs~fcmmMhwF;;Lm`tUVJTuJC3ha#&!Aw4<{s~hAp2gDp!;sQyF;BC8Yco2R}J?P`w))DPn3b~N3S|P z+D+b2)a*1DYXT4~W@;e_r^O26U4GYR6}lZ!Ksg911dAdDGh%h)TSC+q1iNE!whwH& z3B!N|FnKFr6P+%BGt_7r+@>oyb`k|GSU@N+h>1(w{GMaa903K@`MSPmnrQfZgLfB2LXj6Vo5$?9MOv%HE za|#%SeISGnoDjk>j5lPmEuo532z5YdAVQ@K$Rl%@U^&V-*IRj%YIMjUvj?R%M1gon mScj!zo{0bjpa5#v1Vey85UBx5&KjtH;_gVN3KAK!dl%pV4ZQ0B diff --git a/data-raw/curation_opts.csv b/data-raw/curation_opts.csv index 5e7a457a..39b500e5 100644 --- a/data-raw/curation_opts.csv +++ b/data-raw/curation_opts.csv @@ -1,76 +1,65 @@ -data_type,template,inclusion,example,deprecated,notes,optional_values,alternate_title,alternate_format,sparql,export_header,sparql_dt_motif -id,ID,required manual,DOID:0080943,FALSE,replaced older 'iri/curie' header for simplicity; id now covered by obo id field,IRI or CURIE,CI,IRI or CURIE; CI means Class IRI --> type will be CLASS_TYPE,?iri a owl:Class .,ID,NA -label,A rdfs:label,required manual,"46,XX sex reversal 5",FALSE,NA,NA,NA,NA,?iri rdfs:label ?label .,LABEL,rdfs:label -parent id,SC % SPLIT=|,required manual,DOID:0111760,FALSE,"accepts CURIE or IRI; intended for only asserted subclass relationships between named classes +data_type,template,inclusion,example,notes,optional_values,alternate_title,alternate_format,sparql,export_header,sparql_dt_motif +id,ID,required manual,DOID:0080943,replaced older 'iri/curie' header for simplicity; id now covered by obo id field,IRI or CURIE,CI,IRI or CURIE; CI means Class IRI --> type will be CLASS_TYPE,?iri a owl:Class .,ID,NA +label,A rdfs:label,required manual,"46,XX sex reversal 5",NA,NA,NA,NA,?iri rdfs:label ?label .,LABEL,rdfs:label +parent id,SC % SPLIT=|,required manual,DOID:0111760,"accepts CURIE or IRI; intended for only asserted subclass relationships between named classes --> separated from subclass anon for practical purposes - ROBOT template is the same, ROBOT export differs",disease by infectious agent,NA,NA,"?iri rdfs:subClassOf ?parent . FILTER(!isBlank(?parent))",SubClass Of [NAMED ID],rdfs:subClassOf -definition,A IAO:0000115,required manual,"A 46,XX sex reversal that is characterized by genital virilization in 46,XX individuals, associated with congenital heart disease and variable somatic anomalies including blepharophimosis-ptosis-epicanthus inversus syndrome and congenital diaphragmatic hernia and that has_material_basis_in heterozygous mutation in the NR2F2 gene on chromosome 15q26.",FALSE,NA,NA,NA,NA,?iri obo:IAO_0000115 ?definition .,obo:IAO_0000115,IAO:0000115 -definition source(s),>A oboInOwl:hasDbXref SPLIT=|,required manual,url:https://pubmed.ncbi.nlm.nih.gov/29478779/,FALSE,NA,NA,NA,NA,"!<<.definition_axiom>>! +definition,A IAO:0000115,required manual,"A 46,XX sex reversal that is characterized by genital virilization in 46,XX individuals, associated with congenital heart disease and variable somatic anomalies including blepharophimosis-ptosis-epicanthus inversus syndrome and congenital diaphragmatic hernia and that has_material_basis_in heterozygous mutation in the NR2F2 gene on chromosome 15q26.",NA,NA,NA,NA,?iri obo:IAO_0000115 ?definition .,obo:IAO_0000115,IAO:0000115 +definition source(s),>A oboInOwl:hasDbXref SPLIT=|,required manual,url:https://pubmed.ncbi.nlm.nih.gov/29478779/,NA,NA,NA,NA,"!<<.definition_axiom>>! oboInOwl:hasDbXref ?def_src .",NA,IAO:0000115-oboInOwl:hasDbXref -definition source type(s),>AI dc11:type SPLIT=|,optional manual,curator inference from journal publication,FALSE,do not quote!!!,"ECO codes, e.g. ECO:0007645",NA,NA,"!<<.definition_axiom>>! +definition source type(s),>AI dc11:type SPLIT=|,optional manual,curator inference from journal publication,do not quote!!!,"ECO codes, e.g. ECO:0007645",NA,NA,"!<<.definition_axiom>>! dc:type ?src_type .",NA,IAO:0000115-dc:type -synonym(s): exact,A oboInOwl:hasExactSynonym SPLIT=|,optional manual,hemangiosarcoma,FALSE,do not quote!!!,NA,NA,NA,"glueV: .synonym_stmt, syn_scope = ""Exact""",oboInOwl:hasExactSynonym,oboInOwl:hasExactSynonym -synonym(s): broad,A oboInOwl:hasBroadSynonym SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,"glueV: .synonym_stmt, syn_scope = ""Broad""",oboInOwl:hasBroadSynonym,oboInOwl:hasBroadSynonym -synonym(s): narrow,A oboInOwl:hasNarrowSynonym SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,"glueV: .synonym_stmt, syn_scope = ""Narrow""",oboInOwl:hasNarrowSynonym,oboInOwl:hasNarrowSynonym -synonym(s): related,A oboInOwl:hasRelatedSynonym SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,"glueV: .synonym_stmt, syn_scope = ""Related""",oboInOwl:hasRelatedSynonym,oboInOwl:hasRelatedSynonym -acronym(s): exact,A oboInOwl:hasExactSynonym SPLIT=|,optional manual,CAMRQ,FALSE,"must be accompanied by ""acronym annotation"" header/template in the adjacent column to the rigth in robot template",NA,NA,NA,"glueV: .acronym_stmt, acronym_scope = ""Exact""",NA,oboInOwl:hasExactSynonym-OMO:0003012 -acronym(s): broad,A oboInOwl:hasBroadSynonym SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,"glueV: .acronym_stmt, acronym_scope = ""Broad""",NA,oboInOwl:hasBroadSynonym-OMO:0003012 -acronym(s): narrow,A oboInOwl:hasNarrowSynonym SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,"glueV: .acronym_stmt, acronym_scope = ""Narrow""",NA,oboInOwl:hasNarrowSynonym-OMO:0003012 -acronym(s): related,A oboInOwl:hasRelatedSynonym SPLIT=|,optional manual,DES,FALSE,"must be accompanied by ""acronym annotation"" header/template in the adjacent column to the rigth in robot template",NA,NA,NA,"glueV: .acronym_stmt, acronym_scope = ""Related""",NA,oboInOwl:hasRelatedSynonym-OMO:0003012 -acronym annotation,>AI oboInOwl:hasSynonymType,optional auto,acronym,FALSE,NA,NA,NA,NA,NA,NA,NA -xref(s),A oboInOwl:hasDbXref SPLIT=|,optional manual,OMIM:618901,FALSE,NA,NA,NA,NA,?iri oboInOwl:hasDbXref ?xref .,oboInOwl:hasDbXref,oboInOwl:hasDbXref -skos mapping(s): exact,A skos:exactMatch SPLIT=|,optional manual,OMIM:618901,FALSE,adds skos mappings as strings; current INCORRECT DO format,NA,NA,"should use IRIs and be as follows: +synonym(s): exact,A oboInOwl:hasExactSynonym SPLIT=|,optional manual,hemangiosarcoma,do not quote!!!,NA,NA,NA,"glueV: .synonym_stmt, syn_scope = ""Exact""",oboInOwl:hasExactSynonym,oboInOwl:hasExactSynonym +synonym(s): broad,A oboInOwl:hasBroadSynonym SPLIT=|,optional manual,NA,NA,NA,NA,NA,"glueV: .synonym_stmt, syn_scope = ""Broad""",oboInOwl:hasBroadSynonym,oboInOwl:hasBroadSynonym +synonym(s): narrow,A oboInOwl:hasNarrowSynonym SPLIT=|,optional manual,NA,NA,NA,NA,NA,"glueV: .synonym_stmt, syn_scope = ""Narrow""",oboInOwl:hasNarrowSynonym,oboInOwl:hasNarrowSynonym +synonym(s): related,A oboInOwl:hasRelatedSynonym SPLIT=|,optional manual,NA,NA,NA,NA,NA,"glueV: .synonym_stmt, syn_scope = ""Related""",oboInOwl:hasRelatedSynonym,oboInOwl:hasRelatedSynonym +acronym(s): exact,A oboInOwl:hasExactSynonym SPLIT=|,optional manual,CAMRQ,"must be accompanied by ""acronym annotation"" header/template in the adjacent column to the rigth in robot template",NA,NA,NA,"glueV: .acronym_stmt, acronym_scope = ""Exact""",NA,oboInOwl:hasExactSynonym-OMO:0003012 +acronym(s): broad,A oboInOwl:hasBroadSynonym SPLIT=|,optional manual,NA,NA,NA,NA,NA,"glueV: .acronym_stmt, acronym_scope = ""Broad""",NA,oboInOwl:hasBroadSynonym-OMO:0003012 +acronym(s): narrow,A oboInOwl:hasNarrowSynonym SPLIT=|,optional manual,NA,NA,NA,NA,NA,"glueV: .acronym_stmt, acronym_scope = ""Narrow""",NA,oboInOwl:hasNarrowSynonym-OMO:0003012 +acronym(s): related,A oboInOwl:hasRelatedSynonym SPLIT=|,optional manual,DES,"must be accompanied by ""acronym annotation"" header/template in the adjacent column to the rigth in robot template",NA,NA,NA,"glueV: .acronym_stmt, acronym_scope = ""Related""",NA,oboInOwl:hasRelatedSynonym-OMO:0003012 +acronym annotation,>AI oboInOwl:hasSynonymType,optional auto,acronym,NA,NA,NA,NA,NA,NA,NA +xref(s),A oboInOwl:hasDbXref SPLIT=|,optional manual,OMIM:618901,NA,NA,NA,NA,?iri oboInOwl:hasDbXref ?xref .,oboInOwl:hasDbXref,oboInOwl:hasDbXref +skos mapping(s): exact,A skos:exactMatch SPLIT=|,optional manual,OMIM:618901,adds skos mappings as strings; current INCORRECT DO format,NA,NA,"should use IRIs and be as follows: AI skos:exactMatch SPLIT=| - example input: https://omim.org/MIM:618901",?iri skos:exactMatch ?skos_exact .,skos:exactMatch,skos:exactMatch -skos mapping(s): broad,A skos:broadMatch SPLIT=|,optional manual,OMIM:PS613135,FALSE,adds skos mappings as strings; current INCORRECT DO format,NA,NA,"should use IRIs and be as follows: +skos mapping(s): close,A skos:closeMatch SPLIT=|,optional manual,OMIM:618901,adds skos mappings as strings; current INCORRECT DO format,NA,NA,"should use IRIs and be as follows: +AI skos:closeMatch SPLIT=| + - example input: https://omim.org/MIM:618901",?iri skos:closeMatch ?skos_close .,skos:closeMatch,skos:closeMatch +skos mapping(s): broad,A skos:broadMatch SPLIT=|,optional manual,OMIM:PS613135,adds skos mappings as strings; current INCORRECT DO format,NA,NA,"should use IRIs and be as follows: AI skos:exactMatch SPLIT=| - example input: https://omim.org/MIM:618901",?iri skos:broadMatch ?skos_broad .,skos:broadMatch,skos:broadMatch -skos mapping(s): narrow,A skos:narrowMatch SPLIT=|,optional manual,OMIM:618901,FALSE,adds skos mappings as strings; current INCORRECT DO format,NA,NA,"should use IRIs and be as follows: +skos mapping(s): narrow,A skos:narrowMatch SPLIT=|,optional manual,OMIM:618901,adds skos mappings as strings; current INCORRECT DO format,NA,NA,"should use IRIs and be as follows: AI skos:exactMatch SPLIT=| - example input: https://omim.org/MIM:618901",?iri skos:narrowMatch ?skos_narrow .,skos:narrowMatch,skos:narrowMatch -skos mapping(s): related,A skos:relatedMatch SPLIT=|,optional manual,NA,FALSE,adds skos mappings as strings; current INCORRECT DO format,NA,NA,"should use IRIs and be as follows: +skos mapping(s): related,A skos:relatedMatch SPLIT=|,optional manual,NA,adds skos mappings as strings; current INCORRECT DO format,NA,NA,"should use IRIs and be as follows: AI skos:exactMatch SPLIT=| - example input: https://omim.org/MIM:618901",?iri skos:relatedMatch ?skos_related .,skos:relatedMatch,skos:relatedMatch -eq class,EC % SPLIT=|,optional manual,disease and ('has material basis in' some (Viruses or Bacteria or Eukaryota)),FALSE,NA,NA,NA,NA,"?iri owl:equivalentClass ?eq . +eq class,EC % SPLIT=|,optional manual,disease and ('has material basis in' some (Viruses or Bacteria or Eukaryota)),NA,NA,NA,NA,"?iri owl:equivalentClass ?eq . FILTER(!isBlank(?eq))",Equivalent Class [NAMED ID],owl:equivalentClass -eq class anon,EC % SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,NA,Equivalent Class [ANON ID],NA -subclass anon,SC % SPLIT=|,optional manual,'disease has feature' some cherubism,FALSE,"intended for only subclass of anonymous logical expressions +eq class anon,EC % SPLIT=|,optional manual,NA,NA,NA,NA,NA,NA,Equivalent Class [ANON ID],NA +subclass anon,SC % SPLIT=|,optional manual,'disease has feature' some cherubism,"intended for only subclass of anonymous logical expressions --> separated from parent id for practical purposes - ROBOT template is the same, ROBOT export differs",NA,NA,NA,NA,SubClass Of [ANON ID],NA -subclass anon: inheritance,SC 'has material basis in' some % SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,NA,NA,NA -subclass anon: anatomical location,SC 'disease has location' some % SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,NA,NA,NA -subclass anon: onset,SC 'existence starts during' some % SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,NA,NA,NA -subclass anon: has_material_basis_in,SC has_material_basis_in some % SPLIT=|,optional manual,autosomal dominant inheritance,FALSE,do not quote standalone terms!!!,NA,NA,NA,NA,NA,NA -subclass anon: located_in,SC located_in some % SPLIT=|,optional manual,NA,FALSE,NA,rdfs:label (preferred); IRI or CURIE (possible),NA,NA,NA,NA,NA -disjoint class,DC % SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,"?iri owl:disjointClass ?disjoint . +subclass anon: inheritance,SC 'has material basis in' some % SPLIT=|,optional manual,NA,NA,NA,NA,NA,NA,NA,NA +subclass anon: anatomical location,SC 'disease has location' some % SPLIT=|,optional manual,NA,NA,NA,NA,NA,NA,NA,NA +subclass anon: onset,SC 'existence starts during' some % SPLIT=|,optional manual,NA,NA,NA,NA,NA,NA,NA,NA +subclass anon: has_material_basis_in,SC has_material_basis_in some % SPLIT=|,optional manual,autosomal dominant inheritance,do not quote standalone terms!!!,NA,NA,NA,NA,NA,NA +subclass anon: located_in,SC located_in some % SPLIT=|,optional manual,NA,NA,rdfs:label (preferred); IRI or CURIE (possible),NA,NA,NA,NA,NA +disjoint class,DC % SPLIT=|,optional manual,NA,NA,NA,NA,NA,"?iri owl:disjointClass ?disjoint . FILTER(!isBlank(?disjoint))",Disjoint With [NAMED ID],owl:disjointWith -disjoint class anon,DC % SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,NA,Disjoint With [ANON ID],NA -subset(s),AI oboInOwl:inSubset SPLIT=|,optional manual,DO_AGR_slim,FALSE,NA,any subset (aka 'slim') defined in doid-edit.owl,NA,NA,"?iri oboInOwl:inSubset ?subset_iri . +disjoint class anon,DC % SPLIT=|,optional manual,NA,NA,NA,NA,NA,NA,Disjoint With [ANON ID],NA +subset(s),AI oboInOwl:inSubset SPLIT=|,optional manual,DO_AGR_slim,NA,any subset (aka 'slim') defined in doid-edit.owl,NA,NA,"?iri oboInOwl:inSubset ?subset_iri . ?subset_iri rdfs:label ?subset .",oboInOwl:inSubset,oboInOwl:inSubset -alternate id(s),A oboInOwl:hasAlternativeId SPLIT=|,optional manual,DOID:4,FALSE,NA,CURIE of deprecated term,NA,NA,?iri oboInOwl:hasAlternativeId ?alt_id .,oboInOwl:hasAlternativeId,oboInOwl:hasAlternativeId -deprecated,AT owl:deprecated^^xsd:boolean,optional manual,true,FALSE,NA,NA,NA,NA,?iri owl:deprecated ?deprecate .,owl:deprecated,owl:deprecated -obsolescence reason,AI IAO:0000231,optional manual,terms merged,FALSE,must be IRI or label of child of 'obsolescence reason specification' (IAO:0000225),NA,NA,NA,?iri IAO:0000231 ?obs_reason .,NA,IAO:0000231 -term replaced by,AI IAO:0100001,optional manual,DOID:4,FALSE,NA,IRI or CURIE of term to replace by,NA,NA,?iri obo:IAO_0100001 ?term_replaced_by .,IAO:0100001,IAO:0100001 -consider instead,oboInOwl:consider,optional manual,NA,FALSE,NA,NA,NA,NA,?iri oboInOwl:consider ?consider .,oboInOwl:consider,oboInOwl:consider -comment,A rdfs:comment,optional manual,This is a comment. There should only be one per term.,FALSE,NA,NA,NA,NA,?iri rdfs:comment ?comment .,rdfs:comment,rdfs:comment -created by,A oboInOwl:created_by,optional manual,NA,FALSE,NA,NA,NA,NA,?iri oboInOwl:created_by ?created_by .,oboInOwl:created_by,oboInOwl:created_by -creation date,A oboInOwl:creation_date,optional manual,NA,FALSE,NA,NA,NA,NA,?iri oboInOwl:creation_date ?creation_date .,oboInOwl:creation_date,oboInOwl:creation_date -obo id,A oboInOwl:id,required auto,DOID:0080943,FALSE,"required data, but not necessary to include in manual curation; will be inferred from iri/curie +alternate id(s),A oboInOwl:hasAlternativeId SPLIT=|,optional manual,DOID:4,NA,CURIE of deprecated term,NA,NA,?iri oboInOwl:hasAlternativeId ?alt_id .,oboInOwl:hasAlternativeId,oboInOwl:hasAlternativeId +deprecated,AT owl:deprecated^^xsd:boolean,optional manual,true,NA,NA,NA,NA,?iri owl:deprecated ?deprecate .,owl:deprecated,owl:deprecated +obsolescence reason,AI IAO:0000231,optional manual,terms merged,must be IRI or label of child of 'obsolescence reason specification' (IAO:0000225),NA,NA,NA,?iri IAO:0000231 ?obs_reason .,NA,IAO:0000231 +term replaced by,AI IAO:0100001,optional manual,DOID:4,NA,IRI or CURIE of term to replace by,NA,NA,?iri obo:IAO_0100001 ?term_replaced_by .,IAO:0100001,IAO:0100001 +consider instead,oboInOwl:consider,optional manual,NA,NA,NA,NA,NA,?iri oboInOwl:consider ?consider .,oboInOwl:consider,oboInOwl:consider +comment,A rdfs:comment,optional manual,This is a comment. There should only be one per term.,NA,NA,NA,NA,?iri rdfs:comment ?comment .,rdfs:comment,rdfs:comment +created by,A oboInOwl:created_by,optional manual,NA,NA,NA,NA,NA,?iri oboInOwl:created_by ?created_by .,oboInOwl:created_by,oboInOwl:created_by +creation date,A oboInOwl:creation_date,optional manual,NA,NA,NA,NA,NA,?iri oboInOwl:creation_date ?creation_date .,oboInOwl:creation_date,oboInOwl:creation_date +obo id,A oboInOwl:id,required auto,DOID:0080943,"required data, but not necessary to include in manual curation; will be inferred from iri/curie if manually entered it must match the CURIE form of iri/curie",OBO CURIE,NA,NA,?iri oboInOwl:id ?id .,NA,oboInOwl:id -obo namespace,A oboInOwl:hasOBONamespace,required auto,disease_ontology,FALSE,"required data, but not necessary to include in manual curation; will be automatically added for any new disease +obo namespace,A oboInOwl:hasOBONamespace,required auto,disease_ontology,"required data, but not necessary to include in manual curation; will be automatically added for any new disease if manually entered it must be ""disease_ontology"" (without quotes)","OBO namespace of ontology: disease_ontology, symptoms, transmission_process",NA,NA,?iri oboInOwl:hasOBONamespace ?obo_namespace .,oboInOwl:hasOBONamespace,oboInOwl:hasOBONamespace -label - es,AL rdfs:label@es,optional manual,NA,FALSE,NA,NA,NA,NA,"label + glueV: .lang_stmt, object = ""?label"", lang = ""es""",NA,NA -definition - es,AL obo:IAO_0000115@es,optional manual,NA,FALSE,NA,NA,NA,NA,"definition + glueV: .lang_stmt, object = ""?definition"", lang = ""es""",NA,NA -synonym(s): exact - es,AL oboInOwl:hasExactSynonym@es SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,"1. glueV: .lang_stmt, object = ""?!<>!Synonym"", lang = ""es"" -2. +glueV: .synonym_stmt, syn_scope = ""Exact""",NA,NA -synonym(s): broad - es,AL oboInOwl:hasBroadSynonym@es SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,"1. glueV: .lang_stmt, object = ""?!<>!Synonym"", lang = ""es"" -2. +glueV: .synonym_stmt, syn_scope = ""Broad""",NA,NA -synonym(s): narrow - es,AL oboInOwl:hasNarrowSynonym@es SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,"1. glueV: .lang_stmt, object = ""?!<>!Synonym"", lang = ""es"" -2. +glueV: .synonym_stmt, syn_scope = ""Narrow""",NA,NA -synonym(s): related - es,AL oboInOwl:hasRelatedSynonym@es SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,"1. glueV: .lang_stmt, object = ""?!<>!Synonym"", lang = ""es"" -2. +glueV: .synonym_stmt, syn_scope = ""Related""",NA,NA -acronym(s): exact - es,AL oboInOwl:hasExactSynonym@es SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,NA,NA,NA -acronym(s): broad - es,AL oboInOwl:hasBroadSynonym@es SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,NA,NA,NA -acronym(s): narrow - es,AL oboInOwl:hasNarrowSynonym@es SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,NA,NA,NA -acronym(s): related - es,AL oboInOwl:hasRelatedSynonym@es SPLIT=|,optional manual,NA,FALSE,NA,NA,NA,NA,NA,NA,NA diff --git a/data-raw/internal-curation_opts.R b/data-raw/internal-curation_opts.R index 0bfcbfc3..98806974 100644 --- a/data-raw/internal-curation_opts.R +++ b/data-raw/internal-curation_opts.R @@ -7,8 +7,7 @@ curation_opts <- googlesheets4::read_sheet( sheet = "template_options", col_types = "c" ) |> - dplyr::mutate(deprecated = readr::parse_logical(.data$deprecated)) |> - dplyr::filter(!is.na(.data$template) & !.data$deprecated) + dplyr::filter(!is.na(.data$template) & !.data$inclusion == "deprecated") # SPARQL set identifying curation data types ------------------------------ From ca31253dd220965001d48cd3d79583dc68e7cbfb Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Mon, 5 Jan 2026 11:32:45 -0500 Subject: [PATCH 23/33] Fix set_curation_validation() Error caused by addition of missing 'sheet' parameter in GS validation functions. --- R/curation.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R/curation.R b/R/curation.R index 43b8d57b..cd60afa3 100644 --- a/R/curation.R +++ b/R/curation.R @@ -139,12 +139,12 @@ curation_action <- c("retain", "add", "remove", "exclude", "ignore", "restore") # Set Data Validation for Curation Templates set_curation_validation <- function(cur_df, ss, sheet) { # add data_type validation - dt_range <- spreadsheet_range(cur_df, "data_type", sheet = sheet) - range_add_dropdown(ss, dt_range, values = .curation_opts$data_type) + dt_range <- spreadsheet_range(cur_df, "data_type") + range_add_dropdown(ss, sheet, dt_range, values = .curation_opts$data_type) # add action validation - action_range <- spreadsheet_range(cur_df, "action", sheet = sheet) - range_add_dropdown(ss, action_range, values = curation_action) + action_range <- spreadsheet_range(cur_df, "action") + range_add_dropdown(ss, sheet, action_range, values = curation_action) # freeze first two columns googlesheets4::with_gs4_quiet( From 736fdb579aef81c57083167f9cdf68d4cdfad896 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Fri, 23 Jan 2026 14:47:06 -0500 Subject: [PATCH 24/33] Set default 'action' to retain for curation_template.obo_data() --- R/curation.R | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/R/curation.R b/R/curation.R index cd60afa3..a5c466ac 100644 --- a/R/curation.R +++ b/R/curation.R @@ -12,6 +12,18 @@ #' #' @returns The Google Sheet info (`ss`), as a [googlesheets4::sheets_id]. #' +#' @section Formatting Limitations: +#' Formatting to make data more visually distinct is not currently supported due +#' to limitations of the Google Sheets API and the `googlesheets4` package: +#' * Google Sheets API does not support assigning colors to data validation. +#' * `googlesheets4` does not support any formatting. +#' +#' An alternative approach to support some formatting could be to create a +#' functional template with the desired formatting and copy that template with +#' [googlesheets4::sheet_copy()]. The `data_type` could still be populated by +#' this function (only needed to support types not in +#' `.curation_opts$data_type`). +#' #' @export curation_template <- function(.data = NULL, ss = NULL, sheet = NULL, ...) { UseMethod("curation_template", .data) @@ -43,12 +55,13 @@ curation_template.NULL <- function(.data = NULL, ss = NULL, sheet = NULL, ..., curation_template.obo_data <- function(.data, ss = NULL, sheet = NULL, ..., n_max = 20) { cur_df <- .data |> - # need smarter indexing... I think + # need smarter indexing... I think, not currently used (see below) dplyr::mutate( index = dplyr::dense_rank(paste0(.data$predicate, .data$value)), .by = "id", .before = "id" ) |> + # convert predicate & axiom_predicate to patterns in .sparql_dt_motif dplyr::mutate( predicate = dplyr::if_else( !is.na(.data$axiom_predicate) & .data$axiom_predicate == "oboInOwl:hasSynonymType", @@ -88,7 +101,9 @@ curation_template.obo_data <- function(.data, ss = NULL, sheet = NULL, ..., .sparql_dt_motif[.data$data_type], .data$data_type ), - id = dplyr::if_else(duplicated(.data$id), NA_character_, .data$id) + id = dplyr::if_else(duplicated(.data$id), NA_character_, .data$id), + # set default action for existing data + action = "retain" ) |> append_empty_col(curation_cols, order = TRUE) From eb186455659a512ddc8a37d225c6770c9f8c9408 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Fri, 20 Mar 2026 16:44:02 -0400 Subject: [PATCH 25/33] Rename colnum_to_sht_letter() to colnum_to_ss_letter() --- R/curation.R | 2 +- R/utils-spreadsheet.R | 31 +++++++++++++++++++++++++++++++ R/utils.R | 33 --------------------------------- 3 files changed, 32 insertions(+), 34 deletions(-) create mode 100644 R/utils-spreadsheet.R diff --git a/R/curation.R b/R/curation.R index a5c466ac..585b7812 100644 --- a/R/curation.R +++ b/R/curation.R @@ -182,7 +182,7 @@ set_curation_validation <- function(cur_df, ss, sheet) { #' @keywords internal spreadsheet_range <- function(.data, .col, sheet = NULL, rows = NULL, n_header = 1) { - col_letter <- colnum_to_sht_letter(which(names(.data) == .col)) + col_letter <- colnum_to_ss_letter(which(names(.data) == .col)) if (length(col_letter) != 1) { rlang::abort("Exactly one column must be specified in `.col`") } diff --git a/R/utils-spreadsheet.R b/R/utils-spreadsheet.R new file mode 100644 index 00000000..b5f3de68 --- /dev/null +++ b/R/utils-spreadsheet.R @@ -0,0 +1,31 @@ +#' Convert Column Number to Spreadsheet Column Letter +#' +#' Convert a column numeric position into the corresponding spreadsheet column +#' letter reference (e.g. 1 -> "A", 27 -> "AA"). +#' +#' @param x The column number, as a single, positive whole number. +#' @return An spreadsheet column letter, as a string. +#' +#' @section Notes: +#' Modified from a vectorized version created by GPT-5 mini (2025-12-17). +#' +#' @examples +#' colnum_to_ss_letter(1) +#' sapply(c(26, 27, 52, 703), colnum_to_ss_letter) +#' +#' @noRd +colnum_to_ss_letter <- function(x) { + if (!is_scalar_whole_number(x) || !is_positive(x) || is.na(x)) { + rlang::abort("`x` must be a single, positive whole number") + } + + x <- as.integer(x) + pieces <- character() + i <- 1 + while (x > 0L) { + r <- (x - 1L) %% 26L + pieces <- c(LETTERS[r + 1L], pieces) + x <- (x - 1L) %/% 26L + } + paste0(pieces, collapse = "") +} diff --git a/R/utils.R b/R/utils.R index 3008c9c1..a32bc52b 100644 --- a/R/utils.R +++ b/R/utils.R @@ -406,39 +406,6 @@ match_arg_several <- function(arg, choices) { } -#' Convert Column Number to Spreadsheet Column Letter -#' -#' Convert a column numeric position into the corresponding spreadsheet column -#' letter reference (e.g. 1 -> "A", 27 -> "AA"). -#' -#' @param x The column number, as a single, positive whole number. -#' @return An spreadsheet column letter, as a string. -#' -#' @section Notes: -#' Modified from a vectorized version created by GPT-5 mini (2025-12-17). -#' -#' @examples -#' colnum_to_sht_letter(1) -#' sapply(c(26, 27, 52, 703), colnum_to_sht_letter) -#' -#' @noRd -colnum_to_sht_letter <- function(x) { - if (!is_scalar_whole_number(x) || !is_positive(x) || is.na(x)) { - rlang::abort("`x` must be a single, positive whole number") - } - - x <- as.integer(x) - pieces <- character() - i <- 1 - while (x > 0L) { - r <- (x - 1L) %% 26L - pieces <- c(LETTERS[r + 1L], pieces) - x <- (x - 1L) %/% 26L - } - paste0(pieces, collapse = "") -} - - # For producing messages -------------------------------------------------- # concatenates vector input `x` replacing values with the quantity dropped From d0dad10cb0e1970f54a9f7768a294fa4a6669692 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Fri, 20 Mar 2026 16:47:52 -0400 Subject: [PATCH 26/33] Clean up colnum_to_ss_letter() & add tests Tests: PASS --- R/utils-spreadsheet.R | 1 - tests/testthat/test-utils-spreadsheet.R | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 tests/testthat/test-utils-spreadsheet.R diff --git a/R/utils-spreadsheet.R b/R/utils-spreadsheet.R index b5f3de68..4dd6b1c1 100644 --- a/R/utils-spreadsheet.R +++ b/R/utils-spreadsheet.R @@ -21,7 +21,6 @@ colnum_to_ss_letter <- function(x) { x <- as.integer(x) pieces <- character() - i <- 1 while (x > 0L) { r <- (x - 1L) %% 26L pieces <- c(LETTERS[r + 1L], pieces) diff --git a/tests/testthat/test-utils-spreadsheet.R b/tests/testthat/test-utils-spreadsheet.R new file mode 100644 index 00000000..2c83efda --- /dev/null +++ b/tests/testthat/test-utils-spreadsheet.R @@ -0,0 +1,7 @@ +test_that("colnum_to_ss_letter() works", { + expect_equal(colnum_to_ss_letter(1), "A") + expect_equal(colnum_to_ss_letter(26), "Z") + expect_equal(colnum_to_ss_letter(27), "AA") + expect_equal(colnum_to_ss_letter(52), "AZ") + expect_equal(colnum_to_ss_letter(703), "AAA") +}) From 014006c18c76e2ab036bad7c77b6ee7597c95f13 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Fri, 20 Mar 2026 17:40:21 -0400 Subject: [PATCH 27/33] Completely refactor internal data management Modification of #34 for full curation template work --- R/sysdata.rda | Bin 10391 -> 10402 bytes data-raw/internal-curation_opts.R | 29 - data-raw/internal-sssom_spec.R | 31 - .../{internal-DO_gs.R => internal/DO_gs.R} | 14 +- data-raw/internal/DO_gs.rds | Bin 0 -> 280 bytes data-raw/internal/curation_opts.R | 56 ++ data-raw/internal/curation_opts.rds | Bin 0 -> 981 bytes data-raw/internal/curation_opts.tsv | 65 ++ data-raw/internal/html4_tags.tsv | 97 +++ .../html_tags.R} | 37 +- data-raw/internal/html_tags.rds | Bin 0 -> 1582 bytes data-raw/internal/sparql_dt_motif.rds | Bin 0 -> 646 bytes data-raw/internal/sssom_mapping_slots.rds | Bin 0 -> 408 bytes data-raw/internal/sssom_schema-v1.0.0.yaml | 792 ++++++++++++++++++ data-raw/internal/sssom_slot_types.rds | Bin 0 -> 742 bytes data-raw/internal/sssom_spec.R | 58 ++ data-raw/internal/sssom_spec.rds | Bin 0 -> 7707 bytes data-raw/sysdata-update.R | 34 + 18 files changed, 1146 insertions(+), 67 deletions(-) delete mode 100644 data-raw/internal-curation_opts.R delete mode 100644 data-raw/internal-sssom_spec.R rename data-raw/{internal-DO_gs.R => internal/DO_gs.R} (53%) create mode 100644 data-raw/internal/DO_gs.rds create mode 100644 data-raw/internal/curation_opts.R create mode 100644 data-raw/internal/curation_opts.rds create mode 100644 data-raw/internal/curation_opts.tsv create mode 100644 data-raw/internal/html4_tags.tsv rename data-raw/{internal-html_tags.R => internal/html_tags.R} (51%) create mode 100644 data-raw/internal/html_tags.rds create mode 100644 data-raw/internal/sparql_dt_motif.rds create mode 100644 data-raw/internal/sssom_mapping_slots.rds create mode 100644 data-raw/internal/sssom_schema-v1.0.0.yaml create mode 100644 data-raw/internal/sssom_slot_types.rds create mode 100644 data-raw/internal/sssom_spec.R create mode 100644 data-raw/internal/sssom_spec.rds create mode 100644 data-raw/sysdata-update.R diff --git a/R/sysdata.rda b/R/sysdata.rda index 7d80e8abc1783109b1343c4b6a6809d31aeee0d2..7efedf423fe25521103a343b86a35a93a9459c43 100644 GIT binary patch delta 10356 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 S$6ZuE_`8xR!i0h>QSR{Wp0`v0 delta 10342 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 E0CYH{yZ`_I diff --git a/data-raw/internal-curation_opts.R b/data-raw/internal-curation_opts.R deleted file mode 100644 index 98806974..00000000 --- a/data-raw/internal-curation_opts.R +++ /dev/null @@ -1,29 +0,0 @@ -## code to prepare `curation_opts` internal dataset -rlang::check_installed("googlesheets4") -devtools::load_all() - -curation_opts <- googlesheets4::read_sheet( - "https://docs.google.com/spreadsheets/d/1Zn6p5xkVHUwbWe1N8FUa3fNcEkAOoE9P4ADb12f69hQ/edit", - sheet = "template_options", - col_types = "c" -) |> - dplyr::filter(!is.na(.data$template) & !.data$inclusion == "deprecated") - - -# SPARQL set identifying curation data types ------------------------------ - -.sparql_dt_motif <- curation_opts |> - dplyr::filter(!is.na(.data$sparql_dt_motif)) |> - with( - purrr::set_names(data_type, sparql_dt_motif) - ) |> - length_sort(by_name = TRUE, decreasing = TRUE) - -readr::write_csv(curation_opts, "data-raw/curation_opts.csv") - -.curation_opts <- dplyr::select( - curation_opts, - tidyselect::all_of(c("data_type", "template", "inclusion")) -) - -use_data_internal(.sparql_dt_motif, .curation_opts, overwrite = TRUE) diff --git a/data-raw/internal-sssom_spec.R b/data-raw/internal-sssom_spec.R deleted file mode 100644 index 57739278..00000000 --- a/data-raw/internal-sssom_spec.R +++ /dev/null @@ -1,31 +0,0 @@ -# Capture official SSSOM specification and parse for use by DO.utils -rlang::check_installed("yaml") -devtools::load_all() - -sssom_version <- stringr::str_remove( - httr::HEAD("https://github.com/mapping-commons/sssom/releases/latest/")$url, - ".*/" -) -sssom_yaml_path <- glueV( - "https://raw.githubusercontent.com/mapping-commons/sssom/!<>!/src/sssom_schema/schema/sssom_schema.yaml" -) -.sssom_spec <- yaml::read_yaml(sssom_yaml_path) -.sssom_spec$version <- sssom_version -.sssom_spec$access_date <- Sys.Date() - -.sssom_slot_types <- purrr::map_chr(.sssom_spec$slots, ~ .$range) -.sssom_mapping_slots <- .sssom_spec$classes$mapping$slots - -use_data_internal( - .sssom_spec, - .sssom_slot_types, - .sssom_mapping_slots, - overwrite = TRUE -) - -# save YAML for dev reference -dev_dir <- "setup_docs" -yaml_file <- file.path(dev_dir, paste0("sssom_schema-", sssom_version, ".yaml")) - -if (!dir.exists(dev_dir)) dir.create(dev_dir) -download.file(sssom_yaml_path, yaml_file) diff --git a/data-raw/internal-DO_gs.R b/data-raw/internal/DO_gs.R similarity index 53% rename from data-raw/internal-DO_gs.R rename to data-raw/internal/DO_gs.R index e7cda198..881f39b0 100644 --- a/data-raw/internal-DO_gs.R +++ b/data-raw/internal/DO_gs.R @@ -1,5 +1,9 @@ -## code to save DO Google Sheet information internally (`.DO_gs`) -devtools::load_all() +## code to prepare `.DO_gs` internal dataset ## +# +# Serves as a reference for DO-related Google Sheets and relevant sheets (tabs) +# for data retrieval + +rlang::check_installed("here") .DO_gs <- list( users = list( @@ -12,4 +16,8 @@ devtools::load_all() ) ) -use_data_internal(.DO_gs, overwrite = TRUE) +saveRDS( + .DO_gs, + file = here::here("data-raw", "internal", "DO_gs.rds"), + compress = "bzip2" +) diff --git a/data-raw/internal/DO_gs.rds b/data-raw/internal/DO_gs.rds new file mode 100644 index 0000000000000000000000000000000000000000..3af90b105fcd55071389aebdf44238354336d684 GIT binary patch literal 280 zcmV+z0q6cgT4*^jL0KkKSvpMuKmY)Ze}Mk*OaK4_{%{4tw&1?!|L_0-umJOE2~wL= zL7-?FH1!%iKp78Fk)SSg41fRt000006;)3}+MCc(iK9V@ + dplyr::filter(!is.na(.data$template) & !.data$inclusion == "deprecated") + +vroom::vroom_write( + curation_opts, + file.path(out_dir, "curation_opts.tsv"), + na = "" +) + +# save internal data +.curation_opts <- dplyr::select( + curation_opts, + dplyr::all_of(c("data_type", "template", "inclusion")) +) + +saveRDS( + .curation_opts, + file = file.path(out_dir, "curation_opts.rds"), + compress = "bzip2" +) + + +.sparql_dt_motif <- curation_opts |> + dplyr::filter(!is.na(.data$sparql_dt_motif)) |> + with( + purrr::set_names(data_type, sparql_dt_motif) + ) |> + length_sort(by_name = TRUE, decreasing = TRUE) + +saveRDS( + .sparql_dt_motif, + file = file.path(out_dir, "sparql_dt_motif.rds"), + compress = "bzip2" +) diff --git a/data-raw/internal/curation_opts.rds b/data-raw/internal/curation_opts.rds new file mode 100644 index 0000000000000000000000000000000000000000..3c778bf4985eacbef6a46d2d00ddedd0d6f10706 GIT binary patch literal 981 zcmV;`11kJNT4*^jL0KkKS!mp)MgRkef5QLw|Nmb5dQ(3q-a)_r|Kvab0RR92&;!=J zmCa7$0dqqVspOuGOwNEpEplA%5XfzH6nrWcW(9p=x41hE=2AVPe0AVo! zpo&OFO*8<2gVf2PpvVJ8fuH~l4FgR9sKW6ztnnEfgacU!AfW1kFoFw;Er$XjsK1qV z=7f-RjjS6^+q3Fih<|G1@j6q-zFc)OTlnc!=V#^UaFBolPzmpBUn-~;Aga+8#U^v- zILSGzR3mu^mJ*l@ZYUFus00E5y243>L?)Dl2tF(U6KG1C2x+#w{-eH)UBSKQNH4;R z(g+QSaKNn8AxQmDbH}Agt6kb;imJ-%wiuGNgan4&4gqdNOMYVH&FPZE!ofj}PV%35 z$)-*?H}THG;Ks(=yda`I+nKVU*1ooB4|bwn$!y`CN+_gm95@W%5+tL0j6poyHfuGA zq8)KCyb(qov0~A7`N1ek(J|n{g?`cG$QcsDp#&k+B9Z0qAevgi51K5txjQk2>02B6 z;uuk8FdqMg$5%@tEmmJ4fIl*61o_mncic(#rW#u_=^E1mgX}Ap0_?gDR$xD*p=TE4 zgb5!_>7E*Oa6%d&rW-+MqsoDAL5yl0xuxyDMxf!gKrC z?8s%))@|U3L{1@1|6(&r)TRt|NfJq}D#%DbgjDPxtlrg{Qz}{7thJV_s}>8Ig8j(M zE6AmELXj3XHzz2H%>b0zw(}(Z)-{T8D4Odh1J0`ZYJhR+MsQHuP=alYI5R00#`%CJ zDchF<7vTyKOr@)7phzUu0WMUSf(Rl9U_w#^H80c`?v#}jsU$9=lH)xD5kjFbhDQ)3 zBg4qru>)U{AHwFC*Pvf86UdYVBmxyqOa+8QL=hLP+7LgYSW$4cwMfs9HB&C0WRZzL zg)`?aID(TJ@EPcJE0$9#U2A~M8>NYn#|p%&#Mq5K6k$WqAd!i-O$r861Wm^h=*!NR z2jTs<2@fJ9AoyZKNqQS`$5yT+@w-9!!6*Y1#sM=PZ><+d4j>IiV_t}7kU}nPUrWE3;bQl6yZWaqjHrP Do;$PH literal 0 HcmV?d00001 diff --git a/data-raw/internal/curation_opts.tsv b/data-raw/internal/curation_opts.tsv new file mode 100644 index 00000000..6f32c3e2 --- /dev/null +++ b/data-raw/internal/curation_opts.tsv @@ -0,0 +1,65 @@ +data_type template inclusion example notes optional_values alternate_title alternate_format sparql export_header sparql_dt_motif +id ID required manual DOID:0080943 replaced older 'iri/curie' header for simplicity; id now covered by obo id field IRI or CURIE CI IRI or CURIE; CI means Class IRI --> type will be CLASS_TYPE ?iri a owl:Class . ID +label A rdfs:label required manual 46,XX sex reversal 5 ?iri rdfs:label ?label . LABEL rdfs:label +parent id SC % SPLIT=| required manual DOID:0111760 "accepts CURIE or IRI; intended for only asserted subclass relationships between named classes +--> separated from subclass anon for practical purposes - ROBOT template is the same, ROBOT export differs" disease by infectious agent "?iri rdfs:subClassOf ?parent . +FILTER(!isBlank(?parent))" SubClass Of [NAMED ID] rdfs:subClassOf +definition A IAO:0000115 required manual A 46,XX sex reversal that is characterized by genital virilization in 46,XX individuals, associated with congenital heart disease and variable somatic anomalies including blepharophimosis-ptosis-epicanthus inversus syndrome and congenital diaphragmatic hernia and that has_material_basis_in heterozygous mutation in the NR2F2 gene on chromosome 15q26. ?iri obo:IAO_0000115 ?definition . obo:IAO_0000115 IAO:0000115 +definition source(s) >A oboInOwl:hasDbXref SPLIT=| required manual url:https://pubmed.ncbi.nlm.nih.gov/29478779/ "!<<.definition_axiom>>! + oboInOwl:hasDbXref ?def_src ." IAO:0000115-oboInOwl:hasDbXref +definition source type(s) >AI dc11:type SPLIT=| optional manual curator inference from journal publication do not quote!!! ECO codes, e.g. ECO:0007645 "!<<.definition_axiom>>! + dc:type ?src_type ." IAO:0000115-dc:type +xref(s) A oboInOwl:hasDbXref SPLIT=| optional manual OMIM:618901 ?iri oboInOwl:hasDbXref ?xref . oboInOwl:hasDbXref oboInOwl:hasDbXref +skos mapping(s): exact A skos:exactMatch SPLIT=| optional manual OMIM:618901 adds skos mappings as strings; current INCORRECT DO format "should use IRIs and be as follows: +AI skos:exactMatch SPLIT=| + - example input: https://omim.org/MIM:618901" ?iri skos:exactMatch ?skos_exact . skos:exactMatch skos:exactMatch +skos mapping(s): close A skos:closeMatch SPLIT=| optional manual OMIM:618901 adds skos mappings as strings; current INCORRECT DO format "should use IRIs and be as follows: +AI skos:closeMatch SPLIT=| + - example input: https://omim.org/MIM:618901" ?iri skos:closeMatch ?skos_close . skos:closeMatch skos:closeMatch +skos mapping(s): broad A skos:broadMatch SPLIT=| optional manual OMIM:PS613135 adds skos mappings as strings; current INCORRECT DO format "should use IRIs and be as follows: +AI skos:exactMatch SPLIT=| + - example input: https://omim.org/MIM:618901" ?iri skos:broadMatch ?skos_broad . skos:broadMatch skos:broadMatch +skos mapping(s): narrow A skos:narrowMatch SPLIT=| optional manual OMIM:618901 adds skos mappings as strings; current INCORRECT DO format "should use IRIs and be as follows: +AI skos:exactMatch SPLIT=| + - example input: https://omim.org/MIM:618901" ?iri skos:narrowMatch ?skos_narrow . skos:narrowMatch skos:narrowMatch +skos mapping(s): related A skos:relatedMatch SPLIT=| optional manual adds skos mappings as strings; current INCORRECT DO format "should use IRIs and be as follows: +AI skos:exactMatch SPLIT=| + - example input: https://omim.org/MIM:618901" ?iri skos:relatedMatch ?skos_related . skos:relatedMatch skos:relatedMatch +synonym(s): exact A oboInOwl:hasExactSynonym SPLIT=| optional manual hemangiosarcoma do not quote!!! "glueV: .synonym_stmt, syn_scope = ""Exact""" oboInOwl:hasExactSynonym oboInOwl:hasExactSynonym +synonym(s): broad A oboInOwl:hasBroadSynonym SPLIT=| optional manual "glueV: .synonym_stmt, syn_scope = ""Broad""" oboInOwl:hasBroadSynonym oboInOwl:hasBroadSynonym +synonym(s): narrow A oboInOwl:hasNarrowSynonym SPLIT=| optional manual "glueV: .synonym_stmt, syn_scope = ""Narrow""" oboInOwl:hasNarrowSynonym oboInOwl:hasNarrowSynonym +synonym(s): related A oboInOwl:hasRelatedSynonym SPLIT=| optional manual "glueV: .synonym_stmt, syn_scope = ""Related""" oboInOwl:hasRelatedSynonym oboInOwl:hasRelatedSynonym +acronym(s): exact A oboInOwl:hasExactSynonym SPLIT=| optional manual CAMRQ "must be accompanied by ""acronym annotation"" header/template in the adjacent column to the rigth in robot template" "glueV: .acronym_stmt, acronym_scope = ""Exact""" oboInOwl:hasExactSynonym-OMO:0003012 +acronym(s): broad A oboInOwl:hasBroadSynonym SPLIT=| optional manual "glueV: .acronym_stmt, acronym_scope = ""Broad""" oboInOwl:hasBroadSynonym-OMO:0003012 +acronym(s): narrow A oboInOwl:hasNarrowSynonym SPLIT=| optional manual "glueV: .acronym_stmt, acronym_scope = ""Narrow""" oboInOwl:hasNarrowSynonym-OMO:0003012 +acronym(s): related A oboInOwl:hasRelatedSynonym SPLIT=| optional manual DES "must be accompanied by ""acronym annotation"" header/template in the adjacent column to the rigth in robot template" "glueV: .acronym_stmt, acronym_scope = ""Related""" oboInOwl:hasRelatedSynonym-OMO:0003012 +acronym annotation >AI oboInOwl:hasSynonymType optional auto acronym +eq class EC % SPLIT=| optional manual disease and ('has material basis in' some (Viruses or Bacteria or Eukaryota)) "?iri owl:equivalentClass ?eq . +FILTER(!isBlank(?eq))" Equivalent Class [NAMED ID] owl:equivalentClass +eq class anon EC % SPLIT=| optional manual Equivalent Class [ANON ID] +subclass anon SC % SPLIT=| optional manual 'disease has feature' some cherubism "intended for only subclass of anonymous logical expressions +--> separated from parent id for practical purposes - ROBOT template is the same, ROBOT export differs" SubClass Of [ANON ID] +subclass anon: inheritance SC 'has material basis in' some % SPLIT=| optional manual +subclass anon: anatomical location SC 'disease has location' some % SPLIT=| optional manual +subclass anon: onset SC 'existence starts during' some % SPLIT=| optional manual +subclass anon: has_material_basis_in SC has_material_basis_in some % SPLIT=| optional manual autosomal dominant inheritance do not quote standalone terms!!! +subclass anon: located_in SC located_in some % SPLIT=| optional manual rdfs:label (preferred); IRI or CURIE (possible) +disjoint class DC % SPLIT=| optional manual "?iri owl:disjointClass ?disjoint . +FILTER(!isBlank(?disjoint))" Disjoint With [NAMED ID] owl:disjointWith +disjoint class anon DC % SPLIT=| optional manual Disjoint With [ANON ID] +subset(s) AI oboInOwl:inSubset SPLIT=| optional manual DO_AGR_slim any subset (aka 'slim') defined in doid-edit.owl "?iri oboInOwl:inSubset ?subset_iri . +?subset_iri rdfs:label ?subset ." oboInOwl:inSubset oboInOwl:inSubset +alternate id(s) A oboInOwl:hasAlternativeId SPLIT=| optional manual DOID:4 CURIE of deprecated term ?iri oboInOwl:hasAlternativeId ?alt_id . oboInOwl:hasAlternativeId oboInOwl:hasAlternativeId +deprecated AT owl:deprecated^^xsd:boolean optional manual true ?iri owl:deprecated ?deprecate . owl:deprecated owl:deprecated +obsolescence reason AI IAO:0000231 optional manual terms merged must be IRI or label of child of 'obsolescence reason specification' (IAO:0000225) ?iri IAO:0000231 ?obs_reason . IAO:0000231 +term replaced by AI IAO:0100001 optional manual DOID:4 IRI or CURIE of term to replace by ?iri obo:IAO_0100001 ?term_replaced_by . IAO:0100001 IAO:0100001 +consider instead oboInOwl:consider optional manual ?iri oboInOwl:consider ?consider . oboInOwl:consider oboInOwl:consider +comment A rdfs:comment optional manual This is a comment. There should only be one per term. ?iri rdfs:comment ?comment . rdfs:comment rdfs:comment +created by A oboInOwl:created_by optional manual ?iri oboInOwl:created_by ?created_by . oboInOwl:created_by oboInOwl:created_by +creation date A oboInOwl:creation_date optional manual ?iri oboInOwl:creation_date ?creation_date . oboInOwl:creation_date oboInOwl:creation_date +obo id A oboInOwl:id required auto DOID:0080943 "required data, but not necessary to include in manual curation; will be inferred from iri/curie + +if manually entered it must match the CURIE form of iri/curie" OBO CURIE ?iri oboInOwl:id ?id . oboInOwl:id +obo namespace A oboInOwl:hasOBONamespace required auto disease_ontology "required data, but not necessary to include in manual curation; will be automatically added for any new disease + +if manually entered it must be ""disease_ontology"" (without quotes)" OBO namespace of ontology: disease_ontology, symptoms, transmission_process ?iri oboInOwl:hasOBONamespace ?obo_namespace . oboInOwl:hasOBONamespace oboInOwl:hasOBONamespace diff --git a/data-raw/internal/html4_tags.tsv b/data-raw/internal/html4_tags.tsv new file mode 100644 index 00000000..34ae8e9e --- /dev/null +++ b/data-raw/internal/html4_tags.tsv @@ -0,0 +1,97 @@ +name start_tag end_tag deprecated dtd description +a required required FALSE anchor +abbr required required FALSE "abbreviated form (e.g., www, http, +etc.)" +acronym required required FALSE +address required required FALSE information on author +applet required required TRUE loose java applet +area required forbidden FALSE client-side image map area +b required required FALSE bold text style +base required forbidden FALSE document base uri +basefont required forbidden TRUE loose base font size +bdo required required FALSE i18n bidi over-ride +big required required FALSE large text style +blockquote required required FALSE long quotation +body optional optional FALSE document body +br required forbidden FALSE forced line break +button required required FALSE push button +caption required required FALSE table caption +center required required TRUE loose shorthand for div align=center +cite required required FALSE citation +code required required FALSE computer code fragment +col required forbidden FALSE table column +colgroup required optional FALSE table column group +dd required optional FALSE definition description +del required required FALSE deleted text +dfn required required FALSE instance definition +dir required required TRUE loose directory list +div required required FALSE generic language/style container +dl required required FALSE definition list +dt required optional FALSE definition term +em required required FALSE emphasis +fieldset required required FALSE form control group +font required required TRUE loose local change to font +form required required FALSE interactive form +frame required forbidden FALSE forbidden subwindow +frameset required required FALSE forbidden window subdivision +h1 required required FALSE heading +h2 required required FALSE heading +h3 required required FALSE heading +h4 required required FALSE heading +h5 required required FALSE heading +h6 required required FALSE heading +head optional optional FALSE document head +hr required forbidden FALSE horizontal rule +html optional optional FALSE document root element +i required required FALSE italic text style +iframe required required FALSE loose inline subwindow +img required forbidden FALSE embedded image +input required forbidden FALSE form control +ins required required FALSE inserted text +isindex required forbidden TRUE loose single line prompt +kbd required required FALSE text to be entered by the user +label required required FALSE form field label text +legend required required FALSE fieldset legend +li required optional FALSE list item +link required forbidden FALSE a media-independent link +map required required FALSE client-side image map +menu required required TRUE loose menu list +meta required forbidden FALSE generic metainformation +noframes required required FALSE forbidden "alternate content container for non +frame-based rendering" +noscript required required FALSE "alternate content container for non +script-based rendering" +object required required FALSE generic embedded object +ol required required FALSE ordered list +optgroup required required FALSE option group +option required optional FALSE selectable choice +p required optional FALSE paragraph +param required forbidden FALSE named property value +pre required required FALSE preformatted text +q required required FALSE short inline quotation +s required required TRUE loose strike-through text style +samp required required FALSE "sample program output, scripts, +etc." +script required required FALSE script statements +select required required FALSE option selector +small required required FALSE small text style +span required required FALSE generic language/style container +strike required required TRUE loose strike-through text +strong required required FALSE strong emphasis +style required required FALSE style info +sub required required FALSE subscript +sup required required FALSE superscript +table required required FALSE +tbody optional optional FALSE table body +td required optional FALSE table data cell +textarea required required FALSE multi-line text field +tfoot required optional FALSE table footer +th required optional FALSE table header cell +thead required optional FALSE table header +title required required FALSE document title +tr required optional FALSE table row +tt required required FALSE teletype or monospaced text style +u required required TRUE loose underlined text style +ul required required FALSE unordered list +var required required FALSE "instance of a variable or program +argument" diff --git a/data-raw/internal-html_tags.R b/data-raw/internal/html_tags.R similarity index 51% rename from data-raw/internal-html_tags.R rename to data-raw/internal/html_tags.R index 157ab11f..95de392a 100644 --- a/data-raw/internal-html_tags.R +++ b/data-raw/internal/html_tags.R @@ -1,13 +1,31 @@ +## code to prepare `.html_tags` internal dataset ## +# +# HTML tag information is retrieved from the W3C HTML 4.01 specification and +# serves as a reference for parsing and validation of HTML elements in DO +# website curation +# +# NOTE: HTML 4.01 is used as a reference for tag information, but the web +# now supports the HTML Living Standard (https://html.spec.whatwg.org/), which +# includes additional tags and attributes. + rlang::check_installed( - c("dplyr", "janitor", "purrr", "rvest", "stringr", "tidyr") + c("dplyr", "here", "janitor", "purrr", "rvest", "stringr", "tidyr", "vroom") ) -raw_element_index <- rvest::read_html("https://www.w3.org/TR/html401/index/elements.html") +outdir <- here::here("data-raw", "internal") + +raw_element_index <- rvest::read_html( + "https://www.w3.org/TR/html401/index/elements.html" +) index_legend <- raw_element_index |> rvest::html_text() |> stringr::str_match( - stringr::regex("legend:(.*)name[^[:alnum:]]", dotall = TRUE, ignore_case = TRUE) + stringr::regex( + "legend:(.*)name[^[:alnum:]]", + dotall = TRUE, + ignore_case = TRUE + ) ) |> (\(x) x[, 2])() |> stringr::str_split(",[[:space:]]*") |> @@ -39,4 +57,15 @@ if (nrow(.html_tags) != dplyr::n_distinct(.html_tags$name)) { rlang::abort("Duplicate HTML tag names found") } -use_data_internal(.html_tags, overwrite = TRUE) +# save tabular data for reference +vroom::vroom_write( + .html_tags, + file = file.path(outdir, "html4_tags.tsv"), + na = "" +) + +saveRDS( + .html_tags, + file = file.path(outdir, "html_tags.rds"), + compress = "bzip2" +) diff --git a/data-raw/internal/html_tags.rds b/data-raw/internal/html_tags.rds new file mode 100644 index 0000000000000000000000000000000000000000..4e3f4e3055f07afdd0b344ef5a335a79905dd6fe GIT binary patch literal 1582 zcmV+}2GRLKT4*^jL0KkKSrZG)i2w(R|Iq*c|M_Hpzw!wH21vjE|L{Nn0ssI3;09lM z&hFNYT+|TQ0NZM~NvtRYS+NRu*u9-|ejMs*&+2`V;`FnK`YC)$?IHk8VYfn;Vf@AN}iz$&PI!2^{ zblS)Ql*|w!2ynr)*w_jPfe_R%P|{G+&1(^eQqVY=mWgCIX|d}QFrh?CS>LPe$}_;8 zi44fW4NXQNoaHV#MC(A49hnT8OIi8lHb%MBmf1M%tu@;?U4E~1A+otVCYHf*X(1)| z!2l+bK&|##uX*;?>oNpnrho*pq67#?{BoMMXOIX!4IsIQRr$Rq?T9jr^TLQZ)@!=u zLF{EIrR9`qj;r|eg6&$4Z`r-nP{6@BqD7$y%1RK!fW=b$#mA9>RpVk8$z|-1=ij<| ze~d{HN9yU{l7RzU_-%CHV0U#EyI|bjN7?ZBLcc-qp{`?SyE>i0|Qy{P;k| zVq4Bk2Fe$C=b`CSRaAz1uxy33p$Hy%$r$8SY_`(23tf3NIR#gJF~IAN%Hc8Pa0aK( z)uAD^7mm!(%*Is*Kh~_IC zq>Uj6`J=l2wX-6D_p*R8BKm>Hor`isG;&GOj}pLovd|=?A`l_pnmTW7b!jqy3D6>M z2!aDlTa*do&Jnf)!+{b%1#u-}qT-*X>$y=oLCR1MV##JqTPx14Ias}$|4s1$lTwB- zeIui?=af)p9$R(}$5G?iOvkbyi$L1}w9;fa z8hrKMdoboz9<$UG_3b{2_(^F{^jS38E-!=%rahU3;qPHuq*5E*x9IeggIyijv9txW zq@ZPIdP8SYc_n$khUv%B4f6@%%<%5<@4GwjT?jr)dvy;`T+7?K93c_y;jOn~Bp|@| z<7_L(*ha=Sn;RH2vg62sIwTvYtrw((7bwW@5)gM<i3hiGh+!GeveNe|qD_~Mn%!~&P*{`T#(I!y zm5^4vv?4p#CbeQZIc>7QY^1AZv%(A#jjb_2y0PI)O0`mle93C|8~1%%Jo$CO&yVFZ z4Ud-ddpI#zd>3Ri;e0C$53~H|P!P3dEh3Oi+9}6XFKoHVU=hub;v7UYg4c4P=O*HV z$7fp9W5#l+hL4}AEdT+n|A7Dh{qaX)dQd+jp1{BV|KI=tKmu^&5~4V1 zrU+y-(<3Gj4Kx9S(WXoQAZR2=fQ2!MsLc~0v`j#HfYTE}>S?BE4MT>SV1`3YGBRNR z(?A$a8f3r%27*Yb>YkH&r;-f~9-~bf0Aw@+(r6k0(9kGywk;5|w1CtYbI*O(C=i31 z-w1gvlk9=|xCwrre>-dWWOnDampWpauDdudTPkRRs57@Y!DdjiQ(2|1fCeO%R5BQl z5{M9kGZ1VsAo(lDi?&*Ac27?h1WtuEuhdG=b=w#Esv8)_%S0WH6P1!v%maf^4h=K1 zV1iEJUXr6l3jzsMkuwP}Vedo1jNC3BmiaF^p`?JQ6Vi@JPScn(1B>DLjidt|mDtFL z=;5A>1r=gDhs1rcXcnr~YeyxgPUDJ^V!cud|Bf z&jW8{cu?lLD-1Bm{t>!JKvyot2iBRgix!Z=+%a9Fa`;wt9CKT@BI3v&a=^kWMMk3D zM|gpmTVtR^*+k0?a~Zt5{TNm~y6#^tkQXpxsPyd6YSzdh!gYath`Y33B*tC6PKVd3 gR55(hOiJ)*IdH6y2!ug{ocI41az!{$klo)%EkJW7f&c&j literal 0 HcmV?d00001 diff --git a/data-raw/internal/sssom_mapping_slots.rds b/data-raw/internal/sssom_mapping_slots.rds new file mode 100644 index 0000000000000000000000000000000000000000..623c38cd08431aea9aed2ab9a2de52274001a102 GIT binary patch literal 408 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 V!bB8)u%bWwUC9*TLPD`97)YX;=%4@q literal 0 HcmV?d00001 diff --git a/data-raw/sysdata-update.R b/data-raw/sysdata-update.R new file mode 100644 index 00000000..22aae504 --- /dev/null +++ b/data-raw/sysdata-update.R @@ -0,0 +1,34 @@ +rlang::check_installed("here", "usethis") + +indir <- here::here("data-raw", "internal") + + +# DO Google Sheets reference +.DO_gs <- readRDS(file.path(indir, "DO_gs.rds")) + + +# HTML tags reference +.html_tags <- readRDS(file.path(indir, "html_tags.rds")) + + +# curation template specification +.curation_opts <- readRDS(file.path(indir, "curation_opts.rds")) +.sparql_dt_motif <- readRDS(file.path(indir, "sparql_dt_motif.rds")) + +# SSSOM specification +.sssom_spec <- readRDS(file.path(indir, "sssom_spec.rds")) +.sssom_slot_types <- readRDS(file.path(indir, "sssom_slot_types.rds")) +.sssom_mapping_slots <- readRDS(file.path(indir, "sssom_mapping_slots.rds")) + + +usethis::use_data( + .DO_gs, + .html_tags, + .curation_opts, + .sparql_dt_motif, + .sssom_spec, + .sssom_slot_types, + .sssom_mapping_slots, + internal = TRUE, + overwrite = TRUE +) From 76c06a3fdec9e1f8259a34cbec3193e96cc36716 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Thu, 26 Feb 2026 15:44:22 -0500 Subject: [PATCH 28/33] Add internal sort_by_curation_dt() To sort obo_data according to .curation_opts schema in curation_template() --- R/curation.R | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/R/curation.R b/R/curation.R index 585b7812..aab81691 100644 --- a/R/curation.R +++ b/R/curation.R @@ -100,7 +100,10 @@ curation_template.obo_data <- function(.data, ss = NULL, sheet = NULL, ..., data_type = dplyr::coalesce( .sparql_dt_motif[.data$data_type], .data$data_type - ), + ) + ) |> + sort_by_curation_dt() |> + dplyr::mutate( id = dplyr::if_else(duplicated(.data$id), NA_character_, .data$id), # set default action for existing data action = "retain" @@ -121,7 +124,23 @@ curation_template.obo_data <- function(.data, ss = NULL, sheet = NULL, ..., # helpers -------------------------------------------------------------------- -# define expected columns for curation template (in order) +# Sort data_type values within each id group per .curation_opts ordering. +# data_type values not found in .curation_opts are placed at the end. +# The existing order of id groups is preserved (not sorted alphabetically). +sort_by_curation_dt <- function(.data) { + dt_order <- .curation_opts$data_type + .data |> + dplyr::mutate( + .grp = dplyr::consecutive_id(.data$id), + .dt_rank = match(.data$data_type, dt_order, nomatch = length(dt_order) + 1L) + ) |> + dplyr::arrange(.data$.grp, .data$.dt_rank) |> + dplyr::select(-c(".grp", ".dt_rank")) +} + +### define expected columns for curation template (in order) ### + +# full set of curations columns curation_cols <- c( "id", "data_type", "value", "action", "curation_notes", "links", "action_notes" From ec56223984eec90b74d9abc02b52fec5c40eba57 Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Thu, 26 Feb 2026 17:39:45 -0500 Subject: [PATCH 29/33] Add internal add_id_sep() To add a specified number of empty rows between different ID data --- R/curation.R | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/R/curation.R b/R/curation.R index aab81691..9334e0ba 100644 --- a/R/curation.R +++ b/R/curation.R @@ -51,9 +51,12 @@ curation_template.NULL <- function(.data = NULL, ss = NULL, sheet = NULL, ..., invisible(gs_info) } +#' @param n_id_sep The number of blank rows to insert between each `id` group +#' (default: `2`). +#' #' @export curation_template.obo_data <- function(.data, ss = NULL, sheet = NULL, ..., - n_max = 20) { + n_max = 20, n_id_sep = 2L) { cur_df <- .data |> # need smarter indexing... I think, not currently used (see below) dplyr::mutate( @@ -108,7 +111,8 @@ curation_template.obo_data <- function(.data, ss = NULL, sheet = NULL, ..., # set default action for existing data action = "retain" ) |> - append_empty_col(curation_cols, order = TRUE) + append_empty_col(curation_cols, order = TRUE) |> + add_id_sep(n = n_id_sep) class(cur_df) <- c("curation_template", class(cur_df)) @@ -124,6 +128,14 @@ curation_template.obo_data <- function(.data, ss = NULL, sheet = NULL, ..., # helpers -------------------------------------------------------------------- +# Insert n blank rows between each id group (identified by non-NA id values). +add_id_sep <- function(.data, n = 2L) { + grp_id <- cumsum(!is.na(.data$id)) + groups <- split(.data, grp_id) + blanks <- .data[rep(NA_integer_, n), ] + purrr::reduce(groups[-1], ~ dplyr::bind_rows(.x, blanks, .y), .init = groups[[1]]) +} + # Sort data_type values within each id group per .curation_opts ordering. # data_type values not found in .curation_opts are placed at the end. # The existing order of id groups is preserved (not sorted alphabetically). From 3836af2f1987517f7f231c1febd72d36d627922d Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Thu, 26 Feb 2026 17:51:23 -0500 Subject: [PATCH 30/33] Rename curation_template() n_max arg to id_max and implement --- R/curation.R | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/R/curation.R b/R/curation.R index 9334e0ba..f99ed6d4 100644 --- a/R/curation.R +++ b/R/curation.R @@ -51,13 +51,39 @@ curation_template.NULL <- function(.data = NULL, ss = NULL, sheet = NULL, ..., invisible(gs_info) } +#' @param id_max The maximum number of unique classes to include (default: `20`). #' @param n_id_sep The number of blank rows to insert between each `id` group #' (default: `2`). #' #' @export curation_template.obo_data <- function(.data, ss = NULL, sheet = NULL, ..., - n_max = 20, n_id_sep = 2L) { + id_max = 20, n_id_sep = 2L) { + if (!is.numeric(id_max) || length(id_max) != 1L || id_max < 1L) { + rlang::abort("`id_max` must be a single positive integer.") + } + all_ids <- unique(.data$id) + incl_ids <- utils::head(all_ids, id_max) + excl_ids <- all_ids[!all_ids %in% incl_ids] + if (length(excl_ids) > 0) { + max_show <- 10L + if (length(excl_ids) <= max_show) { + excl_txt <- paste(excl_ids, collapse = ", ") + } else { + excl_txt <- paste0( + paste(utils::head(excl_ids, max_show), collapse = ", "), + ", ... and ", length(excl_ids) - max_show, " more" + ) + } + rlang::inform( + paste0( + length(incl_ids), " of ", length(all_ids), + " unique IDs included (id_max = ", id_max, ").", + "\nExcluded: ", excl_txt + ) + ) + } cur_df <- .data |> + dplyr::filter(.data$id %in% incl_ids) |> # need smarter indexing... I think, not currently used (see below) dplyr::mutate( index = dplyr::dense_rank(paste0(.data$predicate, .data$value)), From d50c0bce0249a41de28fcf2f5e7e4bde9ce96a8e Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Sat, 28 Feb 2026 16:37:07 -0500 Subject: [PATCH 31/33] Add debug arg to curation_template.obo_data() --- R/curation.R | 82 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 11 deletions(-) diff --git a/R/curation.R b/R/curation.R index f99ed6d4..f0258249 100644 --- a/R/curation.R +++ b/R/curation.R @@ -54,10 +54,31 @@ curation_template.NULL <- function(.data = NULL, ss = NULL, sheet = NULL, ..., #' @param id_max The maximum number of unique classes to include (default: `20`). #' @param n_id_sep The number of blank rows to insert between each `id` group #' (default: `2`). +#' @param debug Controls debug output. `FALSE` (default) writes to Google Sheets +#' normally. One or more of: +#' * `"output"`: returns the final data frame visibly instead of writing to +#' Google Sheets. +#' * `"types"`: returns a list with `$matched` (named character vector where +#' names are the original predicate strings and values are the resolved +#' `data_type` labels, as mapped by `.sparql_dt_motif`) and `$unmatched` +#' (character vector of predicates not in `.sparql_dt_motif`, used as-is). +#' When combined with `"steps"`, the list is added as `$types` in that output. +#' Combine with `"output"` to also return the final data frame. +#' * `"steps"`: returns a named list of snapshots at each major pipeline step +#' (`filtered`, `pivoted`, `typed`, `output`); implies `"output"`. If `"types"` +#' is also requested, includes `$types` in the returned list. #' #' @export curation_template.obo_data <- function(.data, ss = NULL, sheet = NULL, ..., - id_max = 20, n_id_sep = 2L) { + id_max = 20, n_id_sep = 2L, + debug = FALSE) { + if (!isFALSE(debug)) { + debug <- match.arg( + debug, + choices = c("output", "types", "steps"), + several.ok = TRUE + ) + } if (!is.numeric(id_max) || length(id_max) != 1L || id_max < 1L) { rlang::abort("`id_max` must be a single positive integer.") } @@ -82,8 +103,13 @@ curation_template.obo_data <- function(.data, ss = NULL, sheet = NULL, ..., ) ) } - cur_df <- .data |> - dplyr::filter(.data$id %in% incl_ids) |> + + # Step 1: filter & reshape to long format with resolved predicate strings + step_filtered <- .data |> + dplyr::filter(.data$id %in% incl_ids) + + # Step 2: pivot to long format + step_pivoted <- step_filtered |> # need smarter indexing... I think, not currently used (see below) dplyr::mutate( index = dplyr::dense_rank(paste0(.data$predicate, .data$value)), @@ -123,14 +149,20 @@ curation_template.obo_data <- function(.data, ss = NULL, sheet = NULL, ..., dplyr::rename(data_type = "predicate", "curation_notes" = "extra") |> # for now, just remove index --> need to use for sorting at some point dplyr::select(-"index") |> - unique() |> + unique() + + # Step 3: resolve data_type values via .sparql_dt_motif + step_typed <- step_pivoted |> # collapse_col(value) |> # does nothing... probably don't want to collapse dplyr::mutate( data_type = dplyr::coalesce( .sparql_dt_motif[.data$data_type], .data$data_type ) - ) |> + ) + + # Step 4: sort, finalise, and add id separators + cur_df <- step_typed |> sort_by_curation_dt() |> dplyr::mutate( id = dplyr::if_else(duplicated(.data$id), NA_character_, .data$id), @@ -140,15 +172,43 @@ curation_template.obo_data <- function(.data, ss = NULL, sheet = NULL, ..., append_empty_col(curation_cols, order = TRUE) |> add_id_sep(n = n_id_sep) + class(cur_df) <- c("curation_template", class(cur_df)) - class(cur_df) <- c("curation_template", class(cur_df)) - if (is.null(sheet)) sheet <- paste0("curation-", format(Sys.Date(), "%Y%m%d")) - gs_info <- googlesheets4::write_sheet(cur_df, ss, sheet) + # debug paths: never write to Google Sheets + if (!isFALSE(debug)) { + types_info <- NULL + if ("types" %in% debug) { + raw_types <- unique(step_pivoted$data_type) + matched <- raw_types[raw_types %in% names(.sparql_dt_motif)] + unmatched <- raw_types[!raw_types %in% names(.sparql_dt_motif)] + # $matched: named vector of raw predicate -> resolved data_type + # $unmatched: raw predicates passed through as-is + types_info <- list( + matched = .sparql_dt_motif[matched], + unmatched = unmatched + ) + } + if ("steps" %in% debug) { + out <- list( + filtered = step_filtered, + pivoted = step_pivoted, + typed = step_typed, + output = cur_df + ) + if (!is.null(types_info)) out$types <- types_info + return(out) + } + if ("types" %in% debug) return(types_info) + return(cur_df) + } - if (is.null(ss)) ss <- gs_info - set_curation_validation(cur_df, ss, sheet) + if (is.null(sheet)) sheet <- paste0("curation-", format(Sys.Date(), "%Y%m%d")) + gs_info <- googlesheets4::write_sheet(cur_df, ss, sheet) - invisible(gs_info) + if (is.null(ss)) ss <- gs_info + set_curation_validation(cur_df, ss, sheet) + + invisible(gs_info) } From ba95297f89c258176c52de88b17e8c508381952d Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Sat, 28 Feb 2026 17:15:23 -0500 Subject: [PATCH 32/33] Move some curation_template.obo_data code to internal functions New internal functions: - filter_max_ids() - pivot_obo_to_curation() --- R/curation.R | 145 ++++++++++++++++++++++++++++----------------------- 1 file changed, 79 insertions(+), 66 deletions(-) diff --git a/R/curation.R b/R/curation.R index f0258249..c3d25b4c 100644 --- a/R/curation.R +++ b/R/curation.R @@ -79,6 +79,75 @@ curation_template.obo_data <- function(.data, ss = NULL, sheet = NULL, ..., several.ok = TRUE ) } + step_filtered <- filter_max_ids(.data, id_max) + step_pivoted <- pivot_obo_to_curation(step_filtered) + + # resolve data_type values via .sparql_dt_motif + step_typed <- step_pivoted |> + # collapse_col(value) |> # does nothing... probably don't want to collapse + dplyr::mutate( + data_type = dplyr::coalesce( + .sparql_dt_motif[.data$data_type], + .data$data_type + ) + ) + + # sort, finalise, and add id separators + cur_df <- step_typed |> + sort_by_curation_dt() |> + dplyr::mutate( + id = dplyr::if_else(duplicated(.data$id), NA_character_, .data$id), + # set default action for existing data + action = "retain" + ) |> + append_empty_col(curation_cols, order = TRUE) |> + add_id_sep(n = n_id_sep) + + class(cur_df) <- c("curation_template", class(cur_df)) + + # debug paths: never write to Google Sheets + if (!isFALSE(debug)) { + types_info <- NULL + if ("types" %in% debug) { + raw_types <- unique(step_pivoted$data_type) + matched <- raw_types[raw_types %in% names(.sparql_dt_motif)] + unmatched <- raw_types[!raw_types %in% names(.sparql_dt_motif)] + # $matched: names are original predicates, values are resolved data_types + # $unmatched: predicates not in .sparql_dt_motif, used as-is + types_info <- list( + matched = .sparql_dt_motif[matched], + unmatched = unmatched + ) + } + if ("steps" %in% debug) { + out <- list( + filtered = step_filtered, + pivoted = step_pivoted, + typed = step_typed, + output = cur_df + ) + if (!is.null(types_info)) out$types <- types_info + return(out) + } + if ("types" %in% debug) return(types_info) + return(cur_df) + } + + if (is.null(sheet)) sheet <- paste0("curation-", format(Sys.Date(), "%Y%m%d")) + gs_info <- googlesheets4::write_sheet(cur_df, ss, sheet) + + if (is.null(ss)) ss <- gs_info + set_curation_validation(cur_df, ss, sheet) + + invisible(gs_info) +} + + +# helpers -------------------------------------------------------------------- + +# Filter obo_data to the first id_max unique IDs; informs the user if any are +# excluded, listing up to 10 by name. +filter_max_ids <- function(.data, id_max) { if (!is.numeric(id_max) || length(id_max) != 1L || id_max < 1L) { rlang::abort("`id_max` must be a single positive integer.") } @@ -103,13 +172,16 @@ curation_template.obo_data <- function(.data, ss = NULL, sheet = NULL, ..., ) ) } + dplyr::filter(.data, .data$id %in% incl_ids) +} - # Step 1: filter & reshape to long format with resolved predicate strings - step_filtered <- .data |> - dplyr::filter(.data$id %in% incl_ids) - # Step 2: pivot to long format - step_pivoted <- step_filtered |> +# Pivot obo_data to the long curation format: resolves compound predicate +# strings (handling oboInOwl:hasSynonymType annotations specially), pivots +# axiom columns into rows, arranges, renames to data_type/curation_notes, +# and deduplicates. +pivot_obo_to_curation <- function(.data) { + .data |> # need smarter indexing... I think, not currently used (see below) dplyr::mutate( index = dplyr::dense_rank(paste0(.data$predicate, .data$value)), @@ -150,70 +222,9 @@ curation_template.obo_data <- function(.data, ss = NULL, sheet = NULL, ..., # for now, just remove index --> need to use for sorting at some point dplyr::select(-"index") |> unique() - - # Step 3: resolve data_type values via .sparql_dt_motif - step_typed <- step_pivoted |> - # collapse_col(value) |> # does nothing... probably don't want to collapse - dplyr::mutate( - data_type = dplyr::coalesce( - .sparql_dt_motif[.data$data_type], - .data$data_type - ) - ) - - # Step 4: sort, finalise, and add id separators - cur_df <- step_typed |> - sort_by_curation_dt() |> - dplyr::mutate( - id = dplyr::if_else(duplicated(.data$id), NA_character_, .data$id), - # set default action for existing data - action = "retain" - ) |> - append_empty_col(curation_cols, order = TRUE) |> - add_id_sep(n = n_id_sep) - - class(cur_df) <- c("curation_template", class(cur_df)) - - # debug paths: never write to Google Sheets - if (!isFALSE(debug)) { - types_info <- NULL - if ("types" %in% debug) { - raw_types <- unique(step_pivoted$data_type) - matched <- raw_types[raw_types %in% names(.sparql_dt_motif)] - unmatched <- raw_types[!raw_types %in% names(.sparql_dt_motif)] - # $matched: named vector of raw predicate -> resolved data_type - # $unmatched: raw predicates passed through as-is - types_info <- list( - matched = .sparql_dt_motif[matched], - unmatched = unmatched - ) - } - if ("steps" %in% debug) { - out <- list( - filtered = step_filtered, - pivoted = step_pivoted, - typed = step_typed, - output = cur_df - ) - if (!is.null(types_info)) out$types <- types_info - return(out) - } - if ("types" %in% debug) return(types_info) - return(cur_df) - } - - if (is.null(sheet)) sheet <- paste0("curation-", format(Sys.Date(), "%Y%m%d")) - gs_info <- googlesheets4::write_sheet(cur_df, ss, sheet) - - if (is.null(ss)) ss <- gs_info - set_curation_validation(cur_df, ss, sheet) - - invisible(gs_info) } -# helpers -------------------------------------------------------------------- - # Insert n blank rows between each id group (identified by non-NA id values). add_id_sep <- function(.data, n = 2L) { grp_id <- cumsum(!is.na(.data$id)) @@ -222,6 +233,7 @@ add_id_sep <- function(.data, n = 2L) { purrr::reduce(groups[-1], ~ dplyr::bind_rows(.x, blanks, .y), .init = groups[[1]]) } + # Sort data_type values within each id group per .curation_opts ordering. # data_type values not found in .curation_opts are placed at the end. # The existing order of id groups is preserved (not sorted alphabetically). @@ -236,6 +248,7 @@ sort_by_curation_dt <- function(.data) { dplyr::select(-c(".grp", ".dt_rank")) } + ### define expected columns for curation template (in order) ### # full set of curations columns From 4f6674947b39e06a64794dd7c71d32f045e6e39c Mon Sep 17 00:00:00 2001 From: "J. Allen Baron" Date: Sat, 28 Feb 2026 17:17:00 -0500 Subject: [PATCH 33/33] Render curation_template.obo_data documentation --- R/curation.R | 1 + man/curation_template.Rd | 48 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/R/curation.R b/R/curation.R index c3d25b4c..a0fe4fce 100644 --- a/R/curation.R +++ b/R/curation.R @@ -69,6 +69,7 @@ curation_template.NULL <- function(.data = NULL, ss = NULL, sheet = NULL, ..., #' is also requested, includes `$types` in the returned list. #' #' @export +#' @rdname curation_template curation_template.obo_data <- function(.data, ss = NULL, sheet = NULL, ..., id_max = 20, n_id_sep = 2L, debug = FALSE) { diff --git a/man/curation_template.Rd b/man/curation_template.Rd index 7e33ecab..1c85fccf 100644 --- a/man/curation_template.Rd +++ b/man/curation_template.Rd @@ -3,11 +3,22 @@ \name{curation_template} \alias{curation_template} \alias{curation_template.NULL} +\alias{curation_template.obo_data} \title{Create a Curation Template} \usage{ curation_template(.data = NULL, ss = NULL, sheet = NULL, ...) \method{curation_template}{`NULL`}(.data = NULL, ss = NULL, sheet = NULL, ..., nrow = 50) + +\method{curation_template}{obo_data}( + .data, + ss = NULL, + sheet = NULL, + ..., + id_max = 20, + n_id_sep = 2L, + debug = FALSE +) } \arguments{ \item{.data}{Data to add to the curation sheet. If \code{NULL} (default), an empty @@ -33,6 +44,27 @@ as "\%Y\%m\%d"; see \code{\link[=format.Date]{format.Date()}}).} \item{nrow}{The number of rows to create in the curation template when \code{.data = NULL} (default: \code{50}).} + +\item{id_max}{The maximum number of unique classes to include (default: \code{20}).} + +\item{n_id_sep}{The number of blank rows to insert between each \code{id} group +(default: \code{2}).} + +\item{debug}{Controls debug output. \code{FALSE} (default) writes to Google Sheets +normally. One or more of: +\itemize{ +\item \code{"output"}: returns the final data frame visibly instead of writing to +Google Sheets. +\item \code{"types"}: returns a list with \verb{$matched} (named character vector where +names are the original predicate strings and values are the resolved +\code{data_type} labels, as mapped by \code{.sparql_dt_motif}) and \verb{$unmatched} +(character vector of predicates not in \code{.sparql_dt_motif}, used as-is). +When combined with \code{"steps"}, the list is added as \verb{$types} in that output. +Combine with \code{"output"} to also return the final data frame. +\item \code{"steps"}: returns a named list of snapshots at each major pipeline step +(\code{filtered}, \code{pivoted}, \code{typed}, \code{output}); implies \code{"output"}. If \code{"types"} +is also requested, includes \verb{$types} in the returned list. +}} } \value{ The Google Sheet info (\code{ss}), as a \link[googlesheets4:sheets_id]{googlesheets4::sheets_id}. @@ -40,3 +72,19 @@ The Google Sheet info (\code{ss}), as a \link[googlesheets4:sheets_id]{googleshe \description{ Create a curation template in a Google Sheet, optionally including data. } +\section{Formatting Limitations}{ + +Formatting to make data more visually distinct is not currently supported due +to limitations of the Google Sheets API and the \code{googlesheets4} package: +\itemize{ +\item Google Sheets API does not support assigning colors to data validation. +\item \code{googlesheets4} does not support any formatting. +} + +An alternative approach to support some formatting could be to create a +functional template with the desired formatting and copy that template with +\code{\link[googlesheets4:sheet_copy]{googlesheets4::sheet_copy()}}. The \code{data_type} could still be populated by +this function (only needed to support types not in +\code{.curation_opts$data_type}). +} +