• Posts tagged "app"

Blog Archives

R语言天气可视化应用

R的极客理想系列文章,涵盖了R的思想,使用,工具,创新等的一系列要点,以我个人的学习和体验去诠释R的强大。

R语言作为统计学一门语言,一直在小众领域闪耀着光芒。直到大数据的爆发,R语言变成了一门炙手可热的数据分析的利器。随着越来越多的工程背景的人的加入,R语言的社区在迅速扩大成长。现在已不仅仅是统计领域,教育,银行,电商,互联网….都在使用R语言。

要成为有理想的极客,我们不能停留在语法上,要掌握牢固的数学,概率,统计知识,同时还要有创新精神,把R语言发挥到各个领域。让我们一起动起来吧,开始R的极客理想。

关于作者:

  • 张丹(Conan), 程序员Java,R,PHP,Javascript
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/r-app-china-weather/

r-app-chinaweather

前言

在很多人看来,R语言还只是个玩具,完全不具备企业级应用的能力。说这些话的人,根本就不了解R语言,更不清楚如何做企业级应用开发。从我最早接触R语言时,就把R做为可视化引擎嵌入到了晒粉丝的微博应用中;后来又开发了数据挖掘算法竞赛网站,并把R语言做为算法引擎,并支持在线编程及运行;我做的第三个R语言应用就是本文要给大家分享的每日中国天气微博应用,这次同样是把R做为可视化引擎,并让R完成爬虫、XML文档解析及数据处理等的任务;当然,我还实现了第四个、第五个、第六个以R为核心的应用,都是量化投资方面的,会在下一本书《R的极客理想-量化投资篇》再介绍给大家。

从我的使用经验来看,R语言已经具备了企业级应用的能力,但我并不是要用R语言完成所有编程任务。在我的项目环境中,大都是多种编程语言配合使用的,只有发挥各自语言的特性优势,才是未来的发展方向。

本文所介绍的每日中国天气微博应用开发,将分为3篇介绍R语言和PHP语言的混合编程,第一篇为R语言功能实现,第二篇为R包开发,第三篇为用PHP构建微博应用。本文是第一篇。

目录

  1. 项目介绍
  2. 系统架构设计
  3. R语言程序现实

1. 项目介绍

谈到多语言混编,同如在计算机领域跨学科一样,是我所一直倡导一种工作模式。当编程语言百花齐放,各种细分市场的小众语言如雨后春笋般地成长起来,比起通用型编程语言来说,这些小众语言在特定的领域中有着非常明显的优势。 比如统计应用,如果用Java写个逻辑回归程序感觉深不见底,而用R语言实现逻辑回归就是个很平常的一件事情。 再比如做一个Web网站,用PHP或Nodejs实现轻而易举,如果用Java做不仅代码量大,而且程序复杂。 所以,对于一个应用来说,一种通用的语言并不一定是最好的解决方案,如果能实现多种语言的结合,那么你做出来的应用可以很酷,很不一样!

对于本文要介绍的 每日中国天气 这个新浪微博应用,就是一种多语言混编的实现。

项目介绍

这个项目的出发点很简单,就是通过可视化技术,展示中国每个省份的天气情况,给准备旅游的朋友,提供一种出行的提示。

要做实现这个应用,我们首先要列出,要实现哪些功能,会遇到哪些问题等。

  • 天气数据:数据从哪里找到,如何下载,如何存储。
  • 定时任务:天气数据需要每日更新,图片需要每日新生成。
  • 地图和天气可视化:要把中国行政区图和天气数据结合在一起画图,让用户一眼就能看明白。
  • Web展示:通过可视化技术,我们生成的只是一张静态图片,如何发布到Web端进行展示。
  • 微博:通过结合新浪微博,让更多的用户看到并使用这个应用。
  • 用户交互:用户可以查看不同日期、不同类型的图片,用户还可以通过微博分享。
  • 虽然是个很小的应用,但五脏俱全,我们也需要完整的思考,如何才能实现这个应用呢!

2. 系统架构设计

从上面的功能描述中,单独使用一种语言也可以实现的。 如果单独用PHP开发,做一个Web网站非常容易,连接新浪微博也有现成的SDK可以调用,爬取数据及存储也不麻烦,那么如何实现地图和天气数据的可视化,似乎就是卡在这里了。 如果单独用R开发,爬取数据及存储同样很容易实现,地图和天气数据的可视化也是很方便就能画出来,但是用R做Web网站,那就会遇到很大的瓶颈了,因为R是单线程同步的计算模型,Web应用的高并发特点,会直接让R程序崩溃的。 所以,综合上面的问题,如果R语言和PHP语言能结合在一起使用,不仅能避开每种语言不擅长的地方,还能在擅长的领域发挥出每种语言的特性,我们将通过多语言的混编技术做出很不一样的应用来。

为了实现应用的功能需求,我们要设计一套系统架构。

w1

系统架构解释:

  • 通过定时器启动爬虫程序,到Yahoo的天气数据源下载数据。
  • 爬虫下载数据到本地服务器进行解析,存储应用相关的数据到CSV文件。
  • 可视化程序,读入天气数据及地图数据,生成静态的图片作为可视化输出。
  • 最终用户通过新浪微博,加载Web应用,看到了可视化生成的静态图片。
  • 最终用户通过新浪微博分享了这个应用,让更多的人看到这个应用。

下面按照语言的优势,把应用架构以语言的特性来划分,让R语言实现爬虫、处理数据和可视化,让PHP完成Web开发、新浪API接入和用户交互。

w2

由于我们这个应用,不需要让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个部分,爬虫程序、本地存储,地图加载、数据可视化处理、生成静态图、生成可交互的静态图。

w3

上图中,分别标出了每个步骤用的到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格式进行发布的。

w_1

我们要解析这个XML文件,从中找到我们需要数据进行提邓。在R语言中,通过RCurl包实现HTTP的网络访问,抓取到整个的XML文档数据,然后通过XML包解析XML文档的DOM树,就能找到我们需要的数据了。

本文的系统环境

  • Win7 64bit
  • R: 3.1.1 x86_64-w64-mingw32/x64 (64-bit)

当我们把业务逻辑和技术实现都想清楚了,就可以动手写代码了,只十几行代码就能完成爬虫和XML文档解析的功能。


> library(RCurl)        # 加载类库
> library(XML)
>
> getWeather<-function (x){
+     url<-paste('http://weather.yahooapis.com/forecastrss?w=',x,'&u=c',sep="")       # yahoo的数据源地址
+     doc = xmlTreeParse(getURL(url),useInternal = TRUE)                              # 解析XML文档
+
+     ans<-getNodeSet(doc, "//yweather:atmosphere")
+     humidity<-as.numeric(sapply(ans, xmlGetAttr, "humidity"))                       # 温度
+     visibility<-as.numeric(sapply(ans, xmlGetAttr, "visibility"))                   # 能见度
+     pressure<-as.numeric(sapply(ans, xmlGetAttr, "pressure"))                       # 气压
+     rising<-as.numeric(sapply(ans, xmlGetAttr, "rising"))                           # 气压变动
+
+     ans<-getNodeSet(doc, "//item/yweather:condition")
+     code<-sapply(ans, xmlGetAttr, "code")                                           # 天气情况
+
+     ans<-getNodeSet(doc, "//item/yweather:forecast[1]")
+     low<-as.numeric(sapply(ans, xmlGetAttr, "low"))                                 # 最高气温
+     high<-as.numeric(sapply(ans, xmlGetAttr, "high"))                               # 最低气温
+
+     print(paste(x,'==>',low,high,code,humidity,visibility,pressure,rising))
+     cbind(low,high,code,humidity,visibility,pressure,rising)                        # 以data.frame格式返回
+ }

运行程序,查看返回结果。


> w<-getWeather(2151330)    # 执行爬虫程序
[1] "2151330 ==> 9 13 21 59 4.1 1016.4 0"

> w                         # 返回的结果集
     low high code humidity visibility pressure rising
[1,] "9" "13" "21" "59"     "4.1"      "1016.4" "0"

对于功能需求来说,一个城市只保存7个字段就行了,其他的XML文档的数据可以全部过滤掉不管。

3.2 本地存储

我们通过爬虫下载并过滤后的数据,已经是data.frame的格式了,通过write.csv()函数就把这些数据输出到本地文件系统中保存起来,做为数据的备份。

我们在处理本地存储的过程中,除了要生成一个CSV文件,还包括了 文件命名,把多个城市的数据合并到一个文件存储的问题。下面我们需要再定义两个函数,filename()函数用于新生成文件的命名,loadDate()函数用于多个城市数据的加载,合并在一个文件中保存。

城市列表应该是我们需要提单准备好的,我这里只选取了中国的34个城市作为我们要获得的城市天气数据的信息。如果想爬取更多的城市天气数据的信息,那么补充这个列表就行了。

城市列表数据文件WOEID.csv。


beijing,2151330,北京,北京市,116.4666667,39.9
shanghai,2151849,上海,上海市,121.4833333,31.23333333
tianjin,2159908,天津,天津市,117.1833333,39.15
chongqing,20070171,重庆,重庆市,106.5333333,29.53333333
harbin,2141166,哈尔滨,黑龙江省,126.6833333,45.75
changchun,2137321,长春,吉林省,125.3166667,43.86666667
shenyang,2148332,沈阳,辽宁省,123.4,41.83333333
hohhot,2149760,呼和浩特,内蒙古自治区,111.8,40.81666667
shijiazhuang,2171287,石家庄,河北省,114.4666667,38.03333333
wulumuqi,26198317,乌鲁木齐,新疆维吾尔自治区,87.6,43.8
lanzhou,2145605,兰州,甘肃省,103.8166667,36.05
xining,2138941,西宁,青海省,101.75,36.63333333
xian,2157249,西安,陕西省,108.9,34.26666667
yinchuan,2150551,银川,宁夏回族自治区,106.2666667,38.33333333
zhengzhou,2172736,郑州,河南省,113.7,34.8
jinan,2168327,济南,山东省,117,36.63333333
taiyuan,2154547,太原,山西省,112.5666667,37.86666667
hefei,2127866,合肥,安徽省,117.3,31.85
wuhan,2163866,武汉,湖北省,114.35,30.61666667
changsha,26198213,长沙,湖南省,113,28.18333333
nanjing,2137081,南京,江苏省,118.8333333,32.03333333
chengdu,2158433,成都,四川省,104.0833333,30.65
guiyang,2146703,贵阳,贵州省,106.7,26.58333333
kunming,2160693,昆明,云南省,102.6833333,25
nanning,2166473,南宁,广西壮族自治区,108.3333333,22.8
lasa,26198235,拉萨,西藏自治区,91.16666667,29.66666667
hangzhou,2132574,杭州,浙江省,120.15,30.23333333
nanchang,26198151,南昌,江西省,115.8666667,28.68333333
guangzhou,2161838,广州,广东省,113.25,23.13333333
fuzhou,2139963,福州,福建省,119.3,26.08333333
taipei,2306179,台北,台湾省,121.5166667,25.05
haikou,2162779,海口,海南省,110.3333333,20.03333333
hongkong,24865698,香港,香港特别行政区,114.1666667,22.3
macau,20070017,澳门,澳门特别行政区,113.5,22.2

字段解释:

  • 第一列,城市的英文名
  • 第二列,WOEID代码
  • 第三列,城市的中文名
  • 第四列,城市所在的省中文名
  • 第五列,经度(默认为东经)
  • 第六列,纬度(默认为北纬)
    • 用于生成数据文件的R语言的函数实现。

      
      > filename<-function(date=Sys.time()){            # 文件根据日期来命名
      +     paste(format(date, "%Y%m%d"),".csv",sep="")
      + }
      
      > loadDate<-function(date){                       # 读取城市列表,调用爬虫函数,合并数据保存到一个文件中。
      +     print(paste('Date','==>',date))
      +     city<-read.csv(file="WOEID.csv",header=FALSE,fileEncoding="utf-8", encoding="utf-8")  # 加载城市列表
      +     names(city)<-c("en","woeid","zh",'prov','long','lat')
      +     city<-city[-nrow(city),]
      +
      +     wdata<-do.call(rbind, lapply(city$woeid,getWeather))
      +     w<-cbind(city,wdata)
      +     write.csv(w,file=filename(date),row.names=FALSE,fileEncoding="utf-8")
      + }
      

      运行程序loadDate()的函数,程序会根据城市列表的数据,调用getWeather()函数自动爬取我们定义的所有城市的天气数据。

      
      > date=Sys.time();date              # 选择日期
      [1] "2014-10-01 13:01:08 CST"
      
      > loadDate(date)                    # 爬取数据
      [1] "Date ==> 2014-10-01 13:01:08"
      [1] "2151330 ==> 9 13 21 59 4.1 1016.4 0"
      [1] "2151849 ==> 18 23 30 57 9.99 1015.92 0"
      [1] "2159908 ==> 12 22 30 58 9.99 1017 0"
      [1] "20070171 ==> 16 22 26 79 NA 1013.6 0"
      [1] "2141166 ==> 2 13 34 29 9.99 1015.92 0"
      [1] "2137321 ==> 3 6 11 81 9.99 1015.92 1"
      [1] "2148332 ==> 7 16 34 27 9.99 1015.92 0"
      [1] "2149760 ==> 4 19 30 59 9.99 982.05 0"
      [1] "2171287 ==> 12 14 11 94 2.49 982.05 2"
      [1] "26198317 ==> 12 23 34 52 9.99 1015.92 2"
      [1] "2145605 ==> 6 17 20 82 8 812.73 0"
      [1] "2138941 ==> 3 21 32 63 9 745.01 0"
      [1] "2157249 ==> 13 23 11 91 2.99 1017.9 0"
      [1] "2150551 ==> 8 22 28 60 7 1016.8 0"
      [1] "2172736 ==> 13 19 32 52 8 1015.92 0"
      [1] "2168327 ==> 14 22 32 49 NA 1017 0"
      [1] "2154547 ==> 9 18 20 88 1.59 982.05 2"
      [1] "2127866 ==> 17 23 34 60 9.99 1015.92 2"
      [1] "2163866 ==> 19 26 28 78 6 982.05 2"
      [1] "26198213 ==> 21 28 28 65 9.99 982.05 2"
      [1] "2137081 ==> 15 23 34 57 9.99 1015.92 2"
      [1] "2158433 ==> 19 27 20 69 4.01 1015.92 0"
      [1] "2146703 ==> 18 26 28 73 9.99 1015.92 0"
      [1] "2160693 ==> 13 23 28 64 9.99 1015.92 2"
      [1] "2166473 ==> 24 32 30 62 9.99 982.05 0"
      [1] "26198235 ==> -1 15 30 50 NA 643.41 0"
      [1] "2132574 ==> 16 23 30 53 9.99 1015.92 0"
      [1] "26198151 ==> 21 27 20 75 7 1016.4 0"
      [1] "2161838 ==> 25 31 28 58 8 982.05 2"
      [1] "2139963 ==> 21 29 28 65 9.99 982.05 0"
      [1] "2306179 ==> 24 28 28 70 9.99 982.05 0"
      [1] "2162779 ==> 24 31 30 58 9.99 982.05 0"
      [1] "24865698 ==> 26 30 30 59 9.99 982.05 2"
      

      程序运行完成后,会在当前目录生成一个名字为20141001.csv文件。打开20141001.csv文件,这个文件就是接下来用于生成可视化图片的基础数据了。

      
      "en","woeid","zh","prov","long","lat","low","high","code","humidity","visibility","pressure","rising"
      "beijing",2151330,"北京","北京市",116.4666667,39.9,"9","13","21","59","4.1","1016.4","0"
      "shanghai",2151849,"上海","上海市",121.4833333,31.23333333,"18","23","30","57","9.99","1015.92","0"
      "tianjin",2159908,"天津","天津市",117.1833333,39.15,"12","22","30","58","9.99","1017","0"
      "chongqing",20070171,"重庆","重庆市",106.5333333,29.53333333,"16","22","26","79",NA,"1013.6","0"
      "harbin",2141166,"哈尔滨","黑龙江省",126.6833333,45.75,"2","13","34","29","9.99","1015.92","0"
      "changchun",2137321,"长春","吉林省",125.3166667,43.86666667,"3","6","11","81","9.99","1015.92","1"
      "shenyang",2148332,"沈阳","辽宁省",123.4,41.83333333,"7","16","34","27","9.99","1015.92","0"
      "hohhot",2149760,"呼和浩特","内蒙古自治区",111.8,40.81666667,"4","19","30","59","9.99","982.05","0"
      "shijiazhuang",2171287,"石家庄","河北省",114.4666667,38.03333333,"12","14","11","94","2.49","982.05","2"
      "wulumuqi",26198317,"乌鲁木齐","新疆维吾尔自治区",87.6,43.8,"12","23","34","52","9.99","1015.92","2"
      "lanzhou",2145605,"兰州","甘肃省",103.8166667,36.05,"6","17","20","82","8","812.73","0"
      "xining",2138941,"西宁","青海省",101.75,36.63333333,"3","21","32","63","9","745.01","0"
      "xian",2157249,"西安","陕西省",108.9,34.26666667,"13","23","11","91","2.99","1017.9","0"
      "yinchuan",2150551,"银川","宁夏回族自治区",106.2666667,38.33333333,"8","22","28","60","7","1016.8","0"
      "zhengzhou",2172736,"郑州","河南省",113.7,34.8,"13","19","32","52","8","1015.92","0"
      "jinan",2168327,"济南","山东省",117,36.63333333,"14","22","32","49",NA,"1017","0"
      "taiyuan",2154547,"太原","山西省",112.5666667,37.86666667,"9","18","20","88","1.59","982.05","2"
      "hefei",2127866,"合肥","安徽省",117.3,31.85,"17","23","34","60","9.99","1015.92","2"
      "wuhan",2163866,"武汉","湖北省",114.35,30.61666667,"19","26","28","78","6","982.05","2"
      "changsha",26198213,"长沙","湖南省",113,28.18333333,"21","28","28","65","9.99","982.05","2"
      "nanjing",2137081,"南京","江苏省",118.8333333,32.03333333,"15","23","34","57","9.99","1015.92","2"
      "chengdu",2158433,"成都","四川省",104.0833333,30.65,"19","27","20","69","4.01","1015.92","0"
      "guiyang",2146703,"贵阳","贵州省",106.7,26.58333333,"18","26","28","73","9.99","1015.92","0"
      "kunming",2160693,"昆明","云南省",102.6833333,25,"13","23","28","64","9.99","1015.92","2"
      "nanning",2166473,"南宁","广西壮族自治区",108.3333333,22.8,"24","32","30","62","9.99","982.05","0"
      "lasa",26198235,"拉萨","西藏自治区",91.16666667,29.66666667,"-1","15","30","50",NA,"643.41","0"
      "hangzhou",2132574,"杭州","浙江省",120.15,30.23333333,"16","23","30","53","9.99","1015.92","0"
      "nanchang",26198151,"南昌","江西省",115.8666667,28.68333333,"21","27","20","75","7","1016.4","0"
      "guangzhou",2161838,"广州","广东省",113.25,23.13333333,"25","31","28","58","8","982.05","2"
      "fuzhou",2139963,"福州","福建省",119.3,26.08333333,"21","29","28","65","9.99","982.05","0"
      "taipei",2306179,"台北","台湾省",121.5166667,25.05,"24","28","28","70","9.99","982.05","0"
      "haikou",2162779,"海口","海南省",110.3333333,20.03333333,"24","31","30","58","9.99","982.05","0"
      "hongkong",24865698,"香港","香港特别行政区",114.1666667,22.3,"26","30","30","59","9.99","982.05","2"
      

      数据一共有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)
      > library(mapdata)
      > library(maptools)
      
      > map<-readShapePoly('mapdata/bou2_4p.shp')     # 加载中国行政区地图数据
      > plot(map)                                     # 画出中国行政区图
      

      m1

      是不是很神奇,2行就画出是中国行政区地图的轮廓,我们再继续来分析map这个变量。先检查一下的map的类型,发现是sp包中定义的SpatialPolygonsDataFrame类型的。

      
      > class(map)                                    # 查看map对象类型
      [1] "SpatialPolygonsDataFrame"
      attr(,"package")
      [1] "sp"
      

      SpatialPolygonsDataFrame类型我们并不熟悉,再用pryr包的otype查检一下,面向对象系统的类型。

      
      > library(pryr)
      > otype(map)        # 发现是S4类型的data.frame
      [1] "S4"
      

      R语言基于S4的面向对象编程一文,我们已经掌握了S4类型的基础知识,在知道map是一个S4类型的实例后,大概就能猜出这个对象如何使用了。另外从命名上看,SpatialPolygonsDataFrame类型,应该是用data.frame存储了SpatialPolygons的类型的数据。 先通过length()函数和names()函数,从data.frame的角度查看一下map对象,包括7列925行。

      
      > length(map)       # 一共有925条记录
      [1] 925
      
      > names(map)        # data.frame包括有7列
      [1] "AREA"       "PERIMETER"  "BOU2_4M_"   "BOU2_4M_ID" "ADCODE93"
      [6] "ADCODE99"   "NAME"
      

      再通过str()函数查看map对象第一行数据的静态结构。

      
      > str(map[1,])
      Formal class 'SpatialPolygonsDataFrame' [package "sp"] with 5 slots
        ..@ data       :'data.frame': 1 obs. of  1 variable:
        .. ..$ AREA: num 54.4
        ..@ polygons   :List of 1
        .. ..$ :Formal class 'Polygons' [package "sp"] with 5 slots
        .. .. .. ..@ Polygons :List of 1
        .. .. .. .. ..$ :Formal class 'Polygon' [package "sp"] with 5 slots
        .. .. .. .. .. .. ..@ labpt  : num [1:2] 127.8 47.9
        .. .. .. .. .. .. ..@ area   : num 54.4
        .. .. .. .. .. .. ..@ hole   : logi FALSE
        .. .. .. .. .. .. ..@ ringDir: int 1
        .. .. .. .. .. .. ..@ coords : num [1:5784, 1:2] 121 121 122 122 122 ...
        .. .. .. ..@ plotOrder: int 1
        .. .. .. ..@ labpt    : num [1:2] 127.8 47.9
        .. .. .. ..@ ID       : chr "0"
        .. .. .. ..@ area     : num 54.4
        ..@ plotOrder  : int 1
        ..@ bbox       : num [1:2, 1:2] 121.2 43.4 135.1 53.6
        .. ..- attr(*, "dimnames")=List of 2
        .. .. ..$ : chr [1:2] "x" "y"
        .. .. ..$ : chr [1:2] "min" "max"
        ..@ proj4string:Formal class 'CRS' [package "sp"] with 1 slots
        .. .. ..@ projargs: chr NA
      

      从这两个维度的观察,我们基本清楚map的结构,map里每一行是一个SpatialPolygonsDataFrame对象,包括5个属性,用于存储地图数据信息。取第一行数据data属性,查看结果,发现是黑龙江省的行政区地图数据。

      
      > map[1,]@data
          AREA PERIMETER BOU2_4M_ BOU2_4M_ID ADCODE93 ADCODE99     NAME
      0 54.447    68.489        2         23   230000   230000 黑龙江省
      

      用第一行数据画图。

      
      > plot(map[1,])
      

      m2

      如果取前100行数据画图,那么应该是部分中国省的行政区地图了,果然如我所料。

      
      > plot(map[1:100,])
      

      m3

      由于本文并不是地图包的详细介绍,只要了解到map对象的基本使用就行了,稍后在博客中我会单独介绍用R做地图可视化的开发。

      3.4 数据可视化

      完成了地图数据加载后,再接下来就是数据可视化了。数据可视化,我认为要分成2部分操作,一部分是数据处理,另一部分是可视化输出。

      我们先想一下要怎么进行数据处理,才能把天气数据和地图数据结合起来呢。我们的目标是要画出中国各省天气概况,会用到过之前过滤出的数据中code的数据,code的数据都是代码,我们还要定义code代码和实际意义的映射关系。

      Yahoo的源数据中,一共定义了49种天气情况,如code.csv文件所示,根据描述我把相似的天气情况进行合并,最后保留18种天气概况特征。code代码映射文件为lablecode.csv。

      code.csv文件。

      
      "code","en","zh","type"
      0,tornado,龙卷风,3
      1,tropical storm,热带风暴,2
      2,hurricane,暴风,3
      3,severe thunderstorms,强雷雨天气,16
      4,thunderstorms,雷雨,11
      5,mixed rain and snow,雨雪,12
      6,mixed rain and sleet,雨雪,12
      7,mixed snow and sleet,雨雪,12
      8,freezing drizzle,冻毛毛雨,11
      9,drizzle,毛毛雨,11
      10,freezing rain,冻雨,11
      11,showers,阵雨,11
      12,showers,阵雨,11
      13,snow flurries,小雪,13
      14,light snow showers,阵雪,13
      15,blowing snow,飞雪,13
      16,snow,雪,14
      17,hail,冰雹,15
      18,sleet,雨雪,12
      19,dust,灰尘,5
      20,foggy,雾,7
      21,haze,薄雾,7
      22,smoky,烟雾弥漫,6
      23,blustery,大风,3
      24,windy,风,4
      25,cold,冷,18
      26,cloudy,多云,8
      27,mostly cloudy (night),满云密布,8
      28,mostly cloudy (day),满云密布,8
      29,partly cloudy (night),少云,9
      30,partly cloudy (day),少云,9
      31,clear (night),晴,10
      32,sunny,睛,10
      33,fair (night),晴,10
      34,fair (day),晴,10
      35,mixed rain and hail,大雨和冰雹,16
      36,hot,热,1
      37,isolated thunderstorms,局部雷雨,11
      38,scattered thunderstorms,零星雷雨,11
      39,scattered thunderstorms,零星雷雨,11
      40,scattered showers,零星阵雨,11
      41,heavy snow,大雪,14
      42,scattered snow showers,零星阵雪,13
      43,heavy snow,大雪,14
      44,partly cloudy,少云,9
      45,thundershowers,雷阵雨,11
      46,snow showers,阵雪,13
      47,isolated thundershowers,局部雷雨,11
      3200,not available,无数据,19
      

      字段解释:

      • code,源数据天气特征代码
      • en,英文描述
      • zh,中文描述
      • type,分类代码

      lablecode.csv文件。

      
      "type","alias"
      1,热
      2,风暴
      3,大风
      4,微风
      5,灰尘
      6,大雾
      7,薄雾
      8,多云
      9,少云
      10,晴
      11,阵雨
      12,雨加雪
      13,小雪
      14,大雪
      15,冰雹
      16,大雨
      17,雷暴雨
      18,冷
      19,无数据
      

      字段解释:

      • type,分类代码
      • alias,用于显示的别名

      有了天气特征定义后,我们再把特征匹配到不同的颜色,并增加图例及文字描述,就生成了最终的中国各省天气概况的静态图片了。

      
      > library("RColorBrewer")
      > getColors2<-function(map,prov,ctype){
      +     #name change to ADCODE99
      +     ADCODE99<-read.csv(file="ADCODE99.csv",header=TRUE,fileEncoding="utf-8", encoding="utf-8")
      +     fc<-function(x){ADCODE99$ADCODE99[which(x==ADCODE99$prov)]}
      +     code<-sapply(prov,fc)
      +     
      +     f=function(x,y) ifelse(x %in% y,which(y==x),0);
      +     colIndex=sapply(map$ADCODE99,f,code);
      +     ctype[which(is.na(ctype))]=19
      +     return(ctype[colIndex])
      + }
      > summary<-function(data=data,output=FALSE,path=''){
      +     colors<-c(rev(brewer.pal(9,"Blues")),rev(c('#b80137','#8c0287','#d93c5d','#d98698','#f6b400','#c4c4a7','#d6d6cb','#d1b747','#ffeda0')))    # 定义18种天气特征对应的颜色
      +
      +     temp<-data$code
      +     title<-"中国各省天气概况"
      +     ofile<-paste(format(date,"%Y%m%d"),"_code.png",sep="")
      +     sign<-''
      +     colors<-rev(colors)
      +     code<-read.csv(file="code.csv",header=TRUE,fileEncoding="utf-8", encoding="utf-8")
      +     labelcode<-read.csv(file="labelcode.csv",header=TRUE,fileEncoding="utf-8", encoding="utf-8")
      +     ctype<-sapply(temp,function(x){code$type[which(x==code$code)]})
      +
      +     if(output)png(file=paste(path,ofile,sep=''),width=600,height=600)
      +     layout(matrix(data=c(1,2),nrow=1,ncol=2),widths=c(8,1),heights=c(1,2))
      +     par(mar=c(0,0,3,12),oma=c(0.2,0.2,0.2,0.2),mex=0.3)
      +     plot(map,border="white",col=colors[getColors2(map,data$prov,ctype)])       # 地图和天气可视化
      +     points(data$long,data$lat,pch=19,col=rgb(0,0,0,0.3),cex=0.8)                     # 标出采样城市
      +
      +     #=======================================          # 图片中的辅助文字
      +     if(FALSE){
      +         grid()
      +         axis(1,lwd=0);axis(2,lwd=0);axis(3,lwd=0);axis(4,lwd=0)
      +     }
      +     text(100,58, title,cex=2)
      +     text(105,54,format(date,"%Y-%m-%d"))
      +     text(98,65,paste('每日中国天气','http://apps.weibo.com/chinaweatherapp'))
      +     text(120,-8,paste('provided by The Weather Channel',format(date, "%Y-%m-%d %H:%M")),cex=0.8)
      +
      +     #=======================================          # 文字说明
      +     for(row in 1:nrow(data)){
      +         name<-as.character(data$zh[row])
      +         label<-labelcode$alias[labelcode$type==ctype[row]]
      +         x1<-ceiling(row/7)
      +         x2<-ifelse(row%%7==0,7,row%%7)
      +         x3<-ctype[row]
      +         fontCol<-'#000000'
      +         if(x3<=5)fontCol<-head(colors,1)
      +         if(x3>=12)fontCol<-tail(colors,1)
      +         text(68+x1*11,17-x2*3,paste(name,' ',label,sign,sep=''),col=fontCol)
      +     }
      +
      +     #=======================================          # 图例
      +     par(mar = c(5, 0, 15, 10))
      +     image(x=1, y=1:length(colors),z=t(matrix(1:length(colors))),col=rev(colors),axes=FALSE,xlab="",ylab="",xaxt="n")
      +     axis(4, at = 1:(nrow(labelcode)-1), labels=rev(labelcode$alias)[-1], col = "white", las = 1)
      +     abline(h=c(1:(nrow(labelcode)-2)+0.5), col = "white", lwd = 2, xpd = FALSE)
      +     if(output)dev.off()
      + }
      

      运行程序,生成静态图片。

      
      > data<-read.csv(file=filename(date),header=TRUE,fileEncoding="utf-8", encoding="utf-8")   # 定义数据源
      > path=''                                                                                  # 定义输出路径
      > summary(data,output=TRUE,path=path)      # 生成中国各省天气概况图
      RStudioGD
              2
      

      20141001_code

      代码量大概100行左右,就可以生成这么复杂的天气和地图结合的图片,R真的很神奇!

      3.5 可交互的静态图

      这是锦上添花的一步,静态图片对于一般应用来说就够了。但如果图片还能动起来,是不是会更吸引人呢?我们可以尝试生成基于HTML5的、有动态效果的图,通过recharts包调Echarts库实现基于HTML5的动画,生成会动的可交互的图片。

      由于recharts包没有发布的CRAN,我们需要用devtools包通过Github安装这个包。

      
      > library(devtools)                     # 加载devtools
      > install_github("taiyun/recharts")     # 下载安装recharts包
      > library(recharts)                     # 加载recharts包
      

      由于上面的天气概况是由离散值组成的,利用echarts的库,我们做一个连续值的可视化例子,比如白天气温和夜间气温。定义weather_html()函数,提供气温数据并调用recharts包,实现可视化的输出。

      
      > weather_html<-function(data=data,type='high',output=FALSE,path=''){    # 输入HTML的天气图
      +     if(type=='high') {                                   # 白天气温
      +         df<-data[,c('prov','high')]
      +         names(df)<-c("prov","气温")
      +         title<-paste(format(date,"%Y-%m-%d"),"中国各省白天气温",sep="")
      +         ofile<-paste(format(date,"%Y%m%d"),"_day.html",sep="")
      +     }else if(type=='low'){                               # 夜间气温
      +         df<-data[,c('prov','low')]
      +         names(df)<-c("prov","气温")
      +         title<-paste(format(date,"%Y-%m-%d"),"中国各省夜间气温",sep="")
      +         ofile<-paste(format(date,"%Y%m%d"),"_night.html",sep="")
      +     }
      +
      +     df[,1]<-substr(df[,1],0,2)                           # 数据格式整理
      +     df[which(df$prov=='黑龙'),]$prov<-'黑龙江'
      +     df[which(df$prov=='内蒙'),]$prov<-'内蒙古'
      +
      +     recharts.eMap <- eMap(df, namevar=1, datavar = 2, title=title)      # 数据JSON化处理
      +     if(output){                                                         # 输出HTML文件
      +         recharts.eMap$outList[c('chartid','type')]<-NULL
      +         writeLines(unlist(recharts.eMap$outList),paste(path,ofile,sep=''))
      +     }else{                                                              # 在浏览器中打开HTML网页
      +         plot(recharts.eMap)
      +     }
      + }
      

      运行程序,以HTML输出中国各省白天气温。

      
      > date<-as.Date('20141001',format='%Y%m%d')     # 设置日期
      > data<-read.csv(file=filename(date),header=TRUE,fileEncoding="utf-8", encoding="utf-8")      # 加载数据
      > path=''                                                                                     # 设置文件输出路径
      
      > weather_html(data,type='high',output=FALSE,path='')           # 输出中国各省白天气温
      [1] "气温"
      [1] "chart path C:\\Users\\ADMINI~1\\AppData\\Local\\Temp\\RtmpqCHFPY"
      

      程序会自动打开浏览器,呈现HTML的网页。

      w6

      运行程序,以HTML输出中国各省夜间气温。在网页中,通过鼠标对地图进行交互,移动左下角的温度条,选择最高温度30,最低温度8.8,中国地图中由西南到东北变为灰色,说明这些地区的温度不在8.8到30度之间。当鼠标路过海南省的时候,海南省呈现黄色,并提示出温度为23度。

      
      > weather_html(data,type='low', output=FALSE,path='')           # 中国各省夜间气温
      [1] "气温"
      [1] "chart path C:\\Users\\ADMINI~1\\AppData\\Local\\Temp\\RtmpqCHFPY"
      

      w7

      如果不需要在浏览器中打开,只能想存储生成的网页,可以在程序中设置output为TRUE,当前目录下会生成20141001_night.html的文件。

      
      > weather_html(data,type='low',output=TRUE,path='')
      [1] "气温"
      

      本文介绍了每日中国天气应用项目完整情况,并完成R语言部分的功能实现。那么下一篇文章,将介绍如何把R语言的功能代码封装成R包。

      转载请注明出处:
      http://blog.fens.me/r-app-china-weather/

      打赏作者