#
#
# biomass.CTFSdb
#
#
# Calculate biomass from existing R-formatted tables for trees and stems using dbh allometry. By default, it uses the Chave (2005) equations.
# Note that the standard downloads of R Analytical Tables
# already has the agb column filled, using the default parameters for moist forest. This program can be used to repeat or redo the calculation.
# It can also be used for other tables, always requiring two tables: one with trees and one with all stems.
# The function returns the same table as
# submitted (either tree or stem), with a column agb added; if the agb column was already present, it is replaced.
# Calculations are done by AGB.tree and the subroutines it call.
#
#
# RStemTable: Name of table with one row per stem; must have dbh, species (column sp), treeID
# RTreeTable: Name of table with one row per tree; must have dbh, species (column sp), treeID
# whichtable: Set to "tree" to return the entire tree table with agb, or to "stem" to return stem table
# dbhunit: Set to "mm", "cm", or "inch"
# plot: Name of plot, matching the plot name used in the wood-density table
# wsgdata: Name of R object having wood-density for species in all CTFS plots
# forest: Set to "moist", "dry", or "wet" to use the equations publshed in Chave (2005) for the 3 forest types
# ht.param: A vector of parameters for a formula relating tree height to dbh; if NULL, the biomass formula does not use height
# htmodel: Name of an R function that returns tree height giving a dbh and any number of parameters; if ht.param is NULL, htmodel is ignored
#
#
# CTFSplot("bci","full",census=1)
# CTFSplot("bci","stem",census=1)
# attach("biomass/wsg.ctfs.Rdata")
# newtable=biomass.CTFSdb(RStemTable=bci.stem1,RTreeTable=bci.full1)
#
#
biomass.CTFSdb=function(RStemTable,RTreeTable,whichtable='tree',dbhunit='mm',plot='bci',wsgdata=wsg.ctfs2,forest='moist',
ht.param=NULL,htmodel=predht.asym)
{
if(whichtable=='tree')
{
agb.pertree=AGB.tree(df=RStemTable,dbhunit=dbhunit,plot=plot,wsgdata=wsgdata,forest=forest,ht.param=ht.param,htmodel=htmodel)
m=match(RTreeTable$treeID,agb.pertree$treeID)
RTreeTable$agb=agb.pertree$agb[m]
return(RTreeTable)
}
RStemTable$agb=AGB.ind(df=RStemTable,dbhunit=dbhunit,plot=plot,wsgdata=wsgdata,forest=forest,ht.param=ht.param,htmodel=htmodel)
return(RStemTable)
}
#
#
#
#
# density.ind
#
#
# Create a vector of wood density for each individual tree based on the species name and plot. The table of individuals, called df,
# must include a dbh and a species name, the latter named sp. There must be a table of wood density submitted (wsgdata), and this
# table must have a column sp with species names, a column plot, plus the wood density in a column called wsg (though the
# name of that column can be changed using the argument denscol). The CTFS wood-density table has this structure, but any table with those
# columns will work. If a species in the df table has a matching species name in the correct plot, its wood density is taken.
# If a species is not found in the correct plot, then the mean wood density of all species in the same plot is taken. The function
# fails (returns only NAs) if there are no entries for the selected plot in the wood-density table.
# Returns a vector of wood density of the same size as the df table submitted.
#
#
#
#
#
# wooddens=density.ind(df=bci.full1,plot="bci",wsg=wsg.ctfs2) #
# mean(wooddens,na.rm=TRUE) #
# length(which(is.na(wooddens)))
#
#
density.ind=function(df,plot,wsgdata,denscol='wsg')
{
wsgdatamatch=which(wsgdata$site %in% plot)
if(length(wsgdatamatch)==0) return(rep(NA,dim(df)[1]))
wsgdata=unique(wsgdata[wsgdatamatch,])
meanwsg=mean(subset(wsgdata,idlevel=='species')[,denscol],na.rm=TRUE)
m=match(df$sp,wsgdata$sp)
result=wsgdata[m,denscol]
result[is.na(m)]=meanwsg
result[is.na(result)]=meanwsg
return(result)
}
#
#
#
#
# AGB.ind
#
#
# Compute biomass (agb) based on one of the Chave (Oecologia, 2005) models for tropical forest types.
# Requires a table (df) with dbh and species names, a wood-density table (described under density.ind), a plot name,
# dbh units, and a forest type (for most lowland tropical plots, the default moist is recommended.)
# The height parameters default to NULL, and the Chave equations without height are then used. Alternatively, height parameters
# and a height function can be supplied, the latter to calculate height from diameter for every tree, in which case the
# Chave model with height is used. Returns a vector of biomass in kg for every individual in the submitted table df.
# This is called by AGB.tree in the standard calculation of biomass for CTFS R tables.
#
#
#
#
#
# biomass=AGB.ind(df=bci.full1) #
# hist(log(biomass),breaks=100) #
# sum(biomass,na.rm=TRUE)/50
#
#
AGB.ind=function(df,dbhunit='mm',plot='bci',wsgdata=wsg.ctfs2,forest='moist',ht.param=NULL,htmodel=predht.asym)
{
wsg=density.ind(df=df,plot=plot,wsgdata=wsgdata,denscol='wsg')
if(dbhunit=='mm') dbh=df$dbh/10
else if(dbhunit=='inch') dbh=df$dbh/2.54
else if(dbhunit=='cm') dbh=df$dbh
return(Chave.AGB(dbh=dbh,density=wsg,forest=forest,htparam=ht.param,heightmodel=htmodel)/1000)
}
#
#
#
#
# AGB.tree
#
#
# Computes AGB of each tree in a table, grouping all stems of one tree and adding there agbs.
# The submitted table, df, must have dbh, species name (sp),
# and a treeID to identify which tree every stem belong to. There must be just one dbh for each stem. Returns
# a dataframe with one row per tree, including the treeID and total agb per tree. Note that it will have fewer rows
# than the table submitted. This is called by biomass.CTFSdb in the standard calculation of biomass for CTFS R tables.
#
#
# biomasstbl=AGB.tree(df=bci.stem1)
# dim(bci.stem1)
# dim(biomasstbl)
# head(biomasstbl)
#
#
#
#
#
AGB.tree=function(df,dbhunit='mm',plot='bci',wsgdata=wsg.ctfs2,forest='moist',ht.param=NULL,htmodel=predht.asym)
{
AGB.stem=AGB.ind(df=df,dbhunit=dbhunit,plot=plot,wsgdata=wsgdata,forest=forest,ht.param=ht.param,htmodel=htmodel)
AGB.tree=tapply(AGB.stem,df$treeID,sum,na.rm=TRUE)
result=data.frame(treeID=as.numeric(names(AGB.tree)),agb=AGB.tree)
return(result)
}
#
#
#
#
# Chave.AGB
#
#
# The Chave 2005 Oecologia model for calculating biomass from dbh in cm. All dbhs are submitted as a vector, and a vector of wood density
# of the same length must also be submitted (or a single wood density can be passed, to be used for every tree).
# Parameter values for the 3 forest types according to Chave 2005 are hard-coded in the function.
# The recommended CTFS use is with htparam=NULL, so height is not used. If height parameters and a height model are passed,
# then the height of every tree is calculated, and the Chave AGB formula that includes height is used. The default height parameters are
# from Chave et al 2003 on BCI biomass, and the default height function is predht.asym, provided in this file. But any height model can be
# substituted, providing the function name is passed and the necessary number of parameters included as htparam.
# Returns a vector of biomass of same length as vector of dbh submitted.
# This is called by AGB.tree in the standard calculation of biomass for CTFS R tables.
#
#
#
#
#
# testdbh=c(1,2,5,10,20,30,50,100,200) #
# AGBmoist=Chave.AGB(dbh=testdbh,forest="moist") #
# AGBwet=Chave.AGB(dbh=testdbh,forest="wet") #
# plot(testdbh,AGBmoist,col="green",type="l") #
# lines(testdbh,AGBwet,col="blue")
#
#
Chave.AGB=function(dbh,density=0.62,htparam=c(41.7,.057,.748),heightmodel=predht.asym,forest='moist')
{
if(is.null(htparam))
{
if(forest=="moist") param=c(-1.499,2.148,0.207,-0.0281)
else if(forest=="dry") param=c(-.667,1.784,0.207,-.0281)
else if(forest=="wet") param=c(-1.239,1.98,0.207,-0.0281)
AGB=agb.dbhmodel(dbh,density,param)
}
else
{
if(forest=="moist") param=c(.0501,1)
else if(forest=="dry") param=c(.112,.91)
else if(forest=="wet") param=c(.077,.94)
ht=heightmodel(dbh,htparam)
AGB=agb.model(dbh,density,ht,param)
}
return(AGB)
}
#
#
#
#
# agb.model
#
#
# Calculates biomass from density, height, and dbh. Requires just two parameters, following Chave (2005). The parameters can be
# changed, but the formula cannot be. Returns a vector of biomass as long as vector of dbh submitted.
# This is called by Chave.AGB in the standard calculation of biomass for CTFS R tables.
#
#
#
#
#
# agb.model(dbh=c(1,1,2),density=c(.6,.6,.5),height=c(2,3,4),param=c(.0501,1))
#
#
agb.model=function(dbh,density,height,param)
return(param[1]*(density*dbh^2*height)^param[2])
#
#
#
#
# agb.dbhmodel
#
#
# Calculates biomass from density and diameter, without height. Requires four parameters, following Chave (2005).
# The parameters can be changed, but the formula cannot be. Returns a vector of biomass as long as vector of dbh submitted.
# This is called by Chave.AGB in the standard calculation of biomass for CTFS R tables.
#
#
#
#
#
# agb.dbhmodel(dbh=c(1,1,2),density=c(.6,.6,.5),param=c(-1.499,2.148,0.207,-0.0281))
#
#
agb.dbhmodel=function(dbh,density,param)
return(density*exp(param[1]+param[2]*log(dbh)+param[3]*log(dbh)^2+param[4]*log(dbh)^3))
#
#
#
#
# predht.asym
#
#
# An allometric model predicting an asymptote at large size, used in estimating tree height as a function of dbh.
# The model uses 3 parameters, submitted as argument param. The matrix form of param allows a different set of parameters to
# be submitted for every species. The default parameters given in the function Chave.AGB assume dbh is in cm, as do all the biomass
# allometry functions.
#
#
# dbh: Vector of dbh
# param: Either a vector of length 3, or a matrix of 3 columns; if the latter, there must be one row for each dbh
#
#
# htparam=c(41.7,.057,.748) #
# d=c(1,2,5,10,20,50) #
# ht=predht.asym(dbh=d,param=htparam)
#
#
predht.asym=function(dbh,param)
{
if(is.null(dim(param)))
{
ymax=param[1]
a=param[2]
b=param[3]
}
else
{
ymax=param[,1]
a=param[,2]
b=param[,3]
}
return(ymax*(1-exp(-a*dbh^b)))
}
#
#
#
#
# biomass.change
#
#
# Finds biomass in two censuses and change between them. The submitted dataframes are exactly the standard CTFS R Analytical tables,
# with a column for biomass (agb) already calculated. Each dataframe has a record for every tree in a single census (or a stem
# table can be passed, with one record for each stem). Biomass for all living trees is summed over whatever grouping variables are
# submitted (split1 and
# split2) in both censuses, along with the annual rate of change in each category. Returns a list with 6 components:
# N.1: Total biomass in first census submitted
# N.2: Total biomass in second census submitted
# date1: Mean date in first census
# date2: Mean date in second census
# interval: The mean census interval in years
# little.r: The rate of biomass change, or (log(N.2)-log(N.1))/interval
# If no split variables are submitted (split1=split2=NULL), each component of the list is a single number, for the entire plot.
# If split1 is submitted but not split2, each component is a vector, one value for each category of split. If both splits are
# submitted, each component of the list is a matrix.
# Based closely on the function pop.change in abundance.r, and differs only in taking sum of agb instead of counting individuals.
#
#
# census1: The R Analytical Table for a single census, either tree or stem
# census2: The matching R Analytical Table for a later census
# alivecode: A vector of status codes that indicate a tree is alive (usually just "A" in most CTFS R tables)
# mindbh: The minimum dbh to include (if NULL, all dbhs included); biomass is summed in trees larger that this
# split1: A vector of exactly the same length as the number of rows in census1 and census2, with a grouping variable for each tree;
# a common use is the species name
# split2: Another split vector of the same length, for example a dbh category
#
#
# CTFSplot("bci","full",census=c(3,7)) #
# deltaAGB=biomass.change(bci.full3,bci.full7) #
# deltaAGB.spp=biomass.change(bci.full3,bci.full7,split1=bci.full3$sp) #
# deltaAGB.table=assemble.demography(deltaAGB.spp,type="a") #
# rate=deltaAGB.table$little.r #
# hist(rate,breaks=50) #
# summary(rate[is.finite(rate)]) #
# subset(deltaAGB.table,is.infinite(rate)) #
#
#
biomass.change=function(census1,census2,alivecode=c("A"),mindbh=NULL,split1=NULL,split2=NULL)
{
if(is.null(split1)) split1=rep("all",dim(census1)[1])
if(is.null(split2)) split2=rep("all",dim(census2)[1])
if(!is.null(mindbh))
{
inc1=census1$dbh>=mindbh
inc2=census2$dbh>=mindbh
incboth=census1$dbh>=mindbh | census2$dbh>=mindbh
}
else inc1=inc2=incboth=rep(TRUE,length(census1$dbh))
alive1=alive2=rep(FALSE,dim(census1)[1])
for(i in 1:length(alivecode))
{
alive1[census1$status==alivecode[i]]=TRUE
alive2[census2$status==alivecode[i]]=TRUE
}
aliveboth=alive1 | alive2
groupvar1=list(split1[inc1&alive1],split2[inc1&alive1])
groupvar2=list(split1[inc2&alive2],split2[inc2&alive2])
groupvarboth=list(split1[incboth&aliveboth],split2[incboth&aliveboth])
ab1=tapply(census1$agb[inc1&alive1],groupvar1,sum,na.rm=TRUE)
ab2=tapply(census2$agb[inc2&alive2],groupvar2,sum,na.rm=TRUE)
dt1=tapply(census1$date[incboth&aliveboth],groupvarboth,mean,na.rm=T)
dt2=tapply(census2$date[incboth&aliveboth],groupvarboth,mean,na.rm=T)
startdate=tapply(census1$date[incboth&aliveboth],groupvarboth,mean,na.rm=T)
enddate=tapply(census2$date[incboth&aliveboth],groupvarboth,mean,na.rm=T)
class1=sort(unique(split1))
class2=sort(unique(split2))
ab1=fill.dimension(ab1,class1,class2)
ab2=fill.dimension(ab2,class1,class2)
dt1=fill.dimension(dt1,class1,class2,fill=NA)
dt2=fill.dimension(dt2,class1,class2,fill=NA)
startdate=fill.dimension(startdate,class1,class2,fill=NA)
enddate=fill.dimension(enddate,class1,class2,fill=NA)
interval=(dt2-dt1)/365.25
little.r=(log(ab2)-log(ab1))/interval
return(list(N.1=ab1,N.2=ab2,date1=startdate,date2=enddate,interval=interval,little.r=little.r))
}
#
#