CONTRIBUTED RESEARCH ARTICLES 35 rworldmap: A New R package for Mapping Global Data by Andy South There appears to be a gap in the market for free software tools that can be used across disci- Abstract rworldmap is a relatively new pack- plinary boundaries to produce innovative, publica- age available on CRAN for the mapping and vi- tion quality global visualisations. Within R there are sualisation of global data. The vision is to make great building blocks (particularly sp, maptools and the display of global data easier, to facilitate un- fields) for spatial data but users previously had to go derstanding and communication. The initial fo- through a number of steps if they wanted to produce cus is on data referenced by country or grid due world maps of their own data. Experience has shown to the frequency of use of such data in global as- that difficulties with linking data and creating classi- sessments. Tools to link data referenced by coun- fications, colour schemes and legends, currently con- try (either name or code) to a map, and then to strains researchers’ ability to view and display global display the map are provided as are functions to data. We aim to reduce that constraint to allow re- map global gridded data. Country and gridded searchers to spend more time on the more important functions accept the same arguments to specify issue of what they want to display. The vision for the nature of categories and colour and how leg- rworldmap is to produce a package to facilitate the ends are formatted. This package builds on the visualisation and mapping of global data. Because functionality of existing packages, particularly the focus is on global data, the package can be more sp, maptools and fields. Example code is pro- specialised than existing packages, making world vided to produce maps, to link with the pack- mapping easier, partly because it doesn’t have to deal ages classInt, RColorBrewer and ncdf, and to with detailed local maps. Through rworldmap we plot examples of publicly available country and aim to make it easy for R users to explore their global gridded data. data and also to produce publication quality figures from their outputs. Introduction

Global datasets are becoming increasingly common rworldmap was partly inspired and largely and are frequently seen on the web, in journal pa- funded by the UK Natural Environment Research pers and in our newspapers (for example a ’carbon Council (NERC) program Quantifying Uncertainty atlas’ of global emissions available at http://image. in System Science (QUEST). This program guardian.co.uk/sys-files/Guardian/documents/ brings together scientists from a wide range of dis- 2007/12/17/CARBON_ATLAS.pdf). At the same time ciplines including climate modellers, hydrologists there is a greater interest in global issues such as cli- and social scientists. It was apparent that while mate change, the global economy, and poverty (as researchers have common tools for visualisation for example outlined in the Millenium Development within disciplines, they tend to use different ones Goals, http://www.un.org/millenniumgoals/bkgd. across disciplines and that this limits the sharing shtml). Thirdly, there is an increasing availability of of data and methods necessary for truly interdis- software tools for visualising data in new and inter- ciplinary research. Within the project, climate and esting ways. Gapminder (http://www.gapminder. earth system modellers tended to use IDL, ecologists org) has pioneered making UN statistics more avail- ArcGIS, hydrologists and social scientists Matlab and able and intelligible using innovative visualisation fisheries scientists R. With the exception of R, these tools and Many Eyes (http://www-958.ibm.com/) software products cost thousands of pounds which provides a very impressive interface for sharing data acts as a considerable constraint on users being able and creating visualisations. to try out techniques used by collaborators. This World maps have become so common that they high cost and learning curve of adopting new soft- have even attracted satire. ’s Atlas of ware tools hinders the sharing of data and methods the Planet Earth (The Onion, 2007), contains a ’Bono between disciplines. To address this, part of the vi- Awareness’ world map representing ’the intensity sion for rworldmap was to develop a tool that can be with which artist Bono is aware of the plight and freely used and modified across a multi-disciplinary daily struggles of region’, with a categorisation rang- project, to facilitate the sharing of scripts, data and ing from ’has heard of nation once’ through ’moved outputs. Such freely available software offers greater enough by nations crisis to momentarily remove sun- opportunity for collaboration with research institutes glasses’ to ’cares about welfare of nation nearly as in developing countries that may not be able to af- much as his own’. ford expensive licenses.

The R Journal Vol. 3/1, June 2011 ISSN 2073-4859 36 CONTRIBUTED RESEARCH ARTICLES rworldmap data inputs NetCDF is a data storage file format com- monly used by climate scientists and oceanogra- rworldmap consists of tools to visualise global data phers. NetCDF files can be multi-dimensional, e.g. and focuses on two types of data. Firstly, data that holding (x,y) data for multiple attributes over mul- are referenced by country codes or names and sec- tiple months, years, days etc. The package ncdf is ondly, data that are referenced on a grid. good for reading data from netCDF files.

Country data rworldmap functionality

There is a wealth of global country level data avail- rworldmap has three core functions outlined below able on the internet including UN population data, and others that are described later. and many global indices for, among others: Envi- 1. joinCountryData2Map() joins user country ronmental Performance, Global Hunger and Multi- data referenced by country names or codes to dimensional Poverty. a map to enable plotting Data are commonly referenced by country names mapCountryData() as these are the most easily recognised by users, 2. plots a map of country data but country names have the problem that vocabu- 3. mapGriddedData() plots a map of gridded data laries are not well conserved and many countries have a number of subtly different alternate names Joining country data to a map (e.g. Ivory Coast and Cote d’Ivoire, Laos and Peo- ple’s Democratic Republic of Lao). To address this To join the data to a map use joinCountryData2Map. problem there are ISO standard country codes of ei- You will need to specify the name of column contain- ther 2 letters, 3 letters or numeric, and also 2 letter ing your country identifiers (nameJoinColumn) and FIPS country codes, so there is still not one univer- the type of code used (joinCode) e.g. "ISO3" for ISO sally adopted standard. rworldmap supports all of 3 letter codes or "UN" for numeric country codes. these country codes and offers tools to help when data(countryExData) data are referenced by names or other identifiers. sPDF <- joinCountryData2Map( countryExData ,joinCode = "ISO3" ,nameJoinColumn = "ISO3V10") Gridded data This code outputs, to the R console, a summary of Global datasets are frequently spatially referenced how many countries are successfully joined. You can on a grid, because such gridded or raster data for- specify verbose=TRUE to get a full list of countries. mats offer advantages in efficiency of data storage The object returned (named sPDF in this case) is of and processing. Remotely sensed data and other val- type "SpatialPolygonsDataFrame" from the package ues calculated from it are most frequently available sp. This object is required for the next step, display- in gridded formats. These can include terrestrial or ing the map. marine data or both. If you only have country names rather than codes There are many gridded data formats, here I will in your data, use joinCode="NAME"; you can expect concentrate on two: ESRI GridAscii and netCDF. more mismatches due to the greater variation within ESRI GridAscii files are an efficient way of storing country names mentioned previously. To address and transferring gridded data. They are straightfor- this you can use the identifyCountries() function ward text files so can be opened by any text editor. described below, and change any country names in They have a short header defining the structure of your data that do not exactly match those in the in- the file (e.g. number, size and position of rows and ternal map. columns), followed by a single row specifying the value at each grid cell. Thus they use much less space Mapping country data than if the coordinates for each cell had to be speci- fied. To plot anything other than the default map, mapCountryData Example start of gridAscii file for a half degree requires an object of class "SpatialPolygonsDataFrame" global grid: and a specification of the name of the column containing the data to plot: ncols 720 data(countryExData) nrows 360 sPDF <- joinCountryData2Map( countryExData xllcorner -180 ,joinCode = "ISO3" yllcorner -90 ,nameJoinColumn = "ISO3V10") cellsize 0.5 mapDevice() #create world map shaped window NODATA_value -999 mapCountryData(sPDF -999 1 0 1 1 ... [all 259200 cell values] ,nameColumnToPlot='BIODIVERSITY')

The R Journal Vol. 3/1, June 2011 ISSN 2073-4859 CONTRIBUTED RESEARCH ARTICLES 37

BIODIVERSITY "quantiles", "categorical", or a numeric vec- tor defining the breaks between categories. Works with the next argument (numCats) , al- though numCats is not used for "categorical", where the data are not modified, or if the user specifies a numeric vector, where the number of categories will be a result. • numCats specifies the favoured number of cat- 0.2 100 egories for the data. The number of categories used may be different if the number requested catMethod Figure 1: Output of mapCountryData() is not possible for the chosen (e.g. for "quantiles" if there are only 2 values in the data it is not possible to have more than 2 cate- Mapping gridded data gories). mapGriddedData The function can accept either • colourPalette specifies a colour palette to use 1. an object of type "SpatialGridDataFrame", as from: defined in the package sp 1. "palette" for the current palette 2. the name of an ESRI GridAscii file as a charac- 2. a vector of valid colours, e.g. c("red", ter string "white", "blue") or output from RColorBrewer 3. a 2D R matrix or array (rows by columns) 3. a string defining one of the in- rworldmap contains a "SpatialGridDataFrame" ternal rworldmap palettes from: example that can be accessed and mapped as shown "heat", "diverging", "white2Black", in the code and figure below. "black2White", "topo", "rainbow", "terrain", "negpos8", "negpos9". data(gridExData) mapDevice() #create world map shaped window • addLegend set to TRUE for a default legend, mapGriddedData(gridExData) if set to FALSE the function addMapLegend or addMapLegendBoxes can be used to create a more flexible legend. • mapRegion a region to zoom in on, can be set to a country name from getMap()$NAME or one of "eurasia", "", "latin america", "uk", "", "".

mapBubbles(), mapBars(), and mapPies()

0 27350000 Another option for displaying data is to use the mapBubbles function which allows flexible creation Figure 2: Output of mapGriddedData() of bubble plots on global maps. You can specify data columns that will determine the sizing and colouring of the bubbles (using nameZSize and nameZColour). Modifying map appearance The function also accepts other spatialDataFrame ob- rworldmap plotting functions are set up to work jects or data frames containing columns specifying with few parameters specified (usually just those the x and y coordinates. If you wish to represent identifying the data) and in such cases, default val- more attribute values per location there are also the ues will be used. However, there is considerable flex- newer mapBars() and mapPies() functions to pro- ibility to modify the appearance of plots by specify- duce bar and pie charts respectively (noting that pie ing values for arguments. Details of argument op- charts may not be the best way of presenting data tions are provided in the help files but here are a se- when there are more than a few categories). lection of the main arguments: mapDevice() #create world map shaped window mapBubbles(dF=getMap() • catMethod determines how the data values ,nameZSize="POP2005" are put into categories (then colourPalette ,nameZColour="REGION" determines the colours for those cate- ,colourPalette="rainbow" gories). Options for catMethod are: "pretty", ,oceanCol="lightblue" "fixedWidth", "diverging", "logfixedWidth", ,landCol="wheat")

The R Journal Vol. 3/1, June 2011 ISSN 2073-4859 38 CONTRIBUTED RESEARCH ARTICLES

meanCLIMATEbyStern

● ● Australasia 56.92000 ● ● ● ● ● ● ● ● ●

● ● ● ● ● ● ● ● ● ● ● ● ● Caribbean 65.20000 ● ●

● ● ● ● ● ● ● ● ●

● ● ● ● ● ● ● ●

● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●

● ● ● ● ● ● ● ● ● ● ● ● ● Central America 76.11250 ● ● ● ● ● ● ● ● ● ● ● ●

● ● ● ● ● ● ● ● ● ● ● ● ●

● ● ● ● ● ● ● ●

● ● ● ● ●

● ● ● ● ● ● ● ● ● Central Asia 56.18000 ● ● ● ● ●

● ● ● ● ● ● ● ● ● ● ● ● ● ●

● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●

● ● ● ● ● ● ● ● ● East Asia 69.18462 ● ● ● ● ●

● ● ●

● ● ● ● ● ● ● ●

● ● ● ● ● 73.87619 ● ● ● ●

● ● ● ● ● ● ● ● REGION ● ● ● ● North Africa 71.00000 0 ● ● POP2005 2 62.70000 9 ● 0 19 6.56e+08 142 ● 77.01818 150 1.31e+09 South Asia 77.22000 South+E Africa 75.79474 Figure 3: Output of mapBubbles() West Africa 78.68421 West Asia 49.62000 Identifying countries data(countryExData) mapDevice() #create world map shaped window The interactive function identifyCountries() al- mapByRegion(countryExData lows the user to click on the current map and, pro- ,nameDataColumn="CLIMATE" vided the cursor is sufficiently close to country cen- ,joinCode="ISO3" troid, will add the country name to the map. Op- ,nameJoinColumn="ISO3V10" ,regionType="Stern" tionally, the name of an attribute variable can also ,FUN="mean") be passed to the function to cause the value of the attribute to be added to the label. e.g. ’Cape Verde Produces this map: 506807’ will be printed on the map if the code below mean CLIMATE by Stern regions is entered and the mouse clicked over those islands (if you know where they are!). identifyCountries(getMap() ,nameColumnToPlot="POP2005")

Aggregating data to produce different out- puts 49.6 78.7 rworldmap offers options for aggregating half de- gree gridded data to countries, and in turn for aggre- Figure 4: Output of mapByRegion() gating country level data to regions. In both of these options a range of aggregation options are available The identity of which countries are in which re- including mean, minimum, maximum and variance. gions are stored in the data frame countryRegions. mapHalfDegreeGridToCountries() takes a grid- This also identifies which countries are currently ded input file, aggregates to a country level and plots classed by the UN as Least Developed Countries the map. It accepts most of the same arguments as (LDC), Small Island Developing states (SID) and mapCountryData(). Landlocked Developing Countries (LLDC). To map Country level data can be aggregated to global re- just the Least Developed Countries the code below gions specified by regionType in country2Region() could be used: which outputs as text, and mapByRegion() which data(countryRegions) produces a map plot. The regional classifications sPDF <- joinCountryData2Map( countryRegions available include SRES (The Special Report on Emis- ,joinCode = "ISO3" sions Scenarios of the Intergovernmental Panel on ,nameJoinColumn = "ISO3") mapDevice() #create world map shaped window Climate Change (IPCC) ), GEO3(Global Earth Obser- mapCountryData(sPDF[which(sPDF$LDC=='LDC'),] vation), Stern and GBD (Global Burden of Disease). ,nameColumnToPlot="POP2005") data(countryExData) country2Region(countryExData Using rworldmap with other packages ,nameDataColumn="CLIMATE" classInt and RColorBrewer ,joinCode="ISO3" ,nameJoinColumn="ISO3V10" While rworldmap sets many defaults internally there ,regionType="Stern" are also options to use other packages to have greater ,FUN="mean") flexibility. In the example below classInt is used to create the classification and RColorBrewer to spec- Outputs this text: ify the colours.

The R Journal Vol. 3/1, June 2011 ISSN 2073-4859 CONTRIBUTED RESEARCH ARTICLES 39 library(classInt) website at: http://www.happyplanetindex.org/ library(RColorBrewer) learn/download-report.html. I copied the country data from the sheet ‘All Data’ #getting smallexample data and joining to a map and pasted into a ‘.csv’ file so that the header row data(countryExData) was at the top of the file and the summary statistics sPDF <- joinCountryData2Map(countryExData at the bottom were left off. I then edited some of the ,joinCode = "ISO3" ,nameJoinColumn = "ISO3V10" column names to make them appropriate R variable ,mapResolution = "coarse") names (e.g. changing ‘Life Sat (0-10)’ to ‘LifeSat’). This data can then be read in easily using #getting class intervals read.csv() and joinCountryData2Map(). classInt <- classIntervals( sPDF[["EPI"]] ,n=5, style = "jenks") inFile <- 'hpi2_0edited2.csv' catMethod = classInt[["brks"]] dF <- read.csv(inFile,header=TRUE,as.is=TRUE) sPDF <- joinCountryData2Map(dF #getting colours , joinCode='NAME' colourPalette <- brewer.pal(5,'RdPu') , nameJoinColumn='country' , verbose='TRUE') #plot map mapDevice() #create world map shaped window Unfortunately, but in common with many global mapParams <- mapCountryData(sPDF country level datasets, the countries are specified by ,nameColumnToPlot="EPI" name alone rather than by ISO country codes. The ,addLegend=FALSE verbose=TRUE option in joinCountryData2Map() can ,catMethod = catMethod be used to show the names of the 10 countries that ,colourPalette=colourPalette ) don’t join due to their names being slightly different in the data than rworldmap. The names of the coun- #adding legend tries that failed to join can be edited in the csv to be do.call(addMapLegend the same as those in getMap()[[’NAME’]], and then ,c(mapParams ,legendLabels="all" they will join. A selection is shown below. ,legendWidth=0.5 name in HPI name in rworldmap ,legendIntervals="data" Iran Iran (Islamic Republic of) ,legendMar = 2)) Korea Korea, Republic of Laos Lao People's Democratic Republic EPI Moldova Republic of Moldova Syria Syrian Arab Republic Tanzania United Republic of Tanzania Vietnam Viet Nam United States of America United States

The map of the HPI on the website is interesting in that the colours applied to countries are not deter- mined by the value of the HPI for that country, but instead by the values of the three component indices 39.1 50.5 62.3 72.8 81.8 95.5 for Life Expectancy, Life Satisfaction and Ecological Figure 5: Output of mapCountryData() using inputs Footprint. Therefore I needed to add some extra R from classInt and RColorBrewer code to be able to recreate the HPI map. #categorise component indices dF$LifeSatcolour <- Examples using data freely avail- ifelse(dF$LifeSat < 5.5,'red' able on the web ,ifelse(dF$LifeSat > 7.0,'green' ,'amber' ))

Example country data from the web dF$LifeExpcolour <- The Happy Planet Index or HPI (http://www. ifelse(dF$LifeExp < 60,'red' ,ifelse(dF$LifeExp > 75,'green' happyplanetindex.org) combines country by coun- ,'amber' )) try estimates of life expectancy, life satisfaction and ecological footprint to create an index of sustainable dF$HLYcolour <- well-being that makes much more sense than GDP ifelse(dF$HLY < 33,'red' for assessing how countries are doing (Marks, 2010). ,ifelse(dF$HLY > 52.5,'green' An Excel file containing the data (‘hpi-2-0- ,'amber' )) results.xls’) can be downloaded from the HPI

The R Journal Vol. 3/1, June 2011 ISSN 2073-4859 40 CONTRIBUTED RESEARCH ARTICLES

dF$Footprintcolour <- ifelse(dF$Footprint > 8.4,'blood red' ,ifelse(dF$Footprint > 4.2,'red' Happy Planet Index ,ifelse(dF$Footprint < 2.1,'green' ,'amber' )))

#count red, amber , greens per country numReds<- (as.numeric(dF$Footprintcolour=='red') +as.numeric(dF$LifeExpcolour=='red') HPI colour +as.numeric(dF$LifeSatcolour=='red')) 2 good, 1 middle 1 good, 2 middle 3 middle numAmbers<- 1 poor (as.numeric(dF$Footprintcolour=='amber') 2 poor or footprint v.poor +as.numeric(dF$LifeExpcolour=='amber') +as.numeric(dF$LifeSatcolour=='amber')) Figure 6: Happy Planet Index 2.0, using rworldmap numGreens<- to replicate map in happy planet report (as.numeric(dF$Footprintcolour=='green') +as.numeric(dF$LifeExpcolour=='green') +as.numeric(dF$LifeSatcolour=='green')) Example gridded data from the web

#calculate HPI colour per country ‘Koeppen Geiger’ is a published classification divid- dF$HPIcolour <- ing the world into 30 climatic zones. The GIS files ifelse(dF$Footprintcolour=='blood red' for a Koeppen Geiger gridded climatic regions map | numReds>1,6 are freely available from http://koeppen-geiger. ,ifelse(numReds==1,5 vu-wien.ac.at/. The code below shows how to read ,ifelse(numAmbers==3,4 in and plot an ascii file downloaded from that site. ,ifelse(numGreens==1 & numAmbers==2,3 ,ifelse(numGreens==2 & numAmbers==1,2 inFile1 <- 'Koeppen-Geiger-ASCII.txt' ,ifelse(numGreens==3,1 #read in data which is as lon,lat,catID ,NA)))))) dF<-read.table(inFile1,header=TRUE,as.is=TRUE) #convert to sp SpatialPointsDataFrame #join data to map coordinates(dF) = c("Lon", "Lat") sPDF <- joinCountryData2Map(dF # promote to SpatialPixelsDataFrame ,joinCode="NAME" gridded(dF) <- TRUE ,nameJoinColumn="country") # promote to SpatialGridDataFrame sGDF = as(dF, "SpatialGridDataFrame") #set colours #plotting map colourPalette <- c('palegreen' mapDevice() #create world map shaped window ,'yellow' mapParams <- mapGriddedData(sGDF ,'orange' ,catMethod='categorical' ,'orangered' ,addLegend=FALSE) ,'darkred') #adding formatted legend do.call(addMapLegendBoxes #plot map ,c(mapParams mapDevice() #create world map shaped window ,cex=0.8 mapParams <- mapCountryData(sPDF ,ncol=10 ,nameColumnToPlot='HPIcolour' ,x='bottom' ,catMethod='categorical' ,title='Koeppen-Geiger Climate Zones')) ,colourPalette=colourPalette ,addLegend=FALSE ,mapTitle='Happy Planet Index') This produces a map that looks a lot different from the published map because it is using a differ- #changing legendText ent colour palette. The default "heat" colour palette mapParams$legendText <- is not the best for this categorical data and one of c('2 good, 1 middle' the palettes from RColorBrewer more suitable for ,'1 good, 2 middle' categorical data could be used. However it would ,'3 middle' be good to retain the palette created by the authors ,'1 poor' of the data. The ascii file does not contain any ,'2 poor or footprint v.poor') colour information, however the authors also pro- #add legend vide ESRI and compatible files that do do.call( addMapLegendBoxes , c(mapParams have colour information. It appeared to be impos- ,x='bottom' sible to extract the palette from the ESRI files, but ,title="HPI colour")) by opening the ‘.kmz’ file in Google Earth, saving to ‘.kml’ and some fiddly text editing in R the colours

The R Journal Vol. 3/1, June 2011 ISSN 2073-4859 CONTRIBUTED RESEARCH ARTICLES 41 could be extracted as a single column then saved to a This particular file was accessed from: http:// ‘.csv’ file, the first few lines of which are: www.ipcc-data.org/ar4/info/UKMO-HADCM3_SRA1B_ colour tas.html #960000 The ’lon180’ option was chosen to get a file with #ff0000 longitude values from −180 to 180 as that requires #ff9999 less editing before plotting. #ffcccc ... library(ncdf) #the downloaded file Plotting the map in the original colours can then inFile <- be achieved relatively simply by: 'HADCM3_SRA1B_1_tas-change_2046-2065.cyto180.nc' memory.limit(4000) #set memory limit to max #reading in colour palette nc = open.ncdf(inFile, write=FALSE) #as.is=T stops conversion to factors #which otherwise messes up colours print(nc) prints to console a description of the tst <- read.csv('paletteSaved.csv',as.is=TRUE) file contents which includes the information shown below (edited slightly for brevity). #plotting map mapDevice() #create world map shaped window file ... has 4 dimensions: #tst$x passes the palette as a vector time Size: 12 mapParams <- mapGriddedData(sGDF latitude Size: 73 ,catMethod='categorical' longitude Size: 96 ,addLegend=FALSE bounds Size: 2 ,colourPalette=tst$x) ------#adding legend file ... has 4 variables do.call(addMapLegendBoxes float climatological_bounds[bounds,time] ,c(mapParams float latitude_bounds[bounds,latitude] ,cex=0.8 float longitude_bounds[bounds,longitude] ,ncol=3 float ,x='bottomleft' air_temperature_anomaly[longitude,latitude,time] ,title='Koeppen-Geiger Climate Zones')) After the netCDF file has been opened in this way, selected variables can be read into R using get.var.ncdf(), converted to a SpatialGridDataFrame and plotted using the rworldmap function mapGriddedData. The code below first creates a grid from the parameters in netCDF file, and is written to be generic so that it Koeppen−Geiger Climate Zones Af Cfc Dfd Am Csa Dsa should work on other similar netCDF files. Then it As Csb Dsb Aw Csc Dsc BSh Cwa Dwa creates a standard classification and colour scheme BSk Cwb Dwb BWh Cwc Dwc BWk Dfa Dwd for all of the plots based on having looked at the val- Cfa Dfb EF Cfb Dfc ET ues in each months data first. Finally it loops through all months reads in the data for that month, creates a Figure 7: ‘Koeppen Geiger’ climatic zones, map pro- spatialGridDataFrame for each, plots it and saves it duced using mapGriddedData() and freely available as a ‘.png’. data ncArray = get.var.ncdf(nc,'air_temperature_anomaly') Using rworldmap to map netCDF data in tandem with ncdf # creating gridTopology from the netCDF metadata offset = c(min(nc$dim$longitude$vals) netCDF is a common file format used in meteorology ,min(nc$dim$latitude$vals)) and oceanography, it is both multi-dimensional and cellsize = c( abs(diff(nc$dim$longitude$vals[1:2])) self documented. The package ncdf allows netCDF , abs(diff(nc$dim$latitude$vals[1:2]))) files to be opened, queried and data extracted di- # add cellsize/2 to offset # to convert from lower left referencing to centre rectly to R. Data extracted from netCDF files in this offset = offset + cellsize/2 way can then be converted to sp objects and plotted cells.dim = c(nc$dim$longitude$len using rworldmap. In the example below the netCDF ,nc$dim$latitude$len ) file (∼ 333 KB) is first downloaded from the IPCC data distribution centre at: http://www.ipcc-data. gt <- GridTopology(cellcentre.offset = offset org/cgi-bin/downl/ar4_nc/tas/HADCM3_SRA1B_1_ , cellsize = cellsize tas-change_2046-2065.cyto180.nc. , cells.dim = cells.dim )

The R Journal Vol. 3/1, June 2011 ISSN 2073-4859 42 CONTRIBUTED RESEARCH ARTICLES mapDevice() Summary #creating a vector to classify the data catMethod=seq(from=-5,to=19,by=2) rworldmap is a new package to facilitate the display #creating a colourPalette for all plots of global data, referenced by grid, country or region. #-ve blue, 0 white, +ve yellow to red It is available on CRAN at http://cran.r-project. colourPalette=c('blue','lightblue','white' org/web/packages/rworldmap. rworldmap aims to ,brewer.pal(9,'YlOrRd')) provide tools to improve the communication and #looping for each month understanding of world datasets. If you have any for( zDim in 1 : nc$dim$time$len ){ comments or suggestions or would like to contribute #reading the values for this month code please get in touch. The source code and de- ncMatrix <- ncArray[,,zDim] velopment versions are available from http://code. #to get the image up the right way google.com/p/rworld/. I plan to extend this work by #this reverses the y values but not the x ones making datasets, including those used in this paper, ncMatrix2 <-ncMatrix[ ,nc$dim$latitude$len:1 ] more easily accessible, perhaps through a package gridVals <-data.frame(att=as.vector(ncMatrix2)) called rworldmapData. We are also working on vi- #creating a spatialGridDataFrame sualisations to communicate more information than sGDF <-SpatialGridDataFrame(gt, data=gridVals) by maps alone. For more help on rworldmap there is http://cran.r-project.org/ #plotting the map and getting params for legend a vignette available at mapParams <- mapGriddedData( sGDF web/packages/rworldmap/vignettes/rworldmap. ,nameColumnToPlot='att' pdf and an FAQ at http://cran.r-project.org/ ,catMethod=catMethod web/packages/rworldmapvignettes/rworldmapFAQ. ,colourPalette=colourPalette pdf. ,addLegend=FALSE ) #adding formatted legend do.call(addMapLegend Acknowledgements ,c(mapParams ,legendLabels="all" This work was funded by NERC through the QUEST ,legendWidth=0.5 Data Initiative (QESDI) and QUEST Global Systems ,legendMar = 3)) Impacts (GSI) projects. Thanks to Pru Foster and title(paste('month :',zDim))#adding brief title Sarah Cornell at QUEST and Graham Pilling at Cefas outputPlotType = 'png' without whose enthusiasm this would not have hap- savePlot(paste("ipccAirAnomalyMonth",zDim,sep='') pened. Thanks to Martin Juckes for understanding ,type=outputPlotType) project management, Barry Rowlingson and Roger } #end of month loop Bivand for their contributions at a startup workshop close.ncdf(nc) #closing the ncdf file and after, and to Nick Dulvy, Joe Scutt-Phillips and Elisabeth Simelton for inspiration on global vulnera- bility analyses and colour.

Bibliography

N. Marks GDP RIP (1933-2010). Significance, 7(1):27–30, 2010. Published Online: 23 Feb 2010 URL http://www3.interscience.wiley. com/cgi-bin/fulltext/123300797/PDFSTART.

The Onion Our Dumb World: The Onion’s Atlas of the Planet Earth. Little, Brown, and Company, 2007. ISBN: 978 0 7528 9120 0. URL http://www. theonion.com/.

Andy South Centre for Environment, Fisheries and Aquaculture Sci- ence (Cefas) Pakefield Road, Lowestoft, NR33 OHT, UK Current address: Graduate School of Education Figure 8: Two examples of IPCC temperature University of Exeter anomaly data (for December and June respectively) Exeter, EX1 2LU, UK plotted using mapGriddedData() [email protected]

The R Journal Vol. 3/1, June 2011 ISSN 2073-4859 CONTRIBUTED RESEARCH ARTICLES 43

Appendix: Comparing using dev.new(width=9,height=4.5) #so that maps extends to edge of window rworldmap to not using it oldpar <- par(mai=c(0,0,0.2,0))

Here I show a brief comparison between using #categorising the data rworldmap and not. It is not my intention to set numCats <- 7 up a straw-man that demonstrates how superior my quantileProbs <- seq(0,1,1/numCats) package is to others. Other packages have different quantileBreaks <- quantile(wrld_simpl$X2005 objectives, by focusing on world maps I can afford ,na.rm=T to ignore certain details. It is only by building on ,probs=quantileProbs) the great work in other packages that I have been wrld_simpl$toPlot <- cut( wrld_simpl$X2005 able to get this far. Those caveats aside here is the , breaks=quantileBreaks comparison using some data on alcohol consump- , labels=F ) tion per adult by country, downloaded as an Ex- #plotting map cel file from the gapminder website at http://www. plot(wrld_simpl gapminder.org/data/ and saved as a ‘.csv’. Reading ,col=rev(heat.colors(numCats))[wrld_simpl$toPlot]) in the data is common to both approaches: #adding legend using the fields package inFile <- 'indicatoralcoholconsumption20100830.csv' zlim <- range(quantileBreaks,na.rm=TRUE) dF <- read.csv(inFile) image.plot(legend.only=TRUE ,zlim=zlim Using rworldmap ,col=rev(heat.colors(numCats)) ,breaks=quantileBreaks library(rworldmap) ,horizontal=TRUE) sPDF <- joinCountryData2Map(dF, , joinCode = "NAME" par(oldpar) #reset graphics settings , nameJoinColumn = "X" , nameCountryColumn = "X" , verbose = TRUE) Slight differences in country naming, and an ab- sence of country codes, causes 9 countries not to mapCountryData(sPDF,nameColumnToPlot='X2005') to join in both approaches. joinCountryData2Map() outputs the identities of these countries. This means Not using rworldmap that in the maps it incorrectly looks like alcohol con- library(maptools) sumption is zero in both the UK and USA. To correct library(fields) this the country names need to be renamed prior to ## get map joining, either in the ‘.csv’ or in the dataframe. In the data(wrld_simpl) #from package maptools R dataframe the countries could be renamed by: ## joining #first identify failures n1<-'United Kingdom of Great Britain and Northern Ireland' matchPosnsInLookup <- match( n2<-'United Kingdom' levels(dF$X)[which(levels(dF$X)==n1)] <- n2 as.character(dF$X) ,as.character(wrld_simpl$NAME)) failedCodes <- dF$X[is.na(matchPosnsInLookup)] The objective for rworldmap is to make world numFailedCodes <- length(failedCodes) mapping easier, based on difficulties experienced by the author and project collaborators. Of course, there #printing info to console is still a learning curve associated with being able to cat(numFailedCodes use rworldmap itself. All of the operations shown ,"countries failed to join to the map\n") in this paper can be accomplished by accomplished print(failedCodes) R programmers without the need for rworldmap, #find match positions in the data however in an inter-disciplinary project rworldmap matchPosnsInData <- match( made it easier for scientists who are new to R to start as.character(wrld_simpl$NAME) ,as.character(dF$X)) producing outputs, and encouraged them to extend # join data to the map their R learning further. There are repetitive data wrld_simpl@data <- cbind(wrld_simpl@data manipulation routines that are tricky to implement, , dF[matchPosnsInData,]) rworldmap reduces the time spent on these so that more time can be spent on the important task of com- #sizing window to a good shape for the world municating what the data say.

The R Journal Vol. 3/1, June 2011 ISSN 2073-4859