This tutorial demonstrates the quadratic_plateau() function for
fitting a continuous response model and estimating a critical soil test
value (CSTV). This function fits a segmented regression model that
follows two phases: i) a curvilinear phase described as
y = a + b * x + c * x^2
, followed by ii) a plateau phase
(Bullock and Bullock, 1994) were the ry
response to
increasing stv
becomes NULL (flat), described as a plateau
y = a + b*Xc + c*Xc
, where y
represents the
fitted crop relative yield, x
the soil test value,
a
the intercept (ry when stv = 0) , b
the
linear slope (as the change in ry per unit of soil nutrient supply or
nutrient added), c
the quadratic coefficient (giving the
curve shape), and X_c
the join point when the plateau phase
starts (i.e. the CSTV).
This approach is a bit more complex than linear-plateau, but the
curvature of the response brings more biological sense. Similar to
linear-plateau, disadvantages are that: i) the user does not have
control to estimate the CSTV (the Xc
parameter) for an
specific ry level; and ii) the default confidence interval estimation of
the CSTV
is generally unreliable (based on symmetric Wald’s
intervals). We recommend the user to use a re-sampling technique
(e.g. bootstrapping) for a more reliable confidence interval estimation
for parameters and CSTV (for examples on bootstrapping, see nlraa package
vignette. The quadratic_plateau()
function works
automatically with self-starting initial values to facilitate the model
convergence.
Load your dataframe with soil test value and relative yield data.
Specify the following arguments into the function
-quadratic_plateau()-:
(a). data
(optional),
(b). stv
(soil test value) and ry
(relative
yield) columns or vectors,
(c). target
(optional) if want to know stv level needed
for a different ry
than the plateau.
(d). tidy
TRUE (produces a data.frame with results) or
FALSE (store results as list),
(e). plot
TRUE (produces a ggplot as main output) or
FALSE (no plot, only results as data.frame),
(f). resid
TRUE (produces plots with residuals analysis)
or FALSE (no plot),
Run and check results.
Check residuals plot, and warnings related to potential
limitations of this model.
Adjust curve plots as desired.
library(soiltestcorr)
Suggested packages
# Install if needed
library(ggplot2) # Plots
library(dplyr) # Data wrangling
library(tidyr) # Data wrangling
library(utils) # Data wrangling
library(data.table) # Mapping
library(purrr) # Mapping
This is a basic example using three different datasets:
# Example 1 dataset
# Fake dataset manually created
<- data.frame("RY" = c(65,80,85,88,90,94,93,96,97,95,98,100,99,99,100),
data_1 "STV" = c(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15))
# Example 2. Native fake dataset from soiltestcorr package
<- soiltestcorr::data_test
data_2
# Example 3. Native dataset from soiltestcorr package, Freitas et al. (1966), used by Cate & Nelson (1971)
<- soiltestcorr::freitas1966 data_3
tidy
= FALSE It returns a LIST (more efficient for multiple fits at once)
# Using dataframe argument, tidy = FALSE -> return a LIST
<-
fit_1_tidy_false ::quadratic_plateau(data = data_1,
soiltestcorrry = RY,
stv = STV,
tidy = FALSE)
::head(fit_1_tidy_false)
utils#> $intercept
#> [1] 61.01
#>
#> $slope
#> [1] 8.59
#>
#> $equation
#> [1] "61 + 8.59x + -0.5x^2 if x<CSTV"
#>
#> $plateau
#> [1] 97.7
#>
#> $target
#> [1] 97.7
#>
#> $CSTV
#> [1] 8.6
tidy
= TRUE It returns a data.frame (more organized results)
# Using dataframe argument, tidy = FALSE -> return a LIST
<-
fit_1_tidy_true ::quadratic_plateau(data = data_1,
soiltestcorrry = RY,
stv = STV,
tidy = TRUE)
fit_1_tidy_true#> intercept slope equation plateau target CSTV LL_cstv
#> 1 61.01 8.59 61 + 8.59x + -0.5x^2 if x<CSTV 97.7 97.7 8.6 6.6
#> UL_cstv CI_type STVt AIC AICc R2
#> 1 10.5 Wald Conf. Interval 8.6 75 79 0.94
You can call stv
and ry
vectors using the
$
.
The tidy
argument still applies for controlling the
output type
<-
fit_1_vectors_list ::quadratic_plateau(ry = data_1$RY,
soiltestcorrstv = data_1$STV,
tidy = FALSE)
<-
fit_1_vectors_tidy ::quadratic_plateau(ry = data_1$RY,
soiltestcorrstv = data_1$STV,
tidy = TRUE)
<-
fit_2 ::quadratic_plateau(data = data_2,
soiltestcorrry = RY,
stv = STV)
::head(fit_2)
utils#> $intercept
#> [1] 44.15
#>
#> $slope
#> [1] 2.86
#>
#> $equation
#> [1] "44.1 + 2.86x + -0.04x^2 if x<CSTV"
#>
#> $plateau
#> [1] 96.4
#>
#> $target
#> [1] 96.4
#>
#> $CSTV
#> [1] 36.5
<-
fit_3 ::quadratic_plateau(data = data_3,
soiltestcorrry = RY,
stv = STK)
::head(fit_3)
utils#> $intercept
#> [1] 12.86
#>
#> $slope
#> [1] 1.91
#>
#> $equation
#> [1] "12.9 + 1.91x + -0.01x^2 if x<CSTV"
#>
#> $plateau
#> [1] 95.3
#>
#> $target
#> [1] 95.3
#>
#> $CSTV
#> [1] 86.2
Note: the stv
column needs to have the same name for
all datasets
#
<- bind_rows(data_1, data_2,
data.all %>% dplyr::rename(STV = STK),
data_3 .id = "id") %>%
::nest(data = c("STV", "RY")) tidyr
# Run multiple examples at once with map()
<-
fit_multiple_map %>%
data.all ::mutate(quadratic_plateau = purrr::map(data,
dplyr~ soiltestcorr::quadratic_plateau(ry = .$RY,
stv = .$STV,
tidy = TRUE)))
::head(fit_multiple_map)
utils#> # A tibble: 3 × 3
#> id data quadratic_plateau
#> <chr> <list> <list>
#> 1 1 <tibble [15 × 2]> <df [1 × 13]>
#> 2 2 <tibble [137 × 2]> <df [1 × 13]>
#> 3 3 <tibble [24 × 2]> <df [1 × 13]>
Alternatively, with group_map, we do not require nested data.
However, it requires to bind_rows and add an id
column
specifying the name of each dataset.
This option return models as lists objects.
<-
fit_multiple_group_map ::bind_rows(data_1, data_2, .id = "id") %>%
dplyr::group_by(id) %>%
dplyr::group_map(~ soiltestcorr::quadratic_plateau(data = .,
dplyrry = RY,
stv = STV,
tidy = TRUE))
::head(fit_multiple_group_map)
utils#> [[1]]
#> intercept slope equation plateau target CSTV LL_cstv
#> 1 61.01 8.59 61 + 8.59x + -0.5x^2 if x<CSTV 97.7 97.7 8.6 6.6
#> UL_cstv CI_type STVt AIC AICc R2
#> 1 10.5 Wald Conf. Interval 8.6 75 79 0.94
#>
#> [[2]]
#> intercept slope equation plateau target CSTV LL_cstv
#> 1 44.15 2.86 44.1 + 2.86x + -0.04x^2 if x<CSTV 96.4 96.4 36.5 29.7
#> UL_cstv CI_type STVt AIC AICc R2
#> 1 43.4 Wald Conf. Interval 36.5 1023 1024 0.53
We can generate a ggplot with the same quadratic_plateau() function.
We just need to specify the argument plot = TRUE
.
<-
quadratic_plateau_plot ::quadratic_plateau(data = data_3,
soiltestcorrry = RY,
stv = STK,
plot = TRUE)
quadratic_plateau_plot
### 3.1.2 Fine-tune the plots
As ggplot object, plots can be adjusted in several ways.
For example, modifying titles
<-
quadratic_plateau_plot_2 +
quadratic_plateau_plot # Main title
ggtitle("My own plot title")+
# Axis titles
labs(x = "Soil Test K (ppm)",
y = "Cotton RY(%)")
quadratic_plateau_plot_2
Or modifying axis scales
<-
quadratic_plateau_plot_3 +
quadratic_plateau_plot_2 # Axis scales
scale_x_continuous(limits = c(20,220),
breaks = seq(0,220, by = 20))+
# Axis limits
scale_y_continuous(limits = c(30,100),
breaks = seq(30,100, by = 10))
quadratic_plateau_plot_3
We can generate a plot with the same quadratic_plateau() function.
We just need to specify the argument resid
= TRUE`.
# Residuals plot
::quadratic_plateau(data = data_3,
soiltestcorrry = RY,
stv = STK,
resid = TRUE)
#> $intercept
#> [1] 12.86
#>
#> $slope
#> [1] 1.91
#>
#> $equation
#> [1] "12.9 + 1.91x + -0.01x^2 if x<CSTV"
#>
#> $plateau
#> [1] 95.3
#>
#> $target
#> [1] 95.3
#>
#> $CSTV
#> [1] 86.2
#>
#> $LL_cstv
#> [1] 45.5
#>
#> $UL_cstv
#> [1] 126.9
#>
#> $CI_type
#> [1] "Wald Conf. Interval"
#>
#> $STVt
#> [1] 86.2
#>
#> $AIC
#> [1] 187
#>
#> $AICc
#> [1] 189
#>
#> $R2
#> [1] 0.68
References
Bullock, D.G. and Bullock, D.S. (1994), Quadratic and
Quadratic-Plus-Plateau Models for Predicting Optimal Nitrogen Rate of
Corn: A Comparison. Agron. J., 86: 191-195.
10.2134/agronj1994.00021962008600010033x