kde2d.js (5295B)
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 ndarray = require( '@stdlib/ndarray/array' ); 24 var linspace = require( '@stdlib/array/linspace' ); 25 var setReadOnly = require( '@stdlib/utils/define-read-only-property' ); 26 var isNumericArray = require( '@stdlib/assert/is-numeric-array' ); 27 var isMatrixLike = require( '@stdlib/assert/is-matrix-like' ); 28 var pickBandwidth = require( './pick_bandwidth.js' ); 29 var validate = require( './validate.js' ); 30 var ndarrayLike = require( './ndarray_like.js' ); 31 var min = require( './min.js' ); 32 var max = require( './max.js' ); 33 var gaussian = require( './gaussian.js' ); 34 35 36 // MAIN // 37 38 /** 39 * Computes two-dimensional kernel density estimates. 40 * 41 * @param {NumericArray} x - array of x values 42 * @param {NumericArray} y - array of y values 43 * @param {Options} [options] - function options 44 * @param {NumericArray} [options.h] - array of length two containing the bandwidth values for x and y 45 * @param {number} [options.n=25] - number of partitions on the x- and y-axes 46 * @param {number} [options.xMin] - lower limit of x 47 * @param {number} [options.xMax] - upper limit of x 48 * @param {number} [options.yMin] - lower limit of y 49 * @param {number} [options.yMax] - upper limit of y 50 * @param {(string|Function)} [options.kernel='gaussian'] - a string or function to specifying the used kernel function 51 * @throws {TypeError} first argument must be an array or matrix-like 52 * @throws {TypeError} second argument must be an array 53 * @throws {Error} first and second arguments must be of the same length 54 * @throws {RangeError} `xMin` must be smaller than `xMax` 55 * @throws {RangeError} `yMin` must be smaller than `yMax` 56 * @throws {TypeError} options argument must be an object 57 * @throws {TypeError} must provide valid options 58 * @returns {Object} object containing the density estimates (`z`) along grid points (`x` and `y` values) 59 * 60 * @example 61 * var x = [ 0.6333, 0.8643, 1.0952, 1.3262, 1.5571, 1.7881, 2.019, 2.25, 2.481, 2.7119 ]; 62 * var y = [ -0.0468, 0.8012, 1.6492, 2.4973, 3.3454, 4.1934, 5.0415, 5.8896, 6.7376, 7.5857 ]; 63 * var out = kde2d( x, y ); 64 */ 65 function kde2d() { 66 var kernelFunction; 67 var maxArgs; 68 var zScoreX; 69 var zScoreY; 70 var gridX; 71 var gridY; 72 var xMin; 73 var xMax; 74 var yMin; 75 var yMax; 76 var xVal; // For gridspace loop 77 var yVal; // For gridspace loop 78 var subX; 79 var subY; 80 var opts; 81 var arr; 82 var err; 83 var ans; 84 var out; 85 var gx; 86 var gy; 87 var hX; 88 var hY; 89 var ix; 90 var iy; 91 var x; 92 var y; 93 var i; 94 var n; 95 var z; 96 97 opts = {}; 98 99 if ( isMatrixLike( arguments[0] ) ) { 100 // Case of ndarray, opts 101 arr = arguments[ 0 ]; 102 maxArgs = 1; 103 } else { 104 x = arguments[ 0 ]; 105 y = arguments[ 1 ]; 106 if ( !isNumericArray( x ) ) { 107 throw new TypeError( 'invalid argument. First argument `x` must be a numeric array. Value: `' + x + '`.' ); 108 } 109 if ( !isNumericArray( y ) ) { 110 throw new TypeError( 'invalid argument. Second argument `y` must be a numeric array. Value: `' + y + '`.' ); 111 } 112 if ( x.length !== y.length ) { 113 throw new Error( 'invalid arguments. Arguments `x` and `y` must be arrays of the same length' ); 114 } 115 arr = ndarrayLike( x, y ); 116 maxArgs = 2; 117 } 118 119 if ( arguments.length > maxArgs ) { 120 err = validate( opts, arguments[ maxArgs ] ); 121 if ( err ) { 122 throw err; 123 } 124 } 125 126 if ( opts.h ) { 127 hX = opts.h[0]; 128 hY = opts.h[1]; 129 } else { 130 hX = pickBandwidth(arr, 0); 131 hY = pickBandwidth(arr, 1); 132 } 133 134 n = opts.n || 25; 135 xMin = opts.xMin || min( arr, 0, arr.shape[0] ); 136 xMax = opts.xMax || max( arr, 0, arr.shape[0] ); 137 yMin = opts.yMin || min( arr, 1, arr.shape[0] ); 138 yMax = opts.yMax || max( arr, 1, arr.shape[0] ); 139 140 if ( xMin >= xMax ) { 141 throw new RangeError( '`x` min must be strictly less than max' ); 142 } 143 if ( yMin >= yMax ) { 144 throw new RangeError( '`y` min must be strictly less than max' ); 145 } 146 147 kernelFunction = opts.kernel || gaussian; 148 149 // Create the `ndarray` to hold the density values: 150 z = ndarray({ 151 'shape': [n, n] 152 } ); 153 154 // Make the grid: 155 gridX = linspace(xMin, xMax, n); 156 gridY = linspace(yMin, yMax, n); 157 158 // Loop through x and y indices: 159 for ( ix = 0; ix < gridX.length; ix++ ) { 160 gx = gridX[ ix ]; 161 for ( iy = 0; iy < gridY.length; iy++ ) { 162 gy = gridY[ iy ]; 163 ans = 0.0; 164 for ( i = 0; i < arr.shape[ 0 ]; i++ ) { 165 xVal = arr.get( i, 0 ); 166 yVal = arr.get( i, 1 ); 167 168 zScoreX = ( (xVal - gx) / hX ); 169 zScoreY = ( (yVal - gy) / hY ); 170 171 subX = ( 1.0 / hX ) * kernelFunction( zScoreX ); 172 subY = ( 1.0 / hY ) * kernelFunction( zScoreY ); 173 ans += ( subX * subY ); 174 } 175 z.set( ix, iy, ans / arr.shape[0] ); 176 } 177 } 178 out = {}; 179 setReadOnly( out, 'x', gridX ); 180 setReadOnly( out, 'y', gridY ); 181 setReadOnly( out, 'z', z ); 182 return out; 183 } 184 185 186 // EXPORTS // 187 188 module.exports = kde2d;