Skip to content

Commit

Permalink
add package infrastructure
Browse files Browse the repository at this point in the history
  • Loading branch information
rstudio committed Feb 5, 2015
1 parent 065d89c commit 3a2e423
Show file tree
Hide file tree
Showing 13 changed files with 268 additions and 134 deletions.
3 changes: 3 additions & 0 deletions .Rbuildignore
@@ -1,3 +1,6 @@
.git
.gitignore
inst/api
^.*\.Rproj$
^\.Rproj\.user$
^\.travis\.yml$
24 changes: 24 additions & 0 deletions .travis.yml
@@ -0,0 +1,24 @@
# Sample .travis.yml for R projects from https://github.com/craigcitro/r-travis

language: c

before_install:
- curl -OL http://raw.github.com/craigcitro/r-travis/master/scripts/travis-tool.sh
- chmod 755 ./travis-tool.sh
- ./travis-tool.sh bootstrap

install:
- ./travis-tool.sh install_deps

script: ./travis-tool.sh run_tests

after_failure:
- ./travis-tool.sh dump_logs

env:
- WARNINGS_ARE_ERRORS=1

notifications:
email:
on_success: change
on_failure: change
6 changes: 4 additions & 2 deletions DESCRIPTION
Expand Up @@ -4,12 +4,14 @@ Title: R Interface to FishBASE
Version: 2.0.0
License: CC0
Authors@R: c(person("Carl", "Boettiger", role = c("aut"), email = "cboettig@ropensci.org"),
person("Scott", "Chamberlain", role = c("cre", "aut")), email = "scott@ropensci.org")
person("Scott", "Chamberlain", role = c("cre", "aut"), email = "scott@ropensci.org"))
URL: https://github.com/ropensci/rfishbase
BugReports: https://github.com/ropensci/rfishbase/issues
Depends:
R (>= 3.0)
Imports:
httr
httr,
tidyr,
stringr
Suggests:
testthat
6 changes: 6 additions & 0 deletions NAMESPACE
@@ -0,0 +1,6 @@
# Generated by roxygen2 (4.1.0): do not edit by hand

export(species_table)
import(httr)
import(stringr)
import(tidyr)
140 changes: 8 additions & 132 deletions R/fishbase.R
@@ -1,133 +1,9 @@
SERVER = "http://server.carlboettiger.info:4567"

## Provide wrapper to work with species lists.
#' @param species_list Takes a vector of scientific names (each element as "genus species").
#' if only one name is given in an element (no space), assumes it is the genus and returns
#' all species matching that genus.
#' @param verbose should the function give warnings?
#' @return a data.frame with rows for species and columns for the fields returned by the query (FishBase 'species' table)
#' @examples
#' \donttest{
#'
#' species_table(c("Oreochromis niloticus", "Bolbometopon muricatum"))
#' # There are 5 species in this genus, so returns 5 rows:
#' species_table("Labroides")
#' }
#' @export
species_table <- function(species_list, verbose = TRUE, .limit = 50, .server = SERVER){
# Just wraps an lapply around the "per_species" function and combines the resulting data.frames.
# .limit limits the number of returns in a single API call. As we are usually matching species, we expect
# only one hit per call anyway so limit may as well be 1. If we are matching genus only, we can hit
# several species and limit should justifiably be higher.
do.call("rbind", lapply(species_list, per_species, verbose = verbose, limit = .limit, server = .server))
}



#' @import httr stringr
per_species <- function(species, verbose = TRUE, limit = 10, server = SERVER){

## parse scientific name (FIXME function should also do checks.)
s <- parse_name(species)

## Make the API call for the species requested
args <- list(species = s$species, genus = s$genus, limit = limit)
resp <- GET(paste0(server, "/species"), query = args)

## check response for http errors
stop_for_status(resp)

## Parse the http response
out <- content(resp)

## Check for errors or other issues
error_checks(out, verbose = verbose)

## Combine into data.frame and tidy
tidy_species_table(out$data)
}




## Family query is 2 api calls, one to look up FamCode. 1 call for subFamily
## Higher taxonomy: less relevant?




error_checks <- function(resp, verbose = TRUE){
## check for errors in the API query
if(!is.null(out$error) && verbose)
stop(out$error)

## Comment if returns are incomplete.
if(verbose && out$count > out$returned)
warning(paste("Retruning first", out$returned, "matches out of", out$count, "matches.",
"\n Increase limit or refine query for more results"))

}


## Metadata used by tidy_species_table
#meta <- system.file("metadata", "species.csv", package="rfishbase")
meta <- 'inst/metadata/species.csv'
species_meta <- read.csv(meta)
row.names(species_meta) <- species_meta$field


#' @import tidyr
tidy_species_table <- function(data) {
L <- lapply(data, null_to_NA)
df <- do.call(rbind.data.frame, L)
# Convert columns to the appropriate class
for(n in names(df)){
class <- as.character(species_meta[[n, "class"]])
if(class=="Date")
df[,n] <- as.Date(as.character(df[,n]))
else if(class=="logical")
df[,n] <- as(as.numeric(df[,n]), class)
else
df[,n] <- as(as.character(df[,n]), class)
}
## Drop useless columns. Once reference table implemented, we may want to return those numbers. Same for expert ids.
df <- df[,species_meta$keep]
## Rename columns (pick names to indicate units on numeric values?)

## Arrange columns

df
}

# simple helper function
null_to_NA <- function(x) {
x[sapply(x, is.null)] <- NA
return(x)
}

# resp <- GET("http://server.carlboettiger.info:4567/species/?genus=Labroides")
# data <- content(resp)$data
# tidy_species_table(data)

# -1 is TRUE, 0 is FALSE



# parse scientific name. FIXME stupid function, should do more check & error handling
parse_name <- function(x){
x <- str_split(x, " ")[[1]]
switch(length(x),
list(genus = x[1]),
list(genus = x[1], species = x[2]))
}


#'
#' @import httr
get_id <- function(genus, species, server = SERVER){
resp <- GET(paste0(server, "/species", "/?genus=", genus, "&species=", species, "&fields=speciesrefno,speccode"))
sapply(content(resp)$data, `[[`, "speciesrefno")
}


#' @name rfishbase-package
#' @aliases rfishbase
#' @docType package
#' @title The new R interface to Fishbase, v2.0
#' @author Carl Boettiger \email{carl@@ropensci.org}
#' @author Scott Chamberlain \email{scott@@ropensci.org}
#' @keywords package
NULL

4 changes: 4 additions & 0 deletions R/heartbeat.R
@@ -0,0 +1,4 @@
# Check that server is responding

heartbeat <- function(server = SERVER)
GET(paste0(server, "/heartbeat"))
137 changes: 137 additions & 0 deletions R/species_table.R
@@ -0,0 +1,137 @@
SERVER = "http://server.carlboettiger.info:4567"

#' species_table
#'
#' Provide wrapper to work with species lists.
#' @param species_list Takes a vector of scientific names (each element as "genus species").
#' if only one name is given in an element (no space), assumes it is the genus and returns
#' all species matching that genus.
#' @param verbose should the function give warnings?
#' @param limit The maximum number of matches from a single API call. (Applicable only when
#' querying at genus level, typically only one #' record will match the scientific name and
#' will try all names given.) Thus the default is almost always fine.
#' @param server URL of the FishBase API server.
#' @return a data.frame with rows for species and columns for the fields returned by the query (FishBase 'species' table)
#' @details
#' The Species table is the heart of FishBase. This function provides a convenient way to
#' query, tidy, and assemble data from that table given an entire list of species.
#' For details, see: http://www.fishbase.org/manual/english/fishbasethe_species_table.htm
#'
#' Species scientific names are defined according to fishbase taxonomy and nomenclature.
#'
#' @examples
#' \donttest{
#'
#' species_table(c("Oreochromis niloticus", "Bolbometopon muricatum"))
#' # There are 5 species in this genus, so returns 5 rows:
#' species_table("Labroides")
#' }
#' @import httr stringr tidyr
#' @export
species_table <- function(species_list, verbose = TRUE, limit = 50, server = SERVER){
# Just wraps an lapply around the "per_species" function and combines the resulting data.frames.
# .limit limits the number of returns in a single API call. As we are usually matching species, we expect
# only one hit per call anyway so limit may as well be 1. If we are matching genus only, we can hit
# several species and limit should justifiably be higher.
do.call("rbind", lapply(species_list, per_species, verbose = verbose, limit = limit, server = server))
}



per_species <- function(species, verbose = TRUE, limit = 10, server = SERVER){

## parse scientific name (FIXME function should also do checks.)
s <- parse_name(species)

## Make the API call for the species requested
args <- list(species = s$species, genus = s$genus, limit = limit)
resp <- GET(paste0(server, "/species"), query = args)

## check response for http errors
stop_for_status(resp)

## Parse the http response
parsed <- content(resp)

## Check for errors or other issues
error_checks(parsed, verbose = verbose)

## Combine into data.frame and tidy
tidy_species_table(parsed$data)
}




## Family query is 2 api calls, one to look up FamCode. 1 call for subFamily
## Higher taxonomy: less relevant?




error_checks <- function(parsed, verbose = TRUE){
## check for errors in the API query
if(!is.null(parsed$error) && verbose)
stop(parsed$error)

## Comment if returns are incomplete.
if(verbose && parsed$count > parsed$returned)
warning(paste("Retruning first", parsed$returned, "matches parsed of", parsed$count, "matches.",
"\n Increase limit or refine query for more results"))

}


## Metadata used by tidy_species_table
meta <- system.file("metadata", "species.csv", package="rfishbase")
species_meta <- read.csv(meta)
row.names(species_meta) <- species_meta$field


## helper routine for tidying species data
tidy_species_table <- function(data) {
L <- lapply(data, null_to_NA)
df <- do.call(rbind.data.frame, L)
# Convert columns to the appropriate class
for(n in names(df)){
class <- as.character(species_meta[[n, "class"]])
if(class=="Date")
df[,n] <- as.Date(as.character(df[,n]))
else if(class=="logical")
df[,n] <- as(as.numeric(df[,n]), class)
else
df[,n] <- as(as.character(df[,n]), class)
}
## Drop useless columns. Once reference table implemented, we may want to return those numbers. Same for expert ids.
df <- df[,species_meta$keep]
## Rename columns (pick names to indicate units on numeric values?)

## Arrange columns

df
}

# simple helper function
null_to_NA <- function(x) {
x[sapply(x, is.null)] <- NA
return(x)
}

# resp <- GET("http://server.carlboettiger.info:4567/species/?genus=Labroides")
# data <- content(resp)$data
# tidy_species_table(data)

# -1 is TRUE, 0 is FALSE



# parse scientific name. FIXME stupid function, should do more check & error handling
parse_name <- function(x){
x <- str_split(x, " ")[[1]]
switch(length(x),
list(genus = x[1]),
list(genus = x[1], species = x[2]))
}



17 changes: 17 additions & 0 deletions man/rfishbase-package.Rd
@@ -0,0 +1,17 @@
% Generated by roxygen2 (4.1.0): do not edit by hand
% Please edit documentation in R/fishbase.R
\docType{package}
\name{rfishbase-package}
\alias{rfishbase}
\alias{rfishbase-package}
\title{The new R interface to Fishbase, v2.0}
\description{
The new R interface to Fishbase, v2.0
}
\author{
Carl Boettiger \email{carl@ropensci.org}

Scott Chamberlain \email{scott@ropensci.org}
}
\keyword{package}

0 comments on commit 3a2e423

Please sign in to comment.