How to resolve the algorithm P-value correction step by step in the Nim programming language
Published on 12 May 2024 09:40 PM
How to resolve the algorithm P-value correction step by step in the Nim programming language
Table of Contents
Problem Statement
Given a list of p-values, adjust the p-values for multiple comparisons. This is done in order to control the false positive, or Type 1 error rate. This is also known as the "false discovery rate" (FDR). After adjustment, the p-values will be higher but still inside [0,1]. The adjusted p-values are sometimes called "q-values".
Given one list of p-values, return the p-values correcting for multiple comparisons
There are several methods to do this, see:
Each method has its own advantages and disadvantages.
Let's start with the solution:
Step by Step solution about How to resolve the algorithm P-value correction step by step in the Nim programming language
Source code in the nim programming language
import algorithm, math, sequtils, strformat, strutils, sugar
type
CorrectionType {.pure.} = enum
BenjaminiHochberg = "Benjamini-Hochberg"
BenjaminiYekutieli = "Benjamini-Yekutieli"
Bonferroni = "Bonferroni"
Hochberg = "Hochberg"
Holm = "Holm"
Hommel = "Hommel"
Šidák = "Šidák"
Direction {.pure.} = enum Up, Down
PValues = seq[float]
template newPValues(length: Natural): PValues =
## Create a PValues object of given length.
newSeq[float](length)
func ratchet(p: var PValues; dir: Direction) =
var m = p[0]
case dir
of Up:
for i in 1..p.high:
if p[i] > m: p[i] = m
m = p[i]
of Down:
for i in 1..p.high:
if p[i] < m: p[i] = m
m = p[i]
for i in 0..p.high:
if p[i] > 1: p[i] = 1
func schwartzian(p, mult: PValues; dir: Direction): PValues =
let length = p.len
let sortOrder = if dir == Up: Descending else: Ascending
let order1 = toSeq(p.pairs).sorted((x, y) => cmp(x.val, y.val), sortOrder).mapIt(it.key)
var pa = newPValues(length)
for i in 0..pa.high:
pa[i] = mult[i] * p[order1[i]]
ratchet(pa, dir)
let order2 = toSeq(order1.pairs).sortedByIt(it.val).mapIt(it.key)
for idx in order2:
result.add pa[idx]
proc adjust(p: PValues; ctype: CorrectionType): PValues =
let length = p.len
assert length > 0
let flength = length.toFloat
case ctype
of BenjaminiHochberg:
var mult = newPValues(length)
for i in 0..mult.high:
mult[i] = flength / (flength - i.toFloat)
return schwartzian(p, mult, Up)
of BenjaminiYekutieli:
var q = 0.0
for i in 1..length: q += 1 / i
var mult = newPValues(length)
for i in 0..mult.high:
mult[i] = (q * flength) / (flength - i.toFloat)
return schwartzian(p, mult, Up)
of Bonferroni:
result = newPValues(length)
for i in 0..result.high:
result[i] = min(p[i] * flength, 1)
return
of Hochberg:
var mult = newPValues(length)
for i in 0..mult.high:
mult[i] = i.toFloat + 1
return schwartzian(p, mult, Up)
of Holm:
var mult = newPValues(length)
for i in 0..mult.high:
mult[i] = flength - i.toFloat
return schwartzian(p, mult, Down)
of Hommel:
let order1 = toSeq(p.pairs).sortedByIt(it.val).mapIt(it.key)
let s = order1.mapIt(p[it])
var m = Inf
for i in 0..s.high:
m = min(m, s[i] * flength / (i + 1).toFloat)
var q, pa = repeat(m, length)
for j in countdown(length - 1, 2):
let lower = toSeq(0..length - j)
let upper = toSeq((length - j + 1)..<length)
var qmin = j.toFloat * s[upper[0]] / 2
for i in 1..upper.high:
let val = s[upper[i]] * j.toFloat / (i + 2).toFloat
if val < qmin: qmin = val
for idx in lower: q[idx] = min(s[idx] * j.toFloat, qmin)
for idx in upper: q[idx] = q[^j]
for i, val in q:
if pa[i] < val: pa[i] = val
let order2 = toSeq(order1.pairs).sortedByIt(it.val).mapIt(it.key)
return order2.mapIt(pa[it])
of Šidák:
result = newPValues(length)
for i in 0..result.high:
result[i] = 1 - (1 - p[i])^length
return
func pformat(p: PValues; cols = 5): string =
var lines: seq[string]
for i in countup(0, p.high, cols):
let fchunk = p[i..<(i + cols)]
var schunk = newSeq[string](fchunk.len)
for j in 0..<cols:
schunk[j] = fchunk[j].formatFloat(ffDecimal, 10)
lines.add &"[{i:2}] {schunk.join(\" \")}"
result = lines.join("\n")
func adjusted(p: PValues; ctype: CorrectionType): string =
doAssert p.len > 0 and min(p) >= 0 and max(p) <= 1, "p-values must be in range 0.0 to 1.0."
result = &"\n{ctype}\n{pformat(p.adjust(ctype))}"
when isMainModule:
const PVals = @[
4.533744e-01, 7.296024e-01, 9.936026e-02, 9.079658e-02, 1.801962e-01,
8.752257e-01, 2.922222e-01, 9.115421e-01, 4.355806e-01, 5.324867e-01,
4.926798e-01, 5.802978e-01, 3.485442e-01, 7.883130e-01, 2.729308e-01,
8.502518e-01, 4.268138e-01, 6.442008e-01, 3.030266e-01, 5.001555e-02,
3.194810e-01, 7.892933e-01, 9.991834e-01, 1.745691e-01, 9.037516e-01,
1.198578e-01, 3.966083e-01, 1.403837e-02, 7.328671e-01, 6.793476e-02,
4.040730e-03, 3.033349e-04, 1.125147e-02, 2.375072e-02, 5.818542e-04,
3.075482e-04, 8.251272e-03, 1.356534e-03, 1.360696e-02, 3.764588e-04,
1.801145e-05, 2.504456e-07, 3.310253e-02, 9.427839e-03, 8.791153e-04,
2.177831e-04, 9.693054e-04, 6.610250e-05, 2.900813e-02, 5.735490e-03]
for ctype in CorrectionType:
echo adjusted(PVals, ctype)
You may also check:How to resolve the algorithm Find the last Sunday of each month step by step in the S-BASIC programming language
You may also check:How to resolve the algorithm Tree traversal step by step in the Ada programming language
You may also check:How to resolve the algorithm Monty Hall problem step by step in the R programming language
You may also check:How to resolve the algorithm Greatest common divisor step by step in the ACL2 programming language
You may also check:How to resolve the algorithm Loops/Break step by step in the HicEst programming language