R: Нарисовать многоугольник с условным цветом

Я хочу раскрасить область под кривой. Область с y > 0 должна быть красной, область с y ‹ 0 должна быть зеленой.

x <- c(1:4)
y <- c(0,1,-1,2,rep(0,4))
plot(y[1:4],type="l")
abline(h=0)

Использование ifelse() не работает:

polygon(c(x,rev(x)),y,col=ifelse(y>0,"red","green"))

На данный момент я достиг следующего:

polygon(c(x,rev(x)),y,col="green")
polygon(c(x,rev(x)),ifelse(y>0,y,0),col="red")

Но тогда красная область слишком велика. У вас есть идеи, как получить желаемый результат?


person mrub    schedule 02.07.2014    source источник


Ответы (2)


arrow_upward
5
arrow_downward

Если вам нужны два разных цвета, вам нужны два разных полигона. Вы можете либо вызвать полигон несколько раз, либо добавить значения NA в вектора x и y, чтобы указать новый полигон. R не будет автоматически вычислять пересечение для вас. Вы должны сделать это сами. Вот как вы можете нарисовать это разными цветами.

x <- c(1,2,2.5,NA,2.5,3,4)
y <- c(0,1,0,NA,0,-1,0)

#calculate color based on most extreme y value
g <- cumsum(is.na(x))
gc <- ifelse(tapply(y, g,
    function(x) x[which.max(abs(x))])>0,
    "red","green")

plot(c(1, 4),c(-1,1), type = "n")
polygon(x, y, col = gc)
abline(h=0)

введите здесь описание изображения

В более общем случае разделить многоугольник на разные области может быть не так просто. Кажется, есть некоторая поддержка этого типа операций в пакетах ГИС, где этот тип вещей более распространен. Тем не менее, я собрал несколько общий случай, который может работать для простых многоугольников.

Во-первых, я определяю замыкание, которое будет определять линию разреза. Функция возьмет наклон и точку пересечения по оси Y для линии и вернет функции, необходимые для разрезания многоугольника.

getSplitLine <- function(m=1, b=0) {
    force(m); force(b)
    classify <- function(x,y) {
        y >= m*x + b
    }
    intercepts <- function(x,y, class=classify(x,y)) {
        w <- which(diff(class)!=0)
        m2 <- (y[w+1]-y[w])/(x[w+1]-x[w])
        b2 <- y[w] - m2*x[w]

        ix <- (b2-b)/(m-m2)
        iy <- ix*m + b
        data.frame(x=ix,y=iy,idx=w+.5, dir=((rank(ix, ties="first")+1) %/% 2) %% 2 +1)
    }
    plot <- function(...) {
    abline(b,m,...)
    }
    list(
        intercepts=intercepts,
        classify=classify,
        plot=plot
    )
}

Теперь мы определим функцию для фактического разделения многоугольника с помощью только что определенного разделителя.

splitPolygon <- function(x, y, splitter) {
    addnullrow <- function(x) if (!all(is.na(x[nrow(x),]))) rbind(x, NA) else x
    rollup <- function(x,i=1) rbind(x[(i+1):nrow(x),], x[1:i,])
    idx <- cumsum(is.na(x) | is.na(y))
    polys <- split(data.frame(x=x,y=y)[!is.na(x),], idx[!is.na(x)])
    r <- lapply(polys, function(P) {
        x <- P$x; y<-P$y
        side <- splitter$classify(x, y)
        if(side[1] != side[length(side)]) {
            ints <- splitter$intercepts(c(x,x[1]), c(y, y[1]), c(side, side[1]))
        } else {
            ints <- splitter$intercepts(x, y, side)
        }
        sideps <- lapply(unique(side), function(ss) {
            pts <- data.frame(x=x[side==ss], y=y[side==ss],
                idx=seq_along(x)[side==ss], dir=0)
            mm <- rbind(pts, ints)
            mm <- mm[order(mm$idx), ]
            br <- cumsum(mm$dir!=0 & c(0,head(mm$dir,-1))!=0 &
                c(0,diff(mm$idx))>1)
            if (length(unique(br))>1) {
                mm<-rollup(mm, sum(br==br[1]))
            }
            br <- cumsum(c(FALSE,abs(diff(mm$dir*mm$dir))==3))
            do.call(rbind, lapply(split(mm, br), addnullrow))
        })
        pss<-rep(unique(side), sapply(sideps, nrow))
        ps<-do.call(rbind, lapply(sideps, addnullrow))[,c("x","y")]
        attr(ps, "side")<-pss
        ps
   })
   pss<-unname(unlist(lapply(r, attr, "side")))
   src <- rep(seq_along(r), sapply(r, nrow))
   r <- do.call(rbind, r)
   attr(r, "source")<-src
   attr(r, "side")<-pss
   r
}

Входные данные — это просто значения x и y, которые вы бы передали polygon вместе с резаком. Он вернет data.frame со значениями x и y, которые можно использовать с polygon.

Например

x <- c(1,2,2.5,NA,2.5,3,4)
y <- c(1,-2,2,NA,-1,2,-2)

sl<-getSplitLine(0,0)

plot(range(x, na.rm=T),range(y, na.rm=T), type = "n")
p <- splitPolygon(x,y,sl)
g <- cumsum(c(F, is.na(head(p$y,-1))))
gc <- ifelse(attr(p,"side")[is.na(p$y)],
    "red","green")
polygon(p, col=gc)
sl$plot(lty=2, col="grey")

введите здесь описание изображения

Это должно работать как для простых вогнутых многоугольников, так и для наклонных линий. Вот еще один пример

x <- c(1,2,3,4,5,4,3,2)
y <- c(-2,2,1,2,-2,.5,-.5,.5)

sl<-getSplitLine(.5,-1.25)

plot(range(x, na.rm=T),range(y, na.rm=T), type = "n")
p <- splitPolygon(x,y,sl)
g <- cumsum(c(F, is.na(head(p$y,-1))))
gc <- ifelse(attr(p,"side")[is.na(p$y)],
    "red","green")
polygon(p, col=gc)
sl$plot(lty=2, col="grey")

введите здесь описание изображения

Прямо сейчас все может стать немного запутанным, когда вершина многоугольника попадает прямо на линию разделения. Я могу попытаться исправить это в будущем.

person MrFlick    schedule 02.07.2014
comment
А, я только что понял, что привел плохой пример. Из-за 0 в y одна сторона многоугольника уже находится на линии раздела. Как насчет данных без 0, например. y <- c(1,-2,2,NA,-1,2,-2)? Я не вижу, какие корректировки я должен был бы сделать. - person mrub; 02.07.2014
comment
Вы на самом деле все выпуклые многоугольники? Или они тоже вогнутые. У меня почти работает более общее решение, но есть несколько раздражающих крайних случаев. - person MrFlick; 03.07.2014
comment
Я думаю, что у меня будут только выпуклые многоугольники. Обычно мне приходится делить данные на графике, которые будут состоять только из выпуклых многоугольников. - person mrub; 03.07.2014

arrow_upward
3
arrow_downward

Более быстрое, но не очень точное решение состоит в том, чтобы разделить фрейм данных на список в соответствии с группирующей переменной (например, вверху = красный и внизу = синий). Это довольно хороший обходной путь для довольно больших (я бы сказал,> 100 элементов) наборов данных. Для меньших фрагментов могут быть видны неоднородности:

x <- 1:100
y1 <- sin(1:100/10)*0.8
y2 <- sin(1:100/10)*1.2
plot(x, y2, type='l')
lines(x, y1, col='red')

df <- data.frame(x=x, y1=y1, y2=y2)

df$pos_neg <- ifelse(df$y2-df$y1>0,1,-1) # above (1) or below (-1) average

# create the number for chunks to be split into lists:
df$chunk <- c(1,cumsum(abs(diff(df$pos_neg)))/2+1) # first element needs to be added`
df$colors <- ifelse(df$pos_neg>0, "red","blue") # colors to be used for filling the polygons
# create lists to be plotted:
l <- split(df, df$chunk) # we should get 4 sub-lists
lapply(l, function(x) polygon(c(x$x,rev(x$x)),c(x$y2,rev(x$y1)),col=x$colors))

Как я уже сказал, для меньшего набора данных может быть виден некоторый разрыв, если между положительными и отрицательными областями происходят резкие изменения, но если горизонтальная линия различает эти два или наносится больше элементов, то этот эффект игнорируется:

результаты

person speleo    schedule 31.01.2019