R语言天气可视化应用
前言在很多人看来,R语言还只是个玩具,完全不具备企业级应用的能力。说这些话的人,根本就不了解R语言,更不清楚如何做企业级应用开发。
前言
在很多人看来,R语言还只是个玩具,完全不具备企业级应用的能力。说这些话的人,根本就不了解R语言,更不清楚如何做企业级应用开发。从我最早接触R语言时,就把R做为可视化引擎嵌入到了晒粉丝的微博应用中;后来又开发了数据挖掘算法竞赛网站,并把R语言做为算法引擎,并支持在线编程及运行;我做的第三个R语言应用就是本文要给大家分享的每日中国天气微博应用,这次同样是把R做为可视化引擎,并让R完成爬虫、XML文档解析及数据处理等的任务;当然,我还实现了第四个、第五个、第六个以R为核心的应用,都是量化投资方面的,会在下一本书《R的极客理想-量化投资篇》再介绍给大家。
从我的使用经验来看,R语言已经具备了企业级应用的能力,但我并不是要用R语言完成所有编程任务。在我的项目环境中,大都是多种编程语言配合使用的,只有发挥各自语言的特性优势,才是未来的发展方向。
本文所介绍的每日中国天气微博应用开发,将分为3篇介绍R语言和PHP语言的混合编程,第一篇为R语言功能实现,第二篇为R包开发,第三篇为用PHP构建微博应用。本文是第一篇。
目录
- 项目介绍
- 系统架构设计
- R语言程序现实
1. 项目介绍
谈到多语言混编,同如在计算机领域跨学科一样,是我所一直倡导一种工作模式。当编程语言百花齐放,各种细分市场的小众语言如雨后春笋般地成长起来,比起通用型编程语言来说,这些小众语言在特定的领域中有着非常明显的优势。 比如统计应用,如果用Java写个逻辑回归程序感觉深不见底,而用R语言实现逻辑回归就是个很平常的一件事情。 再比如做一个Web网站,用PHP或Nodejs实现轻而易举,如果用Java做不仅代码量大,而且程序复杂。 所以,对于一个应用来说,一种通用的语言并不一定是最好的解决方案,如果能实现多种语言的结合,那么你做出来的应用可以很酷,很不一样!
对于本文要介绍的 每日中国天气 这个新浪微博应用,就是一种多语言混编的实现。
项目介绍
这个项目的出发点很简单,就是通过可视化技术,展示中国每个省份的天气情况,给准备旅游的朋友,提供一种出行的提示。
要做实现这个应用,我们首先要列出,要实现哪些功能,会遇到哪些问题等。
- 天气数据:数据从哪里找到,如何下载,如何存储。
- 定时任务:天气数据需要每日更新,图片需要每日新生成。
- 地图和天气可视化:要把中国行政区图和天气数据结合在一起画图,让用户一眼就能看明白。
- Web展示:通过可视化技术,我们生成的只是一张静态图片,如何发布到Web端进行展示。
- 微博:通过结合新浪微博,让更多的用户看到并使用这个应用。
- 用户交互:用户可以查看不同日期、不同类型的图片,用户还可以通过微博分享。
- 虽然是个很小的应用,但五脏俱全,我们也需要完整的思考,如何才能实现这个应用呢!
2. 系统架构设计
从上面的功能描述中,单独使用一种语言也可以实现的。 如果单独用PHP开发,做一个Web网站非常容易,连接新浪微博也有现成的SDK可以调用,爬取数据及存储也不麻烦,那么如何实现地图和天气数据的可视化,似乎就是卡在这里了。 如果单独用R开发,爬取数据及存储同样很容易实现,地图和天气数据的可视化也是很方便就能画出来,但是用R做Web网站,那就会遇到很大的瓶颈了,因为R是单线程同步的计算模型,Web应用的高并发特点,会直接让R程序崩溃的。 所以,综合上面的问题,如果R语言和PHP语言能结合在一起使用,不仅能避开每种语言不擅长的地方,还能在擅长的领域发挥出每种语言的特性,我们将通过多语言的混编技术做出很不一样的应用来。
为了实现应用的功能需求,我们要设计一套系统架构。
系统架构解释:
- 通过定时器启动爬虫程序,到Yahoo的天气数据源下载数据。
- 爬虫下载数据到本地服务器进行解析,存储应用相关的数据到CSV文件。
- 可视化程序,读入天气数据及地图数据,生成静态的图片作为可视化输出。
- 最终用户通过新浪微博,加载Web应用,看到了可视化生成的静态图片。
- 最终用户通过新浪微博分享了这个应用,让更多的人看到这个应用。
下面按照语言的优势,把应用架构以语言的特性来划分,让R语言实现爬虫、处理数据和可视化,让PHP完成Web开发、新浪API接入和用户交互。
由于我们这个应用,不需要让R和PHP直接进行通信,那么复杂度就会变得小很多了,像我之前做的晒粉丝应用,是3种语言的结合包括了R, PHP, Java,通过Java实现中间程序的调度,让R和PHP能够实现通信。
我们通过语言的划分,就可以扬长避短,让每种语言在最擅长的领域,完成最擅长的事情。
对于后台技术应用,定时器可以用Linux系统的CRON实现;然后用R语言程序来爬取数据,通过RCurl包来完成;爬取后的数据为XML格式,再通过R语言用XML包进行解析,以CSV格式进行本地存储;接下来,再用R语言处理数据,加载地图包ggmap、mapdata、maptools,最后配合plot()函数实现图片的输出,保存在本地服务器上。
对于前端的PHP应用来说,用PHP做一个Web网站很简单,使用YII快速开发框架;用PHP的新浪微博SDK进行API操作,实现新浪登陆,新浪分享等功能;最后Nginx + Spawn构建出PHP运行时环境,让Nginx完成负载均衡和图片加载,并配合PHP的访问规则,实现功能的切换。
合理的架构设计加上适应的语言的分工,就能轻松实现了 每日中国天气 这样的一个微博应用。其实,我们可以用这种多语言混搭的方式,创建出各种创新型的网站应用,但前提是先能掌握多种语言。
这里我想再多说一句,通常我认识的程序员,都是在自己的技术领域中无限畅快,一旦他们掌握了一种语言的核心技术,并有了一些开发经验后,往往不愿意再去学第二种语言。 对这些人来说,总觉得自己就是世界的中心,自己有能力实现的所有的功能。这些也都是有理想的程序员,只不过他们进入了一个误区,被现有的技术给迷住了,看不到、也不愿意看到外面的世界已经变了。我曾经就是这样的!
我承认Java是一种无所不能的编程语言,但是如果你所有程序都用Java实现,难道不觉得又费时又费力吗?通用性越强,反而专有领域的应用性就越差。这也是我从Java单一的技术路线走出来的原因。其实,在精通一门语言后,再去学习另外一门新的语言,就不是那么难了。但如果只是沉醉于已掌握的技术,很快就会被一代新人,一代新工具所超越的。
3. R语言程序现实
下面就开始介绍R语言的部分程序开发,在写代码之前,我们需要先梳理开发流程,做一下程序设计,R语言都需要实现哪些功能,用到哪些第三方R包。
我用一幅图来说明程序之间的调用关系,R语言的程序实现一共包括了6个部分,爬虫程序、本地存储,地图加载、数据可视化处理、生成静态图、生成可交互的静态图。
上图中,分别标出了每个步骤用的到R包或者功能函数,同时我们可以按照这个流程来定义功能函数,这样我们就把整个应用程序都规划好,最后再对应的写代码就不难了。
3.1 爬虫部分
对于爬虫部分来说,就是定时下载每个城市的或地区的天气数据,并解析数据,只保留我们需要的字段,并以CSV的格式存储。互联网上有很多免费公开的天气数据源,对我来说,最方便的数据源有2个,一个是Yahoo的天气数据,另一个Google的天气数据,但由于Google的API从中国大陆会经常会访问不到,所以我在这里选择Yahoo的天气数据源进行访问。
yahoo天气数据源的访问地址,如下所示。
http://weather.yahooapis.com/forecastrss?w=WOEID
其中WOEID代表城市对应的代码,如果想查看北京的天气数据,北京对应的WOEID为2151330,可以访问用浏览器访问 http://weather.yahooapis.com/forecastrss?w=2151330 。
我们通过浏览器打开地址,就可以看到这个数据,数据是以XML格式进行发布的。
我们要解析这个XML文件,从中找到我们需要数据进行提邓。在R语言中,通过RCurl包实现HTTP的网络访问,抓取到整个的XML文档数据,然后通过XML包解析XML文档的DOM树,就能找到我们需要的数据了。
本文的系统环境
- Win7 64bit
- R: 3.1.1 x86_64-w64-mingw32/x64 (64-bit)
当我们把业务逻辑和技术实现都想清楚了,就可以动手写代码了,只十几行代码就能完成爬虫和XML文档解析的功能。
> library(RCurl) # 加载类库n> library(XML)n>n> getWeather<-function (x){n+ url<-paste('http://weather.yahooapis.com/forecastrss?w=',x,'&u=c',sep="") # yahoo的数据源地址n+ doc = xmlTreeParse(getURL(url),useInternal = TRUE) # 解析XML文档n+n+ ans<-getNodeSet(doc, "//yweather:atmosphere")n+ humidity<-as.numeric(sapply(ans, xmlGetAttr, "humidity")) # 温度n+ visibility<-as.numeric(sapply(ans, xmlGetAttr, "visibility")) # 能见度n+ pressure<-as.numeric(sapply(ans, xmlGetAttr, "pressure")) # 气压n+ rising<-as.numeric(sapply(ans, xmlGetAttr, "rising")) # 气压变动n+n+ ans<-getNodeSet(doc, "//item/yweather:condition")n+ code<-sapply(ans, xmlGetAttr, "code") # 天气情况n+n+ ans<-getNodeSet(doc, "//item/yweather:forecast[1]")n+ low<-as.numeric(sapply(ans, xmlGetAttr, "low")) # 最高气温n+ high<-as.numeric(sapply(ans, xmlGetAttr, "high")) # 最低气温n+n+ print(paste(x,'==>',low,high,code,humidity,visibility,pressure,rising))n+ cbind(low,high,code,humidity,visibility,pressure,rising) # 以data.frame格式返回n+ }n
运行程序,查看返回结果。
> w<-getWeather(2151330) # 执行爬虫程序n[1] "2151330 ==> 9 13 21 59 4.1 1016.4 0"nn> w # 返回的结果集n low high code humidity visibility pressure risingn[1,] "9" "13" "21" "59" "4.1" "1016.4" "0"n
对于功能需求来说,一个城市只保存7个字段就行了,其他的XML文档的数据可以全部过滤掉不管。
3.2 本地存储
我们通过爬虫下载并过滤后的数据,已经是data.frame的格式了,通过write.csv()函数就把这些数据输出到本地文件系统中保存起来,做为数据的备份。
我们在处理本地存储的过程中,除了要生成一个CSV文件,还包括了 文件命名,把多个城市的数据合并到一个文件存储的问题。下面我们需要再定义两个函数,filename()函数用于新生成文件的命名,loadDate()函数用于多个城市数据的加载,合并在一个文件中保存。
城市列表应该是我们需要提单准备好的,我这里只选取了中国的34个城市作为我们要获得的城市天气数据的信息。如果想爬取更多的城市天气数据的信息,那么补充这个列表就行了。
城市列表数据文件WOEID.csv。
beijing,2151330,北京,北京市,116.4666667,39.9nshanghai,2151849,上海,上海市,121.4833333,31.23333333ntianjin,2159908,天津,天津市,117.1833333,39.15nchongqing,20070171,重庆,重庆市,106.5333333,29.53333333nharbin,2141166,哈尔滨,黑龙江省,126.6833333,45.75nchangchun,2137321,长春,吉林省,125.3166667,43.86666667nshenyang,2148332,沈阳,辽宁省,123.4,41.83333333nhohhot,2149760,呼和浩特,内蒙古自治区,111.8,40.81666667nshijiazhuang,2171287,石家庄,河北省,114.4666667,38.03333333nwulumuqi,26198317,乌鲁木齐,新疆维吾尔自治区,87.6,43.8nlanzhou,2145605,兰州,甘肃省,103.8166667,36.05nxining,2138941,西宁,青海省,101.75,36.63333333nxian,2157249,西安,陕西省,108.9,34.26666667nyinchuan,2150551,银川,宁夏回族自治区,106.2666667,38.33333333nzhengzhou,2172736,郑州,河南省,113.7,34.8njinan,2168327,济南,山东省,117,36.63333333ntaiyuan,2154547,太原,山西省,112.5666667,37.86666667nhefei,2127866,合肥,安徽省,117.3,31.85nwuhan,2163866,武汉,湖北省,114.35,30.61666667nchangsha,26198213,长沙,湖南省,113,28.18333333nnanjing,2137081,南京,江苏省,118.8333333,32.03333333nchengdu,2158433,成都,四川省,104.0833333,30.65nguiyang,2146703,贵阳,贵州省,106.7,26.58333333nkunming,2160693,昆明,云南省,102.6833333,25nnanning,2166473,南宁,广西壮族自治区,108.3333333,22.8nlasa,26198235,拉萨,西藏自治区,91.16666667,29.66666667nhangzhou,2132574,杭州,浙江省,120.15,30.23333333nnanchang,26198151,南昌,江西省,115.8666667,28.68333333nguangzhou,2161838,广州,广东省,113.25,23.13333333nfuzhou,2139963,福州,福建省,119.3,26.08333333ntaipei,2306179,台北,台湾省,121.5166667,25.05nhaikou,2162779,海口,海南省,110.3333333,20.03333333nhongkong,24865698,香港,香港特别行政区,114.1666667,22.3nmacau,20070017,澳门,澳门特别行政区,113.5,22.2n
字段解释:
- 第一列,城市的英文名
- 第二列,WOEID代码
- 第三列,城市的中文名
- 第四列,城市所在的省中文名
- 第五列,经度(默认为东经)
- 第六列,纬度(默认为北纬)
用于生成数据文件的R语言的函数实现。
> filename<-function(date=Sys.time()){ # 文件根据日期来命名n+ paste(format(date, "%Y%m%d"),".csv",sep="")n+ }nn> loadDate<-function(date){ # 读取城市列表,调用爬虫函数,合并数据保存到一个文件中。n+ print(paste('Date','==>',date))n+ city<-read.csv(file="WOEID.csv",header=FALSE,fileEncoding="utf-8", encoding="utf-8") # 加载城市列表n+ names(city)<-c("en","woeid","zh",'prov','long','lat')n+ city<-city[-nrow(city),]n+n+ wdata<-do.call(rbind, lapply(city$woeid,getWeather))n+ w<-cbind(city,wdata)n+ write.csv(w,file=filename(date),row.names=FALSE,fileEncoding="utf-8")n+ }n
运行程序loadDate()的函数,程序会根据城市列表的数据,调用getWeather()函数自动爬取我们定义的所有城市的天气数据。
> date=Sys.time();date # 选择日期n[1] "2014-10-01 13:01:08 CST"nn> loadDate(date) # 爬取数据n[1] "Date ==> 2014-10-01 13:01:08"n[1] "2151330 ==> 9 13 21 59 4.1 1016.4 0"n[1] "2151849 ==> 18 23 30 57 9.99 1015.92 0"n[1] "2159908 ==> 12 22 30 58 9.99 1017 0"n[1] "20070171 ==> 16 22 26 79 NA 1013.6 0"n[1] "2141166 ==> 2 13 34 29 9.99 1015.92 0"n[1] "2137321 ==> 3 6 11 81 9.99 1015.92 1"n[1] "2148332 ==> 7 16 34 27 9.99 1015.92 0"n[1] "2149760 ==> 4 19 30 59 9.99 982.05 0"n[1] "2171287 ==> 12 14 11 94 2.49 982.05 2"n[1] "26198317 ==> 12 23 34 52 9.99 1015.92 2"n[1] "2145605 ==> 6 17 20 82 8 812.73 0"n[1] "2138941 ==> 3 21 32 63 9 745.01 0"n[1] "2157249 ==> 13 23 11 91 2.99 1017.9 0"n[1] "2150551 ==> 8 22 28 60 7 1016.8 0"n[1] "2172736 ==> 13 19 32 52 8 1015.92 0"n[1] "2168327 ==> 14 22 32 49 NA 1017 0"n[1] "2154547 ==> 9 18 20 88 1.59 982.05 2"n[1] "2127866 ==> 17 23 34 60 9.99 1015.92 2"n[1] "2163866 ==> 19 26 28 78 6 982.05 2"n[1] "26198213 ==> 21 28 28 65 9.99 982.05 2"n[1] "2137081 ==> 15 23 34 57 9.99 1015.92 2"n[1] "2158433 ==> 19 27 20 69 4.01 1015.92 0"n[1] "2146703 ==> 18 26 28 73 9.99 1015.92 0"n[1] "2160693 ==> 13 23 28 64 9.99 1015.92 2"n[1] "2166473 ==> 24 32 30 62 9.99 982.05 0"n[1] "26198235 ==> -1 15 30 50 NA 643.41 0"n[1] "2132574 ==> 16 23 30 53 9.99 1015.92 0"n[1] "26198151 ==> 21 27 20 75 7 1016.4 0"n[1] "2161838 ==> 25 31 28 58 8 982.05 2"n[1] "2139963 ==> 21 29 28 65 9.99 982.05 0"n[1] "2306179 ==> 24 28 28 70 9.99 982.05 0"n[1] "2162779 ==> 24 31 30 58 9.99 982.05 0"n[1] "24865698 ==> 26 30 30 59 9.99 982.05 2"n
程序运行完成后,会在当前目录生成一个名字为20141001.csv文件。打开20141001.csv文件,这个文件就是接下来用于生成可视化图片的基础数据了。
"en","woeid","zh","prov","long","lat","low","high","code","humidity","visibility","pressure","rising"n"beijing",2151330,"北京","北京市",116.4666667,39.9,"9","13","21","59","4.1","1016.4","0"n"shanghai",2151849,"上海","上海市",121.4833333,31.23333333,"18","23","30","57","9.99","1015.92","0"n"tianjin",2159908,"天津","天津市",117.1833333,39.15,"12","22","30","58","9.99","1017","0"n"chongqing",20070171,"重庆","重庆市",106.5333333,29.53333333,"16","22","26","79",NA,"1013.6","0"n"harbin",2141166,"哈尔滨","黑龙江省",126.6833333,45.75,"2","13","34","29","9.99","1015.92","0"n"changchun",2137321,"长春","吉林省",125.3166667,43.86666667,"3","6","11","81","9.99","1015.92","1"n"shenyang",2148332,"沈阳","辽宁省",123.4,41.83333333,"7","16","34","27","9.99","1015.92","0"n"hohhot",2149760,"呼和浩特","内蒙古自治区",111.8,40.81666667,"4","19","30","59","9.99","982.05","0"n"shijiazhuang",2171287,"石家庄","河北省",114.4666667,38.03333333,"12","14","11","94","2.49","982.05","2"n"wulumuqi",26198317,"乌鲁木齐","新疆维吾尔自治区",87.6,43.8,"12","23","34","52","9.99","1015.92","2"n"lanzhou",2145605,"兰州","甘肃省",103.8166667,36.05,"6","17","20","82","8","812.73","0"n"xining",2138941,"西宁","青海省",101.75,36.63333333,"3","21","32","63","9","745.01","0"n"xian",2157249,"西安","陕西省",108.9,34.26666667,"13","23","11","91","2.99","1017.9","0"n"yinchuan",2150551,"银川","宁夏回族自治区",106.2666667,38.33333333,"8","22","28","60","7","1016.8","0"n"zhengzhou",2172736,"郑州","河南省",113.7,34.8,"13","19","32","52","8","1015.92","0"n"jinan",2168327,"济南","山东省",117,36.63333333,"14","22","32","49",NA,"1017","0"n"taiyuan",2154547,"太原","山西省",112.5666667,37.86666667,"9","18","20","88","1.59","982.05","2"n"hefei",2127866,"合肥","安徽省",117.3,31.85,"17","23","34","60","9.99","1015.92","2"n"wuhan",2163866,"武汉","湖北省",114.35,30.61666667,"19","26","28","78","6","982.05","2"n"changsha",26198213,"长沙","湖南省",113,28.18333333,"21","28","28","65","9.99","982.05","2"n"nanjing",2137081,"南京","江苏省",118.8333333,32.03333333,"15","23","34","57","9.99","1015.92","2"n"chengdu",2158433,"成都","四川省",104.0833333,30.65,"19","27","20","69","4.01","1015.92","0"n"guiyang",2146703,"贵阳","贵州省",106.7,26.58333333,"18","26","28","73","9.99","1015.92","0"n"kunming",2160693,"昆明","云南省",102.6833333,25,"13","23","28","64","9.99","1015.92","2"n"nanning",2166473,"南宁","广西壮族自治区",108.3333333,22.8,"24","32","30","62","9.99","982.05","0"n"lasa",26198235,"拉萨","西藏自治区",91.16666667,29.66666667,"-1","15","30","50",NA,"643.41","0"n"hangzhou",2132574,"杭州","浙江省",120.15,30.23333333,"16","23","30","53","9.99","1015.92","0"n"nanchang",26198151,"南昌","江西省",115.8666667,28.68333333,"21","27","20","75","7","1016.4","0"n"guangzhou",2161838,"广州","广东省",113.25,23.13333333,"25","31","28","58","8","982.05","2"n"fuzhou",2139963,"福州","福建省",119.3,26.08333333,"21","29","28","65","9.99","982.05","0"n"taipei",2306179,"台北","台湾省",121.5166667,25.05,"24","28","28","70","9.99","982.05","0"n"haikou",2162779,"海口","海南省",110.3333333,20.03333333,"24","31","30","58","9.99","982.05","0"n"hongkong",24865698,"香港","香港特别行政区",114.1666667,22.3,"26","30","30","59","9.99","982.05","2"n
数据一共有10列,字段解释:
- en,城市英文名
- woeid, Yahoo天气API定义的WOEID,用于匹配城市
- zh,城市中文名
- prov,城市所在省的中文名
- long,经度(中国处于东经,不区别东经西经)
- lat,纬度(中国处于北纬,不区别南纬北纬)
- low,最低温度
- high,最高温度
- code,天气概括代码
- humidity,湿度
- visibility,能见度
- pressure,大气压
- rising,气压变动
这样数据就准备好了,那么接下来就是把天气数据对应到中国行政区地图上了。
3.3 中国地国加载
R语言通过第三方的地图R包,可以很方便的实现基于地图的可视化或基于地理信息的数据处理。那么R语言是如何做到的呢,是通过maps, mapdata, maptools这3个包合作完成的。
我们调用maptools包的readShapePoly()函数,加载中国行政区地图的数据信息,保存在map的变量中,直接用plot()函数就可以看到可视化的效果了。地图数据是我提前下载好的,保存放在mapdata目录中,一共全部3个文件bou2_4p.dbf,bou2_4p.shp和bou2_4p.shx。
> library(maps)n> library(mapdata)n> library(maptools)nn> map<-readShapePoly('mapdata/bou2_4p.shp') # 加载中国行政区地图数据n> plot(map) # 画出中国行政区图
是不是很神奇,2行就画出是中国行政区地图的轮廓,我们再继续来分析map这个变量。先检查一下的map的类型,发现是sp包中定义的SpatialPolygonsDataFrame类型的。
> class(map) # 查看map对象类型n[1] "SpatialPolygonsDataFrame"nattr(,"package")n[1] "sp"n
SpatialPolygonsDataFrame类型我们并不熟悉,再用pryr包的otype查检一下,面向对象系统的类型。
> library(pryr)n> otype(map) # 发现是S4类型的data.framen[1] "S4"n
R语言基于S4的面向对象编程一文,我们已经掌握了S4类型的基础知识,在知道map是一个S4类型的实例后,大概就能猜出这个对象如何使用了。另外从命名上看,SpatialPolygonsDataFrame类型,应该是用data.frame存储了SpatialPolygons的类型的数据。 先通过length()函数和names()函数,从data.frame的角度查看一下map对象,包括7列925行。
> length(map) # 一共有925条记录n[1] 925nn> names(map) # data.frame包括有7列n[1] "AREA" "PERIMETER" "BOU2_4M_" "BOU2_4M_ID" "ADCODE93"n[6] "ADCODE99" "NAME"n
再通过str()函数查看map对象第一行数据的静态结构。
> str(map[1,])nFormal class 'SpatialPolygonsDataFrame' [package "sp"] with 5 slotsn ..@ data :'data.frame': 1 obs. of 1 variable:n .. ..$ AREA: num 54.4n ..@ polygons :List of 1n .. ..$ :Formal class 'Polygons' [package "sp"] with 5 slotsn .. .. .. ..@ Polygons :List of 1n .. .. .. .. ..$ :Formal class 'Polygon' [package "sp"] with 5 slotsn .. .. .. .. .. .. ..@ labpt : num [1:2] 127.8 47.9n .. .. .. .. .. .. ..@ area : num 54.4n .. .. .. .. .. .. ..@ hole : logi FALSEn .. .. .. .. .. .. ..@ ringDir: int 1n .. .. .. .. .. .. ..@ coords : num [1:5784, 1:2] 121 121 122 122 122 ...n .. .. .. ..@ plotOrder: int 1n .. .. .. ..@ labpt : num [1:2] 127.8 47.9n .. .. .. ..@ ID : chr "0"n .. .. .. ..@ area : num 54.4n ..@ plotOrder : int 1n ..@ bbox : num [1:2, 1:2] 121.2 43.4 135.1 53.6n .. ..- attr(*, "dimnames")=List of 2n .. .. ..$ : chr [1:2] "x" "y"n .. .. ..$ : chr [1:2] "min" "max"n ..@ proj4string:Formal class 'CRS' [package "sp"] with 1 slotsn .. .. ..@ projargs: chr NAn
从这两个维度的观察,我们基本清楚map的结构,map里每一行是一个SpatialPolygonsDataFrame对象,包括5个属性,用于存储地图数据信息。取第一行数据data属性,查看结果,发现是黑龙江省的行政区地图数据。
> map[1,]@datan AREA PERIMETER BOU2_4M_ BOU2_4M_ID ADCODE93 ADCODE99 NAMEn0 54.447 68.489 2 23 230000 230000 黑龙江省n
用第一行数据画图。
> plot(map[1,])
如果取前100行数据画图,那么应该是部分中国省的行政区地图了,果然如我所料。
> plot(map[1:100,])
由于本文并不是地图包的详细介绍,只要了解到map对象的基本使用就行了,稍后在博客中我会单独介绍用R做地图可视化的开发。
3.4 数据可视化
完成了地图数据加载后,再接下来就是数据可视化了。数据可视化,我认为要分成2部分操作,一部分是数据处理,另一部分是可视化输出。
我们先想一下要怎么进行数据处理,才能把天气数据和地图数据结合起来呢。我们的目标是要画出中国各省天气概况,会用到过之前过滤出的数据中code的数据,code的数据都是代码,我们还要定义code代码和实际意义的映射关系。
Yahoo的源数据中,一共定义了49种天气情况,如code.csv文件所示,根据描述我把相似的天气情况进行合并,最后保留18种天气概况特征。code代码映射文件为lablecode.csv。
code.csv文件。
"code","en","zh","type"n0,tornado,龙卷风,3n1,tropical storm,热带风暴,2n2,hurricane,暴风,3n3,severe thunderstorms,强雷雨天气,16n4,thunderstorms,雷雨,11n5,mixed rain and snow,雨雪,12n6,mixed rain and sleet,雨雪,12n7,mixed snow and sleet,雨雪,12n8,freezing drizzle,冻毛毛雨,11n9,drizzle,毛毛雨,11n10,freezing rain,冻雨,11n11,showers,阵雨,11n12,showers,阵雨,11n13,snow flurries,小雪,13n14,light snow showers,阵雪,13n15,blowing snow,飞雪,13n16,snow,雪,14n17,hail,冰雹,15n18,sleet,雨雪,12n19,dust,灰尘,5n20,foggy,雾,7n21,haze,薄雾,7n22,smoky,烟雾弥漫,6n23,blustery,大风,3n24,windy,风,4n25,cold,冷,18n26,cloudy,多云,8n27,mostly cloudy (night),满云密布,8n28,mostly cloudy (day),满云密布,8n29,partly cloudy (night),少云,9n30,partly cloudy (day),少云,9n31,clear (night),晴,10n32,sunny,睛,10n33,fair (night),晴,10n34,fair (day),晴,10n35,mixed rain and hail,大雨和冰雹,16n36,hot,热,1n37,isolated thunderstorms,局部雷雨,11n38,scattered thunderstorms,零星雷雨,11n39,scattered thunderstorms,零星雷雨,11n40,scattered showers,零星阵雨,11n41,heavy snow,大雪,14n42,scattered snow showers,零星阵雪,13n43,heavy snow,大雪,14n44,partly cloudy,少云,9n45,thundershowers,雷阵雨,11n46,snow showers,阵雪,13n47,isolated thundershowers,局部雷雨,11n3200,not available,无数据,19n
字段解释:
- code,源数据天气特征代码
- en,英文描述
- zh,中文描述
- type,分类代码
lablecode.csv文件。
"type","alias"n1,热n2,风暴n3,大风n4,微风n5,灰尘n6,大雾n7,薄雾n8,多云n9,少云n10,晴n11,阵雨n12,雨加雪n13,小雪n14,大雪n15,冰雹n16,大雨n17,雷暴雨n18,冷n19,无数据n
字段解释:
- type,分类代码
- alias,用于显示的别名
有了天气特征定义后,我们再把特征匹配到不同的颜色,并增加图例及文字描述,就生成了最终的中国各省天气概况的静态图片了。
> library("RColorBrewer")n> getColors2<-function(map,prov,ctype){n+ #name change to ADCODE99n+ ADCODE99<-read.csv(file="ADCODE99.csv",header=TRUE,fileEncoding="utf-8", encoding="utf-8")n+ fc<-function(x){ADCODE99$ADCODE99[which(x==ADCODE99$prov)]}n+ code<-sapply(prov,fc)n+ n+ f=function(x,y) ifelse(x %in% y,which(y==x),0);n+ colIndex=sapply(map$ADCODE99,f,code);n+ ctype[which(is.na(ctype))]=19n+ return(ctype[colIndex])n+ }n> summary<-function(data=data,output=FALSE,path=''){n+ colors<-c(rev(brewer.pal(9,"Blues")),rev(c('#b80137','#8c0287','#d93c5d','#d98698','#f6b400','#c4c4a7','#d6d6cb','#d1b747','#ffeda0'))) # 定义18种天气特征对应的颜色n+n+ temp<-data$coden+ title<-"中国各省天气概况"n+ ofile<-paste(format(date,"%Y%m%d"),"_code.png",sep="")n+ sign<-''n+ colors<-rev(colors)n+ code<-read.csv(file="code.csv",header=TRUE,fileEncoding="utf-8", encoding="utf-8")n+ labelcode<-read.csv(file="labelcode.csv",header=TRUE,fileEncoding="utf-8", encoding="utf-8")n+ ctype<-sapply(temp,function(x){code$type[which(x==code$code)]})n+n+ if(output)png(file=paste(path,ofile,sep=''),width=600,height=600)n+ layout(matrix(data=c(1,2),nrow=1,ncol=2),widths=c(8,1),heights=c(1,2))n+ par(mar=c(0,0,3,12),oma=c(0.2,0.2,0.2,0.2),mex=0.3)n+ plot(map,border="white",col=colors[getColors2(map,data$prov,ctype)]) # 地图和天气可视化n+ points(data$long,data$lat,pch=19,col=rgb(0,0,0,0.3),cex=0.8) # 标出采样城市n+n+ #======================================= # 图片中的辅助文字n+ if(FALSE){n+ grid()n+ axis(1,lwd=0);axis(2,lwd=0);axis(3,lwd=0);axis(4,lwd=0)n+ }n+ text(100,58, title,cex=2)n+ text(105,54,format(date,"%Y-%m-%d"))n+ text(98,65,paste('每日中国天气','http://apps.weibo.com/chinaweatherapp'))n+ text(120,-8,paste('provided by The Weather Channel',format(date, "%Y-%m-%d %H:%M")),cex=0.8)n+n+ #======================================= # 文字说明n+ for(row in 1:nrow(data)){n+ name<-as.character(data$zh[row])n+ label<-labelcode$alias[labelcode$type==ctype[row]]n+ x1<-ceiling(row/7)n+ x2<-ifelse(row%%7==0,7,row%%7)n+ x3<-ctype[row]n+ fontCol<-'#000000'n+ if(x3<=5)fontCol<-head(colors,1)n+ if(x3>=12)fontCol<-tail(colors,1)n+ text(68+x1*11,17-x2*3,paste(name,' ',label,sign,sep=''),col=fontCol)n+ }n+n+ #======================================= # 图例n+ par(mar = c(5, 0, 15, 10))n+ image(x=1, y=1:length(colors),z=t(matrix(1:length(colors))),col=rev(colors),axes=FALSE,xlab="",ylab="",xaxt="n")n+ axis(4, at = 1:(nrow(labelcode)-1), labels=rev(labelcode$alias)[-1], col = "white", las = 1)n+ abline(h=c(1:(nrow(labelcode)-2)+0.5), col = "white", lwd = 2, xpd = FALSE)n+ if(output)dev.off()n+ }n
运行程序,生成静态图片。
> data<-read.csv(file=filename(date),header=TRUE,fileEncoding="utf-8", encoding="utf-8") # 定义数据源n> path='' # 定义输出路径n> summary(data,output=TRUE,path=path) # 生成中国各省天气概况图nRStudioGDn2
代码量大概100行左右,就可以生成这么复杂的天气和地图结合的图片,R真的很神奇!
3.5 可交互的静态图
这是锦上添花的一步,静态图片对于一般应用来说就够了。但如果图片还能动起来,是不是会更吸引人呢?我们可以尝试生成基于HTML5的、有动态效果的图,通过recharts包调Echarts库实现基于HTML5的动画,生成会动的可交互的图片。
由于recharts包没有发布的CRAN,我们需要用devtools包通过Github安装这个包。
> library(devtools) # 加载devtoolsn> install_github("taiyun/recharts") # 下载安装recharts包n> library(recharts) # 加载recharts包n
由于上面的天气概况是由离散值组成的,利用echarts的库,我们做一个连续值的可视化例子,比如白天气温和夜间气温。定义weather_html()函数,提供气温数据并调用recharts包,实现可视化的输出。
> weather_html<-function(data=data,type='high',output=FALSE,path=''){ # 输入HTML的天气图n+ if(type=='high') { # 白天气温n+ df<-data[,c('prov','high')]n+ names(df)<-c("prov","气温")n+ title<-paste(format(date,"%Y-%m-%d"),"中国各省白天气温",sep="")n+ ofile<-paste(format(date,"%Y%m%d"),"_day.html",sep="")n+ }else if(type=='low'){ # 夜间气温n+ df<-data[,c('prov','low')]n+ names(df)<-c("prov","气温")n+ title<-paste(format(date,"%Y-%m-%d"),"中国各省夜间气温",sep="")n+ ofile<-paste(format(date,"%Y%m%d"),"_night.html",sep="")n+ }n+n+ df[,1]<-substr(df[,1],0,2) # 数据格式整理n+ df[which(df$prov=='黑龙'),]$prov<-'黑龙江'n+ df[which(df$prov=='内蒙'),]$prov<-'内蒙古'n+n+ recharts.eMap <- eMap(df, namevar=1, datavar = 2, title=title) # 数据JSON化处理n+ if(output){ # 输出HTML文件n+ recharts.eMap$outList[c('chartid','type')]<-NULLn+ writeLines(unlist(recharts.eMap$outList),paste(path,ofile,sep=''))n+ }else{ # 在浏览器中打开HTML网页n+ plot(recharts.eMap)n+ }n+ }n
运行程序,以HTML输出中国各省白天气温。
> date<-as.Date('20141001',format='%Y%m%d') # 设置日期n> data<-read.csv(file=filename(date),header=TRUE,fileEncoding="utf-8", encoding="utf-8") # 加载数据n> path='' # 设置文件输出路径nn> weather_html(data,type='high',output=FALSE,path='') # 输出中国各省白天气温n[1] "气温"n[1] "chart path C:UsersADMINI~1AppDataLocalTempRtmpqCHFPY"n
程序会自动打开浏览器,呈现HTML的网页。
运行程序,以HTML输出中国各省夜间气温。在网页中,通过鼠标对地图进行交互,移动左下角的温度条,选择最高温度30,最低温度8.8,中国地图中由西南到东北变为灰色,说明这些地区的温度不在8.8到30度之间。当鼠标路过海南省的时候,海南省呈现黄色,并提示出温度为23度。
> weather_html(data,type='low', output=FALSE,path='') # 中国各省夜间气温n[1] "气温"n[1] "chart path C:UsersADMINI~1AppDataLocalTempRtmpqCHFPY"
如果不需要在浏览器中打开,只能想存储生成的网页,可以在程序中设置output为TRUE,当前目录下会生成20141001_night.html的文件。
> weather_html(data,type='low',output=TRUE,path='')n[1] "气温"
作者介绍:
张丹,R语言中文社区专栏特邀作者,《R的极客理想》系列图书作者,民生银行大数据中心数据分析师,前况客创始人兼CTO。
10年IT编程背景,精通R ,Java, Nodejs 编程,获得10项SUN及IBM技术认证。丰富的互联网应用开发架构经验,金融大数据专家。个人博客 粉丝日志, Alexa全球排名70k。
著有《R的极客理想-工具篇》、《R的极客理想-高级开发篇》,合著《数据实践之美》,新书《R的极客理想-量化投资篇》(即将出版)。
《R的极客理想-工具篇》京东购买快速通道:《数据分析技术丛书:R的极客理想·工具篇》(张丹 )【摘要 书评 试读】- 京东图书
《R的极客理想-高级开发篇》京东购买快速通道:《R的极客理想 高级开发篇》(张丹)【摘要 书评 试读】- 京东图书
《数据实践之美》京东购买快速通道:《数据实践之美:31位大数据专家的方法、技术与思想》(天善智能)【摘要 书评 试读】- 京东图书
大家也可以加小编微信:tswenqu(备注:知乎),进R语言中文社区 交流群,可以跟各位老师互相交流
官方公众号:R语言中文社区 (ID:R_shequ) 欢迎关注,持续连载。