From f0138f1140d35ff620643093c28e277920315ec8 Mon Sep 17 00:00:00 2001 From: PhDMeiwp Date: Sat, 25 Aug 2018 11:04:40 +0900 Subject: [PATCH] version-2.0.3 --- .Rbuildignore | 2 + .gitignore | 3 + DESCRIPTION | 26 ++ NAMESPACE | 12 + NEWS | 25 ++ R/SSexp2P.R | 45 +++ R/SSexp3P.R | 52 +++ R/SSpower2P.R | 47 +++ R/SSpower3P.R | 60 +++ R/trendline.R | 478 +++++++++++++++++++++++ R/trendline_summary.R | 588 +++++++++++++++++++++++++++++ README.md | 298 +++++++++++++++ basicTrendline.Rproj | 21 ++ docs/images/basicTrendline.hex.png | Bin 0 -> 15924 bytes docs/images/case1.png | Bin 0 -> 4283 bytes docs/images/case2.png | Bin 0 -> 4730 bytes docs/images/case3.png | Bin 0 -> 3891 bytes docs/images/case4.png | Bin 0 -> 3731 bytes docs/images/case5.png | Bin 0 -> 4613 bytes docs/images/case6.png | Bin 0 -> 5116 bytes docs/images/case7.png | Bin 0 -> 3288 bytes docs/images/case8.png | Bin 0 -> 4662 bytes man/SSexp2P.Rd | 34 ++ man/SSexp3P.Rd | 34 ++ man/SSpower2P.Rd | 34 ++ man/SSpower3P.Rd | 34 ++ man/trendline.Rd | 134 +++++++ man/trendline_summary.Rd | 65 ++++ 28 files changed, 1992 insertions(+) create mode 100644 .Rbuildignore create mode 100644 .gitignore create mode 100644 DESCRIPTION create mode 100644 NAMESPACE create mode 100644 NEWS create mode 100644 R/SSexp2P.R create mode 100644 R/SSexp3P.R create mode 100644 R/SSpower2P.R create mode 100644 R/SSpower3P.R create mode 100644 R/trendline.R create mode 100644 R/trendline_summary.R create mode 100644 README.md create mode 100644 basicTrendline.Rproj create mode 100644 docs/images/basicTrendline.hex.png create mode 100644 docs/images/case1.png create mode 100644 docs/images/case2.png create mode 100644 docs/images/case3.png create mode 100644 docs/images/case4.png create mode 100644 docs/images/case5.png create mode 100644 docs/images/case6.png create mode 100644 docs/images/case7.png create mode 100644 docs/images/case8.png create mode 100644 man/SSexp2P.Rd create mode 100644 man/SSexp3P.Rd create mode 100644 man/SSpower2P.Rd create mode 100644 man/SSpower3P.Rd create mode 100644 man/trendline.Rd create mode 100644 man/trendline_summary.Rd diff --git a/.Rbuildignore b/.Rbuildignore new file mode 100644 index 0000000..91114bf --- /dev/null +++ b/.Rbuildignore @@ -0,0 +1,2 @@ +^.*\.Rproj$ +^\.Rproj\.user$ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..807ea25 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.Rproj.user +.Rhistory +.RData diff --git a/DESCRIPTION b/DESCRIPTION new file mode 100644 index 0000000..b20220b --- /dev/null +++ b/DESCRIPTION @@ -0,0 +1,26 @@ +Package: basicTrendline +Version: 2.0.3 +Date: 2018-07-26 +Title: Add Trendline and Confidence Interval of Basic Regression Models to Plot +Authors@R: c( + person("Weiping", "Mei", email = "meiweipingg@163.com", role = c("aut", "cre", "cph"), comment = c(ORCID = "0000-0001-6400-9862")), + person("Guangchuang", "Yu", email = "guangchuangyu@gmail.com", role = c("aut"), comment = c(ORCID = "0000-0002-6485-8781")), + person("Jiangshan", "Lai", role = c("ctb")), + person("Qiang", "Rao", role = "ctb"), + person("Yu", "Umezawa", role = "ctb") + ) +Maintainer: Weiping Mei +Description: Plot, draw regression line and confidence interval, and show regression equation, R-square and P-value, as simple as possible, by using different models ("line2P", "line3P", "log2P", "exp2P", "exp3P", "power2P", "power3P") built in the 'trendline()' function. +Depends: R (>= 2.1.0) +Imports: + graphics, + stats, + scales, + investr +BugReports: https://github.com/PhDMeiwp/basicTrendline/issues +License: GPL-3 +URL: https://github.com/PhDMeiwp/basicTrendline +LazyData: true +RoxygenNote: 6.0.1 + + diff --git a/NAMESPACE b/NAMESPACE new file mode 100644 index 0000000..a3f4150 --- /dev/null +++ b/NAMESPACE @@ -0,0 +1,12 @@ +# Generated by roxygen2: do not edit by hand + +export(SSexp2P) +export(SSexp3P) +export(SSpower2P) +export(SSpower3P) +export(trendline) +export(trendline_summary) +import(graphics) +import(investr) +import(scales) +import(stats) diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..98742df --- /dev/null +++ b/NEWS @@ -0,0 +1,25 @@ +Changes in version 2.0.3 +------------------------------------------------------------------------------ +Date: 2018-07-26 +* add several arguments to `trendline()` function, including show.equation, show.Rpvalue, Rname, Pname, xname, yname, yhat, CI.fill, CI.level, CI.alpha, CI.color, CI.lty, CI.lwd, ePos.x, ePos.y, las. +* enable to draw confidence interval for regression models (arguments CI.fill, CI.level, etc.) +* add 'show.equation' and show.Rpvale' arguments to enable to choose which parameter to show +* add 'Rname' and 'Pname' arguments to specify the character of R-square and P-vlaue (i.e. R^2 or r^2; P or p) +* add 'xname' and 'ynameto' arguments to specify the character of 'x' and 'y' in the equation +* add 'yhat' argument to enable to add a hat symbol on the top of 'y' in the equation +* add 'ePos.x' and 'ePos.y' arguments to specify the x and y co-ordinates of equation's position +* deleted the 'ePos' argument +* add "Residual Sum of Squares" to the output of 'trendline_summary()' function + + +Changes in version 1.2.0 +------------------------------------------------------------------------------ +Date: 2018-07-13 +* change the expression for `model` of `exp3P` using a supscript +* add `trendline_summary()` function +* add `SSexp2P()` function +* add `SSpower2P` function +* add `Pvalue.corrected` argument in `trendline()` and `trendline_summary()` , for P-vlaue calculation for non-linear regression. +* add `Details` in `trendline()` and `trendline_summary()` +* add `...` argument in `trendline()` as same as those in `plot()` + diff --git a/R/SSexp2P.R b/R/SSexp2P.R new file mode 100644 index 0000000..ddeefc7 --- /dev/null +++ b/R/SSexp2P.R @@ -0,0 +1,45 @@ +#' Self-Starting Nls 'exp2P' Regression Model +#' +#' This selfStart model evaluates the power regression function (formula as: y=a*exp(b*x)). It has an initial attribute that will evaluate initial estimates of the parameters 'a' and 'b' for a given set of data. +#' +#' @usage SSexp2P(predictor, a, b) +#' @param predictor a numeric vector of values at which to evaluate the model. +#' @param a,b The numeric parameters responsing to the exp2P model. +#' @export +#' @examples +#' library(basicTrendline) +#' x<-1:5 +#' y<-c(2,4,8,20,25) +#' xy<-data.frame(x,y) +#' getInitial(y ~ SSexp2P(x,a,b), data = xy) +#' ## Initial values are in fact the converged values +#' +#' fitexp2P <- nls(y~SSexp2P(x,a,b), data=xy) +#' summary(fitexp2P) +#' +#' @author Weiping Mei \email{meiweipingg@163.com} +#' @seealso \code{\link{trendline}}, \code{\link{SSexp3P}}, \code{\link{SSpower3P}}, \code{\link[stats]{nls}}, \code{\link[stats]{selfStart}} + + +# selfStart method for exp2P model (formula as y = a *exp(b*x)) +SSexp2P<-selfStart( + function(predictor,a,b){a*exp(b*predictor)}, + function(mCall,LHS, data) + { + xy <- sortedXyData(mCall[["predictor"]],LHS, data) + + if (min(y)>0){ + lmFit <- lm(log(xy[,"y"]) ~ xy[,"x"]) + coefs <- coef(lmFit) + a <- exp(coefs[1]) #intercept + b <- coefs[2] #slope + value <- c(a, b) + names(value) <- mCall[c("a","b")] + value + }else{stop(" +>>Try to use other selfStart functions. +Because the 'SSexp2P' function need ALL x values greater than 0.") + } + },c("a","b")) + +# getInitial(y~SSexp2P(x,a,b),data = xy) diff --git a/R/SSexp3P.R b/R/SSexp3P.R new file mode 100644 index 0000000..d8534a8 --- /dev/null +++ b/R/SSexp3P.R @@ -0,0 +1,52 @@ +#' Self-Starting Nls 'exp3P' Regression Model +#' +#' This selfStart model evaluates the exponential regression function (formula as: y=a*exp(b*x)+c). It has an initial attribute that will evaluate initial estimates of the parameters a, b, and c for a given set of data. +#' +#' @usage SSexp3P(predictor, a, b, c) +#' @param predictor a numeric vector of values at which to evaluate the model. +#' @param a,b,c Three numeric parameters responsing to the exp3P model. +#' @export +#' @examples +#' library(basicTrendline) +#' x<-1:5 +#' y<-c(2,4,8,16,28) +#' xy<-data.frame(x,y) +#' getInitial(y ~ SSexp3P(x,a,b,c), data = xy) +#' ## Initial values are in fact the converged values +#' +#' fitexp3P <- nls(y~SSexp3P(x,a,b,c), data=xy) +#' summary(fitexp3P) +#' +#' @author Weiping Mei \email{meiweipingg@163.com} +#' @seealso \code{\link{trendline}}, \code{\link{SSexp3P}}, \code{\link{SSpower3P}}, \code{\link[stats]{nls}}, \code{\link[stats]{selfStart}} + +# selfStart method for exp3P model (formula as y = a *exp(b*x)+ c) +SSexp3P<-selfStart( + function(predictor,a,b,c){a*exp(b*predictor)+c}, + function(mCall,LHS, data) + { + xy <- sortedXyData(mCall[["predictor"]],LHS, data) + y=xy[,"y"] + x=xy[,"x"] + adjy=y-min(y)+1 + xadjy=data.frame(x,adjy) + + lmFit <- lm(log(adjy) ~ x) + coefs <- coef(lmFit) + get.b <- coefs[2] #slope + + nlsFit<-nls(adjy~cbind(1+exp(b*x),exp(b*x)), + start = list(b=get.b),data = xadjy,algorithm = "plinear", + nls.control(maxiter = 5000000,minFactor = 10^(-10))) + + coef<-coef(nlsFit) + b<-coef[1] + c<-coef[2]+min(y)-1 + a<-coef[3]+coef[2] + + value <- c(a,b,c) + names(value) <- mCall[c("a","b","c")] + value + },c("a","b","c")) + + # getInitial(y~SSexp3P(x,a,b,c),data = z) diff --git a/R/SSpower2P.R b/R/SSpower2P.R new file mode 100644 index 0000000..beaefca --- /dev/null +++ b/R/SSpower2P.R @@ -0,0 +1,47 @@ +#' Self-Starting Nls 'power2P' Regression Model +#' +#' This selfStart model evaluates the power regression function (formula as: y=a*x^b). It has an initial attribute that will evaluate initial estimates of the parameters 'a' and 'b' for a given set of data. +#' +#' @usage SSpower2P(predictor, a, b) +#' @param predictor a numeric vector of values at which to evaluate the model. +#' @param a,b The numeric parameters responsing to the exp2P model. +#' @export +#' @examples +#' library(basicTrendline) +#' x<-1:5 +#' y<-c(2,4,8,20,25) +#' xy<-data.frame(x,y) +#' getInitial(y ~ SSpower2P(x,a,b), data = xy) +#' ## Initial values are in fact the converged values +#' +#' fitpower2P <- nls(y~SSpower2P(x,a,b), data=xy) +#' summary(fitpower2P) +#' +#' @author Weiping Mei \email{meiweipingg@163.com} +#' @seealso \code{\link{trendline}}, \code{\link{SSexp3P}}, \code{\link{SSpower3P}}, \code{\link[stats]{nls}}, \code{\link[stats]{selfStart}} + + +# selfStart method for power2P model (formula as y = a *x^b) +SSpower2P<-selfStart( + function(predictor,a,b){a*predictor^b}, + function(mCall,LHS, data) +{ + xy <- sortedXyData(mCall[["predictor"]],LHS, data) + + if (min(x)>0){ + lmFit <- lm(log(xy[,"y"]) ~ log(xy[,"x"])) # both x and adjy values should be greater than 0. + coefs <- coef(lmFit) + a <- exp(coefs[1]) #intercept + b <- coefs[2] #slope + + value <- c(a,b) + names(value) <- mCall[c("a","b")] + value + + }else{stop(" +>>Try to use other selfStart functions. +Because the 'SSpower2P' function need ALL x values greater than 0.") + } + },c("a","b")) + +# getInitial(y~SSpower2P(x,a,b),data = xy) diff --git a/R/SSpower3P.R b/R/SSpower3P.R new file mode 100644 index 0000000..fc72975 --- /dev/null +++ b/R/SSpower3P.R @@ -0,0 +1,60 @@ +#' Self-Starting Nls 'power3P' Regression Model +#' +#' This selfStart model evaluates the power regression function (formula as: y=a*x^b+c). It has an initial attribute that will evaluate initial estimates of the parameters a, b, and c for a given set of data. +#' +#' @usage SSpower3P(predictor, a, b, c) +#' @param predictor a numeric vector of values at which to evaluate the model. +#' @param a,b,c Three numeric parameters responsing to the exp3P model. +#' @export +#' @examples +#' library(basicTrendline) +#' x<-1:5 +#' y<-c(2,4,8,20,25) +#' xy<-data.frame(x,y) +#' getInitial(y ~ SSpower3P(x,a,b,c), data = xy) +#' ## Initial values are in fact the converged values +#' +#' fitpower3P <- nls(y~SSpower3P(x,a,b,c), data=xy) +#' summary(fitpower3P) +#' +#' @author Weiping Mei \email{meiweipingg@163.com} +#' @seealso \code{\link{trendline}}, \code{\link{SSexp3P}}, \code{\link{SSpower3P}}, \code{\link[stats]{nls}}, \code{\link[stats]{selfStart}} + +# selfStart method for power3P model (formula as y = a *x^b+ c) +SSpower3P<-selfStart( + function(predictor,a,b,c){a*predictor^b+c}, + function(mCall,LHS, data) + { + xy <- sortedXyData(mCall[["predictor"]],LHS, data) + y=xy[,"y"] + x=xy[,"x"] + + if (min(x)>0){ + + adjy=y-min(y)+1 + xadjy=data.frame(x,adjy) + + lmFit <- lm(log(adjy) ~ log(x)) # both x and adjy values should be greater than 0. + coefs <- coef(lmFit) + get.b <- coefs[2] #slope + + nlsFit<-nls(adjy~cbind(1+x^b,x^b), + start = list(b=get.b),data = xadjy,algorithm = "plinear", + nls.control(maxiter = 5000000,minFactor = 10^(-10))) + + coef<-coef(nlsFit) + b<-coef[1] + c<-coef[2]+min(y)-1 + a<-coef[3]+coef[2] + + value <- c(a,b,c) + names(value) <- mCall[c("a","b","c")] + value + + }else{stop(" +>>Try to use other selfStart functions. +Because the 'SSpower3P' function need ALL x values greater than 0.") + } + },c("a","b","c")) + + # getInitial(y~SSpower3P(x,a,b,c),data = xy) diff --git a/R/trendline.R b/R/trendline.R new file mode 100644 index 0000000..69eb6d3 --- /dev/null +++ b/R/trendline.R @@ -0,0 +1,478 @@ +#' Add Trendline and Show Equation to Plot +#' +#' Plot, draw regression line and confidence interval, and show regression equation, R-square and P-value, as simple as possible, +#' by using different models built in the 'trendline()' function. The function includes the following models in the latest version: +#' "line2P" (formula as: y=a*x+b), "line3P" (y=a*x^2+b*x+c), "log2P" (y=a*ln(x)+b), "exp2P" (y=a*exp(b*x)),"exp3P" (y=a*exp(b*x)+c), "power2P" (y=a*x^b), and "power3P" (y=a*x^b+c). +#' Besides, the summarized result of each fitted model is also output by default. +#' +#' @param x,y the x and y arguments provide the x and y coordinates for the plot. Any reasonable way of defining the coordinates is acceptable. +#' @param model select which model to fit. Default is "line2P". The "model" should be one of c("line2P", "line3P", "log2P", "exp2P", "exp3P", "power2P", "power3P"), their formulas are as follows:\cr "line2P": y=a*x+b \cr "line3P": y=a*x^2+b*x+c \cr "log2P": y=a*ln(x)+b \cr "exp2P": y=a*exp(b*x) \cr "exp3P": y=a*exp(b*x)+c \cr "power2P": y=a*x^b \cr "power3P": y=a*x^b+c +#' @param Pvalue.corrected if P-value corrected or not, the value is one of c("TRUE", "FALSE"). +#' @param linecolor color of regression line. +#' @param lty line type. lty can be specified using either text c("blank","solid","dashed","dotted","dotdash","longdash","twodash") or number c(0, 1, 2, 3, 4, 5, 6). Note that lty = "solid" is identical to lty=1. +#' @param lwd line width. Default is 1. +#' @param show.equation whether to show the regression equation, the value is one of c("TRUE", "FALSE"). +#' @param show.Rpvalue whether to show the R-square and P-value, the value is one of c("TRUE", "FALSE"). +#' @param Rname to specify the character of R-square, the value is one of c(0, 1), corresponding to c(r^2, R^2). +#' @param Pname to specify the character of P-value, the value is one of c(0, 1), corresponding to c(p, P). +#' @param xname to specify the character of "x" in equation, see Examples [case 5]. +#' @param yname to specify the character of "y" in equation, see Examples [case 5]. +#' @param yhat whether to add a hat symbol (^) on the top of "y" in equation. Default is FALSE. +#' @param CI.fill fill the confidance interval? (TRUE by default, see 'CI.level' to control) +#' @param CI.level level of confidence interval to use (0.95 by default) +#' @param CI.alpha alpha value of fill color of confidence interval. +#' @param CI.color line or fill color of confidence interval. +#' @param CI.lty line type of confidence interval. +#' @param CI.lwd line width of confidence interval. +#' @param summary summarizing the model fits. Default is TRUE. +#' @param ePos.x,ePos.y equation position. Default as ePos.x = "topleft". If no need to show equation, set ePos.x = NA. It's same as those in \code{\link[graphics]{legend}}. +#' @param text.col the color used for the equation text. +#' @param eDigit the numbers of digits for equation parameters. Default is 5. +#' @param eSize font size in percentage of equation. Default is 1. +#' @param xlab,ylab labels of x- and y-axis. +#' @param las style of axis labels. (0=parallel, 1=all horizontal, 2=all perpendicular to axis, 3=all vertical) +#' @param ... additional parameters to \code{\link[graphics]{plot}}, such as type, main, sub, pch, col. +#' @import graphics +#' @import stats +#' @import scales +#' @import investr +#' @export +#' @details The linear models (line2P, line3P, log2P) in this package are estimated by \code{\link[stats]{lm}} function, \cr while the nonlinear models (exp2P, exp3P, power2P, power3P) are estimated by \code{\link[stats]{nls}} function (i.e., least-squares method).\cr\cr The argument 'Pvalue.corrected' is workful for non-linear regression only.\cr\cr If "Pvalue.corrected = TRUE", the P-value is calculated by using "Residual Sum of Squares" and "Corrected Total Sum of Squares (i.e. sum((y-mean(y))^2))".\cr If "Pvalue.corrected = TRUE", the P-value is calculated by using "Residual Sum of Squares" and "Uncorrected Total Sum of Squares (i.e. sum(y^2))". +#' @note +#' Confidence intervals for nonlinear regression (i.e., objects of class +#' \code{nls}) are based on the linear approximation described in Bates & Watts (2007) and Greenwell & Schubert-Kabban (2014). +#' +#' @references +#' Bates, D. M., and Watts, D. G. (2007) +#' \emph{Nonlinear Regression Analysis and its Applications}. Wiley. +#' +#' Greenwell B. M., and Schubert-Kabban, C. M. (2014) +#' \emph{investr: An R Package for Inverse Estimation}. The R Journal, 6(1), 90-100. +#' @return NULL +#' @examples +#' library(basicTrendline) +#' x <- c(1, 3, 6, 9, 13, 17) +#' y <- c(5, 8, 11, 13, 13.2, 13.5) +#' +#' # [case 1] default +#' trendline(x, y, model="line2P", ePos.x = "topleft", summary=TRUE, eDigit=5) + +#' # [case 2] draw lines of confidenc interval only (set CI.fill = FALSE) +#' trendline(x, y, model="line3P", CI.fill = FALSE, CI.color = "black", CI.lty = 2, linecolor = "blue") +#' +#' # [case 3] draw trendliine only (set CI.color = NA) +#' trendline(x, y, model="log2P", ePos.x= "top", linecolor = "red", CI.color = NA) +#' +#' # [case 4] show regression equation only (set show.Rpvalue = FALSE) +#' trendline(x, y, model="exp2P", show.equation = TRUE, show.Rpvalue = FALSE) +#' +#' # [case 5] specify the name of parameters in equation +#' # see Arguments c('xname', 'yname', 'yhat', 'Rname', 'Pname'). +#' trendline(x, y, model="exp3P", xname="T", yname=paste(delta^15,"N"), +#' yhat=FALSE, Rname=1, Pname=0, ePos.x = "bottom") +#' +#' # [case 6] change the digits, font size, and color of equation. +#' trendline(x, y, model="power2P", eDigit = 3, eSize = 1.4, text.col = "blue") +#' +#' # [case 7] don't show equation (set ePos.x = NA) +#' trendline(x, y, model="power3P", ePos.x = NA) +#' +#' ### NOT RUN +#' # [case 8] set graphical parameters by par {graphics} +#' +#' par(mgp=c(1.5,0.4,0), mar=c(3,3,1,1), tck=-0.01, cex.axis=0.9) +#' +#' trendline(x, y) +#' +#' dev.off() +#' +#' ### END (NOT RUN) +#' +#' @author Weiping Mei, Guangchuang Yu +#' @seealso \code{\link{trendline}}, \code{\link{SSexp3P}}, \code{\link{SSpower3P}}, \code{\link[stats]{nls}}, \code{\link[stats]{selfStart}}, \code{\link[investr]{plotFit}} + +trendline <- function(x, y, model="line2P", Pvalue.corrected = TRUE, + linecolor = "blue", lty = 1, lwd = 1, + show.equation = TRUE, show.Rpvalue = TRUE, + Rname = 1, Pname = 0, xname = "x", yname = "y", yhat = FALSE, + summary = TRUE, + ePos.x = NULL, ePos.y = NULL, text.col="black", eDigit = 5, eSize = 1, + CI.fill = TRUE, CI.level = 0.95, CI.color = "grey", CI.alpha = 1, CI.lty = 1, CI.lwd = 1, + las = 1, xlab=NULL, ylab=NULL, ...) +{ + model=model + if(is.null(xlab)) xlab = deparse(substitute(x)) else xlab = xlab + if(is.null(ylab)) ylab = deparse(substitute(y)) else ylab = ylab + if(Rname==0) Rname = "r" else Rname = "R" + if(Pname==0) Pname = "p" else Pname = "P" + xname = substitute(xname) + if(yhat == TRUE) yname = substitute(hat(yname)) else yname = substitute(yname) + + OK <- complete.cases(x, y) + x <- x[OK] + y <- y[OK] + z<-data.frame(x,y) + + return <- trendline_summary(x=x, y=y, model=model, Pvalue.corrected=Pvalue.corrected, summary = FALSE, eDigit = eDigit) + a = return$parameter$a + b = return$parameter$b + if (is.null(return$parameter$c)==FALSE){ + c = return$parameter$c + }else{} + if (return$p.value >= 0.0001){ + pval <- return$p.value + pval <- paste("=" , unname(pval)) + }else{ + pval <- "< 0.0001" + } + r2 <- return$R.squared + adjr2<- return$adj.R.squared + + +# 1) model="line2P" +if (model== c("line2P")) + { Pvalue.corrected=TRUE + formula = 'y = a*x + b' + fitting <- lm(y~x) + + if (summary==TRUE){ + trendline_summary(x,y,"line2P", Pvalue.corrected=Pvalue.corrected, eDigit=eDigit) + }else{} + + aa = abs(a) + bb = abs(b) + aa = format(aa, digits = eDigit) + bb = format(bb, digits = eDigit) + + param <- vector('expression',2) + if (aa==1){aa=c("")} + + if (a>0) + { + if (b>=0) + {param[1] <- substitute(expression(italic(yname) == aa~italic(xname) + bb))[2] + }else{param[1] <- substitute(expression(italic(yname) == aa~italic(xname) - bb))[2] + } + }else{ + if (b>=0) + {param[1] <- substitute(expression(italic(yname) == -aa~italic(xname) + bb))[2] + }else{param[1] <- substitute(expression(italic(yname) == -aa~italic(xname) - bb))[2] + } + } + + param[2] <- substitute(expression(italic(Rname)^2 == r2*","~~italic(Pname)~~pval))[2] + + } + +# 2) model="line3P" + if (model== c("line3P")) + { Pvalue.corrected=TRUE + formula = 'y = a*x^2 + b*x + c' + fitting <- lm(y~I(x^2)+x) + + if (summary==TRUE){ + trendline_summary(x,y,"line3P", Pvalue.corrected=Pvalue.corrected, eDigit=eDigit) + }else{} + + + aa = abs(a) + bb = abs(b) + cc = abs(c) + aa = format(aa, digits = eDigit) + bb = format(bb, digits = eDigit) + cc = format(cc, digits = eDigit) + + param <- vector('expression',2) + + if (aa==1){aa=c("")} + if (bb==1){bb=c("")} + + if (a>0) + { + if (b>=0) + { + if(c>=0) + {param[1] <- substitute(expression(italic(yname) == aa~italic(xname)^2 + bb~italic(xname) +cc))[2] + }else{param[1] <- substitute(expression(italic(yname) == aa~italic(xname)^2 + bb~italic(xname) -cc))[2] + } + }else{ + if(c>=0) + {param[1] <- substitute(expression(italic(yname) == aa~italic(xname)^2 - bb~italic(xname) +cc))[2] + }else{param[1] <- substitute(expression(italic(yname) == aa~italic(xname)^2 - bb~italic(xname) -cc))[2] + } + } + + }else{ + if (b>=0) + { + if(c>=0) + {param[1] <- substitute(expression(italic(yname) == -aa~italic(xname)^2 + bb~italic(xname) +cc))[2] + }else{param[1] <- substitute(expression(italic(yname) == -aa~italic(xname)^2 + bb~italic(xname) -cc))[2] + } + }else{ + if(c>=0) + {param[1] <- substitute(expression(italic(yname) == -aa~italic(xname)^2 - bb~italic(xname) +cc))[2] + }else{param[1] <- substitute(expression(italic(yname) == -aa~italic(xname)^2 - bb~italic(xname) -cc))[2] + } + } + + } + + param[2] <- substitute(expression(italic(Rname)^2 == r2*","~~italic(Pname)~~pval*" "))[2] + + } + +# 3) model="log2P" +if (model== c("log2P")) + { + Pvalue.corrected=TRUE + formula = 'y = a*ln(x) + b' + if (summary==TRUE){ + trendline_summary(x,y,"log2P", Pvalue.corrected=Pvalue.corrected, eDigit=eDigit) + }else{} + + if (min(x)>0) + { + fitting <- lm(y~log(x)) + + aa = abs(a) + bb = abs(b) + + param <- vector('expression',2) + + aa = format(aa, digits = eDigit) + bb = format(bb, digits = eDigit) + + if (aa==1){aa=c("")} + + if (a>0) + { + if (b>=0) + { + param[1] <- substitute(expression(italic(yname) == aa~"ln(x)" + bb))[2] + }else{ + param[1] <- substitute(expression(italic(yname) == aa~"ln(x)" - bb))[2] + } + + }else{ + if (b>=0) + { + param[1] <- substitute(expression(italic(yname) == -aa~"ln(x)" + bb))[2] + }else{ + param[1] <- substitute(expression(italic(yname) == -aa~"ln(x)" - bb))[2] + } + } + + param[2] <- substitute(expression(italic(Rname)^2 == r2*","~~italic(Pname)~~pval))[2] + + }else{ + stop(" +'log2P' model need ALL x values greater than 0. Try other models.") + } +} + +# 4.2) model="exp2P" + if (model== "exp2P") + { + formula = 'y = a*exp(b*x)' + fitting <- nls(y~SSexp2P(x,a,b),data=z) + + if (summary==TRUE){ + trendline_summary(x, y, "exp2P", Pvalue.corrected=Pvalue.corrected, eDigit=eDigit) + }else{} + + aa= abs(a) + bb= abs(b) + param <- vector('expression',2) + + aa = format(aa, digits = eDigit) + bb = format(bb, digits = eDigit) + + if (aa==1){aa=c("")} + if (bb==1){bb=c("")} + + if (a>=0) + { + if (b>=0) + { + param[1] <- substitute(expression(italic(yname) == aa~"e"^{bb~italic(xname)}))[2] + }else{ + param[1] <- substitute(expression(italic(yname) == aa~"e"^{-bb~italic(xname)}))[2] + } + + }else{ + if (b>=0) + { + param[1] <- substitute(expression(italic(yname) == -aa~"e"^{bb~italic(xname)}))[2] + }else{ + param[1] <- substitute(expression(italic(yname) == -aa~"e"^{-bb~italic(xname)}))[2] + } + } + + param[2] <- substitute(expression(italic(Rname)^2 == r2*","~~italic(Pname)~~pval))[2] + + } + + +# 4.3) model="exp3P" + if (model== "exp3P") + { + formula = 'y = a*exp(b*x) + c' + fitting <- nls(y~SSexp3P(x,a,b,c),data=z) + + if (summary==TRUE){ + trendline_summary(x, y, "exp3P", Pvalue.corrected=Pvalue.corrected, eDigit=eDigit) + }else{} + + aa= abs(a) + bb= abs(b) + cc= abs(c) + + param <- vector('expression',2) + + aa = format(aa, digits = eDigit) + bb = format(bb, digits = eDigit) + cc = format(cc, digits = eDigit) + + if (aa==1){aa=c("")} + if (bb==1){bb=c("")} + + if (a>=0) + { + if (b>=0) + { + if (c>=0){ + param[1] <- substitute(expression(italic(yname) == aa~"e"^{bb~italic(xname)}~+cc))[2] + }else{ + param[1] <- substitute(expression(italic(yname) == aa~"e"^{bb~italic(xname)}~-cc))[2] + } + }else{ + if (c>=0){ + param[1] <- substitute(expression(italic(yname) == aa~"e"^{-bb~italic(xname)}~+cc))[2] + }else{ + param[1] <- substitute(expression(italic(yname) == aa~"e"^{-bb~italic(xname)}~-cc))[2] + } + } + + }else{ + if (b>=0) + { + if (c>=0){ + param[1] <- substitute(expression(italic(yname) == -aa~"e"^{bb~italic(xname)}~+cc))[2] + }else{ + param[1] <- substitute(expression(italic(yname) == -aa~"e"^{bb~italic(xname)}~-cc))[2] + } + }else{ + if (c>=0){ + param[1] <- substitute(expression(italic(yname) == -aa~"e"^{-bb~italic(xname)}~+cc))[2] + }else{ + param[1] <- substitute(expression(italic(yname) == -aa~"e"^{-bb~italic(xname)}~-cc))[2] + } + } +} + param[2] <- substitute(expression(italic(Rname)^2 == r2*","~~italic(Pname)~~pval))[2] + } + + +# 5.2) model="power2P" +if (model== "power2P") + {formula = 'y = a*x^b' + + if (summary==TRUE){ + trendline_summary(x, y, "power2P", Pvalue.corrected=Pvalue.corrected, eDigit=eDigit) + }else{} + + if (min(x)>0){ + fitting <- nls(y~SSpower2P(x,a,b),data=z) + + aa<-abs(a) + + param <- vector('expression',2) + + aa = format(aa, digits = eDigit) + + if (aa==1){aa=c("")} + + if (a>=0) + { + param[1] <- substitute(expression(italic(yname) == aa~italic(xname)^b))[2] + }else{ + param[1] <- substitute(expression(italic(yname) == -aa~italic(xname)^b))[2] + } + param[2] <- substitute(expression(italic(Rname)^2 == r2*","~~italic(Pname)~~pval))[2] + + }else{ + stop(" + 'power2P' model need ALL x values greater than 0. Try other models.") + } +} + + + # 5.3) model="power3P" +if (model== "power3P") + {formula = 'y = a*x^b + c' + + if (summary==TRUE){ + trendline_summary(x,y,"power3P", Pvalue.corrected=Pvalue.corrected, eDigit=eDigit) + }else{} + + if (min(x)>0){ + fitting <- nls(y~SSpower3P(x,a,b,c),data=z) + + aa<-abs(a) + cc<-abs(c) + + param <- vector('expression',2) + + aa = format(aa, digits = eDigit) + cc = format(cc, digits = eDigit) + + if (aa==1){aa=c("")} + + if (a>=0) + { + if (c>=0){ + param[1] <- substitute(expression(italic(yname) == aa~italic(xname)^b ~ + cc))[2] + }else{ + param[1] <- substitute(expression(italic(yname) == aa~italic(xname)^b ~ - cc))[2] + } + + }else{ + if (c>=0){ + param[1] <- substitute(expression(italic(yname) == -aa~italic(xname)^b ~ + cc))[2] + }else{ + param[1] <- substitute(expression(italic(yname) == -aa~italic(xname)^b ~ - cc))[2] + } + } + param[2] <- substitute(expression(italic(Rname)^2 == r2*","~~italic(Pname)~~pval))[2] + + }else{ + stop(" +'power3P' model need ALL x values greater than 0. Try other models.") + } + +# 100) beyond the built-in models. + +}else{ + Check<-c("line2P","line3P","log2P","exp2P","exp3P","power2P","power3P") + if (!model %in% Check) + stop(" +\"model\" should be one of c(\"lin2P\",\"line3P\",\"log2P\",\"exp2P\",\"exp3P\",\"power2P\",\"power3P\".") +} + +### plot and draw trendline + if (requireNamespace(c("investr", "scales"), quietly = TRUE)){ + investr::plotFit(fitting, interval = "confidence", level = CI.level, data=z, + shade = CI.fill, + col.fit = linecolor, lty.fit = lty, lwd.fit = lwd, + col.conf = scales::alpha(CI.color, alpha = CI.alpha),lty.conf = CI.lty, lwd.conf = CI.lwd, + las = las, xlab = xlab, ylab = ylab, ...) + } + +### show legend + if (show.equation == TRUE) param[1] = param[1] else param[1]=NULL + if (show.Rpvalue == TRUE) param[2] = param[2] else param[2]=NULL + if (is.null(ePos.x)) ePos.x = "topleft" else ePos.x = ePos.x + legend(ePos.x, ePos.y, text.col = text.col, legend = param, cex = eSize, bty = 'n') + +} diff --git a/R/trendline_summary.R b/R/trendline_summary.R new file mode 100644 index 0000000..855fb35 --- /dev/null +++ b/R/trendline_summary.R @@ -0,0 +1,588 @@ +#' Summarized Results of Each Regression Model +#' +#' Summarizing the results of each regression model which built in the 'trendline()' function. The function includes the following models in the latest version: +#' "line2P" (formula as: y=a*x+b), "line3P" (y=a*x^2+b*x+c), "log2P" (y=a*ln(x)+b), "exp2P" (y=a*exp(b*x)),"exp3P" (y=a*exp(b*x)+c), "power2P" (y=a*x^b), and "power3P" (y=a*x^b+c). +#' +#' @param x,y the x and y arguments provide the x and y coordinates for the plot. Any reasonable way of defining the coordinates is acceptable. +#' @param model select which model to fit. Default is "line2P". The "model" should be one of c("line2P", "line3P", "log2P", "exp2P", "exp3P", "power2P", "power3P"), their formulas are as follows:\cr "line2P": y=a*x+b \cr "line3P": y=a*x^2+b*x+c \cr "log2P": y=a*ln(x)+b \cr "exp2P": y=a*exp(b*x) \cr "exp3P": y=a*exp(b*x)+c \cr "power2P": y=a*x^b \cr "power3P": y=a*x^b+c +#' @param Pvalue.corrected if P-value corrected or not, the vlaue is one of c("TRUE", "FALSE"). +#' @param summary summarizing the model fits. Default is TRUE. +#' @param eDigit the numbers of digits for summarized results. Default is 5. +#' @import stats +#' @export +#' @details The linear models (line2P, line3P, log2P) in this package are estimated by \code{\link[stats]{lm}} function, \cr while the nonlinear models (exp2P, exp3P, power2P, power3P) are estimated by \code{\link[stats]{nls}} function (i.e., least-squares method).\cr\cr The argument 'Pvalue.corrected' is workful for non-linear regression only.\cr\cr If "Pvalue.corrected = TRUE", the P-vlaue is calculated by using "Residual Sum of Squares" and "Corrected Total Sum of Squares (i.e. sum((y-mean(y))^2))".\cr If "Pvalue.corrected = TRUE", the P-vlaue is calculated by using "Residual Sum of Squares" and "Uncorrected Total Sum of Squares (i.e. sum(y^2))". + +#' @return R^2, indicates the R-Squared value of each regression model. +#' @return p, indicates the p-value of each regression model. +#' @return N, indicates the sample size. +#' @return AIC or BIC, indicate the Akaike's Information Criterion or Bayesian Information Criterion for fitted model. Click \code{\link[stats]{AIC}} for details. The smaller the AIC or BIC, the better the model. +#' @return RSS, indicate the value of "Residual Sum of Squares". +#' @examples +#' library(basicTrendline) +#' x1<-1:5 +#' x2<- -2:2 +#' x3<- c(101,105,140,200,660) +#' x4<- -5:-1 +#' x5<- c(1,30,90,180,360) +#' +#' y1<-c(2,14,18,19,20) # increasing convex trend +#' y2<- c(-2,-14,-18,-19,-20) # decreasing concave trend +#' y3<-c(2,4,16,38,89) # increasing concave trend +#' y4<-c(-2,-4,-16,-38,-89) # decreasing convex trend +#' y5<- c(600002,600014,600018,600019,600020) # high y values with low range. +#' +#' trendline_summary(x1,y1,model="line2P",summary=TRUE,eDigit=10) +#' trendline_summary(x2,y2,model="line3P",summary=FALSE) +#' trendline_summary(x3,y3,model="log2P") +#' trendline_summary(x4,y4,model="exp3P") +#' trendline_summary(x5,y5,model="power3P") +#' +#' @author Weiping Mei, Guangchuang Yu +#' @seealso \code{\link{trendline}}, \code{\link{SSexp3P}}, \code{\link{SSpower3P}}, \code{\link[stats]{nls}}, \code{\link[stats]{selfStart}} + +trendline_summary <- function(x,y,model="line2P", Pvalue.corrected=TRUE, summary=TRUE, eDigit=5) +{ + model=model + + OK <- complete.cases(x, y) + x <- x[OK] + y <- y[OK] + z<-data.frame(x,y) + z<-na.omit(z) + nrow = nrow(z) + + # 1) model="line2P" + if (model== c("line2P")) + { + Pvalue.corrected=TRUE + + formula = 'y = a*x + b' + + fit<- lm(y~x) + sum.line2P <- summary(fit) + ss.res<-sum((residuals(fit))^2) # Residual Sum of Squares, DF= n-k + + if (summary==TRUE){ + print(sum.line2P,digits=eDigit) + }else{} + + coeff<-sum.line2P$coefficients + a<-coeff[2,1] # slope + b<-coeff[1,1] # intercept + + n<-length(x) + pval <- coeff[2,4] # p-value of parameter "a", indicates the p-value of whole model. + pval<-unname(pval) + r2 <- sum.line2P$r.squared + adjr2<- sum.line2P$adj.r.squared + + a = format(a, digits = eDigit) + b = format(b, digits = eDigit) + r2 = format(r2, digits = eDigit) + adjr2 = format(adjr2, digits = eDigit) + pval = format(pval, digits = eDigit) + + a=as.numeric(a) + b=as.numeric(b) + param.out<- c(list("a"=a,"b"=b)) # for return values + + } + + # 2) model="line3P" + if (model== c("line3P")) + { + Pvalue.corrected=TRUE + + formula = 'y = a*x^2 + b*x + c' + + fit<-lm(y~I(x^2)+x) + + sum.line3P <- summary(fit) + ss.res<-sum((residuals(fit))^2) # Residual Sum of Squares, DF= n-k + + if (summary==TRUE){ + print(sum.line3P,digits=eDigit) + }else{} + + coeff<-coef(sum.line3P) + a<-coeff[2,1] # slope of x.square + b<-coeff[3,1] # slope of x + c<-coeff[1,1] # intercept c + + n<-length(x) + r2<-sum.line3P$r.squared + adjr2 <- sum.line3P$adj.r.squared + + fstat<-sum.line3P$fstatistic + pval<-pf(fstat[1], fstat[2], fstat[3], lower.tail=FALSE) #p-value of whole model. + pval<-unname(pval) + + a = format(a, digits = eDigit) + b = format(b, digits = eDigit) + c = format(c, digits = eDigit) + r2 = format(r2, digits = eDigit) + adjr2 = format(adjr2, digits = eDigit) + pval = format(pval, digits = eDigit) + + a=as.numeric(a) + b=as.numeric(b) + c=as.numeric(c) + param.out<- c(list("a"=a,"b"=b,"c"=c)) + } + + # 3) model="log2P" + if (model== c("log2P")) + { + Pvalue.corrected=TRUE + + formula = 'y = a*ln(x) + b' + + yadj<-y-min(y) #adjust + + if (min(x)>0) + { + if (summary==TRUE){ + fit0<-lm(y~log(x)) + sum.log0<-summary(fit0) + ss.res<-sum((residuals(fit0))^2) # Residual Sum of Squares, DF= n-k + print(sum.log0, digits = eDigit) + }else{} + + fit<-lm(yadj~log(x)) # adjusted y used + sum.log<-summary(fit) + ss.res<-sum((residuals(fit))^2) # Residual Sum of Squares, DF= n-k + a<-sum.log$coefficients[2,1] # slope + b<-sum.log$coefficients[1,1] # intercept + b=b+min(y) #re-adjust + + fstat<-sum.log$fstatistic + pval<-pf(fstat[1], fstat[2], fstat[3], lower.tail=FALSE) #p-value of whole model. + pval<-unname(pval) + + n<-length(x) + r2<-sum.log$r.squared + adjr2 <- sum.log$adj.r.squared + + a = format(a, digits = eDigit) + b = format(b, digits = eDigit) + r2 = format(r2, digits = eDigit) + adjr2 = format(adjr2, digits = eDigit) + pval= format(pval, digits = eDigit) + + a=as.numeric(a) + b=as.numeric(b) + param.out<- c(list("a"=a,"b"=b)) + + }else{ + stop(" + 'log2P' model need ALL x values greater than 0. Try other models.") + } + } + + + # 4.2) model="exp2P" + if (model== c("exp2P")) + { + formula = 'y = a*exp(b*x)' + + n=length(x) + k = 2 # k means the count numbers of parameters(i.e., 'a', 'b' and 'c' in this case) + + fit<-nls(y~SSexp2P(x,a,b),data=z) + sum.exp2P <- summary(fit) # Get the exact value of each parameter. + + ### calculate the F-statistic and p-value for model + ss.res<-sum((residuals(fit))^2) # Residual Sum of Squares, DF= n-k + ss.total.uncor<-sum(y^2) # Uncorrected Total Sum of Squares, DF=n + ss.total.cor<-sum((y-mean(y))^2) # Corrected Total Sum of Squares, DF=n-1 + + if (Pvalue.corrected==TRUE){ + ss.reg <- ss.total.cor - ss.res # Regression Sum of Squares, DF= (n-1)-(n-k) = k-1 in this case + dfR= k-1 + }else{ + ss.reg <- ss.total.uncor - ss.res # Regression Sum of Squares, DF= n-(n-k) = k in this case + dfR= k + } + + dfE= n-k # degrees of freedom for Error (or Residuals) + + Fval=(ss.reg/dfR)/(ss.res/dfE) + pval=pf(Fval,dfR,dfE,lower.tail = F) + pval<-unname(pval) + + RSE<-sum.exp2P$sigma # Residual standard error, type ?summary.nls in R for more detials. + SSE<-(RSE^2)*(n-1) # Sum of Squares for Error, not equal to 'ss.res'. + + adjr2 <- 1-SSE/((var(y))*(n-1)) + r2<-1-(1-adjr2)*((n-k)/(n-1)) + + if (summary==TRUE){ + ### Start print step by step + coeff = coef(sum.exp2P) + # print + cat("\nNonlinear regression model\n") + cat("\nFormula: y = a*exp(b*x)","\n") + df <- sum.exp2P$df + rdf <- df[2L] + cat("\nParameters:\n") + printCoefmat(coeff, digits = eDigit) + cat("\nResidual standard error:", + format(sum.exp2P$sigma, digits = eDigit), "on", rdf, "degrees of freedom","\n") + + convInfo = fit$convInfo + iterations<-convInfo$finIter + tolerance<-convInfo$finTol + + cat("\nNumber of iterations to convergence:", + format(iterations, digits = eDigit)) + cat("\nAchieved convergence tolerance:",format(tolerance, digits = eDigit),"\n") + + cat("\nMultiple R-squared:", + format(r2, digits = eDigit), ", Adjusted R-squared: ", + format(adjr2, digits = eDigit)) + cat("\nF-statistic:", + format(Fval, digits = eDigit), "on", dfR, "and", dfE, "DF, ", "p-value:", format(pval, digits = eDigit), "\n") + ### finished print + }else{} + + coeffs<-sum.exp2P$coefficients + a<-coeffs[1,1] + b<-coeffs[2,1] + + a = format(a, digits = eDigit) + b = format(b, digits = eDigit) + r2 = format(r2, digits = eDigit) + adjr2 = format(adjr2, digits = eDigit) + pval= format(pval, digits = eDigit) + + a=as.numeric(a) + b=as.numeric(b) + param.out<- c(list("a"=a,"b"=b)) + + } + + # 4.3) model="exp3P" + if (model== c("exp3P")) + { + formula = 'y = a*exp(b*x) + c' + + yadj<-y-min(y)+1 + zzz<-data.frame(x,yadj) + + n=length(x) + k = 3 # k means the count numbers of parameters(i.e., 'a', 'b' and 'c' in this case) + + # use selfStart function 'SSexp3P' for y = a *exp(b*x)+ c + # fit model + fit<-nls(yadj~SSexp3P(x,a,b,c),data=zzz) # use 'yadj', in case of extreme high y-values with low range, such as y= c(600002,600014,600018,600019,600020). + sum.exp3P <- summary(fit) # Get the exact value of each parameter. + + ### calculate the F-statistic and p-value for model + ss.res<-sum((residuals(fit))^2) # Residual Sum of Squares, DF= n-k + ss.total.uncor<-sum(y^2) # Uncorrected Total Sum of Squares, DF=n + ss.total.cor<-sum((y-mean(y))^2) # Corrected Total Sum of Squares, DF=n-1 + + if (Pvalue.corrected==TRUE){ + ss.reg <- ss.total.cor - ss.res # Regression Sum of Squares, DF= (n-1)-(n-k) = k-1 in this case + dfR= k-1 + }else{ + ss.reg <- ss.total.uncor - ss.res # Regression Sum of Squares, DF= n-(n-k) = k in this case + dfR= k + } + + dfE= n-k # degrees of freedom for Error (or Residuals) + + Fval=(ss.reg/dfR)/(ss.res/dfE) + pval=pf(Fval,dfR,dfE,lower.tail = F) + pval<-unname(pval) + + RSE<-sum.exp3P$sigma # Residual standard error, type ?summary.nls in R for more detials. + SSE<-(RSE^2)*(n-1) # Sum of Squares for Error, not equal to 'ss.res'. + + adjr2 <- 1-SSE/((var(y))*(n-1)) + r2<-1-(1-adjr2)*((n-k)/(n-1)) + + if (summary==TRUE){ + ### Start print step by step + #re-adjust the output of coefficients. + coeffadj = coef(sum.exp3P) + ab.param<-coeffadj[1:2,] + # re-adjust the Estimate value of parameter c + c.param<-coeffadj[3,] + c.p1<-c.param[1] + c.p1 = c.p1 + min(y)-1 # re-adjust 'Estimate' value + c.se<-c.param[2] # Std.Error value + c.tval<-c.p1/c.se #re-adjust 't-value' + c.pval<-2 * pt(abs(c.tval), n-k, lower.tail = FALSE) #re-adjust 'p-value' + c<-c(c.p1,c.se,c.tval,c.pval) # re-adjust + coeff.re.adj<- rbind(ab.param,c) + + # print + cat("\nNonlinear regression model\n") + cat("\nFormula: y = a*exp(b*x) + c","\n") + df <- sum.exp3P$df + rdf <- df[2L] + cat("\nParameters:\n") + printCoefmat(coeff.re.adj, digits = eDigit) + cat("\nResidual standard error:", + format(sum.exp3P$sigma, digits = eDigit), "on", rdf, "degrees of freedom","\n") + + convInfo = fit$convInfo + iterations<-convInfo$finIter + tolerance<-convInfo$finTol + + cat("\nNumber of iterations to convergence:", + format(iterations, digits = eDigit)) + cat("\nAchieved convergence tolerance:",format(tolerance, digits = eDigit),"\n") + + cat("\nMultiple R-squared:", + format(r2, digits = eDigit), ", Adjusted R-squared: ", + format(adjr2, digits = eDigit)) + cat("\nF-statistic:", + format(Fval, digits = eDigit), "on", dfR, "and", dfE, "DF, ", "p-value:", format(pval, digits = eDigit), "\n") + ### finished print + }else{} + + coeff<-sum.exp3P$coefficients + a<-coeff[1,1] + b<-coeff[2,1] + c<-coeff[3,1]+min(y)-1 + + a = format(a, digits = eDigit) + b = format(b, digits = eDigit) + c = format(c, digits = eDigit) + r2 = format(r2, digits = eDigit) + adjr2 = format(adjr2, digits = eDigit) + pval= format(pval, digits = eDigit) + + a=as.numeric(a) + b=as.numeric(b) + c=as.numeric(c) + param.out<- c(list("a"=a,"b"=b,"c"=c)) + + } + + + # 5.2) model="power2P" + if (model== c("power2P")) + { + formula = 'y = a*x^b' + + n<-length(x) + k = 2 # k means the count numbers of parameters (i.e., a, b and c in this case) + + if (min(x)>0){ + # use selfStart function 'SSpower2P' for y = a *x^b + # trendline model + fit<-nls(y ~ SSpower2P(x,a,b),data=z) + sum.power2P <- summary(fit) + + ### calculate the F-statistic and p-value for model + ss.res<-sum((residuals(fit))^2) # Residual Sum of Squares, DF= n-k + ss.total.uncor<-sum(y^2) # Uncorrected Total Sum of Squares, DF=n + ss.total.cor<-sum((y-mean(y))^2) # Corrected Total Sum of Squares, DF=n-1 + + if (Pvalue.corrected==TRUE){ + ss.reg <- ss.total.cor - ss.res # Regression Sum of Squares, DF= (n-1)-(n-k) = k-1 in this case + dfR= k-1 + }else{ + ss.reg <- ss.total.uncor - ss.res # Regression Sum of Squares, DF= n-(n-k) = k in this case + dfR= k + } + + dfE = n-k # degrees of freedom for Error (or Residuals) + + Fval = (ss.reg/dfR)/(ss.res/dfE) + pval = pf(Fval,dfR,dfE,lower.tail = F) + pval <- unname(pval) + + RSE<-sum.power2P$sigma # Residual standard error, type ?summary.nls in R for more detials. + SSE<-(RSE^2)*(n-1) # Sum of Squares for Error, not equal to 'ss.res'. + + adjr2 <- 1-SSE/((var(y))*(n-1)) + r2 <- 1-(1-adjr2)*((n-k)/(n-1)) + + if (summary==TRUE){ + + ### Start print step by step + coeff = coef(sum.power2P) + ab.param<-coeff[1:2,] + + # print + cat("\nNonlinear regression model\n") + cat("\nFormula: y = a*x^b","\n") + df <- sum.power2P$df + rdf <- df[2L] + cat("\nParameters:\n") + printCoefmat(coeff, digits = eDigit) + cat("\nResidual standard error:", + format(sum.power2P$sigma, digits = eDigit), "on", rdf, "degrees of freedom","\n") + + convInfo = fit$convInfo + iterations<-convInfo$finIter + tolerance<-convInfo$finTol + + cat("\nNumber of iterations to convergence:", + format(iterations, digits = eDigit)) + cat("\nAchieved convergence tolerance:",format(tolerance, digits = eDigit),"\n") + + cat("\nMultiple R-squared:", + format(r2, digits = eDigit), ", Adjusted R-squared: ", + format(adjr2, digits = eDigit)) + cat("\nF-statistic:", + format(Fval, digits = eDigit), "on", dfR, "and", dfE, "DF, ", "p-value:", format(pval, digits = eDigit), "\n") + ### finished print + }else{} + + coeffs<-sum.power2P$coefficients + a<-coeffs[1,1] + b<-coeffs[2,1] + + a = format(a, digits = eDigit) + b = format(b, digits = eDigit) + + r2 = format(r2, digits = eDigit) + adjr2 = format(adjr2, digits = eDigit) + pval = format(pval, digits = eDigit) + + a=as.numeric(a) + b=as.numeric(b) + param.out<- c(list("a"=a,"b"=b)) + }else{ + stop(" + 'power2P' model need ALL x values greater than 0. Try other models.") + } + } + + + # 5.3) model="power3P" + if (model== c("power3P")) + { + formula = 'y = a*x^b + c' + + yadj<-y-min(y)+1 + zzz<-data.frame(x,yadj) + + n<-length(x) + k = 3 # k means the count numbers of parameters (i.e., a, b and c in this case) + + if (min(x)>0){ + # use selfStart function 'SSpower3P' for y = a *x^b+ c + # trendline model + fit<-nls(yadj~SSpower3P(x,a,b,c),data=zzz) # use 'yadj', in case of extreme high y-values with low range. + sum.power3P <- summary(fit) + + ### calculate the F-statistic and p-value for model + ss.res<-sum((residuals(fit))^2) # Residual Sum of Squares, DF= n-k + ss.total.uncor<-sum(y^2) # Uncorrected Total Sum of Squares, DF=n + ss.total.cor<-sum((y-mean(y))^2) # Corrected Total Sum of Squares, DF=n-1 + + if (Pvalue.corrected==TRUE){ + ss.reg <- ss.total.cor - ss.res # Regression Sum of Squares, DF= (n-1)-(n-k) = k-1 in this case + dfR= k-1 + }else{ + ss.reg <- ss.total.uncor - ss.res # Regression Sum of Squares, DF= n-(n-k) = k in this case + dfR= k + } + + dfE= n-k # degrees of freedom for Error (or Residuals) + + Fval=(ss.reg/dfR)/(ss.res/dfE) + pval=pf(Fval,dfR,dfE,lower.tail = F) + pval<-unname(pval) + + RSE<-sum.power3P$sigma # Residual standard error, type ?summary.nls in R for more detials. + SSE<-(RSE^2)*(n-1) # Sum of Squares for Error, not equal to 'ss.res'. + + adjr2 <- 1-SSE/((var(y))*(n-1)) + r2<-1-(1-adjr2)*((n-k)/(n-1)) + + if (summary==TRUE){ + + ### Start print step by step + #re-adjust the output of coefficients. + coeffadj = coef(sum.power3P) + ab.param<-coeffadj[1:2,] + # re-adjust the 'Estimate\ value of parameter 'c' + c.param<-coeffadj[3,] + c.p1<-c.param[1] + c.p1 = c.p1 + min(y)-1 # re-adjust + c.se<-c.param[2] # Std.Error value + c.tval<-c.p1/c.se #re-adjust 't-value' + c.pval<-2 * pt(abs(c.tval), n-k, lower.tail = FALSE) #re-adjust 'p-value' + c<-c(c.p1,c.se,c.tval,c.pval) # re-adjust + coeff.re.adj<- rbind(ab.param,c) + + # print + cat("\nNonlinear regression model\n") + cat("\nFormula: y = a*x^b + c","\n") + df <- sum.power3P$df + rdf <- df[2L] + cat("\nParameters:\n") + printCoefmat(coeff.re.adj, digits = eDigit) + cat("\nResidual standard error:", + format(sum.power3P$sigma, digits = eDigit), "on", rdf, "degrees of freedom","\n") + + convInfo = fit$convInfo + iterations<-convInfo$finIter + tolerance<-convInfo$finTol + + cat("\nNumber of iterations to convergence:", + format(iterations, digits = eDigit)) + cat("\nAchieved convergence tolerance:",format(tolerance, digits = eDigit),"\n") + + cat("\nMultiple R-squared:", + format(r2, digits = eDigit), ", Adjusted R-squared: ", + format(adjr2, digits = eDigit)) + cat("\nF-statistic:", + format(Fval, digits = eDigit), "on", dfR, "and", dfE, "DF, ", "p-value:", format(pval, digits = eDigit), "\n") + ### finished print + }else{} + + coeff<-sum.power3P$coefficients + a<-coeff[1,1] + b<-coeff[2,1] + c<-coeff[3,1] + c<-c+min(y)-1 #re-adjust + + a = format(a, digits = eDigit) + b = format(b, digits = eDigit) + c = format(c, digits = eDigit) + r2 = format(r2, digits = eDigit) + adjr2 = format(adjr2, digits = eDigit) + pval = format(pval, digits = eDigit) + + a=as.numeric(a) + b=as.numeric(b) + c=as.numeric(c) + param.out<- c(list("a"=a,"b"=b,"c"=c)) + }else{ + stop(" + 'power3P' model need ALL x values greater than 0. Try other models.") + } + + # 100) beyond the built-in models. + + }else{ + Check<-c("line2P","line3P","log2P","exp2P","exp3P","power2P","power3P") + if (!model %in% Check) + stop(" + \"model\" should be one of c(\"lin2P\",\"line3P\",\"log2P\",\"exp2P\",\"exp3P\",\"power2P\",\"power3P\".") + } + + nrow = as.numeric(nrow) + r2=as.numeric(r2) + adjr2=as.numeric(adjr2) + pval=as.numeric(pval) + AIC = as.numeric(format(AIC(fit), digits = eDigit)) + BIC = as.numeric(format(BIC(fit), digits = eDigit)) + ss.res=as.numeric(format(ss.res, digits = eDigit)) + + if (summary==TRUE){ + ##print N, AIC, BIC and RSS + cat("\nN:", nrow, ", AIC:", AIC, ", BIC: ", BIC, "\nResidual Sum of Squares: ", ss.res,"\n") + }else{} + + invisible(list(formula=formula, parameter=param.out, R.squared=r2, adj.R.squared=adjr2, p.value = pval, + N = nrow, AIC=AIC, BIC=BIC, RSS=ss.res)) + } diff --git a/README.md b/README.md new file mode 100644 index 0000000..e3d747b --- /dev/null +++ b/README.md @@ -0,0 +1,298 @@ +# basicTrendline: an R package for adding trendline of basic regression models to plot + + + +[![cran version](http://www.r-pkg.org/badges/version/basicTrendline)](http://cran.rstudio.com/web/packages/basicTrendline) +[![rstudio mirror downloads](http://cranlogs.r-pkg.org/badges/grand-total/basicTrendline)](https://github.com/metacran/cranlogs.app) +[![rstudio mirror downloads](http://cranlogs.r-pkg.org/badges/basicTrendline)](https://github.com/metacran/cranlogs.app) +[![HitCount](http://hits.dwyl.io/PhDMeiwp/basicTrendline.svg)](http://hits.dwyl.io/PhDMeiwp/basicTrendline) + + +## Authors + + + +Weiping MEI https://PhDMeiwp.github.io + + +Graduate School of Fisheries and Environmental Sciences, Nagasaki University + +## Citation + +Mei W, Yu G, Lai J, Rao Q, Umezawa Y (2018) basicTrendline: Add Trendline and Confidence Interval of Basic Regression Models to Plot. R package version 2.0.3. http://CRAN.R-project.org/package=basicTrendline + +## Installation + +Get the released version from CRAN: + + install.packages("basicTrendline") + +Or the development version from github: + + install.packages("devtools") + devtools::install_github("PhDMeiwp/basicTrendline@master", force = TRUE) + + +## Changes in version 2.0.3 + +- add several arguments to `trendline()` function, including show.equation, show.Rpvalue, Rname, Pname, xname, yname, yhat, CI.fill, CI.level, CI.alpha, CI.color, CI.lty, CI.lwd, ePos.x, ePos.y, las. +- enable to draw confidence interval for regression models (arguments CI.fill, CI.level, etc.) +- add 'show.equation' and show.Rpvale' arguments to enable to choose which parameter to show +- add 'Rname' and 'Pname' arguments to specify the character of R-square and P-vlaue (i.e. R^2 or r^2; P or p) +- add 'xname' and 'ynameto' arguments to specify the character of 'x' and 'y' in the equation +- add 'yhat' argument to enable to add a hat symbol on the top of 'y' in the equation +- add 'ePos.x' and 'ePos.y' arguments to specify the x and y co-ordinates of equation's position +- deleted the 'ePos' argument +- add "Residual Sum of Squares" to the output of 'trendline_summary()' function + +## Changes in version 1.2.0 + +- change the expression for `model` of `exp3P` using a supscript +- add `trendline_summary()` function +- add `SSexp2P()` function +- add `SSpower2P` function +- add `Pvalue.corrected` argument in `trendline()` and `trendline_summary()` , for P-vlaue calculation for non-linear regression. +- add `Details` in `trendline()` and `trendline_summary()` +- add `...` argument in `trendline()` as same as those in `plot()` + +--- + +# Examples + + library(basicTrendline) + x <- c(1, 3, 6, 9, 13, 17) + y <- c(5, 8, 11, 13, 13.2, 13.5) + +# [case 1] default + trendline(x, y, model="line2P", ePos.x = "topleft", summary=TRUE, eDigit=5) + + + +# [case 2] draw lines of confidenc interval only (set CI.fill = FALSE) + trendline(x, y, model="line3P", CI.fill = FALSE, CI.color = "black", CI.lty = 2, linecolor = "blue") + + + +# [case 3] draw trendliine only (set CI.color = NA) + trendline(x, y, model="log2P", ePos.x= "top", linecolor = "red", CI.color = NA) + + + +# [case 4] show regression equation only (set show.Rpvalue = FALSE) + trendline(x, y, model="exp2P", show.equation = TRUE, show.Rpvalue = FALSE) + + + +# [case 5] specify the name of parameters in equation +** see Arguments c('xname', 'yname', 'yhat', 'Rname', 'Pname') ** + trendline(x, y, model="exp3P", xname="T", yname=paste(delta^15,"N"), + yhat=FALSE, Rname=1, Pname=0, ePos.x = "bottom") + + + +# [case 6] change the digits, font size, and color of equation. + trendline(x, y, model="power2P", eDigit = 3, eSize = 1.4, text.col = "blue") + + + +# [case 7] don't show equation (set ePos.x = NA) + trendline(x, y, model="power3P", ePos.x = NA) + + + +# [case 8] set graphical parameters by par {graphics} + ### NOT RUN + par(mgp=c(1.5,0.4,0), mar=c(3,3,1,1), tck=-0.01, cex.axis=0.9) + + trendline(x, y) + + dev.off() + + ### END (NOT RUN) + + + +--- + +## Description + +Plot, draw regression line and confidence interval, and show regression equation, R-square and P-value, as simple as possible, + +by using different models built in the 'trendline()' function. The function includes the following models in the latest version: + +"line2P" (formula as: y=a\*x+b), + +"line3P" (y=a\*x2+b\*x+c), + +"log2P" (y=a\*ln(x)+b), + +"exp2P" (y=a\*eb\*x), + +"exp3P" (y=a\*eb\*x+c), + +"power2P" (y=a\*xb), + +"power3P" (y=a\*xb+c). + +Besides, the summarized results of each fitted model are also output by default. + +## Usage + + trendline(x, y, model = "line2P", Pvalue.corrected = TRUE, + linecolor = "blue", lty = 1, lwd = 1, + show.equation = TRUE, show.Rpvalue = TRUE, + Rname = 1, Pname = 0, xname = "x", yname = "y", + yhat = FALSE, + summary = TRUE, + ePos.x = NULL, ePos.y = NULL, text.col = "black", eDigit = 5, eSize = 1, + CI.fill = TRUE, CI.level = 0.95, CI.color = "grey", CI.alpha = 1, CI.lty = 1, CI.lwd = 1, + las = 1, xlab = NULL, ylab = NULL, ...) + +## Arguments + +
**x, y**
+the x and y arguments provide the x and y coordinates for the plot. Any reasonable way of defining the coordinates is acceptable. + +
**model**
+select which model to fit. Default is "line2P". The "model" should be one of c("line2P", "line3P", "log2P", "exp3P", "power3P"), their formulas are as follows: +
"line2P": y=a\*x+b +
"line3P": y=a\*x2+b\*x+c +
"log2P": y=a\*ln(x)+b +
"exp2P": y=a\*eb\*x +
"exp3P": y=a\*eb\*x+c +
"power2P": y=a\*xb +
"power3P": y=a\*xb+c + +
**Pvalue.corrected**
+if P-value corrected or not, the vlaue is one of c("TRUE", "FALSE"). + +
**linecolor**
+color of regression line. + +
**lty**
+line type. lty can be specified using either text c("blank","solid","dashed","dotted","dotdash","longdash","twodash") or number c(0, 1, 2, 3, 4, 5, 6). Note that lty = "solid" is identical to lty=1. + +
**lwd**
+line width. Default is 1. + +
**show.equation**
+whether to show the regression equation, the value is one of c("TRUE", "FALSE"). + +
**show.Rpvalue**
+whether to show the R-square and P-value, the value is one of c("TRUE", "FALSE"). + +
**Rname**
+to specify the character of R-square, the value is one of c(0, 1), corresponding to c(r^2, R^2). + +
**Pname**
+to specify the character of P-value, the value is one of c(0, 1), corresponding to c(p, P). + +
**xname**
+to specify the character of "x" in equation, see Examples [case 5]. + +
**yname**
+to specify the character of "y" in equation, see Examples [case 5]. + +
**yhat**
+whether to add a hat symbol (^) on the top of "y" in equation. Default is FALSE. + +
**summary**
+summarizing the model fits. Default is TRUE. + +
**ePos.x, ePos.y**
+equation position. Default as ePos.x = "topleft". If no need to show equation, set ePos.x = NA. It's same as those in legend. + +
**text.col**
+the color used for the legend text. + +
**eDigit**
+the numbers of digits for equation parameters. Default is 5. + +
**eSize**
+font size in percentage of equation. Default is 1. + +
**CI.fill**
+fill the confidance interval? (TRUE by default, see 'CI.level' to control) + +
**CI.level**
+level of confidence interval to use (0.95 by default) + +
**CI.color**
+line or fill color of confidence interval. + +
**CI.alpha**
+alpha value of fill color of confidence interval. + +
**CI.lty**
+line type of confidence interval. + +
**CI.lwd**
+line width of confidence interval. + +
**las**
+style of axis labels. (0=parallel, 1=all horizontal, 2=all perpendicular to axis, 3=all vertical) + +
**xlab, ylab**
+labels of x- and y-axis. + +**...**
+additional parameters to plot,such as type, main, sub, xlab, ylab, col. + +## Details + +The linear models (line2P, line3P, log2P) in this package are estimated by **lm** function, while the **nonlinear models (exp2P, exp3P, power2P, power3P)** are estimated by **nls** function (i.e., **least-squares method**). +
The argument 'Pvalue.corrected' is workful for non-linear regression only. +
If "Pvalue.corrected = TRUE", the P-vlaue is calculated by using "Residual Sum of Squares" and "Corrected Total Sum of Squares (i.e. sum((y-mean(y))^2))". +
If "Pvalue.corrected = TRUE", the P-vlaue is calculated by using "Residual Sum of Squares" and "Uncorrected Total Sum of Squares (i.e. sum(y^2))". + +## Note + +Confidence intervals for nonlinear regression (i.e., objects of class nls) are based on the linear approximation described in Bates & Watts (2007). + +## References + +Bates, D. M., and Watts, D. G. (2007) *Nonlinear Regression Analysis and its Applications*. Wiley. + +Greenwell B. M., and Schubert-Kabban, C. M. (2014) *investr: An R Package for Inverse Estimation*. The R Journal, 6(1), 90-100. + +## Value + +R2, indicates the R-Squared value of each regression model. + +p, indicates the p-value of each regression model. + +AIC or BIC, indicate the Akaike's Information Criterion or Bayesian Information Criterion for fitted model. Click AIC for details. The smaller the AIC or BIC, the better the model. + +RSS, indicates the "Residual Sum of Squares” of regression model. + +--- + +To see examples on how to use "basicTrendline" in R software, you can run the following R code if you have the "basicTrendline" package installed: + + library(basicTrendline) + ?trendline() + + +## Acknowledgements + +We would like to express my special thanks to **Uwe Ligges, Swetlana Herbrandt, and CRAN team** for their very valuable comments to the 'basicTrendline' package. +Our thanks also go to those who contributed R codes by: + +- adding conficende interval for both lm and nls objects: https://github.com/bgreenwell/investr +- adding-regression-line-equation-and-r2-on-graph-1: http://blog.sciencenet.cn/blog-267448-1021594.html +- adding-regression-line-equation-and-r2-on-graph-2: https://stackoverflow.com/questions/7549694/adding-regression-line-equation-and-r2-on-graph +- What is non-linear regression?: https://datascienceplus.com/first-steps-with-non-linear-regression-in-r/ +- adding regression line for nonlinear regression: http://blog.sciencenet.cn/blog-651374-1014133.html +- R codes for 'print.summary.nls of exp3P and power3P' cite from https://github.com/SurajGupta/r-source/blob/master/src/library/stats/R/nls.R + +## Contact + +- If you have any question or comment to this package, tell me at [here](http://meiweiping.cn/en/basicTrendline-an-R-package-for-adding-trendline-of-basic-regression-models-to-plot/). + +- Bugs and feature requests can be filed to https://github.com/PhDMeiwp/basicTrendline/issues. BTW, [Pull requests](https://github.com/PhDMeiwp/basicTrendline/pulls) are also welcome. + +## Appendix + +The **PDF file** of this R package is available at https://cran.r-project.org/web/packages/basicTrendline/index.html + +> 点击进入 [basicTrendline函数包中文介绍入口](http://meiweiping.cn/%E7%94%A8%E4%BA%8E%E5%B8%B8%E8%A7%84%E7%BA%BF%E6%80%A7%E9%9D%9E%E7%BA%BF%E6%80%A7%E6%8B%9F%E5%90%88%E7%9A%84R%E5%87%BD%E6%95%B0%E5%8C%85%EF%BC%88basicTrendline%EF%BC%89%E4%BB%8B%E7%BB%8D/) \ No newline at end of file diff --git a/basicTrendline.Rproj b/basicTrendline.Rproj new file mode 100644 index 0000000..cba1b6b --- /dev/null +++ b/basicTrendline.Rproj @@ -0,0 +1,21 @@ +Version: 1.0 + +RestoreWorkspace: No +SaveWorkspace: No +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX + +AutoAppendNewline: Yes +StripTrailingWhitespace: Yes + +BuildType: Package +PackageUseDevtools: Yes +PackageInstallArgs: --no-multiarch --with-keep.source +PackageRoxygenize: rd,collate,namespace diff --git a/docs/images/basicTrendline.hex.png b/docs/images/basicTrendline.hex.png new file mode 100644 index 0000000000000000000000000000000000000000..5f91a38f2e0458c785f6e716b9295422509cc317 GIT binary patch literal 15924 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 SY7ao)kg~kGT(OL4!2btlik#>G literal 0 HcmV?d00001 diff --git a/docs/images/case1.png b/docs/images/case1.png new file mode 100644 index 0000000000000000000000000000000000000000..c5e29617a9480be45a7a3f919ee5aff0c39108e7 GIT binary patch literal 4283 zcmds5do)zv-yV`8N+w36k|Ir+Qb{A^UWmvHLyaP%%-8)sk|cy;y0}wvH#3Y&?j1@> z5fNiz#@#S(jhJy6G4JVJ>;30_|M{(V{r>vxwach=hP?A`U{tffNRjA*jYds&U9_BC;B^1=ZC8 zMzuiD*x1M*A{o^<#$O79ArOE(0fYG8NihF6Ag+d(1Qm&SU&8o7AUi+)y@Xo5if%(7 zySgtM>DvZp&Sy=hGphHRxE7XX<+UHZqH_hGe?`Yk!P0GVlT&)QcE{mQTM@F0nFp#x zpI?Lwhm>6v2_bAD`iS3v{5;LxYS6XgQ8sM5K0@jt_MixGaXTSGiYu)2pO_f4r$YBV zyh^C+*g5upS6VwZ{ng9>;r+1(N{EOql!hw;HaUKOfxg~e*z z-@s~}zntI-t3LTswGF7~nI24;;kDP*=CNH=dAw6r!R$s_p2gyy;%|ko@vk-q>6WFK zfZaV)RZ+u2x$g4@)Z;Zr$s2^tWph1{E|_vCvTmXkSWNOqYnY_Ob9J%>fPj!;x>UDC zW4doAmk@RwnPV<9LfL-*9B=Jfclq_D34Vzt%d?ahQ!)85#0&NA*OZScb;I|2i9fe~ z61cqIzagVGmfQNqwkPpM=y1up_6tt_CUbU}1k2hay}*0Y4ap8!txJUs%JcivM$t$@ zT9Ky9%@~_UNOlUR{k*jvZS@)`Y0tY%y`~!(|0ntE5~Uz zYE`2*1l5Kb12V6;al5QeAhVWBRPrYlNA)~7J?wxJpWpL?0czm3wwT4i+yHFlpqVK( zC*^K^k$WBP;V|qgF}yS-y=rOoSiGfKqrpG<9y|hlcB9_OLzFoaYItKN0W$__WBJYd z)`zusoV@c~H|$pdE~`?Cl}X81l- z_t3uV`{bbgZ?u#L(k`4Oq(Nmn?l33#=xN&5amY1lm3w|3eYHOyzqVvm*V)k9)MZ;t zugHaC(Ov;IZLxN4mh>6d`@cfUd`q}96XolEB*(w924hZ{R2PGj3A7J5$3dZtK{I1& z%2Gp9y^g%acuR>bSD0Gsy8{>zjRDUj*XOuhIzfgWw|_-kSEiF@hN8~4GEbtPt3Rtv zD#C81=lT^8MRY^ZyT9rm#D|?czvW( zWVt~XWUpsz$fYFC@9+z|TXjX5<0v~#(>9q(bQiezJyCJZc^9$3MjH^$Iy#slhAY+M zH5Ff#q`dht%O5Rt1h5ALf0pjf!MNZQj{fA0#Q9U{iqGh&pN*TqjknV=m46M#WFXZy zev?jn(;OofsrG9?=J3Y&Jbn4$^cW7xW5sN^a5ukrZfiZ3-vnqJ-u!z~Ni`uGQ>%jp zo;7*2jN|ERLE5{&w>^gCX5`z^rX&@*he!I>bBD(IO4Z|DZZ5ZZi}--w43h2yHzu6- zyrBW<$s=K9!cwgZLaZ0~sWCup_RtUVjDK|N+fYJ4VmEN(Vwafx(W-kh0YPc^r&J)u zRM$*7z2=S)`?wwp;vB;=6=6@p{Nh@NXbvzrqmCADIC50vHbxgc>Ick($*7x2mviV(T>1Wi zC{;fIb1raOYX%gWq|~8eyl#AMJo?8-`BP;VRTTn|GWa@5Zl7U1%2~$z4L#G9e>-q~ z&`718x_OtzPtsYb=PsWdV)OpYCprz?S5*A=*^->l34o_zWA^|_lzcm&4G%QoaI9Qk|fPg0jKHp{d2m>yhNW$%ej5IDPsYXG6O}?UcQtc zYP@uZf|z%5{57kN_P3l%(gQ{5UJ_P~p;Sf+xzf+P?^n-$j7Gu5JB4QMX+_xzZkE{00&9*bT=I5KgM)Rvp<5~+6A}S^ZA?Wt=BYXnlRwpQiWK;fOC?z6Fx;7 z+9j8FfTQo_#)+MKMY-945hWY2Wp?b$X^75`|0pJ^UTHv;^OhcDX+hh)+rV{}<=A;M zw+8F(T&JWnOI6{eY#Hgo-&JE_NXY_i7#Ycg5AFJurH+(q4nh=JLr#)u7?p8;tWw@3N_|{<&Zm?ZTPpzrM zh|5Eu$~-)QN{)1sHOaRd$)0NlBNgy%tsT*y-q^9d57%mCwz!Yb{HO+Hl8HERvK{;6 zo;h=hH2DpEIw{R@(NfRbM?Y{gwclc(y?3S0jVAGSMI4BWd610t2(HL<0%WUSEY!1r z*nq-OI%VJR`C$117=YGHyF>rv$){}H{gV|!ecCaFrE)tQ_QonsvnXm=Gf5Q~hRgFt z>_c3@*%h-N$K4{-E};+}P%rHHf@&>)=^mjUKj8j*aL*F#Jq?jY;)JM#=6`;$E19Cc z>A^g4#i*zj&t%3zhjAW#DQNN$J`njd^RU8Kb$==J?n+F2&gA~5;5PW_>a`EHMQ>D8 z?ofP;GIH;GZh!e1J1MCtut`@IhPp0F-D&PbmFsYjQr0`lZL{2!<#yM}P1ju8|4y=0 zn!}RexI#CR+&jxac%5qseJxL+K1%4eaMr+EUwW9`OU?|Gv}&~Jh09mUMjblJJ{zy~ z&0){OqYD?bEN{b*fi5lHrT*58)op|5n%?{=#DT~q^G227d&FhC)Rn-rWajqYfASQM7D)agm>&4m*Q{#Z^UvsCOFHdPEJr$i|+#7Cf)RZm9K$FeJXj3e|9GOEzv@GC`|0!D$+Kf?E7p}Lz5A30 zt3eJZ{bolwXo~>Mz8!>pj)3BNhYQU3tL{0 zf$do&;Z_c*37SLd?%n}KZDdKIo+e;Zi)J{F{%5G43jb1VRe=Ch_TQe#n&NzWDyRfY z4E8tzu$KKCzBFe}jop^UpJe^>3jU1c>H1-VBtG60vYbzEiL}sp5#(Tu!5)Y&G`}vq zQi!@GeCB&2H$9kG>{QVc8SA?CAQNvtJKHRB=T+FWtKOG0`|`0kE%D*vn(PVGKXcwoAiP)M(zDoj`EIH>n=PLG4;KiX{u)inkl=QSmVks g)=&K#tLM#KXVF8M?OhUoe^Wp%8(SKc8r*#NU&LEyX#fBK literal 0 HcmV?d00001 diff --git a/docs/images/case2.png b/docs/images/case2.png new file mode 100644 index 0000000000000000000000000000000000000000..20e09a606d1c18b8abcdd9078a32489e94c7573c GIT binary patch literal 4730 zcmd^DS5(tWx6eV8rhpU!2!e{`&>~GF%0a0DDxFCCdjRPmJwRwSK%_;Aw1e~(N~l3f zP!R!%lt3UrC<20z0;oxpP;%q_)_uA!_u;PZ<(svq?cZrTOkW+&|P!Pxi1VVy9BoK%Vf_i|UNRS6d zFdP61f4-nFWLm?qaY>Wp8;{nAW zp%_k^jlr-<7&e=Pgpx2Gq~AD+#Ab6y9Myj#zd?>dENzO#SrlL3P5TfKNbvpd{Kr5* z^?eZNc&drP4V#F6XoUh%L9*1v)0P5gSNZ%XD~ z$lUi#JnC>cktBpBPcpudn1NS=c5xJX;w23jv&l5^5uO5%w77s8Oe-VO1|XJ0x2x5u zhw!z7Y86Bp7ok>Z?S0OHkb9E7ju8>E*{M35dMu%yOuEqS>})pY4>KBiRgVlXS8q7_ ziJzeA(?su@LT7?XU@u}+vVfhH(M#Cp-)+Qb8)Ld&CPy7@+z`rfY$m;qJdMurw=^b- zgp*cny|eE@?_2i7blv)uo%13zwN|37jO9!rdV{2*{=^Ph`?ZVUWP6$453Ew(qh+IX z#X23Q?5g|e@S49eiHx9D6cJ_jz9fbyvdDmML1YWYLkBDO_FO`#c5$6E7#(i^6teRQ z@gr!m7P?-Wz-&7tYTW6aNRXt(4w6#o$rx3Csf4wAWw!OW&RN%PHm$9_sbPAw5`dP^ zH!=0#_)P{qpY`Us%U);|@WbnlNmwpy!+rg8g?5>13*)c6)%I{ocLtNfJZi5 zRS-4f)knj-Fd>A~$%*Kk1{x(^qbxg24LWcpYBRR+p5NEqAU$z)ek(NVAM|+bY%qCi z+ApP3_CZeP>aS=bq?4?n>=bE4vtzJ+6;%OM>-bvI9p{_t$jHeja7~Op2H%Xhx*r?v zUb?94H2>q;iQSguz?)b?h2>g4zpM~!f2ZA(fhwiDhfv(b0=h4zl*HJ}blEB|k_VXU z745aqo_5pX%l9YN*Ue{7Q@m(_KlTm9>)h^ck-;In)Fan5sbdE{%?yQtB#*|qfxzJj z?WtB`B29pi985_>>&9-hp|&8*x3ya#;I{b@31$s)Sl9yW`&4=_OpIobpc>ju%6ThF z#lNfnvp4A7d24jDwIVg-uRitGfLLNd;iW`#-EOACWb~DpFAuPEI2O}>IDt zekr^{vj<%ElV|BZ(Pd&;%{kp#U{aA?_@Fzwuq?{8RQsy?H&(o}jHap!p|<;S_Xt&* zM5~()8$@YsEvV{w7-{&nxEMxR-ag~C+R!`5qqefRP@q2j^CkSnZB3xZdy>^{cNVd~ z+%RIVL#fg7VxSgeLT`#9HiE3dYUr*%0I;PWcml9XpW5*{=ZEItG*Se(-; zEg&mOdx>s&ypWc(4~qt2`@Y;wQExJCm^g;LJm*Q(EE93j<>dok9dLatxl;cK*tX~4 zOTR0tkA9|oLOnN3N*N2!$NPNOw?pfSR3G7RdBKVUu7b&ee=}q)>OYDbenPFMKT;?& z2SkcONSj#s!@5KJhlEQy;ysJ6r+7&O~${l$xX*~u5z>0`v!b` z^ypmpZ~>SRDRDj&75=HNhzz?i*{dQd+>8@L!F->wYao9cMFw~!X$Dx_Aqs~et2 zHa%m>;9;A&OC=MZOs(4A1=us4fFhe;J@%w~=*lkfqD@`#Yp=)iFNZ)LVn{M^G(pk2}vGyY!uW-VuQEi?4Y7DFS zpqZButirq#8U(Xd;f^hDLB`M=;@D6%T7*(p^ulS> zl3T+;XDT#HjiHe52^|`(9G0jP_uVGr6%Gg-{Z~-nbL!w*pwLIN?AsnB^Sdc1WwLqGpBLt zty&NT7GL3_u7tgb`KVjRt3zK9?u~=7DZucT(Ot91m%%^ZYpg60ilTJSE>NZGO`aq) z%c?c|e@amapiD|oqv)NAWlDrsxn5=%>rxaPchSj{%e)veKdZbsJs@;dVJtTv_BV%l+kDbFc zg`0rOtO%QHL44;-%kbotnLq1*j&Mzloq-6eC#w%WE^a8lW(uNUn?<+9V#}*)+X?kH z{SIfTKV#B-9^iEYPsCI|(Q+#Nd4kUh5~hEDryF*^lIpNF#h0(Fs}|rb;IV{2HX@`C zk=@nt<4PHnPs1-CBl2zyi7SlT7Xu;on}Y zm*Csk<2tLe+-j+q_Ra?~=SEn-!GoY%DwmxKZjjy4$9m4q+09j?YB^;of-a2t4(BHO z#JhYQ337jZEJEru^LZC&((LV6tnGwhm);|SBCD@2-`)l#E>aFORoUjWJ9T@?8~Y?suvqM9oiL)b+z?u%MVWy-`f#uWj@s{_Lck49IQDTYIbT^Q4~ zB7RHYBD6xd?Y8DbHTPR6_Bl%+r2kEzszt}U?V%6fuBC}1Z0`w8My$S4J?6OieNe!d z9x5=HuV_5_L9TL6?$M00(V4cy5}h)Y#j2T57ZX!-{EzA^fjfE#^-PYsrXi>Pm_h998qS9C%2V2?zDXM|ugg`EEO!2a1CqZ=Q0td(YK*#C79@(sGrZd=*oN7lF~0?`-eU^ zQl9aS`x3-2J0}{X0m%g_XJoYzJ4^eN_rbha%n`XJBpN(G=P~LAq{Cz+>wp0BysuI7 zo(U_Bl2xY=cJ3AkA6Z^f63NARuAC@&ki3<7?C-#s$S%+-#p?*;8mw!mMO`fdFY*CWt zVI)12iJC-c^ZY7XxyJXvLvW2hF&>%JXkdJY9)4(EB@pUmQpLxtz0F z@wC-(RvLaTUI}|8J71!dzTBv&DZgRZm7$*o^rXFj%SdP=Xx1t-;;M*mI87^q=RjTy zuT*&8)Aej^T(PsUaWl|L+2)-}hRevFMnv`lUq0W&C&SWA&KN=bg5(AD7^TkMa#j## zW(}vz;OBUVFRH*F%y@$k_=c>9l(3jRjJ&il!{pIa?cmD|!wjxEB25oFa`Y~G@7veI z2z&?OEt$dGz&&mP(@X`cfk`e925_T9;Tz}=(WGniBM&k(`W&s51}09?Xt#-8Yu>V2Wd)jH59cLQ3SumKG}qj=Vb z{Y1_ueFTuS+?D0Fj0$BAJ)33AJ)13Sff*4Fcy16*6=Ky&nYz5C%ynw^@iz92|1ou; z=Enck5%%5)>$0)8l68ZXt@aPVt+NN(dg}iL z5EpaYl1H0-@nh?@;l|%iPF5b_Oi)hmbVh|mHK^^OM!nRj&vk#H6h)V|YnAImgNAzbA7?iQsz{c6t$cPL$mCLT|{m;7kcXiiq1W>DMEu zahij&?A=}gV+g@lv>n%y6-GDI!(I-G*|TG&OV8~V|9?k4c69$@aq-y+_HO@-oJY&gbl; zC6y!*2!yo#8SC>1#AX12*c7&P3mh3=`&|c~GM!NuY~UGzKq3$r1QIp^0s|Wj_K^rA z5`jVf9Ppihgo6awIGDevn4bf7X*3K?L_i=2NZ2q041$1xi5vn)L_;8G7}$_B0+I&1 zA{tG^p@~Es1cF0Aa%dQih{F+yLkWt8duz^M=H5MeygSajzmCJ46@h?-rVv3 zm{9a2iMcm7Hy2Ut+BN-~=3Rv~ePISk*=Z@3cgtpnkhI`lWjSHafRtjhM%5=u)T4!~C zx?V+oxZIQ^Lkbr$O5fcxV~&3+QRY^0CYHBSHXN~A|4W7GxAlW%4kQ7-pCE2Hz@|v* zI>>~;WQCrpBI7cuja{<-Kuroqi#{^uxk#Wcv%@wTw80ZA&rX!MYbLC>MZ5Byn01c+ z4A(WaYAAN>k_G8r^R{oDYQN1l(`eX67sZFy1-FbPA@3E2s1AxwG`n!f+sC<>HPcoW zR*v?3LMo|*_zf+peeG>8;(Np;3wbnKAntJx{s&cXFR z-_J~TR5d=s`%iw!W^=w>+@=I&E70|o49l|a6GBXB%$SSn98N&Af~W90!^QTK4;UZ5 z5fkLT;CdjH)ovKVIYQBw#8IEw%r&iNCR&pYw$7#6c1}|xl)cw`?4oOvXzk=Ia!59k zU&*ejY}4h3tj&0JW*RyAhh+$;`(Aac>g(&_Gk@ z%6fwvJlV?>_ooAsey<~J7TU;Y7ytPKa<|+fw&70ngSnZNmX)j&>_8si+1G!Kph)>5ZLfMu-8_V?__?~77^2&+6@X@uOB`tU8g(bM{p33z zw7bv19nHq^W`_h;sjL13epD%DKM!w0eFe80F#j!c9d!luO0i z;;J6~Sfv!PycEe&dS|>6_L;>B%Ivm4Q(+i9Kqr2;5wgt)p2VmGkn&!Uti@ ztjl0UI3+XHE!fx4gv;z)Rlnd9-&URsse+27FY*m_-uI6vdcTV~DY0Csy-R-O$27w{ z%Txn5shoGtzWVGLp*gvZFUgyiNi(`P0$qjte1!gzm|n#dm3P(!Xh*|&!?D%EhWYwF*Bv;0~&0~C*0m5|u$3jhrozCZ)BSd;iYZFh&DdO6mDlzleo zRYF&Y$y~_AyuQcUDa_FMMN9t8C1h2>c2U8H)d{8Q{BPOG4!*)jNem&@M273;nqN5@ zNSXgV@2q`waFPVLrrSnuYy9zOdBjqNhuvF8=UrS}a`0nD@+G8wqoNX5#NZO+qbUjR z0mW~@H-QoDlp805`b~^xjrMJ1h}dT9pl>(Ith)Hx^K_!XJzhIs8W8Y}m=fAgVsBI) z$`f0zERRwi+Zf5$i|p~^muXC;CeIl|MOfVT+DGh|*%r2hr=`v{W0#lj-9m!ml}ii? zv!pAHG}b;Pu)F_euOjjI2z7fd71JDKYX9}? zi7y>IJW@zGgZgm z$jTl-hEAjGuZSppiQ9>C?|~ti-;U3fF7ZlEP`YJElJR*Zqy}AaSt!)maMQTg0sk)w z+mwD^F4drUk&=#iD=D90D_u#5@M~@y{_HteiZAtRtw5W23|D_V@!Z&)&pV*`lq4X0 zG1X2HlHq@mlhYchWS2^GP?wT>JWt`i@wui@L4eWk4o@g|(k0|hY}&{Pb;?cW5xW)P zSNYpWrl^ta@GbC!NEp0WUp8vuF^A0NpziE-%nR~~x0A&DS_UxvBCiI<#YXO&{~p}P zaBOfqTHh@;aR9a0;4+^voskh%yu@p-Ez<^Wo4J;k(LMhF8dtk6yTr1S9?oQllc90B zs6ffJRGatUX$F@Hkk7sWgUmneT9~0u8C6+Uq1nOcVNlII^#X~KAh4_kdoki# ze4a1i&^hSH%+?a1X?~$#qT_TiQ<_eZNb(dGuy$Z6qnjY2cwH9)B5tB~i9v^xH|BTs zRNZfgD=Lps<1#JLR)#f$h6OBxwsuRu!3-D_0u-+;7Nh_r$Qh>!GZf>-2QR}*gc<4~ zshdER(^ZB_Tsf0*z1Y`gIdzR zj)UhXaLJt@}upKI-R!vweeJyML>+G`M@{ip*=ih-)T3_Wi* z@`Cy|*fRIJ*tDiuJ5!z5RL6N7XZQyr_fuR8Pi0&dhv>lAEEd&UD7g}WZ*K*L2; ztCsWy)UZh!H91A*Wl#HYb`L;V4bpvPr{8~u&mJ}VLR!iF1$R1mD^WfL(kmWts7rn~ zjuk_v-FD%Xc#PdIB-#{4K(;<(QjE-&qD%VO#F9cNYFKdCCNdXbUEhicw5LgkTS-*8bG-}3hhq**~+1IL@% zDu%bYiT_15t2NS1=@7o<=JD=@0FhH!?&0nA`z^Ce;+JBg33u_Mk|slPQfHy_E4a6l z`+eo*?)dboGON4;Ci;e?>L)BK(Q59DzBg+v{%C7#0LhBfg=nN3&))l`1gP(n)BRk4 zMqyd*kPwi*k?X4rwj^HkN>u*Pr&nB%}z(=yyzeF-77s)r8$sNEfCo234t@{}ai)c*uRMwNE z0-lM0u5C3dJPE+rR-?gnS+GW7ftS25zjgGdX|4LMMl*(5p{;_=Y?-lA^>s?Qf8!ks;tZ`$Cu5G6z zOVhA(rUATFyQYDiHSxhH6_M5xWD)h-gNjf4#9i$uf?R``AH{yWyMqnKO4AZ{qf~m? zS6c@b(i8vM?8aT`e@nfmIFLqKgX~8-p}pdyQ)cxEwHj;l$5$FIm>v_dWV3*lT9VOF zSopAFryxA2ZryFJW7*fq8~*<2*qiailY*9--n=iIKREp%!I)KWqEDCFtN&8=yK_!E zD~|qWB$CAEcXiD3-;}TfGV4PV{$C+2ZNARS;Og0wB6KBZr(-A;npV*JYr*(!_%!jyH_Q!qK>=5q&tdcLfR1Z?>k@SA z(JwIbsOc8jn=@C^(5^Xl4u#M|f9Ci1k&7hF!?5=26S{gGbEfX9NO@|->_TnutxM&H zHvbQM9G2?v%cT0$RPh$QL?G#Ml}u?k6vP#SZa*BkE{xn@j&~#iTRteQ-O_tnz_K{1 zdp|52PA&*m-?dr|7+I41pV1r5qsE7%KrLJ$b( zMx+E-(gKDB`J_mRfh}wZ5Q;!ZkQPWJ(sP#QJ1^(yKHPI&?)v6j;~)PRV~#QB!~Dj4 zdc($I=Z-@=5D3Ih%WLL#2!sq4f!O3HyBTIimtUTNMbZtc+gD(TKwuCEJOUv_U@!;_ z9)Tet@bI9+EUaS?c+Ag_gn=0n7xsfa@CZEqrvVQOc;aE64i6~_gCPCWm4ru-xKcVE zLC0h0QaW8Kt*@`=k}zC4o-5^YrBc`?<&vaa`ad<;{*#Lf3q22)BNuqhDHws+)%bI6 zY6&Q~3-6-X(%j_s!>rjM-^kogo8oXfT>lDdJJh!?Hiu852AWHXrBZSGIn3q@3gV96 zfw|pJ$0lFk9+qxWRVvzi>{c@tQLF@*{10@@9J@yiElWl|X_YtSZB-RL-t+%}X17ky z<`osGiwg>ib~6>2%Fn(UgVhwBLdFWcgckLu!jI;=cea>d!JHg!bnnO`AoBdJJ_*+g z9iJ#CDj#r~9()x~F-yA(_%WA7D;5>*4_axiMNo#MZ&)R4Ncj=lm~Yl$4ru2eUu|qH zGXxY(QXIRfl-~q*(%ppWFQEI8y$NQkf6Rp8L`5{g+(=gX=~jQ_X^_)t+hURBpxNw? zJPQugYhQBe89%ziny7aopjtlNF_B_sP}U1K zjL1Z}Fx?1gZ2=@P-Vm(}QE1zTaAQQzXDf8gndMpU3RLv^sPF2ppJedf7FguF%;3wj zG1WQJm5_(4f&kTeOvKjQS>?WM2j~MGQ8LnpY~iAD$lHD6LnS67ahLLCyO5pL#fjPpQ#Z z_QK``7Ws>$o6|VU@nGAobY7ebOCC~sneT~}>?>{@{WVW}sBC?sGbbI2Q{bdVVX5nU zQGIEl6@nuOn6;hD@s19Rz84J$?n%4Nd)V0clI5cT#?oB=9;Wx(k}6*4q6VMn>A~3e z-nPNp04#=(QgM3sbsVqvixV8!Ginj zEznrM+W12x*6oOtzD3^f87dQEpcfJ!Is4RakBsP3Irr*#2`yklIhwEvF{1LOPmfLp zx%TtCB9t;ANoKG*KELlsYYwye;Yu4@Jd-;rq~aJq#@bJT>-^=cM#<8t`Tg9Uw|L2$ zYJf^4hz(X-=S)T?*Xg1*8uiJ{=*r(`mt9S&ZJ~a>>}Y}*8m-u$jMmi$n3I;ey+2n-tw-F*^a3 z1I0-pSyi?lj5b(b>_T>hPLMf7@$bU<=7}x`EkISPh2APJ41S&f%_pA>A|q9tdJh1j zzc<71>ce{p(7DgHuDn{_2^cBZyYjZWx$x{U zKJZT`)Rkwi6$PV$V?JcZSC5;oV4LOo4giX$9>65&D@^MB(WIyaj&DuZ<#3{0<_YPl zcFvo`Q~uq-p(RVP==e4@y5d=o|IjdFJLTd@^zSu(vF)_NWX^%&mH-zEY<;s0Yf|&& zWFL!PwZ5?tA?oQbrZnn%gopm+qUD8MP5MCMhRvG>@+Pcj-gOueXEZZIB#h|EHNwGP zY6~i!m;9;U(lNitYh%UqSs&@IOdQ_3t<`saW(%OXU%MdTZ_ZZ(G*W?nAc2}2@Wq+2 zxS*q%TU{^^p3HCB5--7C(J0J!cgU}<9H5SeeRn|dCXR)eH*Uk1qzvyBlf$$gpSm<= z;zS)bO>x|2+9U4M_fe`lsSY;9$_m_sY-jblJazC%A??*&57_At$uZ+eot18A7iPL- z8<1zQpJjECHTB&g7J4zp+>5O6*7W@vd5)L5LrAU(La_=4`lpE%FC1kc$!JGV7v}!- zz#<(5G*6Ty#U4oQQ3WfP3dTkyrA3yVE;>GL#Gz!U>-{4ao54Y$WFo&STf?hKIwSXz5=iAQRzz4ayQNC=*Ywg#%Ry=Ln#3UVyVmK<0-Ra9zsvrJp@M=Oa zAi?@TKnG(oDU&s0KjQAtW}*T#58df*-p!iY`Fgw~CejzCO2e-emQ(y;ECSX zw^XI+&2)WKrptb$5$mrL2jSL2U)h*;HM#j`w`ofFu1kJE~!Wf;lox-PX z5PoSI-jVQDDXr(Q4<$VL=vB13k=qjFv3s3R*x`hn%hY3uv1RexOkQL8pl+}c(5&Lw zc|n6VUjM4Q10|`s&%5o=5_K`3;y6?Gvc0r8c@=M?2KF@~O;@(hkHZm_kcYypCm>!) zDcssmJPHs#;{2SV4qp=FB0E{X;~HljDsMTwwIyJb%#VEpD3@;L7K*C z^lTXi1Lvk`XrRC7x1`!QIl}l1dZhPg+5~ndpr>*2EDH|EpcR*6=6XJ}rWb4H65_1w zlOE|k=m8WD!d~oR_->nvEVkx4TpZ+F7oX9ALo)OLZ3$>FjQxCcpWk-r8A2$asFJ35 z0(=+2U>x={T=1_fi~ppAqi$`R4rKHPg(GJRb@h`CFYPk2>(%^mUs+Th@20!my~k-7 zC}Gl^G9zz)g;XHdnT4!-UO4tZk+>{4>wzMSp0O;&&a5|4!!Qoa))2Q#o6PAga}_#i zqzhn{EscM$w^(z3+$D6d5_QICe|ss->F}v@MqNfMzXnmQ$)#3QZRwi7Y)QGey$HoT zd@Rva-0ssL(x1nrra9WUc^yf!x54U#Ry}@SEFD8p?aqIV}*w+51i} zL)4C<*~fd8oB%)bX>OamLJ6HHX6X(&R=YOHTCjhMlEWocMh7r z{_M+K{$Axb<1;DM6KF@D-}8KFFW&qH9>JQHrVweE-r7|2TvZMUW^g(4=VZ z$~CSKeR)S$n>^~@8{RkffH$&a(e=xAzPDEzPE>7RPgGsaLtEKR3$Og2sXK@N_jcOZ z5xaX1xp#0nd}whoqiE$^1m)>(GYwCUW`|BJekH2DnbjSdIGjl@IThjF5-VqHFx@`= zaotqLS6#E>Yyg_CmdtGxe+$B?DReSK5rIt7`;4hVZkhyB89F#+E58r-us79FN>p9w zgBU|M*DQOqOqNES?6J?`q^{LhW^s;dF`)m?@FG$9Sf?WOyJ*VrFBcrP@rS&e)<>$R z`s94^dGJz)&D&u3eAy`8eDN930ydUf3Ywa%r)QL@hBfZj0YGd9q1>n m49p$Xt2t`+pLiX8kCx7uza#{BzWDjLw7g#t8OOkOI^!LVKoJtL^gh?1@9URic z2r~>V6@{D{GzO!FoMw!}YZy7t9@o3~r~PGr*!#1ozJXBm{?q z|0j>b;m8aegHJ|6$VfODCoqAWK^7o1;&vAT5pUhServz;>NW&&aKq}diBn|3d|{N&WSwjh2t5l`D;1%_ zRH;5m*U6v4UQ;Uc z;WO#|CK?A503~7Y(tgbSgDXPH{~w8;Zp8g)lhfdq%0AuyzTS(GodH`i;7uX>`SWoS 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 PagLR_-Q_CNn+g8`#`qi& literal 0 HcmV?d00001 diff --git a/docs/images/case6.png b/docs/images/case6.png new file mode 100644 index 0000000000000000000000000000000000000000..de842f2095e665dd951340020cdb85e7757c4801 GIT binary patch literal 5116 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 literal 0 HcmV?d00001 diff --git a/docs/images/case7.png b/docs/images/case7.png new file mode 100644 index 0000000000000000000000000000000000000000..a15f7f2de013fc264415e3e709327e3265bda381 GIT binary patch literal 3288 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 RsQK%MVrS!IU2SzG<)5mfa!~*P literal 0 HcmV?d00001 diff --git a/docs/images/case8.png b/docs/images/case8.png new file mode 100644 index 0000000000000000000000000000000000000000..f64e4dde9011782e9e214bd622186c228fb6649f GIT binary patch literal 4662 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