Is OwenQ reliable?

Purpose

The purpose of this vignette is to assess the correctness of the functions of the OwenQ package.

As said in the main vignette, the fourth Owen cumulative function O4, implemented under the name powen4 allows to evaluate the power of equivalence tests. This is perhaps the main application of the OwenQ package and we will particularly focus on this situation.

Our strategy runs as follows:

  • To test the OwenT function, we will compare some values it returns to those returned by other implementations. Note that the other functions of the OwenQ package rely on OwenT when the number of degrees of freedom is odd. Therefore, by testing these other functions for an odd number of degrees of freedom, we implicitely test the OwenT function.

  • We will compare a couple of values returned by OwenQ1 and OwenQ2 to the values obtained by numerical integration in Wolfram|Alpha.

  • We will test powen4 by performing some power calculations relying on this function and comparing the results with the ones provided by SAS. We will also perform these power calculations with the help of the ipowen4 function, which evaluates the O4 function by a powerful numerical integration, and we will compare the results.

  • These power calculations will be performed for 100 different scenarios, and we will conclude that powen4 is reliable for these scenarios. Then, to test the other functions of the OwenQ package, we will check that some mathematical relations hold for these 100 scenarios.

The folder tests/testthat of the OwenQ package contains many comparisons with Wolfram|Alpha. The results we obtain with OwenQ are always the same as the ones obtained with Wolfram|Alpha up to a tolerance of at least 10 decimal digits.

Owen T-function (OwenT)

The Owen T-function is implemented under the name OwenT. This is a port of the function owens_t of the C++ special_functions library included in the boost set of libraries. Some details about the C++ implementation are available here.

The libraries provided by boost are peer-reviewed, and this is enough to trust the reliability of OwenT. Nevertheless, below we compare the results of OwenT to the six values given in Patefield’s article, up to 14 decimal digits. We have also checked that Wolfram|Alpha gives these values.

testData <- data.frame(
  h = c(0.0625, 6.5, 7, 4.78125, 2, 1), 
  a = c(0.25, 0.4375, 0.96875, 0.0625, 0.5, 0.9999975),
  Patefield = c(3.8911930234701e-02, 2.0005773048508e-11, 6.3990627193899e-13, 1.0632974804687e-07, 8.6250779855215e-03, 6.6741808978229e-02),
  OwenT = numeric(6)
)
for(i in 1:nrow(testData)){
  testData$OwenT[i] <- OwenT(testData$h[i], testData$a[i])
}
print(testData, digits=14)
##         h         a           Patefield               OwenT
## 1 0.06250 0.2500000 3.8911930234701e-02 3.8911930234701e-02
## 2 6.50000 0.4375000 2.0005773048508e-11 2.0005773048508e-11
## 3 7.00000 0.9687500 6.3990627193899e-13 6.3990627193899e-13
## 4 4.78125 0.0625000 1.0632974804687e-07 1.0632974804687e-07
## 5 2.00000 0.5000000 8.6250779855215e-03 8.6250779855215e-03
## 6 1.00000 0.9999975 6.6741808978229e-02 6.6741808978229e-02

We get the same results with OwenT.

Owen Q-functions: comparing a couple of values

The two Owen Q-functions Q1 and Q2 are defined by: $$ Q_1(\nu, t, \delta, R) = \frac{1}{\Gamma\left(\frac{\nu}{2}\right)2^{\frac12(\nu-2)}} \int_0^R \Phi\left(\frac{tx}{\sqrt{\nu}}-\delta\right) x^{\nu-1} e^{-\frac{x^2}{2}} \mathrm{d}x $$ and $$ Q_2(\nu, t, \delta, R) = \frac{1}{\Gamma\left(\frac{\nu}{2}\right)2^{\frac12(\nu-2)}} \int_R^\infty \Phi\left(\frac{tx}{\sqrt{\nu}}-\delta\right) x^{\nu-1} e^{-\frac{x^2}{2}} \mathrm{d}x. $$

They are implemented in the OwenQ package under the respective names OwenQ1 and OwenQ2, for integer values of ν, following Owen’s algorithm (1965).

In Wolfram|Alpha, these functions are not available, but we can evaluate them by numerical integration.

Below we compare two values of OwenQ1 to the ones returned by Wolfram|Alpha.

# wolfram: Integrate[(1+Erf[(3*x/Sqrt[3]-2)/Sqrt[2]])*x^(3-1)*Exp[-x^2/2],{x,0,5}]/2/Gamma[3/2]/2^((3-2)/2)
OwenQ1(3, 3, 2, 5)
## [1] 0.6800117

Our value rounded to 6 digits is the same as the one given by Wolfram.

# wolfram: Integrate[(1+Erf[(3*x/Sqrt[1000]-2)/Sqrt[2]])*x^(1000-1)*Exp[-x^2/2],{x,0,30}]/2/Gamma[1000/2]/2^((1000-2)/2)
print(OwenQ1(1000, 3, 2, 30), digits=16)
## [1] 0.008518809463589428

The two values are the same up to 13 digits.


Now we compare two values of OwenQ2 to the ones returned by Wolfram|Alpha.

# wolfram: Integrate[(1+Erf[(3*x/Sqrt[3]-2)/Sqrt[2]])*x^(3-1)*Exp[-x^2/2],{x,5,Infinity}]/2/Gamma[3/2]/2^((3-2)/2)
OwenQ2(3, 3, 2, 5)
## [1] 1.54405e-05

The two values are identical.

# wolfram: Integrate[(1+Erf[(3*x/Sqrt[1000]-2)/Sqrt[2]])*x^(1000-1)*Exp[-x^2/2],{x,5,Infinity}]/2/Gamma[1000/2]/2^((1000-2)/2)
print(OwenQ2(1000, 3, 2, 5), digits=16)
## [1] 0.8406201459600969

The two values are identical up to 13 digits.

Fourth Owen cumulative function O4 (powen4)

As seen in the other vignette, the fourth Owen cumulative function O4, implemented under the name powen4, can be used to evaluate the power of equivalence tests.

The powerTOST function below returns the power of the equivalence test for a so-called parallel design with equal variances, when considering the alternative hypothesis H1: {−Δ < δ0 < Δ}, where δ0 denotes the difference between the two means. This function takes as arguments the significance level α, the difference δ0 between the two means, the threshold Δ, the common standard deviation σ of the two samples, and the two sample sizes n1 and n2.

powerTOST <- function(alpha, delta0, Delta, sigma, n1, n2, algo=2) {
  se <- sqrt(1/n1 + 1/n2) * sigma
  delta1 <- (delta0 + Delta) / se
  delta2 <- (delta0 - Delta) / se
  dof <- n1 + n2 - 2
  q <- qt(1 - alpha, dof)
  powen4(dof, q, -q, delta1, delta2, algo=algo)
}

As you can see, the powen4 function has an argument algo. This is also the case for the other Owen functions, except for ptOwen. The algo argument can take two values, 1 or 2. Th default value is algo=2, and as we will see later, the evaluation is more reliable with this option. The value of algo corresponds to a small difference in the algorithm. With algo=1, the evaluation is a bit faster. And we will see that powerTOST
is reliable for algo=1 when the value of n1+n2 is not too large.

Comparisons with SAS

The table shown below contains 100 results of power calculations with SAS version 9.4, for a total sample size n1 + n2 going from 20 to 1200. The results are rounded to 5 decimal digits.

alpha delta0 Delta sigma n1 n2 powerSAS
1 0.05 0.0 1 1 10 10 0.39094
2 0.05 0.0 1 1 15 15 0.69541
3 0.05 0.0 1 1 20 20 0.85580
4 0.05 0.0 1 1 25 25 0.93426
5 0.05 0.0 1 1 30 30 0.97092
6 0.05 0.1 1 1 10 10 0.38272
7 0.05 0.1 1 1 15 15 0.67836
8 0.05 0.1 1 1 20 20 0.83661
9 0.05 0.1 1 1 25 25 0.91781
10 0.05 0.1 1 1 30 30 0.95889
11 0.05 0.2 1 1 10 10 0.35908
12 0.05 0.2 1 1 15 15 0.62954
13 0.05 0.2 1 1 20 20 0.78069
14 0.05 0.2 1 1 25 25 0.86796
15 0.05 0.2 1 1 30 30 0.92017
16 0.05 0.3 1 1 10 10 0.32287
17 0.05 0.3 1 1 15 15 0.55540
18 0.05 0.3 1 1 20 20 0.69322
19 0.05 0.3 1 1 25 25 0.78471
20 0.05 0.3 1 1 30 30 0.84909
21 0.05 0.4 1 1 10 10 0.27820
22 0.05 0.4 1 1 15 15 0.46531
23 0.05 0.4 1 1 20 20 0.58306
24 0.05 0.4 1 1 25 25 0.67174
25 0.05 0.4 1 1 30 30 0.74260
26 0.05 0.5 1 1 10 10 0.22970
27 0.05 0.5 1 1 15 15 0.36967
28 0.05 0.5 1 1 20 20 0.46208
29 0.05 0.5 1 1 25 25 0.53883
30 0.05 0.5 1 1 30 30 0.60600
31 0.05 0.0 1 1 10 30 0.70373
32 0.05 0.0 1 1 15 30 0.85764
33 0.05 0.0 1 1 20 30 0.92324
34 0.05 0.0 1 1 25 30 0.95452
35 0.05 0.0 1 1 30 30 0.97092
36 0.05 0.1 1 1 10 30 0.68648
37 0.05 0.1 1 1 15 30 0.83846
38 0.05 0.1 1 1 20 30 0.90604
39 0.05 0.1 1 1 25 30 0.94003
40 0.05 0.1 1 1 30 30 0.95889
41 0.05 0.2 1 1 10 30 0.63703
42 0.05 0.2 1 1 15 30 0.78255
43 0.05 0.2 1 1 20 30 0.85439
44 0.05 0.2 1 1 25 30 0.89501
45 0.05 0.2 1 1 30 30 0.92017
46 0.05 0.3 1 1 10 30 0.56192
47 0.05 0.3 1 1 15 30 0.69503
48 0.05 0.3 1 1 20 30 0.76944
49 0.05 0.3 1 1 25 30 0.81676
50 0.05 0.3 1 1 30 30 0.84909
51 0.05 0.4 1 1 10 30 0.47061
52 0.05 0.4 1 1 15 30 0.58470
53 0.05 0.4 1 1 20 30 0.65610
54 0.05 0.4 1 1 25 30 0.70594
55 0.05 0.4 1 1 30 30 0.74260
56 0.05 0.5 1 1 10 30 0.37365
57 0.05 0.5 1 1 15 30 0.46343
58 0.05 0.5 1 1 20 30 0.52475
59 0.05 0.5 1 1 25 30 0.57052
60 0.05 0.5 1 1 30 30 0.60600
61 0.05 0.0 3 4 50 50 0.96239
62 0.05 1.0 3 4 50 50 0.79849
63 0.05 2.0 3 4 50 50 0.34329
64 0.05 2.5 3 4 50 50 0.15288
65 0.05 0.0 4 4 10 90 0.81791
66 0.05 1.0 4 4 10 90 0.70346
67 0.05 2.0 4 4 10 90 0.43595
68 0.05 2.5 4 4 10 90 0.29819
69 0.05 0.0 4 7 100 100 0.98278
70 0.05 1.0 4 7 100 100 0.91512
71 0.05 2.0 4 7 100 100 0.64376
72 0.05 3.0 4 7 100 100 0.26169
73 0.01 0.0 4 7 100 100 0.90831
74 0.01 1.0 4 7 100 100 0.74924
75 0.01 2.0 4 7 100 100 0.37443
76 0.01 3.0 4 7 100 100 0.09290
77 0.05 0.0 4 4 185 10 0.84568
78 0.05 1.0 4 4 185 10 0.73025
79 0.05 2.0 4 4 185 10 0.45459
80 0.05 3.0 4 4 185 10 0.19001
81 0.05 0.0 4 8 185 100 0.98240
82 0.05 1.0 4 8 185 100 0.91417
83 0.05 2.0 4 8 185 100 0.64226
84 0.05 3.0 4 8 185 100 0.26103
85 0.01 0.0 4 10 250 250 0.96713
86 0.01 1.0 4 10 250 250 0.84523
87 0.01 2.0 4 10 250 250 0.46161
88 0.01 3.0 4 10 250 250 0.11288
89 0.01 0.0 4 14 500 500 0.97112
90 0.01 1.0 4 14 500 500 0.85433
91 0.01 2.0 4 14 500 500 0.47184
92 0.01 3.0 4 14 500 500 0.11536
93 0.01 0.0 5 35 600 600 0.11547
94 0.01 4.0 5 35 600 600 0.01658
95 0.05 0.0 5 30 600 600 0.78512
96 0.05 4.0 5 50 600 600 0.02642
97 0.01 0.0 5 6 1190 10 0.23194
98 0.01 4.0 5 6 1190 10 0.02739
99 0.05 0.0 5 9 1190 10 0.08256
100 0.05 4.0 5 9 1190 10 0.03115

This table is stored in a dataframe named SAS. We compare the SAS values to the ones obtained by our powerTOST function.

power <- numeric(nrow(SAS))
for (i in 1:nrow(SAS)) {
  power[i] <-
    powerTOST(
      alpha = SAS$alpha[i],
      delta0 = SAS$delta0[i],
      Delta = SAS$Delta[i],
      sigma = SAS$sigma[i],
      n1 = SAS$n1[i],
      n2 = SAS$n2[i]
    )
}

Rounding our values to 5 decimal digits, we find that our results are identical to the SAS results:

identical(round(power,5), SAS$powerSAS)
## [1] TRUE

With the option algo=1, the results are identical as well:

power <- numeric(nrow(SAS))
for (i in 1:nrow(SAS)) {
  power[i] <-
    powerTOST(
      alpha = SAS$alpha[i],
      delta0 = SAS$delta0[i],
      Delta = SAS$Delta[i],
      sigma = SAS$sigma[i],
      n1 = SAS$n1[i],
      n2 = SAS$n2[i],
      algo = 1
    )
}
identical(round(power,5), SAS$powerSAS)
## [1] TRUE

Comparison with numerical integration

The ipowen4 internal function of the OwenQ package evaluates the fourth Owen cumulative function O4 by a powerful numerical integration implemented in C++ (with the package RcppNumerical). Thus we have two completely different implementations of O4. The ipowerTOST function below is obtained by replacing powen4 with ipowen4 in powerTOST. We will compare the results of powerTOST with the ones of ipowerTOST.

ipowerTOST <- function(alpha, delta0, Delta, sigma, n1, n2) {
  se <- sqrt(1/n1 + 1/n2) * sigma
  delta1 <- (delta0 + Delta) / se
  delta2 <- (delta0 - Delta) / se
  dof <- n1 + n2 - 2
  q <- qt(1 - alpha, dof)
  OwenQ:::ipowen4(dof, q, -q, delta1, delta2)
}

Failures and successes of powen4

  • For n1 = n2 = 1000, α = 0.05, δ = 0, Δ = 5, the powen4 function with the option algo=1 abnormally takes some negative values:

Note that the discrepancy between powen4 and ipowen4 occurs only for σ > 65.

  • For n1 = n2 = 720, α = 0.05, δ = 0, Δ = 5, we observe a discrepancy between powen4 with the option algo=1 and ipowen4:

There is no discrepancy between powen4 with the option algo=2 and ipowen4. The irregularity of powen4 with the option algo=1 suggests that it returns wrong values.

  • For n1 = n2 = 700, α = 0.01, δ = 0, Δ = 5, we still observe a small discrepancy between powen4 with the option algo=1 and ipowen4, and we still observe an irregularity of powen4 (on the second figure below):

With the option algo=2, the results of powen4 coincide with the ones of ipowen4.

  • For n1 = n2 = 600, α = 0.005, δ = 0, Δ = 5, we do not observe any discrepancy between powen4 and ipowen4:

We conclude that powen4 with the option algo=1 is not reliable when the number of degrees of freedom is too large. As said before, the interest of the option algo=1 is that the evaluation is faster.

For n1 = n2 = 2500, the results of powerTOST with algo=2 still coincide with the result of ipowerTOST:

alpha <- 0.05; delta0 <- 0; Delta <- 5
sigma <- 110
n1 <- n2 <- 2500
powerTOST(alpha, delta0, Delta, sigma, n1, n2)
## [1] 4.523596e-05
ipowerTOST(alpha, delta0, Delta, sigma, n1, n2)
## [1] 4.523596e-05
## attr(,"err_est")
## [1] 5.022201e-19
## attr(,"err_code")
## [1] 0

And even for n1 = n2 = 5000:

sigma <- 152
n1 <- n2 <- 5000
powerTOST(alpha, delta0, Delta, sigma, n1, n2)
## [1] 0.003612374
ipowerTOST(alpha, delta0, Delta, sigma, n1, n2)
## [1] 0.003612374
## attr(,"err_est")
## [1] 4.010541e-17
## attr(,"err_code")
## [1] 0

Comparisons for n1 + n2 ≤ 1200

Below we compare the results returned by powerTOST to the ones returned by ipowerTOST for the parameters given in the SAS dataset.

power <- ipower <- numeric(nrow(SAS))
for (i in 1:nrow(SAS)) {
  power[i] <-
    powerTOST(
      alpha = SAS$alpha[i],
      delta0 = SAS$delta0[i],
      Delta = SAS$Delta[i],
      sigma = SAS$sigma[i],
      n1 = SAS$n1[i],
      n2 = SAS$n2[i]
    )
  ipower[i] <-
    ipowerTOST(
      alpha = SAS$alpha[i],
      delta0 = SAS$delta0[i],
      Delta = SAS$Delta[i],
      sigma = SAS$sigma[i],
      n1 = SAS$n1[i],
      n2 = SAS$n2[i]
    )
}
identical(round(power, 10), round(ipower, 10))
## [1] TRUE

Results are the same up to 10 digits. And also with the option algo=1:

power <- numeric(nrow(SAS))
for (i in 1:nrow(SAS)) {
  power[i] <-
    powerTOST(
      alpha = SAS$alpha[i],
      delta0 = SAS$delta0[i],
      Delta = SAS$Delta[i],
      sigma = SAS$sigma[i],
      n1 = SAS$n1[i],
      n2 = SAS$n2[i], 
      algo = 1
    )
}
identical(round(power, 10), round(ipower, 10))
## [1] TRUE

Conclusion

  • The powen4 function with option algo=1 seems to return correct values for ν ≤ 1200.

  • The powen4 function with option algo=2 allows higher values of ν.

  • In any case, we recommend to compare the result of powen4 with the one of ipowen4.

Other Owen cumulative functions

We previously validated the powen4 function for the 100 scenarios of the dataset SAS.

Now we test the functions powen1, powen2 and powen3 by checking the equality O1(ν, t1, t2, δ1, δ2) + O2(ν, t1, t2, δ1, δ2) + O3(ν, t1, t2, δ1, δ2) + O4(ν, t1, t2, δ1, δ2) = 1.

We restrict our attention to default setting algo=2. We check the above equality for the 100 scenarios of the dataset SAS.

f <- function(alpha, delta0, Delta, sigma, n1, n2) {
  se <- sqrt(1/n1 + 1/n2) * sigma
  delta1 <- (delta0 + Delta) / se
  delta2 <- (delta0 - Delta) / se
  dof <- n1 + n2 - 2
  q <- qt(1 - alpha, dof)
  powen1(dof, q,-q, delta1, delta2) + powen2(dof, q,-q, delta1, delta2) + 
    powen3(dof, q,-q, delta1, delta2) + powen4(dof, q,-q, delta1, delta2)
}
test <- numeric(nrow(SAS))
for (i in 1:nrow(SAS)) {
  test[i] <-
    f(
      alpha = SAS$alpha[i],
      delta0 = SAS$delta0[i],
      Delta = SAS$Delta[i],
      sigma = SAS$sigma[i],
      n1 = SAS$n1[i],
      n2 = SAS$n2[i]
    )
}
all(abs(test-1) < 1e-14)
## [1] TRUE

We find that each of the 100 sums is equal to 1 up to a tolerance of 14 digits.

First Owen Q-function Q1 (OwenQ1)

We previously validated the powen4 function (implementation of the fourth Owen cumulative function O4) for the 100 scenarios of the dataset SAS.
Now, to test OwenQ1, we will use the following equality:

$$ O_4(\nu, t_1, t_2, \delta_1, \delta_2) = Q_1\left(\nu, t_2, \delta_2, \frac{\sqrt{\nu}(\delta_1-\delta_2)}{t_1-t_2}\right) - Q_1\left(\nu, t_1, \delta_1, \frac{\sqrt{\nu}(\delta_1-\delta_2)}{t_1-t_2}\right). $$

We use this formula to perform the power calculations with OwenQ1 instead of powen4.

powerTOST2 <- function(alpha, delta0, Delta, sigma, n1, n2) {
  se <- sqrt(1/n1 + 1/n2) * sigma
  delta1 <- (delta0 + Delta) / se
  delta2 <- (delta0 - Delta) / se
  dof <- n1 + n2 - 2
  q <- qt(1 - alpha, dof)
  R <- sqrt(dof)*(delta1 - delta2)/q/2
  OwenQ1(dof, -q, delta2, R) - OwenQ1(dof, q, delta1, R)
}
power2 <- numeric(nrow(SAS))
for (i in 1:nrow(SAS)) {
  power2[i] <-
    powerTOST2(
      alpha = SAS$alpha[i],
      delta0 = SAS$delta0[i],
      Delta = SAS$Delta[i],
      sigma = SAS$sigma[i],
      n1 = SAS$n1[i],
      n2 = SAS$n2[i]
    )
}
all(abs(power - power2) < 1e-9)
## [1] TRUE

The values rounded to 9 digits are the same as the ones we previously obtained with powen4.

Second Owen Q-function Q2 (OwenQ2)

We previously validated powen2 for the 100 scenarios of the dataset SAS. Now we test the OwenQ2 function by checking the equality $$ O_2(\nu, t_1, t_2, \delta_1, \delta_2) = Q_2\left(\nu, t_1, \delta_1, \frac{\sqrt{\nu}(\delta_1-\delta_2)}{t_1-t_2}\right) - Q_2\left(\nu, t_2, \delta_2, \frac{\sqrt{\nu}(\delta_1-\delta_2)}{t_1-t_2}\right). $$

We check this equality for the 100 scenarios of the dataset SAS.

g <- function(alpha, delta0, Delta, sigma, n1, n2) {
  se <- sqrt(1/n1 + 1/n2) * sigma
  delta1 <- (delta0 + Delta) / se
  delta2 <- (delta0 - Delta) / se
  dof <- n1 + n2 - 2
  q <- qt(1 - alpha, dof)
  R <- sqrt(dof)*(delta1 - delta2)/q/2
  x <- OwenQ2(dof, q, delta1, R) - OwenQ2(dof, -q, delta2, R)
  y <- powen2(dof, q, -q, delta1, delta2)
  x - y
}
test <- numeric(nrow(SAS))
for (i in 1:nrow(SAS)) {
  test[i] <-
    g(
      alpha = SAS$alpha[i],
      delta0 = SAS$delta0[i],
      Delta = SAS$Delta[i],
      sigma = SAS$sigma[i],
      n1 = SAS$n1[i],
      n2 = SAS$n2[i]
    )
}
all(abs(test) < 1e-15)
## [1] TRUE

We find that each of the 100 equalities hold true up to a tolerance of 15 digits.

Student cumulative distribution function (ptOwen)

We finally test ptOwen, the implementation of the cumulative distribution function of the noncentral Student distribution.
If T1 follows the noncentral Student distribution with number of degrees of freedom ν and noncentrality parameter δ1, then for any t1 the equality Pr (T1 ≤ t1) = O1(ν, t1, t2, δ1, δ2) + O2(ν, t1, t2, δ1, δ2) holds for any t2 and δ2. We check this equality for the 100 scenarios of the dataset SAS.

h <- function(alpha, delta0, Delta, sigma, n1, n2) {
  se <- sqrt(1/n1 + 1/n2) * sigma
  delta1 <- (delta0 + Delta) / se
  delta2 <- (delta0 - Delta) / se
  dof <- n1 + n2 - 2
  q <- qt(1 - alpha, dof)
  x <- ptOwen(q, dof, delta1)
  y <- powen1(dof, q, -q, delta1, delta2) + powen2(dof, q, -q, delta1, delta2)
  x - y
}
test <- numeric(nrow(SAS))
for (i in 1:nrow(SAS)) {
  test[i] <-
    h(
      alpha = SAS$alpha[i],
      delta0 = SAS$delta0[i],
      Delta = SAS$Delta[i],
      sigma = SAS$sigma[i],
      n1 = SAS$n1[i],
      n2 = SAS$n2[i]
    )
}
all(abs(test) < 1e-15)
## [1] TRUE

For each scenario, the equality holds up to a tolerance of 15 digits.

References

  • Patefield, M. (2000). Fast and Accurate Calculation of Owen’s T Function. Journal of Statistical Software 5 (5), 1-25.

  • Owen, D. B. (1965). A special case of a bivariate noncentral t-distribution. Biometrika 52, 437-446.

  • Wolfram Alpha LLC. 2017. Wolfram|Alpha. (access July 17, 2017).