我可以从 "map_data" 访问 "setup_data" 的结果吗? (适用于 "compute_layout" 但不适用于 "map_data")在 ggplot2 ggproto
Can I access results of "setup_data" from "map_data"? (works fine for "compute_layout" but not "map_data") in ggplot2 ggproto
我可以在 ggpplot2 ggproto 中从“map_data”访问“setup_data”的结果吗?
(适用于“compute_layout”但不适用于“map_data”)
大家好。
我正在开发一个 ggplot2 扩展,它将实现一个新的分面方法。
我不想深入了解算法的细节,但足以说明我需要首先为输入的每一行计算一些新列 data
,然后才可以我执行 compute_layout
和 map_data
.
当然,一种选择是计算我的新列两次,一次在 compute_layout
内,另一次在 map_data
内,但这在计算上将是两倍的代价,而且不那么优雅.
似乎 setup_params
和 setup_data
就是针对这个确切用例的。
什么不起作用 ❌
我正在基于此 great vignette.
创建一个可重现的小示例
我刚刚做了一个小修改,尝试使用 setup_data
函数向数据添加一个 hello
列。
library(ggplot2)
facet_bootstrap <- function(n = 9, prop = 0.2, nrow = NULL, ncol = NULL,
scales = "fixed", shrink = TRUE, strip.position = "top") {
facet <- facet_wrap(~.bootstrap, nrow = nrow, ncol = ncol, scales = scales,
shrink = shrink, strip.position = strip.position)
facet$params$n <- n
facet$params$prop <- prop
ggproto(NULL, FacetBootstrap,
shrink = shrink,
params = facet$params
)
}
FacetBootstrap <- ggproto("FacetBootstrap", FacetWrap,
setup_data = function(data, params){
data[[1]]$hello <- 'world'
print("In SETUP_DATA:")
print(" names(data):")
print(names(data[[1]]))
print("")
data
},
compute_layout = function(data, params) {
id <- seq_len(params$n)
print("In COMPUTE_LAYOUT:")
print(" names(data):")
print(names(data[[1]]))
print("")
dims <- wrap_dims(params$n, params$nrow, params$ncol)
layout <- data.frame(PANEL = factor(id))
if (params$as.table) {
layout$ROW <- 1+as.integer((id - 1L) %/% dims[2] + 1L)
} else {
layout$ROW <- 1+as.integer(dims[1] - (id - 1L) %/% dims[2])
}
layout$COL <- 2+as.integer((id - 1L) %% dims[2] + 1L)
layout <- layout[order(layout$PANEL), , drop = FALSE]
rownames(layout) <- NULL
# Add scale identification
layout$SCALE_X <- if (params$free$x) id else 1L
layout$SCALE_Y <- if (params$free$y) id else 1L
cbind(layout, .bootstrap = id)
},
map_data = function(data, layout, params) {
print("In MAP_DATA:")
print(" names(data):")
print(names(data))
print("")
if (is.null(data) || nrow(data) == 0) {
return(cbind(data, PANEL = integer(0)))
}
n_samples <- round(nrow(data) * params$prop)
new_data <- lapply(seq_len(params$n), function(i) {
cbind(data[sample(nrow(data), n_samples), , drop = FALSE], PANEL = i)
})
do.call(rbind, new_data)
}
)
ggplot(diamonds, aes(carat, price)) +
geom_point(alpha = 0.1) +
facet_bootstrap(n = 9, prop = 0.05)
输出:
[1] "In SETUP_DATA:"
[1] " names(data):"
[1] "carat" "cut" "color" "clarity" "depth" "table"
[7] "price" "x" "y" "z" "hello"
[1] ""
[1] "In COMPUTE_LAYOUT:"
[1] " names(data):"
[1] "carat" "cut" "color" "clarity" "depth" "table"
[7] "price" "x" "y" "z" "hello"
[1] ""
[1] "In MAP_DATA:"
[1] " names(data):"
[1] "carat" "cut" "color" "clarity" "depth" "table"
[7] "price" "x" "y" "z"
[1] ""
请注意我的 hello
列 如何在 compute_layout
中可用 而在 map_data
中可用
什么有效 ✅
作为解决方法,我 可以 创建一些列并使用 setup_params
作为 parameters
传递它们。这有点粗暴,因为它们在概念上不是“参数”,而是数据。但如果一切都失败了——我会采用这种方法
library(ggplot2)
facet_bootstrap <- function(n = 9, prop = 0.2, nrow = NULL, ncol = NULL,
scales = "fixed", shrink = TRUE, strip.position = "top") {
facet <- facet_wrap(~.bootstrap, nrow = nrow, ncol = ncol, scales = scales,
shrink = shrink, strip.position = strip.position)
facet$params$n <- n
facet$params$prop <- prop
ggproto(NULL, FacetBootstrap,
shrink = shrink,
params = facet$params
)
}
FacetBootstrap <- ggproto("FacetBootstrap", FacetWrap,
setup_params = function(data, params){
params$hello <- 'world'
print("In SETUP_DATA:")
print(" params$hello:")
print(params$hello)
print("")
params
},
compute_layout = function(data, params) {
id <- seq_len(params$n)
print("In COMPUTE_LAYOUT:")
print(" params$hello:")
print(params$hello)
print("")
dims <- wrap_dims(params$n, params$nrow, params$ncol)
layout <- data.frame(PANEL = factor(id))
if (params$as.table) {
layout$ROW <- 1+as.integer((id - 1L) %/% dims[2] + 1L)
} else {
layout$ROW <- 1+as.integer(dims[1] - (id - 1L) %/% dims[2])
}
layout$COL <- 2+as.integer((id - 1L) %% dims[2] + 1L)
layout <- layout[order(layout$PANEL), , drop = FALSE]
rownames(layout) <- NULL
# Add scale identification
layout$SCALE_X <- if (params$free$x) id else 1L
layout$SCALE_Y <- if (params$free$y) id else 1L
cbind(layout, .bootstrap = id)
},
map_data = function(data, layout, params) {
print("In MAP_DATA:")
print(" params$hello:")
print(params$hello)
print("")
if (is.null(data) || nrow(data) == 0) {
return(cbind(data, PANEL = integer(0)))
}
n_samples <- round(nrow(data) * params$prop)
new_data <- lapply(seq_len(params$n), function(i) {
cbind(data[sample(nrow(data), n_samples), , drop = FALSE], PANEL = i)
})
do.call(rbind, new_data)
}
)
ggplot(diamonds, aes(carat, price)) +
geom_point(alpha = 0.1) +
facet_bootstrap(n = 9, prop = 0.05)
具有以下输出
[1] "In SETUP_DATA:"
[1] " params$hello:"
[1] "world"
[1] ""
[1] "In COMPUTE_LAYOUT:"
[1] " params$hello:"
[1] "world"
[1] ""
[1] "In MAP_DATA:"
[1] " params$hello:"
[1] "world"
[1] ""
结果总结
- 当我修改来自“setup_data”的数据时,我可以在“compute_layout”中访问它 ✅
- 当我修改“setup_data”中的数据时,我无法在“map_data”中访问它 ❌
- 当我修改“setup_params”中的数据时,我可以在“compute_layout”中访问它 ✅
- 当我修改来自“setup_params”的数据时,我可以在“map_data”中访问它 ✅
最终问题
- 以上结果是否符合预期,还是我想错了?
- 什么是计算
data
一次新列的理想方法,然后使它们可用于 map_data
和 compute_layout
Facet
ggproto
?
提前致谢!
TL;DR: 在 setup_data
函数中 data
的每个列表元素中设置一个新列。
It seems that setup_params and setup_data are meant for this exact use case.
没错,但我从你的问题中得到的印象是数据摄取的操作顺序存在一些混淆。构面和坐标是绘图 'layout' 的一部分。在设置布局之前,层会设置它们的数据(有时会制作全局数据的副本)。然后,布局可以检查数据并进行调整(通常附加一个 PANEL 列)。如果我们 inspect/print 去安慰 ggplot2:::Layout$setup
,我们会看到以下内容(我的评论):
<ggproto method>
<Wrapper function>
function (...)
f(..., self = self)
<Inner function (f)>
function (self, data, plot_data = new_data_frame(), plot_env = emptyenv())
{
data <- c(list(plot_data), data)
# First `setup_params` is used
self$facet_params <- self$facet$setup_params(data, self$facet$params)
self$facet_params$plot_env <- plot_env
# Second, `setup_data` is used
data <- self$facet$setup_data(data, self$facet_params)
self$coord_params <- self$coord$setup_params(data)
data <- self$coord$setup_data(data, self$coord_params)
# Third, `compute_layout` is used.
self$layout <- self$facet$compute_layout(data, self$facet_params)
self$layout <- self$coord$setup_layout(self$layout, self$coord_params)
check_layout(self$layout)
# Lastly, `map_data` is used for every data *except* the global data!
lapply(data[-1], self$facet$map_data, layout = self$layout,
params = self$facet_params)
}
所以从这里我们得知运算顺序是setup_params
--> setup_data
--> compute_layout
--> map_data
。请注意,map_data
以 lapply(data[-1], ...)
开头,其中 data
是一个包含 data.frames 的列表,其中全局数据位于位置 1,图层数据位于其后。
您的 setup_data
方法仅适用于 data[[1]]$hello <- 'world'
全局数据,而不适用于图层数据。用 data <- lapply(data, cbind, hello = "world")
替换该行将其应用于全局数据 和 图层数据。此时,每一层都已经拥有自己的(全局副本)数据,因此从效率的角度来看,方面可以做的事情并不多,无法有效地将列附加到图层可以 'inherit' .
更明确地说,这就是我的提议:
FacetBootstrap <- ggproto(
"FacetBootstrap", FacetWrap,
setup_data = function(data, params){
data <- lapply(data, cbind, hello = "world")
print("In SETUP_DATA:")
print(" names(data):")
print(names(data[[1]]))
print("")
data
},
...other code...
)
我可以在 ggpplot2 ggproto 中从“map_data”访问“setup_data”的结果吗?
(适用于“compute_layout”但不适用于“map_data”)
大家好。 我正在开发一个 ggplot2 扩展,它将实现一个新的分面方法。
我不想深入了解算法的细节,但足以说明我需要首先为输入的每一行计算一些新列 data
,然后才可以我执行 compute_layout
和 map_data
.
当然,一种选择是计算我的新列两次,一次在 compute_layout
内,另一次在 map_data
内,但这在计算上将是两倍的代价,而且不那么优雅.
似乎 setup_params
和 setup_data
就是针对这个确切用例的。
什么不起作用 ❌
我正在基于此 great vignette.
创建一个可重现的小示例我刚刚做了一个小修改,尝试使用 setup_data
函数向数据添加一个 hello
列。
library(ggplot2)
facet_bootstrap <- function(n = 9, prop = 0.2, nrow = NULL, ncol = NULL,
scales = "fixed", shrink = TRUE, strip.position = "top") {
facet <- facet_wrap(~.bootstrap, nrow = nrow, ncol = ncol, scales = scales,
shrink = shrink, strip.position = strip.position)
facet$params$n <- n
facet$params$prop <- prop
ggproto(NULL, FacetBootstrap,
shrink = shrink,
params = facet$params
)
}
FacetBootstrap <- ggproto("FacetBootstrap", FacetWrap,
setup_data = function(data, params){
data[[1]]$hello <- 'world'
print("In SETUP_DATA:")
print(" names(data):")
print(names(data[[1]]))
print("")
data
},
compute_layout = function(data, params) {
id <- seq_len(params$n)
print("In COMPUTE_LAYOUT:")
print(" names(data):")
print(names(data[[1]]))
print("")
dims <- wrap_dims(params$n, params$nrow, params$ncol)
layout <- data.frame(PANEL = factor(id))
if (params$as.table) {
layout$ROW <- 1+as.integer((id - 1L) %/% dims[2] + 1L)
} else {
layout$ROW <- 1+as.integer(dims[1] - (id - 1L) %/% dims[2])
}
layout$COL <- 2+as.integer((id - 1L) %% dims[2] + 1L)
layout <- layout[order(layout$PANEL), , drop = FALSE]
rownames(layout) <- NULL
# Add scale identification
layout$SCALE_X <- if (params$free$x) id else 1L
layout$SCALE_Y <- if (params$free$y) id else 1L
cbind(layout, .bootstrap = id)
},
map_data = function(data, layout, params) {
print("In MAP_DATA:")
print(" names(data):")
print(names(data))
print("")
if (is.null(data) || nrow(data) == 0) {
return(cbind(data, PANEL = integer(0)))
}
n_samples <- round(nrow(data) * params$prop)
new_data <- lapply(seq_len(params$n), function(i) {
cbind(data[sample(nrow(data), n_samples), , drop = FALSE], PANEL = i)
})
do.call(rbind, new_data)
}
)
ggplot(diamonds, aes(carat, price)) +
geom_point(alpha = 0.1) +
facet_bootstrap(n = 9, prop = 0.05)
输出:
[1] "In SETUP_DATA:"
[1] " names(data):"
[1] "carat" "cut" "color" "clarity" "depth" "table"
[7] "price" "x" "y" "z" "hello"
[1] ""
[1] "In COMPUTE_LAYOUT:"
[1] " names(data):"
[1] "carat" "cut" "color" "clarity" "depth" "table"
[7] "price" "x" "y" "z" "hello"
[1] ""
[1] "In MAP_DATA:"
[1] " names(data):"
[1] "carat" "cut" "color" "clarity" "depth" "table"
[7] "price" "x" "y" "z"
[1] ""
请注意我的 hello
列 如何在 compute_layout
中可用 而在 map_data
什么有效 ✅
作为解决方法,我 可以 创建一些列并使用 setup_params
作为 parameters
传递它们。这有点粗暴,因为它们在概念上不是“参数”,而是数据。但如果一切都失败了——我会采用这种方法
library(ggplot2)
facet_bootstrap <- function(n = 9, prop = 0.2, nrow = NULL, ncol = NULL,
scales = "fixed", shrink = TRUE, strip.position = "top") {
facet <- facet_wrap(~.bootstrap, nrow = nrow, ncol = ncol, scales = scales,
shrink = shrink, strip.position = strip.position)
facet$params$n <- n
facet$params$prop <- prop
ggproto(NULL, FacetBootstrap,
shrink = shrink,
params = facet$params
)
}
FacetBootstrap <- ggproto("FacetBootstrap", FacetWrap,
setup_params = function(data, params){
params$hello <- 'world'
print("In SETUP_DATA:")
print(" params$hello:")
print(params$hello)
print("")
params
},
compute_layout = function(data, params) {
id <- seq_len(params$n)
print("In COMPUTE_LAYOUT:")
print(" params$hello:")
print(params$hello)
print("")
dims <- wrap_dims(params$n, params$nrow, params$ncol)
layout <- data.frame(PANEL = factor(id))
if (params$as.table) {
layout$ROW <- 1+as.integer((id - 1L) %/% dims[2] + 1L)
} else {
layout$ROW <- 1+as.integer(dims[1] - (id - 1L) %/% dims[2])
}
layout$COL <- 2+as.integer((id - 1L) %% dims[2] + 1L)
layout <- layout[order(layout$PANEL), , drop = FALSE]
rownames(layout) <- NULL
# Add scale identification
layout$SCALE_X <- if (params$free$x) id else 1L
layout$SCALE_Y <- if (params$free$y) id else 1L
cbind(layout, .bootstrap = id)
},
map_data = function(data, layout, params) {
print("In MAP_DATA:")
print(" params$hello:")
print(params$hello)
print("")
if (is.null(data) || nrow(data) == 0) {
return(cbind(data, PANEL = integer(0)))
}
n_samples <- round(nrow(data) * params$prop)
new_data <- lapply(seq_len(params$n), function(i) {
cbind(data[sample(nrow(data), n_samples), , drop = FALSE], PANEL = i)
})
do.call(rbind, new_data)
}
)
ggplot(diamonds, aes(carat, price)) +
geom_point(alpha = 0.1) +
facet_bootstrap(n = 9, prop = 0.05)
具有以下输出
[1] "In SETUP_DATA:"
[1] " params$hello:"
[1] "world"
[1] ""
[1] "In COMPUTE_LAYOUT:"
[1] " params$hello:"
[1] "world"
[1] ""
[1] "In MAP_DATA:"
[1] " params$hello:"
[1] "world"
[1] ""
结果总结
- 当我修改来自“setup_data”的数据时,我可以在“compute_layout”中访问它 ✅
- 当我修改“setup_data”中的数据时,我无法在“map_data”中访问它 ❌
- 当我修改“setup_params”中的数据时,我可以在“compute_layout”中访问它 ✅
- 当我修改来自“setup_params”的数据时,我可以在“map_data”中访问它 ✅
最终问题
- 以上结果是否符合预期,还是我想错了?
- 什么是计算
data
一次新列的理想方法,然后使它们可用于map_data
和compute_layout
Facet
ggproto
?
提前致谢!
TL;DR: 在 setup_data
函数中 data
的每个列表元素中设置一个新列。
It seems that setup_params and setup_data are meant for this exact use case.
没错,但我从你的问题中得到的印象是数据摄取的操作顺序存在一些混淆。构面和坐标是绘图 'layout' 的一部分。在设置布局之前,层会设置它们的数据(有时会制作全局数据的副本)。然后,布局可以检查数据并进行调整(通常附加一个 PANEL 列)。如果我们 inspect/print 去安慰 ggplot2:::Layout$setup
,我们会看到以下内容(我的评论):
<ggproto method>
<Wrapper function>
function (...)
f(..., self = self)
<Inner function (f)>
function (self, data, plot_data = new_data_frame(), plot_env = emptyenv())
{
data <- c(list(plot_data), data)
# First `setup_params` is used
self$facet_params <- self$facet$setup_params(data, self$facet$params)
self$facet_params$plot_env <- plot_env
# Second, `setup_data` is used
data <- self$facet$setup_data(data, self$facet_params)
self$coord_params <- self$coord$setup_params(data)
data <- self$coord$setup_data(data, self$coord_params)
# Third, `compute_layout` is used.
self$layout <- self$facet$compute_layout(data, self$facet_params)
self$layout <- self$coord$setup_layout(self$layout, self$coord_params)
check_layout(self$layout)
# Lastly, `map_data` is used for every data *except* the global data!
lapply(data[-1], self$facet$map_data, layout = self$layout,
params = self$facet_params)
}
所以从这里我们得知运算顺序是setup_params
--> setup_data
--> compute_layout
--> map_data
。请注意,map_data
以 lapply(data[-1], ...)
开头,其中 data
是一个包含 data.frames 的列表,其中全局数据位于位置 1,图层数据位于其后。
您的 setup_data
方法仅适用于 data[[1]]$hello <- 'world'
全局数据,而不适用于图层数据。用 data <- lapply(data, cbind, hello = "world")
替换该行将其应用于全局数据 和 图层数据。此时,每一层都已经拥有自己的(全局副本)数据,因此从效率的角度来看,方面可以做的事情并不多,无法有效地将列附加到图层可以 'inherit' .
更明确地说,这就是我的提议:
FacetBootstrap <- ggproto(
"FacetBootstrap", FacetWrap,
setup_data = function(data, params){
data <- lapply(data, cbind, hello = "world")
print("In SETUP_DATA:")
print(" names(data):")
print(names(data[[1]]))
print("")
data
},
...other code...
)