Skip to content

Commit a46a9e1

Browse files
authored
Merge pull request #198 from Crunch-io/develop
WIP: Release 1.30
2 parents bc0b91a + 7a63ed2 commit a46a9e1

File tree

137 files changed

+16232
-13751
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

137 files changed

+16232
-13751
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
- name: Add kableExtra
1313
run: Rscript -e 'devtools::install_github("haozhu233/kableExtra")'
1414
- name: CRAN crunch
15-
run: Rscript -e 'install.packages("crunch")'
15+
run: Rscript -e 'devtools::install_github("crunch-io/rcrunch", ref = "gfe-multi-weight-tabbook")'
1616
- name: Build
1717
run: R CMD build --no-build-vignettes --no-manual .
1818
- name: Check

DESCRIPTION

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Description: In order to generate custom survey reports, this package provides
66
'banners' (cross-tabulations) of datasets in the Crunch
77
(<https://crunch.io/>) web service. Reports can be written in 'PDF' format
88
using 'LaTeX' or in Microsoft Excel '.xlsx' files.
9-
Version: 1.2.9
9+
Version: 1.3.0
1010
Authors@R: c(
1111
person("Persephone", "Tsebelis", role="aut"),
1212
person("Kamil", "Sedrowicz", role="aut"),
@@ -19,7 +19,7 @@ Depends:
1919
R (>= 3.5.0),
2020
crunch
2121
Imports:
22-
kableExtra,
22+
kableExtra (>= 1.1.0.9000),
2323
rlang,
2424
openxlsx,
2525
digest,
@@ -31,9 +31,9 @@ Suggests:
3131
httptest (>= 2.0.0),
3232
jsonlite,
3333
knitr,
34+
httpcache,
3435
rmarkdown,
35-
testthat (>= 2.1.0),
36-
kableExtra (>= 1.1.0.9000)
36+
testthat (>= 2.1.0)
3737
RoxygenNote: 7.1.1
3838
VignetteBuilder: knitr
3939
Encoding: UTF-8

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export(themeHuffPoCrosstabs)
5353
export(themeHuffPoToplines)
5454
export(themeNew)
5555
export(themeUKPolitical)
56+
export(toplines)
5657
export(with_api_fixture)
5758
export(writeCodeBookLatex)
5859
export(writeExcel)

NEWS.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
## crunchtabs 1.3.0
2+
3+
- Fixes problems with enforce_onehundred (#195)
4+
- Adds option to remove page numbers from toplines/crosstabs (#200)
5+
- Add toplines() function as an alias to crosstabs() (#201)
6+
- Add functionality for flipping grids and presenting recontact questions (#103 )
7+
- Fixes two new issues with codebooks where kableExtra added breaking elements and an issue with the basename of a dataaset containing special characters (#204, #205 )
8+
- Adds support for custom weights (#209)
9+
- Adds support for recontact_toplines (#199)
10+
- Adds functionality for flipping grids on an exceptional basis (#212)
11+
- Fixes a bug with include_q_number = FALSE (#207)
12+
- Vignettes for recontact toplines, custom weights and other (#218)
13+
114
## crunchtabs 1.2.9
215

316
- Codebook question descriptions now appropriately escape special characters (#187)

R/codebookLatex.R

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,12 +184,14 @@ codeBookItemBody.CategoricalVariable <- function(x, ...) {
184184

185185

186186
if (max(nchar(k$`{Label}`)) > 80) {
187-
kab %>% kableExtra::column_spec(2, width = "5.25in") %>%
187+
kab <- kab %>% kableExtra::column_spec(2, width = "5.25in") %>%
188188
kable_styling_defaults(...)
189189
} else {
190-
kab %>%
190+
kab <- kab %>%
191191
kable_styling_defaults(...)
192192
}
193+
# Fix for square braces in options
194+
gsub("\\hspace*{0in}", "", kab, fixed = TRUE)
193195

194196
}
195197

@@ -436,11 +438,17 @@ curlyWrap <- function(x) paste0("{", x, "}")
436438
#' @param k A data.frame to be printed using \link[kableExtra]{kable}
437439
#' @param alignment A string vector of alignments
438440
scolumnAlign <- function(k, alignment) {
439-
nchars <- unlist(lapply(k, function(x) max(nchar(x), na.rm = TRUE)))
441+
nchars <- unlist(lapply(k, function(x) suppressWarnings(max(nchar(x), na.rm = TRUE))))
440442

441443
for (i in 1:ncol(k)) {
442444
if (alignment[i] == "d") {
443-
maxnchar <- max(nchar(k[[i]]), na.rm = TRUE)
445+
# If entire column is NA, set to two
446+
if (all(is.na(k[[i]]))) {
447+
maxnchar <- 2
448+
} else {
449+
maxnchar <- max(nchar(k[[i]]), na.rm = TRUE)
450+
}
451+
444452
if (maxnchar > 6) {
445453
alignment[i] <- sprintf("S[table-format=%s]", maxnchar)
446454
} else {

R/crosstabs.R

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@
66
#' @param dataset A Crunch dataset.
77
#' @param vars An optional vector of aliases of the non-hidden variables that shoulds be used.
88
#' Defaults to all non-hidden variables.
9-
#' @param weight The alias of a numeric variable that should be used for data weighting.
10-
#' Defaults to current weight variable. For unweighted, set to \code{NULL}
9+
#' @param weight The alias of a numeric variable that should be used for data
10+
#' weighting. Alternatively a named list where the name is the alias of the weight
11+
#' and the contents of the list component are a character vector of aliases to
12+
#' which that weight should apply. Defaults to current weight variable. For
13+
#' unweighted, set to \code{NULL}
1114
#' @param banner An optional object of class \code{Banner} that should be used to generate
1215
#' a Crosstabs summary. Defaults to \code{NULL} - a Toplines summary is produced and returned.
1316
#' @param codebook If \code{TRUE}, codebook data summaries are prepared. Defaults to \code{FALSE}.
1417
#' @param include_numeric Logical. Should we include numeric questions? Defaults to FALSE. Implemented for Toplines only.
1518
#' @param include_datetime Logical. Should we include date time questions? Defaults to FALSE. Implemented for Toplines only.
1619
#' @param include_verbatims Logical. Should we include a sample text varaibles? Defaults to FALSE. Implemented for Toplines only.
20+
#' @param include_original_weighted Logical. When providing list of weights to apply, should we include the default weighted vars? Defaults to TRUE.
1721
#' @param num_verbatims An integer identifying the number of examples to extract from a text variable. Defaults to 10. Implemented for Toplines only.
1822
#' @return A Toplines (when no banner is provided) or Crosstabs (when a banner is provided)
1923
#' summary of the input dataset.
@@ -26,9 +30,8 @@
2630
#' @importFrom crunch name aliases allVariables is.Numeric is.dataset weight alias weightVariables is.variable
2731
#' @importFrom methods is
2832
#' @export
29-
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) {
33+
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) {
3034

31-
# TODO: open ends
3235
wrong_class_error(dataset, "CrunchDataset", "dataset")
3336

3437
all_types = crunch::types(crunch::allVariables(dataset))
@@ -41,35 +44,42 @@ crosstabs <- function(dataset, vars = names(dataset), weight = crunch::weight(da
4144
quotes = TRUE
4245
)
4346

44-
if (!is.null(weight)) {
45-
if (crunch::is.variable(weight)) { weight <- crunch::alias(weight) }
47+
if (!is.null(weight) & !is.list(weight)) {
48+
49+
if (crunch::is.variable(weight)) {
50+
weight <- crunch::alias(weight)
51+
}
52+
4653
if (!weight %in% all_aliases) {
4754
stop("`weight`, if provided, must be a valid variable in `dataset`. '",
4855
weight, "' is not found."
4956
)
5057
}
58+
5159
if (!weight %in% weightVariables(dataset)) {
5260
stop(
5361
"`weight`, if provided, must be a valid weight variable in `dataset`. '",
5462
weight,
5563
"' is not a weight variable."
5664
)
5765
}
66+
5867
}
5968
if (!is.null(banner) && !is(banner, "Banner")) {
6069
stop("`banner`, if provided, must be an object of class 'Banner'.")
6170
}
6271

63-
weight_var <- if (!is.null(weight)) dataset[[weight]]
64-
65-
vars_out <- if (codebook) { vars } else {
66-
intersect(vars, all_aliases[all_types %in% c("categorical", "multiple_response", "categorical_array", "numeric")]) }
72+
if (!is.null(weight) & !is.list(weight)) {
73+
weight_var <- dataset[[weight]]
74+
} else {
75+
weight_var <- weight
76+
}
6777

78+
# TODO: Add check here to verify variables in weight are included
79+
# in vars and the ds
6880

69-
# error_if_items(
70-
# unique(types(allVariables(dataset[setdiff(vars, vars_out)]))),
71-
# "`vars` of type(s) {items} are not supported and have been skipped.",
72-
# and = TRUE, error = FALSE)
81+
vars_out <- if (codebook) { vars } else {
82+
intersect(vars, all_aliases[all_types %in% c("categorical", "multiple_response", "categorical_array")]) }
7383

7484
if (length(vars_out) == 0) {
7585
stop("No variables provided.")
@@ -89,7 +99,8 @@ crosstabs <- function(dataset, vars = names(dataset), weight = crunch::weight(da
8999
vars = vars_out,
90100
banner = banner_use,
91101
weight = weight_var,
92-
topline = is.null(banner)
102+
topline = is.null(banner),
103+
include_original_weighted = include_original_weighted
93104
)
94105

95106
if (codebook) {
@@ -217,3 +228,7 @@ crosstabs <- function(dataset, vars = names(dataset), weight = crunch::weight(da
217228

218229
return(summary_data)
219230
}
231+
232+
#' @describeIn crosstabs An alias for \code{crosstabs}
233+
#' @export
234+
toplines <- crosstabs

R/recontactQuestion.R

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#' Recontact Toplines
2+
#'
3+
#' Allows the user to create a simple report that shows recontact question.
4+
#' @param dataset A crunch dataset
5+
#' @param questions A character vector of aliases that should be included in the
6+
#' report. If your recontact has been named using a suffix such as _pre, _post
7+
#' leave that out.
8+
#' @param suffixes The suffixes of recontact questions, for example _pre, _post
9+
#' @param labels Formal labels for
10+
#' the election", "After the election".
11+
#' @param weights A character vector of equal to the length of suffixes. You may
12+
#' specify a unique weight per recontact period. The default would return
13+
#' all variables with the default survey weighting `weight(ds)`. Your weights
14+
#' should be in the same order as your suffixies.
15+
#' @param default_weight The default weight of the dataset, if any.
16+
recontact_toplines <- function(dataset, questions, suffixes, labels,
17+
weights = crunch::weight(dataset), default_weight = crunch::alias(crunch::weight(dataset))) {
18+
19+
stopifnot(is.dataset(dataset))
20+
stopifnot(is.character(questions))
21+
stopifnot(is.character(suffixes))
22+
stopifnot(is.character(labels))
23+
24+
groupings <- lapply(questions, function(x) paste0(x, suffixes))
25+
names(groupings) <- questions
26+
vars <- unlist(groupings)
27+
28+
if (length(weights) > 1) {
29+
weight_spec <- lapply(suffixes, function(x) vars[grepl(x, vars)])
30+
names(weight_spec) <- weights
31+
}
32+
33+
ct <- crosstabs(
34+
dataset,
35+
vars = c(unlist(groupings), names(weight_spec)),
36+
weight = weight_spec,
37+
include_original_weighted = FALSE
38+
)
39+
40+
for (question in questions) {
41+
42+
if (!is.null(weights)) {
43+
44+
if (weights[1] == default_weight) {
45+
p1 <- groupings[[question]][1]
46+
} else {
47+
p1 <- paste0(groupings[[question]][1],"_", weights[1])
48+
}
49+
50+
if (weights[2] == default_weight) {
51+
p2 <- groupings[[question]][2]
52+
} else {
53+
p2 <- paste0(groupings[[question]][2],"_", weights[2])
54+
}
55+
56+
} else {
57+
58+
p1 <- groupings[[question]][1]
59+
p2 <- groupings[[question]][2]
60+
}
61+
62+
ct$results[[question]] <- as.ToplineCategoricalArray(
63+
ct$results[[p1]],
64+
ct$results[[p2]],
65+
question,
66+
labels,
67+
weights
68+
)
69+
70+
ct$results[[p1]] <- NULL
71+
ct$results[[p2]] <- NULL
72+
73+
}
74+
75+
ct
76+
}
77+
78+
#' Combine two questions as a categorical array
79+
#'
80+
#' Here we manipulate the tabBook results so that they match the layout
81+
#' of a categoricalArray, which has the benefit of already having
82+
#' distinct code to write it to latex.
83+
#'
84+
#' @param q1 The results object for the first question
85+
#' @param q2 The results object for the second question
86+
#' @param question_alias A string specifying the resulting alias.
87+
#' @param labels Two character strings used to describe the pre and post waves
88+
#' @param weights A single alias, list, or NULL
89+
as.ToplineCategoricalArray <- function(q1, q2, question_alias = NULL, labels = c("Pre", "Post"), weights) {
90+
91+
q1$alias <- question_alias
92+
q1$subnames <- labels
93+
q1$notes <- paste0(labels, " is weighted by ", weights, collapse = " : ")
94+
q1$type <- "categorical_array"
95+
96+
matrix_rows <- length(
97+
attr(q1$crosstabs$Results$`___total___`$counts, "dimnames")[[1]]
98+
)
99+
100+
# Build counts
101+
m <- matrix(
102+
c(
103+
as.numeric(q1$crosstabs$Results$`___total___`$counts),
104+
as.numeric(q2$crosstabs$Results$`___total___`$counts)),
105+
nrow = matrix_rows
106+
)
107+
108+
dimnames(m) <- list(
109+
attr(q1$crosstabs$Results$`___total___`$counts, "dimnames")[[1]],
110+
labels
111+
)
112+
113+
q1$crosstabs$Results$`___total___`$counts <- m
114+
115+
# Build proportions
116+
m <- matrix(
117+
c(
118+
as.numeric(q1$crosstabs$Results$`___total___`$proportions),
119+
as.numeric(q2$crosstabs$Results$`___total___`$proportions)
120+
),
121+
nrow = matrix_rows
122+
)
123+
124+
dimnames(m) <- list(
125+
attr(q1$crosstabs$Results$`___total___`$counts, "dimnames")[[1]],
126+
labels
127+
)
128+
129+
q1$crosstabs$Results$`___total___`$proportions <- m
130+
131+
class(q1) <- c("ToplineCategoricalArray", "ToplineVar", "CrossTabVar")
132+
133+
q1
134+
135+
}

R/reformatResults.R

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ reformatVar <- function(var, banner_name, theme, proportions, banner_info, latex
308308
#' @param var The crunch variable
309309
#' @param theme The theme object from \link{themeNew}
310310
getVarInfo <- function(var, theme) {
311+
311312
if_there <- function(str) {
312313
if (!is.null(str) && !is.na(str) && str != "") {
313314
return(str)
@@ -322,8 +323,13 @@ getVarInfo <- function(var, theme) {
322323
format_var_filtertext = if_there(var[["notes"]]),
323324
format_var_subname = if_there(var[["subname"]])
324325
)
326+
327+
if (is.null(var_info$format_var_description))
328+
var_info$format_var_description <- var_info$format_var_name
329+
325330
number <- if_there(var[["number"]])
326331
var_info2 <- list()
332+
327333
for (info_name in intersect(names(theme), names(var_info))) {
328334
if (!is.null(theme[[info_name]]) && (var$type != "categorical_array" ||
329335
(is.null(theme[[info_name]]$repeat_for_subs) ||

0 commit comments

Comments
 (0)