【问题标题】:Filling area under curve based on value基于值的曲线下填充区域
【发布时间】:2013-07-31 00:48:25
【问题描述】:

我们正在尝试使用 ggplot2 绘制面积图,其中 x 轴上方的正区域是一种颜色,而负区域是另一种颜色。

鉴于这个数据集,我想要一个区域图,在轴的每一侧用不同的颜色着色。

我可以看到一种将数据集划分为两个子集的方法,一个是所有负值都为零的正值,一个是所有正值为零的负值,然后将它们分别绘制在同一轴上,但似乎在那里将是一种更像ggplot的方式来做到这一点。

this question 上发布的解决方案没有给出准确的结果(见下文)。

准确显示为条形图的示例数据

由此代码生成:

# create some fake data with zero-crossings
yvals=c(2,2,-1,2,2,2,0,-1,-2,2,-2)
test = data.frame(x=seq(1,length(yvals)),y=yvals)

# generate the bar plot
ggplot(data=test,aes(x=x,y=y)) 
    + geom_bar(data=test[test$y>0,],aes(y=y), fill="blue",stat="identity", width=.5) 
    + geom_bar(data=test[test$y<0,],aes(y=y), fill="red",stat="identity", width=.5)

RLE 方法不通用

the other question 上提出的 RLE 方法在应用于我们的数据集时会产生与过零相关的伪影:

由以下代码生成(请勿使用):

# set up grouping function
rle.grp <- function(x) {
   xx <- rle(x)
   xx$values = seq_along(xx$values)
   inverse.rle(xx) }

# generate ribbon plot
ggplot(test, aes(x=x,y=y,group = factor(rle.grp(sign(y))))) + 
    geom_ribbon(aes(ymax = pmax(0,y),ymin = pmin(0,y),
   fill = factor(sign(y), levels = c(-1,0,1), labels = c('-','0','+')))) 
   + scale_fill_brewer(name = 'sign', palette = 'RdBu')

按照@baptiste 和 Kohske 的建议查看下面的最终答案。

【问题讨论】:

标签: r plot ggplot2


【解决方案1】:

根据@baptiste 的评论(已删除),我会说这是最好的答案。它基于 Kohske 的this post。它在零交叉处向数据集添加新的 x-y 对,并生成如下图:

# create some fake data with zero-crossings
yvals = c(2,2,-1,2,2,2,0,-1,-2,2,-2)
d = data.frame(x=seq(1,length(yvals)),y=yvals)

rx <- do.call("rbind",
   sapply(1:(nrow(d)-1), function(i){
   f <- lm(x~y, d[i:(i+1),])
   if (f$qr$rank < 2) return(NULL)
   r <- predict(f, newdata=data.frame(y=0))
   if(d[i,]$x < r & r < d[i+1,]$x)
      return(data.frame(x=r,y=0))
    else return(NULL)
 }))
 d2 <- rbind(d,rx)
 ggplot(d2,aes(x,y)) + geom_area(data=subset(d2, y<=0), fill="pink") 
     + geom_area(data=subset(d2, y>=0), fill="lightblue") + geom_point()

生成以下输出:

【讨论】:

    【解决方案2】:

    我使用以下易于理解的逻辑做了一个非常相似的情节。我为正值和负值创建了以下两个对象。请注意,其中有一个“非常小的数字”,以避免从一个点跳转到另一个点而不通过零。

    pos <- mutate(df, y = ifelse(ROI >= 0, y, 0.0001))
    neg <- mutate(df, y = ifelse(ROI < 0, y, -0.0001))
    

    然后,只需将 geom_areas 添加到您的 ggplot 对象中:

    ggplot(..., aes(y = y)) + 
      geom_area(data = pos, fill = "#3DA4AB") +
      geom_area(data = neg, fill = "tomato")
    

    希望它对你有用! ;)

    【讨论】:

      【解决方案3】:

      我想对此添加一个更新,首先是使用dplyr 提供一种更简单的方法,其次是让@beroe 的答案更具可读性。

      新答案

      您可以用代数方式求解 x。该方程来自重新排列直线方程 (y = mx + b) 以求解给定其他两个点和 y = 0 的 x。

      library(dplyr)
      library(magrittr)
      library(ggplot2)
      
      df <- data.frame(x = 1:10, y = runif(10, -1, 1))
      
      df_inbetween <- df %>% 
        mutate(
          # Solve for x given two points and y = 0
          xzero = -((y * (lead(x) - x)) / (lead(y) - y)) + x,
          xzero_valid = xzero > x & xzero < lead(x),
          xzero = replace(xzero, !xzero_valid, NA),
          yzero = 0,
          yzero = replace(yzero, !xzero_valid, NA)
        ) %>% 
        select(x = xzero, y = yzero) %>% 
        filter(!is.na(x))
      
      df <- rbind(df, df_inbetween)
      
      ggplot(data = df, aes(x = x, y = y)) + 
        geom_area(data = filter(df, y >= 0), fill = 'pink') +
        geom_area(data = filter(df, y <= 0), fill = 'light blue') +
        geom_point()
      

      重写beroe的答案

      这不太简洁,但原始答案很难阅读。另外,最好使用lapply,因为sapply 并没有简化这里的列表。

      library(ggplot2)
      d <- data.frame(x = 1:10, y = runif(10, -1, 1))
      
      find_root <- function(i){
        f <- lm(x~y, d[c(i, i+1),])
        
        # If the model is invalid, NULL
        if (f$qr$rank < 2) return(NULL)
        
        r <- predict(f, newdata=data.frame(y=0))
        
        # Check if that point falls between the two other x-values
        if(d[i,]$x < r & r < d[i+1,]$x)
          return(data.frame(x=r,y=0))
        
        else return(NULL)
      }
      
      # Make dataset containing root points
      rx <- do.call('rbind', 
        lapply(1:(nrow(d) - 1), find_root)
      )
      
      # Append and plot
      d2 <- rbind(d,rx)
      
      ggplot(d2,aes(x, y)) + 
        geom_area(data=subset(d2, y<=0), fill="pink") + 
        geom_area(data=subset(d2, y>=0), fill="lightblue") + 
        geom_point()
      

      注意:对于这两种解决方案,如果您的数据集除了 x 和 y 之外还有其他变量,则最终的 rbind 调用将失败。在dplyr解决方案中,您可以根据需要更改select调用。

      【讨论】:

      • 谢谢查理。我感谢这些努力,并将研究这些解决方案。
      猜你喜欢
      • 2011-12-15
      • 2020-07-02
      • 2018-11-01
      • 2021-12-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多