Preencher área entre duas linhas, com alta / baixa e datas
Prefácio: dou uma resposta razoavelmente satisfatória à minha própria pergunta. Entendo que essa é uma prática aceitável. Naturalmente, minha esperança é convidar sugestões e melhorias.
Meu objetivo é plotar duas séries temporais (armazenadas em um quadro de dados com datas armazenadas como classe 'Data') e preencher a área entre os pontos de dados com duas cores diferentes, dependendo se uma está acima da outra. Por exemplo, plotar um índice de títulos e um índice de ações e preencher a área em vermelho quando o índice de ações estiver acima do índice de títulos e preencher a área em azul caso contrário.
Eu tenho usadoggplot2
para esse fim, porque estou razoavelmente familiarizado com o pacote (autor: Hadley Wickham), mas fique à vontade para sugerir outras abordagens. Eu escrevi uma função personalizada com base nogeom_ribbon()
função doggplot2
pacote. No início, enfrentei problemas relacionados à minha falta de experiência no manuseio dogeom_ribbon()
função e objetos de classe'Date'
. A função abaixo representa meu esforço para resolver esses problemas, quase certamente é rotatória, desnecessariamente complicada, desajeitada, etc. Então, minha pergunta é:Por favor, sugerir melhorias e / ou abordagens alternativas. Por fim, seria ótimo ter uma função de uso geral disponibilizada aqui.
Dados:
set.seed(123456789)
df <- data.frame(
Date = seq.Date(as.Date("1950-01-01"), by = "1 month", length.out = 12*10),
Stocks = 100 + c(0, cumsum(runif(12*10-1, -30, 30))),
Bonds = 100 + c(0, cumsum(runif(12*10-1, -5, 5))))
library('reshape2')
df <- melt(df, id.vars = 'Date')
Função personalizada:
## Function to plot geom_ribbon for class Date
geom_ribbon_date <- function(data, group, N = 1000) {
# convert column of class Date to numeric
x_Date <- as.numeric(data[, which(sapply(data, class) == "Date")])
# append numeric date to dataframe
data$Date.numeric <- x_Date
# ensure fill grid is as fine as data grid
N <- max(N, length(x_Date))
# generate a grid for fill
seq_x_Date <- seq(min(x_Date), max(x_Date), length.out = N)
# ensure the grouping variable is a factor
group <- factor(group)
# create a dataframe of min and max
area <- Map(function(z) {
d <- data[group == z,];
approxfun(d$Date.numeric, d$value)(seq_x_Date);
}, levels(group))
# create a categorical variable for the max
maxcat <- apply(do.call('cbind', area), 1, which.max)
# output a dataframe with x, ymin, ymax, is. max 'dummy', and group
df <- data.frame(x = seq_x_Date,
ymin = do.call('pmin', area),
ymax = do.call('pmax', area),
is.max = levels(group)[maxcat],
group = cumsum(c(1, diff(maxcat) != 0))
)
# convert back numeric dates to column of class Date
df$x <- as.Date(df$x, origin = "1970-01-01")
# create and return the geom_ribbon
gr <- geom_ribbon(data = df, aes(x, ymin = ymin, ymax = ymax, fill = is.max, group = group), inherit.aes = FALSE)
return(gr)
}
Uso:
ggplot(data = df, aes(x = Date, y = value, group = variable, colour = variable)) +
geom_ribbon_date(data = df, group = df$variable) +
theme_bw() +
xlab(NULL) +
ylab(NULL) +
ggtitle("Bonds Versus Stocks (Fake Data!)") +
scale_fill_manual('is.max', breaks = c('Stocks', 'Bonds'),
values = c('darkblue','darkred')) +
theme(legend.position = 'right', legend.direction = 'vertical') +
theme(legend.title = element_blank()) +
theme(legend.key = element_blank())
Resultado:
Embora existam perguntas e respostas relacionadas ao stackoverflow, não encontrei uma que fosse suficientemente detalhada para o meu propósito. Aqui está uma seleção de trocas úteis:
create-geom-ribbon-for-min-max-range: Faz uma pergunta semelhante, mas fornece menos detalhes do que eu estava procurando.possível-bug-in-geom-ribbon: Faltam etapas estreitamente relacionadas, mas intermediárias, sobre como calcular o máximo / min.preencher-região-entre-duas-loess-suavizou-linhas-em-r-com-ggplot: Estreitamente relacionado, mas se concentra nas linhas loess. Excelente.ggplot-coloração-áreas-entre-densidade-linhas-de acordo com a posição relativa : Estreitamente relacionado, mas se concentra em densidades. Este post me inspirou muito.