自定义ggplot2 geom_geoheat报错:unit()参数长度需大于0
自定义ggplot2几何对象geom_geoheat报错:grid::unit()中'x' and 'units' must have length >0
我正在开发基于GeomTile的自定义ggplot2几何对象geom_geoheat,用于绘制地图热力图,但在将geom转换为grob时遇到错误,提示grid::unit()中'x' and 'units' must have length > 0。
实现代码
library(ggplot2) geom_geoheat <- function( mapping = NULL, data = NULL, border_col = "white", border_size = 2, lbl_size = 3, dark_lbl = "black", light_lbl = "white", radius = grid::unit(6, "pt"), ..., na.rm = FALSE, show.legend = NA, inherit.aes = TRUE) { ggplot2::layer( data = data, mapping = mapping, stat = "identity", geom = GeomGeoHeat, position = "identity", show.legend = show.legend, inherit.aes = inherit.aes, params = list( border_col = border_col, border_size = border_size, lbl_size = lbl_size, dark_lbl = dark_lbl, light_lbl = light_lbl, radius = radius, na.rm = na.rm, ... ) ) } GeomGeoHeat <- ggproto("GeomGeoHeat", GeomTile, required_aes = c("fill", "facet_col"), default_aes = aes( fill = "grey20", colour = NA, size = 0.1, linetype = 1, facet_col = "country", label = "code", angle = 0, hjust = 0.5, vjust = 0.5, alpha = NA, family = "", fontface = 1, lineheight = 1.2 ), extra_params = c("na.rm"), setup_data = function(data, params) { facet_data <- data.frame(data, stringsAsFactors=FALSE) facet_col <- facet_col # 此处存在未定义变量问题 merged_data <- mergeGridAndData(facet_data, grid_data, facet_col) merged_data$y <- -merged_data$y print(summary(merged_data)) merged_data }, draw_panel = function(self, data, panel_params, coord, border_col = "white", border_size = 2, lbl_size = 3, dark_lbl = "black", light_lbl = "white", radius = grid::unit(6, "pt")) { print(head(data)) coord <- coord_equal() grid::gList( GeomTile$draw_panel(data, panel_params, coord) ) -> grobs ggname("geom_geoheat", grid::grobTree(children = grobs)) }, draw_key = ggplot2::draw_key_polygon ) # Merge user data with grid data mergeGridAndData <- function(facet_data, grid_data, facet_col) { print(summary(facet_data)) if (max(nchar(facet_data[,"facet_col"])) <= 3) { merge.grid <- "code" } else { merge.grid <- "name" } # Rename rows and columns to x and y for panel coordinates. Flip row coordinates to start at bottom left. colnames(grid_data) <- c("name", "code", "y", "x") merged_data <- merge(grid_data, facet_data, by.x = merge.grid, by.y = "facet_col", all = FALSE, sort=TRUE) }
错误信息
Error in `geom_geoheat()`: ! Problem while converting geom to grob. ℹ Error occurred in the 1st layer. Caused by error in `unit()`: ! 'x' and 'units' must have length > 0 —> more specifically, the error occurs in grid::rectGrob(...) └─grid::unit(x, default.units) └─base::stop("'x' and 'units' must have length > 0")
可复现示例代码
# Example dataset data <- data.frame( country = c("USA", "Canada", "Germany", "Japan"), usage = c(50, 30, 45, 60) ) # Dummy grid_data grid_data <- data.frame( name = c("United States", "Canada", "Germany", "Japan"), code = c("US", "CA", "DE", "JP"), row = c(1, 2, 3, 4), col = c(1, 2, 3, 4) ) facet_data <- data facet_col <- "country" # Example usage ggplot(data = data, mapping = aes(fill = usage, facet_col = country)) + geom_geoheat()
错误原因分析
- mergeGridAndData函数无返回值:函数最后没有返回合并后的
merged_data,导致setup_data拿到的是NULL,后续GeomTile需要的x、y、width、height等字段完全缺失,调用grid::rectGrob()时参数为空,触发报错。 - setup_data中变量未定义:
setup_data里的facet_col <- facet_col属于无效循环引用,该变量未被定义,会导致后续合并逻辑出错。 - 缺少GeomTile必需的aes字段:
GeomTile要求必须有x、y、width、height四个aes字段,当前合并后的merged_data只有x和y,缺少width和height。 - 数据匹配失败:示例数据中
country值为"USA",但grid_data的code是"US",合并逻辑判断字符长度≤3时用code匹配,导致无匹配结果,merged_data为空。
解决方法
- 修复mergeGridAndData函数的返回值:在函数末尾添加
return(merged_data),确保返回合并后的数据:
mergeGridAndData <- function(facet_data, grid_data, facet_col) { print(summary(facet_data)) if (max(nchar(facet_data[,"facet_col"])) <= 3) { merge.grid <- "code" } else { merge.grid <- "name" } colnames(grid_data) <- c("name", "code", "y", "x") merged_data <- merge(grid_data, facet_data, by.x = merge.grid, by.y = "facet_col", all = FALSE, sort=TRUE) return(merged_data) # 添加返回语句 }
- 修正setup_data中的变量问题:去掉无效的
facet_col <- facet_col语句,直接使用数据中的facet_col列,同时添加GeomTile必需的width和height字段:
setup_data = function(data, params) { facet_data <- data.frame(data, stringsAsFactors=FALSE) merged_data <- mergeGridAndData(facet_data, grid_data, "facet_col") # 明确传入列名 merged_data$y <- -merged_data$y # 添加GeomTile必需的width和height字段 merged_data$width <- 1 merged_data$height <- 1 print(summary(merged_data)) merged_data }
- 修正数据匹配问题:调整示例数据或
grid_data的匹配值,比如将示例数据的country改为"US"、"CA"等,确保能和grid_data的code匹配:
data <- data.frame( country = c("US", "CA", "DE", "JP"), usage = c(50, 30, 45, 60) )
- 避免全局变量依赖:将
grid_data作为geom_geoheat的参数传入,不要依赖全局变量,提升代码健壮性:
geom_geoheat <- function( mapping = NULL, data = NULL, grid_data = NULL, # 添加grid_data参数 border_col = "white", border_size = 2, lbl_size = 3, dark_lbl = "black", light_lbl = "white", radius = grid::unit(6, "pt"), ..., na.rm = FALSE, show.legend = NA, inherit.aes = TRUE) { ggplot2::layer( data = data, mapping = mapping, stat = "identity", geom = GeomGeoHeat, position = "identity", show.legend = show.legend, inherit.aes = inherit.aes, params = list( grid_data = grid_data, # 传入参数 border_col = border_col, border_size = border_size, lbl_size = lbl_size, dark_lbl = dark_lbl, light_lbl = light_lbl, radius = radius, na.rm = na.rm, ... ) ) } # 同时修改setup_data,从params中获取grid_data setup_data = function(data, params) { facet_data <- data.frame(data, stringsAsFactors=FALSE) merged_data <- mergeGridAndData(facet_data, params$grid_data, "facet_col") merged_data$y <- -merged_data$y merged_data$width <- 1 merged_data$height <- 1 print(summary(merged_data)) merged_data } # 调用时传入grid_data ggplot(data = data, mapping = aes(fill = usage, facet_col = country)) + geom_geoheat(grid_data = grid_data)
内容的提问来源于stack exchange,提问作者nostrum
相关产品推荐
相关产品推荐

