Skip to content

Commit 281ac1c

Browse files
authored
Merge pull request #450 from stitam/resources
ChEMBL offline resources
2 parents 9a1ae40 + f5c4b45 commit 281ac1c

9 files changed

Lines changed: 1380 additions & 449 deletions

File tree

NEWS.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,29 @@
22

33
## NEW FEATURES
44

5-
* `chembl_query()` can now perform both online queries (mode = "ws", default) and offline retrievals (mode = "offline"). Note, offline functionality is currently very limited.
6-
* Added a new function `chembl_status()` which returns status information about the ChEMBL webservice.
7-
* `chembl_query()` now works with the "similarity" resource as well.
5+
### OFFLINE ACCESS
6+
7+
* `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: `atc_class`, `binding_site`, `biotherapeutic`, `cell_line`, `chembl_id_lookup`, `compound_record`, and `document`.
8+
* Added a new function `db_download_chembl()` for downloading ChEMBL for fully offline access.
9+
10+
### OTHER
11+
12+
* Added a new function `chembl_status()` which returns status information about the ChEMBL webservice (database version, release date, and entity counts).
13+
* Added a new function `chembl_atc_classes()` to retrieve all available ATC classifications from ChEMBL.
14+
* `chembl_query()` now works with the "similarity" resource (note: currently limited to 20 results).
815
* `bcpc_query()` now also looks for derivatives (esters and salts) of active compounds.
916
* Added a new function `chembl_img()` for downloading SVG images from ChEMBL.
10-
* Added a new function `db_download_chembl()` for downloading ChEMBL for fully offline access.
1117

1218
## MINOR IMPROVEMENTS
1319

14-
* `chembl_query()` now returns a named list and uses better formatting for nested output.
15-
* Added new argument `tidy = TRUE` to `chembl_query()` so we can now control whether we want to try to convert the output to a flat format.
20+
* `chembl_query()` now returns a named list with improved formatting for nested output.
21+
* Added new argument `output` to `chembl_query()` (values: "raw" or "tidy") to control output format. Raw format returns the full nested structure; tidy format attempts to flatten the results.
22+
* Added new `options` argument to `chembl_query()` for passing resource- and mode-specific options (cache file name, similarity threshold, database version, etc.).
23+
* `chembl_query()` can now replace NULL values with typed NA values (`NA_character_`, `NA_integer_`, `NA_real_`) based on the field schema when `replace_nulls = TRUE` in options.
1624

1725
## BUG FIXES
1826

19-
* `chembl_query()` did not work with the "compound_structural_alerts" resource. This has been fixed.
27+
* `chembl_query()` did not work with the "compound_structural_alert" resource. This has been fixed.
2028

2129
# webchem 1.3.1
2230

R/chembl.R

Lines changed: 98 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,24 @@
22
#'
33
#' Use this to group resource or mode specific options and pass them via the
44
#' `options` argument to `chembl_query()`.
5+
#' @param replace_nulls logical; if TRUE, replaces JSON NULL values with typed
6+
#' NA values (`NA_character_`, `NA_integer_`, `NA_real_`) based on the field's
7+
#' schema type.
58
#' @param cache_file character or NULL
69
#' @param similarity numeric
7-
#' @param tidy logical
810
#' @param version character
911
#' @return A list with class 'chembl_options'.
1012
#' @noRd
1113
chembl_options <- function(
14+
replace_nulls = TRUE,
1215
cache_file = NULL,
1316
similarity = 70,
14-
tidy = TRUE,
1517
version = "latest"
1618
) {
1719
options <- list(
20+
replace_nulls = replace_nulls,
1821
cache_file = cache_file,
1922
similarity = similarity,
20-
tidy = tidy,
2123
version = version
2224
)
2325
class(options) <- "chembl_options"
@@ -221,14 +223,15 @@ chembl_files <- function(version = "latest") {
221223
#' @param mode character; either "ws" (default) to use the webservice or
222224
#' "offline" to use a local ChEMBL database. Note, to use the "offline" mode,
223225
#' you need to have a local ChEMBL database. See [db_download_chembl()].
226+
#' @param output character; either "raw" (default) to return the raw results
227+
#' which is a list of lists, or "tidy" to return simplified results, if
228+
#' possible.
224229
#' @param options function; returns a named list for resource- and mode-specific
225230
#' options. Supported entries:
226231
#' - cache_file: character or NULL; name of the cache file (without extension)
227232
#' used when mode = "ws". If NULL (default), results are not cached.
228233
#' - similarity: numeric; similarity threshold for similarity searches
229234
#' (default 70).
230-
#' - tidy: logical; attempt to convert output to a simpler structure
231-
#' (default TRUE).
232235
#' - version: character; database version to use in "offline" mode (default
233236
#' "latest").
234237
#' @param verbose logical; should a verbose output be printed on the console?
@@ -371,16 +374,18 @@ chembl_query <- function(
371374
query,
372375
resource = "molecule",
373376
mode = "ws",
377+
output = "raw",
374378
verbose = getOption("verbose"),
375379
options = chembl_options(
380+
replace_nulls = TRUE,
376381
cache_file = NULL,
377382
similarity = 70,
378-
tidy = TRUE,
379383
version = "latest"
380384
),
381385
...
382386
) {
383387
resource <- match.arg(resource, chembl_resources())
388+
output <- match.arg(output, choices = c("raw", "tidy"))
384389
if (resource == "image") {
385390
stop("To download images, please use chembl_img().")
386391
}
@@ -393,9 +398,10 @@ chembl_query <- function(
393398
query = query,
394399
resource = resource,
395400
verbose = verbose,
401+
replace_nulls = options$replace_nulls,
396402
cache_file = options$cache_file,
397403
similarity = options$similarity,
398-
tidy = options$tidy,
404+
output = output,
399405
...
400406
)
401407
} else {
@@ -404,7 +410,8 @@ chembl_query <- function(
404410
resource = resource,
405411
verbose = verbose,
406412
similarity = options$similarity,
407-
version = options$version
413+
version = options$version,
414+
output = output
408415
)
409416
}
410417
}
@@ -420,17 +427,27 @@ chembl_query_ws <- function(
420427
query,
421428
resource = "molecule",
422429
verbose = getOption("verbose"),
430+
replace_nulls = TRUE,
423431
cache_file = NULL,
424432
similarity = 70,
425-
tidy = TRUE,
433+
output = "raw",
426434
...
427435
) {
428436
if (resource == "similarity") {
429437
warning("Similarity search currently returns no more than 20 results.")
430438
}
431439
stem <- "https://www.ebi.ac.uk/chembl/api/data"
432440
opts <- list(...)
433-
foo <- function(query, verbose, similarity) {
441+
if (replace_nulls) {
442+
if (verbose) message("Retrieving schema to replace NULLs.")
443+
# TODO retrieve once per session and cache
444+
schema <- jsonlite::fromJSON(paste0(
445+
"https://www.ebi.ac.uk/chembl/api/data/", resource, "/schema.json"
446+
))
447+
} else {
448+
schema <- NULL
449+
}
450+
foo <- function(query, verbose, similarity, schema) {
434451
if (is.na(query)) {
435452
if (verbose) webchem_message("na")
436453
return(NA)
@@ -464,14 +481,15 @@ chembl_query_ws <- function(
464481
}
465482
if (verbose) message(httr::message_for_status(res))
466483
cont <- httr::content(res, type = "application/json")
467-
if (tidy) {
484+
if (replace_nulls) cont <- replace_nulls(cont, schema)
485+
if (output == "tidy") {
468486
cont <- format_chembl(cont)
469487
}
470488
return(cont)
471489
}
472490
if (is.null(cache_file)) {
473491
out <- lapply(query, function(x) {
474-
foo(x, verbose = verbose, similarity = similarity)
492+
foo(x, verbose = verbose, similarity = similarity, schema = schema)
475493
})
476494
} else {
477495
if (!dir.exists("cache")) dir.create("cache")
@@ -487,7 +505,7 @@ chembl_query_ws <- function(
487505
if (verbose) message("Already retrieved.")
488506
return(query_results[[x]])
489507
} else {
490-
new <- foo(x, verbose = verbose, similarity = similarity)
508+
new <- foo(x, verbose = verbose, similarity = similarity, schema = schema)
491509
if (!is.na(x)) {
492510
query_results[[x]] <<- new
493511
saveRDS(query_results, file = cfpath)
@@ -612,7 +630,7 @@ chembl_validate_query <- function(query, resource, verbose) {
612630
if (resource %in% c(
613631
"activity",
614632
"binding_site",
615-
"compund_record",
633+
"compound_record",
616634
"drug_indication",
617635
"drug_warning",
618636
"mechanism",
@@ -868,7 +886,7 @@ format_chembl <- function(cont) {
868886
validate_chembl_version <- function(version = "latest") {
869887
assert(version, "character")
870888
stopifnot(length(version) == 1)
871-
if (version == "latest") version <- "35"
889+
if (version == "latest") version <- "36"
872890
version_num <- suppressWarnings(as.numeric(version))
873891
version_base <- as.character(floor(version_num))
874892
if (is.na(version_num)) {
@@ -1001,7 +1019,7 @@ chembl_example_query <- function(resource) {
10011019
assay = c("CHEMBL615117", "CHEMBL1061852", "CHEMBL5445082", "CHEMBL5441382", "CHEMBL2184458"),
10021020
atc_class = "A01AA01",
10031021
binding_site = "2",
1004-
biotherapeutic = c("CHEMBL8234","CHEMBL448105"),
1022+
biotherapeutic = c("CHEMBL8234", "CHEMBL448105", "CHEMBL441738"),
10051023
cell_line = c("CHEMBL3307241", "CHEMBL3307242"),
10061024
chembl_id_lookup = "CHEMBL1",
10071025
compound_record = "1",
@@ -1033,3 +1051,67 @@ chembl_example_query <- function(resource) {
10331051
}
10341052
example_queries[[resource]]
10351053
}
1054+
1055+
#' Replace NULLs in ChEMBL webservice response
1056+
#'
1057+
#' Recursively replace NULL values in the ChEMBL webservice response with NA
1058+
#' values of the appropriate type based on the provided schema.
1059+
#' @param res list; the ChEMBL webservice response to process.
1060+
#' @param schema list; the schema for the ChEMBL resource.
1061+
#' @noRd
1062+
1063+
replace_nulls <- function(res, schema) {
1064+
# link schema types to NA types
1065+
get_na_type <- function(type) {
1066+
switch(
1067+
type,
1068+
"string" = NA_character_,
1069+
"integer" = NA_integer_,
1070+
"float" = NA_real_,
1071+
"number" = NA_real_,
1072+
"boolean" = NA,
1073+
"datetime" = NA_character_,
1074+
"related" = NA_character_,
1075+
NA_character_ # default
1076+
)
1077+
}
1078+
1079+
# If res is not a list, stop with an error
1080+
if (!is.list(res)) stop("ChEMBL raw output should be a list.")
1081+
1082+
# Internal recursive function with depth tracking
1083+
foo <- function(x, field_name, depth = 0) {
1084+
if (depth > 10) {
1085+
stop("Exceeded maximum recursion depth while replacing NULLs.")
1086+
}
1087+
# if x is NULL, replace with appropriate NA based on schema
1088+
if (is.null(x)) {
1089+
if (!is.null(field_name) &&
1090+
!is.null(schema$fields) &&
1091+
field_name %in% names(schema$fields)) {
1092+
field_schema <- schema$fields[[field_name]]
1093+
if (!is.null(field_schema$type)) {
1094+
return(get_na_type(field_schema$type))
1095+
} else {
1096+
return(NA_character_)
1097+
}
1098+
} else {
1099+
return(NA_character_)
1100+
}
1101+
}
1102+
# if x is a list, recursively apply foo to its elements
1103+
if (is.list(x)) {
1104+
for (i in seq_along(x)) {
1105+
x[[i]] <- foo(x[[i]], names(x)[i], depth + 1)
1106+
}
1107+
}
1108+
return(x)
1109+
}
1110+
1111+
# Apply foo to each element at the top level
1112+
result <- lapply(seq_along(res), function(i) {
1113+
foo(res[[i]], names(res)[i])
1114+
})
1115+
names(result) <- names(res)
1116+
return(result)
1117+
}

0 commit comments

Comments
 (0)