Skip to content

Commit 6861696

Browse files
authored
Merge pull request #463 from stitam/pubchem
Retrieve more data from PubChem web pages
2 parents 507d0d5 + 0fbe44f commit 6861696

11 files changed

Lines changed: 1508 additions & 247 deletions

File tree

DESCRIPTION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Imports:
4343
data.tree,
4444
RSQLite,
4545
tibble,
46+
tidyr,
4647
base64enc,
4748
rlang,
4849
utils

NAMESPACE

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ import(stringr)
9595
import(tibble)
9696
import(xml2)
9797
importFrom(data.tree,Do)
98-
importFrom(data.tree,FindNode)
9998
importFrom(data.tree,as.Node)
10099
importFrom(dplyr,bind_rows)
101100
importFrom(dplyr,everything)

NEWS.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,24 @@
22

33
## NEW FEATURES
44

5-
### NEW FEATURES
6-
75
* `webchem` can now access FooDB (https://foodb.ca/), a database of food constituents, chemistry and biology, implemented in offline mode.
86
* Added `db_download_foodb()` to download the database, `foodb_list_compounds()` to list all compounds, and `foodb_convert()` to convert between IDs and `foodb_query()` to query the database.
7+
* `pc_sect()` can now retrieve data from previously inaccessible PubChem sections including spectral data and data that come from separate subsystems.
98

10-
### OFFLINE ACCESS
9+
## OFFLINE ACCESS
1110

1211
* `chembl_query()` can now perform both online queries (`mode = "ws"`, default) and offline retrievals (`mode = "offline"`) from a local ChEMBL database. Offline mode currently supports the following resources: `activity`, `assay`, `atc_class`, `binding_site`, `biotherapeutic`, `cell_line`, `chembl_id_lookup`, `compound_record`, `document`, `drug`,`drug_indication`, `drug_warning`, `go_slim`, `molecule`.
1312
* Results for online and offline queries are identical for most resources. If there are differences, the offline version throws informative warnings.
1413
* Added a new function `db_download_chembl()` for downloading ChEMBL for fully offline access.
1514

16-
### OTHER
15+
## OTHER
1716

1817
* Added a new function `chembl_status()` which returns status information about the ChEMBL webservice (database version, release date, and entity counts).
1918
* Added a new function `chembl_atc_classes()` to retrieve all available ATC classifications from ChEMBL.
2019
* `chembl_query()` now works with the "similarity" resource (note: currently limited to 20 results).
2120
* `bcpc_query()` now also looks for derivatives (esters and salts) of active compounds.
2221
* Added a new function `chembl_img()` for downloading SVG images from ChEMBL.
22+
* Added a new vignette about retrieving data from PubChem pages.
2323

2424
## MINOR IMPROVEMENTS
2525

R/pubchem.R

Lines changed: 0 additions & 228 deletions
Original file line numberDiff line numberDiff line change
@@ -608,231 +608,3 @@ pc_synonyms <- function(query,
608608
out <- unlist(out)
609609
return(out)
610610
}
611-
612-
#' Retrieve data from PubChem content pages
613-
#'
614-
#' When you search for an entity at \url{https://pubchem.ncbi.nlm.nih.gov/},
615-
#' e.g. a compound or a substance, and select the record you are interested in,
616-
#' you will be forwarded to a PubChem content page. When you look at a PubChem
617-
#' content page, you can see that chemical information is organised into
618-
#' sections, subsections, etc. The chemical data live at the lowest levels of
619-
#' these sections. Use this function to retrieve the lowest level information
620-
#' from PubChem content pages.
621-
#' @param id numeric or character; a vector of PubChem identifiers to search
622-
#' for.
623-
#' @param section character; the section of the content page to be imported.
624-
#' @param domain character; the query domain. Can be one of \code{"compound"},
625-
#' \code{"substance"}, \code{"assay"}, \code{"gene"}, \code{"protein"} or
626-
#' \code{"patent"}.
627-
#' @param verbose logical; should a verbose output be printed on the console?
628-
#' @return Returns a tibble of query results. In the returned tibble,
629-
#' \code{SourceName} is the name of the depositor, and \code{SourceID} is the
630-
#' ID of the search term within the depositor's database. You can browse
631-
#' \url{https://pubchem.ncbi.nlm.nih.gov/sources/} for more information about
632-
#' the depositors.
633-
#' @details \code{section} is not case sensitive but it is sensitive to typing
634-
#' errors and it requires the full name of the section as it is printed on the
635-
#' content page. The PubChem Table of Contents Tree can also be found at
636-
#' \url{https://pubchem.ncbi.nlm.nih.gov/classification/#hid=72}.
637-
#' @note Please respect the Terms and Conditions of the National Library of
638-
#' Medicine, \url{https://www.nlm.nih.gov/databases/download.html} the data
639-
#' usage policies of National Center for Biotechnology Information,
640-
#' \url{https://www.ncbi.nlm.nih.gov/home/about/policies/},
641-
#' \url{https://pubchem.ncbi.nlm.nih.gov/docs/programmatic-access}, and the data
642-
#' usage policies of the individual data sources
643-
#' \url{https://pubchem.ncbi.nlm.nih.gov/sources/}.
644-
#' @references Kim, S., Thiessen, P.A., Cheng, T. et al. PUG-View: programmatic
645-
#' access to chemical annotations integrated in PubChem. J Cheminform 11, 56
646-
#' (2019). \doi{10.1186/s13321-019-0375-2}.
647-
#' @seealso \code{\link{get_cid}}, \code{\link{pc_prop}}
648-
#' @examples
649-
#' # might fail if API is not available
650-
#' \dontrun{
651-
#' pc_sect(176, "Dissociation Constants")
652-
#' pc_sect(c(176, 311), "density")
653-
#' pc_sect(2231, "depositor-supplied synonyms", "substance")
654-
#' pc_sect(780286, "modify date", "assay")
655-
#' pc_sect(9023, "Ensembl ID", "gene")
656-
#' pc_sect("1ZHY_A", "Sequence", "protein")
657-
#' }
658-
#' @export
659-
pc_sect <- function(id,
660-
section,
661-
domain = c("compound", "substance", "assay", "gene",
662-
"protein", "patent"),
663-
verbose = getOption("verbose")) {
664-
domain <- match.arg(domain)
665-
section <- tolower(gsub(" +", "+", section))
666-
if (section %in% c("standard non-polar",
667-
"Semi-standard non-polar",
668-
"Standard polar")) {
669-
stop("use nist_ri() to obtain more information on this.")
670-
}
671-
res <- pc_page(id, section, domain, verbose)
672-
out <- pc_extract(res, section)
673-
return(out)
674-
}
675-
676-
#' Import PubChem content pages
677-
#'
678-
#' @importFrom jsonlite fromJSON
679-
#' @importFrom data.tree as.Node Do
680-
#' @param id numeric or character; a vector of identifiers to search for.
681-
#' @param section character; the section of the content page to be imported.
682-
#' @param domain character; the query domain. Can be one of \code{"compound"},
683-
#' \code{"substance"}, \code{"assay"}, \code{"gene"}, \code{"protein"} or
684-
#' \code{"patent"}.
685-
#' @return A named list of content pages where each element is either a
686-
#' data.tree or NA.
687-
#' @details \code{section} can be any section of a PubChem content page, e.g.
688-
#' \code{section = "solubility"} will import the section on solubility, or
689-
#' \code{section = "experimental properties"} will import all experimental
690-
#' properties. The \code{section} argument is not case sensitive but it
691-
#' is sensitive to typing errors and it requires the full name of the section as
692-
#' it is printed on the content page. The PubChem Table of Contents Tree can
693-
#' also be found at
694-
#' \url{https://pubchem.ncbi.nlm.nih.gov/classification/#hid=72}.
695-
#' @references Kim, S., Thiessen, P.A., Cheng, T. et al. PUG-View: programmatic
696-
#' access to chemical annotations integrated in PubChem. J Cheminform 11, 56
697-
#' (2019). \doi{10.1186/s13321-019-0375-2}.
698-
#' @examples
699-
#' # might fail if API is not available
700-
#' \dontrun{
701-
#' pc_page(c(176, 311), "Dissociation Constants")
702-
#' pc_page(49854366, "external id", domain = "substance")
703-
#' }
704-
#' @noRd
705-
pc_page <- function(id,
706-
section,
707-
domain = c("compound", "substance", "assay", "gene",
708-
"protein", "patent"),
709-
verbose = getOption("verbose")) {
710-
711-
if (!ping_service("pc")) stop(webchem_message("service_down"))
712-
713-
domain <- match.arg(domain)
714-
section <- tolower(gsub(" +", "+", section))
715-
foo <- function(id, section, domain) {
716-
if (is.na(id)) {
717-
if (verbose) webchem_message("na")
718-
return(NA)
719-
}
720-
qurl <- paste0("https://pubchem.ncbi.nlm.nih.gov/rest/pug_view/data/",
721-
domain, "/", id, "/JSON?heading=", section)
722-
if (verbose) webchem_message("query", id, appendLF = FALSE)
723-
webchem_sleep(type = 'API')
724-
res <- try(httr::RETRY("GET",
725-
qurl,
726-
user_agent(webchem_url()),
727-
terminate_on = 404,
728-
quiet = TRUE), silent = TRUE)
729-
if (inherits(res, "try-error")) {
730-
if (verbose) webchem_message("service_down")
731-
return(NA)
732-
}
733-
if (verbose) message(httr::message_for_status(res))
734-
if (res$status_code == 200) {
735-
cont <- httr::content(res, type = "text", encoding = "UTF-8")
736-
# Intercepting any NA cont before it gets to fromJSON.
737-
if(is.na(cont)) {
738-
return(NA)
739-
}
740-
cont <- jsonlite::fromJSON(cont, simplifyDataFrame = FALSE)
741-
tree <- data.tree::as.Node(cont, nameName = "TOCHeading")
742-
tree$Do(function(node) node$name <- tolower(node$name))
743-
return(tree)
744-
}
745-
else {
746-
return(NA)
747-
}
748-
}
749-
cont <- lapply(id, function(x) foo(x, section, domain))
750-
names(cont) <- id
751-
attr(cont, "domain") <- domain
752-
attr(cont, "id") <- switch(domain, compound = "CID", substance = "SID",
753-
assay = "AID", gene = "GeneID", protein = "pdbID",
754-
patent = "PatentID")
755-
return(cont)
756-
}
757-
758-
#' Extract data from PubChem content pages
759-
#'
760-
#' This function takes a list of PubChem content pages, and extracts the
761-
#' required information from them.
762-
#' @importFrom data.tree FindNode
763-
#' @importFrom dplyr bind_rows
764-
#' @importFrom tibble as_tibble
765-
#' @param pages list; a list of PubChem content pages.
766-
#' @param section character; the lowest level section of the data to be
767-
#' accessed.
768-
#' @return A tibble of chemical information with references.
769-
#' @details When you look at a PubChem content page, you can see that chemical
770-
#' information is organised into sections, subsections, etc. The chemical data
771-
#' live at the lowest levels of these sections. Use this function to extract the
772-
#' lowest level information from PubChem content pages, e.g. IUPAC Name, Boiling
773-
#' Point, Lower Explosive Limit (LEL).
774-
#' @details The \code{section} argument is not case sensitive, but it is
775-
#' sensitive to typing errors, and requires the full name of the section as it
776-
#' is printed on the content page. The PubChem Table of Contents Tree can also
777-
#' be found at \url{https://pubchem.ncbi.nlm.nih.gov/classification/#hid=72}.
778-
#' @references Kim, S., Thiessen, P.A., Cheng, T. et al. PUG-View: programmatic
779-
#' access to chemical annotations integrated in PubChem. J Cheminform 11, 56
780-
#' (2019). \doi{10.1186/s13321-019-0375-2}.
781-
#' @examples
782-
#' # might fail if API is not available
783-
#' \dontrun{
784-
#' comps <- pc_page(c(176, 311), "Dissociation Constants")
785-
#' pc_extract(comps, "Dissociation Constants")
786-
#' subs <- pc_page(49854366, "external id", domain = "substance")
787-
#' pc_extract(subs, "external id")
788-
#' }
789-
#' @noRd
790-
pc_extract <- function(page, section) {
791-
section <- tolower(section)
792-
ids <- names(page)
793-
foo <- function(i, section) {
794-
tree <- page[[i]]
795-
if (length(tree) == 1 && is.na(tree)) return(tibble(ID = ids[i]))
796-
node <- FindNode(tree, "information")
797-
if (is.null(node)) return(tibble(ID = ids[i],
798-
Name = tree$record$RecordTitle))
799-
info <- lapply(node, function(y) {
800-
lownode <- data.tree::FindNode(data.tree::as.Node(y), "stringwithmarkup")
801-
if (is.null(lownode)) {
802-
info <- tibble(Result = paste(y$value, collapse = " "),
803-
ReferenceNumber = y$ReferenceNumber)
804-
return(info)
805-
}
806-
else{
807-
string <- sapply(lownode, function(z) z$String)
808-
info <- tibble(Result = string,
809-
ReferenceNumber = y$ReferenceNumber)
810-
}
811-
})
812-
info <- dplyr::bind_rows(info)
813-
info <- tibble(ID = ids[i],
814-
Name = tree$record$RecordTitle,
815-
info)
816-
node <- FindNode(tree, "reference")
817-
if (is.null(node)) return(tibble(info, SourceName = NA, SourceID = NA))
818-
ref <- lapply(node, function(y) {
819-
ref <- tibble(ReferenceNumber = y$ReferenceNumber,
820-
SourceName = y$SourceName,
821-
SourceID = y$SourceID)
822-
return(ref)
823-
})
824-
ref <- dplyr::bind_rows(ref)
825-
info$SourceName <- sapply(info$ReferenceNumber, function(x) {
826-
ref$SourceName[ref$ReferenceNumber == x]
827-
})
828-
info$SourceID <- sapply(info$ReferenceNumber, function(x) {
829-
ref$SourceID[ref$ReferenceNumber == x]
830-
})
831-
return(info)
832-
}
833-
info <- lapply(seq_along(page), function(x) foo(x, section))
834-
info <- dplyr::bind_rows(info)
835-
info <- info[, -which(names(info) == "ReferenceNumber")]
836-
names(info)[1] <- attr(page, "id")
837-
return(info)
838-
}

0 commit comments

Comments
 (0)