showtext:字体,好玩的字体和好玩的图形

统计图形的作用想必不用我多说,一幅美观的图往往能让枯燥的数据变得有趣起来,而R恰巧就是这样一个作图的利器。然而,从论坛上的帖子来看,大家在用R画图时经常会遇到几个终极问题:

  1. 中文无法显示
  2. XX类型的图怎么画?
  3. 中文无法显示
  4. 中文无法显示
  5. 中文无法显示
  6. ……

第2个问题由于太过终极我还没法回答,所以就先试着解决第1个,第3个,第4个,第5个,第……个问题好了。

使用字体

图片无法显示中文,究其原因,是R的很多图形设备只能使用一些标准的字体,但它们往往不包含中文的字符。而包含中文的字体,如Windows自带的宋体、黑体等,R又不知道如何使用它们。于是这就成了一个死循环:我们有中文字体吧,R不会用;R能用的字体吧,我们又看不上——所以说R和useR都不好伺候……

不过现在情况有了一定的改善,我们有了sysfonts这个包,专门用来加载系统里的字体文件,其中主要一个函数是font.add(),用法为

font.add(family, regular, ...)

其中family是你给这个字体赋予的名称,在后面的绘图命令中你将通过它来引用这个字体。regular是字体文件的路径,如果字体在系统的标准位置(例如Windows的C:\Windows\Fonts)或是当前的工作目录,则可以直接输入文件名。例如,在Windows系统下,以下命令将导入系统中的楷体文件,并给它取名为“kaishu”:

font.add("kaishu", "simkai.ttf")

添加完字体之后,可以使用font.families()函数来查看当前可用的字体名称,不出意外的话现在应该包含四种字体:sans,serif,mono和kaishu。其中前三个是sysfonts包自动加载的,而kaishu则是我们刚才添加进去的。

字体的加载过程完毕,接下来就是如何使用它们了。当然了,R本身是不认识这些字体的,我们需要使用showtext附加包来真正利用这些字体绘图。

showtext的用法更加简单,目前只有两个函数:showtext.begin()showtext.end()。我们需要做的就是把绘图的命令包含在这两个语句中间,然后在适当的地方选取字体即可。不多说,直接上代码:

# showtext会自动加载sysfonts包
library(showtext);
# 导入楷体
font.add("kaishu", "simkai.ttf");

library(Cairo);
# 打开图形设备
CairoPNG("chinese-char.png", 600, 600);
# 开始使用showtext
showtext.begin();
# 一系列绘图命令
set.seed(123);
plot(1, xlim = c(-3, 3), ylim = c(-3, 3), type = "n");
text(runif(100, -3, 3), runif(100, -3, 3),
     intToUtf8(round(runif(100, 19968, 40869)), multiple = TRUE),
     col = rgb(runif(100), runif(100), runif(100), 0.5 + runif(100)/2),
     cex = 2, family = "kaishu");    # 指定kaishu字体
title("随机汉字", family = "wqy");   # 指定wqy字体
# 停止使用showtext
showtext.end();
# 关闭图形设备
dev.off();

也就是说,要让R使用我们之前加载的字体,只需要将画图命令包含在一对showtext.begin()showtext.end()中间,然后在绘图命令中选取family = ...即可。代码中的wqyshowtext包自带的文泉驿微米黑字体,可以显示绝大多数的汉字,所以即使你的系统中没有中文字体,也可以用它来绘制带中文的图形。

上面的小程序会在图形中随机显示一些汉字,效果如下图:

随机显示汉字

图1:随机显示汉字

(我赌两块糖,你不认识上面一半以上的汉字……)

好玩的字体

有了上面介绍的showtext包,你基本上可以使用任何一种字体来显示文字了。这时候我们可以做一些有意思的事情:有些字体中包含的并不是字母和数字,而是一些符号或图标。例如这个WM People 1字体,其中字母p和字母u分别是男人和女人的图案,利用这一点我们可以绘制出下面这幅图:

教育程度统计图2:用特殊字体绘图

其实这幅图本质上就是一个堆叠的条形图,但这样画出来之后,可以很直观地体现出各个类别的人数和性别比例,而且图形本身就已经有解释性,不需要再额外添加图例等元素。

绘制这幅图的代码为:

link = "http://img.dafont.com/dl/?f=wm_people_1";
download.file(link, "wmpeople1.zip", mode = "wb");
unzip("wmpeople1.zip");

library(showtext);
font.add("wmpeople1", "wmpeople1.TTF");

library(ggplot2);
library(plyr);
library(Cairo);

dat = read.csv(textConnection('
edu,educode,gender,population
未上过学,1,m,17464
未上过学,1,f,41268
小  学,2,m,139378
小  学,2,f,154854
初  中,3,m,236369
初  中,3,f,205537
高  中,4,m,94528
高  中,4,f,70521
大专及以上,5,m,57013
大专及以上,5,f,50334
'));

dat$int = round(dat$population / 10000);
gdat = ddply(dat, "educode", function(d) {
    male = d$int[d$gender == "m"];
    female = d$int[d$gender == "f"];
    data.frame(gender = c(rep("m", male), rep("f", female)),
               x = 1:(male + female));
});
gdat$char = ifelse(gdat$gender == "m", "p", "u");

CairoPNG("edu-stat.png", 600, 300);
showtext.begin();
theme_set(theme_grey(base_size = 15));
ggplot(gdat, aes(x = x, y = educode)) +
    geom_text(aes(label = char, colour = gender),
              family = "wmpeople1", size = 8) +
    scale_x_continuous("人数(千万)") +
    scale_y_discrete("受教育程度",
        labels = unique(dat$edu[order(dat$educode)])) +
    scale_colour_hue(guide = FALSE) +
    ggtitle("2012年人口统计数据");
showtext.end();
dev.off();

其实,图中的每一个小人都是一个p或者u的字符,只是因为在这种字体下,它们显示出不一样的图案罢了。

好玩的图形

更进一步,如果坐标轴也用不一样的字体来展现呢?结果当然是,被!玩!坏!了!!

图3:暴漫版图形(图片来源:http://cos.name/cn/topic/147769 @doctorjxd)

不过真的很贴切有木有!!小学的时候各种玩具枪!初中的时候哈哈哈笑个不停!高中的时候多么正经的好少年!去念大学之后就成了那副熊样了不是吗!!(请无视此刻暴走的作者)

不过这种思路确实很赞,试想一下,如果我们把一些枯燥的坐标轴说明文字变成了更形象的图案,那么整幅图的表现力是不是就更强了呢?像是下面这样:

豆瓣评分图4:坐标轴上放置表情

附上相应的R代码:

link = "http://img.dafont.com/dl/?f=emoticons";
download.file(link, "emoticons.zip", mode = "wb");
unzip("emoticons.zip");

library(showtext);
font.add("emoticons", "emoticons.ttf");

library(ggplot2);
library(Cairo);
emotions = c("W", "s", "C", "A", "p");
score = c(0.5, 0.9, 5.5, 18.4, 74.7);
x = factor(emotions, emotions);
gdat2 = data.frame(x, score);
CairoPNG("douban.png", 600, 600);
showtext.begin();
ggplot(gdat2, aes(x = x, y = score)) +
    geom_bar(stat = "identity") +
    scale_x_discrete("") +
    scale_y_continuous("百分比") +
    theme(axis.text.x=element_text(size=rel(4), family="emoticons")) +
    ggtitle("《神探夏洛克第三季》豆瓣评分");
showtext.end();
dev.off();

所以,发挥你的想像力,绘制出更形象、更有趣的统计图形吧!

附:相关资源

showtext:字体,好玩的字体和好玩的图形》有60个想法

  1. 可以解决长久以来用R做中文词云丑陋无比的问题……

  2. 问一个有点跑题的问题,Cairo包里画的png图片的函数要比基础包里的png函数好在哪儿?

    1. Cairo包对showtext非常重要,因为默认的png()没有抗锯齿功能,画出来的字形很难看,你用png()和CairoPNG()对比一下就知道了。

  3. 还是那幅正常的图看起来比较顺眼,我是指“其实这幅图本质上就是一个堆叠的条形图”上面那幅。深深的体会到图表不编号后人难以引用的痛苦哇。

      1. 赞一个行动效率!(貌似我自己也从来不编号,哈哈哈哈,无耻就是这么养成的)

      1. 有道理。但是个人感觉没有什么必要,直接换行就可以了。不过,这可能是个人习惯问题,写惯了C或Java.

  4. 请教下,为什么我运行图1的代码后,没提示出错信息,就是图没得出来呢

    1. 也许那些网站不支持外链,你可以先想办法下载到本地,再加载到R中。

    1. 脚本运行ggplot的图的时候要先把ggplot的图存为对象,然后再用print(p)之类的方法“打印”出来。

      1. 昨天一直装不上showtext这个包,后来用的github装上了。。还有昨天一直装Cairo也装不上,今天install.packages一下就好了。。。好奇怪。。

      2. 谢谢yixuan的回复,发现统计之都文章作者都能很及时的回复留言,让人感觉很好。

  5. 尊敬的作者大人你好,这么长时间过去了,把这个帖子翻出来,我想咨询一下,我是用的是OS系统,用OS的根目录载入字体后,showtext使用后警告—-字体家族”wqy”没有字体,是我操作有什么问题么?

      1. 还是显示 字体家族”wqy-microhei”没有字体。。。

  6. 您好,例2不使用cariro,直接在Rstudio显示时,只能显示P,U,这是为什么呢?

  7. 请问为什么运行第一个命令时没有报错,最后却没有输出结果,在结尾显示null device是怎么回事呢

      1. 图像被保存到一个文件中了,不会有窗口弹出。文件就存在R的工作目录中。

    1. 我的也显示不了,请问你解决了吗?我的是rep(“m”,male)那边提示有错

      1. 把错误信息贴出来我才好判断是什么情况。我运行这段代码是没有问题的。

      1. 请问为什么inToUft8这个函数用“intToUtf8(round(runif(20, 4500, 5000)),multiple = T)”时有问题(画出的是一个个方框,没有汉字),而本文中的“intToUtf8(round(runif(100, 19968, 40869))”就没问题呢?

      2. 还有请问为什么dat末尾有一空白行,不去掉的话下面代码无法进行;去掉的话,画出来的图中y轴没有刻度(就是“小学”、“初中”等消失了)

  8. scale_y_discrete中的label改为limits试试,轩哥说是ggplot包的更新搞得不兼容了

发表评论

电子邮件地址不会被公开。 必填项已用*标注