Skip to content

Commit d72cc9b

Browse files
committed
Support drop in facet_grid and facet_wrap.
Fixes #163
1 parent 29caa3a commit d72cc9b

File tree

9 files changed

+158
-31
lines changed

9 files changed

+158
-31
lines changed

R/facet-grid-.r

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
#' @param shrink If \code{TRUE}, will shrink scales to fit output of
2525
#' statistics, not raw data. If \code{FALSE}, will be range of raw data
2626
#' before statistical summary.
27+
#' @param drop If \code{TRUE}, the default, all factor levels not used in the
28+
#' data will automatically be dropped. If \code{FALSE}, all factor levels
29+
#' will be shown, regardless of whether or not they appear in the data.
2730
#' @export
2831
#' @examples
2932
#' # faceting displays subsets of the data in different panels
@@ -122,7 +125,7 @@
122125
#' p <- qplot(wt, mpg, data = mtcars)
123126
#' p + facet_grid(~ vs, labeller = label_bquote(alpha ^ .(x)))
124127
#' p + facet_grid(~ vs, labeller = label_bquote(.(x) ^ .(x)))
125-
facet_grid <- function(facets, margins = FALSE, scales = "fixed", space = "fixed", shrink = TRUE, labeller = "label_value", as.table = TRUE) {
128+
facet_grid <- function(facets, margins = FALSE, scales = "fixed", space = "fixed", shrink = TRUE, labeller = "label_value", as.table = TRUE, drop = TRUE) {
126129
scales <- match.arg(scales, c("fixed", "free_x", "free_y", "free"))
127130
free <- list(
128131
x = any(scales %in% c("free_x", "free")),
@@ -152,18 +155,19 @@ facet_grid <- function(facets, margins = FALSE, scales = "fixed", space = "fixed
152155
facet(
153156
rows = rows, cols = cols, margins = margins, shrink = shrink,
154157
free = free, space_is_free = (space == "free"),
155-
labeller = labeller, as.table = as.table,
158+
labeller = labeller, as.table = as.table, drop = drop,
156159
subclass = "grid"
157160
)
158161
}
159162

160163
#' @S3method facet_train_layout grid
161164
facet_train_layout.grid <- function(facet, data) {
162-
layout <- layout_grid(data, facet$rows, facet$cols, facet$margins)
163-
165+
layout <- layout_grid(data, facet$rows, facet$cols, facet$margins,
166+
facet$drop)
167+
164168
# Relax constraints, if necessary
165-
layout$SCALE_X <- if (facet$free$x) layout$COL else 1
166-
layout$SCALE_Y <- if (facet$free$y) layout$ROW else 1
169+
layout$SCALE_X <- if (facet$free$x) layout$COL else 1L
170+
layout$SCALE_Y <- if (facet$free$y) layout$ROW else 1L
167171

168172
layout
169173
}

R/facet-layout.r

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
# @return a data frame with columns \code{PANEL}, \code{ROW} and \code{COL},
77
# that match the facetting variable values up with their position in the
88
# grid
9-
layout_grid <- function(data, rows = NULL, cols = NULL, margins = NULL) {
9+
layout_grid <- function(data, rows = NULL, cols = NULL, margins = NULL, drop = TRUE) {
1010
if (length(rows) == 0 && length(cols) == 0) return(layout_null())
1111
rows <- as.quoted(rows)
1212
cols <- as.quoted(cols)
13+
1314

14-
base_rows <- layout_base(data, rows)
15-
base_cols <- layout_base(data, cols)
15+
base_rows <- layout_base(data, rows, drop = drop)
16+
base_cols <- layout_base(data, cols, drop = drop)
1617
base <- df.grid(base_rows, base_cols)
1718

1819
# Add margins
@@ -24,8 +25,8 @@ layout_grid <- function(data, rows = NULL, cols = NULL, margins = NULL) {
2425
panel <- id(base, drop = TRUE)
2526
panel <- factor(panel, levels = seq_len(attr(panel, "n")))
2627

27-
rows <- if (is.null(names(rows))) 1 else id(base[names(rows)], drop = TRUE)
28-
cols <- if (is.null(names(cols))) 1 else id(base[names(cols)], drop = TRUE)
28+
rows <- if (is.null(names(rows))) 1L else id(base[names(rows)], drop = TRUE)
29+
cols <- if (is.null(names(cols))) 1L else id(base[names(cols)], drop = TRUE)
2930

3031
panels <- data.frame(PANEL = panel, ROW = rows, COL = cols, base)
3132
arrange(panels, PANEL)
@@ -35,28 +36,26 @@ layout_grid <- function(data, rows = NULL, cols = NULL, margins = NULL) {
3536
#
3637
# @params drop should missing combinations be excluded from the plot?
3738
# @keywords internal
38-
layout_wrap <- function(data, vars = NULL, nrow = NULL, ncol = NULL, as.table = TRUE) {
39+
layout_wrap <- function(data, vars = NULL, nrow = NULL, ncol = NULL, as.table = TRUE, drop = TRUE) {
3940
vars <- as.quoted(vars)
4041
if (length(vars) == 0) return(layout_null())
4142

42-
base <- unrowname(layout_base(data, vars))
43+
base <- unrowname(layout_base(data, vars, drop = drop))
4344

4445
id <- id(base, drop = TRUE)
4546
n <- attr(id, "n")
4647

4748
dims <- wrap_dims(n, nrow, ncol)
48-
4949
layout <- data.frame(PANEL = factor(id, levels = seq_len(n)))
5050

5151
if (as.table) {
52-
layout$ROW <- (as.integer(id) - 1L) %/% dims[2] + 1L
52+
layout$ROW <- as.integer((id - 1L) %/% dims[2] + 1L)
5353
} else {
54-
layout$ROW <- dims[1] - (as.integer(id) - 1L) %/% dims[2]
54+
layout$ROW <- as.integer(dims[1] - (id - 1L) %/% dims[2])
5555
}
56-
layout$COL <- (as.integer(id) - 1L) %% dims[2] + 1L
56+
layout$COL <- as.integer((id - 1L) %% dims[2] + 1L)
5757

58-
layout <- cbind(layout, base)
59-
layout[order(layout$PANEL), ]
58+
cbind(layout, unrowname(base))
6059
}
6160

6261
layout_null <- function(data) {
@@ -68,7 +67,7 @@ layout_null <- function(data) {
6867
#
6968
# @params data list of data frames (one for each layer)
7069
# @keywords internal
71-
layout_base <- function(data, vars = NULL) {
70+
layout_base <- function(data, vars = NULL, drop = TRUE) {
7271
if (length(vars) == 0) return(data.frame())
7372

7473
# For each layer, compute the facet values
@@ -80,20 +79,44 @@ layout_base <- function(data, vars = NULL) {
8079
if (!any(has_all)) {
8180
stop("At least one layer must contain all variables used for facetting")
8281
}
83-
base <- unique(ldply(values[has_all]))
82+
83+
base <- unique(ldply(values[has_all]))
84+
if (!drop) {
85+
base <- unique_combs(base)
86+
}
8487

8588
# Systematically add on missing combinations
8689
for (value in values[!has_all]) {
8790
if (empty(value)) next;
8891

8992
old <- base[setdiff(names(base), names(value))]
9093
new <- unique(value[intersect(names(base), names(value))])
94+
if (drop) {
95+
new <- unique_combs(new)
96+
}
9197

9298
base <- rbind(base, df.grid(old, new))
9399
}
94100
base
95101
}
96102

103+
ulevels <- function(x) {
104+
if (is.factor(x)) {
105+
x <- addNA(x, TRUE)
106+
factor(levels(x), levels(x), exclude = NULL)
107+
} else {
108+
sort(unique(x))
109+
}
110+
}
111+
112+
unique_combs <- function(df) {
113+
if (length(df) == 0) return()
114+
115+
unique_values <- llply(df, ulevels)
116+
rev(expand.grid(rev(unique_values), stringsAsFactors = FALSE,
117+
KEEP.OUT.ATTRS = TRUE))
118+
}
119+
97120
df.grid <- function(a, b) {
98121
if (nrow(a) == 0) return(b)
99122
if (nrow(b) == 0) return(a)
@@ -102,11 +125,10 @@ df.grid <- function(a, b) {
102125
i_a = seq_len(nrow(a)),
103126
i_b = seq_len(nrow(b))
104127
)
105-
both <- cbind(
128+
unrowname(cbind(
106129
a[indexes$i_a, , drop = FALSE],
107130
b[indexes$i_b, , drop = FALSE]
108-
)
109-
131+
))
110132
}
111133

112134
quoted_df <- function(data, vars) {

R/facet-locate.r

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ locate_grid <- function(data, panels, rows = NULL, cols = NULL, margins = FALSE)
3636
data$PANEL <- 1
3737
} else {
3838
facet_vals[] <- lapply(facet_vals[], as.factor)
39+
facet_vals[] <- lapply(facet_vals[], addNA, ifany = TRUE)
3940
keys <- join.keys(facet_vals, panels, by = vars)
4041

4142
data$PANEL <- panels$PANEL[match(keys$x, keys$y)]

R/facet-wrap.r

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
#' facet_wrap(~ cyl)
5151
#' p + geom_point(data = transform(cyl6, cyl = NULL), colour = "red") +
5252
#' facet_wrap(~ cyl)
53-
facet_wrap <- function(facets, nrow = NULL, ncol = NULL, scales = "fixed", shrink = TRUE, as.table = TRUE) {
53+
facet_wrap <- function(facets, nrow = NULL, ncol = NULL, scales = "fixed", shrink = TRUE, as.table = TRUE, drop = TRUE) {
5454
scales <- match.arg(scales, c("fixed", "free_x", "free_y", "free"))
5555
free <- list(
5656
x = any(scales %in% c("free_x", "free")),
@@ -68,14 +68,14 @@ facet_wrap <- function(facets, nrow = NULL, ncol = NULL, scales = "fixed", shrin
6868
#' @S3method facet_train_layout wrap
6969
facet_train_layout.wrap <- function(facet, data) {
7070
panels <- layout_wrap(data, facet$facets, facet$nrow, facet$ncol,
71-
facet$as.table)
71+
facet$as.table, facet$drop)
7272

7373
n <- nrow(panels)
7474
nrow <- max(panels$ROW)
7575

7676
# Add scale identification
77-
panels$SCALE_X <- if (facet$free$x) seq_len(n) else 1
78-
panels$SCALE_Y <- if (facet$free$y) seq_len(n) else 1
77+
panels$SCALE_X <- if (facet$free$x) seq_len(n) else 1L
78+
panels$SCALE_Y <- if (facet$free$y) seq_len(n) else 1L
7979

8080
# Figure out where axes should go
8181
panels$AXIS_X <- if (facet$free$x) TRUE else panels$ROW == nrow

R/panel.r

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ new_panel <- function() {
2727
# @return an updated panel object
2828
train_layout <- function(panel, facet, data, plot_data) {
2929
layout <- facet_train_layout(facet, c(data, list(plot_data)))
30-
3130
panel$layout <- layout
3231
panel$shrink <- facet$shrink
3332

@@ -141,6 +140,7 @@ scale_apply <- function(data, vars, f, scale_id, scales) {
141140
if (length(vars) == 0) return()
142141

143142
n <- length(scales)
143+
if (any(is.na(scale_id))) stop()
144144
scale_index <- plyr:::split_indices(seq_len(nrow(data)), scale_id, n)
145145

146146
lapply(vars, function(var) {

inst/tests/test-facet-layout.r

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,62 @@ test_that("wrap: as.table reverses rows", {
7878
two <- layout_wrap(list(a), "a", nrow = 1, as.table = FALSE)
7979
expect_that(two$ROW, equals(c(1, 1)))
8080

81+
})
82+
83+
# Drop behaviour -------------------------------------------------------------
84+
85+
a2 <- data.frame(
86+
a = factor(1:3, levels = 1:4),
87+
b = factor(1:3, levels = 4:1)
88+
)
89+
90+
test_that("layout_wrap: drop = FALSE preserves unused levels", {
91+
wrap_a <- layout_wrap(list(a2), "a", drop = FALSE)
92+
expect_equal(nrow(wrap_a), 4)
93+
expect_equal(as.character(wrap_a$a), as.character(1:4))
94+
95+
wrap_b <- layout_wrap(list(a2), "b", drop = FALSE)
96+
expect_equal(nrow(wrap_b), 4)
97+
expect_equal(as.character(wrap_b$b), as.character(4:1))
98+
99+
})
100+
101+
test_that("layout_grid: drop = FALSE preserves unused levels", {
102+
grid_a <- layout_grid(list(a2), "a", drop = FALSE)
103+
expect_equal(nrow(grid_a), 4)
104+
expect_equal(as.character(grid_a$a), as.character(1:4))
105+
106+
grid_b <- layout_grid(list(a2), "b", drop = FALSE)
107+
expect_equal(nrow(grid_b), 4)
108+
expect_equal(as.character(grid_b$b), as.character(4:1))
109+
110+
grid_ab <- layout_grid(list(a2), "a", "b", drop = FALSE)
111+
expect_equal(nrow(grid_ab), 16)
112+
expect_equal(as.character(grid_ab$a), as.character(rep(1:4, each = 4)))
113+
expect_equal(as.character(grid_ab$b), as.character(rep(4:1, 4)))
114+
115+
})
116+
117+
# Missing behaviour ----------------------------------------------------------
118+
119+
a3 <- data.frame(
120+
a = c(1:3, NA),
121+
b = factor(c(1:3, NA)),
122+
c = factor(c(1:3, NA), exclude = NULL)
123+
)
124+
125+
test_that("missing values get a panel", {
126+
wrap_a <- layout_wrap(list(a3), "a")
127+
wrap_b <- layout_wrap(list(a3), "b")
128+
wrap_c <- layout_wrap(list(a3), "c")
129+
grid_a <- layout_grid(list(a3), "a")
130+
grid_b <- layout_grid(list(a3), "b")
131+
grid_c <- layout_grid(list(a3), "c")
132+
133+
expect_equal(nrow(wrap_a), 4)
134+
expect_equal(nrow(wrap_b), 4)
135+
expect_equal(nrow(wrap_c), 4)
136+
expect_equal(nrow(grid_a), 4)
137+
expect_equal(nrow(grid_b), 4)
138+
expect_equal(nrow(grid_c), 4)
81139
})

inst/tests/test-facet-locate.r

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,33 @@ test_that("wrap: missing facet columns are duplicated", {
5858
expect_that(nrow(loc_c), equals(4))
5959
expect_that(loc_c$PANEL, equals(factor(1:4)))
6060

61+
})
62+
63+
# Missing behaviour ----------------------------------------------------------
64+
65+
a3 <- data.frame(
66+
# a = c(1:3, NA), Not currently supported
67+
b = factor(c(1:3, NA)),
68+
c = factor(c(1:3, NA), exclude = NULL)
69+
)
70+
71+
test_that("wrap: missing values located correctly", {
72+
panel_b <- layout_wrap(list(a3), "b", ncol = 1)
73+
loc_b <- locate_wrap(data.frame(b = NA), panel_b, "b")
74+
expect_equal(as.character(loc_b$PANEL), "4")
75+
76+
panel_c <- layout_wrap(list(a3), "c", ncol = 1)
77+
loc_c <- locate_wrap(data.frame(c = NA), panel_c, "c")
78+
expect_equal(as.character(loc_c$PANEL), "4")
79+
80+
})
81+
82+
test_that("grid: missing values located correctly", {
83+
panel_b <- layout_grid(list(a3), "b")
84+
loc_b <- locate_grid(data.frame(b = NA), panel_b, "b")
85+
expect_equal(as.character(loc_b$PANEL), "4")
86+
87+
panel_c <- layout_grid(list(a3), "c")
88+
loc_c <- locate_grid(data.frame(c = NA), panel_c, "c")
89+
expect_equal(as.character(loc_c$PANEL), "4")
6190
})

man/facet_grid.Rd

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
\usage{
55
facet_grid(facets, margins = FALSE, scales = "fixed",
66
space = "fixed", shrink = TRUE,
7-
labeller = "label_value", as.table = TRUE)
7+
labeller = "label_value", as.table = TRUE, drop = TRUE)
88
}
99
\arguments{
1010
\item{facets}{a formula with the rows (of the tabular
@@ -44,6 +44,12 @@
4444
\item{shrink}{If \code{TRUE}, will shrink scales to fit
4545
output of statistics, not raw data. If \code{FALSE}, will
4646
be range of raw data before statistical summary.}
47+
48+
\item{drop}{If \code{TRUE}, the default, all factor
49+
levels not used in the data will automatically be
50+
dropped. If \code{FALSE}, all factor levels will be
51+
shown, regardless of whether or not they appear in the
52+
data.}
4753
}
4854
\description{
4955
Lay out panels in a grid.

man/facet_wrap.Rd

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
\title{Wrap a 1d ribbon of panels into 2d.}
44
\usage{
55
facet_wrap(facets, nrow = NULL, ncol = NULL,
6-
scales = "fixed", shrink = TRUE, as.table = TRUE)
6+
scales = "fixed", shrink = TRUE, as.table = TRUE,
7+
drop = TRUE)
78
}
89
\arguments{
910
\item{nrow}{number of rows}
@@ -24,6 +25,12 @@
2425
are laid out like a table with highest values at the
2526
bottom-right. If \code{FALSE}, the facet are laid out
2627
like a plot with the highest value at the top-right.}
28+
29+
\item{drop}{If \code{TRUE}, the default, all factor
30+
levels not used in the data will automatically be
31+
dropped. If \code{FALSE}, all factor levels will be
32+
shown, regardless of whether or not they appear in the
33+
data.}
2734
}
2835
\description{
2936
Wrap a 1d ribbon of panels into 2d.

0 commit comments

Comments
 (0)