R + ggplot:带事件的时间序列

64

我是一个R/ggplot的新手。我想创建一个geom_line图,用于连续变量时间序列,并添加由事件组成的层。连续变量及其时间戳存储在一个数据框中,事件及其时间戳存储在另一个数据框中。

我真正想做的是类似finance.google.com上的图表。在这些图表中,时间序列是股价,有“标志”来指示新闻事件。我实际上没有画金融图表,但图表类型相似。我正在尝试绘制日志文件数据的可视化效果。这是我想要的一个例子...

google chart with events

如果可以的话,我想为每个层使用单独的数据框(一个用于连续变量观测值,另一个用于事件)。

经过一些尝试和错误,这是我能得到的最接近的结果。在这里,我使用ggplot自带的数据集作为示例数据。“economics”包含一些时间序列数据,我想绘制它,“presidential”包含一些事件(总统选举)。

library(ggplot2)
data(presidential)
data(economics)

presidential <- presidential[-(1:3),]
yrng <- range(economics$unemploy)
ymin <- yrng[1]
ymax <- yrng[1] + 0.1*(yrng[2]-yrng[1])

p2 <- ggplot()
p2 <- p2 + geom_line(mapping=aes(x=date, y=unemploy), data=economics , size=3, alpha=0.5) 
p2 <- p2 + scale_x_date("time") +  scale_y_continuous(name="unemployed [1000's]")
p2 <- p2 + geom_segment(mapping=aes(x=start,y=ymin, xend=start, yend=ymax, colour=name), data=presidential, size=2, alpha=0.5)
p2 <- p2 + geom_point(mapping=aes(x=start,y=ymax, colour=name ), data=presidential, size=3) 
p2 <- p2 + geom_text(mapping=aes(x=start, y=ymax, label=name, angle=20, hjust=-0.1, vjust=0.1),size=6, data=presidential)
p2

我的尝试

问题:

  • 对于非常稀疏的事件,这样做还可以,但如果有一堆事件聚集在一起(通常在日志文件中会发生),就会变得混乱。有没有什么技巧可以用来整洁地显示在短时间内发生的一大堆事件?我想到了position_jitter,但这真的很难实现。如果有很多这样的事件“标志”,谷歌图表将它们叠放在一起。

  • 实际上,我不喜欢将事件数据粘贴在连续测量显示的同一比例尺中。我更喜欢将其放在facet_grid中。问题在于,所有分面都必须来自相同的data.frame(不确定是否是这样)。如果是这样,那也似乎不理想(或者可能我只是不想使用reshape?)


7
有趣的情节:不要期望在共和党总统上台后能找到工作! - James
这只是最方便和可用的数据,用作示例 - 但是是的,这确实让你思考 :-) - Angelo
4个回答

87

我和其他人一样喜欢ggplot,但如果你想制作类似于Google Finance的图表,为什么不直接使用Google图形API呢?!?你会喜欢这个的:

install.packages("googleVis")
library(googleVis)

dates <- seq(as.Date("2011/1/1"), as.Date("2011/12/31"), "days")
happiness <- rnorm(365)^ 2
happiness[333:365] <- happiness[333:365]  * 3 + 20
Title <- NA
Annotation <- NA
df <- data.frame(dates, happiness, Title, Annotation)
df$Title[333] <- "Discovers Google Viz"
df$Annotation[333] <- "Google Viz API interface by Markus Gesmann causes acute increases in happiness."

### Everything above here is just for making up data ### 
## from here down is the actual graphics bits        ###
AnnoTimeLine  <- gvisAnnotatedTimeLine(df, datevar="dates",
                                       numvar="happiness", 
                                       titlevar="Title", annotationvar="Annotation",
                                       options=list(displayAnnotations=TRUE,
                                                    legendPosition='newRow',
                                                    width=600, height=300)
                                       )
# Display chart
plot(AnnoTimeLine) 
# Create Google Gadget
cat(createGoogleGadget(AnnoTimeLine), file="annotimeline.xml")

然后它生成了这张精美的图表:

在此输入图片描述


13
你感觉到了幸福感增加,是吗?看,图表不会欺骗人! :) - JD Long
预测:你的演示将会让你的声誉大增。 - IRTFM

41

尽管我很喜欢@JD Long的答案,但我会提供一个仅使用R/ggplot2的解决方案。

方法是创建第二个事件数据集,并使用它来确定位置。从@Angelo开始:

library(ggplot2)
data(presidential)
data(economics)
提取(总统)事件数据并进行转换。计算基准线(baseline)和偏移量(offset),作为将要与经济数据一起绘制的分数。将底部(ymin)设置为基准线。这是棘手的部分。如果标签过于接近,我们需要能够错开它们。因此,确定相邻标签之间的间距(假设事件已排序)。如果小于某个数量(我选择了这个数据规模约为4年),那么注意该标签需要更高。但它必须比其后面的标签更高,所以使用rle获取TRUE长度(即必须更高的长度),并使用该长度计算偏移矢量(每个TRUE字符串都必须从其长度倒数到2,FALSE只在偏移1处)。使用此方法确定条形图的顶部(ymax)。
events <- presidential[-(1:3),]
baseline = min(economics$unemploy)
delta = 0.05 * diff(range(economics$unemploy))
events$ymin = baseline
events$timelapse = c(diff(events$start),Inf)
events$bump = events$timelapse < 4*370 # ~4 years
offsets <- rle(events$bump)
events$offset <- unlist(mapply(function(l,v) {if(v){(l:1)+1}else{rep(1,l)}}, l=offsets$lengths, v=offsets$values, USE.NAMES=FALSE))
events$ymax <- events$ymin + events$offset * delta

将这些内容结合起来绘制成图:

ggplot() +
    geom_line(mapping=aes(x=date, y=unemploy), data=economics , size=3, alpha=0.5) +
    geom_segment(data = events, mapping=aes(x=start, y=ymin, xend=start, yend=ymax)) +
    geom_point(data = events, mapping=aes(x=start,y=ymax), size=3) +
    geom_text(data = events, mapping=aes(x=start, y=ymax, label=name), hjust=-0.1, vjust=0.1, size=6) +
    scale_x_date("time") +  
    scale_y_continuous(name="unemployed \[1000's\]")

您可以使用分面绘图,但在使用不同刻度时会有些棘手。另一种方法是组合两个图表。需要进行一些额外的调整,以确保绘图具有相同的x范围,在下面的图中适合所有标签,并消除上面的图中的x轴。

xrange = range(c(economics$date, events$start))

p1 <- ggplot(data=economics, mapping=aes(x=date, y=unemploy)) +
    geom_line(size=3, alpha=0.5) +
    scale_x_date("", limits=xrange) +  
    scale_y_continuous(name="unemployed [1000's]") +
    opts(axis.text.x = theme_blank(), axis.title.x = theme_blank())

ylims <- c(0, (max(events$offset)+1)*delta) + baseline
p2 <- ggplot(data = events, mapping=aes(x=start)) +
    geom_segment(mapping=aes(y=ymin, xend=start, yend=ymax)) +
    geom_point(mapping=aes(y=ymax), size=3) +
    geom_text(mapping=aes(y=ymax, label=name), hjust=-0.1, vjust=0.1, size=6) +
    scale_x_date("time", limits=xrange) +
    scale_y_continuous("", breaks=NA, limits=ylims)

#install.packages("ggExtra", repos="http://R-Forge.R-project.org")
library(ggExtra)

align.plots(p1, p2, heights=c(3,1))


3
哇喔!在你和@JDLong的帮助下,我今天学会了一些非常不错的R语言技巧! - Angelo
非常有用,谢谢@Brian Diggs。有点过时了。这里是代码的更新版本:http://pastebin.com/sVAACtQe(必须调整边距,很繁琐 - 当然可以复制粘贴)。 - PatrickT

5

考虑到您要绘制时间序列和定性信息,大多数经济书籍使用绘图区域来指示数据的结构变化或事件,因此我建议使用类似于这样的方法:

library(ggplot2)
data(presidential)
data(economics)

ggplot() +
  geom_rect(aes(xmin = start,
                xmax = end,
                ymin = 0, ymax = Inf,
                fill = name),
            data = presidential,
            show.legend = F) +
  geom_text(aes(x = start+500,
                y = 2000,
                label = name,
                angle = 90),
            data = presidential) +
  geom_line(aes(x = date, y = unemploy),
            data= economics) +
  scale_fill_brewer(palette = "Blues") +
  labs(x = "time", y = "unemploy")

enter image description here


5
Plotly 是使 ggplots 交互的简单方法。为了显示事件,将它们强制转换为可以作为美学元素(例如颜色)显示的因子。
最终结果是一个可以在其上拖动光标的图表。这些图表显示感兴趣的数据:
以下是制作 ggplot 的代码:
# load data    
data(presidential)
data(economics)

# events of interest
events <- presidential[-(1:3),]

# strip year from economics and events data frames
economics$year = as.numeric(format(economics$date, format = "%Y")) 

# use dplyr to summarise data by year
#install.packages("dplyr")
library(dplyr)
econonomics_mean <- economics %>% 
  group_by(year) %>% 
  summarise(mean_unemployment = mean(unemploy))

# add president terms to summarized data frame as a factor
president <- c(rep(NA,14), rep("Reagan", 8), rep("Bush", 4), rep("Clinton", 8), rep("Bush", 8), rep("Obama", 7))
econonomics_mean$president <- president

# create ggplot
p <- ggplot(data = econonomics_mean, aes(x = year, y = mean_unemployment)) +
  geom_point(aes(color = president)) +
  geom_line(alpha = 1/3)

只需要一行代码就可以将ggplot转换为plotly对象。
# make it interactive!
#install.packages("plotly")
library(plotly)
ggplotly(p)

哇,这真的很漂亮。谢谢。 - Alexey Burnakov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接