Exploring power for one-way ANOVA with F-distribution




show with app

# install required packages not yet installed
pckgs <- c('shiny','reshape2','ggplot2','plyr','arm','pwr')
todo <- pckgs[!is.element(pckgs, names(installed.packages()[,"Package"]))]
if(length(todo) > 0) install.packages(todo)  #,repos="http://cran.us.r-project.org")


# load in the packages
library(shiny)
library(ggplot2)  # ggplot2 -> plotting
library(dplyr)     # ddply -> data manipulation
library(pwr)

# server file
shinyServer(function(input,output,session){
	
	# calculate effect sizes per time point
	processChange <- reactive({
		# post hoc
		
		sldBeta <- as.numeric(as.character((input$sldPwr)))
		sldNtotal <- as.numeric(as.character((input$sldNtotal)))
		sldEff <- as.numeric(as.character((input$sldEff)))
		dbAlpha <- as.numeric(as.character(input$dbAlpha))
		dbPred <- as.numeric(as.character(input$dbPred))
		
		if(input$dbFree=="b"){
			sldBeta <- pwr.anova.test(k = dbPred, n = round(sldNtotal/dbPred), f = sldEff, sig.level = dbAlpha, power = )$power
		}
		# apriori
		if(input$dbFree=="es"){
			sldEff <- pwr.anova.test(k = dbPred, n = round(sldNtotal/dbPred), f = , sig.level = dbAlpha, power = sldBeta)$f
		}
		# sensitivity
		if(input$dbFree=="ss"){
			sldNtotal <- dbPred*pwr.anova.test(k = dbPred, n = , f = sldEff, sig.level = dbAlpha, power = sldBeta)$n		
		}
		dfNum <- dbPred - 1
		dfDen <- sldNtotal - dfNum - 1
		outCritF <- qf(1-dbAlpha,dfNum,dfDen)
		outNcp <- sldEff^2 * sldNtotal

		list(sldBeta=sldBeta,sldEff=sldEff,sldNtotal=sldNtotal,dbAlpha=dbAlpha,outCritF=outCritF,outNcp=outNcp,dfNum=dfNum,dfDen=dfDen,dbPred=dbPred)
	})

	
	# output ncp critical F ----------------------------------------------------
	output$txt.out.1 <- renderText({
		inx <- processChange()
		out <- ""
		out <- paste0(out,"<strong>power</strong> <b>",1-round(inx$sldBeta,4),"</b> or type II error (beta): ",round(inx$sldBeta,2),"<br>")
		out <- paste0(out,"total <strong>sample size</strong> (n): ",round(inx$sldNtotal),"<br>")
		out <- paste0(out,"<strong>effect size</strong> (f): ",round(inx$sldEff,2),"<br>")
		out <- paste0(out,"type I error (alpha): ",round(inx$dbAlpha,4),"<br>")
		out
	})
	# output ncp critical F ----------------------------------------------------
	output$txt.out.2 <- renderText({
		inx <- processChange()
		out <- ""
		out <- paste0(out,"<u>critical F value</u> with df ",round(inx$dfNum)," and ",round(inx$dfDen),": ",round(inx$outCritF,3),"<br>")
		out <- paste0(out,"<u>non-centrality parameter</u> (ncp): ",round(inx$outNcp,2),"<br>")
		out <- paste0(out,"ratio between / within variance ",round(inx$sldEff^2,3),"<br>")
		out
	})
	# total sample size
	output$sld.ntotal <- renderUI({
		inx <- processChange()
		txt <- "sample size:"
		if(input$dbFree=="ss") txt <- "sample size SELECTED"
		sldNtotal <- isolate(inx$sldNtotal) 
		if(all(inx$sldBeta==0 & inx$sldEff==.01 & inx$sldNtotal==3)) sldNtotal <- 190
		sliderInput("sldNtotal", txt, min = 3, max = 256, value = sldNtotal)
	})
	# effect size f
	output$sld.eff <- renderUI({
		inx <- processChange()
		txt <- "effect size:"
		if(input$dbFree=="es") txt <- "effect size SELECTED"
		sldEff <- isolate(inx$sldEff) 
		if(all(inx$sldBeta==0 & inx$sldEff==.01 & inx$sldNtotal==3)) sldEff <- .25
		sliderInput("sldEff", txt, min = 0.01, max = 2, value = sldEff, step=.001)
	})
	# type II error, beta
	output$sld.pwr <- renderUI({
		inx <- processChange()
		txt <- "power (type II error ~"
		if(input$dbFree=="b") txt <- "power SELECTED (type II error ~ "
		sldBeta <- isolate(inx$sldBeta)
		if(all(inx$sldBeta==0 & inx$sldEff==.01 & inx$sldNtotal==3)) sldBeta <- .8
		sliderInput("sldPwr", paste0(txt,round(1-sldBeta,2),")"), min = 0.05, max = 1, value = sldBeta,step=.001)
	})
	
	# ----- #
	
	# permanent plot, two rows of F distributions Ho and Ha 
	output$plotHoHa2 <- renderPlot({
		validate(
			need(input$dbFree!="NA", "")
		)
		plotOutput(getHoHa2(), height = 100, width = 450)
	})
	
	# remaining plots (possibly remove some of them, extend others)
	

	# power curve
	output$checkPlotPowercurve <- renderUI({
		checkboxInput("showPowerCurve","Power Curve", FALSE)
	})
	output$plotPowerCurve <- renderPlot({
		validate(
			need(input$dbFree!="NA", "first select free parameter")
		)
		plotOutput(getPowerCurve(), height = 100, width = 450)
	})
	
	# null distribution with rejection area
	output$checkPlotHoF <- renderUI({
		checkboxInput("showPlotHoF","F Ho + rejection area", FALSE)
	})
	output$plotHoF <- renderPlot({
		# if(input$showPlotHoF) 
		validate(
			need(input$dbFree!="NA", "")
		)
		plotOutput(getHoF(), height = 100, width = 450)
	})	
	
	# null and alternative distribution
	output$checkPlotHoHaF <- renderUI({
		validate(
			need(input$dbFree!="NA", "")
		)
		checkboxInput("showPlotHoHaF","F Ho + Ha", FALSE)
	})
	output$plotHoHaF <- renderPlot({
		# if(input$showPlotHoHaF) 
		validate(
			need(input$dbFree!="NA", "")
		)
		plotOutput(getHoHaF(), height = 100, width = 450)
	})

	# ----------------------------------- #
	
	# function to get power, given type I error, number of observations, and predictors and eta-squared as effect size
	fpi <- function(eta2,n,alpha,a){
		f.crit=qf(1-alpha,a-1,n-a)
		lambda=eta2/(1-eta2)*n
		fpi = 1-pf(f.crit,a-1,n-a,lambda)
		return(fpi)
	}
	f2eta2 <- function(f){
		f^2/(1+f^2)
	}
	eta2f <- function(eta2){
		sqrt(eta2/(1-eta2))
	}

	# F-null and alternative distributions on top of each other with rejection areas
	getHoHa2 <- function(){
		inx <- processChange()
		# F-null and alternative distributions - rows
		xmax=15 
		xmax.xx = 30
		xx=seq(0.01,xmax.xx,.001) 
		fxx=df(xx,inx$dfNum,inx$dfDen) 
		mnx=max(fxx)+.2
		par(mar=c(2.5,0,0,0))
		plot(xx,fxx,type="n",xlab="",ylab="",xlim=c(0,xmax),ylim=c(0,2*mnx),xaxt="n",yaxt="n",axes=FALSE)
		lines(xx,fxx,lwd=2) 
		cc=qf(1-inx$dbAlpha,inx$dfNum,inx$dfDen) 
		text((cc+1.5),.2,"reject H0",col="red")  
		text(1.3,.2,"do not reject H0") 
		xxsub=xx[xx>cc] 
		fxxsub=fxx[xx>cc]   
		polygon(x=c(cc,xxsub,rev(xxsub),cc),y=c(df(cc,inx$dfNum,inx$dfDen),fxxsub,0*fxxsub,0),col="red") 
		segments(cc,0,cc,.15) 
		segments(0,0,0,1.1) 
		segments(0,0,xmax,0)
		fxx2=df(xx,inx$dfNum,inx$dfDen,ncp=inx$outNcp) 
		lines(xx,fxx2+mnx,lwd=2) 
		segments(0,mnx,0,mnx+1.1) 
		segments(0,mnx,xmax,mnx) 
		segments(cc,0,cc,1.5*mnx) 
		xs=xx[xx>cc] 
		ys=fxx2[xx>cc] 
		polygon(x=c(cc,xs,rev(xs),cc),y=mnx+c(df(cc,inx$dfNum,inx$dfDen,inx$outNcp),ys,0*ys,0),col="red")
		text(cc,.6+mnx,"true state of nature",cex=1.5,pos=4) 
		text(cc,.6,"decision",cex=1.5,pos=4)
		text(cc-2.9,.3+mnx,substitute(italic(F)[list(dfn,dfd,ncp)],list(dfn=inx$dfNum,dfd=inx$dfDen,ncp=inx$outNcp)),cex=1.3,pos=4) 
		text(cc-2.9,.8,substitute(italic(F)[list(dfn,dfd,ncp)],list(dfn=inx$dfNum,dfd=inx$dfDen,ncp=0)),cex=1.3,pos=4) 
	}
	
	# depends on df num/den, alpha and critical F
	getHoF <- function(){
		inx <- processChange()
		dfNum <- inx$dfNum
		dfDen <- inx$dfDen
		outCritF <- inx$outCritF
		dbAlpha <- inx$dbAlpha
		# F-null distribution with area of rejection
		fVal=seq(0,6,.001)
		fDens=df(fVal,dfNum,dfDen)
		plot(fVal,fDens,type="n",xlab=expression(paste(italic(F)," value")),ylab="Density",xlim=c(0,5.5),ylim=c(0,1),xaxt="n",yaxt="n",axes=FALSE)
		axis(side=1,pos=0,at=c(0,1,2,4,5))
		axis(side=2,pos=0)
		lines(fVal,fDens,lwd=2)
		lines(c(outCritF,outCritF),c(-.05,.0),col="red",lwd=2)
		mtext(substitute(italic(F)[list(dfn,dfd)]^{cfd}==es,list(dfn=dfNum,dfd=dfDen,cfd=1-dbAlpha,es=round(outCritF,2))),side=1,adj=.6,padj=.5,col="red")
		text((outCritF+1),.2,"reject H0",col="red") 
		text(1.3,.2,"do not reject H0")
		fValReject=fVal[fVal>outCritF]
		fDensReject=fDens[fVal>outCritF]  
		polygon(x=c(outCritF,fValReject,rev(fValReject),outCritF),y=c(df(outCritF,dfNum,dfDen),fDensReject,0*fDensReject,0),col="red")
		segments(outCritF,0,outCritF,.15)
	}	
	getPowerCurve <- function(){
		# f = sqrt(eta2/(1-eta2))
		inx <- processChange()
		sldNtotal <- inx$sldNtotal
		dbAlpha <- inx$dbAlpha
		dbPred <- inx$dbPred
		sldBeta <- inx$sldBeta
		sldEff <- inx$sldEff
		outEta2 <- sldEff^2/(1+sldEff^2)

		# eta - probability curve
		cexlab=1.25 
		cexaxis=1.25 
		par(mgp=c(2.3,1,0)) 
		eta2seq=seq(0,.3,.001) 
		alpha=.05 
		pp=fpi(eta2=eta2seq,n=sldNtotal,alpha=dbAlpha,a=dbPred) 
		plot(eta2seq,pp,type="l",xlab=expression(eta^2),ylab=expression(pi),ylim=c(0,1),yaxt="n",axes=FALSE, cex.lab=cexlab,cex.axis=cexaxis,lwd=3) 
		pp0=fpi(eta2=outEta2,n=sldNtotal,alpha=dbAlpha,a=dbPred) 
		points(outEta2,pp0,cex=2,pch=18)
		axis(1,pos=0,cex.lab=cexlab,cex.axis=cexaxis) 
		axis(2,pos=0,at=c(0,dbAlpha,.2,.4,.6,.8,1),labels=c(0,sldBeta,.2,.4,.6,.8,1),cex.lab=cexlab,cex.axis=cexaxis) 
		abline(h=0.05,col="gray80",lwd=2) 
	}

	getHoHaF <- function(){
		# inx <- adjustInput()
		inx <- processChange()
		xx=seq(0,20,.001)   
		fxx=df(xx,inx$dfNum,inx$dfDen)
		plot(xx,fxx,type="n",xlab=expression(paste(italic(F)," value")),ylab="Density",xlim=c(0,20),ylim=c(0,1),xaxt="n",yaxt="n",axes=FALSE) 
		axis(side=1,pos=0)
		axis(side=2,pos=0) 
		lines(xx,fxx,lwd=2) 
		ff=c(.1,.25,.4) 
		R2=ff^2/(1+ff^2) 
		for (j in 1:3){ 
			f=ff[j] 
			fxx=df(xx,inx$dfNum,inx$dfDen,ncp=inx$outNcp)
			lines(xx,fxx,lwd=2,col=rgb(10+j*40,10+j*40,10+j*40,255,maxColorValue=255))   
		}
	}

})

# freePar ~ dropdown("",...) || sst ~ textfield 	|| e1 ~ textfield || e2 ~ textfield (indicator power) || fs ~ textfield || Bvar/Wvar ~ textfields 0-1

# nrPrdtF ~ dropdown(2,3)	-> nrPrd				|| ncp ~ text || crf ~ text || dfn ~ text || dfd ~ text 
# sample sizes:
# prd 1:: ss1 ~ textfields for number 0-100
# prd 2:: ss2
# prd 3:: ss3
# sample sizes:
# prd 1:: av1 ~ textfields for number -100 - 100
# prd 2:: av2
# prd 3:: av3
# sample sizes:
# prd 1:: sd1 ~ textfields for number 0 - 100
# prd 2:: sd2
# prd 3:: sd3

shinyUI(fluidPage(

	titlePanel("Exploring power for one-way ANOVA with F-distribution"),
		fluidRow(
			column(4,
				wellPanel(
					selectInput("dbFree", "free parameter:", c("select one"="NA","sample size"="ss","effect size"="es","power"="b"),selected="select one"),
					uiOutput("sld.ntotal"),
					uiOutput("sld.eff"),
					uiOutput("sld.pwr"),
					selectInput("dbAlpha", "type I error:", c(".001" = ".001",".01" = ".01",".05" = ".05",".1" = ".1"),selected=".01"),
					numericInput("dbPred", "number of groups:", 3, min = 2, max = 8),
					htmlOutput('txt.out.2')
				)
			),
			column(4,
				# wellPanel(
					# htmlOutput('txt.out.1')
				# ),
				selectInput("dbPlots", "select plot", c("Power Curve" = "1","Ho and Ha" = "2","Ho" = "3"),selected="1"),
				conditionalPanel("condition=input.dbPlots == 1",plotOutput("plotPowerCurve")),
				conditionalPanel("condition=input.dbPlots == 2",plotOutput("plotHoHaF")),
				conditionalPanel("condition=input.dbPlots == 3",plotOutput("plotHoF"))#,
				# uiOutput("checkPlotPowercurve"),
				# uiOutput("checkPlotHoF"),
				# uiOutput("checkPlotHoHaF"),
				# plotOutput("plotPowerCurve"),
				# plotOutput("plotHoHaF"),
				# plotOutput("plotHoF")
			),
			column(4,
				br(),
				br(),
				br(),
				plotOutput("plotHoHa2")
				# ,
				# wellPanel(
					# htmlOutput('txt.out.2')
				# )
			)
		)
	)
)