How to analyze linear plateau model in R Studio?

# This is the full code to generate the above graph. You can simply copy and paste this code in your R console to obtain the graph. 

# upload required libraries
if(!require(readr)) install.packages("readr")
library (readr)
if(!require(ggplot2)) install.packages("ggplot2")
library(ggplot2)
if(!require(dplyr)) install.packages("dplyr")
library(dplyr)
if(!require(minpack.lm)) install.packages("minpack.lm")
library(minpack.lm)
if(!require(nlraa)) install.packages("nlraa")
library(nlraa)

# upload data
github= paste0("https://raw.githubusercontent.com/agronomy4future/",
                "raw_data_practice/main/sulphur%20application.csv")
dataA=data.frame(read_csv(url(github), show_col_types= FALSE))

# Find reasonable initial values for parameters
fit.lm= lm(yield~sulphur,data=dataA)
a_parameter= fit.lm$coefficients[1]
b_parameter= fit.lm$coefficients[2]
x_mean= mean(dataA$sulphur)

# Define linear plateau function
#a = intercept
#b = slope
#jp = join point or break point 

linplat= function(x, a, b, jp){
         ifelse(x<jp, 
                a+b*x,
                a+b*jp)
}

# Find best fit parameters
model= nls(yield~linplat(sulphur, a, b, jp),
            data=dataA,
            start=list(a=a_parameter,
                       b=b_parameter,
                       jp=x_mean),
            trace=FALSE,
            nls.control(maxiter=1000))
summary(model)

# Create a graph
dataA %>% 
  ggplot(aes(sulphur, yield)) +
  geom_point(size=4, alpha = 0.5) +
  geom_line(stat="smooth",
            method="nlsLM",
            formula=y~SSlinp(x,a,b,jp),
            se=FALSE,
            color="darkred") +
  geom_vline(xintercept=23.2722, linetype="solid", color="grey") + 
  annotate("text", label=paste("sulphur=","23.3","kg/ha"), x=23.3, y=1000, 
         angle=90, hjust=0, vjust=1.5, alpha=0.5)+
  scale_y_continuous(breaks=seq(1000, 1500, 100), limits= c(1000, 1500)) +
  labs(x="Sulphur application (kg/ha)", y="Yield (kg/ha)") +
  theme_classic(base_size=15, base_family="serif") +
  theme(legend.position="none",
        axis.line=element_line(size=0.5, colour="black"))

When we talk about regression, it’s usually about simple linear regression model. This is about the relationship between two variables.


FYI
Simple linear regression (1/5)- correlation and covariance
Simple linear regression (2/5)- slope and intercept of linear regression model


Linear plateau model is similar with simple linear model, but linear plateau model is a segmented model, and this statistical model is interested in the critical value (the x-value above which there is no further increase in y), indicating the plateau value (the statistically highest value that y reaches).

I always talk with data. I’ll run the below R code.

if(!require(readr)) install.packages("readr")
library (readr)

github= paste0("https://raw.githubusercontent.com/agronomy4future/",
                "raw_data_practice/main/sulphur%20application.csv")
dataA=data.frame(read_csv(url(github), show_col_types= FALSE))

print(head(dataA, 3))
  variety sulphur yield
1     CV1   20.67  1286
2     CV1   19.90  1165
3     CV1   19.73  1176
.
.
.

Or you can directly generate the data like below.

variety= rep(c("CV1","CV2","CV3","CV4","CV5"), each=9)
sulphur= c(20.67, 19.9, 19.73,21.16, 21.61, 20.9, 22.36, 21.97, 21.9, 21.34, 21.28, 21.9, 20.7, 22.65, 22.16, 22.03, 20.4, 22.47, 22.65, 21.82, 24.41,  22.34, 24.03, 24.38, 23.6, 23.44, 23.51, 23.89, 24.7, 22.8, 23.08, 25.05, 24.09, 24.3, 25.23, 23.14, 24.06, 26.98, 25.71, 23.93, 25.16, 23.79, 26.34, 24.64, 24.77)
yield= c(1286, 1165, 1176, 1262, 1271, 1221, 1374, 1288, 1285, 1261, 1281, 1302, 1199, 1368, 1340, 1272, 1276, 1329, 1361, 1267, 1380, 1386, 1408, 1380, 1378, 1413, 1400, 1412, 1422, 1381, 1410, 1410, 1381, 1422, 1389, 1403, 1420, 1403, 1400, 1420, 1379, 1393, 1417, 1415, 1383)
dataA= data.frame(variety,sulphur,yield)

print(head(dataA, 3))
  variety sulphur yield
1     CV1   20.67  1286
2     CV1   19.90  1165
3     CV1   19.73  1176
.
.
.
Also, you can download this data in my Github https://github.com/agronomy4future/raw_data_practice/blob/main/sulphur%20application.csv

Let’s assume that this data is about yield data in five different crop variety according to different sulphur application. First, this is about analysis for simple linear regression.

if(!require(ggplot2)) install.packages("ggplot2")
library(ggplot2)

Fig1= ggplot (data=dataA, aes (x= sulphur, y= yield)) +
  geom_smooth (method= lm, level= 0.95, se= FALSE, linetype= 1, linewidth= 0.5, formula= y ~ x) +
  geom_point (aes(shape= variety, fill=variety),col="black", size= 5, stroke = 0.5) +
  scale_shape_manual (values= rep(c(21),5)) +
  scale_fill_manual (values= rep(c("dark grey"),5))+
  scale_x_continuous (breaks= seq(15,30,5), limits=c(15,30)) +
  scale_y_continuous (breaks= seq(500,1800,300), limits=c(500,1800)) +
  labs(x="sulphur application (kg/ha)", y="Yield (kg/ha)") +
  theme_grey (base_size= 15, base_family= "serif")+
  theme (legend.position="none",
                 axis.line= element_line (size= 0.5, colour= "black"))

options(repr.plot.width=5.5, repr.plot.height=5)
print(Fig1)

ggsave("Fig1.png", plot= Fig1, width=5.5, height=5, dpi= 300)
regression= lm (yield~sulphur, data= dataA)
summary (regression)
Call:
lm(formula = yield ~ sulphur, data = dataA)

Residuals:
    Min      1Q  Median      3Q     Max 
-85.236 -25.614  -1.877  30.319  64.933 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  516.204     78.601   6.567 5.46e-08 ***
sulphur       36.028      3.402  10.591 1.47e-13 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 38.79 on 43 degrees of freedom
Multiple R-squared:  0.7229,	Adjusted R-squared:  0.7164 
F-statistic: 112.2 on 1 and 43 DF,  p-value: 1.467e-13

We obtained the model equation, y= 516.204 + 36.025x

However, this model equation would be different from actual data because the model equation predicts yield increase when sulphur increases, but in the actual data, yield does not increase at certain point.

I’m interestedin this certain point. Therefore, I’d like to know the optimun sulphur application to bring about the grestest yield, using a linear plateau model.



1) Find reasonable initial values for parameters

fit.lm= lm(yield~sulphur,data=dataA)
a_parameter= fit.lm$coefficients[1]
b_parameter= fit.lm$coefficients[2]
x_mean= mean(dataA$sulphur)

> a_parameter
(Intercept) 
516.2043 

> b_parameter
sulphur 
36.02786
 
> x_mean
23.04378

Now, the gereral values for simple linear regresion were calculated again. a_parameter is for intercept, b_parameter is for slope, and x_mean is for average of sulphur.


2) Define linear plateau function

Linear plateau model was defined as below.

#a = intercept
#b = slope
#jp = join point or break point 

linplat= function(x, a, b, jp){
         ifelse(x<jp, 
                a+b*x,
                a+b*jp)
}

3) Find best fit parameters

model= nls(yield~linplat(sulphur, a, b, jp),
            data=dataA,
            start=list(a=a_parameter,
                       b=b_parameter,
                       jp=x_mean),
            trace=FALSE,
            nls.control(maxiter=1000))

summary(model)

----------- or ----------- 

if(!require(minpack.lm)) install.packages("minpack.lm")
library(minpack.lm)

model= nlsLM(formula= yield ~ linplat(sulphur, a, b, jp),
             data= dataA,
             start=list(a=a_parameter,
                        b=b_parameter,
                        jp=x_mean))

summary(model)
Formula: yield ~ linplat(sulphur, a, b, jp)

Parameters:
    Estimate Std. Error t value Pr(>|t|)    
a  -125.3667   115.2847  -1.087    0.283    
b    65.5958     5.3061  12.362 1.39e-15 ***
jp   23.2722     0.1683 138.276  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 24.34 on 42 degrees of freedom

Number of iterations to convergence: 4 
Achieved convergence tolerance: 2.832e-09

We obtained the linear plateau model equation. The below code is the whole code to obtain this model equation.


Tip!! nlraa() package

if(!require(nlraa)) install.packages("nlraa")
library(nlraa)

model= nlsLM (formula=yield~SSlinp (sulphur,a,b,jp),data= dataA)
summary (model)
Formula: yield ~ SSlinp(sulphur, a, b, jp)

Parameters:
    Estimate Std. Error t value Pr(>|t|)    
a  -125.3667   115.2847  -1.087    0.283    
b    65.5958     5.3061  12.362 1.39e-15 ***
jp   23.2722     0.1683 138.276  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 24.34 on 42 degrees of freedom

Number of iterations to convergence: 2 
Achieved convergence tolerance: 1.49e-08

If we use nlraa(), we can obtain the model eqution at once without extra coding. However, it would be better to follow what I suggested step by step to understand the concept of linear plateau model.



How to interpret linear plateau model?

Formula: yield ~ linplat(sulphur, a, b, jp)

Parameters:
    Estimate Std. Error t value Pr(>|t|)    
a  -125.3667   115.2847  -1.087    0.283    
b    65.5958     5.3061  12.362 1.39e-15 ***
jp   23.2722     0.1683 138.276  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 24.34 on 42 degrees of freedom

Number of iterations to convergence: 4 
Achieved convergence tolerance: 2.832e-09

In the model equation; y= – 125.4 + 65.6, the important thing is critical value, 23.3. Statistically, at this point, yield does not increase although sulphur application increases. Therefore, critical value is regarded as the highest value that yield reaches.

Therefore, the model equation is expressed as below.

y= -125.4 + 65.6x if x < 23.3

and when sulphur application is 23.3, yield would be

y= -125.3667 + 65.5958*23.2722 ≈ 1401.2

That is, when 23.3 S kg/ha was applied, yield would be 1,401.2 kg/ha and no more yield increases, indicating the optimum sulphur application would be 23.3 kg/ha.


How to create a graph?

Using the below code, we can easily draw a graph for linear plateau.

if(!require(ggplot2)) install.packages("ggplot2")
library(ggplot2)
if(!require(dplyr)) install.packages("dplyr")
library(dplyr)
if(!require(minpack.lm)) install.packages("minpack.lm")
library(minpack.lm)
if(!require(nlraa)) install.packages("nlraa")
library(nlraa)

Fig1= dataA %>% 
  ggplot(aes(sulphur, yield)) +
  geom_point(size=4, alpha = 0.5) +
  geom_line(stat="smooth",
            method="nlsLM",
            formula=y~SSlinp(x,a,b,jp),
            se=FALSE,
            color="darkred") +
  scale_y_continuous(breaks=seq(1000, 1500, 100), limits= c(1000, 1500)) +
  labs(x="Sulphur application (kg/ha)", y="Yield (kg/ha)") +
  theme_classic(base_size=15, base_family="serif") +
  theme(legend.position="none",
        axis.line=element_line(size=0.5, colour="black"))

options(repr.plot.width=5.5, repr.plot.height=5)
print(Fig1)

ggsave("Fig1.png", plot= Fig1, width=5.5, height=5, dpi= 300)

The breaking point is 23.3 and at this point, yield would be 1,401.2

To visualize more clearly, more code will be added.

if(!require(ggplot2)) install.packages("ggplot2")
library(ggplot2)
if(!require(dplyr)) install.packages("dplyr")
library(dplyr)
if(!require(minpack.lm)) install.packages("minpack.lm")
library(minpack.lm)
if(!require(nlraa)) install.packages("nlraa")
library(nlraa)

Fig2= dataA %>% 
  ggplot(aes(sulphur, yield)) +
  geom_point(size=4, alpha = 0.5) +
  geom_line(stat="smooth",
            method="nlsLM",
            formula=y~SSlinp(x,a,b,jp),
            se=FALSE,
            color="darkred") +
  geom_vline(xintercept=23.2722, linetype="solid", color="grey") + 
  annotate("text", label=paste("sulphur=","23.3","kg/ha"), x=23.3, y=1000, 
         angle=90, hjust=0, vjust=1.5, alpha=0.5)+
  scale_y_continuous(breaks=seq(1000, 1500, 100), limits= c(1000, 1500)) +
  labs(x="Sulphur application (kg/ha)", y="Yield (kg/ha)") +
  theme_classic(base_size=15, base_family="serif") +
  theme(legend.position="none",
        axis.line=element_line(size=0.5, colour="black"))

options(repr.plot.width=5.5, repr.plot.height=5)
print(Fig2)

ggsave("Fig2.png", plot= Fig2, width=5.5, height=5, dpi= 300)

p-value and pseudo R-squared

# Define null model
nullfunct= function(x, m){m}
m.ini= mean(dataA$sulphur)
null= nls(yield~nullfunct(sulphur, m),
          data=dataA,
          start=list(m=m.ini),
          trace=FALSE,
          nls.control(maxiter=1000))

# Find p-value and pseudo R-squared
if(!require(rcompanion)) install.packages("rcompanion")
library(rcompanion)

print(nagelkerke(model,null))
$Models
                                                                                                                                                                                
Model: "nlsLM, yield ~ linplat(sulphur, a, b, jp), dataA, list(a = a_parameter, b = b_parameter, jp = x_mean), LM, list(50, 1e-05, 0.0009765625, FALSE, FALSE, 0, FALSE), FALSE"
Null:  "nls, yield ~ nullfunct(sulphur, m), dataA, list(m = m.ini), list(1000, 1e-05, 0.0009765625, FALSE, FALSE, 0, FALSE), FALSE, default"                                    

$Pseudo.R.squared.for.model.vs.null
                             Pseudo.R.squared
McFadden                             0.196568
Cox and Snell (ML)                   0.893469
Nagelkerke (Cragg and Uhler)         0.893479

$Likelihood.ratio.test
 Df.diff LogLik.diff  Chisq    p.value
      -2     -50.385 100.77 1.3127e-22

$Number.of.observations
         
Model: 45
Null:  45

$Messages
[1] "Note: For models fit with REML, these statistics are based on refitting with ML"

$Warnings
[1] "None"

Reference
https://gradcylinder.org/post/linear-plateau/
https://rcompanion.org/handbook/I_11.html



Alternative method

We used the code below to create the linear plateau line.

geom_line(stat="smooth",
            method="nlsLM",
            formula=y~SSlinp(x,a,b,jp),
            se=FALSE,
            color="darkred")

However, we can simply use geom_line() to plot the predicted yield.

model= nls(yield~linplat(sulphur, a, b, jp),
            data=dataA,
            start=list(a=a_parameter,
                       b=b_parameter,
                       jp=x_mean),
            trace=FALSE,
            nls.control(maxiter=1000))

In this analysis, let’s predict the yield based on the linear plateau model.

pred_lp = data.frame(sulphur = seq(19, 27, 0.1))  
# This seq value is based on the range of the independent variable (x).

pred_lp$yield = predict(model, newdata = pred_lp)

print(head(pred_lp, 5))
  sulphur    yield
1    19.0 1120.954
2    19.1 1127.514
3    19.2 1134.073
4    19.3 1140.633
5    19.4 1147.192
.
.
.

and let’s calculate the plateau point, which turns out to be 23.3.

jp_val = round(coef(m_lp)["jp"], 1)
print(jp_val)

  jp 
23.3

Let’s plot the graph using a simple geom_line().

if(!require(ggplot2)) install.packages("ggplot2")
library(ggplot2)
if(!require(dplyr)) install.packages("dplyr")
library(dplyr)
if(!require(minpack.lm)) install.packages("minpack.lm")
library(minpack.lm)
if(!require(nlraa)) install.packages("nlraa")
library(nlraa)

Fig3=ggplot(data=dataA, aes(x=sulphur, y=yield)) +
  geom_point(size=4, alpha = 0.5, na.rm = TRUE) +
  geom_line(data = pred_lp, color= "red", linewidth= 0.8, na.rm = TRUE) +
  geom_vline(xintercept=jp_val, linetype="solid", color="grey") + 
  annotate("text", label=paste("sulphur=","23.3","kg/ha"), x=23.3, y=1100, 
           angle=90, hjust=0, vjust=1.5, alpha=0.5, na.rm = TRUE)+
  scale_x_continuous(breaks=seq(20, 28, 2), limits= c(20, 28)) +
  scale_y_continuous(breaks=seq(1100, 1500, 50), limits= c(1100, 1500)) +
  labs(x="Sulphur application (kg/ha)", y="Yield (kg/ha)") +
  theme_classic(base_size=15, base_family="serif") +
  theme(legend.position="none",
        panel.border= element_rect(color="black", fill=NA, linewidth=0.5),
        axis.line= element_line(linewidth=0.5, colour="black"))

options(repr.plot.width=5.5, repr.plot.height=5)
print(Fig3)

ggsave("Fig3.png", plot= Fig3, width=5.5, height=5, dpi= 300)

To create the figure, I used raw data rather than averaged values. Now, let’s group it by variety.

if(!require(ggplot2)) install.packages("ggplot2")
library(ggplot2)
if(!require(dplyr)) install.packages("dplyr")
library(dplyr)
if(!require(minpack.lm)) install.packages("minpack.lm")
library(minpack.lm)
if(!require(nlraa)) install.packages("nlraa")
library(nlraa)

Fig2A= dataA %>% 
  ggplot(aes(sulphur, yield)) +
  geom_point(aes(fill=variety, shape=variety), size=4, alpha = 0.5) +
  scale_fill_manual(values=c("grey25","red","blue","cadetblue","orange"))+
  scale_shape_manual(values=c(21,21,21,21,21))+
  geom_line(stat="smooth",
            method="nlsLM",
            formula=y~SSlinp(x,a,b,jp),
            se=FALSE,
            color="darkred") +
  geom_vline(xintercept=23.2722, linetype="solid", color="grey") + 
  annotate("text", label=paste("sulphur=","23.3","kg/ha"), x=23.3, y=1000, 
         angle=90, hjust=0, vjust=1.5, alpha=0.5)+
  scale_y_continuous(breaks=seq(1000, 1500, 100), limits= c(1000, 1500)) +
  labs(x="Sulphur application (kg/ha)", y="Yield (kg/ha)") +
  theme_classic(base_size=15, base_family="serif") +
  theme(legend.position=c(0.88,0.15),
        legend.title=element_blank(),
        legend.key=element_rect(color="white", fill=alpha(0.5)),
        legend.text=element_text(family="serif", face="plain",
        size=13, color="black"),
        legend.background= element_rect(fill=alpha(0.5)),
        panel.border= element_rect(color="black", fill=NA, linewidth=0.5),
        axis.line= element_line(linewidth=0.5, colour="black"),
        strip.background=element_rect(color="white", 
        linewidth=0.5, linetype="solid"))

options(repr.plot.width=5.5, repr.plot.height=5)
print(Fig2A)

ggsave("Fig2A.png", plot= Fig2, width=5.5, height=5, dpi= 300)

I’ll summarize the data by grouping it by variety.

dataAMean= dataA %>%
       group_by(variety) %>%
       dplyr::summarize(
       across(
        .cols= c(yield, sulphur),
        .fns= list(
         Mean= ~mean(., na.rm= TRUE),
         n= ~length(.),
         se= ~sd(., na.rm= TRUE) / sqrt(length(.)))),
        .groups= "drop") %>%
        as.data.frame()

print(dataAMean)
  variety yield_Mean yield_n yield_se sulphur_Mean sulphur_n sulphur_se
1     CV1   1258.667       9 21.34114     21.13333         9  0.3068659
2     CV2   1292.000       9 16.63998     21.65889         9  0.2591838
3     CV3   1374.778       9 14.52849     23.35333         9  0.3026274
.
.
.

Then, I’ll repeat the same process as above.

# Find reasonable initial values for parameters
fit.lm= lm(yield_Mean~sulphur_Mean, data= dataAMean)
a_parameter= fit.lm$coefficients[1]
b_parameter= fit.lm$coefficients[2]
x_mean= mean(dataAMean$sulphur_Mean)
# Fit the model
linplat= function(x, a, b, jp){ifelse(x<jp, a+b*x, a+b*jp)}
model= nls(data=dataAMean, yield_Mean~linplat(sulphur_Mean, a, b, jp),
       start=list(a=a_parameter, b=b_parameter, jp=x_mean),
       trace=FALSE, nls.control(maxiter=1000))

summary(model)
Formula: yield_Mean ~ linplat(sulphur_Mean, a, b, jp)

Parameters:
    Estimate Std. Error t value Pr(>|t|)    
a  173.94831   43.43324   4.005  0.05706 .  
b   51.45621    1.96808  26.145  0.00146 ** 
jp  23.89187    0.09084 263.002 1.45e-05 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 3.229 on 2 degrees of freedom

Number of iterations to convergence: 3 
Achieved convergence tolerance: 1.064e-06

I’ll calculate the predicted yield.

# Predict and evalulate
pred_lp = data.frame(sulphur_Mean = seq(19, 27, 0.1))
pred_lp$yield_Mean = predict(model, newdata= pred_lp)

print(head(pred_lp, 5))
  sulphur_Mean yield_Mean
1         19.0   1151.616
2         19.1   1156.762
3         19.2   1161.908
4         19.3   1167.053
5         19.4   1172.199
.
.
.
if(!require(ggplot2)) install.packages("ggplot2")
library(ggplot2)
if(!require(dplyr)) install.packages("dplyr")
library(dplyr)
if(!require(minpack.lm)) install.packages("minpack.lm")
library(minpack.lm)
if(!require(nlraa)) install.packages("nlraa")
library(nlraa)

Fig4=ggplot(data=dataAMean, aes(x= sulphur_Mean, y= yield_Mean)) +
  geom_point(size=4, alpha = 0.5, na.rm = TRUE) +

  geom_errorbar(aes(xmin=sulphur_Mean-sulphur_se, 
                    xmax=sulphur_Mean+sulphur_se),
                    position = position_dodge(0.9),                 
                    linewidth=0.5, width=0.5, color="grey25") +

  geom_errorbar(aes(ymin=yield_Mean-yield_se, 
                    ymax=yield_Mean+yield_se),
                    position = position_dodge(0.9),                 
                    linewidth=0.5, width=0.5, color="grey25") +

  geom_line(data= pred_lp, color = "red", linewidth = 0.8, na.rm = TRUE) +
  geom_vline(xintercept=jp_val, linetype="solid", color="grey", na.rm = TRUE) + 
  annotate("text", label=paste("sulphur=","23.3","kg/ha"), x=23.3, y=1100, 
           angle=90, hjust=0, vjust=1.5, alpha=0.5, na.rm = TRUE)+
  scale_x_continuous(breaks=seq(20, 28, 2), limits= c(20, 28)) +
  scale_y_continuous(breaks=seq(1100, 1500, 50), limits= c(1100, 1500)) +
  labs(x="Sulphur application (kg/ha)", y="Yield (kg/ha)") +
  theme_classic(base_size=15, base_family="serif") +
  theme(legend.position="none",
        legend.title=element_blank(),
        legend.key=element_rect(color="white", fill=alpha(0.5)),
        legend.text=element_text(family="serif", face="plain",
        size=13, color="black"),
        legend.background= element_rect(fill=alpha(0.5)),
        panel.border= element_rect(color="black", fill=NA, linewidth=0.5),
        axis.line= element_line(linewidth=0.5, colour="black"),
        strip.background=element_rect(color="white", 
        linewidth=0.5, linetype="solid"))

options(repr.plot.width=5.5, repr.plot.height=5)
print(Fig4)

ggsave("Fig4.png", plot= Fig4, width=5.5, height=5, dpi= 300)


How to analyze the same data as quadratic plateau model? The answer is below.

How to analyze quadratic plateau model in R Studio?

We aim to develop open-source code for agronomy ([email protected])

© 2022 – 2025 https://agronomy4future.com – All Rights Reserved.

Last Updated: 07/24/2026