Skip to content

Commit 41e978b

Browse files
authored
Merge pull request #239 from Crunch-io/develop
WIP: Finalizing tracking reports
2 parents 996a7b5 + fa320d7 commit 41e978b

File tree

9 files changed

+198
-36
lines changed

9 files changed

+198
-36
lines changed

R/asToplineCategoricalArray.R

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,12 @@
1212
#' @param labels A character vector of labels that will be displayed in the
1313
#' resulting crunchtabs output. Should match the number of results objects
1414
as.ToplineCategoricalArray <- function(questions, question_alias = NULL, labels) {
15-
1615
if (length(questions) != length(labels))
1716
stop("Number of labels provided does not match number of result sets")
1817

1918
# Use the first result item as a skeleton
2019
obj <- questions[[1]]
2120

22-
is_mr <- questions[[1]]$type == "multiple_response"
2321
is_catarray <- questions[[1]]$type == "categorical_array"
2422

2523
if(is_catarray) {

R/trackingReports.R

Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,43 +5,105 @@
55
#' @param dataset_list A list of two or more crunch datasets. Datasets should be
66
#' provided in time order. From oldest to youngest. (i.e, wave 1, wave 2,
77
#' ..., wave n)
8-
#' @param vars A character vector of question aliases to be included in the report
8+
#' @param vars A character vector of question aliases to be included in the
9+
#' report this may include aliases that are available in at least one of the
10+
#' datasets specified in dataset_list
911
#' @param weight NULL to accept each dataset's current weight or a single alias
10-
#' that is available in all datasets as a string.
12+
#' that is available in all datasets as a string. Multiple weights is not
13+
#' recommended in a tracking report.
1114
#' @param labels The labels for each wave. Should be of a length that
1215
#' matches the number of datasets.
1316
tracking_report <- function(dataset_list, vars, labels = NULL, weight = NULL) {
14-
# topline tabbooks
1517
tabs <- tracking_report_tabs(dataset_list, vars, weight)
1618

1719
if (is.null(labels))
1820
labels <- paste0("Wave ", seq_len(length(dataset_list)))
1921

20-
# Use the first result item as a skeleton
21-
rebuilt_results <- tabs[[1]]
22+
# In previous iterations we used the first item of tabs as a skeleton
23+
# However, what if there is an alias that is not included in the first
24+
# variable? Instead we build the rebuilt_results object piece by piece
25+
# using the first available result for each alias to create a skeleton
26+
27+
rebuilt_results <- list()
28+
class(rebuilt_results) <- c("Toplines", "CrunchTabs")
29+
rebuilt_results$results <- lapply(vars, function(x) NULL)
30+
rebuilt_results$metadata <- tabs[[1]]$metadata
31+
names(rebuilt_results$results) <- vars
32+
rebuilt_results$banner <- NULL
33+
34+
# Loop through each element of tabs, suck out the first result available
35+
# per alias and use that result as part of the skeleton. If there is more
36+
# than one result but less than n results, we need to denote that for future
37+
# use.
38+
#
39+
# For example, if someone has a survey where "q1" was asked in waves 1 and 3
40+
# but not 2 - we need a good way to identify this.
41+
42+
for (v in vars) {
43+
var_results <- lapply(tabs, function(x) return(x$results[[v]]))
44+
results_available <-which(!unlist(lapply(var_results, is.null)))
45+
first_var_result <- which(!unlist(lapply(var_results, is.null)))[1]
46+
rebuilt_results$results[[v]] <- var_results[[first_var_result]]
47+
rebuilt_results$results[[v]]$available_at <- results_available
48+
49+
50+
# For each alias, we set an attribute that identifies it's availability
51+
# across all the datasets: "all", "partial", and "single"
52+
# - "all" means it is available in every dataset
53+
# - "partial" means it is available in only some datasets
54+
# - "single" means it is available in exactly one dataset
55+
56+
# Because we use subsetting at the list level, "all" and "partial"
57+
# would follow a typical path that labeling was adjusted appropriately
58+
# for presentation in the resulting pdf "single" should act as a simple
59+
# passthrough where no additional formatting or manipulation takes place
60+
# on the result.
61+
62+
# The single case
63+
if(length(results_available) == 1) {
64+
rebuilt_results$results[[v]]$availability <- "single"
65+
} else {
66+
rebuilt_results$results[[v]]$availability <- "general"
67+
}
68+
69+
}
70+
71+
# Now that we have an attribute that identifies availability we can use it as
72+
# a trigger for logic that allows us to customize the result of each
73+
# condition.
74+
#
75+
# We wil loop over each variable and either combine those elements that are
76+
# setup for tracking, or passthrough those that are singles. As singles
77+
# represent the simplest case, we will deal with them first.
2278

2379
for (v in vars) {
24-
message("Preparing: ",v)
80+
if (rebuilt_results$results[[v]]$availability == "single") {
81+
next
82+
}
83+
84+
available_at <- rebuilt_results$results[[v]]$available_at
85+
86+
message("Preparing: ",v) # TODO: Delete me after feature dev
2587
result_list <- lapply(tabs, function(x) x$results[[v]])
2688
if(rebuilt_results$results[[v]]$type == "categorical_array") {
2789
rebuilt_results$results <- c(
2890
catArrayToCategoricals(
29-
result_list,
91+
result_list[available_at],
3092
question_alias=v,
31-
labels=labels
93+
labels=labels[available_at]
3294
),
3395
rebuilt_results$results
3496
)
3597
rebuilt_results$results[[v]] <- NULL
3698

37-
# Fix the class!
99+
# We must fake the class of the object
38100
class(rebuilt_results$results) <- c("ToplineResults", "CrosstabsResults")
39101
} else {
40102
rebuilt_results$results[[v]] <- as.ToplineCategoricalArray(
41-
result_list,
103+
result_list[available_at],
42104
question_alias = v,
43-
labels = labels
44-
)
105+
labels = labels[available_at]
106+
)
45107
}
46108

47109
}
@@ -59,7 +121,8 @@ tracking_report_tabs <- function(datasets, vars, weight = NULL) {
59121
if(is.null(weight)) {
60122
weight = weight(x)
61123
}
62-
crosstabs(x, vars, weight, include_numeric = TRUE)
124+
adj_vars = vars[vars %in% names(x)]
125+
crosstabs(x, adj_vars, weight, include_numeric = TRUE)
63126
}
64127
)
65128
}

R/writeCodeBookLatex.R

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ writeCodeBookLatex <- function(
2828
preamble = NULL, suppress_zero_counts = FALSE, appendix = TRUE, logo = NULL,
2929
position = NULL, path = NULL, filename = NULL, logging = FALSE,
3030
...) {
31+
32+
if(!is.null(crunch::weight(ds)))
33+
stop(paste(
34+
"Codebooks are designed to work with whole numbers. Your dataset is",
35+
"weighted and the resulting codebook will either not run or have",
36+
"breaking display issues."
37+
))
3138

3239
options("crunchtabs.codebook.suppress.zeros" = suppress_zero_counts)
3340

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,9 @@ While recontact reports are designed for questions asked in the same dataset, we
9898
)
9999

100100
writeLatex(ct, pdf = TRUE, theme = theme)
101-
102-
![Tracking Report Example - Flipped grids](example-016-tracking-report.png "Tracking reports")
101+
102+
103+
![Tracking Report Example - Flipped grids](vignettes/example-016-tracking-report.png)
103104

104105
### Create a Cross Tabulation
105106

dev-misc/tracking_reports_setup.R

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,25 @@ weight(ds3) <- ds3$weight1
3232
ds1 <- loadDataset("Example dataset W1")
3333
ds2 <- loadDataset("Example dataset W2")
3434
ds3 <- loadDataset("Example dataset W3")
35-
36-
37-
tema <- themeNew(default_theme = themeDefaultLatex(), latex_flip_grids = TRUE, one_per_sheet = FALSE)
38-
ct <- tracking_report(list(ds1, ds2, ds3), vars = c("allpets", "q1", "petloc"))
39-
writeLatex(ct, pdf = TRUE,theme = tema)
35+
ds1$only_wave1 <- factor(sample(letters[1:5], 20, replace = T))
36+
ds2$only_wave2 <- factor(sample(letters[1:5], 20, replace = T))
37+
ds3$only_wave3 <- factor(sample(letters[1:5], 20, replace = T))
38+
ds1$avail_wave13 <- factor(sample(letters[1:5], 20, replace = T))
39+
ds3$avail_wave13 <- factor(sample(letters[1:5], 20, replace = T))
40+
description(ds2$only_wave2) <- "This question is only available in wave 2"
41+
description(ds1$avail_wave13) <- "This question is only available in waves 1 and 3"
42+
description(ds1$avail_wave13) <- "This question is only available in waves 1 and 3"
43+
44+
thema <- themeNew(default_theme = themeDefaultLatex(), latex_flip_grids = FALSE, one_per_sheet = FALSE)
45+
ct <- tracking_report(list(ds1, ds2, ds3), vars = c("allpets", "q1", "only_wave2", "avail_wave13"))
46+
writeLatex(ct, pdf = TRUE,theme = thema, title = "Data from 3 Example Datasets")
47+
48+
49+
# Clean up
50+
51+
datasets() %>%
52+
as.data.frame() %>%
53+
filter(grepl("Example", name)) %>%
54+
pull(id) %>%
55+
lapply(function(x)
56+
with_consent(deleteDataset(sprintf("https://app.crunch.io/datasets/%s",x))))

tests/testthat/test-banner.R

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -151,16 +151,28 @@ test_that("Single banner with one variable, recodes - categories rename, else",
151151
expect_identical(banner_data[["Results"]][["age5"]][["categories"]], c("Under 25", "Over 54"))
152152
})
153153

154-
# TODO: Figure these out, maybe never.
155-
# with_test_authentication({
156-
# ds <- loadDataset("https://app.crunch.io/api/datasets/868e8b3e01834c45b73e56e80160d3c3/")
157-
# test_that("Error handling - banner", {
158-
# expect_warning(banner(ds, list(c(), "A"="art3")),
159-
# "No variables found in 'Banner1' in `vars`. 'Banner1' will be ignored.")
160-
#
161-
# expect_error(banner(ds, list(Results = c("profile_gender")), recodes = list(profile_gender = list("Male2"="Man"))),
162-
# "Responses in `recodes` must be included in variable responses. This is not true for 'Male2' in 'profile_gender'.")
163-
# expect_error(banner(ds, list(Results = c("profile_gender")), recodes = list(profile_gender = list("Male"="Man", "Female"="Man"))),
164-
# "Combining categories is not currently supported. Please check 'profile_gender' recodes.")
165-
# })
166-
# })
154+
context("getBannerInfo")
155+
156+
test_that("Returns default banner", {
157+
expect_equal(getBannerInfo(NULL), default_banner)
158+
})
159+
160+
context("removeInserts")
161+
162+
test_that("Adjustments for subtotals", {
163+
var <- list()
164+
theme <- list()
165+
theme$format_subtotals <- NULL
166+
theme$format_headers <- NULL
167+
var$inserts_obj <- list()
168+
var$inserts_obj$test <- "Fake Object of class Subtotal"
169+
class(var$inserts_obj$test) <- "Subtotal"
170+
var$inserts_obj$other <- "Fake Object of class Headers"
171+
class(var$inserts_obj$other) <- "Headers"
172+
173+
expect_equal(
174+
removeInserts(var, theme),
175+
list(inserts_obj = structure(list(), .Names = character(0)),
176+
inserts = structure(list(), .Names = character(0)))
177+
)
178+
})

tests/testthat/test-write-latex.R

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ with_temp_dir({
8585
writeLatex(cs, theme = theme, pdf = TRUE)
8686
theme <- themeNew(default_theme = theme, format_weighted_n=list(latex_add_parenthesis = TRUE))
8787
writeLatex(cs, theme = theme, pdf = TRUE)
88+
expect_true(file.remove("Example Dataset with Nets.pdf"))
89+
expect_true(file.remove("Example Dataset with Nets.tex"))
8890
})
8991

9092
test_that("Write Latex toplines", {
@@ -109,6 +111,8 @@ with_temp_dir({
109111
ts$results[[1]]$description <- bad_description
110112
writeLatex(ts, pdf = TRUE, file = "topline2")
111113
expect_true(file.exists("topline2.pdf"))
114+
expect_true(file.remove("Example Dataset with Nets.pdf"))
115+
expect_true(file.remove("Example Dataset with Nets.tex"))
112116
})
113117
})
114118

@@ -179,4 +183,9 @@ test_that("Adds nonTabBookSummary as expected", {
179183
any(grepl("clearpage$", res))
180184
)
181185

186+
})
187+
188+
test_that("Clean up", {
189+
expect_true(file.remove("Example Dataset with Nets.pdf"))
190+
expect_true(file.remove("Example Dataset with Nets.tex"))
182191
})

tests/testthat/test-writeCodeBookLatex.R

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ context("writeCodeBookLatex")
33
test_that("End to end writeCodeBookLatex", {
44
ds <- readRDS(test_path("fixtures/example_dataset.rds"))
55

6+
mockery::stub(
7+
writeCodeBookLatex,
8+
"crunch::weight", NULL
9+
)
10+
611
mockery::stub(
712
writeCodeBookLatex,
813
"codeBookItemBody",
@@ -19,7 +24,9 @@ test_that("End to end writeCodeBookLatex", {
1924
sample_desc = "US Voting Adults",
2025
logo = "yougov",
2126
pdf = TRUE)
27+
2228
)
29+
2330
tex <- readLines("Example-dataset.tex")
2431
expect_equal(res, NULL)
2532
expect_equal(length(tex), 149)
@@ -54,12 +61,19 @@ test_that("End to end writeCodeBookLatex", {
5461
test_that("Dataset name as title if title not specified", {
5562
ds <- readRDS(test_path("fixtures/example_dataset.rds"))
5663

64+
mockery::stub(
65+
writeCodeBookLatex,
66+
"crunch::weight", NULL
67+
)
68+
5769
mockery::stub(
5870
writeCodeBookLatex,
5971
"codeBookItemBody",
6072
readRDS(test_path("fixtures/codeBookItem_allpets.rds"))
6173
)
6274

75+
76+
6377
mockery::stub(
6478
writeCodeBookLatex,
6579
"crunch::name",
@@ -87,6 +101,11 @@ test_that("Dataset name as title if title not specified", {
87101
test_that("Dataset name as title if title not specified", {
88102
ds <- readRDS(test_path("fixtures/example_dataset.rds"))
89103

104+
mockery::stub(
105+
writeCodeBookLatex,
106+
"crunch::weight", NULL
107+
)
108+
90109
mockery::stub(
91110
writeCodeBookLatex,
92111
"codeBookItemBody",
@@ -131,6 +150,11 @@ test_that("Appendices are positioned as expected", {
131150
# dput %>%
132151
# saveRDS("tests/testthat/fixtures/codeBookItem_inputregstate")
133152

153+
mockery::stub(
154+
writeCodeBookLatex,
155+
"crunch::weight", NULL
156+
)
157+
134158
mockery::stub(
135159
writeCodeBookLatex,
136160
"codeBookItemBody",
@@ -174,6 +198,10 @@ test_that("Position functions as expected", {
174198
# ds <- loadDataset("Example dataset")
175199
# codeBookItemBody(ds$allpets) %>% dput() %>%
176200
# saveRDS("tests/testthat/fixtures/codeBookItem_allpets.rds")
201+
mockery::stub(
202+
writeCodeBookLatex,
203+
"crunch::weight", NULL
204+
)
177205

178206
mockery::stub(
179207
writeCodeBookLatex,
@@ -242,3 +270,30 @@ test_that("default yg logo returns normal path", {
242270
p <- default_yg_logo()
243271
expect_equal(p, system.file("YouGov.png", package = "crunchtabs"))
244272
})
273+
274+
test_that("Expect a stop if dataset is weighted", {
275+
ds <- readRDS(test_path("fixtures/example_dataset.rds"))
276+
277+
mockery::stub(
278+
writeCodeBookLatex,
279+
"crunch::weight", "weight_alias"
280+
)
281+
282+
mockery::stub(
283+
writeCodeBookLatex,
284+
"codeBookItemBody",
285+
readRDS(test_path("fixtures/codeBookItem_allpets.rds"))
286+
)
287+
288+
mockery::stub(writeCodeBookLatex, "file.open", NULL)
289+
290+
expect_error(suppressWarnings(
291+
writeCodeBookLatex(
292+
ds[c("allpets")],
293+
title = "Hello",
294+
subtitle = "Goodbye",
295+
sample_desc = "US Voting Adults",
296+
logo = "yougov",
297+
pdf = TRUE)
298+
), "Codebooks are designed to work with whole numbers")
299+
})

vignettes/Tracking-Recontact-Reports.Rmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ There are a number of important elements here:
133133

134134
4. Many defaults are extracted from the first dataset in the `dataset_list`.
135135

136-
![](example-016-tracking-report.png "Tracking reports")
136+
![Example tracking report](example-016-tracking-report.png "Tracking reports")
137137

138138
### Tracking Reports for Other Stacking Data
139139

0 commit comments

Comments
 (0)