Public
Edited
Mar 15
18 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
randi = require( 'https://cdn.jsdelivr.net/gh/stdlib-js/random-base-randi@umd/browser.js' )
Insert cell
randu = require( 'https://cdn.jsdelivr.net/gh/stdlib-js/random-base-randu@umd/browser.js' )
Insert cell
randn = require( 'https://cdn.jsdelivr.net/gh/stdlib-js/random-base-normal@umd/browser.js' )
Insert cell
Insert cell
ndarray = require( 'https://cdn.jsdelivr.net/gh/stdlib-js/ndarray-ctor@umd/browser.js' )
Insert cell
/**
* Creates a vector from an array-like object.
*
* @param {ArrayLikeObject} x - source array
* @returns {ndarray} vector
*/
function vector( x ) {
return new ndarray( 'float64', new Float64Array( x ), [ x.length ], [ 1 ], 0, 'row-major' );
}
Insert cell
/**
* Converts a vector to a matrix.
*
* @param {ndarray} v - vector
* @returns {ndarray} matrix
*/
function vec2mat( v ) {
return new ndarray( 'float64', v.data, [ 1, 2 ], [ 2, 1 ], 0, 'row-major' );
}
Insert cell
Insert cell
plot_pkg = require( 'https://cdn.jsdelivr.net/npm/@stdlib/dist-plot-flat' )
Insert cell
plot = plot_pkg.plot
Insert cell
Insert cell
incrkmeans = require( 'https://cdn.jsdelivr.net/gh/stdlib-js/ml-incr-kmeans@umd/browser.js' )
Insert cell
Insert cell
Insert cell
/**
* Computes the cumulative sum.
*
* @param {ArrayLikeObject} arr - array over which to compute the cumulative sum
* @returns {Array} cumulative sum
*/
function csum( arr ) {
var out;
var i;
out = [];
out.push( arr[ 0 ] );
for ( i = 1; i < arr.length; i++ ) {
out.push( out[ i-1 ] + arr[ i ] );
}
return out;
}
Insert cell
Insert cell
/**
* Generates a "corrupted" (i.e., noisy) observation.
*
* @param {ArrayLikeObject} means - state observation means
* @param {ArrayLikeObject} sigmas - state observation standard deviations
* @returns {Array} noisy data
*/
function corrupt( means, sigmas ) {
var ndims;
var out;
var i;
// Determine the number of dimensions:
ndims = means.length;
// For each dimension, generate a value corrupted by Guassian noise...
out = [];
for ( i = 0; i < ndims; i++ ) {
out.push( randn( means[ i ], sigmas[ i ] ) );
}
// Return the noisy data:
return out;
}
Insert cell
Insert cell
/**
* Returns a iterator protocol-compliant object for simulating a discrete-time hidden Markov chain.
*
* @param {integer} N - number of data points to simulate
* @param {ArrayLikeObject} initial - initial probabilities
* @param {ArrayLikeObject<ArrayLikeObject>} transition - transition probabilities
* @param {ArrayLikeObject<ArrayLikeObject>} means - state observation means
* @param {ArrayLikeObject<ArrayLikeObject>} sigmas - state observation standard deviations
* @param {integer} [seed] - pseudorandom number generator (PRNG) seed
* @returns {Iterator} iterator
*/
function simulator( N, initial, transition, means, sigmas, seed ) {
var nStates;
var state;
var probs;
var iter;
var rand;
var FLG;
var n;
var i;
nStates = initial.length;
n = -1;
// Define a PRNG seed:
seed = seed || (randi()>>>0);
// Create a seeded PRNG:
rand = randu.factory({
'seed': seed
});
// Convert the initial probabilities to cumulative probabilities:
initial = csum( initial );
// Convert each set of transition probabilities to cumulative probabilities:
transition = transition.slice(); // avoid mutation
for ( i = 0; i < nStates; i++ ) {
transition[ i ] = csum( transition[ i ] );
}
// Return an iterator protocol-compliant object:
iter = {};
iter.next = next;
iter.return = end;
return iter;
/**
* Generates the next simulated observation.
*
* @private
* @returns {Object} simulated observation as an iterator protocol-compliant object
*/
function next() {
var r;
var v;
var i;
n += 1;
if ( FLG || n >= N ) {
return {
'done': true
};
}
if ( n === 0 ) {
// Determine the initial state:
r = rand();
for ( i = 0; i < nStates; i++ ) {
if ( r < initial[ i ] ) {
state = i;
v = corrupt( means[ state ], sigmas[ state ] );
v.push( state );
return {
'value': v,
'done': false
};
}
}
} else {
// Simulate the next state:
probs = transition[ state ];
r = rand();
for ( i = 0; i < nStates; i++ ) {
if ( r < probs[ i ] ) {
state = i;
v = corrupt( means[ state ], sigmas[ state ] );
v.push( state );
return {
'value': v,
'done': false
};
}
}
}
}
/**
* Finishes the iterator.
*
* @private
* @param {*} [value] - value to return
* @returns {Object} iterator protocol-compliant object
*/
function end( value ) {
FLG = true;
if ( arguments.length ) {
return {
'value': value,
'done': true
};
}
return {
'done': true
};
}
}
Insert cell
Insert cell
I = [ 0.1, 0.3, 0.2, 0.1, 0.3 ] // should sum to 1
Insert cell
Insert cell
A = {
var transition = new Array( 5 );
// Transition probabilities when in state s_0:
transition[ 0 ] = [ 0.2, 0.7, 0.1, 0.0, 0.0 ]; // should sum to 1
// Transition probabilities when in state s_1:
transition[ 1 ] = [ 0.4, 0.3, 0.25, 0.05, 0.0 ]; // should sum to 1
// Transition probabilities when in state s_2:
transition[ 2 ] = [ 0.05, 0.2, 0.4, 0.3, 0.05 ]; // should sum to 1
// Transition probabilities when in state s_3:
transition[ 3 ] = [ 0.02, 0.18, 0.2, 0.2, 0.4 ]; // should sum to 1
// Transition probabilities when in state s_4:
transition[ 4 ] = [ 0.3, 0.0, 0.0, 0.2, 0.5 ]; // should sum to 1
return transition;
}
Insert cell
Insert cell
means = [ [ 0.2, 0.7 ], [ 0.35, 0.52 ], [ 0.55, 0.67 ], [ 0.6, 0.1 ], [ 0.8, 0.1 ] ]
Insert cell
Insert cell
sigmas = [ [ 0.09, 0.05 ], [ 0.11, 0.07 ], [ 0.07, 0.08 ], [ 0.12, 0.04 ], [ 0.15, 0.04 ] ]
Insert cell
Insert cell
nStates = means.length
Insert cell
ndims = means[ 0 ].length
Insert cell
Insert cell
it = yield simulator( 10, I, A, means, sigmas, 123456 )
Insert cell
it
Insert cell
Insert cell
N = 2500
Insert cell
Insert cell
sample_data = {
var states;
var seed;
var it;
var x;
var y;
var v;
// Define a seed in order to ensure that simulated data is easily reproducible:
seed = 123456;
// Create a new (seeded) simulator:
it = simulator( N, I, A, means, sigmas, seed );
// Initialize output arrays for storing the simulated values:
x = [];
y = [];
states = [];
// Generate simulated data...
while ( true ) {
v = it.next();
if ( v.done ) {
break;
}
// Note: we assume 2-dimensional data...
x.push( v.value[ 0 ] );
y.push( v.value[ 1 ] );
states.push( v.value[ 2 ] );
}
return [ x, y, states ];
}
Insert cell
Insert cell
sample_timeseries_plot = {
var x = [];
var i;
for ( i = 0; i < N; i++ ) {
x.push( i );
}
return plot( [ x, x ], [ sample_data[ 0 ], sample_data[ 1 ] ], {
'xMin': 0,
'xMax': 100, // adjust to visualize more or less of the timeseries
'yMin': -0.1,
'yMax': 1.1,
'width': 800,
'height': 600
});
}
Insert cell
html`${sample_timeseries_plot.render( 'html' )}`
Insert cell
Insert cell
sample_plot = plot( [ sample_data[ 0 ] ], [ sample_data[ 1 ] ], {
'xMin': -0.1,
'xMax': 1.1,
'yMin': -0.1,
'yMax': 1.1,
'lineStyle': 'none',
'symbols': 'closed-circle',
'symbolsOpacity': 0.15,
'width': 600,
'height': 600
})
Insert cell
html`${sample_plot.render( 'html' )}`
Insert cell
Insert cell
sample_state_data = {
var states;
var X;
var Y;
var N;
var s;
var x;
var y;
var i;
X = sample_data[ 0 ];
Y = sample_data[ 1 ];
states = sample_data[ 2 ];
N = X.length;
x = new Array( nStates );
y = new Array( nStates );
for ( i = 0; i < nStates; i++ ) {
x[ i ] = [];
y[ i ] = [];
}
// Group simulated data according to the state index...
for ( i = 0; i < N; i++ ) {
s = states[ i ];
x[ s ].push( X[ i ] );
y[ s ].push( Y[ i ] );
}
return [ x, y ];
}
Insert cell
Insert cell
sample_plot_color_coded = plot( sample_state_data[ 0 ], sample_state_data[ 1 ], {
'xMin': -0.1,
'xMax': 1.1,
'yMin': -0.1,
'yMax': 1.1,
'lineStyle': 'none',
'symbols': 'closed-circle',
'symbolsOpacity': 0.15,
'width': 600,
'height': 600
})
Insert cell
html`${sample_plot_color_coded.render( 'html' )}`
Insert cell
Insert cell
Insert cell
Insert cell
cluster = incrkmeans( nStates, ndims, {
'metric': 'euclidean',
'init': [ 'kmeans++' ],
'seed': 1234 // provide seed in order to ensure reproducible k-means++ initialization
})
Insert cell
Insert cell
cluster_data = {
var states;
var seed;
var vec;
var res;
var it;
var cx;
var cy;
var x;
var y;
var v;
var i;
// Define a seed in order to ensure that simulated data is easily reproducible:
seed = 123456;
// Create a new (seeded) simulator:
it = simulator( N, I, A, means, sigmas, seed );
x = [];
y = [];
states = [];
cx = [];
cy = [];
vec = vector( [ 0.0, 0.0 ] );
// Simulate each observation individually and incrementally update the k-means accumulator...
while ( true ) {
v = it.next();
if ( v.done ) {
break;
}
// Note: we assume 2-dimensional data...
x.push( v.value[ 0 ] );
vec.set( 0, v.value[ 0 ] );
y.push( v.value[ 1 ] );
vec.set( 1, v.value[ 1 ] );
states.push( v.value[ 2 ] );
// Update the accumulator:
res = cluster( vec );
// If results are returned, store the centroid positions...
if ( res ) {
for ( i = 0; i < nStates; i++ ) {
cx.push( res.centroids.get( i, 0 ) );
cy.push( res.centroids.get( i, 1 ) );
}
}
}
return [ [ x, cx ], [ y, cy ], states, res ];
}
Insert cell
Insert cell
cluster_plot = plot({
'xMin': -0.1,
'xMax': 1.1,
'yMin': -0.1,
'yMax': 1.1,
'lineStyle': 'none',
'symbols': 'closed-circle',
'symbolsOpacity': [ 0.15, 0.01 ],
'width': 600,
'height': 600
})
Insert cell
cluster_plot_html = {
cluster_plot.x = cluster_data[ 0 ];
cluster_plot.y = cluster_data[ 1 ];
return cluster_plot.render( 'html' );
}
Insert cell
html`${cluster_plot_html}`
Insert cell
Insert cell
Insert cell
Insert cell
Analyzer = {
/**
* Analyzer constructor.
*
* @returns {Analyzer} Analyzer instance
*/
function Analyzer() {
this.init();
return this;
}
/**
* Initializes an analyzer.
*/
Analyzer.prototype.init = function init() {
// Initialize `x` and `y` data vectors:
this.x = new Float64Array( N );
this.y = new Float64Array( N );
// Initialize centroid data vectors:
this.cx = new Float64Array( nStates );
this.cy = new Float64Array( nStates );
// Initialize a "time" vector:
this.t = new Uint32Array( N );
// Initialize a "time" counter:
this.i = -1;
// Initialize a vector for updating the k-means accumulator:
this.v = vector( [ 0.0, 0.0 ] );
// Initialize a k-means accumulator:
this.acc = incrkmeans( nStates, ndims, {
'metric': 'euclidean',
'init': [ 'kmeans++' ],
'seed': 1234 // provide seed in order to ensure reproducible k-means++ initialization
});
// Initialize a plot constructor for visualizing simulated timeseries:
this.timeseries = plot({
'title': 'Simulated Timeseries',
'xMin': 0,
'xMax': 250,
'yMin': -0.1,
'yMax': 1.1,
'width': 800,
'height': 600
});
// Initialize a plot constructor for a two-dimensional scatter plot:
this.scatter = plot({
'title': 'Cluster Analysis',
'xMin': -0.1,
'xMax': 1.1,
'yMin': -0.1,
'yMax': 1.1,
'lineStyle': 'none',
'symbols': 'closed-circle',
'symbolsOpacity': [ 0.15, 1.0 ],
'width': 600,
'height': 600
});
};
/**
* Updates an analyzer.
*
* @param {ArrayLikeObject} datum - datum
* @returns {Analyzer} Analyzer instance
*/
Analyzer.prototype.update = function update( datum ) {
var res;
var i;
// Update the "time" counter:
this.i += 1;
// Update the "time" vector:
this.t[ this.i ] = this.i;
// Cache the provided data:
this.x[ this.i ] = datum[ 0 ];
this.y[ this.i ] = datum[ 1 ];
// Update the accumulator:
this.v.set( 0, datum[ 0 ] );
this.v.set( 1, datum[ 1 ] );
res = this.acc( this.v );
// If results are returned, update the centroid positions...
if ( res ) {
for ( i = 0; i < nStates; i++ ) {
this.cx[ i ] = res.centroids.get( i, 0 );
this.cy[ i ] = res.centroids.get( i, 1 );
}
}
return this;
};
/**
* Renders analysis plots.
*
* @returns {Array<string>} plots
*/
Analyzer.prototype.render = function render() {
var t;
var x;
var y;
// Create typed array views:
t = new Uint32Array( this.t.buffer, 0, this.i+1 );
x = new Float64Array( this.x.buffer, 0, this.i+1 );
y = new Float64Array( this.y.buffer, 0, this.i+1 );
// Update the timeseries plot:
this.timeseries.x = [ t, t ];
this.timeseries.y = [ x, y ];
if ( this.i > 250 ) {
this.timeseries.xMin = t[ this.i-250 ];
this.timeseries.xMax = t[ this.i ];
}
// Update the scatter plot:
this.scatter.x = [ x, this.cx ];
this.scatter.y = [ y, this.cy ];
// Render the plots:
return [ this.timeseries.render( 'html' ), this.scatter.render( 'html' ) ];
}
return Analyzer;
}
Insert cell
Insert cell
analyzer = new Analyzer()
Insert cell
Insert cell
simulated_datum = {
var simulate;
var interval;
var seed;
var v;
// Define a seed in order to ensure that simulated data is easily reproducible:
seed = 123456;
// Define the interval between simulated datums:
interval = 100; // milliseconds
// Create a new simulator:
simulate = simulator( 0, I, A, means, sigmas, seed ); // ATTN: uncomment me to clear the simulation!
//simulate = simulator( N, I, A, means, sigmas, seed ); // ATTN: uncomment me to start the simulation!
// Reset the simulation:
yield Promises.delay( interval, null );
// Simulate real-time data...
while ( true ) {
v = simulate.next();
if ( v.done ) {
yield Promises.delay( interval, null );
break;
}
yield Promises.delay( interval, v.value.slice( 0, ndims ) );
}
}
Insert cell
Insert cell
Insert cell
analysis_plots = {
if ( simulated_datum === null ) {
analyzer.init();
return analyzer.render();
}
analyzer.update( simulated_datum );
return analyzer.render();
}
Insert cell
Insert cell
html`${analysis_plots[ 0 ]}`
Insert cell
html`${analysis_plots[ 1 ]}`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more