You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义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()

错误原因分析

  1. mergeGridAndData函数无返回值:函数最后没有返回合并后的merged_data,导致setup_data拿到的是NULL,后续GeomTile需要的x、y、width、height等字段完全缺失,调用grid::rectGrob()时参数为空,触发报错。
  2. setup_data中变量未定义:setup_data里的facet_col <- facet_col属于无效循环引用,该变量未被定义,会导致后续合并逻辑出错。
  3. 缺少GeomTile必需的aes字段:GeomTile要求必须有x、y、width、height四个aes字段,当前合并后的merged_data只有x和y,缺少width和height。
  4. 数据匹配失败:示例数据中country值为"USA",但grid_data的code是"US",合并逻辑判断字符长度≤3时用code匹配,导致无匹配结果,merged_data为空。

解决方法

  1. 修复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)  # 添加返回语句
}
  1. 修正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
}
  1. 修正数据匹配问题:调整示例数据或grid_data的匹配值,比如将示例数据的country改为"US"、"CA"等,确保能和grid_data的code匹配:
data <- data.frame(
  country = c("US", "CA", "DE", "JP"),
  usage = c(50, 30, 45, 60)
)
  1. 避免全局变量依赖:将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 02:37:04