Published
Edited
Dec 7, 2018
18 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
stdlib = require( 'https://unpkg.com/@stdlib/stdlib@0.0.55' )
Insert cell
Insert cell
randi = stdlib.base.random.randi
Insert cell
randu = stdlib.base.random.randu
Insert cell
randn = stdlib.base.random.normal
Insert cell
Insert cell
ndarray = stdlib.ndarray
Insert cell
Vector = ndarray( 'float64', 1 )
Insert cell
Matrix = ndarray( 'float64', 2 )
Insert cell
/**
* Creates a vector from an array-like object.
*
* @param {ArrayLikeObject} x - source array
* @returns {ndarray} vector
*/
function vector( x ) {
return new Vector( 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 Matrix( v.data, [ 1, 2 ], [ 2, 1 ], 0, 'row-major' );
}
Insert cell
Insert cell
plot = stdlib.plot
Insert cell
Insert cell
incrkmeans = stdlib.incrkmeans
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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more