jupyter-ijavascript-utils 1.33.0 → 1.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/DOCS.md CHANGED
@@ -75,6 +75,8 @@ Give it a try here:
75
75
 
76
76
  ## What's New
77
77
 
78
+ * 1.35 - {@link module:object.extractObjectProperties|extractObjectProperties} / {@link module:object.extractObjectProperty|extractObjectProperty} - to do horizontal transposes on objects
79
+ * 1.34 - {@link module:format.mapArrayDomain|format.mapArrayDomain} and add notes in the header of {@link module:random|random} on using non-uniform distributions.
78
80
  * 1.33 - Object.augmentInherit and Object.union
79
81
  * 1.32 - Array.indexify to identify sections within a 1d array into a hierarchy.
80
82
  * 1.31 - harden Array.transpose for arrays with nulls, and Table.generateTSV
package/Dockerfile CHANGED
@@ -1,3 +1,3 @@
1
1
  # syntax=docker/dockerfile:1
2
2
 
3
- FROM darkbluestudios/jupyter-ijavascript-utils:binder_1.32.0
3
+ FROM darkbluestudios/jupyter-ijavascript-utils:binder_1.35.0
package/README.md CHANGED
@@ -55,6 +55,8 @@ This is not intended to be the only way to accomplish many of these tasks, and a
55
55
 
56
56
  # What's New
57
57
 
58
+ * 1.35 - object.extractObjectProperties / object.extractObjectProperty - to do horizontal transposes on objects
59
+ * 1.34 - format.mapArrayDomain and add notes in to random on using non-uniform distributions.
58
60
  * 1.33 - Object.augmentInherit and Object.union
59
61
  * 1.32 - Array.indexify to identify sections within a 1d array into a hierarchy.
60
62
  * 1.31 - harden Array.transpose for arrays with nulls, and Table.generateTSV
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyter-ijavascript-utils",
3
- "version": "1.33.0",
3
+ "version": "1.35.0",
4
4
  "description": "Utilities for working with iJavaScript - a Jupyter Kernel",
5
5
  "homepage": "https://jupyter-ijavascript-utils.onrender.com/",
6
6
  "license": "MIT",
package/src/aggregate.js CHANGED
@@ -502,6 +502,7 @@ module.exports.unique = function unique(collection, accessor, uniquifierFn) {
502
502
 
503
503
  return Array.from(new Set(
504
504
  collection.map(cleanedFunc)
505
+ .reduce((result, val) => (val instanceof Set || Array.isArray(val)) ? [...result, ...val] : [...result, val], [])
505
506
  ));
506
507
  };
507
508
 
package/src/format.js CHANGED
@@ -24,6 +24,7 @@
24
24
  * * {@link module:format.millisecondDuration|format.millisecondDuration}
25
25
  * * Mapping Values
26
26
  * * {@link module:format.mapDomain|format.mapDomain} - projects a value from a domain of expected values to a range of output values, ex: 10% of 2 Pi
27
+ * * {@link module:format.mapArrayDomain|format.mapArrayDomain} - projects a value from between a range a value, and picks the corresponding value from an array
27
28
  * * Identifying Time Periods
28
29
  * * {@link module:format.timePeriod|format.timePeriod} - Converts a time to a time period, very helpful for animations
29
30
  * * {@link module:format.timePeriodPercent|format.timePeriodPercent} - Determines the percent complete of the current time period
@@ -383,6 +384,112 @@ module.exports.mapDomain = function mapDomain(val, [domainMin, domainMax], [rang
383
384
  return (((val - domainMin) * (rangeMax - rangeMin)) / (domainMax - domainMin)) + rangeMin;
384
385
  };
385
386
 
387
+ /**
388
+ * projects a value from a domain of expected values to an array - very useful for random distributions.
389
+ *
390
+ * like mapping normal / gaussian distributions to an array of values with
391
+ * [d3-random](https://observablehq.com/@d3/d3-random)
392
+ * as format.mapArrayDomain projects a value from between a range a value,
393
+ * and picks the corresponding value from an array.
394
+ *
395
+ * For example:
396
+ *
397
+ * ```
398
+ * require('esm-hook');
399
+ * d3 = require('d3');
400
+ *
401
+ * //-- create a number generator using Normal / Gaussian distribution
402
+ * randomGenerator = d3.randomNormal(
403
+ * 0.5, // mu - or centerline
404
+ * 0.1 // sigma - or spread of values
405
+ * );
406
+ *
407
+ * randomValue = randomGenerator();
408
+ * // randomValue - 0.4
409
+ *
410
+ * randomDataset = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
411
+ *
412
+ * //-- create an array of 3 items, each with the results from randomGenerator
413
+ * results = utils.array.size(3, () => randomGenerator());
414
+ * // [ 0.6235937672428706, 0.4991359903898883, 0.4279365561645624 ]
415
+ *
416
+ * //-- map those values to the randomDataset
417
+ * results.map(val => ({ pick: utils.format.mapArrayDomain(val, randomDataset) }));
418
+ * // [ { pick: 'g' }, { pick: 'e' }, { pick: 'e' } ]
419
+ *
420
+ * //-- group them by the pick field
421
+ * //-- then add a new property called count - using the # of records with the same value
422
+ * groupedResults = utils.group.by(resultPicks, 'pick')
423
+ * .reduce((list) => ({ count: list.length }));
424
+ * // [ { pick: 'g', count: 1 }, { pick: 'e', count: 2 } ]
425
+ *
426
+ * //-- make a bar chart (only with 10k results)
427
+ * utils.vega.embed((vl) => {
428
+ * return vl
429
+ * .markBar()
430
+ * .title('Distribution')
431
+ * .data(groupedResults)
432
+ * .encode(
433
+ * vl.x().fieldN('value'),
434
+ * vl.y().fieldQ('count').scale({type: 'log'})
435
+ * );
436
+ * });
437
+ * ```
438
+ * ![Screenshot of the chart above](img/randomMap_normalDistribution.png)
439
+ *
440
+ * @param {Number} val - value to be mapped
441
+ * @param {Array} targetArray - array of values to pick from
442
+ * @param {Array} domain - [min, max] - domain of possible input values
443
+ * @param {Array} [domain.domainMin = 0] - minimum input value (anything at or below maps to rangeMin)
444
+ * @param {Array} [domain.domainMax = 1] - maximum input value (anything at or above maps to rangeMax)
445
+ * @returns Number
446
+ * @see {@link module:format.clampDomain|clampDomain(value, [min, max])}
447
+ * @example
448
+ *
449
+ * //-- array of 10 values
450
+ * randomArray = ['a', 'b', 'c', 'd', 'e'];
451
+ *
452
+ * format.mapArrayDomain(-1, randomArray, [0, 5]);
453
+ * // 'a' - since it is below the minimum value
454
+ * format.mapArrayDomain(6, randomArray, [0, 5]);
455
+ * // 'e' - since it is the minimum value
456
+ *
457
+ * format.mapArrayDomain(0.9, randomArray, [0, 5]);
458
+ * // 'a'
459
+ * format.mapArrayDomain(1, randomArray, [0, 5]);
460
+ * // 'b'
461
+ * format.mapArrayDomain(2.5, randomArray, [0, 5]);
462
+ * // 'c'
463
+ *
464
+ * //-- or leaving the domain of possible values value can be out:
465
+ * format.mapArrayDomain(0.5, randomArray); // assumed [0, 1]
466
+ * // 'c'
467
+ */
468
+ module.exports.mapArrayDomain = function mapArrayDomain(val, targetArray, domain = null) {
469
+ if (!targetArray || !Array.isArray(targetArray)) {
470
+ throw Error('mapArrayDomain: targetArray is not an array');
471
+ } else if (targetArray.length < 1) {
472
+ throw Error('mapArrayDomain: targetArray is not a populated array');
473
+ }
474
+
475
+ const cleanArray = domain || [];
476
+ const [domainMin = 0, domainMax = 1] = cleanArray;
477
+
478
+ if (val <= domainMin) {
479
+ return targetArray[0];
480
+ } else if (val >= domainMax) {
481
+ return targetArray[targetArray.length - 1];
482
+ }
483
+
484
+ const targetIndex = Math.floor(FormatUtils.mapDomain(
485
+ val,
486
+ [domainMin, domainMax],
487
+ [0, targetArray.length]
488
+ ));
489
+ // console.log(targetIndex);
490
+ return targetArray[targetIndex];
491
+ };
492
+
386
493
  /**
387
494
  * Given that a period of time is millisecondPeriod number of milliseconds long,
388
495
  * determines which period of time we are currently in (timeEpoch / millisecondPeriod)
package/src/object.js CHANGED
@@ -31,6 +31,8 @@ const FormatUtils = require('./format');
31
31
  * * {@link module:object.join|join(array, index, map, fn)} - join a collection against a map by a given index
32
32
  * * {@link module:object.joinProperties|join(array, index, map, ...fields)} - join a collection, and copy properties over from the mapped object.
33
33
  * * {@link module:object.propertyFromList|propertyFromList(array, propertyName)} - fetches a specific property from all objects in a list
34
+ * * {@link module:object.extractObjectProperty|extractObjectProperty(list, propertyNameOrFn)} - extracts a property or fn across all objects in list.
35
+ * * {@link module:object.extractObjectProperties|extractObjectProperties(list, propertyNameOrFnMap)} - extracts multiple propertie or fn across all objects in list.
34
36
  * * Rename properties
35
37
  * * {@link module:object.cleanProperties|cleanProperties()} - correct inaccessible property names in a list of objects - in place
36
38
  * * * {@link module:object.cleanProperties2|cleanProperties2()} - correct inaccessible property names in a list of objects - on a cloned list
@@ -536,6 +538,100 @@ module.exports.fetchObjectProperties = function fetchObjectProperties(list, prop
536
538
  });
537
539
  };
538
540
 
541
+ /**
542
+ * Similar to a transpose, this finds all the values of a particular property
543
+ * within a list of objects.
544
+ *
545
+ * ```
546
+ * weather = [
547
+ * { id: 1, city: 'Seattle', month: 'Aug', precip: 0.87 },
548
+ * null,
549
+ * { id: 3, city: 'New York', month: 'Apr', precip: 3.94 },
550
+ * { id: 6, city: 'Chicago', month: 'Apr', precip: 3.62 }
551
+ * ];
552
+ *
553
+ * utils.object.extractObjectProperty(weather, 'city');
554
+ * // [ 'Seattle', 'New York', 'Chicago'];
555
+ * ```
556
+ *
557
+ * @param {Object|Object[]} objectList - list of objects to extract the property from
558
+ * @param {Function | String} propertyOrFn - Name of the property or accessor function
559
+ * @returns {Array} - single array the values stored in propertyOrFn across all objects in objectList.
560
+ * @see {@link module:aggregate.unique|unique()} to see all the unique values stored
561
+ * @see {@link module:object.extractObjectProperties|extractObjectProperties(list, propertyNameOrFnMap)} to see the values extracted into a single object / horizontal transpose.
562
+ */
563
+ module.exports.extractObjectProperty = function extractObjectProperty(list, propertyOrFn) {
564
+ let cleanList = !list ? [] : Array.isArray(list) ? list : [list];
565
+ cleanList = cleanList.filter((r) => r);
566
+ const fn = ObjectUtils.evaluateFunctionOrProperty(propertyOrFn);
567
+ return cleanList.map(fn);
568
+ };
569
+
570
+ /**
571
+ * Similar to a transpose, this finds all the values of a particular property
572
+ * within a list of objects.
573
+ *
574
+ * ```
575
+ * weather = [
576
+ * { id: 1, city: 'Seattle', month: 'Aug', precip: 0.87 },
577
+ * null,
578
+ * { id: 3, city: 'New York', month: 'Apr', precip: 3.94 },
579
+ * { id: 6, city: 'Chicago', month: 'Apr', precip: 3.62 }
580
+ * ];
581
+ *
582
+ * utils.object.extractObjectProperties(weather, ['city', 'month']);
583
+ * // { city: [ 'Seattle', 'New York', 'Chicago'], month: ['Aug', 'Apr', 'Apr'] }
584
+ *
585
+ * // note you can also pass maps with property name strings, or functions.
586
+ * extractionMap = new Map();
587
+ * extractionMap.set('city', null); // default the property by the key name
588
+ * extractionMap.set('city2', 'city'); // specify the property to use
589
+ * extractionMap.set('city3', (r) => r.city); // specify a function
590
+ *
591
+ * utils.object.extractObjectProperties(weather, extractionMap);
592
+ * // {
593
+ * // city: ['Seattle', 'New York', 'Chicago'],
594
+ * // city2: ['Seattle', 'New York', 'Chicago'],
595
+ * // city3: ['Seattle', 'New York', 'Chicago']
596
+ * // };
597
+ * ```
598
+ *
599
+ * @param {Object|Object[]} objectList - list of objects to extract the property from
600
+ * @param {Map<Function | String>} propertyOrFnMap - Name of the property or accessor function
601
+ * @returns {Object} - Object with the keys in the map as properties - extracting the values across all in list.
602
+ * @see {@link module:object.extractObjectProperty|extractObjectProperty(list, propertyNameOrFn)} to see all the values stored for a single property.
603
+ */
604
+ module.exports.extractObjectProperties = function extractObjectProperties(list, propertyOrFnMap) {
605
+ let propertyEntries = [];
606
+ const signature = 'object.extractObjectProperties(list:Object[], propertyOrFnMap:Map<String, stringOrFn>)';
607
+
608
+ if (!propertyOrFnMap) {
609
+ return [];
610
+ } else if (Array.isArray(propertyOrFnMap)) {
611
+ for (let i = 0; i < propertyOrFnMap.length; i += 1) {
612
+ const propertyOrFnKey = propertyOrFnMap[i];
613
+
614
+ //-- only string properties are accepted as an array
615
+ if (typeof propertyOrFnKey === 'string') {
616
+ propertyEntries.push([propertyOrFnKey, propertyOrFnKey]);
617
+ }
618
+ }
619
+ } else if (propertyOrFnMap instanceof Map) {
620
+ propertyEntries = [...propertyOrFnMap.entries()];
621
+ } else {
622
+ throw Error(`${signature}: propertyOrFnMap must be a map of propertyName keys, with a function or property name as the value`);
623
+ }
624
+
625
+ const results = {};
626
+ propertyEntries.forEach(([propertyName, propertyOrFn]) => {
627
+ results[propertyName] = ObjectUtils.extractObjectProperty(list, propertyOrFn || propertyName);
628
+ // const fn = ObjectUtils.evaluateFunctionOrProperty(propertyOrFn);
629
+ // results[propertyName] = cleanList.map(fn);
630
+ });
631
+
632
+ return results;
633
+ };
634
+
539
635
  /**
540
636
  * Accesses a property using a string
541
637
  * @param {Object} obj - object to access the properties on
package/src/random.js CHANGED
@@ -17,6 +17,8 @@ const SimplexModule = require('./random_simplex');
17
17
  * * Simplex Noise
18
18
  * * {@link module:random.simplexGenerator|simplexGenerator(seed)} - Number generator between -1 and 1 given an x/y/z coordinate
19
19
  *
20
+ * If leveraging
21
+ *
20
22
  * While generating a simple number between two values is common, it is very important - and useful in generating fake data.
21
23
  *
22
24
  * ```
@@ -39,6 +41,60 @@ const SimplexModule = require('./random_simplex');
39
41
  *
40
42
  * The possibilities are endless.
41
43
  *
44
+ * <hr />
45
+ *
46
+ * <b>This library is meant to provide simple use cases.</b>
47
+ * Please use Standard libraries
48
+ * - like [d3-random](https://observablehq.com/@d3/d3-random) for additional alternatives
49
+ *
50
+ * see {@link module:format.mapArrayDomain|format.mapArrayDomain} - as it projects a value
51
+ * from between a range a value, and picks the corresponding value from an array.
52
+ *
53
+ * For example:
54
+ *
55
+ * ```
56
+ * require('esm-hook');
57
+ * d3 = require('d3');
58
+ *
59
+ * //-- create a number generator using Normal / Gaussian distribution
60
+ * randomGenerator = d3.randomNormal(
61
+ * 0.5, // mu - or centerline
62
+ * 0.1 // sigma - or spread of values
63
+ * );
64
+ *
65
+ * randomValue = randomGenerator();
66
+ * // randomValue - 0.4
67
+ *
68
+ * randomDataset = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
69
+ *
70
+ * //-- create an array of 3 items, each with the results from randomGenerator
71
+ * results = utils.array.size(3, () => randomGenerator());
72
+ * // [ 0.6235937672428706, 0.4991359903898883, 0.4279365561645624 ]
73
+ *
74
+ * //-- map those values to the randomDataset
75
+ * results.map(val => ({ pick: utils.format.mapArrayDomain(val, randomDataset) }));
76
+ * // [ { pick: 'g' }, { pick: 'e' }, { pick: 'e' } ]
77
+ *
78
+ * //-- group them by the pick field
79
+ * //-- then add a new property called count - using the # of records with the same value
80
+ * groupedResults = utils.group.by(resultPicks, 'pick')
81
+ * .reduce((list) => ({ count: list.length }));
82
+ * // [ { pick: 'g', count: 1 }, { pick: 'e', count: 2 } ]
83
+ *
84
+ * //-- make a bar chart (only with 10k results)
85
+ * utils.vega.embed((vl) => {
86
+ * return vl
87
+ * .markBar()
88
+ * .title('Distribution')
89
+ * .data(groupedResults)
90
+ * .encode(
91
+ * vl.x().fieldN('value'),
92
+ * vl.y().fieldQ('count').scale({type: 'log'})
93
+ * );
94
+ * });
95
+ * ```
96
+ * ![Screenshot of the chart above](img/randomMap_normalDistribution.png)
97
+ *
42
98
  * @module random
43
99
  */
44
100
  const RandomUtil = module.exports;
@@ -65,13 +121,14 @@ module.exports.getSeed = function getSeed() {
65
121
  };
66
122
 
67
123
  /**
68
- * Generate a random integer
124
+ * Generate a random integer (with uniform distribution)
69
125
  * @param {Number} [min = 0] - Minimum integer (inclusive) that could be generated
70
126
  * @param {Number} [max = 100] - Maximum integer (inclusive) number that could be generated
71
127
  * @returns {Number} - number between (and including) min and max
72
128
  * @example
73
129
  * utils.random.randomInteger(0, 100) // 40
74
130
  * utils.random.randomInteger(0, 100) // 96
131
+ * @see [d3-random](https://observablehq.com/@d3/d3-random) for additional alternatives
75
132
  */
76
133
  module.exports.randomInteger = function randomInteger(min = 0, max = 100) {
77
134
  //-- can only occur if the user exports to htmlScript
@@ -84,11 +141,12 @@ module.exports.randomInteger = function randomInteger(min = 0, max = 100) {
84
141
  };
85
142
 
86
143
  /**
87
- * Generates a random floating point number
144
+ * Generates a random floating point number (with uniform distribution)
88
145
  * @param {Number} [min = 0] - Minimum float (inclusive) that could be generated
89
146
  * @param {Number} [max = 100] - Maximum float (inclusive) number that the value will be less than
90
147
  * @returns {Number} - number between (and including) min and max
91
148
  * utils.random.randomInteger(0, 1) // 0.224223
149
+ * @see [d3-random](https://observablehq.com/@d3/d3-random) for additional alternatives
92
150
  */
93
151
  module.exports.random = function random(min = 0, max = 1) {
94
152
  //-- can only occur if the user exports to htmlScript
@@ -102,11 +160,12 @@ module.exports.random = function random(min = 0, max = 1) {
102
160
  };
103
161
 
104
162
  /**
105
- * Picks a random value from an array of values
163
+ * Picks a random value from an array of values (with uniform distribution)
106
164
  * @param {Array} targetArray - Array of values to pick from
107
165
  * @returns {any} - one of the values picked at random from the target array
108
166
  * @example
109
167
  * utils.random.pickRandom(['apple', 'orange', 'pear']); // 'pear'
168
+ * @see [d3-random](https://observablehq.com/@d3/d3-random) for additional alternatives
110
169
  */
111
170
  module.exports.pickRandom = function pickRandom(targetArray) {
112
171
  if (!targetArray || !Array.isArray(targetArray)) {