ggimage:让你在ggplot2中愉快地使用图片
卖萌控的博客
点击这里进入电脑版页面!体验更好
ggimage:让你在ggplot2中愉快地使用图片
2023-6-10 萌小白


导言



本文介绍了ggimage包,允许在ggplot2作图时嵌入图片,并支持aes映射,可以把离散型变量映射到不同图片。目前有几个包可以使用图片嵌入做图,但都是针对特定的场景,这里使用ggimage来展示在这些特定领域里的应用,ggimage的设计是通用的,并不被特定场景所限定,文末又介绍了用R图标来画出R、用饼图来画气泡图等实例。



图上嵌图片



R 基础图形库(base graphics)可以在做图的时候嵌入图片,使用的是graphics::rasterImage:



imgurl <- ""



library(EBImage)x <- readImage(imgurl)plot( 1, type = "n", xlab = "", ylab = "", xlim = c( 0, 8), ylim = c( 0, 8))rasterImage(x, 2, 2, 6, 4)






如果我们搜索”ggplot2 image”,会找到类似于下面这样的帖子/博文:







CatterPlots实现的方式就是上面谈到的rasterImage内部使用了循环。rphylopic同时支持基础图形库(base graphics)和ggplot2,也是一样的实现方式,不过rphylopic内部没有使用循环,一次只能加一个图,它使用的图来自于phylopic数据库。



我们用ggimage同样可以使用phylopic图片:








emoGG是专门来画emoji的,如果要画emoji的话,我推荐我写的emojifont包,在轩哥的showtext基础上,把emoji当做普通字体一样操作,更方便。



emoGG这个包提供了geom_emoji图层,虽然一次可以画出散点,但因为不支持aes映射,而ggimage所提供的geom_emoji则支持映射,下面的例子中我们做了一个简单的回归分析,如果残差<0.5用笑脸表示,>0.5则用哭脸来表示。



set.seed( 123)iris2 <- iris[sample( 1:nrow(iris), 30),
]model <- lm(Petal.Length ~ Sepal.Length, data = iris2)iris2$fitted
<- predict(model)p <- ggplot(iris2, aes(x = Sepal.Length, y =
Petal.Length)) + geom_linerange(aes(ymin = fitted, ymax = Petal.Length),
colour = "purple") + geom_abline(intercept = model$coefficients[ 1], slope = model$coefficients[ 2])p + ggimage::geom_emoji(aes(image = ifelse(abs(Petal.Length-fitted) > 0.5, '1f622', '1f600')))






如果要用emoGG来做的话,则需要自己切数据分两次来进行:



p + emoGG::geom_emoji(data = subset(iris2, (Petal.Length-fitted) < 0.5), emoji = "1f600") + emoGG::geom_emoji(data = subset(iris2, (Petal.Length-fitted) > 0.5), emoji = "1f622")



这里我们只分两类(残差是否大于0.5),所以需要加两次,试想我们的分类变量有多种可能的取值,则我们需要分多次切数据加图层,CatterPlotsrphylopicemoGG都有这个问题,这也是aes映射之于ggplot2的重要和强大之处,它让我们可以在更高的抽像水平思考,



ggflags是支持aes映射的,只不过它只能用来画国旗而已。同样ggimage也提供



了相应的geom_flag来使用国旗用于做图。



library(rvest)



library(dplyr)url <- "http://www.nbcolympics.com/medals"medals <- read_html(url) %>% html_nodes( "table") %>% html_table() %>% .[[ 1]]



library(countrycode)



library(tidyr)medals <- medals %>% mutate(code = countrycode(Country, "country.name", "iso2c")) %>% gather(medal, count, Gold:Bronze) %>% filter(Total >= 10)head(medals)























































Country

Total

code

medal

count

Russia

33

RU

Gold

13

United States

28

US

Gold

9

Norway

26

NO

Gold

11

Canada

25

CA

Gold

10

Netherlands

24

NL

Gold

8

Germany

19

DE

Gold

8


首先我们从网站上爬回来2016年各个国家的奥林匹克奖牌数,画出柱状图,并在xlab国家名边上用ggimage画上国旗:



p <- ggplot(medals, aes(Country, count)) + geom_col(aes(fill = medal), width = .8)p + geom_flag(y = - 2, aes(image = code)) + coord_flip() + expand_limits(y = - 2) + scale_fill_manual(values = c( "Gold"= "gold", "Bronze"= "#cd7f32", "Silver"= "#C0C0C0"))






ggimage



前面我们介绍了ggimage在一些场景的应用实例,虽然有专门的包针对这些应用场景,但ggimage在这些领域中的表现要比大多数的包要好(支持aes映射)。但ggimage的使用并不限于这些(geom_phylopic,geom_emoji和geom_flag只是通用图层geom_image的简单封装),这里将展示一些有趣的例子。



用R图标来画R形状 x <- c( 2, 2, 2, 2, 2, 3, 3, 3.5, 3.5, 4)y <- c( 2, 3, 4, 5, 6, 4, 6, 3, 5, 2)d <- data.frame(x = x, y = y)img <- system.file( "img", "Rlogo.png", package = "png")ggplot(d, aes(x, y)) + geom_image(image = img, size = .1) + xlim( 0, 6) + ylim( 0, 7)






嵌套式绘图



这里我要展示的是非常有名的气泡图(Bubble Plot),但气泡不是圆圈,而是使用



ggplot2画的饼图,我先把饼图保存起来,再用ggimage拿来画,饼图的大小



与人口总数正相关。这个例子可以应用到很多场景中去,比如一个时间序列的曲线,你要用统计图在某些时间点上展示相关的信息,比如你要在地图上加某些地方的相关统计信息(如果要在地图上画饼图,可以使用我写的scatterpie包)。



crime <- read.csv( "http://datasets.flowingdata.com/crimeRatesByState2005.tsv",header = TRUE, sep = "t", stringsAsFactors = F) library(gtable)plot_pie <- function(i) { df <- gather(crime[i, ], type, value, murder:motor_vehicle_theft) ggplot(df, aes(x = 1, value, fill = type)) + geom_col() + coord_polar(theta = 'y') + ggtitle(crime[i, "state"]) + theme_void() + theme_transparent() + theme(legend.position = "none", plot.title = element_text(size = rel( 6), hjust = 0.5))}pies <- sapply( 1:nrow(crime), function(i) { outfile <- paste0( "crime_", i, ".png") plot_pie(i) + ggsave(outfile, bg = "transparent") outfile})radius <- sqrt(crime$population / pi)crime$radius <- 0.2* radius/max(radius)crime$pie <- piesleg1 <- gtable_filter( ggplot_gtable( ggplot_build(plot_pie( 1) + theme(legend.position = "right")) ), "guide-box")ggplot(crime, aes(murder, Robbery)) + geom_image(aes(image = pie, size = I(radius))) + geom_subview(leg1, x = 8.8, y = 50)






我们还可以每次只画一个州的数据,制作成动图。



plot_crime <- function(i) { o <- paste0(i, ".png")
ggplot(crime, aes(murder, Robbery)) + geom_blank() + geom_image(data =
crime[i, ], aes(image = pie, size = I(radius))) + geom_subview(p, leg1, x
= 8.8, y = 50) + ggsave(o) o} library(magick)



library(purrr)order(crime$murder, decreasing = F) %>% map(plot_crime) %>% map(image_read) %>% image_join() %>% image_animate(fps = 2) %>% image_write( "crime.gif")






geom_subview可以图上嵌图,并不需要保存为图片,但对于ggplot2来讲,保存图片也是有好处的,因为ggplot2画图,点线是在数据空间上,随着我们保存图片的大小是按比例缩小或放大的,但文字是在像素空间上,和画图空间并不相关。所以当我们嵌图时缩小了画图窗口之后,字体会显得格外大,微调起来也比较繁琐,这时候保存为合适尺寸的图片,再用geom_image来加上去,显然就轻松得多。



其它来自R社区的例子



SAS博客对M&M巧克力的颜色分布做了分析,通过模拟估计不同颜色的置信区间。这个分析被翻译成R,并产生下图:






其中垂直片段|是真实值,水平片段当然就是置信空间了,而估计值用了ggimage来画不同颜色的巧克力。



另一个例子是迪斯尼电影主人公名字的流行程度:






最近我还添加了geom_pokemon图层,让大家可以用pokemon来画图,比如:






ggimage是通用的包,所以可以被应用于不同的领域/场景中,起码可以让我们画出更好玩的图出来,后续我有时间的话,会写一个draw_key_image的函数,实现使用图片来当legend key的功能。



最后祝大家玩得开心!不要把图画得太有魔性哦:)







感谢大为和太云的校稿,特别是大为提出很多修改意见以及给出了用R画R的例子。



参考资料


发表评论:
昵称

邮件地址 (选填)

个人主页 (选填)

内容