Chapter 9 Evaluating Model Predictions
“Being a statistician means never having to say you are certain.”
In environmental sciences, it can be extremely valuable to not only try to parse out correlations and “peer inside the black box” to explain phenomena, but to also try to predict outcomes based on specific inputs and parameters. Equally important when we try to model predictions is to evaluate how well our model performs and predicts data. For this chapter, I looked at predictions from the CENTURY Model and evaluated how well the model fit with observed corn and wheat yield data from 1999-2013.
Data and assignment provided by Dr. Michael Lefsky of Colorado State University.
9.1 Merge model predictions and observed data
# Load datasets
#Century Model output through 2016
<- read.csv("Data_sci_bookdown/data/model-assess/century_harvest.csv")
centgrain
<- centgrain[-c(16:18),] #Century model outputs through 2013
centgrain13
<- centgrain13 %>%
centwheat filter(Crop == "wheat") #just the wheat outputs
<- centgrain13 %>%
centcorn filter(Crop == "corn") #just the corn outputs
#load observed data
<- read.csv("Data_sci_bookdown/data/model-assess/obs_corn_wheat_cgrain.csv") #observed data through 2013
obsgrain
<- obsgrain %>%
obswheat filter(Crop == "wheat") #just the wheat outputs
<- obsgrain %>%
obscorn filter(Crop == "corn") #just the corn outputs
#merge data from Century Model and observations
<- merge(centwheat,obswheat, by=c('Year','Crop'), all.x = T) # merge predicted and observed wheat yields
wheat <- merge(centcorn,obscorn, by=c('Year','Crop'), all.x = T) # merge predicted and observed wheat yields
corn <- merge(centgrain13, obsgrain, by=c('Year','Crop'), all.x = T) # merge all predicted and observed allgrain
9.2 Linear regression model and ANOVA of wheat predictions vs. observations
<- lm(cgrain_cent ~ cgrain_obs, data = wheat) # linear model of predicted vs. observed wheat yields
lm_wheat summary(lm_wheat) # intercept, slope (estimate column), P, R^2
##
## Call:
## lm(formula = cgrain_cent ~ cgrain_obs, data = wheat)
##
## Residuals:
## Min 1Q Median 3Q Max
## -59.857 -2.631 6.911 14.383 25.962
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 512.0318 127.8900 4.004 0.00709 **
## cgrain_obs -0.4359 0.3544 -1.230 0.26473
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 28.83 on 6 degrees of freedom
## Multiple R-squared: 0.2014, Adjusted R-squared: 0.06827
## F-statistic: 1.513 on 1 and 6 DF, p-value: 0.2647
summary(aov(lm_wheat)) # ANOVA
## Df Sum Sq Mean Sq F value Pr(>F)
## cgrain_obs 1 1257 1257.3 1.513 0.265
## Residuals 6 4986 831.1
ggplot(data = wheat, aes(x=cgrain_cent,y=cgrain_obs)) +
geom_point(color="black") + geom_smooth(method="lm", se=FALSE, color="#78917E") +
xlab("Predicted Yields (g C/m2/yr)") + ylab(expression(paste("Observed Yields (g C/m2/yr)"))) +
ggtitle("Predicted vs. Observed Wheat Yields") +
theme_few(base_size = 16)
## `geom_smooth()` using formula 'y ~ x'
9.3 Linear regression model and ANOVA of corn predictions vs. observations
<- lm(cgrain_cent ~ cgrain_obs, data = corn) # linear model of predicted vs. observed corn yields
lm_corn summary(lm_corn) # intercept, slope (estimate column), P, R^2
##
## Call:
## lm(formula = cgrain_cent ~ cgrain_obs, data = corn)
##
## Residuals:
## 1 2 3 4 5 6 7
## -7.7548 -0.1305 -16.2379 -18.3472 14.2575 14.2393 13.9737
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 281.5815 74.7988 3.765 0.0131 *
## cgrain_obs 0.3199 0.1731 1.849 0.1238
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 15.89 on 5 degrees of freedom
## Multiple R-squared: 0.406, Adjusted R-squared: 0.2872
## F-statistic: 3.418 on 1 and 5 DF, p-value: 0.1238
summary(aov(lm_corn)) # ANOVA
## Df Sum Sq Mean Sq F value Pr(>F)
## cgrain_obs 1 862.4 862.4 3.418 0.124
## Residuals 5 1261.7 252.3
ggplot(data = corn, aes(x=cgrain_cent,y=cgrain_obs)) +
geom_point(color="black") + geom_smooth(method="lm", se=FALSE, color="#78917E") +
xlab("Predicted Yields (g C/m2/yr)") + ylab(expression(paste("Observed Yields (g C/m2/yr)"))) +
ggtitle("Predicted vs. Observed Corn Yields") +
theme_few(base_size = 16)
## `geom_smooth()` using formula 'y ~ x'
9.4 Mean, standard deviation, and standard error for predicted and observed outputs
#Summary stats for predicted outputs - mean, sd, se
%>% # summary stats for CM wheat outputs
centwheat summarise(n = n(),
mean = mean(cgrain_cent), # mean
sd = sd(cgrain_cent), # standard deviation
SE = sd/sqrt(n)) # standard error
## n mean sd SE
## 1 8 355.2258 29.86568 10.55911
%>% # summary stats for CM corn outputs
centcorn summarise(n = n(),
mean = mean(cgrain_cent), # mean
sd = sd(cgrain_cent), # standard deviation
SE = sd/sqrt(n)) # standard error
## n mean sd SE
## 1 7 419.4124 18.81556 7.111612
#Summary stats for observed outputs - mean, sd, se
%>% # summary stats for observed wheat outputs
obswheat summarise(n = n(),
mean = mean(cgrain_obs), # mean
sd = sd(cgrain_obs), # standard deviation
SE = sd/sqrt(n)) # standard error
## n mean sd SE
## 1 8 359.7 30.74364 10.86952
%>% # summary stats for observed corn outputs
obscorn summarise(n = n(),
mean = mean(cgrain_obs), # mean
sd = sd(cgrain_obs), # standard deviation
SE = sd/sqrt(n)) # standard error
## n mean sd SE
## 1 7 430.8286 37.47473 14.16412
9.5 Model evaluation via Mean Absolute Error (MAE) and Root Mean Squared Error (RMSE)
#Model evaluation stats
mae(centgrain13[,3], obsgrain[,3]) # mean absolute error for predicted vs. observed yields
## [1] 32.46613
rmse(centgrain13[,3], obsgrain[,3]) # root mean squared error for predicted vs. observed yields
## [1] 40.71014
From the MAE, we see that the average amount that predicted values deviated from observed values in either the positive or negative direction was 32.47. If the RMSE was close or equal to the standard deviations of observed data, then the model would be considered a good fit. In this case, the RMSE is much higher than the standard deviations. From this and the low significance of the linear regression models, we can see that the Century Model was not a very good fit with the observed data.