├── .DS_Store ├── .Rbuildignore ├── .gitignore ├── DESCRIPTION ├── NAMESPACE ├── R ├── .DS_Store ├── get_refs.R └── globals.R ├── README.md ├── citationchaser.Rproj ├── code-of-conduct.md ├── contributing.md ├── inst ├── .DS_Store ├── CITATION ├── extdata │ ├── .DS_Store │ ├── citationchaser.png │ ├── export.csv │ ├── import.csv │ └── model.png └── shiny-examples │ ├── .DS_Store │ └── citationchaser │ ├── .DS_Store │ ├── app.R │ ├── functions.R │ ├── mixed_id_wrapper.R │ ├── rsconnect │ └── shinyapps.io │ │ └── estech │ │ └── citationchaser.dcf │ └── www │ ├── .DS_Store │ ├── citation.ris │ ├── example.csv │ ├── example.png │ └── legendnew.png └── man ├── getLENSData.Rd ├── get_citation.Rd └── get_refs.Rd /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nealhaddaway/citationchaser/ba382a7336fbc61090b092e67db9ddecfcc4e8ca/.DS_Store -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE\.md$ 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: citationchaser 2 | Title: Perform Forward and Backwards Chasing in Evidence Syntheses 3 | Version: 0.0.4 4 | Authors@R: c( 5 | person(given = "Neal", 6 | family = "Haddaway", 7 | role = c("aut", "cre"), 8 | email = "nealhaddaway@gmail.com", 9 | comment = c(ORCID = "0000-0003-3902-2234")), 10 | person(given = "Matthew", family = "Grainger", role = "ctb"), 11 | person(given = "Charles", family = "Gray", role = "ctb")) 12 | Description: In searching for research articles, we often want to 13 | obtain lists of references from across studies, and also obtain lists 14 | of articles that cite a particular study. In systematic reviews, this 15 | supplementary search technique is known as 'citation chasing': forward 16 | citation chasing looks for all records citing one or more articles of 17 | known relevance; backward citation chasing looks for all records 18 | referenced in one or more articles. Traditionally, this process would 19 | be done manually, and the resulting records would need to be checked 20 | one-by-one against included studies in a review to identify potentially 21 | relevant records that should be included in a review. This package 22 | contains functions to automate this process by making use of the 23 | Lens.org API. An input article list can be used to return a list of 24 | all referenced records, and/or all citing records in the Lens.org 25 | database (consisting of PubMed, PubMed Central, CrossRef, Microsoft 26 | Academic Graph and CORE; ). 27 | Imports: 28 | dplyr, 29 | httr, 30 | jsonlite, 31 | maditr, 32 | MESS, 33 | networkD3, 34 | scales, 35 | tibble, 36 | utils, 37 | data.table 38 | Suggests: 39 | knitr, 40 | rmarkdown 41 | License: GPL (>= 3) 42 | Encoding: UTF-8 43 | Roxygen: list(markdown = TRUE) 44 | RoxygenNote: 7.1.1 45 | Depends: 46 | R (>= 2.10) 47 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(get_citation) 4 | export(get_refs) 5 | importFrom(MESS,cumsumbinning) 6 | importFrom(data.table,data.table) 7 | importFrom(dplyr,bind_rows) 8 | importFrom(dplyr,group_split) 9 | importFrom(dplyr,mutate) 10 | importFrom(httr,POST) 11 | importFrom(httr,add_headers) 12 | importFrom(httr,content) 13 | importFrom(jsonlite,fromJSON) 14 | importFrom(maditr,vlookup) 15 | importFrom(tibble,tibble) 16 | importFrom(utils,write.table) 17 | -------------------------------------------------------------------------------- /R/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nealhaddaway/citationchaser/ba382a7336fbc61090b092e67db9ddecfcc4e8ca/R/.DS_Store -------------------------------------------------------------------------------- /R/get_refs.R: -------------------------------------------------------------------------------- 1 | #' Automated citation chasing in systematic reviews 2 | #' 3 | #' @description This function takes a list of articles in the form of established 4 | #' identifiers (e.g. digital object identifiers) and sends a request to the 5 | #' lens.org API to firstly identify all cited references in all articles (in the 6 | #' form of lists of lens IDs), and then query these lens IDs to bring back full 7 | #' citation information for all listed records. Deduplicates references to the 8 | #' same records across articles, resulting in an RIS file and a summary report 9 | #' in the console. 10 | #' @param article_list List of article identifiers for which the reference 11 | #' lists will be returned. Must be a list/vector of identifiers, e.g. 12 | #' '"10.1186/s13750-018-0126-2" "10.1002/jrsm.1378"'. 13 | #' @param type Specification of the type of input provided. The default is 14 | #' 'doi' (digital object identifier), but any of the following are accepted: 15 | #' "pmid" (PubMed ID), "pmcid" (PubMed Central ID), "magid" (Microsoft 16 | #' Academic ID), "coreid" (CORE identifier), lens_id" (The Lens.org ID), 17 | #' "title" (article title; much lower specificity). 18 | #' @param get_records Specification of whether to look for records referenced 19 | #' within the input articles ('references'), records citing the input articles 20 | #' ('citations'), or both ('both'). 21 | #' @param save_object Option to save the resultant ris file as an object in 22 | #' the Global Environment. The default is FALSE. 23 | #' @param token An access key for the lens.org API. Tokens can be obtained by 24 | #' applying for scholarly API access and creating a token once approved. See 25 | #' 'https://www.lens.org/lens/user/subscriptions#scholar' for further details. 26 | #' @return An RIS file is saved to the working directory. A report is printed 27 | #' to the console. If 'save_object=TRUE', the RIS file is returned as an 28 | #' object 29 | #' @importFrom maditr vlookup 30 | #' @importFrom httr content 31 | #' @importFrom jsonlite fromJSON 32 | #' @importFrom utils write.table 33 | #' @importFrom tibble tibble 34 | #' @importFrom dplyr mutate group_split bind_rows 35 | #' @importFrom MESS cumsumbinning 36 | #' @export 37 | #' @examples 38 | #' \dontrun{ 39 | #' article_list <- c("10.1007/978-3-642-37048-9_13", 40 | #' "10.1111/sum.12030", 41 | #' "10.5194/bg-13-3619-2016", 42 | #' "10.1016/j.agee.2012.09.006") 43 | #' token <- 'token' 44 | #' refs <- get_refs(article_list, get_records = 'references', token = token) 45 | #' refs 46 | #' } 47 | get_refs <- function(article_list, 48 | type = 'doi', 49 | get_records, 50 | save_object = FALSE, 51 | token) { 52 | 53 | # set the maximum number of results returned for each query 54 | max_results <- 500 55 | 56 | # if the number of input articles is 1, then split the input list where there are commas (and strip out whitespace) 57 | if(length(article_list) == 1){ 58 | article_list <- trimws(unlist(strsplit(article_list, '[,]'))) 59 | } 60 | print('Input record list:') 61 | print(article_list) 62 | 63 | ## input article search 64 | # build query for input article search 65 | request1 <- paste0('{\n\t"query": {\n\t\t"terms": {\n\t\t\t"',type,'": ["', paste0('', paste(article_list, collapse = '", "'), '"'),']\n\t\t}\n\t},\n\t"size":500\n}') 66 | print('Input article request:') 67 | print(request1) 68 | 69 | # perform article search and extract text results 70 | data <- getLENSData(token, request1) 71 | print('Input request executed.') 72 | 73 | # report requests remaining within the limit (currently 50/min) 74 | requests_remaining <- data[["headers"]][["x-rate-limit-remaining-request-per-minute"]] 75 | print(paste0('Remaining requests = ', requests_remaining)) 76 | 77 | # extract the JSON content of the response 78 | record_json <- httr::content(data, "text") 79 | 80 | # convert json output from article search to list 81 | record_list <- jsonlite::fromJSON(record_json) 82 | print('Results converted to df from JSON output:') 83 | print(record_list$results) 84 | 85 | # error messages 86 | if (data$status_code == 404){ 87 | print('Error 404: no matched records') 88 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 89 | } 90 | if (data$status_code == 429){ 91 | print('Error 429: system overloaded') 92 | return('Warning: Right now there are too many people using citationchaser. This has been logged and we will endeavour to increase the bandwith as soon as possible. Please try again later.') 93 | } 94 | if (record_list$total == 0){ 95 | print('Error 1: no matched records') 96 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 97 | } 98 | 99 | # report number of input articles returned 100 | input_number <- record_list[["total"]] 101 | 102 | # list input article lens IDs (for later use) 103 | articles_id <- record_list[["data"]][["lens_id"]] 104 | print('Returned records from input request:') 105 | print(articles_id) 106 | 107 | ### search for citations of input articles 108 | if (get_records == 'citations') { 109 | print('Seeking citations...') 110 | 111 | # citations per article 112 | citation_count <- record_list[["data"]][["scholarly_citations_count"]] 113 | citation_count[is.na(citation_count)] <- 0 114 | print('Citations per record sought:') 115 | print(citation_count) 116 | # sum of all citations 117 | all_citations <- sum(citation_count, na.rm = TRUE) 118 | print('Total citations sought:') 119 | print(all_citations) 120 | 121 | # return error message if input article(s) have no citations 122 | #if (record_list[["data"]][["scholarly_citations_count"]] == 0 || is.null(record_list[["data"]][["scholarly_citations_count"]]) == TRUE){ 123 | if (is.null(all_citations) == TRUE || identical(all_citations, 0) == TRUE){ 124 | print('Error 2: no matched citations') 125 | return('Warning: Your input articles have no recorded citations in the Lens.org database') 126 | } 127 | 128 | ## group articles into chunks based on max number of results per search 129 | # list citing articles per input article 130 | cit_by_art <- record_list[["data"]][["scholarly_citations"]] 131 | citations <- unlist(record_list[["data"]][["scholarly_citations"]]) 132 | print('Citations record list: ') 133 | print(citations) 134 | # remove duplicates across articles 135 | citations_unique <- unique(citations) 136 | print('Deduplicated citations record list:') 137 | print(citations_unique) 138 | # set up dataframe for groups of articles to search 139 | cit_counts_df <- tibble::tibble(articles_id, 140 | citation_count, 141 | cit_by_art) 142 | # remove records with no citations 143 | cit_counts_df[is.na(cit_counts_df)] <- 0 144 | cit_counts_df <- cit_counts_df[cit_counts_df$citation_count!=0,] 145 | # subset of records that indidivually have more citations than the max number of results per query 146 | single <- subset(cit_counts_df, citation_count >= max_results) 147 | single$export_group <- 0 148 | # subset of records that have fewer citations than the max number of results per query 149 | rest <- subset(cit_counts_df, citation_count < max_results) 150 | 151 | #the following code mistakenly bins around 500 (>500 records in some groups) 152 | #rest <- mutate(rest, 153 | # # divide by max citations allowed 154 | # export_group = ceiling(cumsum(citation_count) / max_results)) 155 | #fix: 156 | rest <- dplyr::mutate(rest, export_group = MESS::cumsumbinning(citation_count, 500)) 157 | 158 | # bind both dataframes back together 159 | cit_counts_df <- rbind(single, rest) 160 | print(cit_counts_df) 161 | 162 | # get a list of vectors of the ids for searches within the export limits 163 | citgroups <- dplyr::group_split(cit_counts_df, export_group) 164 | citgroups_single <- dplyr::group_split(single, export_group) 165 | citgroups_rest <- dplyr::group_split(rest, export_group) 166 | print(citgroups_rest) 167 | 168 | # define query function 169 | run_request <- function(input){ 170 | 171 | # build query for article citations - this will pull back a maximum of 500 hits from lens.org 172 | request <- paste0('{ 173 | "query": { 174 | "terms": { 175 | "lens_id": [', paste0('"', paste(input, collapse = '", "'), '"'), '] 176 | } 177 | }, 178 | "size":500, 179 | "scroll": "1m", 180 | "include": ["lens_id", "authors", "publication_type", "title", "external_ids", "start_page", "end_page", "volume", "issue", "references", "scholarly_citations", "source_urls", "abstract", "date_published", "year_published", "references_count", "scholarly_citations_count", "source"] 181 | }') 182 | # perform search and extract results 183 | results <- getLENSData(token, request) 184 | return(results) 185 | } 186 | 187 | cit_results <- data.frame() 188 | 189 | # run the query function for each cluster of records, looping through and recording the timing 190 | if(length(citgroups_rest) > 0){ 191 | 192 | tStart_cit <- Sys.time() 193 | for (i in 1:length(citgroups_rest)){ 194 | print(paste0('Running group ', i, ' request...')) 195 | print(unlist(citgroups_rest[[i]]$cit_by_art)) 196 | data_cit <- run_request(unlist(citgroups_rest[[i]]$cit_by_art)) 197 | requests_remaining <- data_cit[["headers"]][["x-rate-limit-remaining-request-per-minute"]] 198 | # print the requests remaining to the Shinyapps log 199 | print(paste0('Remaining requests = ', requests_remaining)) 200 | # extract and convert the results to a JSON 201 | record_json_cit <- httr::content(data_cit, "text") 202 | record_list_cit <- jsonlite::fromJSON(record_json_cit) 203 | 204 | # error messages 205 | if (data_cit$status_code == 404){ 206 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 207 | } 208 | if (data_cit$status_code == 429){ 209 | return('Warning: Right now there are too many people using citationchaser. This has been logged and we will endeavour to increase the bandwith as soon as possible. Please try again later.') 210 | } 211 | if (record_list_cit$total == 0){ 212 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 213 | } 214 | 215 | # convert the results to a dataframe 216 | record_list_cit_df <- as.data.frame(record_list_cit) 217 | cit_results <- dplyr::bind_rows(cit_results, record_list_cit_df) 218 | tEnd_cit <- Sys.time() 219 | 220 | # back off if the requests per minute is close to the limit (currently 50/min) 221 | if (data_cit[["headers"]][["x-rate-limit-remaining-request-per-minute"]] < 1){ 222 | t_cit <- tEnd_cit - tStart_cit 223 | Sys.sleep(60 - t) 224 | } 225 | } 226 | } 227 | 228 | ## cursor-based pagination for any single-record query with more than 500 citations 229 | # only run if there are single-record queries 230 | if (length(citgroups_single) != 0){ 231 | runrequest <- function(input){ 232 | request <- paste0('{ 233 | "query": { 234 | "terms": { 235 | "lens_id": [', paste0('"', paste(input, collapse = '", "'), '"'), '] 236 | } 237 | }, 238 | "size": ',max_results,', 239 | "scroll": "1m", 240 | "include": ["lens_id", "authors", "publication_type", "title", "external_ids", "start_page", "end_page", "volume", "issue", "references", "scholarly_citations", "source_urls", "abstract", "date_published", "year_published", "references_count", "scholarly_citations_count", "source"] 241 | }') 242 | 243 | # perform article search and extract text results 244 | data <- getLENSData(token, request) 245 | requests_remaining <- data[["headers"]][["x-rate-limit-remaining-request-per-minute"]] 246 | # print the requests remaining to the Shinyapps log 247 | print(paste0('Remaining requests = ', requests_remaining)) 248 | # extract and convert the results to a JSON 249 | record_json <- httr::content(data, "text") 250 | record_list <- jsonlite::fromJSON(record_json) 251 | 252 | # error messages 253 | if (data$status_code == 404){ 254 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 255 | } 256 | if (data$status_code == 429){ 257 | return('Warning: Right now there are too many people using citationchaser. This has been logged and we will endeavour to increase the bandwith as soon as possible. Please try again later.') 258 | } 259 | if (record_list$total == 0){ 260 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 261 | } 262 | 263 | # convert to a dataframe 264 | record_df <- data.frame(record_list) 265 | 266 | # if a result contains more than the max number of records per request, use cursor-based pagination 267 | if(record_list[["total"]] > max_results) { 268 | 269 | sets <- ceiling(record_list[["total"]] / max_results) # calculate the number of queries needed for those with more than the max number of results 270 | 271 | scroll_id <- record_list[["scroll_id"]] # extract the scroll id from the query to go back to the same search 272 | 273 | for (i in 2:sets){ # loop through the sets of results needed to bring back all records into a dataframe 274 | scroll_id <- record_list[["scroll_id"]] #extract the latest scroll_id from the last query 275 | 276 | request <- paste0('{"scroll_id": "', # new query based on scroll_id and including 'include' for efficiency 277 | scroll_id, 278 | '","include": ["lens_id", "authors", "publication_type", "title", "external_ids", "start_page", "end_page", "volume", "issue", "references", "scholarly_citations", "source_urls", "abstract", "date_published", "year_published", "references_count", "scholarly_citations_count", "source"]}') 279 | 280 | # perform article search and extract text results 281 | data <- getLENSData(token, request) 282 | requests_remaining <- data[["headers"]][["x-rate-limit-remaining-request-per-minute"]] 283 | print(paste0('Remaining requests = ', requests_remaining)) 284 | record_json <- httr::content(data, "text") 285 | record_list <- jsonlite::fromJSON(record_json) # convert json output from article search to list 286 | 287 | # error messages 288 | if (data$status_code == 404){ 289 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 290 | } 291 | if (data$status_code == 429){ 292 | return('Warning: Right now there are too many people using citationchaser. This has been logged and we will endeavour to increase the bandwith as soon as possible. Please try again later.') 293 | } 294 | if (record_list$total == 0){ 295 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 296 | } 297 | 298 | new_df <- data.frame(record_list) 299 | # output 300 | record_df <- dplyr::bind_rows(record_df,new_df) # bind the latest search dataframe to the previous dataframe 301 | 302 | } 303 | } 304 | return(record_df) 305 | } 306 | 307 | # loop through single-record queries that are less than the maximum allowed results per query 308 | for (i in 1:length(citgroups_single)){ 309 | data_cit <- runrequest(unlist(citgroups_single[[i]]$cit_by_art)) 310 | requests_remaining <- data_cit[["headers"]][["x-rate-limit-remaining-request-per-minute"]] 311 | print(paste0('Remaining requests = ', requests_remaining)) 312 | 313 | # error messages 314 | if (data$status_code == 404){ 315 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 316 | } 317 | if (data$status_code == 429){ 318 | return('Warning: Right now there are too many people using citationchaser. This has been logged and we will endeavour to increase the bandwith as soon as possible. Please try again later.') 319 | } 320 | 321 | cit_results <- dplyr::bind_rows(cit_results, data_cit) 322 | #tEnd_cit <- Sys.time() 323 | #if (data_cit[["headers"]][["x-rate-limit-remaining-request-per-minute"]] < 1){ 324 | # t_cit <- tEnd_cit - tStart_cit 325 | # Sys.sleep(60 - t) # pause to limit requests below 50 requests/min 326 | #} 327 | } 328 | } 329 | 330 | # remove duplicate records 331 | cit_results <- cit_results[!duplicated(cit_results$data.lens_id),] 332 | all_results_cit <- cit_results$data.lens_id 333 | 334 | # convert json to ris style 335 | type_list <- data.frame(type = c("ABST", "BOOK", "CHAP", "COMP", "CONF", "DATA", "JOUR"), 336 | description = c("abstract reference", "whole book reference", "book chapter reference", "computer program", "conference proceeding", "data file", "journal/periodical reference"), 337 | publication_type = c("reference entry", "book", "book chapter", "component", "conference proceedings", "dataset", "journal article")) 338 | publication_type_cit <- cit_results$data.publication_type 339 | 340 | authors_cit <- list() 341 | for (i in 1:length(cit_results$data.authors)) { 342 | 343 | authors_cit_next <- paste0( 344 | "AU - ", 345 | cit_results$data.authors[[i]]$last_name, 346 | ', ', 347 | cit_results$data.authors[[i]]$first_name, 348 | '\n', 349 | collapse='') 350 | 351 | authors_cit <- unlist(c(authors_cit, authors_cit_next)) 352 | } 353 | 354 | title_cit <- cit_results$data.title 355 | year_cit <- cit_results$data.year_published 356 | abstract_cit <- cit_results$data.abstract 357 | start_page_cit <- cit_results$data.start_page 358 | end_page_cit <- cit_results$data.end_page 359 | source_title_cit <- list() 360 | for (i in 1:length(cit_results[,1])) { 361 | source_title_cit <- unlist(c(source_title_cit, cit_results$data.source[[1]][i])) 362 | } 363 | volume_cit <- cit_results$data.volume 364 | issue_cit <- cit_results$data.issue 365 | publisher_cit <- cit_results$data.source.publisher 366 | issn_cit <- cit_results$data.source.issn 367 | doi_cit <- unlist(lapply(cit_results$data.external_ids, function(ch) maditr::vlookup('doi', ch, result_column = 'value', lookup_column = 'type'))) 368 | 369 | # generate data table for Shiny UI 370 | level1_table_cit <- data.table::data.table(authors = authors_cit, 371 | year = year_cit, 372 | title = title_cit, 373 | source_title = source_title_cit, 374 | publisher = publisher_cit, 375 | volume = volume_cit, 376 | issue = issue_cit, 377 | start_page = start_page_cit, 378 | end_page = end_page_cit, 379 | doi = doi_cit) 380 | 381 | # generate RIS file 382 | level1_ris_cit <- paste0('\n', 383 | 'TY - ', maditr::vlookup(publication_type_cit, type_list, result_column = 'type', lookup_column = 'publication_type'), '\n', 384 | authors_cit, 385 | 'TI - ', title_cit, '\n', 386 | 'PY - ', year_cit, '\n', 387 | 'AB - ', abstract_cit, '\n', 388 | 'SP - ', start_page_cit, '\n', 389 | 'EP - ', end_page_cit, '\n', 390 | 'JF - ', source_title_cit, '\n', 391 | 'VL - ', volume_cit, '\n', 392 | 'IS - ', issue_cit, '\n', 393 | 'PB - ', publisher_cit, '\n', 394 | # 'SN - ', issn_cit, '\n', 395 | 'DO - ', doi_cit, '\n', 396 | 'ER - ', 397 | sep='') 398 | 399 | # generate ris build report 400 | ris_records_cit <- lengths(regmatches(level1_ris_cit, gregexpr("TY - ", level1_ris_cit))) 401 | 402 | stage1_report_cit <- paste0('Your ', scales::comma(input_number), ' articles were cited a total of ', scales::comma(all_citations), ' times. This corresponds to ', 403 | scales::comma(length(citations_unique)), ' unique article IDs. Your RIS file is ready for download and contains ', 404 | scales::comma(ris_records_cit), ' records exported from Lens.org.') 405 | 406 | report_cit <- stage1_report_cit 407 | 408 | return(list(display = level1_table_cit, ris = level1_ris_cit, report = report_cit, df = cit_results)) 409 | 410 | ### search for references articles 411 | } else if (get_records == 'references') { 412 | 413 | if (is.null(record_list[["data"]][["references_count"]]) == TRUE || identical(record_list[["data"]][["references_count"]], 0) == TRUE){ 414 | return('Warning: Your input articles contained no references in the Lens.org database') 415 | } 416 | 417 | # obtain reference lists from article search 418 | reference_count <- record_list[["data"]][["references_count"]] 419 | reference_count[is.na(reference_count)] <- 0 420 | all_references <- sum(reference_count, na.rm = TRUE) 421 | ref_by_art <- record_list[["data"]][["references"]] 422 | references <- unlist(record_list[["data"]][["references"]]) 423 | references_unique <- unique(references) 424 | deduped_references <- length(references_unique) 425 | 426 | ref_counts_df <- tibble::tibble(articles_id, 427 | reference_count, 428 | ref_by_art) 429 | ref_counts_df[is.na(ref_counts_df)] <- 0 430 | #below code not working because it was tagging excess records from one group onto the next (of 500) 431 | #ref_counts_df <- dplyr::mutate(ref_counts_df, 432 | # # cumulatively add up citation count 433 | # cumulative_n = cumsum(reference_count), 434 | # # divide by max citations allowed 435 | # export_group = floor(cumsum(reference_count) / 500)) 436 | ref_counts_df <- dplyr::mutate(ref_counts_df, export_group = MESS::cumsumbinning(reference_count, 500)) 437 | 438 | # get a list of vectors of the ids 439 | refgroups <- dplyr::group_split(ref_counts_df, export_group) 440 | 441 | # define query function 442 | run_request <- function(input){ 443 | 444 | # build query for article citations - this will pull back a maximum of 1,000 hits from lens.org, so not great if lots of refs 445 | request <- paste0('{ 446 | "query": { 447 | "terms": { 448 | "lens_id": [', paste0('"', paste(input, collapse = '", "'), '"'), '] 449 | } 450 | }, 451 | "size":500, 452 | "include": ["lens_id", "authors", "publication_type", "title", "external_ids", "start_page", "end_page", "volume", "issue", "references", "scholarly_citations", "source_urls", "abstract", "date_published", "year_published", "references_count", "scholarly_citations_count", "source"] 453 | }') 454 | #perform references search and extract text results 455 | results <- getLENSData(token, request) 456 | return(results) 457 | } 458 | 459 | # run the query function for each tibble, adding to a final dataset 460 | ref_results <- data.frame() 461 | tStart_ref <- Sys.time() 462 | for (i in 1:length(refgroups)){ 463 | data_ref <- run_request(unique(unlist(refgroups[[i]]$ref_by_art))) 464 | requests_remaining <- data_ref[["headers"]][["x-rate-limit-remaining-request-per-minute"]] 465 | print(paste0('Remaining requests = ', requests_remaining)) 466 | record_json_ref <- httr::content(data_ref, "text") 467 | record_list_ref <- jsonlite::fromJSON(record_json_ref) 468 | 469 | # error messages 470 | if (data_ref$status_code == 404){ 471 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 472 | } 473 | if (data_ref$status_code == 429){ 474 | return('Warning: Right now there are too many people using citationchaser. This has been logged and we will endeavour to increase the bandwith as soon as possible. Please try again later.') 475 | } 476 | if (record_list_ref$total == 0){ 477 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 478 | } 479 | 480 | record_list_ref_df <- as.data.frame(record_list_ref) 481 | ref_results <- dplyr::bind_rows(ref_results, record_list_ref_df) 482 | tEnd_ref <- Sys.time() 483 | if (data_ref[["headers"]][["x-rate-limit-remaining-request-per-minute"]] < 1){ 484 | t_ref <- tEnd_ref - tStart_ref 485 | Sys.sleep(60 - t) # pause to limit requests below 50 requests/min 486 | } 487 | } 488 | ref_results <- ref_results[!duplicated(ref_results$data.lens_id),] 489 | all_results_ref <- ref_results$data.lens_id 490 | 491 | # convert json to ris style 492 | type_list <- data.frame(type = c("ABST", "BOOK", "CHAP", "COMP", "CONF", "DATA", "JOUR"), 493 | description = c("abstract reference", "whole book reference", "book chapter reference", "computer program", "conference proceeding", "data file", "journal/periodical reference"), 494 | publication_type = c("reference entry", "book", "book chapter", "component", "conference proceedings", "dataset", "journal article")) 495 | publication_type_ref <- ref_results$data.publication_type 496 | 497 | authors_ref <- list() 498 | for (i in 1:length(ref_results$data.authors)) { 499 | 500 | authors_ref_next <- paste0( 501 | "AU - ", 502 | ref_results$data.authors[[i]]$last_name, 503 | ', ', 504 | ref_results$data.authors[[i]]$first_name, 505 | '\n', 506 | collapse='') 507 | 508 | authors_ref <- unlist(c(authors_ref, authors_ref_next)) 509 | } 510 | title_ref <- ref_results$data.title 511 | year_ref <- ref_results$data.year_published 512 | abstract_ref <- ref_results$data.abstract 513 | start_page_ref <- ref_results$data.start_page 514 | end_page_ref <- ref_results$data.end_page 515 | source_title_ref <- list() 516 | for (i in 1:length(ref_results[,1])) { 517 | source_title_ref <- unlist(c(source_title_ref, ref_results$data.source[[1]][i])) 518 | } 519 | volume_ref <- ref_results$data.volume 520 | issue_ref <- ref_results$data.issue 521 | publisher_ref <- ref_results$data.source.publisher 522 | issn_ref <- ref_results$data.source.issn 523 | doi_ref <- unlist(lapply(ref_results$data.external_ids, function(ch) maditr::vlookup('doi', ch, result_column = 'value', lookup_column = 'type'))) 524 | 525 | level1_table_ref <- data.table::data.table(authors = authors_ref, 526 | year = year_ref, 527 | title = title_ref, 528 | source_title = source_title_ref, 529 | publisher = publisher_ref, 530 | volume = volume_ref, 531 | issue = issue_ref, 532 | start_page = start_page_ref, 533 | end_page = end_page_ref, 534 | doi = doi_ref) 535 | 536 | level1_ris_ref <- paste('\n', 537 | 'TY - ', maditr::vlookup(publication_type_ref, type_list, result_column = 'type', lookup_column = 'publication_type'), '\n', 538 | authors_ref, 539 | 'TI - ', title_ref, '\n', 540 | 'PY - ', year_ref, '\n', 541 | 'AB - ', abstract_ref, '\n', 542 | 'SP - ', start_page_ref, '\n', 543 | 'EP - ', end_page_ref, '\n', 544 | 'JF - ', source_title_ref, '\n', 545 | 'VL - ', volume_ref, '\n', 546 | 'IS - ', issue_ref, '\n', 547 | 'PB - ', publisher_ref, '\n', 548 | # 'SN - ', issn_ref, '\n', 549 | 'DO - ', doi_ref, '\n', 550 | 'ER - ', 551 | sep='') 552 | 553 | # ris build report 554 | ris_records_ref <- lengths(regmatches(level1_ris_ref, gregexpr("TY - ", level1_ris_ref))) 555 | 556 | stage1_report_ref <- paste0('Your ', scales::comma(input_number), ' articles contained a total of ', scales::comma(all_references), ' references. This corresponds to ', 557 | scales::comma(deduped_references), ' unique IDs. Your RIS file is ready for download and contains ', scales::comma(ris_records_ref), ' records exported from Lens.org.') 558 | 559 | report_ref <- stage1_report_ref 560 | 561 | return(list(display = level1_table_ref, ris = level1_ris_ref, report = report_ref, df = ref_results)) 562 | 563 | } 564 | } 565 | 566 | 567 | #' Function to query Lens.org 568 | #' 569 | #' @description Function written by lens.org for use of their API. 570 | #' @param token An access key for the lens.org API. Tokens can be obtained by 571 | #' applying for scholarly API access and creating a token once approved. See 572 | #' 'https://www.lens.org/lens/user/subscriptions#scholar' for further details. 573 | #' @param query A search string formulated according to the Lens.org API 574 | #' documentation: 'https://docs.api.lens.org/request-scholar.html'. 575 | #' @return A summary response. The results are viewable using 576 | #' 'content(data, "text")'. Other details regarding the request (e.g. repsonse 577 | #' times) can be accessed through the main output. 578 | #' @importFrom httr add_headers POST 579 | getLENSData <- function(token, query){ 580 | url <- 'https://api.lens.org/scholarly/search' 581 | headers <- c('Authorization' = token, 'Content-Type' = 'application/json') 582 | httr::POST(url = url, httr::add_headers(.headers=headers), body = query) 583 | } 584 | 585 | 586 | #' Find citation based on identifier 587 | #' 588 | #' @description This function takes a list of articles in the form of established 589 | #' identifiers (e.g. digital object identifiers) and sends a request to the 590 | #' lens.org API to obtain full citation information from the Lens database for 591 | #' all sought articles. 592 | #' @param article_list List of article identifiers for which the reference 593 | #' lists will be returned. Must be a list/vector of identifiers, e.g. 594 | #' '"10.1186/s13750-018-0126-2" "10.1002/jrsm.1378"'. 595 | #' @param type Specification of the type of input provided. The default is 596 | #' 'doi' (digital object identifier), but any of the following are accepted: 597 | #' "pmid" (PubMed ID), "pmcid" (PubMed Central ID), "magid" (Microsoft 598 | #' Academic ID), "coreid" (CORE identifier), lens_id" (The Lens.org ID), 599 | #' "title" (article title; much lower specificity). 600 | #' @param token An access key for the lens.org API. Tokens can be obtained by 601 | #' applying for scholarly API access and creating a token once approved. See 602 | #' 'https://www.lens.org/lens/user/subscriptions#scholar' for further details. 603 | #' @return A dataframe containing the matching citation from Lens.org. 604 | #' @importFrom maditr vlookup 605 | #' @importFrom httr content 606 | #' @importFrom jsonlite fromJSON 607 | #' @importFrom data.table data.table 608 | #' @examples 609 | #' \dontrun{ 610 | #' article_list <- c("10.1007/978-3-642-37048-9_13", "10.1111/sum.12030", 611 | #' "10.5194/bg-13-3619-2016", "10.1016/j.agee.2012.09.006") 612 | #' results <- get_citation(article_list) 613 | #' articles <- results$display 614 | #' } 615 | #' @export 616 | get_citation <- function(article_list, 617 | type = 'doi', 618 | token = 'WCFlpCtuJXYI1sDhZcZ8y7hHpri0SEmTnLNkeU4OEM5JTQRNXB9w'){ 619 | 620 | if(length(article_list) == 1){ 621 | article_list <- trimws(unlist(strsplit(article_list, '[,]'))) 622 | } 623 | 624 | request <- paste0('{\n\t"query": {\n\t\t"terms": {\n\t\t\t"', type, '": ["', paste0('', paste(article_list, collapse = '", "'), '"'),']\n\t\t}\n\t},\n\t"size":500\n}') 625 | 626 | # perform article search and extract text results 627 | data <- getLENSData(token, request) 628 | requests_remaining <- data[["headers"]][["x-rate-limit-remaining-request-per-minute"]] 629 | print(paste0('Remaining requests = ', requests_remaining)) 630 | record_json <- httr::content(data, "text") 631 | 632 | # convert json output from article search to list 633 | record_list <- jsonlite::fromJSON(record_json) 634 | 635 | # error messages 636 | if (data$status_code == 404){ 637 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 638 | } 639 | if (data$status_code == 429){ 640 | return('Warning: Right now there are too many people using citationchaser. This has been logged and we will endeavour to increase the bandwith as soon as possible. Please try again later.') 641 | } 642 | if (record_list$total == 0){ 643 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 644 | } 645 | 646 | inputs_df <- as.data.frame(record_list) 647 | 648 | # citations and references 649 | citation_count <- record_list[["data"]][["scholarly_citations_count"]] 650 | reference_count <- record_list[["data"]][["references_count"]] 651 | 652 | # convert json to ris style 653 | type_list <- data.frame(type = c("ABST", "BOOK", "CHAP", "COMP", "CONF", "DATA", "JOUR"), 654 | description = c("abstract reference", "whole book reference", "book chapter reference", "computer program", "conference proceeding", "data file", "journal/periodical reference"), 655 | publication_type = c("reference entry", "book", "book chapter", "component", "conference proceedings", "dataset", "journal article")) 656 | publication_type <- record_list[["data"]][["publication_type"]] 657 | authors <- list() 658 | for (i in 1:length(record_list[["data"]][["authors"]])) { 659 | authors <- unlist(c(authors, paste0(record_list[["data"]][["authors"]][[i]]$last_name, ', ', 660 | record_list[["data"]][["authors"]][[i]]$first_name, collapse = '; '))) 661 | } 662 | title <- record_list[["data"]][["title"]] 663 | year <- record_list[["data"]][["year_published"]] 664 | abstract <- record_list[["data"]][["abstract"]] 665 | start_page <- record_list[["data"]][["start_page"]] 666 | end_page <- record_list[["data"]][["end_page"]] 667 | source_title <- record_list[["data"]][["source"]][["title"]] 668 | volume <- record_list[["data"]][["volume"]] 669 | issue <- record_list[["data"]][["issue"]] 670 | publisher <- record_list[["data"]][["source"]][["publisher"]] 671 | issn <- record_list[["data"]][["source"]][["issn"]] 672 | doi <- unlist(lapply(record_list[["data"]][["external_ids"]], function(ch) maditr::vlookup('doi', ch, result_column = 'value', lookup_column = 'type'))) 673 | 674 | article_table <- data.table::data.table(authors = authors, 675 | year = year, 676 | title = title, 677 | source_title = source_title, 678 | publisher = publisher, 679 | volume = volume, 680 | issue = issue, 681 | start_page = start_page, 682 | end_page = end_page, 683 | doi = doi, 684 | Lens_refs = reference_count, 685 | Lens_cited = citation_count) 686 | 687 | article_ris <- paste(paste0('\n', 688 | 'TY - ', maditr::vlookup(publication_type, type_list, result_column = 'type', lookup_column = 'publication_type'), '\n', 689 | 'AU - ', authors, '\n', 690 | 'TI - ', title, '\n', 691 | 'PY - ', year, '\n', 692 | 'AB - ', abstract, '\n', 693 | 'SP - ', start_page, '\n', 694 | 'EP - ', end_page, '\n', 695 | 'JF - ', source_title, '\n', 696 | 'VL - ', volume, '\n', 697 | 'IS - ', issue, '\n', 698 | 'PB - ', publisher, '\n', 699 | # 'SN - ', issn, '\n', 700 | 'DO - ', doi, '\n', 701 | 'ER - '), 702 | collapse = '\n') 703 | 704 | return(list(display = article_table, ris = article_ris, df = inputs_df)) 705 | 706 | } 707 | -------------------------------------------------------------------------------- /R/globals.R: -------------------------------------------------------------------------------- 1 | utils::globalVariables(c("export_group")) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # citationchaser 2 | 3 | In searching for research articles, we often want to obtain lists of references from across studies, and also obtain lists of articles that cite a particular study. In systematic reviews, this supplementary search technique is known as 'citation chasing': forward citation chasing looks for all records citing one or more articles of known relevance; backward citation chasing looks for all records referenced in one or more articles. 4 | 5 | Traditionally, this process would be done manually, and the resulting records would need to be checked one-by-one against included studies in a review to identify potentially relevant records that should be included in a review. 6 | 7 | This package contains functions to automate this process by making use of the Lens.org API. An input article list can be used to return a list of all referenced records, and/or all citing records in the Lens.org database (consisting of PubMed, PubMed Central, CrossRef, Microsoft Academic Graph and CORE; 'https://www.lens.org'). 8 | 9 | USERS MUST OBTAIN A TOKEN FOR THE LENS.ORG SCHOLARLY API (available for free here). The API may be time limited, but not rate limited. Requests for tokens may need initial approval from The Lens. 10 | 11 | ***A shiny app version of the package is available [here](https://estech.shinyapps.io/citationchaser/). This version requires no Lens.org API and is immediately usable and ENTIRELY FREE.*** 12 | 13 | Install the package using the following code: 14 | `devtools::install_github("nealhaddaway/citationchaser")` 15 | 16 | Please cite as:
17 | Haddaway, N. R., Grainger, M. J., Gray, C. T. 2021. citationchaser: An R package and Shiny app for forward and backward citations chasing in academic searching. doi: 10.5281/zenodo.4543513
18 | Citation in .ris format (right click 'Save Link As') 19 | 20 | 21 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.4543513.svg)](https://doi.org/10.5281/zenodo.4543513) 22 | 23 | ![GitHub all releases](https://img.shields.io/github/downloads/nealhaddaway/citationchaser/total) 24 | ![GitHub Release Date](https://img.shields.io/github/release-date/nealhaddaway/citationchaser) 25 | ![GitHub R package version](https://img.shields.io/github/r-package/v/nealhaddaway/citationchaser) 26 | ![GitHub last commit](https://img.shields.io/github/last-commit/nealhaddaway/citationchaser) 27 | 28 | ![GitHub Repo stars](https://img.shields.io/github/stars/nealhaddaway/citationchaser?style=social) 29 | ![Twitter URL](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Fwww.twitter.com%2Fnealhaddaway) 30 | 31 | -------------------------------------------------------------------------------- /citationchaser.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | BuildType: Package 16 | PackageUseDevtools: Yes 17 | PackageInstallArgs: --no-multiarch --with-keep.source 18 | PackageRoxygenize: rd,collate,namespace,vignette 19 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all people who 4 | contribute through reporting issues, posting feature requests, updating documentation, 5 | submitting pull requests or patches, and other activities. 6 | 7 | We are committed to making participation in this project a harassment-free experience for 8 | everyone, regardless of level of experience, gender, gender identity and expression, 9 | sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. 10 | 11 | Examples of unacceptable behavior by participants include the use of sexual language or 12 | imagery, derogatory comments or personal attacks, trolling, public or private harassment, 13 | insults, or other unprofessional conduct. 14 | 15 | Project maintainers have the right and responsibility to remove, edit, or reject comments, 16 | commits, code, wiki edits, issues, and other contributions that are not aligned to this 17 | Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed 18 | from the project team. 19 | 20 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by 21 | opening an issue, emailing the project lead , Neal Haddaway (), or one of the core developers. 22 | 23 | This Code of Conduct is adapted from the Contributor Covenant 24 | (http://contributor-covenant.org), version 1.0.0, available at 25 | http://contributor-covenant.org/version/1/0/0/ 26 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to citationchaser 2 | 3 | The goal of this guide is to help you get up and contributing to citationchaser as 4 | quickly as possible. 5 | 6 | Please note that citationchaser is released with a [Contributor Code of Conduct](code-of-conduct.md). By contributing to this project, you agree to abide by its terms. 7 | 8 | 9 | ### Fixing typos 10 | Small typos or grammatical errors in documentation may be edited directly using 11 | the GitHub web interface, so long as the changes are made in the _source_ file. 12 | 13 | * DO: edit a roxygen comment in a `.R` file below `R/`. 14 | * DO NOT: edit an `.Rd` file below `man/`. 15 | 16 | Similarly, typos/errors in the supporting documents (e.g. these contributing guidelines, code-of-conduct.md) 17 | may be edited directly using the GitHub web interface. 18 | 19 | The exception to the above is when typos/errors occur in the README.md document. To correct these: 20 | 21 | * Fork the respository to your personal GitHub account 22 | * Edit the README.Rmd document to fix the error 23 | * Knit the README.Rmd document to produce the corrected README.md 24 | * Commit changes and push both corrected documents to your forked respository 25 | * Issue a pull request (see the section on [Pull Requests](#pull-requests), below). 26 | 27 | 28 | ### Filing an issue 29 | 30 | When filing an issue, the most important thing is to include a minimal 31 | reproducible example so that we can quickly verify the problem, and then figure 32 | out how to fix it. See "[Writing a good reproducible example](https://reprex.tidyverse.org/articles/reprex-dos-and-donts.html)". 33 | 34 | 35 | 1. **Packages** should be loaded at the top of the script, so it's easy to 36 | see which ones the example needs. 37 | 38 | 1. Spend a little bit of time ensuring that your **code** is easy for others to 39 | read: 40 | 41 | * make sure you've used spaces and your variable names are concise, but 42 | informative 43 | 44 | * use comments generously to indicate where your problem lies 45 | 46 | * do your best to remove everything that is not related to the problem. 47 | 48 | You can check you have actually made a reproducible example by starting up a 49 | fresh R session and pasting your script in. 50 | 51 | 52 | ### Pull Requests 53 | 54 | #### Getting Started 55 | * Make sure you have a [GitHub account](https://github.com/signup/free). 56 | * Familiarise yourself with Git and Github, using the [resources](#additional-resources) at the end of this page. 57 | 58 | #### Prerequisites 59 | Before you make a substantial pull request, you should always file an issue and 60 | make sure someone from the team agrees that it’s a problem. If you’ve found a 61 | bug, create an associated issue and illustrate the bug with a minimal 62 | [reproducible example](https://reprex.tidyverse.org/articles/reprex-dos-and-donts.html). 63 | 64 | #### Pull request process 65 | * We recommend that you create a Git branch for each pull request (PR). 66 | * Look at the Travis and AppVeyor build status before and after making changes. 67 | The `README` should contain badges for any continuous integration services used 68 | by the package. 69 | * New code should follow the tidyverse [style guide](http://style.tidyverse.org). 70 | You can use the [styler](https://CRAN.R-project.org/package=styler) package to 71 | apply these styles, but please don't restyle code that has nothing to do with 72 | your PR. 73 | * We use [roxygen2](https://cran.r-project.org/package=roxygen2), with 74 | [Markdown syntax](https://cran.r-project.org/web/packages/roxygen2/vignettes/markdown.html), 75 | for documentation. 76 | * We use [testthat](https://cran.r-project.org/package=testthat). Contributions 77 | with test cases included are easier to accept. 78 | 79 | ### Additional Resources 80 | 81 | * The [Git and Github](http://r-pkgs.had.co.nz/git.html) section of the __R Packages__ book by Hadley Wickham 82 | * [Happy Git and GitHub for the useR](https://happygitwithr.com/) 83 | * General GitHub [documentation](https://help.github.com/) 84 | * GitHub pull request [documentation]](https://help.github.com/articles/creating-a-pull-request/) 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /inst/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nealhaddaway/citationchaser/ba382a7336fbc61090b092e67db9ddecfcc4e8ca/inst/.DS_Store -------------------------------------------------------------------------------- /inst/CITATION: -------------------------------------------------------------------------------- 1 | citHeader("To cite citationchaser in publications, please use:") 2 | 3 | citEntry( 4 | entry = "Article", 5 | title = "citationchaser: An R package and Shiny app for forward and backward citations chasing in academic searching", 6 | author = "Neal R Haddaway, Matthew J Grainger, and Charles T Gray", 7 | journal = "Zenodo", 8 | year = "2022", 9 | url = "https://github.com/nealhaddaway/citationchaser", 10 | doi = "10.5281/zenodo.4543513", 11 | textVersion = paste( 12 | "Neal R Haddaway, Matthew J Grainger, and Charles T Gray (2022).", 13 | "citationchaser: An R package and Shiny app for forward and backward citations chasing in academic searching", 14 | "Zenodo. https://doi.org/10.5281/zenodo.4543513" 15 | ) 16 | ) -------------------------------------------------------------------------------- /inst/extdata/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nealhaddaway/citationchaser/ba382a7336fbc61090b092e67db9ddecfcc4e8ca/inst/extdata/.DS_Store -------------------------------------------------------------------------------- /inst/extdata/citationchaser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nealhaddaway/citationchaser/ba382a7336fbc61090b092e67db9ddecfcc4e8ca/inst/extdata/citationchaser.png -------------------------------------------------------------------------------- /inst/extdata/export.csv: -------------------------------------------------------------------------------- 1 | ,title,GScites,WoScites,Scopuscites,Actualrefs,WoSrefs,Scopusrefs 2 | 1,"The Cognitive, Instrumental and InstitutioNFl Origins of NFnoscale Research: The Place of Biology",7,NF,NF,13,, 3 | 2,"AIDS research ""spin-off""--ganciclovir CMV prevention after liver transplantation.",1,NF,1,NF,,NF 4 | 3,Informed consent in medical research. Risk of bias may be another reason not to seek consent.,4,NF,3,3,,NF 5 | 4,Overseas Market Research,1,NF,NF,0,, 6 | 5,Große Unterschiede im Verfahren zur Ernennung zum außerplanmäßigen Professor@@@Major differences in procedure in receiving the academic title “ausserplanmaessiger Professor”. Criteria and evaluation of research and teaching at German medical faculties: Verfahren und Kriterien der Forschung und Lehre an deutschen Fakultäten,0,2,2,4,4,4 7 | 6,"Hagenberg Research - Parallel, Distributed, and Grid Computing",1,NF,NF,93,, 8 | 7,Adult Education Research: General,9,NF,NF,11,, 9 | 8,A Place for Research Strategies in Clinical Psychology,0,NF,NF,20,, 10 | 9,The Importance of Ethics in Scientific Research,0,NF,NF,NF,, 11 | 10,A Plea for Improvement of Research in Mathematics Education.,0,NF,NF,9,, 12 | 11,Animal Models in Biomedical Research,10,NF,4,NF,,22 13 | 12,Basic Research and Implementation Decisions for a Text-to-Speech Synthesis System in Romanian,36,NF,19,24,,24 14 | 13,On the Need of New Mechanisms for the Protection of Intellectual Property of Research Universities,4,NF,NF,42,, 15 | 14,BIODIVERSITY RESEARCH: Geographical linkages between threats and imperilment in freshwater fish in the Mediterranean Basin,59,44,43,43,42,43 16 | 15,AIDS-Antiviral NFtural Products Research at The U.S. NFtioNFl Cancer Institute,2,NF,NF,28,, 17 | 16,Fathers' and Mothers' Participation in Research.,31,11,16,NF,23,NF 18 | 17,"Australian physiological research in the Antarctic and Subantarctic, with special reference to thermal stress and acclimatization",12,NF,NF,43,, 19 | 18,Understanding the impact of research on policy using Altmetric data,2,NF,NF,0,, 20 | 19,Progress in genetic research of lactic acid bacteria,6,NF,NF,75,, 21 | 20,Becoming a Self-Reflective Teacher: A Meaningful Research Process,42,NF,NF,41,, 22 | 21,Protein Patent Problem to Hinder Research,1,1,0,0,0,NF 23 | 22,Implementation of a Performance Measurement Framework in Greek Manufacture: An Empirical Research,0,0,NF,7,7, 24 | 23,"Tipping points, thresholds and the keystone role of physiology in marine climate change research.",84,60,60,178,178,178 25 | 24,The Challenge Facing Infant Research in the Next Decade,8,NF,NF,41,, 26 | 25,A Simple Research Impacts Model Applied to the Information Systems Field,7,NF,5,31,,31 27 | 26,"Integrating Objectives for Clinical Education, Research, and Service Learning into Community Health Promotion Projects",19,NF,NF,27,, 28 | 27,Research and Implementation of Vehicle Detection in Intelligent Transportation System,NF,0,0,9,9,9 29 | 28,FAO/IAEA co-ordiNFted research programme on enhancement of nitrogen fixation in leguminous crops,0,NF,NF,22,, 30 | 29,Using EducatioNFl Research as a Resource for Continuous Improvement in Education: The Best Evidence Syntheses,3,NF,1,19,,19 31 | 30,Handbook of Research on Fuzzy Information Processing in Databases - How to Achieve Fuzzy RelatioNFl Databases MaNFging Fuzzy Data and Metadata,140,NF,NF,60,, 32 | 31,"Women in Magazines : Research, Representation, Production and Consumption",12,NF,2,NF,,260 33 | 32,Möglichkeiten und Grenzen der Anwendung von Methoden des Operations Research für die Begründung von Instandhaltungsstrategien,0,NF,NF,NF,, 34 | 33,Research on the System Model of Network Intrusion Detection,1,0,1,12,12,12 35 | 34,"Methodological Issues in Cross-Cultural MaNFgement Research: Problems, Solutions, and Proposals",67,NF,31,92,,92 36 | 35,Measuring the Effects of a RESEARCH-BASED Field Experience on Undergraduates and K - 12 Teachers,57,NF,22,19,,19 37 | 36,General practice research in Denmark: status and organization. The Danish Society of General Practice,0,NF,NF,NF,, 38 | 37,Research for Leaders: The NFtioNFl College for School Leadership,0,NF,0,NF,,29 39 | 38,"Inflation accounting survey: CACA & Market Opinion Research InterNFtioNFl Research Study for CACA (London, 1987). 40 pp. £7.50 (pbk)",0,NF,NF,NF,, 40 | 39,Procurement of State-of-the-Art Research Equipment to Support Faculty Members Within the RNFi Therapeutics Institute,0,NF,NF,NF,, 41 | 40,PAKDD (2) - Learning the funding momentum of research projects,4,1,4,19,19,19 42 | 41,Urban research: Cambridge fights nerve gases,0,0,0,0,0,NF 43 | 42,Research Methods in Language and Education - Researching Developing Discourses and Competences in Immersion Classrooms,1,NF,NF,NF,, 44 | 43,Strategies and tactics of human behavioral research,3,NF,NF,8,, 45 | 44,Russia's age structure in 1996: a research report.,1,0,2,NF,17,17 46 | 45,RESEARCH AND DEVELOPMENT OF HIGH-RESOLUTION STEREOLITHOGRAPHY SYSTEM FOR SMALL OBJECTS,4,NF,2,NF,,4 47 | 46,Helping Research Programs Demonstrate Impact,0,NF,NF,NF,, 48 | 47,Social Indicators Research: A Case Study of the Development of a JourNFl,2,NF,NF,2,, 49 | 48,Gravity Research Missions reviewed in light of the indirect ocean tide potential.,8,NF,NF,24,, 50 | 49,Research of open situation x-ray of cervical vertebra in healthy people,0,NF,0,NF,,NF 51 | 50,ICIC (1) - Research on image mosaic algorithm based on computer wizard vector field algorithm,3,0,1,9,9,9 52 | 51,Recent progressions in stem cell research: breakthroughs achieved and challenges faced.,11,NF,4,39,,NF 53 | 52,Screening for psychosocial risk in an urban preNFtal clinic population: a retrospective practice-based research study.,17,9,11,32,32,32 54 | 53,Microspectroscopy as a tool to discrimiNFte NFno-molecular cellular alterations in biomedical research.,24,NF,NF,154,, 55 | 54,CoordiNFting societies of research agents—IMS experience,5,4,4,22,22,22 56 | 55,THE GROWTH OF LOGIC OUT OF THE FOUNDATIONFL RESEARCH IN MATHEMATICS,20,NF,NF,15,, 57 | 56,Democratising Mathematics Education and the Role of Research,0,NF,NF,20,, 58 | 57,"If life gives you limes, make mojitos!: Identifying stakeholders, selling user experience research, and dealing with difficult people and situations",NF,NF,NF,NF,, 59 | 58,Turk researchers harassed,0,NF,0,NF,,NF 60 | 59,Numerical Research of Combustion Efficiency of a LOX/GCH4 Shear Coaxial Injector,NF,0,NF,NF,6, 61 | 60,Molecular mechanisms of the cross-impact of pathological processes in combined diabetes and cancer. Research and clinical aspects.,18,5,233,233,233,6 62 | 61,OccupatioNFl health nursing research priorities: a changing focus.,12,NF,3,5,,5 63 | 62,"SavanNFh River Ecology Laboratory annual technical progress report of ecological research, period ending July 31, 1993",0,NF,NF,NF,, 64 | 63,Research on Key Techniques of Digital SigNFl Processing Theory for Electric Machines and Motor Control,0,0,NF,8,8, 65 | 64,Advances in research on extra-oesophageal symptoms of pediatric gastroesophageal reflux,5,NF,0,55,,55 66 | 65,Knowledge Mining: A Quantitative Synthesis of Research Results and Findings,3,0,0,34,33,34 67 | 66,Physical Chemical Techniques. Vol. II of Physical Techniques in Biological Research,12,NF,NF,NF,, 68 | 67,Summary of research on the effects of topographic amplification of earthquake shaking on slope stability,58,NF,NF,27,, 69 | 68,Testing – Practice and Research Techniques - Testing - Practice and Research Techniques,0,NF,0,NF,,NF 70 | 69,Overview of Service Science Research System,0,NF,NF,9,, 71 | 70,InterNFtioNFl Performance Research Institute (IPRI),0,NF,NF,0,, 72 | 71,EducatioNFl and vocatioNFl choices of gifted individuals: guidance counseling and research for adolescents into adulthood,3,NF,NF,142,, 73 | 72,The `Qualitative' Versus `Quantitative' Research Debate: A Question of Metaphorical Assumptions?,170,NF,NF,29,, 74 | 73,Response of western mountain ecosystems to climatic variability and change: a collaborative research approach,2,NF,3,NF,,109 75 | 74,Social Workers' Perceptions of Privacy: A Research Note,1,0,0,12,12,11 76 | 75,"Supporting Frontier Research, Which Institutions and Which Processes",4,NF,NF,46,, 77 | 76,"Implications, Limitations and Future Research",0,NF,NF,0,, 78 | 77,Research in gold mining and metallurgy,0,NF,0,0,,NF 79 | 78,Methods of data aNFlysis in person-oriented research — The sample case of ANOVA,8,NF,NF,17,, 80 | 79,Summary report on the research needs and priorities in the tribology of boundary layer lubrication,0,0,0,0,0,NF 81 | 80,Research Needs Identified at Workshop,0,NF,NF,0,, 82 | 81,Collection and aNFlysis of Health Physics Research Reactor operatioNFl and use data,2,NF,NF,17,, 83 | 82,Research on Automatic Layout Planning and Performance ANFlysis System of Production Line Based on Simulation,0,NF,0,10,,10 84 | 83,The Research of Ontology Merging Based on Rough Concept Lattice Isomorphic Merging,0,NF,0,3,,3 85 | 84,Children and Consent to Participate in Research,70,NF,NF,99,, 86 | 85,The NFtioNFl pain maNFgement guideline: implications for neoNFtal intensive care. Agency for Health Care Policy and Research,41,NF,19,NF,,NF 87 | 86,McNFy Memorial Research and Demonstration Farm Summary,0,NF,NF,0,, 88 | 87,Research Directions in Multiattribute Utility ANFlysis,28,0,NF,180,182, 89 | 88,"Teachers, Administrators, and Researchers Work Together in Classroom Observation Research: A ProfessioNFl Development Opportunity.",0,0,NF,NF,26, 90 | 89,Immersive Multimodal Interactive Presence - Psychological Experiments in Haptic Collaboration Research,4,NF,NF,87,, 91 | 90,"Global health challenges: trends in public health training, research and advocacy.",5,NF,2,NF,,28 92 | 91,Changes in Perspective: Feminist Research in the Social Sciences,4,NF,NF,45,, 93 | 92,Supplementary research on the changes caused by accommodation in the refractive elements of the eyes. I. Improvement of the infrared phacometer,0,NF,0,NF,,NF 94 | 93,"Education in sub‐Saharan Africa: researching access, transitions and equity",44,6,10,0,0,NF 95 | 94,Annual Review of Cybertherapy and Telemedicine - Between Cyberplace and Cyberspace: the researcher's role in virtual setting research.,8,NF,7,7,,NF 96 | 95,MODEL SYSTEMS IN TERATOLOGY RESEARCH,10,NF,NF,52,, 97 | 96,Issues in the dating violence research: A review of the literature,349,95,122,95,77,NF 98 | 97,Legal Research: The Resource Base and TraditioNFl Approaches:,6,NF,0,2,,NF 99 | 98,NFtural Rubber Industry: Research and Development,0,0,0,1,0,NF 100 | 99,Deciding to Enter the Research Field,0,NF,NF,0,, 101 | 100,Short athletes in view of anthropometric research,0,NF,0,NF,,NF -------------------------------------------------------------------------------- /inst/extdata/import.csv: -------------------------------------------------------------------------------- 1 | ,title,GScites,WoScites,Scopuscites,Actualrefs,WoSrefs,Scopusrefs 2 | 1,"The Cognitive, Instrumental and InstitutioNFl Origins of NFnoscale Research: The Place of Biology",7,NF,NF,13,, 3 | 2,"AIDS research ""spin-off""--ganciclovir CMV prevention after liver transplantation.",1,NF,1,NF,,NF 4 | 3,Informed consent in medical research. Risk of bias may be another reason not to seek consent.,4,NF,3,3,,NF 5 | 4,Overseas Market Research,1,NF,NF,0,, 6 | 5,Große Unterschiede im Verfahren zur Ernennung zum außerplanmäßigen Professor@@@Major differences in procedure in receiving the academic title “ausserplanmaessiger Professor”. Criteria and evaluation of research and teaching at German medical faculties: Verfahren und Kriterien der Forschung und Lehre an deutschen Fakultäten,0,2,2,4,4,4 7 | 6,"Hagenberg Research - Parallel, Distributed, and Grid Computing",1,NF,NF,93,, 8 | 7,Adult Education Research: General,9,NF,NF,11,, 9 | 8,A Place for Research Strategies in Clinical Psychology,0,NF,NF,20,, 10 | 9,The Importance of Ethics in Scientific Research,0,NF,NF,NF,, 11 | 10,A Plea for Improvement of Research in Mathematics Education.,0,NF,NF,9,, 12 | 11,Animal Models in Biomedical Research,10,NF,4,NF,,22 13 | 12,Basic Research and Implementation Decisions for a Text-to-Speech Synthesis System in Romanian,36,NF,19,24,,24 14 | 13,On the Need of New Mechanisms for the Protection of Intellectual Property of Research Universities,4,NF,NF,42,, 15 | 14,BIODIVERSITY RESEARCH: Geographical linkages between threats and imperilment in freshwater fish in the Mediterranean Basin,59,44,43,43,42,43 16 | 15,AIDS-Antiviral NFtural Products Research at The U.S. NFtioNFl Cancer Institute,2,NF,NF,28,, 17 | 16,Fathers' and Mothers' Participation in Research.,31,11,16,NF,23,NF 18 | 17,"Australian physiological research in the Antarctic and Subantarctic, with special reference to thermal stress and acclimatization",12,NF,NF,43,, 19 | 18,Understanding the impact of research on policy using Altmetric data,2,NF,NF,0,, 20 | 19,Progress in genetic research of lactic acid bacteria,6,NF,NF,75,, 21 | 20,Becoming a Self-Reflective Teacher: A Meaningful Research Process,42,NF,NF,41,, 22 | 21,Protein Patent Problem to Hinder Research,1,1,0,0,0,NF 23 | 22,Implementation of a Performance Measurement Framework in Greek Manufacture: An Empirical Research,0,0,NF,7,7, 24 | 23,"Tipping points, thresholds and the keystone role of physiology in marine climate change research.",84,60,60,178,178,178 25 | 24,The Challenge Facing Infant Research in the Next Decade,8,NF,NF,41,, 26 | 25,A Simple Research Impacts Model Applied to the Information Systems Field,7,NF,5,31,,31 27 | 26,"Integrating Objectives for Clinical Education, Research, and Service Learning into Community Health Promotion Projects",19,NF,NF,27,, 28 | 27,Research and Implementation of Vehicle Detection in Intelligent Transportation System,NF,0,0,9,9,9 29 | 28,FAO/IAEA co-ordiNFted research programme on enhancement of nitrogen fixation in leguminous crops,0,NF,NF,22,, 30 | 29,Using EducatioNFl Research as a Resource for Continuous Improvement in Education: The Best Evidence Syntheses,3,NF,1,19,,19 31 | 30,Handbook of Research on Fuzzy Information Processing in Databases - How to Achieve Fuzzy RelatioNFl Databases MaNFging Fuzzy Data and Metadata,140,NF,NF,60,, 32 | 31,"Women in Magazines : Research, Representation, Production and Consumption",12,NF,2,NF,,260 33 | 32,Möglichkeiten und Grenzen der Anwendung von Methoden des Operations Research für die Begründung von Instandhaltungsstrategien,0,NF,NF,NF,, 34 | 33,Research on the System Model of Network Intrusion Detection,1,0,1,12,12,12 35 | 34,"Methodological Issues in Cross-Cultural MaNFgement Research: Problems, Solutions, and Proposals",67,NF,31,92,,92 36 | 35,Measuring the Effects of a RESEARCH-BASED Field Experience on Undergraduates and K - 12 Teachers,57,NF,22,19,,19 37 | 36,General practice research in Denmark: status and organization. The Danish Society of General Practice,0,NF,NF,NF,, 38 | 37,Research for Leaders: The NFtioNFl College for School Leadership,0,NF,0,NF,,29 39 | 38,"Inflation accounting survey: CACA & Market Opinion Research InterNFtioNFl Research Study for CACA (London, 1987). 40 pp. £7.50 (pbk)",0,NF,NF,NF,, 40 | 39,Procurement of State-of-the-Art Research Equipment to Support Faculty Members Within the RNFi Therapeutics Institute,0,NF,NF,NF,, 41 | 40,PAKDD (2) - Learning the funding momentum of research projects,4,1,4,19,19,19 42 | 41,Urban research: Cambridge fights nerve gases,0,0,0,0,0,NF 43 | 42,Research Methods in Language and Education - Researching Developing Discourses and Competences in Immersion Classrooms,1,NF,NF,NF,, 44 | 43,Strategies and tactics of human behavioral research,3,NF,NF,8,, 45 | 44,Russia's age structure in 1996: a research report.,1,0,2,NF,17,17 46 | 45,RESEARCH AND DEVELOPMENT OF HIGH-RESOLUTION STEREOLITHOGRAPHY SYSTEM FOR SMALL OBJECTS,4,NF,2,NF,,4 47 | 46,Helping Research Programs Demonstrate Impact,0,NF,NF,NF,, 48 | 47,Social Indicators Research: A Case Study of the Development of a JourNFl,2,NF,NF,2,, 49 | 48,Gravity Research Missions reviewed in light of the indirect ocean tide potential.,8,NF,NF,24,, 50 | 49,Research of open situation x-ray of cervical vertebra in healthy people,0,NF,0,NF,,NF 51 | 50,ICIC (1) - Research on image mosaic algorithm based on computer wizard vector field algorithm,3,0,1,9,9,9 52 | 51,Recent progressions in stem cell research: breakthroughs achieved and challenges faced.,11,NF,4,39,,NF 53 | 52,Screening for psychosocial risk in an urban preNFtal clinic population: a retrospective practice-based research study.,17,9,11,32,32,32 54 | 53,Microspectroscopy as a tool to discrimiNFte NFno-molecular cellular alterations in biomedical research.,24,NF,NF,154,, 55 | 54,CoordiNFting societies of research agents—IMS experience,5,4,4,22,22,22 56 | 55,THE GROWTH OF LOGIC OUT OF THE FOUNDATIONFL RESEARCH IN MATHEMATICS,20,NF,NF,15,, 57 | 56,Democratising Mathematics Education and the Role of Research,0,NF,NF,20,, 58 | 57,"If life gives you limes, make mojitos!: Identifying stakeholders, selling user experience research, and dealing with difficult people and situations",NF,NF,NF,NF,, 59 | 58,Turk researchers harassed,0,NF,0,NF,,NF 60 | 59,Numerical Research of Combustion Efficiency of a LOX/GCH4 Shear Coaxial Injector,NF,0,NF,NF,6, 61 | 60,Molecular mechanisms of the cross-impact of pathological processes in combined diabetes and cancer. Research and clinical aspects.,18,5,6,233,233,233 62 | 61,OccupatioNFl health nursing research priorities: a changing focus.,12,NF,3,5,,5 63 | 62,"SavanNFh River Ecology Laboratory annual technical progress report of ecological research, period ending July 31, 1993",0,NF,NF,NF,, 64 | 63,Research on Key Techniques of Digital SigNFl Processing Theory for Electric Machines and Motor Control,0,0,NF,8,8, 65 | 64,Advances in research on extra-oesophageal symptoms of pediatric gastroesophageal reflux,5,NF,0,55,,55 66 | 65,Knowledge Mining: A Quantitative Synthesis of Research Results and Findings,3,0,0,34,33,34 67 | 66,Physical Chemical Techniques. Vol. II of Physical Techniques in Biological Research,12,NF,NF,NF,, 68 | 67,Summary of research on the effects of topographic amplification of earthquake shaking on slope stability,58,NF,NF,27,, 69 | 68,Testing – Practice and Research Techniques - Testing - Practice and Research Techniques,0,NF,0,NF,,NF 70 | 69,Overview of Service Science Research System,0,NF,NF,9,, 71 | 70,InterNFtioNFl Performance Research Institute (IPRI),0,NF,NF,0,, 72 | 71,EducatioNFl and vocatioNFl choices of gifted individuals: guidance counseling and research for adolescents into adulthood,3,NF,NF,142,, 73 | 72,The `Qualitative' Versus `Quantitative' Research Debate: A Question of Metaphorical Assumptions?,170,NF,NF,29,, 74 | 73,Response of western mountain ecosystems to climatic variability and change: a collaborative research approach,2,NF,3,NF,,109 75 | 74,Social Workers' Perceptions of Privacy: A Research Note,1,0,0,12,12,11 76 | 75,"Supporting Frontier Research, Which Institutions and Which Processes",4,NF,NF,46,, 77 | 76,"Implications, Limitations and Future Research",0,NF,NF,0,, 78 | 77,Research in gold mining and metallurgy,0,NF,0,0,,NF 79 | 78,Methods of data aNFlysis in person-oriented research — The sample case of ANOVA,8,NF,NF,17,, 80 | 79,Summary report on the research needs and priorities in the tribology of boundary layer lubrication,0,0,0,0,0,NF 81 | 80,Research Needs Identified at Workshop,0,NF,NF,0,, 82 | 81,Collection and aNFlysis of Health Physics Research Reactor operatioNFl and use data,2,NF,NF,17,, 83 | 82,Research on Automatic Layout Planning and Performance ANFlysis System of Production Line Based on Simulation,0,NF,0,10,,10 84 | 83,The Research of Ontology Merging Based on Rough Concept Lattice Isomorphic Merging,0,NF,0,3,,3 85 | 84,Children and Consent to Participate in Research,70,NF,NF,99,, 86 | 85,The NFtioNFl pain maNFgement guideline: implications for neoNFtal intensive care. Agency for Health Care Policy and Research,41,NF,19,NF,,NF 87 | 86,McNFy Memorial Research and Demonstration Farm Summary,0,NF,NF,0,, 88 | 87,Research Directions in Multiattribute Utility ANFlysis,28,0,NF,180,182, 89 | 88,"Teachers, Administrators, and Researchers Work Together in Classroom Observation Research: A ProfessioNFl Development Opportunity.",0,0,NF,NF,26, 90 | 89,Immersive Multimodal Interactive Presence - Psychological Experiments in Haptic Collaboration Research,4,NF,NF,87,, 91 | 90,"Global health challenges: trends in public health training, research and advocacy.",5,NF,2,NF,,28 92 | 91,Changes in Perspective: Feminist Research in the Social Sciences,4,NF,NF,45,, 93 | 92,Supplementary research on the changes caused by accommodation in the refractive elements of the eyes. I. Improvement of the infrared phacometer,0,NF,0,NF,,NF 94 | 93,"Education in sub‐Saharan Africa: researching access, transitions and equity",44,6,10,0,0,NF 95 | 94,Annual Review of Cybertherapy and Telemedicine - Between Cyberplace and Cyberspace: the researcher's role in virtual setting research.,8,NF,7,7,,NF 96 | 95,MODEL SYSTEMS IN TERATOLOGY RESEARCH,10,NF,NF,52,, 97 | 96,Issues in the dating violence research: A review of the literature,349,95,122,95,77,NF 98 | 97,Legal Research: The Resource Base and TraditioNFl Approaches:,6,NF,0,2,,NF 99 | 98,NFtural Rubber Industry: Research and Development,0,0,0,1,0,NF 100 | 99,Deciding to Enter the Research Field,0,NF,NF,0,, 101 | 100,Short athletes in view of anthropometric research,0,NF,0,NF,,NF -------------------------------------------------------------------------------- /inst/extdata/model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nealhaddaway/citationchaser/ba382a7336fbc61090b092e67db9ddecfcc4e8ca/inst/extdata/model.png -------------------------------------------------------------------------------- /inst/shiny-examples/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nealhaddaway/citationchaser/ba382a7336fbc61090b092e67db9ddecfcc4e8ca/inst/shiny-examples/.DS_Store -------------------------------------------------------------------------------- /inst/shiny-examples/citationchaser/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nealhaddaway/citationchaser/ba382a7336fbc61090b092e67db9ddecfcc4e8ca/inst/shiny-examples/citationchaser/.DS_Store -------------------------------------------------------------------------------- /inst/shiny-examples/citationchaser/app.R: -------------------------------------------------------------------------------- 1 | suppressPackageStartupMessages({ 2 | library(shiny) 3 | library(DT) 4 | library(shinycssloaders) 5 | library(data.table) 6 | library(tibble) 7 | library(dplyr) 8 | library(httr) 9 | library(maditr) 10 | library(scales) 11 | library(tidyr) 12 | library(networkD3) 13 | library(stringr) 14 | library(shinybusy) 15 | library(sendmailR) 16 | library(MESS) 17 | }) 18 | 19 | options(shiny.sanitize.errors = TRUE) 20 | 21 | source('functions.R') 22 | source('mixed_id_wrapper.R') 23 | 24 | # error supression CSS 25 | tags$style(type="text/css", 26 | ".shiny-output-error { visibility: hidden; }", 27 | ".shiny-output-error:before { visibility: hidden; }" 28 | ) 29 | 30 | # Define UI for application that draws a histogram 31 | ui <- navbarPage("citationchaser", id = "tabs", 32 | 33 | # Sidebar with a slider input for number of bins 34 | tabPanel("Home", 35 | fluidRow( 36 | column(10, 37 | h3('Welcome to citationchaser!'), 38 | br(), 39 | 'In searching for research articles, we often want to obtain lists of references from across studies, and also obtain lists of articles that cite a particular study. In systematic reviews, this supplementary search technique is known as "citation chasing": forward citation chasing looks for all records citing one or more articles of known relevance; backward ciation chasing looks for all records referenced in one or more articles.', 40 | br(),br(), 41 | 'Traditionally, this process would be done manually, and the resulting records would need to be checked one-by-one against included studies in a review to identify potentially relevant records that should be included in a review.', 42 | br(),br(), 43 | 'This package contains functions to automate this process by making use of the Lens.org API. An input article list can be used to return a list of all referenced records, and/or all citing records in the Lens.org database (consisting of PubMed, PubMed Central, CrossRef, Microsoft Academic Graph and CORE); ', 44 | tags$a(href="https://www.lens.org", "read more here."), 45 | br(),br(), 46 | 'Large searches may take several minutes to complete, so please be patient.', 47 | br(),br(), 48 | 'Consider asking your library to support Lens.org to continue to enable Open Discovery of research articles.'), 49 | column(2, 50 | br(),tags$img(height = 200, src = "https://github.com/nealhaddaway/citationchaser/blob/master/inst/extdata/citationchaser.png?raw=true")), 51 | column(12, 52 | br(), 53 | h4('Follow these steps to start chasing!'), 54 | icon("arrow-right"),' In the "Article input" tab, paste a list of article identifiers (e.g. DOIs)', 55 | br(),br(), 56 | icon("arrow-right"),' Check the articles returned are the ones your interested in', 57 | br(),br(), 58 | icon("arrow-right"),' If you want to perform backward citation chasing (which articles did my articles reference?) then proceed to the "References" tab and click "Search for all referenced articles in Lens.org"', 59 | br(),br(), 60 | icon("arrow-right"),' If you want to perform forward citation chasing (which articles have cited my articles?) then proceed to the "Citations" tab and click "Search for all citing articles in Lens.org"', 61 | br(),br(), 62 | icon("arrow-right"),' You can download in RIS format a list of your input articles, referenced articles, and citing articles for easy integration with your reference/review management workflow', 63 | br(),br(), 64 | icon("arrow-right"),' Once you have finished citation chasing, why not check out the citation network visualisation in the "Network" tab? Please be aware that large citation networks (many or highly cited starting articles) may take considerable time to load.', 65 | hr(), 66 | h4('Developers'), 67 | 'Note that you can now refer users to citationchaser with a preloaded set of article identifiers, which will be initially run, ready for citationchasing. Simply concatenate the following URL stem with a comma-separated list of identifiers:',br(), 68 | 'https://estech.shinyapps.io/citationchaser/?dois=[doi1],[doi2],[doi3]&pmids=9pmid1],[pmid2],[pmid3].',br(), 69 | 'Any identifier type can be used together for multiple concurrent searches.',br(),br(), 70 | 'This works for DOIs (\'dois=\'), PubMed IDs (\'pmids=\'), PMC IDs (\'pmcids=\'), CORE IDs (\'coreids=\'), and Microsoft Academic IDs (\'magids=\'), as per the following worked example:',br(), 71 | tags$a(href="https://estech.shinyapps.io/citationchasertest/?dois=10.1038/s41559-020-01295-x,10.1371/journal.pone.0138237&pmids=32652585,32706299", "https://estech.shinyapps.io/citationchasertest/?dois=10.1038/s41559-020-01295-x,10.1371/journal.pone.0138237&pmids=32652585,32706299"),br(), 72 | hr(), 73 | tags$img(height = 30, src = "https://static.lens.org/lens/7.2.3/img/home-page/Lens-logo-tagline.png"), 74 | br(), 75 | 'The Lens is a service provided by the not-for-profit organisation Cambia. This application and its developers have no financial relationship with The Lens or with Cambia.', 76 | br(),br(), 77 | 'If you\'d like to cite this work, please use:', 78 | br(), 79 | 'Haddaway, N. R., Grainger, M. J., Gray, C. T. (2021) citationchaser: An R package and Shiny app for forward and backward citations chasing in academic searching. doi:', tags$a(href="https://www.doi.org/10.5281/zenodo.4543513", "10.5281/zenodo.4543513"), 80 | br(), 81 | icon("save"),tags$a(href="citation.ris", "Download package citation (.ris)", download=NA, target="_blank"), 82 | br(),br(), 83 | icon("github"),tags$a(href="https://github.com/nealhaddaway/citationchaser", "See the GitHub repository") 84 | ) 85 | ) 86 | ), 87 | 88 | # Sidebar with a slider input for number of bins 89 | tabPanel("Article input", 90 | fluidRow( 91 | column(12, 92 | h4('Enter the articles that you want to start from. We will first check the full citations in the Lens.org database.'), 93 | 'You must complete this step before retrieving references and citations.', 94 | br(), 95 | br(), 96 | 'EITHER:',br(), 97 | '1: Paste your identifiers in (each id separated from the next using a comma, spaces are permitted)', 98 | br(),br(), 99 | splitLayout(textAreaInput("article_ids_doi","Digital Object Identifiers (DOIs)", placeholder = "separate identifiers with a comma", width = '90%'), 100 | textAreaInput("article_ids_pmid","PubMed identifiers (PMIDs)", placeholder = "separate identifiers with a comma", width = '90%'), 101 | textAreaInput("article_ids_pmcid","PubMedCentral identifiers (PMCIDs)", placeholder = "separate identifiers with a comma", width = '90%')), 102 | splitLayout(textAreaInput("article_ids_magid","Microsoft Academic identifiers (MAGIDs)", placeholder = "separate identifiers with a comma", width = '90%'), 103 | textAreaInput("article_ids_coreid","CORE identifiers (COREIDs)", placeholder = "separate identifiers with a comma", width = '90%'), 104 | textAreaInput("article_ids_lens_id","Lens.org identifiers (LENSIDS)", placeholder = "separate identifiers with a comma", width = '90%')), 105 | br(),br(), 106 | 'OR: ',br(), 107 | '2: Upload your data as a 2-column file here. Your file must have the column names "ids" (each identifier on a new row) and "type" of id ("doi", "pmid", "pmcid", "magid", "coreid", "lens_id"):', 108 | br(),br(), 109 | tags$img(height = 150, src='example.png'), 110 | br(),br(), 111 | tags$a(href="example.csv", "Download an example input CSV file.", download=NA, target="_blank"), 112 | br(),br(), 113 | fileInput("data_upload", "Choose CSV File", 114 | multiple = FALSE, 115 | accept = c("text/csv", 116 | "text/comma-separated-values,text/plain", 117 | ".csv")), 118 | textInput("token", "Paste your Lens.org token here:"), 119 | 'Visit Lens.org to obtain a token for access to their scholarly API (see "Home" tab).', 120 | br(), 121 | br(), 122 | actionButton("find_inputs", "Load my input articles", class = "btn-info"), 123 | actionButton("reset", "Reset", class = "btn-warning"), 124 | add_busy_spinner(spin = "fading-circle", color = "#19d0fc", margins = c(70, 20)) 125 | ), 126 | 127 | # Show table of results 128 | column(12, 129 | conditionalPanel( 130 | condition='input.find_inputs!=null && input.find_inputs!=""', 131 | h3('Your input articles'), 132 | br(), 133 | textOutput('article_report'), 134 | br(), 135 | uiOutput('download_art'), 136 | br(),br()), 137 | add_busy_spinner(spin = "fading-circle", color = "#19d0fc", margins = c(70, 20)), 138 | dataTableOutput('article_ref') 139 | ) 140 | ) 141 | ), 142 | 143 | tabPanel("References", 144 | fluidRow( 145 | column(12, 146 | h3('References from your articles (backward citation chasing)'), 147 | br(), 148 | 'Once you have loaded your input articles, you can search for all referenced articles across them.', 149 | br(), 150 | br(), 151 | br(), 152 | conditionalPanel( 153 | condition='(output.redirect_goahead!=null && output.redirect_goahead!="") || (input.find_inputs!=null && input.find_inputs!="")', 154 | actionButton("find_refs", "Search for all referenced articles in Lens.org", class = "btn-info"), 155 | actionButton("reset2", "Reset", class = "btn-warning"), 156 | br(),br(), 157 | textOutput('refs_report'), 158 | br(), 159 | conditionalPanel( 160 | condition='input.find_refs!=null && input.find_refs!=""', 161 | downloadButton('refs_ris', 'Download an RIS file of referenced articles (including abstracts)')), 162 | br(), 163 | add_busy_spinner(spin = "fading-circle", color = "#19d0fc", margins = c(70, 20)), 164 | dataTableOutput('references')) 165 | ) 166 | ) 167 | ), 168 | tabPanel("Citations", 169 | fluidRow( 170 | column(12, 171 | h3('Citations of your articles (forward citation chasing)'), 172 | br(), 173 | 'Once you have loaded your input articles, you can search for all articles that cite them.', 174 | br(), 175 | br(), 176 | br(), 177 | conditionalPanel( 178 | condition='(output.redirect_goahead!=null && output.redirect_goahead!="") || (input.find_inputs!=null && input.find_inputs!="")', 179 | actionButton("find_cits", "Search for all citing articles in Lens.org", class = "btn-info"), 180 | actionButton("reset3", "Reset", class = "btn-warning"), 181 | br(),br(), 182 | textOutput('cits_report'), 183 | br(), 184 | conditionalPanel( 185 | condition='input.find_cits!=null && input.find_cits!=""', 186 | downloadButton('cits_ris', 'Download an RIS file of citing articles (including abstracts)')), 187 | br(), 188 | add_busy_spinner(spin = "fading-circle", color = "#19d0fc", margins = c(70, 20)), 189 | dataTableOutput('citations')) 190 | ) 191 | ) 192 | ), 193 | 194 | tabPanel("Network", 195 | fluidRow( 196 | column(12, 197 | h3('Visualise the citation network'), 198 | br(), 199 | 'Once you have loaded your input articles and searched for references and citations, you will be able to visualise your network here. Please be aware that large citation networks (many or highly cited starting articles) may take considerable time to load. Consider downloading your citation chasing results before visualising the network.', 200 | br(), 201 | br(), 202 | br(), 203 | conditionalPanel( 204 | condition='(output.redirect_goahead!=null && output.redirect_goahead!="") 205 | || ((input.find_refs!=null && input.find_refs!="") && (input.find_cits!=null && input.find_cits!=""))', 206 | actionButton("get_network", "Visualise"), tags$img(height = 80, src = "legendnew.png"), 207 | br(), 208 | br(), 209 | 'The network visualisation may take a few moments to generate. Zoom in and out using your mouse wheel or two fingers on a trackpad. Move around the network by clicking and dragging. Click on a node to see the record on Lens.org.', 210 | br(), 211 | add_busy_spinner(spin = "fading-circle", color = "#19d0fc", margins = c(70, 20)), 212 | conditionalPanel( 213 | condition='input.get_network!=null && input.get_network!=""', 214 | forceNetworkOutput("force", height = '1100px'))) 215 | ) 216 | ) 217 | ) 218 | ) 219 | 220 | # Define server logic required to draw a histogram 221 | server <- function(input, output, session) { 222 | 223 | rv <- reactiveValues() 224 | 225 | # detect redirect PMIDs in Shiny URL 226 | observe({ 227 | x <- parseQueryString(session$clientData$url_search) 228 | rv$dois <- x$dois 229 | rv$pmids <- x$pmids 230 | rv$pmcids <- x$pmcids 231 | rv$magids <- x$magids 232 | rv$coreids <- x$coreids 233 | rv$lens_ids <- x$lens_ids 234 | 235 | if (is.null(x$dois) == FALSE){ 236 | rv$redirect <- TRUE 237 | rv$doi_inputs <- data.frame(ids = x$dois, 238 | type = rep('doi', length(x$dois))) 239 | } else if (is.null(x$pmids) == FALSE){ 240 | rv$redirect <- TRUE 241 | rv$pmid_inputs <- data.frame(ids = x$pmids, 242 | type = rep('pmid', length(x$pmids))) 243 | } 244 | else if (is.null(x$pmcids) == FALSE){ 245 | rv$redirect <- TRUE 246 | rv$pmcid_inputs <- data.frame(ids = x$pmcids, 247 | type = rep('pmcid', length(x$pmcids))) 248 | } else if (is.null(x$magids) == FALSE){ 249 | rv$redirect <- TRUE 250 | rv$magid_inputs <- data.frame(ids = x$magids, 251 | type = rep('magid', length(x$magids))) 252 | } else if (is.null(x$coreids) == FALSE){ 253 | rv$redirect <- TRUE 254 | rv$coreid_inputs <- data.frame(ids = x$coreids, 255 | type = rep('coreid', length(x$coreids))) 256 | } else if (is.null(x$lens_ids) == FALSE){ 257 | rv$redirect <- TRUE 258 | rv$lens_id_inputs <- data.frame(ids = x$lens_ids, 259 | type = rep('lens_id', length(x$lens_ids))) 260 | } else { 261 | rv$redirect <- FALSE 262 | updateTextInput(session, "type", value = 'doi') 263 | } 264 | #build article ids dataframe 265 | rv$article_ids <- data.frame(ids = c(rv$pmids, rv$dois, rv$pmcids, rv$magids, rv$coreids, rv$lens_ids), 266 | type = c(rep('pmid', length(rv$pmids)), 267 | rep('doi', length(rv$dois)), 268 | rep('pmcid', length(rv$pmcids)), 269 | rep('magid', length(rv$magids)), 270 | rep('coreid', length(rv$coreids)), 271 | rep('lens_id', length(rv$lens_ids)))) 272 | #build report of identifier types searched 273 | rv$types <- paste0(rep('DOI', length(rv$dois)), if(is.null(rv$pmids)==FALSE){', '}, 274 | rep('PMID', length(rv$pmids)), if(is.null(rv$pmcids)==FALSE){', '}, 275 | rep('PMCID', length(rv$pmcids)), if(is.null(rv$magids)==FALSE){', '}, 276 | rep('MAGID', length(rv$magids)), if(is.null(rv$coreids)==FALSE){', '}, 277 | rep('COREID', length(rv$coreids)), if(is.null(rv$lens_ids)==FALSE){', '}, 278 | rep('LENSID', length(rv$lens_ids))) 279 | rv$article_number <- nrow(rv$article_ids) 280 | 281 | # show redirect tab if redirect URL used 282 | if (rv$redirect == TRUE){ 283 | 284 | if (rv$article_number > 100){ 285 | article_ref <- '' 286 | rv$articles <- '' 287 | rv$articles_ris <- '' 288 | rv$articles_df <- '' 289 | } else { 290 | article_ref <- get_refs_mixed(rv$article_ids, 291 | search = 'input', 292 | token = 'WCFlpCtuJXYI1sDhZcZ8y7hHpri0SEmTnLNkeU4OEM5JTQRNXB9w') 293 | print(rv$article_ids) 294 | if (substr(article_ref[1], start = 1, stop = 7) == 'Warning'){ 295 | rv$articles <- data.frame(Error = article_ref) 296 | rv$articles_ris <- NULL 297 | rv$articles_df <- NULL 298 | rv$input_lens_ids <- NULL 299 | } else { 300 | rv$articles <- article_ref$display 301 | rv$articles_ris <- article_ref$ris 302 | rv$articles_df <- article_ref$df 303 | rv$input_lens_ids <- rv$articles_df[["data.lens_id"]] 304 | } 305 | } 306 | # render article report text 307 | rv$article_report <- 308 | if (rv$article_number > 100){ 309 | 'The maximum number of starting articles is 100. Please revise your search and try again.' 310 | } else { 311 | paste0('You provided ', nrow(rv$articles), ' starting articles using the following IDs (using a URL redirect): ', rv$types) 312 | } 313 | # render articles table 314 | rv$article_ref <- renderDataTable({ 315 | if (rv$article_number > 100){ 316 | } else { 317 | rv$articles 318 | } 319 | }, options = list(dom = 'tpl'), rownames = FALSE) 320 | # download articles as RIS 321 | rv$input_ris <- downloadHandler( 322 | filename = function(){ 323 | paste("articles-", Sys.Date(), ".ris", sep = "") 324 | }, 325 | content = function(file) { 326 | write.table(rv$articles_ris, file,col.names=FALSE) 327 | } 328 | ) 329 | 330 | #display Redirect tab 331 | removeTab(inputId = "tabs", 332 | target = "Article input") 333 | insertTab(inputId = "tabs", 334 | tabPanel("Your articles", 335 | fluidRow( 336 | column(12, 337 | h3('Your input articles'), 338 | br(), 339 | rv$article_report, 340 | br(), br(), 341 | rv$article_ref, 342 | br(), 343 | rv$input_ris, 344 | actionButton("reset_redirect", "Reset", class = "btn-warning", 345 | onclick ="location.href='https://estech.shinyapps.io/citationchaser/';") 346 | #, 347 | #textAreaInput("article_ids","Your input article IDs", placeholder = "separate identifiers with a comma", width = '90%') 348 | ) 349 | )), 350 | target = "Home", 351 | position = 'after', 352 | select = TRUE 353 | ) 354 | } 355 | }) 356 | 357 | # render dummy output to trigger redirect 358 | output$redirect_goahead <- reactive({ 359 | if (rv$redirect == TRUE){ 360 | 'TRUE' 361 | } 362 | }) 363 | outputOptions(output, "redirect_goahead", suspendWhenHidden = FALSE) 364 | 365 | 366 | # reset button 367 | observeEvent(input$reset,{ 368 | rv$articles <- NULL 369 | rv$articles_ris <- NULL 370 | }) 371 | observeEvent(input$reset2,{ 372 | rv$refs_display <- NULL 373 | rv$refs_report <- NULL 374 | rv$refs_ris <- NULL 375 | }) 376 | observeEvent(input$reset3,{ 377 | rv$cits_display <- NULL 378 | rv$cits_report <- NULL 379 | rv$cits_ris <- NULL 380 | }) 381 | 382 | # build articles table 383 | observeEvent(input$find_inputs,{ 384 | 385 | if(is.null(input$data_upload) == FALSE){ 386 | rv$inputs <- read.csv(input$data_upload$datapath) 387 | rv$article_number <- nrow(rv$inputs) 388 | } else { 389 | #number of inputs 390 | rv$doi_number <- length(strsplit(input$article_ids_doi, ',')[[1]]) 391 | rv$pmid_number <- length(strsplit(input$article_ids_pmid, ',')[[1]]) 392 | rv$pmcid_number <- length(strsplit(input$article_ids_pmcid, ',')[[1]]) 393 | rv$magid_number <- length(strsplit(input$article_ids_magid, ',')[[1]]) 394 | rv$coreid_number <- length(strsplit(input$article_ids_coreid, ',')[[1]]) 395 | rv$lens_id_number <- length(strsplit(input$article_ids_lens_id, ',')[[1]]) 396 | 397 | rv$article_number <- rv$doi_number + 398 | rv$pmid_number + 399 | rv$pmcid_number + 400 | rv$magid_number + 401 | rv$coreid_number + 402 | rv$lens_id_number 403 | 404 | rv$inputs <- data.frame(ids = c(input$article_ids_doi, 405 | input$article_ids_pmid, 406 | input$article_ids_pmcid, 407 | input$article_ids_magid, 408 | input$article_ids_coreid, 409 | input$article_ids_lens_id), 410 | type = c(rep('doi', length(input$article_ids_doi)), 411 | rep('pmid', length(input$article_ids_pmid)), 412 | rep('pmcid', length(input$article_ids_pmcid)), 413 | rep('magid', length(input$article_ids_magid)), 414 | rep('coreid', length(input$article_ids_coreid)), 415 | rep('lens_id', length(input$article_ids_lens_id)))) 416 | rv$inputs <- subset(rv$inputs, rv$inputs$ids!='') 417 | } 418 | print(rv$inputs) 419 | 420 | #if (rv$article_number > 100){ 421 | # article_ref <- '' 422 | # rv$articles <- '' 423 | # rv$articles_ris <- '' 424 | # rv$articles_df <- '' 425 | #} else { 426 | if(is.null(rv$inputs) == FALSE){ 427 | rv$inputs <- rv$inputs 428 | } 429 | 430 | article_ref <- get_refs_mixed(rv$inputs, 431 | search = 'input', 432 | token = 'WCFlpCtuJXYI1sDhZcZ8y7hHpri0SEmTnLNkeU4OEM5JTQRNXB9w') 433 | print('success') 434 | if (substr(article_ref[1], start = 1, stop = 7) == 'Warning'){ 435 | rv$articles <- data.frame(Error = article_ref) 436 | rv$articles_ris <- NULL 437 | rv$articles_df <- NULL 438 | rv$input_lens_ids <- NULL 439 | } else { 440 | rv$articles <- article_ref$display 441 | rv$articles_ris <- article_ref$ris 442 | rv$articles_df <- article_ref$df 443 | print(rv$articles_df[["data.lens_id"]]) 444 | rv$input_lens_ids <- rv$articles_df[["data.lens_id"]] 445 | } 446 | #} 447 | 448 | # render article report text 449 | output$article_report <- renderText({ 450 | #rv$article_number <- 1 + str_count(input$article_ids,",") 451 | #if (rv$article_number > 100){ 452 | # 'The maximum number of starting articles is 100. Please revise your search and try again.' 453 | #} else { 454 | paste0('You provided ', rv$article_number, ' starting ', 455 | if(rv$article_number == 1){paste0('identifier')} else {paste0('identifiers')}, 456 | ', corresponding to ', nrow(rv$articles), 457 | if(nrow(rv$articles) == 1){paste0(' article:')} else {paste0(' articles:')}) 458 | #} 459 | }) 460 | # render number of articles 461 | output$download_art <- renderUI({ 462 | #rv$article_number <- 1 + str_count(input$article_ids,",") 463 | #if (rv$article_number < 101){ 464 | downloadButton('input_ris', 'Download an RIS file of your articles (including abstracts)') 465 | #} else { 466 | #} 467 | }) 468 | # render articles table 469 | output$article_ref <- renderDataTable({ 470 | #if ((1 + str_count(input$article_ids,",")) > 100){ 471 | #} else { 472 | rv$articles 473 | #} 474 | }, options = list(dom = 'tpl'), rownames = FALSE) 475 | # download articles as RIS 476 | output$input_ris <- downloadHandler( 477 | filename = function(){ 478 | paste("articles-", Sys.Date(), ".ris", sep = "") 479 | }, 480 | content = function(file) { 481 | write.table(rv$articles_ris, file,col.names=FALSE) 482 | } 483 | ) 484 | }) 485 | 486 | # build references table 487 | observeEvent(input$find_refs,{ 488 | references <- get_refs(rv$input_lens_ids, 489 | get_records = 'references', 490 | type = 'lens_id', 491 | token = 'WCFlpCtuJXYI1sDhZcZ8y7hHpri0SEmTnLNkeU4OEM5JTQRNXB9w') 492 | 493 | if (substr(references[1], start = 1, stop = 7) == 'Warning'){ 494 | rv$refs_display <- data.frame(Error = references) 495 | rv$refs_report <- NULL 496 | rv$refs_ris <- NULL 497 | rv$refs_df <- NULL 498 | } else { 499 | rv$refs_display <- references$display 500 | rv$refs_report <- references$report 501 | rv$refs_ris <- references$ris 502 | rv$refs_df <- references$df 503 | } 504 | }) 505 | # render references table 506 | output$references <- renderDataTable({ 507 | rv$refs_display 508 | }, rownames = FALSE, options = list(dom = 'tpl')) 509 | # render references report text 510 | output$refs_report <- renderText({ 511 | rv$refs_report 512 | }) 513 | # download references as RIS 514 | output$refs_ris <- downloadHandler( 515 | filename = function(){ 516 | paste("references-", Sys.Date(), ".ris", sep = "") 517 | }, 518 | content = function(file) { 519 | write.table(rv$refs_ris, file, col.names=FALSE) 520 | } 521 | ) 522 | 523 | # build citations table 524 | observeEvent(input$find_cits,{ 525 | citations <- get_refs(rv$input_lens_ids, 526 | get_records = 'citations', 527 | type = 'lens_id', 528 | token = 'WCFlpCtuJXYI1sDhZcZ8y7hHpri0SEmTnLNkeU4OEM5JTQRNXB9w') 529 | if (substr(citations[1], start = 1, stop = 7) == 'Warning'){ 530 | rv$cits_display <- data.frame(Error = citations) 531 | rv$cits_report <- NULL 532 | rv$cits_ris <- NULL 533 | rv$cits_df <- NULL 534 | } else { 535 | rv$cits_display <- citations$display 536 | rv$cits_report <- citations$report 537 | rv$cits_ris <- citations$ris 538 | rv$cits_df <- citations$df 539 | } 540 | }) 541 | # render citations table 542 | output$citations <- renderDataTable({ 543 | rv$cits_display 544 | }, rownames = FALSE, options = list(dom = 'tpl')) 545 | # render citations report text 546 | output$cits_report <- renderText({ 547 | rv$cits_report 548 | }) 549 | # download references as RIS 550 | output$cits_ris <- downloadHandler( 551 | filename = function(){ 552 | paste("citations-", Sys.Date(), ".ris", sep = "") 553 | }, 554 | content = function(file) { 555 | write.table(rv$cits_ris, file, col.names=FALSE) 556 | } 557 | ) 558 | 559 | 560 | # prepare lens_ids for network visualisation 561 | observeEvent(input$get_network,{ 562 | if (substr(rv$cits_display[[1]], start = 1, stop = 7) == 'Warning' && substr(rv$refs_display[[1]], start = 1, stop = 7) == 'Warning'){ 563 | rv$network <- 'Warning: Your input has no citation network (it has no references and has not been cited).' 564 | } else { 565 | input_refs <- unnest(rv$articles_df, data.references) 566 | input_refs <- data.frame(input_lensID = input_refs$data.lens_id, reference_lensID = input_refs$lens_id, type = 'reference') 567 | 568 | input_cits <- unnest(rv$articles_df, data.scholarly_citations) 569 | input_cits <- data.frame(input_lensID = input_cits$data.lens_id, reference_lensID = input_cits$data.scholarly_citations, type = 'citation') 570 | 571 | rv$network <- rbind(input_refs, input_cits) 572 | } 573 | }) 574 | #network viz 575 | output$force <- renderForceNetwork({ 576 | if (substr(rv$network[1], start = 1, stop = 7) == 'Warning'){ 577 | n_net <- rv$network 578 | } else { 579 | network=rv$network 580 | inputarticles=network$input_lensID 581 | tmp1<-data.frame("IDs"=network$input_lensID, "Group"= network$type) 582 | tmp2<-data.frame("IDs"=network$reference_lensID, "Group"= network$type) 583 | tmp=rbind(tmp1,tmp2) 584 | Nodes=unique(tmp) 585 | Nodes=Nodes %>% 586 | mutate(Group2=ifelse(IDs%in%inputarticles, 0, Group)) %>% 587 | mutate(Group2=as.character(Group2)) %>% 588 | mutate(Group2=dplyr::recode(Group2, "0"="input", "1"="reference", "2"="citation")) 589 | 590 | Nodes2<-Nodes %>% 591 | filter(!Group2=="input") 592 | 593 | inputs<-Nodes %>% 594 | filter(Group2=="input") %>% 595 | distinct(.,IDs,Group2, .keep_all=TRUE) 596 | 597 | Nodes=rbind(inputs, Nodes2) 598 | 599 | # make a links data frame using the indexes (0-based) of nodes in 'nodes' 600 | links <- data.frame(source = match(network$input_lensID, Nodes$IDs) - 1, 601 | target = match(network$reference_lensID,Nodes$IDs) - 1) 602 | 603 | links<-links %>% 604 | drop_na() 605 | 606 | n_net<-forceNetwork(Links = links, Nodes = Nodes, Source = "source", 607 | Target = "target", NodeID ="IDs", Group="Group2", 608 | linkColour = "black", 609 | opacity = 1, opacityNoHover = 1, zoom=TRUE, colourScale = JS('d3.scaleOrdinal().range(["black", "#a50026","#4575b4"]);')) 610 | 611 | n_net$x$nodes$hyperlink<-paste0('https://www.lens.org/lens/search/scholar/list?q=lens_id:', Nodes$IDs, '&p=0&n=10&s=_score&d=%2B&f=false&e=false&l=en&authorField=author&dateFilterField=publishedYear&orderBy=%2B_score&presentation=false&stemmed=true&useAuthorId=false') 612 | n_net$x$options$clickAction = 'window.open(d.hyperlink)' 613 | n_net 614 | } 615 | }) 616 | } 617 | 618 | # Run the application 619 | shinyApp(ui = ui, server = server) 620 | 621 | -------------------------------------------------------------------------------- /inst/shiny-examples/citationchaser/functions.R: -------------------------------------------------------------------------------- 1 | #' Automated citation chasing in systematic reviews 2 | #' 3 | #' @description This function takes a list of articles in the form of established 4 | #' identifiers (e.g. digital object identifiers) and sends a request to the 5 | #' lens.org API to firstly identify all cited references in all articles (in the 6 | #' form of lists of lens IDs), and then query these lens IDs to bring back full 7 | #' citation information for all listed records. Deduplicates references to the 8 | #' same records across articles, resulting in an RIS file and a summary report 9 | #' in the console. 10 | #' @param article_list List of article identifiers for which the reference 11 | #' lists will be returned. Must be a list/vector of identifiers, e.g. 12 | #' '"10.1186/s13750-018-0126-2" "10.1002/jrsm.1378"'. 13 | #' @param type Specification of the type of input provided. The default is 14 | #' 'doi' (digital object identifier), but any of the following are accepted: 15 | #' "pmid" (PubMed ID), "pmcid" (PubMed Central ID), "magid" (Microsoft 16 | #' Academic ID), "coreid" (CORE identifier), lens_id" (The Lens.org ID), 17 | #' "title" (article title; much lower specificity). 18 | #' @param get_records Specification of whether to look for records referenced 19 | #' within the input articles ('references'), records citing the input articles 20 | #' ('citations'), or both ('both'). 21 | #' @param save_object Option to save the resultant ris file as an object in 22 | #' the Global Environment. The default is FALSE. 23 | #' @param token An access key for the lens.org API. Tokens can be obtained by 24 | #' applying for scholarly API access and creating a token once approved. See 25 | #' 'https://www.lens.org/lens/user/subscriptions#scholar' for further details. 26 | #' @return An RIS file is saved to the working directory. A report is printed 27 | #' to the console. If 'save_object=TRUE', the RIS file is returned as an 28 | #' object 29 | #' @importFrom maditr vlookup 30 | #' @importFrom httr content 31 | #' @importFrom jsonlite fromJSON 32 | #' @importFrom utils write.table 33 | #' @importFrom tibble tibble 34 | #' @importFrom dplyr mutate group_split bind_rows 35 | #' @importFrom MESS cumsumbinning 36 | #' @export 37 | #' @examples 38 | #' \dontrun{ 39 | #' article_list <- c("10.1007/978-3-642-37048-9_13", 40 | #' "10.1111/sum.12030", 41 | #' "10.5194/bg-13-3619-2016", 42 | #' "10.1016/j.agee.2012.09.006") 43 | #' token <- 'token' 44 | #' refs <- get_refs(article_list, get_records = 'references', token = token) 45 | #' refs 46 | #' } 47 | get_refs <- function(article_list, 48 | type = 'doi', 49 | get_records, 50 | save_object = FALSE, 51 | token) { 52 | 53 | # set the maximum number of results returned for each query 54 | max_results <- 500 55 | 56 | # if the number of input articles is 1, then split the input list where there are commas (and strip out whitespace) 57 | if(length(article_list) == 1){ 58 | article_list <- trimws(unlist(strsplit(article_list, '[,]'))) 59 | } 60 | print('Input record list:') 61 | print(article_list) 62 | 63 | ## input article search 64 | # build query for input article search 65 | request1 <- paste0('{\n\t"query": {\n\t\t"terms": {\n\t\t\t"',type,'": ["', paste0('', paste(article_list, collapse = '", "'), '"'),']\n\t\t}\n\t},\n\t"size":500\n}') 66 | print('Input article request:') 67 | print(request1) 68 | 69 | # perform article search and extract text results 70 | data <- getLENSData(token, request1) 71 | print('Input request executed.') 72 | 73 | # report requests remaining within the limit (currently 50/min) 74 | requests_remaining <- data[["headers"]][["x-rate-limit-remaining-request-per-minute"]] 75 | print(paste0('Remaining requests = ', requests_remaining)) 76 | 77 | # extract the JSON content of the response 78 | record_json <- httr::content(data, "text") 79 | 80 | # convert json output from article search to list 81 | record_list <- jsonlite::fromJSON(record_json) 82 | print('Results converted to df from JSON output:') 83 | print(record_list$results) 84 | 85 | # error messages 86 | if (data$status_code == 404){ 87 | print('Error 404: no matched records') 88 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 89 | } 90 | if (data$status_code == 429){ 91 | print('Error 429: system overloaded') 92 | return('Warning: Right now there are too many people using citationchaser. This has been logged and we will endeavour to increase the bandwith as soon as possible. Please try again later.') 93 | } 94 | if (record_list$total == 0){ 95 | print('Error 1: no matched records') 96 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 97 | } 98 | 99 | # report number of input articles returned 100 | input_number <- record_list[["total"]] 101 | 102 | # list input article lens IDs (for later use) 103 | articles_id <- record_list[["data"]][["lens_id"]] 104 | print('Returned records from input request:') 105 | print(articles_id) 106 | 107 | ### search for citations of input articles 108 | if (get_records == 'citations') { 109 | print('Seeking citations...') 110 | 111 | # citations per article 112 | citation_count <- record_list[["data"]][["scholarly_citations_count"]] 113 | citation_count[is.na(citation_count)] <- 0 114 | print('Citations per record sought:') 115 | print(citation_count) 116 | # sum of all citations 117 | all_citations <- sum(citation_count, na.rm = TRUE) 118 | print('Total citations sought:') 119 | print(all_citations) 120 | 121 | # return error message if input article(s) have no citations 122 | #if (record_list[["data"]][["scholarly_citations_count"]] == 0 || is.null(record_list[["data"]][["scholarly_citations_count"]]) == TRUE){ 123 | if (is.null(all_citations) == TRUE || identical(all_citations, 0) == TRUE){ 124 | print('Error 2: no matched citations') 125 | return('Warning: Your input articles have no recorded citations in the Lens.org database') 126 | } 127 | 128 | ## group articles into chunks based on max number of results per search 129 | # list citing articles per input article 130 | cit_by_art <- record_list[["data"]][["scholarly_citations"]] 131 | citations <- unlist(record_list[["data"]][["scholarly_citations"]]) 132 | print('Citations record list: ') 133 | print(citations) 134 | # remove duplicates across articles 135 | citations_unique <- unique(citations) 136 | print('Deduplicated citations record list:') 137 | print(citations_unique) 138 | # set up dataframe for groups of articles to search 139 | cit_counts_df <- tibble::tibble(articles_id, 140 | citation_count, 141 | cit_by_art) 142 | # remove records with no citations 143 | cit_counts_df[is.na(cit_counts_df)] <- 0 144 | cit_counts_df <- cit_counts_df[cit_counts_df$citation_count!=0,] 145 | # subset of records that indidivually have more citations than the max number of results per query 146 | single <- subset(cit_counts_df, citation_count >= max_results) 147 | single$export_group <- 0 148 | # subset of records that have fewer citations than the max number of results per query 149 | rest <- subset(cit_counts_df, citation_count < max_results) 150 | 151 | #the following code mistakenly bins around 500 (>500 records in some groups) 152 | #rest <- mutate(rest, 153 | # # divide by max citations allowed 154 | # export_group = ceiling(cumsum(citation_count) / max_results)) 155 | #fix: 156 | rest <- dplyr::mutate(rest, export_group = MESS::cumsumbinning(citation_count, 500)) 157 | 158 | # bind both dataframes back together 159 | cit_counts_df <- rbind(single, rest) 160 | print(cit_counts_df) 161 | 162 | # get a list of vectors of the ids for searches within the export limits 163 | citgroups <- dplyr::group_split(cit_counts_df, export_group) 164 | citgroups_single <- dplyr::group_split(single, export_group) 165 | citgroups_rest <- dplyr::group_split(rest, export_group) 166 | print(citgroups_rest) 167 | 168 | # define query function 169 | run_request <- function(input){ 170 | 171 | # build query for article citations - this will pull back a maximum of 500 hits from lens.org 172 | request <- paste0('{ 173 | "query": { 174 | "terms": { 175 | "lens_id": [', paste0('"', paste(input, collapse = '", "'), '"'), '] 176 | } 177 | }, 178 | "size":500, 179 | "scroll": "1m", 180 | "include": ["lens_id", "authors", "publication_type", "title", "external_ids", "start_page", "end_page", "volume", "issue", "references", "scholarly_citations", "source_urls", "abstract", "date_published", "year_published", "references_count", "scholarly_citations_count", "source"] 181 | }') 182 | # perform search and extract results 183 | results <- getLENSData(token, request) 184 | return(results) 185 | } 186 | 187 | cit_results <- data.frame() 188 | 189 | # run the query function for each cluster of records, looping through and recording the timing 190 | if(length(citgroups_rest) > 0){ 191 | 192 | tStart_cit <- Sys.time() 193 | for (i in 1:length(citgroups_rest)){ 194 | print(paste0('Running group ', i, ' request...')) 195 | print(unlist(citgroups_rest[[i]]$cit_by_art)) 196 | data_cit <- run_request(unlist(citgroups_rest[[i]]$cit_by_art)) 197 | requests_remaining <- data_cit[["headers"]][["x-rate-limit-remaining-request-per-minute"]] 198 | # print the requests remaining to the Shinyapps log 199 | print(paste0('Remaining requests = ', requests_remaining)) 200 | # extract and convert the results to a JSON 201 | record_json_cit <- httr::content(data_cit, "text") 202 | record_list_cit <- jsonlite::fromJSON(record_json_cit) 203 | 204 | # error messages 205 | if (data_cit$status_code == 404){ 206 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 207 | } 208 | if (data_cit$status_code == 429){ 209 | return('Warning: Right now there are too many people using citationchaser. This has been logged and we will endeavour to increase the bandwith as soon as possible. Please try again later.') 210 | } 211 | if (record_list_cit$total == 0){ 212 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 213 | } 214 | 215 | # convert the results to a dataframe 216 | record_list_cit_df <- as.data.frame(record_list_cit) 217 | cit_results <- dplyr::bind_rows(cit_results, record_list_cit_df) 218 | tEnd_cit <- Sys.time() 219 | 220 | # back off if the requests per minute is close to the limit (currently 50/min) 221 | if (data_cit[["headers"]][["x-rate-limit-remaining-request-per-minute"]] < 1){ 222 | t_cit <- tEnd_cit - tStart_cit 223 | Sys.sleep(60 - t) 224 | } 225 | } 226 | } 227 | 228 | ## cursor-based pagination for any single-record query with more than 500 citations 229 | # only run if there are single-record queries 230 | if (length(citgroups_single) != 0){ 231 | runrequest <- function(input){ 232 | request <- paste0('{ 233 | "query": { 234 | "terms": { 235 | "lens_id": [', paste0('"', paste(input, collapse = '", "'), '"'), '] 236 | } 237 | }, 238 | "size": ',max_results,', 239 | "scroll": "1m", 240 | "include": ["lens_id", "authors", "publication_type", "title", "external_ids", "start_page", "end_page", "volume", "issue", "references", "scholarly_citations", "source_urls", "abstract", "date_published", "year_published", "references_count", "scholarly_citations_count", "source"] 241 | }') 242 | 243 | # perform article search and extract text results 244 | data <- getLENSData(token, request) 245 | requests_remaining <- data[["headers"]][["x-rate-limit-remaining-request-per-minute"]] 246 | # print the requests remaining to the Shinyapps log 247 | print(paste0('Remaining requests = ', requests_remaining)) 248 | # extract and convert the results to a JSON 249 | record_json <- httr::content(data, "text") 250 | record_list <- jsonlite::fromJSON(record_json) 251 | 252 | # error messages 253 | if (data$status_code == 404){ 254 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 255 | } 256 | if (data$status_code == 429){ 257 | return('Warning: Right now there are too many people using citationchaser. This has been logged and we will endeavour to increase the bandwith as soon as possible. Please try again later.') 258 | } 259 | if (record_list$total == 0){ 260 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 261 | } 262 | 263 | # convert to a dataframe 264 | record_df <- data.frame(record_list) 265 | 266 | # if a result contains more than the max number of records per request, use cursor-based pagination 267 | if(record_list[["total"]] > max_results) { 268 | 269 | sets <- ceiling(record_list[["total"]] / max_results) # calculate the number of queries needed for those with more than the max number of results 270 | 271 | scroll_id <- record_list[["scroll_id"]] # extract the scroll id from the query to go back to the same search 272 | 273 | for (i in 2:sets){ # loop through the sets of results needed to bring back all records into a dataframe 274 | scroll_id <- record_list[["scroll_id"]] #extract the latest scroll_id from the last query 275 | 276 | request <- paste0('{"scroll_id": "', # new query based on scroll_id and including 'include' for efficiency 277 | scroll_id, 278 | '","include": ["lens_id", "authors", "publication_type", "title", "external_ids", "start_page", "end_page", "volume", "issue", "references", "scholarly_citations", "source_urls", "abstract", "date_published", "year_published", "references_count", "scholarly_citations_count", "source"]}') 279 | 280 | # perform article search and extract text results 281 | data <- getLENSData(token, request) 282 | requests_remaining <- data[["headers"]][["x-rate-limit-remaining-request-per-minute"]] 283 | print(paste0('Remaining requests = ', requests_remaining)) 284 | record_json <- httr::content(data, "text") 285 | record_list <- jsonlite::fromJSON(record_json) # convert json output from article search to list 286 | 287 | # error messages 288 | if (data$status_code == 404){ 289 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 290 | } 291 | if (data$status_code == 429){ 292 | return('Warning: Right now there are too many people using citationchaser. This has been logged and we will endeavour to increase the bandwith as soon as possible. Please try again later.') 293 | } 294 | if (record_list$total == 0){ 295 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 296 | } 297 | 298 | new_df <- data.frame(record_list) 299 | # output 300 | record_df <- dplyr::bind_rows(record_df,new_df) # bind the latest search dataframe to the previous dataframe 301 | 302 | } 303 | } 304 | return(record_df) 305 | } 306 | 307 | # loop through single-record queries that are less than the maximum allowed results per query 308 | for (i in 1:length(citgroups_single)){ 309 | data_cit <- runrequest(unlist(citgroups_single[[i]]$cit_by_art)) 310 | requests_remaining <- data_cit[["headers"]][["x-rate-limit-remaining-request-per-minute"]] 311 | print(paste0('Remaining requests = ', requests_remaining)) 312 | 313 | # error messages 314 | if (data$status_code == 404){ 315 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 316 | } 317 | if (data$status_code == 429){ 318 | return('Warning: Right now there are too many people using citationchaser. This has been logged and we will endeavour to increase the bandwith as soon as possible. Please try again later.') 319 | } 320 | 321 | cit_results <- dplyr::bind_rows(cit_results, data_cit) 322 | #tEnd_cit <- Sys.time() 323 | #if (data_cit[["headers"]][["x-rate-limit-remaining-request-per-minute"]] < 1){ 324 | # t_cit <- tEnd_cit - tStart_cit 325 | # Sys.sleep(60 - t) # pause to limit requests below 50 requests/min 326 | #} 327 | } 328 | } 329 | 330 | # remove duplicate records 331 | cit_results <- cit_results[!duplicated(cit_results$data.lens_id),] 332 | all_results_cit <- cit_results$data.lens_id 333 | 334 | # convert json to ris style 335 | type_list <- data.frame(type = c("ABST", "BOOK", "CHAP", "COMP", "CONF", "DATA", "JOUR"), 336 | description = c("abstract reference", "whole book reference", "book chapter reference", "computer program", "conference proceeding", "data file", "journal/periodical reference"), 337 | publication_type = c("reference entry", "book", "book chapter", "component", "conference proceedings", "dataset", "journal article")) 338 | publication_type_cit <- cit_results$data.publication_type 339 | authors_cit <- list() 340 | for (i in 1:length(cit_results$data.authors)) { 341 | authors_cit <- unlist(c(authors_cit, paste0(cit_results$data.authors[[i]]$last_name, ', ', 342 | cit_results$data.authors[[i]]$first_name, collapse = '; '))) 343 | } 344 | title_cit <- cit_results$data.title 345 | year_cit <- cit_results$data.year_published 346 | abstract_cit <- cit_results$data.abstract 347 | start_page_cit <- cit_results$data.start_page 348 | end_page_cit <- cit_results$data.end_page 349 | source_title_cit <- list() 350 | for (i in 1:length(cit_results[,1])) { 351 | source_title_cit <- unlist(c(source_title_cit, cit_results$data.source[[1]][i])) 352 | } 353 | volume_cit <- cit_results$data.volume 354 | issue_cit <- cit_results$data.issue 355 | publisher_cit <- cit_results$data.source.publisher 356 | issn_cit <- cit_results$data.source.issn 357 | doi_cit <- unlist(lapply(cit_results$data.external_ids, function(ch) maditr::vlookup('doi', ch, result_column = 'value', lookup_column = 'type'))) 358 | 359 | # generate data table for Shiny UI 360 | level1_table_cit <- data.table::data.table(authors = authors_cit, 361 | year = year_cit, 362 | title = title_cit, 363 | source_title = source_title_cit, 364 | publisher = publisher_cit, 365 | volume = volume_cit, 366 | issue = issue_cit, 367 | start_page = start_page_cit, 368 | end_page = end_page_cit, 369 | doi = doi_cit) 370 | 371 | # generate RIS file 372 | level1_ris_cit <- paste(paste0('\n', 373 | 'TY - ', maditr::vlookup(publication_type_cit, type_list, result_column = 'type', lookup_column = 'publication_type'), '\n', 374 | 'AU - ', authors_cit, '\n', 375 | 'TI - ', title_cit, '\n', 376 | 'PY - ', year_cit, '\n', 377 | 'AB - ', abstract_cit, '\n', 378 | 'SP - ', start_page_cit, '\n', 379 | 'EP - ', end_page_cit, '\n', 380 | 'JF - ', source_title_cit, '\n', 381 | 'VL - ', volume_cit, '\n', 382 | 'IS - ', issue_cit, '\n', 383 | 'PB - ', publisher_cit, '\n', 384 | # 'SN - ', issn_cit, '\n', 385 | 'DO - ', doi_cit, '\n', 386 | 'ER - '), 387 | collapse = '\n') 388 | 389 | # generate ris build report 390 | ris_records_cit <- lengths(regmatches(level1_ris_cit, gregexpr("TY - ", level1_ris_cit))) 391 | 392 | stage1_report_cit <- paste0('Your ', scales::comma(input_number), ' articles were cited a total of ', scales::comma(all_citations), ' times. This corresponds to ', 393 | scales::comma(length(citations_unique)), ' unique article IDs. Your RIS file is ready for download and contains ', 394 | scales::comma(ris_records_cit), ' records exported from Lens.org.') 395 | 396 | report_cit <- stage1_report_cit 397 | 398 | return(list(display = level1_table_cit, ris = level1_ris_cit, report = report_cit, df = cit_results)) 399 | 400 | ### search for references articles 401 | } else if (get_records == 'references') { 402 | 403 | if (is.null(record_list[["data"]][["references_count"]]) == TRUE || identical(record_list[["data"]][["references_count"]], 0) == TRUE){ 404 | return('Warning: Your input articles contained no references in the Lens.org database') 405 | } 406 | 407 | # obtain reference lists from article search 408 | reference_count <- record_list[["data"]][["references_count"]] 409 | reference_count[is.na(reference_count)] <- 0 410 | all_references <- sum(reference_count, na.rm = TRUE) 411 | ref_by_art <- record_list[["data"]][["references"]] 412 | references <- unlist(record_list[["data"]][["references"]]) 413 | references_unique <- unique(references) 414 | deduped_references <- length(references_unique) 415 | 416 | ref_counts_df <- tibble::tibble(articles_id, 417 | reference_count, 418 | ref_by_art) 419 | ref_counts_df[is.na(ref_counts_df)] <- 0 420 | #below code not working because it was tagging excess records from one group onto the next (of 500) 421 | #ref_counts_df <- dplyr::mutate(ref_counts_df, 422 | # # cumulatively add up citation count 423 | # cumulative_n = cumsum(reference_count), 424 | # # divide by max citations allowed 425 | # export_group = floor(cumsum(reference_count) / 500)) 426 | ref_counts_df <- dplyr::mutate(ref_counts_df, export_group = MESS::cumsumbinning(reference_count, 500)) 427 | 428 | # get a list of vectors of the ids 429 | refgroups <- dplyr::group_split(ref_counts_df, export_group) 430 | 431 | # define query function 432 | run_request <- function(input){ 433 | 434 | # build query for article citations - this will pull back a maximum of 1,000 hits from lens.org, so not great if lots of refs 435 | request <- paste0('{ 436 | "query": { 437 | "terms": { 438 | "lens_id": [', paste0('"', paste(input, collapse = '", "'), '"'), '] 439 | } 440 | }, 441 | "size":500, 442 | "include": ["lens_id", "authors", "publication_type", "title", "external_ids", "start_page", "end_page", "volume", "issue", "references", "scholarly_citations", "source_urls", "abstract", "date_published", "year_published", "references_count", "scholarly_citations_count", "source"] 443 | }') 444 | #perform references search and extract text results 445 | results <- getLENSData(token, request) 446 | return(results) 447 | } 448 | 449 | # run the query function for each tibble, adding to a final dataset 450 | ref_results <- data.frame() 451 | tStart_ref <- Sys.time() 452 | for (i in 1:length(refgroups)){ 453 | data_ref <- run_request(unique(unlist(refgroups[[i]]$ref_by_art))) 454 | requests_remaining <- data_ref[["headers"]][["x-rate-limit-remaining-request-per-minute"]] 455 | print(paste0('Remaining requests = ', requests_remaining)) 456 | record_json_ref <- httr::content(data_ref, "text") 457 | record_list_ref <- jsonlite::fromJSON(record_json_ref) 458 | 459 | # error messages 460 | if (data_ref$status_code == 404){ 461 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 462 | } 463 | if (data_ref$status_code == 429){ 464 | return('Warning: Right now there are too many people using citationchaser. This has been logged and we will endeavour to increase the bandwith as soon as possible. Please try again later.') 465 | } 466 | if (record_list_ref$total == 0){ 467 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 468 | } 469 | 470 | record_list_ref_df <- as.data.frame(record_list_ref) 471 | ref_results <- dplyr::bind_rows(ref_results, record_list_ref_df) 472 | tEnd_ref <- Sys.time() 473 | if (data_ref[["headers"]][["x-rate-limit-remaining-request-per-minute"]] < 1){ 474 | t_ref <- tEnd_ref - tStart_ref 475 | Sys.sleep(60 - t) # pause to limit requests below 50 requests/min 476 | } 477 | } 478 | ref_results <- ref_results[!duplicated(ref_results$data.lens_id),] 479 | all_results_ref <- ref_results$data.lens_id 480 | 481 | # convert json to ris style 482 | type_list <- data.frame(type = c("ABST", "BOOK", "CHAP", "COMP", "CONF", "DATA", "JOUR"), 483 | description = c("abstract reference", "whole book reference", "book chapter reference", "computer program", "conference proceeding", "data file", "journal/periodical reference"), 484 | publication_type = c("reference entry", "book", "book chapter", "component", "conference proceedings", "dataset", "journal article")) 485 | publication_type_ref <- ref_results$data.publication_type 486 | authors_ref <- list() 487 | for (i in 1:length(ref_results$data.authors)) { 488 | authors_ref <- unlist(c(authors_ref, paste0(ref_results$data.authors[[i]]$last_name, ', ', 489 | ref_results$data.authors[[i]]$first_name, collapse = '; '))) 490 | } 491 | title_ref <- ref_results$data.title 492 | year_ref <- ref_results$data.year_published 493 | abstract_ref <- ref_results$data.abstract 494 | start_page_ref <- ref_results$data.start_page 495 | end_page_ref <- ref_results$data.end_page 496 | source_title_ref <- list() 497 | for (i in 1:length(ref_results[,1])) { 498 | source_title_ref <- unlist(c(source_title_ref, ref_results$data.source[[1]][i])) 499 | } 500 | volume_ref <- ref_results$data.volume 501 | issue_ref <- ref_results$data.issue 502 | publisher_ref <- ref_results$data.source.publisher 503 | issn_ref <- ref_results$data.source.issn 504 | doi_ref <- unlist(lapply(ref_results$data.external_ids, function(ch) maditr::vlookup('doi', ch, result_column = 'value', lookup_column = 'type'))) 505 | 506 | level1_table_ref <- data.table::data.table(authors = authors_ref, 507 | year = year_ref, 508 | title = title_ref, 509 | source_title = source_title_ref, 510 | publisher = publisher_ref, 511 | volume = volume_ref, 512 | issue = issue_ref, 513 | start_page = start_page_ref, 514 | end_page = end_page_ref, 515 | doi = doi_ref) 516 | 517 | level1_ris_ref <- paste(paste0('\n', 518 | 'TY - ', maditr::vlookup(publication_type_ref, type_list, result_column = 'type', lookup_column = 'publication_type'), '\n', 519 | 'AU - ', authors_ref, '\n', 520 | 'TI - ', title_ref, '\n', 521 | 'PY - ', year_ref, '\n', 522 | 'AB - ', abstract_ref, '\n', 523 | 'SP - ', start_page_ref, '\n', 524 | 'EP - ', end_page_ref, '\n', 525 | 'JF - ', source_title_ref, '\n', 526 | 'VL - ', volume_ref, '\n', 527 | 'IS - ', issue_ref, '\n', 528 | 'PB - ', publisher_ref, '\n', 529 | # 'SN - ', issn_ref, '\n', 530 | 'DO - ', doi_ref, '\n', 531 | 'ER - '), 532 | collapse = '\n') 533 | 534 | # ris build report 535 | ris_records_ref <- lengths(regmatches(level1_ris_ref, gregexpr("TY - ", level1_ris_ref))) 536 | 537 | stage1_report_ref <- paste0('Your ', scales::comma(input_number), ' articles contained a total of ', scales::comma(all_references), ' references. This corresponds to ', 538 | scales::comma(deduped_references), ' unique IDs. Your RIS file is ready for download and contains ', scales::comma(ris_records_ref), ' records exported from Lens.org.') 539 | 540 | report_ref <- stage1_report_ref 541 | 542 | return(list(display = level1_table_ref, ris = level1_ris_ref, report = report_ref, df = ref_results)) 543 | 544 | } 545 | } 546 | 547 | 548 | #' Function to query Lens.org 549 | #' 550 | #' @description Function written by lens.org for use of their API. 551 | #' @param token An access key for the lens.org API. Tokens can be obtained by 552 | #' applying for scholarly API access and creating a token once approved. See 553 | #' 'https://www.lens.org/lens/user/subscriptions#scholar' for further details. 554 | #' @param query A search string formulated according to the Lens.org API 555 | #' documentation: 'https://docs.api.lens.org/request-scholar.html'. 556 | #' @return A summary response. The results are viewable using 557 | #' 'content(data, "text")'. Other details regarding the request (e.g. repsonse 558 | #' times) can be accessed through the main output. 559 | #' @importFrom httr add_headers POST 560 | getLENSData <- function(token, query){ 561 | url <- 'https://api.lens.org/scholarly/search' 562 | headers <- c('Authorization' = token, 'Content-Type' = 'application/json') 563 | httr::POST(url = url, httr::add_headers(.headers=headers), body = query) 564 | } 565 | 566 | 567 | #' Find citation based on identifier 568 | #' 569 | #' @description 570 | #' @param article_list List of article identifiers for which the reference 571 | #' lists will be returned. Must be a list/vector of identifiers, e.g. 572 | #' '"10.1186/s13750-018-0126-2" "10.1002/jrsm.1378"'. 573 | #' @param type Specification of the type of input provided. The default is 574 | #' 'doi' (digital object identifier), but any of the following are accepted: 575 | #' "pmid" (PubMed ID), "pmcid" (PubMed Central ID), "magid" (Microsoft 576 | #' Academic ID), "coreid" (CORE identifier), lens_id" (The Lens.org ID), 577 | #' "title" (article title; much lower specificity). 578 | #' @param token An access key for the lens.org API. Tokens can be obtained by 579 | #' applying for scholarly API access and creating a token once approved. See 580 | #' 'https://www.lens.org/lens/user/subscriptions#scholar' for further details. 581 | #' @return A dataframe containing the matching citation from Lens.org. 582 | #' @importFrom expss vlookup 583 | #' @importFrom httr content 584 | #' @importFrom jsonlite fromJSON 585 | #' @importFrom data.table data.table 586 | #' @examples 587 | #' \dontrun{ 588 | #' article_list <- c("10.1007/978-3-642-37048-9_13", "10.1111/sum.12030", "10.5194/bg-13-3619-2016", "10.1016/j.agee.2012.09.006") 589 | #' results <- get_citation(article_list) 590 | #' articles <- results$display 591 | #' } 592 | #' @export 593 | get_citation <- function(article_list, 594 | type = 'doi', 595 | token = 'WCFlpCtuJXYI1sDhZcZ8y7hHpri0SEmTnLNkeU4OEM5JTQRNXB9w'){ 596 | 597 | if(length(article_list) == 1){ 598 | article_list <- trimws(unlist(strsplit(article_list, '[,]'))) 599 | } 600 | 601 | request <- paste0('{\n\t"query": {\n\t\t"terms": {\n\t\t\t"', type, '": ["', paste0('', paste(article_list, collapse = '", "'), '"'),']\n\t\t}\n\t},\n\t"size":500\n}') 602 | 603 | # perform article search and extract text results 604 | data <- getLENSData(token, request) 605 | requests_remaining <- data[["headers"]][["x-rate-limit-remaining-request-per-minute"]] 606 | print(paste0('Remaining requests = ', requests_remaining)) 607 | record_json <- httr::content(data, "text") 608 | 609 | # convert json output from article search to list 610 | record_list <- jsonlite::fromJSON(record_json) 611 | 612 | # error messages 613 | if (data$status_code == 404){ 614 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 615 | } 616 | if (data$status_code == 429){ 617 | return('Warning: Right now there are too many people using citationchaser. This has been logged and we will endeavour to increase the bandwith as soon as possible. Please try again later.') 618 | } 619 | if (record_list$total == 0){ 620 | return('Warning: Your search returned no matched results. Please double check your input article identifiers and try again.') 621 | } 622 | 623 | inputs_df <- as.data.frame(record_list) 624 | 625 | # citations and references 626 | citation_count <- record_list[["data"]][["scholarly_citations_count"]] 627 | reference_count <- record_list[["data"]][["references_count"]] 628 | 629 | # convert json to ris style 630 | type_list <- data.frame(type = c("ABST", "BOOK", "CHAP", "COMP", "CONF", "DATA", "JOUR"), 631 | description = c("abstract reference", "whole book reference", "book chapter reference", "computer program", "conference proceeding", "data file", "journal/periodical reference"), 632 | publication_type = c("reference entry", "book", "book chapter", "component", "conference proceedings", "dataset", "journal article")) 633 | publication_type <- record_list[["data"]][["publication_type"]] 634 | authors <- list() 635 | for (i in 1:length(record_list[["data"]][["authors"]])) { 636 | authors <- unlist(c(authors, paste0(record_list[["data"]][["authors"]][[i]]$last_name, ', ', 637 | record_list[["data"]][["authors"]][[i]]$first_name, collapse = '; '))) 638 | } 639 | title <- record_list[["data"]][["title"]] 640 | year <- record_list[["data"]][["year_published"]] 641 | abstract <- record_list[["data"]][["abstract"]] 642 | start_page <- record_list[["data"]][["start_page"]] 643 | end_page <- record_list[["data"]][["end_page"]] 644 | source_title <- record_list[["data"]][["source"]][["title"]] 645 | volume <- record_list[["data"]][["volume"]] 646 | issue <- record_list[["data"]][["issue"]] 647 | publisher <- record_list[["data"]][["source"]][["publisher"]] 648 | issn <- record_list[["data"]][["source"]][["issn"]] 649 | doi <- unlist(lapply(record_list[["data"]][["external_ids"]], function(ch) maditr::vlookup('doi', ch, result_column = 'value', lookup_column = 'type'))) 650 | 651 | article_table <- data.table(authors = authors, 652 | year = year, 653 | title = title, 654 | source_title = source_title, 655 | publisher = publisher, 656 | volume = volume, 657 | issue = issue, 658 | start_page = start_page, 659 | end_page = end_page, 660 | doi = doi, 661 | Lens_refs = reference_count, 662 | Lens_cited = citation_count) 663 | 664 | article_ris <- paste(paste0('\n', 665 | 'TY - ', maditr::vlookup(publication_type, type_list, result_column = 'type', lookup_column = 'publication_type'), '\n', 666 | 'AU - ', authors, '\n', 667 | 'TI - ', title, '\n', 668 | 'PY - ', year, '\n', 669 | 'AB - ', abstract, '\n', 670 | 'SP - ', start_page, '\n', 671 | 'EP - ', end_page, '\n', 672 | 'JF - ', source_title, '\n', 673 | 'VL - ', volume, '\n', 674 | 'IS - ', issue, '\n', 675 | 'PB - ', publisher, '\n', 676 | # 'SN - ', issn, '\n', 677 | 'DO - ', doi, '\n', 678 | 'ER - '), 679 | collapse = '\n') 680 | 681 | return(list(display = article_table, ris = article_ris, df = inputs_df)) 682 | 683 | } 684 | -------------------------------------------------------------------------------- /inst/shiny-examples/citationchaser/mixed_id_wrapper.R: -------------------------------------------------------------------------------- 1 | #' Wrapper for multiple input ID types 2 | #' 3 | #' @description A wrapper function allowing the searching of multiple different 4 | #' identifiers at once. 5 | #' @param input a dataframe with one column containing the record identifiers, 6 | #' and a second column detailing the type of ID (must be one of the following: 7 | #' 'doi', 'pmid', 'opmcid', 'coreid', 'magid', or 'lens_id'.) 8 | #' @param search The type of search to be run, whether looking up citations 9 | #' to populate the input table ('input'), or references for backward 10 | #' citation chasing ('references'), citing articles for forward citation 11 | #' chasing ('citations') or both references and citations ('both'). 12 | #' @param token The lens.org API access token. 13 | #' @return A list of three outputs: a data-table for presentation in the Shiny 14 | #' app, an RIS (text) file containing all citations, and a dataframe with the 15 | #' full JSON output from the lens.org API query. 16 | #' @examples 17 | #' \dontrun{ 18 | #' ids <- c('10.1007/978-3-642-37048-9_13', 19 | #' '10.1111/sum.12030', 20 | #' '10.5194/bg-13-3619-2016', 21 | #' '10.1016/j.agee.2012.09.006', 22 | #' '32652585', 23 | #' '32706299') 24 | #' type <- c('doi', 25 | #' 'doi', 26 | #' 'doi', 27 | #' 'doi', 28 | #' 'pmid', 29 | #' 'pmid') 30 | #' input <- data.frame(ids, type) 31 | #' test1 <- get_refs_mixed(input, search = 'input', token = token) 32 | #' test2 <- get_refs_mixed(input, search = 'references', token = token) 33 | #' } 34 | get_refs_mixed <- function(input, 35 | search, 36 | token){ 37 | 38 | #split input data by ID type 39 | #doi <- split(input, input$type)$doi 40 | #pmid <- split(input, input$type)$pmid 41 | #pmcid <- split(input, input$type)$pmcid 42 | #core <- split(input, input$type)$core 43 | #magid <- split(input, input$type)$magid 44 | #lens <- split(input, input$type)$lens 45 | 46 | #for each ID type, run a lookup 47 | #for input 48 | if(search == 'input'){ 49 | #create blank outputs 50 | display <- data.table::data.table(NULL) 51 | ris <- '' 52 | df <- data.frame() 53 | 54 | #dois 55 | if(('doi' %in% input$type) == TRUE){ 56 | data_doi <- split(input, input$type)$doi 57 | id_list_doi <- as.vector(data_doi[,1]) 58 | intermediate_doi <- get_citation(id_list_doi, type = 'doi', token = token) 59 | display_doi <- tryCatch(intermediate_doi$display, error = function(cond){return(NULL)}) 60 | ris_doi <- tryCatch(intermediate_doi$ris, error = function(cond){return(NULL)}) 61 | df_doi <- tryCatch(intermediate_doi$df, error = function(cond){return(NULL)}) 62 | print('DOIs searched') 63 | } else { 64 | display_doi <- NULL 65 | ris_doi <- NULL 66 | df_doi <- NULL 67 | } 68 | 69 | #pmid 70 | if(('pmid' %in% input$type) == TRUE){ 71 | data_pmid <- split(input, input$type)$pmid 72 | id_list_pmid <- as.vector(data_pmid[,1]) 73 | intermediate_pmid <- get_citation(id_list_pmid, type = 'pmid', token = token) 74 | display_pmid <- tryCatch(intermediate_pmid$display, error = function(cond){return(NULL)}) 75 | ris_pmid <- tryCatch(intermediate_pmid$ris, error = function(cond){return(NULL)}) 76 | df_pmid <- tryCatch(intermediate_pmid$df, error = function(cond){return(NULL)}) 77 | print('PMIDs searched') 78 | } else { 79 | display_pmid <- NULL 80 | ris_pmid <- NULL 81 | df_pmid <- NULL 82 | } 83 | 84 | #pmcid 85 | if(('pmcid' %in% input$type) == TRUE){ 86 | data_pmcid <- split(input, input$type)$pmcid 87 | id_list_pmcid <- as.vector(data_pmcid[,1]) 88 | intermediate_pmcid <- get_citation(id_list_pmcid, type = 'pmcid', token = token) 89 | display_pmcid <- tryCatch(intermediate_pmcid$display, error = function(cond){return(NULL)}) 90 | ris_pmcid <- tryCatch(intermediate_pmcid$ris, error = function(cond){return(NULL)}) 91 | df_pmcid <- tryCatch(intermediate_pmcid$df, error = function(cond){return(NULL)}) 92 | print('PMIDs searched') 93 | } else { 94 | display_pmcid <- NULL 95 | ris_pmcid <- NULL 96 | df_pmcid <- NULL 97 | } 98 | 99 | #magid 100 | if(('magid' %in% input$type) == TRUE){ 101 | data_magid <- split(input, input$type)$magid 102 | id_list_magid <- as.vector(data_magid[,1]) 103 | intermediate_magid <- get_citation(id_list_magid, type = 'magid', token = token) 104 | display_magid <- tryCatch(intermediate_magid$display, error = function(cond){return(NULL)}) 105 | ris_magid <- tryCatch(intermediate_magid$ris, error = function(cond){return(NULL)}) 106 | df_magid <- tryCatch(intermediate_magid$df, error = function(cond){return(NULL)}) 107 | print('MAGIDs searched') 108 | } else { 109 | display_magid <- NULL 110 | ris_magid <- NULL 111 | df_magid <- NULL 112 | } 113 | 114 | #coreid 115 | if(('coreid' %in% input$type) == TRUE){ 116 | data_coreid <- split(input, input$type)$coreid 117 | id_list_coreid <- as.vector(data_coreid[,1]) 118 | intermediate_coreid <- get_citation(id_list_coreid, type = 'coreid', token = token) 119 | display_coreid <- tryCatch(intermediate_coreid$display, error = function(cond){return(NULL)}) 120 | ris_coreid <- tryCatch(intermediate_coreid$ris, error = function(cond){return(NULL)}) 121 | df_coreid <- tryCatch(intermediate_coreid$df, error = function(cond){return(NULL)}) 122 | print('COREIDs searched') 123 | } else { 124 | display_coreid <- NULL 125 | ris_coreid <- NULL 126 | df_coreid <- NULL 127 | } 128 | 129 | #lensid 130 | if(('lens_id' %in% input$type) == TRUE){ 131 | data_lens_id <- split(input, input$type)$lens_id 132 | id_list_lens_id <- as.vector(data_lens_id[,1]) 133 | intermediate_lens_id <- get_citation(id_list_lens_id, type = 'lens_id', token = token) 134 | display_lens_id <- tryCatch(intermediate_lens_id$display, error = function(cond){return(NULL)}) 135 | ris_lens_id <- tryCatch(intermediate_lens_id$ris, error = function(cond){return(NULL)}) 136 | df_lens_id <- tryCatch(intermediate_lens_id$df, error = function(cond){return(NULL)}) 137 | print('LENSIDs searched') 138 | } else { 139 | display_lens_id <- NULL 140 | ris_lens_id <- NULL 141 | df_lens_id <- NULL 142 | } 143 | 144 | #combine list outputs 145 | display <- dplyr::bind_rows(display_doi, 146 | display_pmid, 147 | display_pmcid, 148 | display_magid, 149 | display_coreid, 150 | display_lens_id) 151 | ris <- paste(ris_doi, 152 | ris_pmid, 153 | ris_pmcid, 154 | ris_magid, 155 | ris_coreid, 156 | ris_lens_id, 157 | sep = '\n\n') 158 | df <- dplyr::bind_rows(df_doi, 159 | df_pmid, 160 | df_pmcid, 161 | df_magid, 162 | df_coreid, 163 | df_lens_id) 164 | 165 | return(list(display = display, ris = ris, df = df)) 166 | } else { 167 | 168 | #create blank outputs 169 | display <- data.table::data.table(NULL) 170 | ris <- '' 171 | df <- data.frame() 172 | 173 | #dois 174 | if(('doi' %in% input$type) == TRUE){ 175 | data_doi <- split(input, input$type)$doi 176 | id_list_doi <- as.vector(data_doi[,1]) 177 | intermediate_doi <- get_refs(id_list_doi, type = 'doi', get_records = search, token = token) 178 | display_doi <- tryCatch(intermediate_doi$display, error = function(cond){return(NULL)}) 179 | ris_doi <- tryCatch(intermediate_doi$ris, error = function(cond){return(NULL)}) 180 | df_doi <- tryCatch(intermediate_doi$df, error = function(cond){return(NULL)}) 181 | } else { 182 | display_doi <- NULL 183 | ris_doi <- NULL 184 | df_doi <- NULL 185 | } 186 | 187 | #pmid 188 | if(('pmid' %in% input$type) == TRUE){ 189 | data_pmid <- split(input, input$type)$pmid 190 | id_list_pmid <- as.vector(data_pmid[,1]) 191 | intermediate_pmid <- get_refs(id_list_pmid, type = 'pmid', get_records = search, token = token) 192 | display_pmid <- tryCatch(intermediate_pmid$display, error = function(cond){return(NULL)}) 193 | ris_pmid <- tryCatch(intermediate_pmid$ris, error = function(cond){return(NULL)}) 194 | df_pmid <- tryCatch(intermediate_pmid$df, error = function(cond){return(NULL)}) 195 | } else { 196 | display_pmid <- NULL 197 | ris_pmid <- NULL 198 | df_pmid <- NULL 199 | } 200 | 201 | #pmcid 202 | if(('pmcid' %in% input$type) == TRUE){ 203 | data_pmcid <- split(input, input$type)$pmcid 204 | id_list_pmcid <- as.vector(data_pmcid[,1]) 205 | intermediate_pmcid <- get_refs(id_list_pmcid, type = 'pmcid', get_records = search, token = token) 206 | display_pmcid <- tryCatch(intermediate_pmcid$display, error = function(cond){return(NULL)}) 207 | ris_pmcid <- tryCatch(intermediate_pmcid$ris, error = function(cond){return(NULL)}) 208 | df_pmcid <- tryCatch(intermediate_pmcid$df, error = function(cond){return(NULL)}) 209 | } else { 210 | display_pmcid <- NULL 211 | ris_pmcid <- NULL 212 | df_pmcid <- NULL 213 | } 214 | 215 | #magid 216 | if(('magid' %in% input$type) == TRUE){ 217 | data_magid <- split(input, input$type)$magid 218 | id_list_magid <- as.vector(data_magid[,1]) 219 | intermediate_magid <- get_refs(id_list_magid, type = 'magid', get_records = search, token = token) 220 | display_magid <- tryCatch(intermediate_magid$display, error = function(cond){return(NULL)}) 221 | ris_magid <- tryCatch(intermediate_magid$ris, error = function(cond){return(NULL)}) 222 | df_magid <- tryCatch(intermediate_magid$df, error = function(cond){return(NULL)}) 223 | } else { 224 | display_magid <- NULL 225 | ris_magid <- NULL 226 | df_magid <- NULL 227 | } 228 | 229 | #coreid 230 | if(('coreid' %in% input$type) == TRUE){ 231 | data_coreid <- split(input, input$type)$coreid 232 | id_list_coreid <- as.vector(data_coreid[,1]) 233 | intermediate_coreid <- get_refs(id_list_coreid, type = 'coreid', get_records = search, token = token) 234 | display_coreid <- tryCatch(intermediate_coreid$display, error = function(cond){return(NULL)}) 235 | ris_coreid <- tryCatch(intermediate_coreid$ris, error = function(cond){return(NULL)}) 236 | df_coreid <- tryCatch(intermediate_coreid$df, error = function(cond){return(NULL)}) 237 | } else { 238 | display_coreid <- NULL 239 | ris_coreid <- NULL 240 | df_coreid <- NULL 241 | } 242 | 243 | #lensid 244 | if(('lens_id' %in% input$type) == TRUE){ 245 | data_lens_id <- split(input, input$type)$lens_id 246 | id_list_lens_id <- as.vector(data_lens_id[,1]) 247 | intermediate_lens_id <- get_refs(id_list_lens_id, type = 'lens_id', get_records = search, token = token) 248 | display_lens_id <- tryCatch(intermediate_lens_id$display, error = function(cond){return(NULL)}) 249 | ris_lens_id <- tryCatch(intermediate_lens_id$ris, error = function(cond){return(NULL)}) 250 | df_lens_id <- tryCatch(intermediate_lens_id$df, error = function(cond){return(NULL)}) 251 | } else { 252 | display_lens_id <- NULL 253 | ris_lens_id <- NULL 254 | df_lens_id <- NULL 255 | } 256 | 257 | #combine list outputs 258 | display <- dplyr::bind_rows(display_doi, 259 | display_pmid, 260 | display_pmcid, 261 | display_magid, 262 | display_coreid, 263 | display_lens_id) 264 | ris <- paste(ris_doi, 265 | ris_pmid, 266 | ris_pmcid, 267 | ris_magid, 268 | ris_coreid, 269 | ris_lens_id, 270 | sep = '\n\n') 271 | df <- dplyr::bind_rows(df_doi, 272 | df_pmid, 273 | df_pmcid, 274 | df_magid, 275 | df_coreid, 276 | df_lens_id) 277 | 278 | return(list(display = display, ris = ris, df = df)) 279 | 280 | } 281 | 282 | } 283 | 284 | -------------------------------------------------------------------------------- /inst/shiny-examples/citationchaser/rsconnect/shinyapps.io/estech/citationchaser.dcf: -------------------------------------------------------------------------------- 1 | name: citationchaser 2 | title: 3 | username: 4 | account: estech 5 | server: shinyapps.io 6 | hostUrl: https://api.shinyapps.io/v1 7 | appId: 3624008 8 | bundleId: 4390195 9 | url: https://estech.shinyapps.io/citationchaser/ 10 | when: 1616603468.45333 11 | -------------------------------------------------------------------------------- /inst/shiny-examples/citationchaser/www/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nealhaddaway/citationchaser/ba382a7336fbc61090b092e67db9ddecfcc4e8ca/inst/shiny-examples/citationchaser/www/.DS_Store -------------------------------------------------------------------------------- /inst/shiny-examples/citationchaser/www/citation.ris: -------------------------------------------------------------------------------- 1 | TY - COMP 2 | AU - Haddaway, N. R. 3 | AU - Grainger, M. J. 4 | AU - Gray, C. T. 5 | DO - 10.5281/zenodo.4543513 6 | ET - 0.0.3 7 | PY - 2021 8 | ST - citationchaser: an R package for forward and backward citations chasing in academic searching 9 | TI - citationchaser: an R package for forward and backward citations chasing in academic searching 10 | UR - https://github.com/nealhaddaway/citationchaser 11 | ID - 12 | ER - 13 | 14 | -------------------------------------------------------------------------------- /inst/shiny-examples/citationchaser/www/example.csv: -------------------------------------------------------------------------------- 1 | ids,type 2 | 3041487187,magid 3 | 304952007,coreid 4 | 32915751,pmid 5 | 063-361-128-530-65X,lens_id 6 | 7291190,pmcid 7 | 10.3889/oamjms.2020.5098,doi -------------------------------------------------------------------------------- /inst/shiny-examples/citationchaser/www/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nealhaddaway/citationchaser/ba382a7336fbc61090b092e67db9ddecfcc4e8ca/inst/shiny-examples/citationchaser/www/example.png -------------------------------------------------------------------------------- /inst/shiny-examples/citationchaser/www/legendnew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nealhaddaway/citationchaser/ba382a7336fbc61090b092e67db9ddecfcc4e8ca/inst/shiny-examples/citationchaser/www/legendnew.png -------------------------------------------------------------------------------- /man/getLENSData.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_refs.R 3 | \name{getLENSData} 4 | \alias{getLENSData} 5 | \title{Function to query Lens.org} 6 | \usage{ 7 | getLENSData(token, query) 8 | } 9 | \arguments{ 10 | \item{token}{An access key for the lens.org API. Tokens can be obtained by 11 | applying for scholarly API access and creating a token once approved. See 12 | 'https://www.lens.org/lens/user/subscriptions#scholar' for further details.} 13 | 14 | \item{query}{A search string formulated according to the Lens.org API 15 | documentation: 'https://docs.api.lens.org/request-scholar.html'.} 16 | } 17 | \value{ 18 | A summary response. The results are viewable using 19 | 'content(data, "text")'. Other details regarding the request (e.g. repsonse 20 | times) can be accessed through the main output. 21 | } 22 | \description{ 23 | Function written by lens.org for use of their API. 24 | } 25 | -------------------------------------------------------------------------------- /man/get_citation.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_refs.R 3 | \name{get_citation} 4 | \alias{get_citation} 5 | \title{Find citation based on identifier} 6 | \usage{ 7 | get_citation( 8 | article_list, 9 | type = "doi", 10 | token = "WCFlpCtuJXYI1sDhZcZ8y7hHpri0SEmTnLNkeU4OEM5JTQRNXB9w" 11 | ) 12 | } 13 | \arguments{ 14 | \item{article_list}{List of article identifiers for which the reference 15 | lists will be returned. Must be a list/vector of identifiers, e.g. 16 | '"10.1186/s13750-018-0126-2" "10.1002/jrsm.1378"'.} 17 | 18 | \item{type}{Specification of the type of input provided. The default is 19 | 'doi' (digital object identifier), but any of the following are accepted: 20 | "pmid" (PubMed ID), "pmcid" (PubMed Central ID), "magid" (Microsoft 21 | Academic ID), "coreid" (CORE identifier), lens_id" (The Lens.org ID), 22 | "title" (article title; much lower specificity).} 23 | 24 | \item{token}{An access key for the lens.org API. Tokens can be obtained by 25 | applying for scholarly API access and creating a token once approved. See 26 | 'https://www.lens.org/lens/user/subscriptions#scholar' for further details.} 27 | } 28 | \value{ 29 | A dataframe containing the matching citation from Lens.org. 30 | } 31 | \description{ 32 | This function takes a list of articles in the form of established 33 | identifiers (e.g. digital object identifiers) and sends a request to the 34 | lens.org API to obtain full citation information from the Lens database for 35 | all sought articles. 36 | } 37 | \examples{ 38 | \dontrun{ 39 | article_list <- c("10.1007/978-3-642-37048-9_13", "10.1111/sum.12030", 40 | "10.5194/bg-13-3619-2016", "10.1016/j.agee.2012.09.006") 41 | results <- get_citation(article_list) 42 | articles <- results$display 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /man/get_refs.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_refs.R 3 | \name{get_refs} 4 | \alias{get_refs} 5 | \title{Automated citation chasing in systematic reviews} 6 | \usage{ 7 | get_refs(article_list, type = "doi", get_records, save_object = FALSE, token) 8 | } 9 | \arguments{ 10 | \item{article_list}{List of article identifiers for which the reference 11 | lists will be returned. Must be a list/vector of identifiers, e.g. 12 | '"10.1186/s13750-018-0126-2" "10.1002/jrsm.1378"'.} 13 | 14 | \item{type}{Specification of the type of input provided. The default is 15 | 'doi' (digital object identifier), but any of the following are accepted: 16 | "pmid" (PubMed ID), "pmcid" (PubMed Central ID), "magid" (Microsoft 17 | Academic ID), "coreid" (CORE identifier), lens_id" (The Lens.org ID), 18 | "title" (article title; much lower specificity).} 19 | 20 | \item{get_records}{Specification of whether to look for records referenced 21 | within the input articles ('references'), records citing the input articles 22 | ('citations'), or both ('both').} 23 | 24 | \item{save_object}{Option to save the resultant ris file as an object in 25 | the Global Environment. The default is FALSE.} 26 | 27 | \item{token}{An access key for the lens.org API. Tokens can be obtained by 28 | applying for scholarly API access and creating a token once approved. See 29 | 'https://www.lens.org/lens/user/subscriptions#scholar' for further details.} 30 | } 31 | \value{ 32 | An RIS file is saved to the working directory. A report is printed 33 | to the console. If 'save_object=TRUE', the RIS file is returned as an 34 | object 35 | } 36 | \description{ 37 | This function takes a list of articles in the form of established 38 | identifiers (e.g. digital object identifiers) and sends a request to the 39 | lens.org API to firstly identify all cited references in all articles (in the 40 | form of lists of lens IDs), and then query these lens IDs to bring back full 41 | citation information for all listed records. Deduplicates references to the 42 | same records across articles, resulting in an RIS file and a summary report 43 | in the console. 44 | } 45 | \examples{ 46 | \dontrun{ 47 | article_list <- c("10.1007/978-3-642-37048-9_13", 48 | "10.1111/sum.12030", 49 | "10.5194/bg-13-3619-2016", 50 | "10.1016/j.agee.2012.09.006") 51 | token <- 'token' 52 | refs <- get_refs(article_list, get_records = 'references', token = token) 53 | refs 54 | } 55 | } 56 | --------------------------------------------------------------------------------