Plotting_LPJmL_output_in_R - PIK-LPJmL/LPJmL GitHub Wiki
Plotting LPJmL output in R
You find the output files of LPJmL in the output directory that you
defined in the lpjmlMexicoLanduse.conf
. In this case, the directory is
$LPJROOT/outputMexicoLanduse
. Here, you will see a number of binary
files that your can read into R and view the output, e.g. as scalar
fields projected on a map.
Output files starting with an “m” are monthly output, e.g. the mnpp.bin file stands for “monthly net primary production”.
Start R either on your local machine, or on the cluster. On the cluster, you need to load the module for R.
The advantage of using your local machine is that you can easily debug your R script within the R GUI or RStudio. On the cluster, you can only use the command line input. The disadvantage of using your local machine is that you cannot read in very large input files (e.g. global ones). For this tutorial, however, it is better to use your local machine so you can debug and change your code according to your needs.
Let’s assume you want to plot the average monthly primary production (NPP) in the simulation years 1980-2005 and the average across the entire period. So the output file of interest is mnpp.bin. As this file contains monthly data, you will have to do some averaging in R.
The R script starts with defining some general variables. Each block of code is commented here to lead you through the code.
settings
rm(list=ls(all=TRUE)) # deletes all variables
# load required libaries (install before usage!)
require(fields)
require(maps)
require(plotrix)
################################################### define custom variables ##################################
res <- 0.5 #grid resolution in degrees, 0.5 is LPJmLs default resolution
#dpath <- "/iplex/01/biodiversity/alice/LPJ/" #path for running on the cluster
dpath <- "W://biodiversity/alice/LPJ/" #path for debugging on windows with the mapped drive
dir <- "outputMexicoLanduse/" #your output directory
title <- "Net primary production (gC m^-2 month^-1) from file: " #title for the plot
title.nbands <- " " #not needed here
grid <- paste(dpath,dir,"grid.bin",sep="") #the grid is needed to plot the map
ofiles <- c("mnpp.bin") #the output file of interest (NPP in this case)
yearMin <- 1980 #the first year of the evaluation period
yearMax <- 2005 #the last year of the evaluation period
sizeof.data <- 4 #4 bytes for LPJ output, 2 bytes for climate data
sizeof.data.grid <- 2 # 2 bytes for lat/long pairs
data.type <- numeric() # numeric() for LPJ output, integer() for climate data
noutput_years = yearMax - yearMin #number of output years that should be plotted
#define the data range of the axes:
#x-, y-axis are for the grid, and the z-axis is for the output variable, e.g. NPP
set.zlim <- 1 #0=automatic, 1=disp.zlim as limit: if you want to define min/max of the axis yourself, use 1 here
disp.zlim=c(-10,200) #define custom limits for NPP
set.ylim <- 0 #keep automatically set limits
disp.ylim=c(-180,180) #unused in this case
set.xlim <- 0 #keep automatically set limits
disp.xlim=c(-90,90) #unused in this case
npixel <- file.info(grid)$size / sizeof.data.grid / 2 #number of pixels in the image. Divide by 2 because there are 2 values in the grid (lat/lon)
npixel
nyears <- file.info(paste(dpath,dir,"vegc.bin",sep=""))$size/sizeof.data/npixel #total number of years in the data set
nyears
colors <- matrix(0,nrow=length(ofiles),ncol=100) #set color range
for (i in 1:length(ofiles)) { colors[i,] <- tim.colors(100)} #default blue to red
reading the grid
Next, you have to read in the underlying grid of Mexico:
################################################### read in the underlying grid ##################################
#read underlying grid (z-values of NPP are not read yet!)
grid.fn <- file(grid,"rb") #open binary file of the grid for reading (flag: 'rb')
seek(grid.fn, where=0, origin="start") #go to the starting pointer of the grid
grid.data <- matrix(readBin(grid.fn, integer(), n=npixel*2, size=2),ncol=2,byrow=T) / 100 #read in the grid
#define extent of the grid
ext.lon <- c(min(grid.data[,1]),max(grid.data[,1]))
ext.lat <- c(min(grid.data[,2]),max(grid.data[,2]))
nrow <- (ext.lat[2]-ext.lat[1])/res+1
ncol <- (ext.lon[2]-ext.lon[1])/res+1
#the image that is going to be created has pixels with i,j integer indices. We have to define
#the position in the image matrix for each coordinate to convert the lat/lon info to i,j position in the image matrix
grid.ilon <- as.integer((grid.data[,1]-ext.lon[1])/res + 1.01)
grid.ilat <- as.integer((grid.data[,2]-ext.lat[1])/res + 1.01)
close(grid.fn) #close the file
#create the image matrix for a single band (here, a month)
img <- array(NA, dim=c(nrow,ncol)) # create matrix with established dimensions filled with NA
if(!set.xlim) disp.xlim <- ext.lon #just making sure that the axis limits are set
if(!set.ylim) disp.ylim <- ext.lat
reading in data
Now you are ready to read in your z-values from the LPJmL output file, NPP in this case. Note that the following block of code opens a loop over all output files, so you may have several output files processed, if you wish. In this case, we only process one at a time (mnpp.bin).
################################################### read in NPP (z-values) ##################################
for (i in 1:length(ofiles)){ #open the loop over the output files (1 in this case)
#z-values are read in here
file <- ofiles[i] #file handler
col <- colors[i,] #colors
nbands <- file.info(paste(dpath,dir,file,sep=""))$size/sizeof.data/npixel/nyears #number of bands is 12 because of monthly data in this case
nbands
offset <- nbands * noutput_years * sizeof.data * npixel #offset in the data grid to only plot a certain range of years, e.g. 1980-2005
offset
file.f <- file(paste(dpath,dir,file,sep=""),"rb") #open binary file for reading (flag: 'rb')
seek(file.f, where=offset, origin="start") #seek for the desired years with the offset defined above
#read in file with z-values
# all pixels of one band in a row
# time series for a pixel in a column
file.data <- matrix(readBin(file.f, data.type, n=npixel*noutput_years*nbands, size=sizeof.data),ncol=npixel,nrow=noutput_years*nbands,byrow=T)
close(file.f)
if(!set.zlim) disp.zlim=c(min(file.data),max(file.data)) #make sure the z-axis range is set
slicing data
Next, a 3D plot matrix is composed of all the 2D images for each month which are stacked upon each other. That is, the 3D plot matrix contains all (still monthly) time slices that we want to average later. Afterwards, the loop over the output files is closed.
################################################### create a 3D temporal matrix of 2D images ##################################
#construct the 3D matrix
plot_matrix_all <- array(NA,dim=c(nrow,ncol,nbands*noutput_years)) # this contains all available time slices
for (b in seq(1,(nbands*noutput_years)))#for each band = each month
{
#for each pixel in the plot, write z-value (NPP) into 2D image at the matching i,j (ilon/ilat) position)
for(p in seq(1,npixel)) { img[grid.ilat[p],grid.ilon[p]] <- file.data[b,p] }
plot_matrix_all[,,b] <- img #write the entire img into 3D plot matrix (stack on top)
if(set.zlim){
img <- ifelse(img<disp.zlim[1],disp.zlim[1],img)
img <- ifelse(img>disp.zlim[2],disp.zlim[2],img)
}
} #endfor each band
}#endfor (outfiles)
averaging over time
Then, the averaging across the years is done.
################################################### averaging data across years #################################################################
#aggregate data to average NPP over each year and over the entire period
plot_matrix_ann_avg <- array(NA,dim=c(nrow,ncol,noutput_years)) #final output matrix with annual averages of NPP
plot_matrix_avg <- array(0,dim=c(nrow,ncol,1)) #final output matrix with average of NPP across the entire simulation period
nmonths = dim(plot_matrix_all)[3] #total number of nbands = months in the simulation
for (i in seq(1:noutput_years))#loop over the output years you want to plot
{
NPP_ann_avg <- array(0,dim=c(nrow,ncol,noutput_years)) #temporary storage for summation and averaging
#remember that nrow = dim(plot_matrix_all)[1] and ncol = dim(plot_matrix_all)[2]
for ( latitude in seq(1:nrow) )#loop over matrix dimension 1 = latitude
{
for ( longitude in seq(1:ncol) )#loop over matrix dimension 2 = longitude
{
for (j in seq(1:nbands))#loop over months
{
#ID of the respective month in the matrix per year
#plot_matrix_all has dim 36x61x300, 300 because of 25 (years) * 12 (months)
#sum up over each year: (i-1)*nbands + j is e.g. 0+j, j=1,...12 for year 1 and 12+1=13 for year 2
monthID = (i-1)*nbands + j
NPP_ann_avg[latitude,longitude,i] = NPP_ann_avg[latitude,longitude,i] + 1/nbands * plot_matrix_all[latitude,longitude,monthID]#sum up over the months of year i and build arithm. average
plot_matrix_avg[latitude,longitude,1] = plot_matrix_avg[latitude,longitude,1] + 1/nmonths * plot_matrix_all[latitude,longitude,monthID]#sum up over all months and build average
#cat("MonthID: ", monthID, "\n", sep="") #debug
#cat("Matrix value: ", plot_matrix_all[latitude,longitude,monthID], "\n", sep="") #debug
#cat("Summed value: ", NPP_ann_avg[latitude,longitude,i], "\n", sep="") #debug
}
}#loop over lon
}#loop over lat
#store permanently
plot_matrix_ann_avg[,,i] = NPP_ann_avg[,,i]
#plot annual averages on the fly
image.plot(x=seq(ext.lon[1],ext.lon[2],by=res), y=seq(ext.lat[1],ext.lat[2],by=res),z=t(plot_matrix_ann_avg[,,i]), zlim=disp.zlim, xlim=disp.xlim, ylim=disp.ylim,xlab="",ylab="",asp=1,col=rev(col),tcl=-0.2,yaxp=c(disp.ylim,4),xaxp=c(disp.xlim,8),axes=T)
ititle <- paste("NPP (gC m^-2 month^-1), annual avg. of ", (yearMin + i - 1), sep = " ")
title(paste(title,file),ititle,cex.sub=1.5)
map("world",add=TRUE,fill=FALSE)
}#end (noutput_years)
#plot average across all years #
#plot total average on the fly
image.plot(x=seq(ext.lon[1],ext.lon[2],by=res), y=seq(ext.lat[1],ext.lat[2],by=res),z=t(plot_matrix_avg[,,1]), zlim=disp.zlim, xlim=disp.xlim, ylim=disp.ylim,xlab="",ylab="",asp=1,col=rev(col),tcl=-0.2,yaxp=c(disp.ylim,4),xaxp=c(disp.xlim,8),axes=T)
ititle <- paste("NPP (gC m^-2 month^-1), avg. ", yearMin, " - ", yearMax, sep = " ")
title(paste(title,file),ititle,cex.sub=1.5)
map("world",add=TRUE,fill=FALSE)
write to PDF
After gathering all the averages you need, write them out to PDF for selected years.
################################################### write out to PDF ##################################
#output annual averages in pdf file
pdf.file <- paste(dpath,dir,"NPP_Mexico.pdf",sep='') #choose file name
pdf(pdf.file, width=600,height=600,paper="default") #open the file
par(mfrow=c(3,2)) #page layout
yearIDs = c(1,6,12,18,25) #year IDs starting from yearMin = 1980 you like to plot
for (i in 1:length(yearIDs))#for each year you like to plot
{
yearID = yearIDs[i]
#plot the image (it will be written out as PDF due to previous 'pdf' command
image.plot(x=seq(ext.lon[1],ext.lon[2],by=res), y=seq(ext.lat[1],ext.lat[2],by=res),z=t(plot_matrix_ann_avg[,,yearID]), zlim=disp.zlim, xlim=disp.xlim, ylim=disp.ylim,xlab="lon",ylab="lat",asp=1,col=rev(col),tcl=-0.2,yaxp=c(disp.ylim,4),xaxp=c(disp.xlim,8),axes=T)
par(new=TRUE)
map("world",add=TRUE,fill=FALSE)
ititle <- paste("NPP (gC m^-2 month^-1) in ", yearMin + yearID - 1, sep = " ")
title(ititle,cex.sub=1.5)
}
#add the total avg. over all years from yearMin to yearMax
image.plot(x=seq(ext.lon[1],ext.lon[2],by=res), y=seq(ext.lat[1],ext.lat[2],by=res),z=t(plot_matrix_avg[,,1]), zlim=disp.zlim, xlim=disp.xlim, ylim=disp.ylim,xlab="lon",ylab="lat",asp=1,col=rev(col),tcl=-0.2,yaxp=c(disp.ylim,4),xaxp=c(disp.xlim,8),axes=T)
ititle <- paste("NPP (gC m^-2 month^-1), avg. ", yearMin, " - ", yearMax, sep = " ")
title(ititle,cex.sub=1.5)
map("world",add=TRUE,fill=FALSE)
dev.off() #close output to PDF
Finally, you can also write out the results as ASCII grids, e.g. for reading them into ArcGIS.
#######################################write out to ASCII grid ################################
###the avg over each specific year
yearIDs = c(1,6,12,18,25)
for (i in 1:length(yearIDs))
{
yearID = yearIDs[i]
matrixRotated <- rot90(plot_matrix_ann_avg[,,yearID], -1) #matrix must be rotated anti-clockwise to be displayed correctly in ArcGIS
v_lpjoutput <- as.vector(matrixRotated) #convert to a vector
#establish a grid topology of correct size
#arguments are: lower left corner of the country grid (Mexico in this case), grid resolution (half degree in this case), and dimensions of the read-in plot matrix
#you can get the first argument for the grid from variable ext.lon[1] and ext.lat[1] (this is lower left corner on grid))
xy <- GridTopology(c(ext.lon[1], ext.lat[1]), c(res, res), c(dim(plot_matrix_avg)[2], dim(plot_matrix_avg)[1])) #turn dimension of plot matrix around because it is rotated anti-clockwise by 90 degrees
#do projections for the spatial object of type spatial grid data frame..crs is the coordinate reference system, achieves conversion between coordinate systems
sgdf_lpjoutput <- SpatialGridDataFrame(xy,proj4string=CRS("+proj=lonlat"),data=data.frame(v_lpjoutput))
names(sgdf_lpjoutput) <- c('NPP')
#write out the ASCII grid
# na.value is the no-data value to prevent the grid from crashing at undefined values
write.asciigrid(sgdf_lpjoutput,paste(dpath,dir,"NPP_avg_", (yearMin + yearID - 1) , "_mexico.txt",sep=''),na.value= -9999)
}
#If you don't have ArcGIS, you can read an ASCII grid back in for testing, e.g. by
#fname <- paste(dpath,dir,"NPP_avg_1981_mexico.txt",sep='')
#x <- read.asciigrid(fname, as.image = FALSE, plot.image = TRUE, colname = fname, proj4string = CRS(as.character(NA)))
#image(x)
###the avg over all years
matrixRotated <- rot90(plot_matrix_avg[,,1], -1)
v_lpjoutput <- as.vector(matrixRotated)
xy <- GridTopology(c(ext.lon[1], ext.lat[1]), c(res, res), c(dim(plot_matrix_avg)[2], dim(plot_matrix_avg)[1])) #when rotated by 90 degrees anti-clockwise
sgdf_lpjoutput <- SpatialGridDataFrame(xy,proj4string=CRS("+proj=lonlat"),data=data.frame(v_lpjoutput))
names(sgdf_lpjoutput) <- c('NPP')
write.asciigrid(sgdf_lpjoutput,paste(dpath,dir,"NPP_avg_1980-2005_mexico.txt",sep=''),na.value= -9999)
An example of the PDF output from this R script is attached to this page below.
plotting raster maps in alternative projections
This can be a real pain in the a.., er, neck. Here’s an example of how it works.
rm(list=ls(all=TRUE))
gc()
#require(maptools)
#require(ncdf4)
require(raster)
require(rgdal)
require(ggplot2)
require(gridExtra)
#### crop names
cr.names <- c("mai","whe","ric","soy")
#### prepare CWP data from NC file
f <- "D:/data/GGCMI/median_delta_cwp_2050.nc" # NC file with 4 layers for maize, wheat, rice and soy, global extent
cwp <- raster(f)
#plot(cwp)
#read individual layers to separate variables
for( i in 1:cwp@file@nbands ){
assign( paste("cwp" , i , sep = ".") , raster(f, band=i))
}
# function to project raster data and convert to data frame as needed by ggplot
project.raster <- function(data,pr="+proj=robin",str="CWP",newex=c(-180,180,-65,85)){
newext <- newex
data <- crop(data,newext)
data.p <- projectRaster(data,crs=pr)
# inspired by http://nrelscience.org/2013/05/30/this-is-how-i-did-it-mapping-in-r-with-ggplot2/
#convert the raster to points for plotting
map.p <- rasterToPoints(data.p)
#Make the points a dataframe for ggplot
df.data <- data.frame(map.p)
#Make appropriate column headings
colnames(df.data) <- c("long", "lat", str)
df.data
}
#### read spatial data for nice look
# inspired by http://rpsychologist.com/working-with-shapefiles-projections-and-world-maps-in-ggplot
setwd("D:/data/GGCMI/")
# downloaded from http://www.naturalearthdata.com/downloads/110m-cultural-vectors/110m-admin-0-countries/
# add country borders
countries <- readOGR("ne_110m_admin_0_countries", layer="ne_110m_admin_0_countries")
countries_robin <- spTransform(countries, CRS("+init=ESRI:54030"))
countries_robin_df <- fortify(countries_robin)
# downloaded from http://www.naturalearthdata.com/downloads/110m-physical-vectors/110m-graticules/
grat <- readOGR("ne_110m_graticules_all", layer="ne_110m_graticules_15") # this is the 15° grat. There are finer and coarser data available, too
bbox <- readOGR("ne_110m_graticules_all", layer="ne_110m_wgs84_bounding_box")
grat_robin <- spTransform(grat, CRS("+proj=robin")) # reproject graticule
grat_robin_df <- fortify(grat_robin)
bbox_robin <- spTransform(bbox, CRS("+proj=robin")) # reproject bounding box
bbox_robin_df <- fortify(bbox_robin)
#### plot things using ggplot
# taken from https://github.com/hadley/ggplot2/wiki/Share-a-legend-between-two-ggplot2-graphs
grid_arrange_shared_legend <- function(...) {
plots <- list(...)
g <- ggplotGrob(plots[1](/PIK-LPJmL/LPJmL/wiki/1) + theme(legend.position="bottom"))$grobs
legend <- g[which(sapply(g, function(x) x$name) == "guide-box")](/PIK-LPJmL/LPJmL/wiki/which(sapply(g,-function(x)-x$name)-==-"guide-box"))
lheight <- sum(legend$height)
grid.arrange(
do.call(arrangeGrob, lapply(plots, function(x)
x + theme(legend.position="none"))),
legend,
ncol = 1,
heights = unit.c(unit(1, "npc") - lheight, lheight))
}
plot.proj.raster <- function(bbox,rast.df,countries.df,grat.df,tit){
# inspired by http://rpsychologist.com/working-with-shapefiles-projections-and-world-maps-in-ggplot
# some settings, set most thing to empty, background to grey
theme_opts <- list(theme(panel.grid.minor = element_blank(),
panel.grid.major = element_blank(),
panel.background = element_blank(),
plot.background = element_rect(fill="#e6e8ed"),
panel.border = element_blank(),
axis.line = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_blank(),
axis.ticks = element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
plot.title = element_text(size=10)))
# conversion of raster to points seems to be the only workable method to combine grid lines with raster data in specific projections....
ggplot(bbox, aes(long,lat)) +
geom_polygon(fill="white") +
#geom_polygon(data=df.cwp.robin, aes(long,lat, group=group)) +
geom_raster(aes(long,lat,fill=CWP),data=rast.df) +
geom_path(data=countries.df, aes(long,lat, group=group), color="black", size=0.3) +
geom_path(data=grat.df, aes(long, lat, group=group, fill=NULL), linetype="dashed", color="grey50",size=0.3) +
labs(title=tit) +
coord_equal() +
theme_opts +
scale_fill_distiller(palette="Spectral")
}
#### for one layer tryout
df.cwp <- project.raster(cwp.1)
plot.proj.raster(bbox_robin_df,df.cwp,countries_robin_df,grat_robin_df,"CWP")
#### plot to 2x2 png file with one legend
png("CWP_robinson_R_tryout.png",width=5*300,height=4*300,res=300,pointsize=6)
for(i in 1:4){
cwp.act <- get(paste("cwp",i,sep="."))
df.cwp.act <- project.raster(cwp.act)
assign(paste("p",i,sep="."),plot.proj.raster(bbox_robin_df,df.cwp.act,countries_robin_df,grat_robin_df,cr.names[i]))
}
#grid.arrange(p.1,p.2,p.3,p.4)
grid_arrange_shared_legend(p.1, p.2, p.3, p.4)
dev.off()