Skip to content

Commit

Permalink
eliminate check of non-numeric 1st col for add_totals functions
Browse files Browse the repository at this point in the history
See #57
  • Loading branch information
sfirke committed Mar 19, 2017
1 parent 5c70a27 commit 4e8950e
Show file tree
Hide file tree
Showing 9 changed files with 28 additions and 55 deletions.
6 changes: 3 additions & 3 deletions R/add_totals.R
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ add_totals_row <- function(dat, fill = "-", na.rm = TRUE){
add_totals_col <- function(dat, na.rm = TRUE){

clean_dat <- clean_names(dat) # bad names will make select_if choke
if(ncol(dplyr::select_if(clean_dat, is.numeric)) == 0){stop("data.frame must contain at least one column of class numeric")}
if(sum(unlist(lapply(dat, is.numeric))) == 0){stop("input data.frame must contain at least one column of class numeric")}
row_totals <- clean_dat %>%
dplyr::select_if(is.numeric) %>%
dplyr::transmute(Total = rowSums(., na.rm = na.rm))

dplyr::bind_cols(dat, row_totals) %>%
stats::setNames(c(names(dat), "Total")) # put back original names
dat$Total <- row_totals$Total
dat
}

20 changes: 10 additions & 10 deletions R/adorn_crosstab.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#' @description
#' Designed to run on the output of a call to \code{crosstab}, this adds formatting, percentage sign, Ns, totals row/column, and custom rounding to a table of numeric values. The result is no longer clean data, but it saves time in reporting table results.
#'
#' @param crosstab a data.frame with row names in the first column and numeric values in all other columns. Usually the piped-in result of a call to \code{crosstab} that included the argument \code{percent = "none"}.
#' @param dat a data.frame with row names in the first column and numeric values in all other columns. Usually the piped-in result of a call to \code{crosstab} that included the argument \code{percent = "none"}.
#' @param denom the denominator to use for calculating percentages. One of "row", "col", or "all".
#' @param show_n should counts be displayed alongside the percentages?
#' @param digits how many digits should be displayed after the decimal point?
Expand All @@ -28,23 +28,23 @@

# take result of a crosstab() call and print a nice result
#' @export
adorn_crosstab <- function(crosstab, denom = "row", show_n = TRUE, digits = 1, show_totals = FALSE, rounding = "half to even"){
adorn_crosstab <- function(dat, denom = "row", show_n = TRUE, digits = 1, show_totals = FALSE, rounding = "half to even"){
# some input checks
if(! rounding %in% c("half to even", "half up")){stop("'rounding' must be one of 'half to even' or 'half up'")}
check_all_cols_after_first_are_numeric(crosstab)
dat[[1]] <- as.character(dat[[1]]) # for type matching when binding the word "Total" on a factor. Moved up to this line so that if only 1st col is numeric, the function errors
if(sum(unlist(lapply(dat, is.numeric))) == 0){stop("input data.frame must contain at least one column of class numeric")} # changed from select_if as it can't handle numbers as col names

crosstab[[1]] <- as.character(crosstab[[1]]) # for type matching when binding the word "Total" on a factor

showing_col_totals <- (show_totals & denom %in% c("col", "all"))
showing_row_totals <- (show_totals & denom %in% c("row", "all"))

complete_n <- complete_n <- sum(crosstab[, -1], na.rm = TRUE) # capture for percent calcs before any totals col/row is added
complete_n <- complete_n <- sum(dat[, -1], na.rm = TRUE) # capture for percent calcs before any totals col/row is added

if(showing_col_totals){ crosstab <- add_totals_col(crosstab) }
if(showing_row_totals){ crosstab <- add_totals_row(crosstab) }
n_col <- ncol(crosstab)
if(showing_col_totals){ dat <- add_totals_col(dat) }
if(showing_row_totals){ dat <- add_totals_row(dat) }
n_col <- ncol(dat)

percs <- ns_to_percents(crosstab, denom, total_n = complete_n) # last argument only gets used in the "all" case = no harm in passing otherwise
percs <- ns_to_percents(dat, denom, total_n = complete_n) # last argument only gets used in the "all" case = no harm in passing otherwise

# round %s using specified method, add % sign
percs <- dplyr::mutate_at(percs, dplyr::vars(2:n_col), dplyr::funs(. * 100)) # since we'll be adding % sign - do this before rounding
Expand All @@ -56,7 +56,7 @@ adorn_crosstab <- function(crosstab, denom = "row", show_n = TRUE, digits = 1, s

# paste Ns if needed
if(show_n){
result <- paste_ns(percs, crosstab)
result <- paste_ns(percs, dat)
} else{ result <- percs}

as.data.frame(result) # drop back to data.frame from tibble
Expand Down
14 changes: 1 addition & 13 deletions R/adorn_helpers.R
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,4 @@ fix_parens_whitespace <- function(x){
fixed = TRUE)
}

}

# check that all columns in a data.frame beyond the first one are numeric
check_all_cols_after_first_are_numeric <- function(x){
non_numeric_count <- x %>%
dplyr::select(-1) %>%
lapply(function(x) !is.numeric(x)) %>%
unlist %>%
sum
if(non_numeric_count > 0){
stop("all columns after the first one must be numeric")
}
}
}
5 changes: 2 additions & 3 deletions R/ns_to_percents.R
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@
ns_to_percents <- function(dat, denom = "row", na.rm = TRUE, total_n = NULL){
# catch bad inputs
if(! denom %in% c("row", "col", "all")){stop("'denom' must be one of 'row', 'col', or 'all'")}
if(ncol(dplyr::select_if(clean_dat, is.numeric)) == 0){stop("data.frame must contain at least one column of class numeric")}

numeric_cols <- which(unlist(lapply(dat, is.numeric)))
numeric_cols <- setdiff(numeric_cols, 1) # assume 1st column should not be included so remove it from numeric_cols
numeric_cols <- setdiff(numeric_cols, 1) # assume 1st column should not be included so remove it from numeric_cols. Moved up to this line so that if only 1st col is numeric, the function errors
if(length(numeric_cols) == 0){stop("input data.frame must contain at least one column of class numeric")}

if(!is.null(total_n)){
if(!is.numeric(total_n)){stop("override_n must be numeric")}
Expand Down
6 changes: 3 additions & 3 deletions man/add_totals_col.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions man/add_totals_row.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions man/adorn_crosstab.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 0 additions & 16 deletions tests/testthat/test-adorn-helpers.R

This file was deleted.

4 changes: 2 additions & 2 deletions tests/testthat/test-ns-to-percents.R
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ test_that("NAs handled correctly with na.rm = FALSE", {
)
})

test_that("data.frames with non-numeric columns cause failure", {
test_that("data.frames with no numeric columns beyond the first cause failure", {
expect_error(ns_to_percents(data.frame(a = 1:2, b = c("hi", "lo"))),
"all columns after the first one must be numeric")
"input data.frame must contain at least one column of class numeric")
})

test_that("non-numeric argument to total_n fails", {
Expand Down

0 comments on commit 4e8950e

Please sign in to comment.