diff --git a/DESCRIPTION b/DESCRIPTION index dd0da69..6b4f988 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -38,6 +38,6 @@ Suggests: rmarkdown, stringi, testthat (>= 2.1.0) -RoxygenNote: 7.2.1 +RoxygenNote: 7.3.2 VignetteBuilder: knitr Encoding: UTF-8 diff --git a/R/crosstabs.R b/R/crosstabs.R index a2d33bd..9ba960a 100644 --- a/R/crosstabs.R +++ b/R/crosstabs.R @@ -26,6 +26,9 @@ #' should we include the default weighted vars? Defaults to TRUE. #' @param num_verbatims An integer identifying the number of examples to extract #' from a text variable. Defaults to 10. Implemented for Toplines only. +#' @param filter A list of `CrunchExpression`s, `CrunchFilter`s, or string names +#' of filters to be combined with the filter applied to dataset passed into the +#' `dataset` argument. #' @return A Toplines (when no banner is provided) or Crosstabs (when a banner #' is provided) summary of the input dataset. #' @examples @@ -44,7 +47,8 @@ crosstabs <- function( dataset, vars = names(dataset), weight = crunch::weight(dataset), banner = NULL, codebook = FALSE, include_numeric = FALSE, include_datetime = FALSE, include_verbatims = FALSE, - num_verbatims = 10, include_original_weighted = TRUE) { + num_verbatims = 10, include_original_weighted = TRUE, + filter = NULL) { wrong_class_error(dataset, "CrunchDataset", "dataset") # nolint start all_types <- crunch::types(crunch::allVariables(dataset)) @@ -121,6 +125,7 @@ crosstabs <- function( vars = vars_out, banner = banner_use, weight = weight_var, + filter = filter, topline = is.null(banner), include_original_weighted = include_original_weighted ) diff --git a/R/tabBooks.R b/R/tabBooks.R index fbc1a71..9b0dc00 100644 --- a/R/tabBooks.R +++ b/R/tabBooks.R @@ -11,9 +11,12 @@ #' @param weight A weighting variable passed to \link[crunch]{tabBook} #' @param topline Logical identifying if this is a topline only #' @param include_original_weighted Logical, if you have specified complex weights +#' @param filter A list of `CrunchExpression`s, `CrunchFilter`s, or string names +#' of filters to be combined with the filter applied to dataset passed into the +#' `dataset` argument. #' should the original weighted variable be included or only the custom weighted version? tabBooks <- function(dataset, vars, banner, weight = NULL, topline = FALSE, - include_original_weighted = TRUE) { + include_original_weighted = TRUE, filter = NULL) { banner_flatten <- unique(unlist(banner, recursive = FALSE)) names(banner_flatten) <- sapply(banner_flatten, function(v) v$alias) banner_use <- banner @@ -42,7 +45,8 @@ tabBooks <- function(dataset, vars, banner, weight = NULL, topline = FALSE, multitable, dataset = dataset[unique(c(vars, unique(tab_frame$weight)))], weight = weight, - append_default_wt = include_original_weighted + append_default_wt = include_original_weighted, + filter = filter ) ) } else { @@ -52,7 +56,8 @@ tabBooks <- function(dataset, vars, banner, weight = NULL, topline = FALSE, tabBook_crunchtabs( multitable, dataset = dataset[vars], - weight = weight + weight = weight, + filter = filter ) ) } diff --git a/R/tabbook-additions.R b/R/tabbook-additions.R index 974f870..2dc87d3 100644 --- a/R/tabbook-additions.R +++ b/R/tabbook-additions.R @@ -274,6 +274,9 @@ resultsObject <- function(x, top = NULL, weighted, body_values, body_labels, vec #' if specified, or the the autogenerated file name). If you request "json" and #' wish to access the JSON data underlying the `TabBookResult`, pass in a path #' for `file` and you will get a JSON file written there as well. +#' @param filter A list of `CrunchExpression`s, `CrunchFilter`s, or string names +#' of filters to be combined with the filter applied to dataset passed into the +#' `dataset` argument. #' @examples #' \dontrun{ #' m <- newMultitable(~ gender + age4 + marstat, data = ds) @@ -284,28 +287,29 @@ resultsObject <- function(x, top = NULL, weighted, body_values, body_labels, vec #' @importFrom jsonlite fromJSON #' @export tabBook_crunchtabs <- function(multitable, dataset, weight = crunch::weight(dataset), - append_default_wt = TRUE) { + append_default_wt = TRUE, filter = NULL) { if (is.null(weight) | is.variable(weight)) { - return(tabBookSingle_crunchtabs(multitable, dataset, weight)) + return(tabBookSingle_crunchtabs(multitable, dataset, weight, filter)) } else if (is.list(weight) || is.data.frame(weight)) { return(tabBookMulti_crunchtabs( multitable, dataset, weight, - append_default_wt + append_default_wt, + filter )) } else { stop("weight must be NULL, a CrunchVariable or a list indicating a multi-weight spec") } } -tabBookSingle_crunchtabs <- function(multitable, dataset, weight) { +tabBookSingle_crunchtabs <- function(multitable, dataset, weight, filter = NULL) { if (!is.null(weight)) { weight <- self(weight) } - # filter <- standardize_tabbook_filter(dataset, filter) + filter <- standardizeTabbookFilter(dataset, filter) body <- list( - filter = NULL, + filter = filter, weight = weight, options = list(format = NULL) ) @@ -331,6 +335,11 @@ varFilter <- function(dataset) { variablesFilter(dataset) } +standardizeTabbookFilter <- function(dataset, filter) { + func <- utils::getFromNamespace("standardize_tabbook_filter", "crunch") + func(dataset, filter) +} + download_result <- function(result) { retry <- utils::getFromNamespace("retry", "crunch") retry(crunch::crGET(result), wait = 0.5) # For mocks @@ -347,7 +356,8 @@ tabBookMulti_crunchtabs <- function( multitable, dataset, weight_spec, - append_default_wt) { + append_default_wt, + filter = NULL) { if (length(weight_spec) == 0) { stop("Empty list not allowed as a weight spec, use NULL to indicate no weights") } @@ -379,7 +389,8 @@ tabBookMulti_crunchtabs <- function( tabBookSingle_crunchtabs( multitable, dataset[page_vars], - weight = dataset[[wt]] + weight = dataset[[wt]], + filter = filter ) }) diff --git a/R/writeLatex.R b/R/writeLatex.R index b7bad44..52d49fc 100644 --- a/R/writeLatex.R +++ b/R/writeLatex.R @@ -315,7 +315,7 @@ latexDocHead <- function(theme, title, subtitle, banner = NULL) { "includeheadfoot" ), usepackage("array"), - usepackage("babel", "english"), + usepackage("babel"), # , "english" "\\newcolumntype{B}[2]{>{#1\\hspace{0pt}\\arraybackslash}b{#2}}", "\\setlength{\\parindent}{0pt}", usepackage("color", "dvipsnames"), diff --git a/inst/codebook_latex_wrap.tex b/inst/codebook_latex_wrap.tex index 6c6a5c9..b7cdd0d 100644 --- a/inst/codebook_latex_wrap.tex +++ b/inst/codebook_latex_wrap.tex @@ -20,7 +20,7 @@ } \usepackage[top=0.6in, bottom=0.6in, left=1in, right=1in, includeheadfoot]{geometry} \usepackage{array} -\usepackage[english]{babel} +\usepackage{babel} \newcolumntype{B}[2]{>{#1\hspace{0pt}\arraybackslash}b{#2}} \setlength{\parindent}{0pt} \usepackage[dvipsnames]{color} diff --git a/man/crosstabs.Rd b/man/crosstabs.Rd index 7501e3e..c4f9674 100644 --- a/man/crosstabs.Rd +++ b/man/crosstabs.Rd @@ -15,7 +15,8 @@ crosstabs( include_datetime = FALSE, include_verbatims = FALSE, num_verbatims = 10, - include_original_weighted = TRUE + include_original_weighted = TRUE, + filter = NULL ) toplines( @@ -28,7 +29,8 @@ toplines( include_datetime = FALSE, include_verbatims = FALSE, num_verbatims = 10, - include_original_weighted = TRUE + include_original_weighted = TRUE, + filter = NULL ) } \arguments{ @@ -64,6 +66,10 @@ from a text variable. Defaults to 10. Implemented for Toplines only.} \item{include_original_weighted}{Logical. When providing list of weights to apply, should we include the default weighted vars? Defaults to TRUE.} + +\item{filter}{A list of `CrunchExpression`s, `CrunchFilter`s, or string names +of filters to be combined with the filter applied to dataset passed into the +`dataset` argument.} } \value{ A Toplines (when no banner is provided) or Crosstabs (when a banner diff --git a/man/crunchtabs-package.Rd b/man/crunchtabs-package.Rd index 3d512a5..c423c24 100644 --- a/man/crunchtabs-package.Rd +++ b/man/crunchtabs-package.Rd @@ -2,8 +2,8 @@ % Please edit documentation in R/crunchtabs-package.R, R/crunchtabs.R \docType{package} \name{crunchtabs-package} -\alias{crunchtabs} \alias{crunchtabs-package} +\alias{crunchtabs} \title{crunchtabs: Custom Report Generation for Crunch Datasets} \description{ In order to generate custom survey reports, this package provides functions for computing 'toplines' (one-way frequency summaries), 'banners' (cross-tabulations) and codebooks of datasets in the Crunch (\url{https://crunch.io/}) web service. Reports can be written in 'PDF' format using 'LaTeX' or in Microsoft Excel '.xlsx' files. @@ -17,6 +17,13 @@ Useful links: \item Report bugs at \url{https://github.com/Crunch-io/crunchtabs/issues} } + +Useful links: +\itemize{ + \item \url{https://github.com/Crunch-io/crunchtabs} + \item Report bugs at \url{https://github.com/Crunch-io/crunchtabs/issues} +} + } \author{ \strong{Maintainer}: Brandon Bertelsen \email{brandon.bertelsen@yougov.com} diff --git a/man/tabBook_crunchtabs.Rd b/man/tabBook_crunchtabs.Rd index 652791f..25b2a80 100644 --- a/man/tabBook_crunchtabs.Rd +++ b/man/tabBook_crunchtabs.Rd @@ -8,7 +8,8 @@ tabBook_crunchtabs( multitable, dataset, weight = crunch::weight(dataset), - append_default_wt = TRUE + append_default_wt = TRUE, + filter = NULL ) } \arguments{ @@ -28,6 +29,10 @@ generated from the `multitable`'s name if one is not supplied and the of \code{\link{filters}} defined in the dataset.} \item{append_default_wt}{passed to [`tabBookWeightSpec()`] if `weight` is a list} + +\item{filter}{A list of `CrunchExpression`s, `CrunchFilter`s, or string names +of filters to be combined with the filter applied to dataset passed into the +`dataset` argument.} } \value{ If "json" format is requested, the function returns an object of diff --git a/man/tabBooks.Rd b/man/tabBooks.Rd index 6d6ac2f..e1bdfe3 100644 --- a/man/tabBooks.Rd +++ b/man/tabBooks.Rd @@ -10,7 +10,8 @@ tabBooks( banner, weight = NULL, topline = FALSE, - include_original_weighted = TRUE + include_original_weighted = TRUE, + filter = NULL ) } \arguments{ @@ -24,7 +25,11 @@ tabBooks( \item{topline}{Logical identifying if this is a topline only} -\item{include_original_weighted}{Logical, if you have specified complex weights +\item{include_original_weighted}{Logical, if you have specified complex weights} + +\item{filter}{A list of `CrunchExpression`s, `CrunchFilter`s, or string names +of filters to be combined with the filter applied to dataset passed into the +`dataset` argument. should the original weighted variable be included or only the custom weighted version?} } \description{ diff --git a/tests/testthat/ref/tabbook1.tex b/tests/testthat/ref/tabbook1.tex index 1075641..b8e3a25 100644 --- a/tests/testthat/ref/tabbook1.tex +++ b/tests/testthat/ref/tabbook1.tex @@ -11,7 +11,7 @@ \usepackage{booktabs, dcolumn, longtable} \usepackage[top=0.6in, bottom=0.6in, left=0.5in, right=0.5in, includeheadfoot]{geometry} \usepackage{array} -\usepackage[english]{babel} +\usepackage{babel} \newcolumntype{B}[2]{>{#1\hspace{0pt}\arraybackslash}b{#2}} \setlength{\parindent}{0pt} \usepackage[dvipsnames]{color} diff --git a/tests/testthat/ref/topline1.tex b/tests/testthat/ref/topline1.tex index 1e54e02..9f3d4da 100644 --- a/tests/testthat/ref/topline1.tex +++ b/tests/testthat/ref/topline1.tex @@ -11,7 +11,7 @@ \usepackage{booktabs, dcolumn, longtable} \usepackage[top=0.6in, bottom=0.6in, left=1in, right=1in, includeheadfoot]{geometry} \usepackage{array} -\usepackage[english]{babel} +\usepackage{babel} \newcolumntype{B}[2]{>{#1\hspace{0pt}\arraybackslash}b{#2}} \setlength{\parindent}{0pt} \usepackage[dvipsnames]{color} diff --git a/tests/testthat/test-nonTabBookSummary.R b/tests/testthat/test-nonTabBookSummary.R index 3c8f489..f44389e 100644 --- a/tests/testthat/test-nonTabBookSummary.R +++ b/tests/testthat/test-nonTabBookSummary.R @@ -34,7 +34,7 @@ test_that("Creates result object appropriately for a TextVariable", { ), class = c( "POSIXct", "POSIXt" - ))), .Names = NA_character_, class = "data.frame", row.names = c( + ), tzone = "UTC")), .Names = NA_character_, class = "data.frame", row.names = c( "Minimum", "1st Quartile", "Median", "3rd Quartile", "Maximum" )), diff --git a/tests/testthat/test-tabbooks.R b/tests/testthat/test-tabbooks.R index 4d2d9cc..9c893e5 100644 --- a/tests/testthat/test-tabbooks.R +++ b/tests/testthat/test-tabbooks.R @@ -193,6 +193,7 @@ test_that("tabBookSingle_crunchtabs", { mockery::stub(tabBookSingle_crunchtabs, "crunch::shojiURL", "shoji_url") mockery::stub(tabBookSingle_crunchtabs, "download_result", "downloaded_result") mockery::stub(tabBookSingle_crunchtabs, "varFilter", "Doesn't matter!") + mockery::stub(tabBookSingle_crunchtabs, "standardizeTabbookFilter", "Doesn't matter!") mockery::stub(tabBookSingle_crunchtabs, "tabBookResult", function(x) x) mockery::stub(tabBookSingle_crunchtabs, "crunch::crPOST", function(x, ...) x) res <- tabBookSingle_crunchtabs("mt", "dataset", weight = NULL) diff --git a/vignettes/Edgar-Anderson-s-Iris-Data.tex b/vignettes/Edgar-Anderson-s-Iris-Data.tex index 54f79ed..c7f56b1 100644 --- a/vignettes/Edgar-Anderson-s-Iris-Data.tex +++ b/vignettes/Edgar-Anderson-s-Iris-Data.tex @@ -19,7 +19,7 @@ } \usepackage[top=0.6in, bottom=0.6in, left=1in, right=1in, includeheadfoot]{geometry} \usepackage{array} -\usepackage[english]{babel} +\usepackage{babel} \newcolumntype{B}[2]{>{#1\hspace{0pt}\arraybackslash}b{#2}} \setlength{\parindent}{0pt} \usepackage[dvipsnames]{color} @@ -77,8 +77,8 @@ \vspace{.25in} \begin{longtable}[l]{ll} -Sample & The irises of the Gaspe Peninsula \\ -Conducted & 1935 \\ +Sample & The irises of the Gaspe Peninsula \\ +Conducted & 1935 \\ \end{longtable} This famous (Fisher's or Anderson's) iris data set gives the measurements in centimeters of the variables sepal length and width and petal length and width, respectively, for 50 flowers from each of 3 species of iris. The species are Iris setosa, versicolor, and virginica. @@ -96,7 +96,7 @@ \vskip 0.10in The length of the flower's sepal \addcontentsline{lot}{table}{\parbox{1.8in}{\ttfamily{Sepal.Length}} Sepal Length} -\vskip 0.10in\end{absolutelynopagebreak} +\vskip 0.10in\end{absolutelynopagebreak} \begin{longtable}[l]{cccccc} \toprule {Mean} & {SD} & {Min} & {Max} & {n} & {Missing}\\ @@ -115,7 +115,7 @@ \vskip 0.10in The iris species \addcontentsline{lot}{table}{\parbox{1.8in}{\ttfamily{Species}} Iris Species} -\vskip 0.10in\end{absolutelynopagebreak} +\vskip 0.10in\end{absolutelynopagebreak} \begin{longtable}[l]{JlK} \toprule {Code} & {Label} & {Count}\\ diff --git a/vignettes/Example-dataset.tex b/vignettes/Example-dataset.tex index df0ebd3..fa41e90 100644 --- a/vignettes/Example-dataset.tex +++ b/vignettes/Example-dataset.tex @@ -19,7 +19,7 @@ } \usepackage[top=0.6in, bottom=0.6in, left=1in, right=1in, includeheadfoot]{geometry} \usepackage{array} -\usepackage[english]{babel} +\usepackage{babel} \newcolumntype{B}[2]{>{#1\hspace{0pt}\arraybackslash}b{#2}} \setlength{\parindent}{0pt} \usepackage[dvipsnames]{color} @@ -77,10 +77,10 @@ \vspace{.25in} \begin{longtable}[l]{ll} -Sample & US Voting Adults \\ -Conducted & Feb 2021 - Mar 2021 \\ +Sample & US Voting Adults \\ +Conducted & Feb 2021 - Mar 2021 \\ \end{longtable} -You can enter arbitrary \textbf{\emph{tex}} in the preamble, but it must be double escaped. +You can enter arbitrary \textbf{\emph{tex}} in the preamble, but it must be double escaped. \renewcommand{\listtablename}{Table of Contents} @@ -97,7 +97,7 @@ \addcontentsline{lot}{table}{\parbox{1.8in}{\ttfamily{allpets}} All pets owned} \vskip 0.10in \emph{This is a note, usually describing a sub-sample} -\vskip 0.10in\end{absolutelynopagebreak} +\vskip 0.10in\end{absolutelynopagebreak} \begin{longtable}[l]{>{}ll} \toprule \multicolumn{2}{c}{Rows} \\ @@ -105,9 +105,9 @@ {Variable} & {Label}\\ \midrule \ttfamily{allpets\_1} & Cat\\ - + \ttfamily{allpets\_2} & Dog\\ - + \ttfamily{allpets\_3} & Bird\\ \bottomrule \end{longtable}\end{absolutelynopagebreak} @@ -120,11 +120,11 @@ {Code} & {Label}\\ \midrule 1 & selected\\ - + 2 & not selected\\ - + 8 & skipped\\ - + 9 & not asked\\ \bottomrule \end{longtable}\end{absolutelynopagebreak} @@ -137,9 +137,9 @@ {Variable} & {1} & {2} & {8} & {9}\\ \midrule \ttfamily{allpets\_1} & 1 & 2 & 3 & 4\\ - + \ttfamily{allpets\_2} & 4 & 1 & 3 & 2\\ - + \ttfamily{allpets\_3} & 4 & 4 & 1 & 1\\ \bottomrule \end{longtable}\end{absolutelynopagebreak} diff --git a/vignettes/FAQ.Rmd b/vignettes/FAQ.Rmd index 65ccc96..6a8f6d3 100644 --- a/vignettes/FAQ.Rmd +++ b/vignettes/FAQ.Rmd @@ -274,6 +274,51 @@ crosstabs_theme <- themeNew(..., pagebreak_in_banner = FALSE) ``` +### How do I filter the data used in a crosstabs to a subpopulation? + +For legacy reasons in the crunch package, there are many different ways to filter +data to a subpopulation in crosstabs. + +You can use a named filter saved to the dataset by passing the name or the `CrunchFilter` +object itself, or even a `FilterCatalog` to the `filter` parameter of `crosstabs()` +```{r, eval = FALSE} +# ... +topline_summary = crosstabs(ds, filter = "Young Folk") +# equivalent to +my_filter <- filters(ds)[["Young Folk"]] +topline_summary = crosstabs(ds, filter = my_filter) +# Also equivalent to +my_filters <- filters(ds)[c("Young Folk")] # This allows passing more than 1 filter +topline_summary = crosstabs(ds, filter = my_filters) +``` + +If there's no saved filter for the subpopulation you are targeting, you can pass a +`CrunchExpression` to the filter or filter the dataset passed in. +```{r, eval = FALSE} +# ... +topline_summary = crosstabs(ds, filter = ds$age <= 25) +# equivalent to +topline_summary = crosstabs(ds[ds$age <= 25, ]) +``` + +If more than one filter is passed in as a list to the filter argument (or if a filter +is passed in and the dataset is filtered) the filters are combined with `&`. +```{r, eval = FALSE} +# ... +# This will filter to respondents where age is 25 or under, with named filters +# "High Earners", "First wave" & "Healthy eaters" from the North region +topline_summary = crosstabs( + ds[ds$age <= 25, ], + filter = list("High Earners", filters(ds)[c("First wave", "Healthy eaters")], ds$region == "North") +) +# equivalent to +topline_summary = crosstabs( + ds, + filter = ds$age <= 25 & ds$income == "High" & ds$wave == 1 & ds$healthy_eater == "Yes" & ds$region == "North" +) +``` + + ### How do I request a new feature? We welcome features requests as new issues to the [crunchtabs github repository](https://github.com/Crunch-io/crunchtabs/issues)