Skip to content

Commit 828e948

Browse files
authored
Implement modify_list and substitute modifyList calls with it (tidyverse#3005)
1 parent a057a26 commit 828e948

File tree

9 files changed

+97
-50
lines changed

9 files changed

+97
-50
lines changed

DESCRIPTION

+2-1
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ Collate:
164164
'layout.R'
165165
'limits.r'
166166
'margins.R'
167+
'performance.R'
167168
'plot-build.r'
168169
'plot-construction.r'
169170
'plot-last.r'
@@ -243,6 +244,6 @@ Collate:
243244
'zxx.r'
244245
'zzz.r'
245246
VignetteBuilder: knitr
246-
RoxygenNote: 6.1.0
247+
RoxygenNote: 6.1.1
247248
Roxygen: list(markdown = TRUE)
248249
Encoding: UTF-8

R/aaa-.r

-40
Original file line numberDiff line numberDiff line change
@@ -12,43 +12,3 @@ NULL
1212
#' @keywords internal
1313
#' @name ggplot2-ggproto
1414
NULL
15-
16-
# Fast data.frame constructor and indexing
17-
# No checking, recycling etc. unless asked for
18-
new_data_frame <- function(x = list(), n = NULL) {
19-
if (length(x) != 0 && is.null(names(x))) stop("Elements must be named", call. = FALSE)
20-
lengths <- vapply(x, length, integer(1))
21-
if (is.null(n)) {
22-
n <- if (length(x) == 0) 0 else max(lengths)
23-
}
24-
for (i in seq_along(x)) {
25-
if (lengths[i] == n) next
26-
if (lengths[i] != 1) stop("Elements must equal the number of rows or 1", call. = FALSE)
27-
x[[i]] <- rep(x[[i]], n)
28-
}
29-
30-
class(x) <- "data.frame"
31-
32-
attr(x, "row.names") <- .set_row_names(n)
33-
x
34-
}
35-
36-
data_frame <- function(...) {
37-
new_data_frame(list(...))
38-
}
39-
40-
data.frame <- function(...) {
41-
stop('Please use `data_frame()` or `new_data_frame()` instead of `data.frame()` for better performance. See the vignette "ggplot2 internal programming guidelines" for details.', call. = FALSE)
42-
}
43-
44-
mat_2_df <- function(x, col_names = colnames(x), .check = FALSE) {
45-
x <- lapply(seq_len(ncol(x)), function(i) x[, i])
46-
if (!is.null(col_names)) names(x) <- col_names
47-
new_data_frame(x)
48-
}
49-
50-
df_col <- function(x, name) .subset2(x, name)
51-
52-
df_rows <- function(x, i) {
53-
new_data_frame(lapply(x, `[`, i = i))
54-
}

R/guide-legend.r

+1-1
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ guide_geom.legend <- function(guide, layers, default_mapping) {
280280
}
281281

282282
# override.aes in guide_legend manually changes the geom
283-
data <- utils::modifyList(data, guide$override.aes)
283+
data <- modify_list(data, guide$override.aes)
284284

285285
list(
286286
draw_key = layer$geom$draw_key,

R/performance.R

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Fast data.frame constructor and indexing
2+
# No checking, recycling etc. unless asked for
3+
new_data_frame <- function(x = list(), n = NULL) {
4+
if (length(x) != 0 && is.null(names(x))) stop("Elements must be named", call. = FALSE)
5+
lengths <- vapply(x, length, integer(1))
6+
if (is.null(n)) {
7+
n <- if (length(x) == 0) 0 else max(lengths)
8+
}
9+
for (i in seq_along(x)) {
10+
if (lengths[i] == n) next
11+
if (lengths[i] != 1) stop("Elements must equal the number of rows or 1", call. = FALSE)
12+
x[[i]] <- rep(x[[i]], n)
13+
}
14+
15+
class(x) <- "data.frame"
16+
17+
attr(x, "row.names") <- .set_row_names(n)
18+
x
19+
}
20+
21+
data_frame <- function(...) {
22+
new_data_frame(list(...))
23+
}
24+
25+
data.frame <- function(...) {
26+
stop('Please use `data_frame()` or `new_data_frame()` instead of `data.frame()` for better performance. See the vignette "ggplot2 internal programming guidelines" for details.', call. = FALSE)
27+
}
28+
29+
mat_2_df <- function(x, col_names = colnames(x), .check = FALSE) {
30+
x <- lapply(seq_len(ncol(x)), function(i) x[, i])
31+
if (!is.null(col_names)) names(x) <- col_names
32+
new_data_frame(x)
33+
}
34+
35+
df_col <- function(x, name) .subset2(x, name)
36+
37+
df_rows <- function(x, i) {
38+
new_data_frame(lapply(x, `[`, i = i))
39+
}
40+
41+
# More performant modifyList without recursion
42+
modify_list <- function(old, new) {
43+
for (i in names(new)) old[[i]] <- new[[i]]
44+
old
45+
}
46+
modifyList <- function(...) {
47+
stop('Please use `modify_list()` instead of `modifyList()` for better performance. See the vignette "ggplot2 internal programming guidelines" for details.', call. = FALSE)
48+
}

R/sf.R

+4-4
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ GeomSf <- ggproto("GeomSf", Geom,
204204
},
205205

206206
draw_key = function(data, params, size) {
207-
data <- utils::modifyList(default_aesthetics(params$legend), data)
207+
data <- modify_list(default_aesthetics(params$legend), data)
208208
if (params$legend == "point") {
209209
draw_key_point(data, params, size)
210210
} else if (params$legend == "line") {
@@ -221,7 +221,7 @@ default_aesthetics <- function(type) {
221221
} else if (type == "line") {
222222
GeomLine$default_aes
223223
} else {
224-
utils::modifyList(GeomPolygon$default_aes, list(fill = "grey90", colour = "grey35"))
224+
modify_list(GeomPolygon$default_aes, list(fill = "grey90", colour = "grey35"))
225225
}
226226
}
227227

@@ -230,7 +230,7 @@ sf_grob <- function(row, lineend = "butt", linejoin = "round", linemitre = 10) {
230230
geometry <- row$geometry[[1]]
231231

232232
if (inherits(geometry, c("POINT", "MULTIPOINT"))) {
233-
row <- utils::modifyList(default_aesthetics("point"), row)
233+
row <- modify_list(default_aesthetics("point"), row)
234234
gp <- gpar(
235235
col = alpha(row$colour, row$alpha),
236236
fill = alpha(row$fill, row$alpha),
@@ -240,7 +240,7 @@ sf_grob <- function(row, lineend = "butt", linejoin = "round", linemitre = 10) {
240240
)
241241
sf::st_as_grob(geometry, gp = gp, pch = row$shape)
242242
} else {
243-
row <- utils::modifyList(default_aesthetics("poly"), row)
243+
row <- modify_list(default_aesthetics("poly"), row)
244244
gp <- gpar(
245245
col = row$colour,
246246
fill = alpha(row$fill, row$alpha),

R/theme-elements.r

+3-3
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ element_grob.element_rect <- function(element, x = 0.5, y = 0.5,
186186
element_gp <- gpar(lwd = len0_null(element$size * .pt), col = element$colour,
187187
fill = element$fill, lty = element$linetype)
188188

189-
rectGrob(x, y, width, height, gp = utils::modifyList(element_gp, gp), ...)
189+
rectGrob(x, y, width, height, gp = modify_list(element_gp, gp), ...)
190190
}
191191

192192

@@ -214,7 +214,7 @@ element_grob.element_text <- function(element, label = "", x = NULL, y = NULL,
214214
lineheight = element$lineheight)
215215

216216
titleGrob(label, x, y, hjust = hj, vjust = vj, angle = angle,
217-
gp = utils::modifyList(element_gp, gp), margin = margin,
217+
gp = modify_list(element_gp, gp), margin = margin,
218218
margin_x = margin_x, margin_y = margin_y, debug = element$debug)
219219
}
220220

@@ -242,7 +242,7 @@ element_grob.element_line <- function(element, x = 0:1, y = 0:1,
242242
}
243243
polylineGrob(
244244
x, y, default.units = default.units,
245-
gp = utils::modifyList(element_gp, gp),
245+
gp = modify_list(element_gp, gp),
246246
id.lengths = id.lengths, arrow = arrow, ...
247247
)
248248
}

R/utilities.r

+1-1
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ find_args <- function(...) {
388388
vals <- mget(args, envir = env)
389389
vals <- vals[!vapply(vals, is_missing_arg, logical(1))]
390390

391-
utils::modifyList(vals, list(..., `...` = NULL))
391+
modify_list(vals, list(..., `...` = NULL))
392392
}
393393

394394
# Used in annotations to ensure printed even when no

tests/testthat/test-performance.R

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
context("Performance related alternatives")
2+
3+
testlist <- list(
4+
a = 5.5,
5+
b = "x",
6+
c = 1:10
7+
)
8+
testappend <- list(
9+
b = "y",
10+
c = NULL,
11+
d = FALSE
12+
)
13+
14+
test_that("modifyList is masked", {
15+
expect_error(modifyList(testlist, testappend))
16+
})
17+
18+
test_that("modify_list retains unreferenced elements", {
19+
res <- modify_list(testlist, testappend)
20+
expect_equal(testlist$a, res$a)
21+
})
22+
test_that("modify_list overwrites existing values", {
23+
res <- modify_list(testlist, testappend)
24+
expect_equal(res$b, testappend$b)
25+
})
26+
test_that("modify_list adds new values", {
27+
res <- modify_list(testlist, testappend)
28+
expect_equal(res$d, testappend$d)
29+
})
30+
test_that("modify_list erases null elements", {
31+
res <- modify_list(testlist, testappend)
32+
expect_null(res$c)
33+
expect_named(res, c('a', 'b', 'd'))
34+
})

vignettes/profiling.Rmd

+4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ are summarised below:
6767
and will just lead to slower code. The `data.frame()` call is now only used
6868
when dealing with output from other packages where the extra safety is a
6969
benefit.
70+
- **Use a performant alternative to `utils::modifyList`** `modifyList()` is a
71+
nice convenience function but carries a lot of overhead. It was mainly used
72+
in the plot element constructions where it slowed down the application of
73+
theme settings. A more performant version has been added and used throughout.
7074
- **Speed up position transformation** The `transform_position` helper was
7175
unreasonably slow due to the slowness of getting and assigning columns in
7276
data.frame. The input is now treated as a list during transformation.

0 commit comments

Comments
 (0)