main.js (5864B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2018 The Stdlib Authors. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 'use strict'; 20 21 // MODULES // 22 23 var isNumberArray = require( '@stdlib/assert/is-number-array' ).primitives; 24 var isTypedArrayLike = require( '@stdlib/assert/is-typed-array-like' ); 25 var setReadOnly = require( '@stdlib/utils/define-read-only-property' ); 26 var isObject = require( '@stdlib/assert/is-plain-object' ); 27 var tCDF = require( './../../base/dists/t/cdf' ); 28 var tQuantile = require( './../../base/dists/t/quantile' ); 29 var sqrt = require( '@stdlib/math/base/special/sqrt' ); 30 var abs = require( '@stdlib/math/base/special/abs' ); 31 var mean = require( './../../base/mean' ); 32 var variance = require( './../../base/variance' ); 33 var gcopy = require( '@stdlib/blas/base/gcopy' ); 34 var NINF = require( '@stdlib/constants/float64/ninf' ); 35 var PINF = require( '@stdlib/constants/float64/pinf' ); 36 var Float64Array = require( '@stdlib/array/float64' ); 37 var validate = require( './validate.js' ); 38 var print = require( './print.js' ); // eslint-disable-line stdlib/no-redeclare 39 40 41 // MAIN // 42 43 /** 44 * Computes a one-sample or paired Student's t test. 45 * 46 * @param {NumericArray} x - input array 47 * @param {NumericArray} [y] - optional paired array 48 * @param {Options} [options] - function options 49 * @param {number} [options.alpha=0.05] - significance level 50 * @param {string} [options.alternative='two-sided'] - alternative hypothesis (`two-sided`, `less`, or `greater`) 51 * @param {number} [options.mu=0.0] - mean under `H0` 52 * @throws {TypeError} first argument must be a numeric array 53 * @throws {Error} first argument must have at least two elements 54 * @throws {Error} paired array must have the same length as the first argument 55 * @throws {TypeError} second argument must be either a numeric array or an options object 56 * @throws {TypeError} `alpha` option must be number 57 * @throws {RangeError} `alpha` option must be reside along the interval `[0,1]` 58 * @throws {TypeError} `alternative` option must be a recognized option value (`two-sided`, `less`, or `greater`) 59 * @throws {TypeError} `mu` option must be a number 60 * @returns {Object} test results 61 * 62 * @example 63 * var x = [ 4.0, 4.0, 6.0, 6.0, 5.0 ]; 64 * var opts = { 65 * 'mu': 5.0 66 * }; 67 * var out = ttest( x, opts ); 68 * // returns {...} 69 * 70 * @example 71 * var x = [ 4.0, 4.0, 6.0, 6.0, 5.0 ]; 72 * var opts = { 73 * 'alternative': 'greater' 74 * }; 75 * var out = ttest( x, opts ); 76 * // returns {...} 77 */ 78 function ttest( x ) { 79 var stderr; 80 var xmean; 81 var cint; 82 var pval; 83 var opts; 84 var stat; 85 var err; 86 var len; 87 var out; 88 var df; 89 var tq; 90 var y; 91 var i; 92 93 if ( !isTypedArrayLike( x ) && !isNumberArray( x ) ) { 94 throw new TypeError( 'invalid argument. First argument must be a numeric array. Value: `' + x + '`.' ); 95 } 96 len = x.length; 97 if ( len < 2 ) { 98 throw new Error( 'invalid argument. First argument must have at least two elements. Value: `' + x + '`.' ); 99 } 100 opts = { 101 'mu': 0.0, 102 'alpha': 0.05, 103 'alternative': 'two-sided' 104 }; 105 if ( arguments.length === 2 ) { 106 if ( isObject( arguments[ 1 ] ) ) { 107 err = validate( opts, arguments[ 1 ] ); 108 if ( err ) { 109 throw err; 110 } 111 } else { 112 y = arguments[ 1 ]; 113 if ( !isTypedArrayLike( y ) && !isNumberArray( y ) ) { 114 throw new TypeError( 'invalid argument. Second argument must be either a numeric array or an options object. Value: `' + y + '`.' ); 115 } 116 } 117 } else if ( arguments.length > 2 ) { 118 y = arguments[ 1 ]; 119 if ( !isTypedArrayLike( y ) && !isNumberArray( y ) ) { 120 throw new TypeError( 'invalid argument. Second argument must be a numeric array. Value: `' + y + '`.' ); 121 } 122 err = validate( opts, arguments[ 2 ] ); 123 if ( err ) { 124 throw err; 125 } 126 } 127 if ( y ) { 128 if ( y.length !== len ) { 129 throw new Error( 'invalid arguments. The first and second arguments must have the same length.' ); 130 } 131 x = gcopy( len, x, 1, new Float64Array( len ), 1 ); 132 for ( i = 0; i < len; i++ ) { 133 x[ i ] -= y[ i ]; 134 } 135 } 136 stderr = sqrt( variance( len, 1, x, 1 ) / len ); // TODO: replace with base/sem 137 xmean = mean( len, x, 1 ); // TODO: ideally, we would get both the sem and the mean from the same function and without needing to traverse 3-4 times 138 stat = ( xmean-opts.mu ) / stderr; 139 df = len - 1; 140 if ( opts.alternative === 'two-sided' ) { 141 pval = 2.0 * tCDF( -abs(stat), df ); 142 tq = tQuantile( 1.0-(opts.alpha/2.0), df ); 143 cint = [ 144 opts.mu + ( (stat-tq)*stderr ), 145 opts.mu + ( (stat+tq)*stderr ) 146 ]; 147 } else if ( opts.alternative === 'greater' ) { 148 pval = 1.0 - tCDF( stat, df ); 149 tq = tQuantile( 1.0-opts.alpha, df ); 150 cint = [ 151 opts.mu + ( (stat-tq)*stderr ), 152 PINF 153 ]; 154 } else { // opts.alternative === 'less' 155 pval = tCDF( stat, df ); 156 tq = tQuantile( 1.0-opts.alpha, df ); 157 cint = [ 158 NINF, 159 opts.mu + ( (stat+tq)*stderr ) 160 ]; 161 } 162 out = {}; 163 setReadOnly( out, 'rejected', pval <= opts.alpha ); 164 setReadOnly( out, 'alpha', opts.alpha ); 165 setReadOnly( out, 'pValue', pval ); 166 setReadOnly( out, 'statistic', stat ); 167 setReadOnly( out, 'ci', cint ); 168 setReadOnly( out, 'df', df ); 169 setReadOnly( out, 'nullValue', opts.mu ); 170 setReadOnly( out, 'mean', xmean ); 171 setReadOnly( out, 'sd', stderr ); 172 setReadOnly( out, 'alternative', opts.alternative ); 173 setReadOnly( out, 'method', ( y ) ? 'Paired t-test' : 'One-sample t-test' ); 174 setReadOnly( out, 'print', print ); 175 return out; 176 } 177 178 179 // EXPORTS // 180 181 module.exports = ttest;