node-pandas 1.0.4 → 2.0.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.
Files changed (41) hide show
  1. package/.kiro/agents/git-committer-agent.md +208 -0
  2. package/.kiro/agents/npm-publisher-agent.md +501 -0
  3. package/.kiro/publish-status-2.0.0.md +134 -0
  4. package/.kiro/published-versions.md +11 -0
  5. package/.kiro/specs/pandas-like-enhancements/.config.kiro +1 -0
  6. package/.kiro/specs/pandas-like-enhancements/design.md +377 -0
  7. package/.kiro/specs/pandas-like-enhancements/requirements.md +257 -0
  8. package/.kiro/specs/pandas-like-enhancements/tasks.md +477 -0
  9. package/CHANGELOG.md +42 -0
  10. package/README.md +375 -103
  11. package/TESTING_SETUP.md +183 -0
  12. package/jest.config.js +25 -0
  13. package/package.json +11 -3
  14. package/src/bases/CsvBase.js +4 -13
  15. package/src/dataframe/dataframe.js +596 -64
  16. package/src/features/GroupBy.js +561 -0
  17. package/src/features/dateRange.js +106 -0
  18. package/src/index.js +6 -1
  19. package/src/series/series.js +690 -14
  20. package/src/utils/errors.js +314 -0
  21. package/src/utils/getIndicesColumns.js +1 -1
  22. package/src/utils/getTransformedDataList.js +1 -1
  23. package/src/utils/logger.js +259 -0
  24. package/src/utils/typeDetection.js +339 -0
  25. package/src/utils/utils.js +5 -1
  26. package/src/utils/validation.js +450 -0
  27. package/tests/README.md +151 -0
  28. package/tests/integration/.gitkeep +0 -0
  29. package/tests/integration/README.md +3 -0
  30. package/tests/property/.gitkeep +0 -0
  31. package/tests/property/README.md +3 -0
  32. package/tests/setup.js +16 -0
  33. package/tests/test.js +58 -21
  34. package/tests/unit/.gitkeep +0 -0
  35. package/tests/unit/README.md +3 -0
  36. package/tests/unit/dataframe.test.js +1141 -0
  37. package/tests/unit/example.test.js +23 -0
  38. package/tests/unit/series.test.js +441 -0
  39. package/tests/unit/tocsv.test.js +838 -0
  40. package/tests/utils/testAssertions.js +143 -0
  41. package/tests/utils/testDataGenerator.js +123 -0
@@ -1,18 +1,694 @@
1
- class NodeSeries extends Array {
2
- constructor(data) {
3
- super(...data) // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
4
- this._data = data
5
- }
6
-
7
- get show() {
8
- console.table(this._data)
9
- }
1
+ /**
2
+ * @fileoverview Series class for one-dimensional labeled data structures.
3
+ * Extends JavaScript's native Array class to provide pandas-like Series functionality
4
+ * with support for statistical operations, transformations, and data manipulation.
5
+ *
6
+ * Validates: Requirements 1.1, 1.6, 6.1, 6.4, 7.1, 7.2, 7.3, 7.5, 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 12.1, 12.2, 12.3, 12.4, 12.5, 12.6, 12.7, 12.8
7
+ */
8
+
9
+ const {
10
+ isNull,
11
+ isNumeric,
12
+ detectType,
13
+ inferArrayType,
14
+ toNumeric
15
+ } = require('../utils/typeDetection');
16
+
17
+ const {
18
+ validateNotNull,
19
+ validateArray,
20
+ validateFunction,
21
+ validateNumber
22
+ } = require('../utils/validation');
23
+
24
+ const {
25
+ DataFrameError,
26
+ ValidationError,
27
+ TypeError: TypeErrorClass,
28
+ OperationError
29
+ } = require('../utils/errors');
30
+
31
+ const { getLogger } = require('../utils/logger');
32
+
33
+ const logger = getLogger();
34
+
35
+ /**
36
+ * Series class - A one-dimensional labeled array with pandas-like functionality.
37
+ * Extends JavaScript's native Array class to provide array-like behavior while
38
+ * adding statistical operations, transformations, and data manipulation methods.
39
+ *
40
+ * @class Series
41
+ * @extends Array
42
+ *
43
+ * @example
44
+ * // Create a Series from an array
45
+ * const series = new Series([1, 2, 3, 4, 5]);
46
+ *
47
+ * @example
48
+ * // Create a Series with custom index
49
+ * const series = new Series([10, 20, 30], { index: ['a', 'b', 'c'] });
50
+ *
51
+ * @example
52
+ * // Access elements
53
+ * console.log(series[0]); // 10
54
+ * console.log(series.get('a')); // 10
55
+ *
56
+ * @example
57
+ * // Perform statistical operations
58
+ * console.log(series.mean()); // 20
59
+ * console.log(series.sum()); // 60
60
+ * console.log(series.std()); // standard deviation
61
+ */
62
+ class Series extends Array {
63
+ /**
64
+ * Creates a new Series instance.
65
+ *
66
+ * @param {Array} data - The data for the Series. Can be any array of values.
67
+ * @param {Object} [options={}] - Configuration options for the Series
68
+ * @param {Array<string|number>} [options.index] - Custom index labels for elements.
69
+ * If not provided, numeric indices (0, 1, 2, ...) are used.
70
+ * @param {string} [options.name] - Optional name for the Series
71
+ *
72
+ * @throws {ValidationError} If data is not an array
73
+ *
74
+ * @example
75
+ * const series = new Series([1, 2, 3]);
76
+ *
77
+ * @example
78
+ * const series = new Series([10, 20, 30], {
79
+ * index: ['a', 'b', 'c'],
80
+ * name: 'values'
81
+ * });
82
+ */
83
+ constructor(data, options = {}) {
84
+ // Validate input
85
+ validateArray(data, 'data');
86
+
87
+ // Call parent constructor with spread operator
88
+ super(...data);
89
+
90
+ // Store metadata
91
+ this._data = data;
92
+ this._index = options.index || data.map((_, i) => i);
93
+ this._name = options.name || '';
94
+ this._type = inferArrayType(data);
95
+
96
+ // Ensure properties are non-enumerable to avoid iteration issues
97
+ Object.defineProperty(this, '_data', {
98
+ value: this._data,
99
+ writable: true,
100
+ enumerable: false,
101
+ configurable: true
102
+ });
103
+
104
+ Object.defineProperty(this, '_index', {
105
+ value: this._index,
106
+ writable: true,
107
+ enumerable: false,
108
+ configurable: true
109
+ });
110
+
111
+ Object.defineProperty(this, '_name', {
112
+ value: this._name,
113
+ writable: true,
114
+ enumerable: false,
115
+ configurable: true
116
+ });
117
+
118
+ Object.defineProperty(this, '_type', {
119
+ value: this._type,
120
+ writable: true,
121
+ enumerable: false,
122
+ configurable: true
123
+ });
124
+ }
125
+
126
+ /**
127
+ * Gets the index labels for the Series.
128
+ *
129
+ * @type {Array<string|number>}
130
+ * @readonly
131
+ *
132
+ * @example
133
+ * const series = new Series([1, 2, 3], { index: ['a', 'b', 'c'] });
134
+ * console.log(series.index); // ['a', 'b', 'c']
135
+ */
136
+ get index() {
137
+ return this._index;
138
+ }
139
+
140
+ /**
141
+ * Gets the name of the Series.
142
+ *
143
+ * @type {string}
144
+ * @readonly
145
+ *
146
+ * @example
147
+ * const series = new Series([1, 2, 3], { name: 'values' });
148
+ * console.log(series.name); // 'values'
149
+ */
150
+ get name() {
151
+ return this._name;
152
+ }
153
+
154
+ /**
155
+ * Gets the inferred data type of the Series.
156
+ *
157
+ * @type {string}
158
+ * @readonly
159
+ *
160
+ * @example
161
+ * const series = new Series([1, 2, 3]);
162
+ * console.log(series.dtype); // 'numeric'
163
+ */
164
+ get dtype() {
165
+ return this._type;
166
+ }
167
+
168
+ /**
169
+ * Gets the length of the Series.
170
+ *
171
+ * @type {number}
172
+ * @readonly
173
+ *
174
+ * @example
175
+ * const series = new Series([1, 2, 3]);
176
+ * console.log(series.length); // 3
177
+ */
178
+
179
+ /**
180
+ * Displays the Series in a formatted table.
181
+ * Uses console.table to show index and values in a readable format.
182
+ *
183
+ * @returns {void}
184
+ *
185
+ * @example
186
+ * const series = new Series([10, 20, 30], { index: ['a', 'b', 'c'] });
187
+ * series.show;
188
+ * // Displays:
189
+ * // ┌─────────┬────────┐
190
+ * // │ (index) │ Values │
191
+ * // ├─────────┼────────┤
192
+ * // │ a │ 10 │
193
+ * // │ b │ 20 │
194
+ * // │ c │ 30 │
195
+ * // └─────────┴────────┘
196
+ */
197
+ get show() {
198
+ const displayData = this._data.map((value, idx) => ({
199
+ index: this._index[idx],
200
+ value
201
+ }));
202
+ console.table(displayData);
203
+ }
204
+
205
+ /**
206
+ * Gets a value by its index label.
207
+ *
208
+ * @param {string|number} label - The index label to retrieve
209
+ * @returns {*} The value at the specified index label
210
+ *
211
+ * @throws {DataFrameError} If the label does not exist in the index
212
+ *
213
+ * @example
214
+ * const series = new Series([10, 20, 30], { index: ['a', 'b', 'c'] });
215
+ * console.log(series.get('b')); // 20
216
+ */
217
+ get(label) {
218
+ const idx = this._index.indexOf(label);
219
+ if (idx === -1) {
220
+ throw new DataFrameError(`Index label '${label}' not found in Series`, {
221
+ operation: 'get',
222
+ value: label,
223
+ expected: `one of ${JSON.stringify(this._index)}`
224
+ });
225
+ }
226
+ return this._data[idx];
227
+ }
228
+
229
+ /**
230
+ * Sets a value by its index label.
231
+ *
232
+ * @param {string|number} label - The index label to set
233
+ * @param {*} value - The value to set
234
+ * @returns {Series} Returns this Series for method chaining
235
+ *
236
+ * @throws {DataFrameError} If the label does not exist in the index
237
+ *
238
+ * @example
239
+ * const series = new Series([10, 20, 30], { index: ['a', 'b', 'c'] });
240
+ * series.set('b', 25);
241
+ * console.log(series.get('b')); // 25
242
+ */
243
+ set(label, value) {
244
+ const idx = this._index.indexOf(label);
245
+ if (idx === -1) {
246
+ throw new DataFrameError(`Index label '${label}' not found in Series`, {
247
+ operation: 'set',
248
+ value: label,
249
+ expected: `one of ${JSON.stringify(this._index)}`
250
+ });
251
+ }
252
+ this._data[idx] = value;
253
+ return this;
254
+ }
255
+
256
+ /**
257
+ * Applies a transformation function to each element.
258
+ * Returns a new Series with transformed values.
259
+ *
260
+ * @param {Function} fn - Transformation function that takes (value, index) and returns transformed value
261
+ * @returns {Series} A new Series with transformed values
262
+ *
263
+ * @throws {ValidationError} If fn is not a function
264
+ * @throws {OperationError} If transformation function throws an error
265
+ *
266
+ * @example
267
+ * const series = new Series([1, 2, 3]);
268
+ * const doubled = series.map(x => x * 2);
269
+ * console.log(doubled); // Series([2, 4, 6])
270
+ *
271
+ * @example
272
+ * const series = new Series([1, 2, 3], { index: ['a', 'b', 'c'] });
273
+ * const squared = series.map((x, i) => x * x);
274
+ * console.log(squared); // Series([1, 4, 9])
275
+ */
276
+ map(fn) {
277
+ validateFunction(fn, 'fn');
278
+
279
+ try {
280
+ const transformed = this._data.map((value, idx) => {
281
+ try {
282
+ return fn(value, idx);
283
+ } catch (error) {
284
+ throw new OperationError(
285
+ `Transformation function failed at index ${idx}`,
286
+ {
287
+ operation: 'map',
288
+ value,
289
+ expected: 'function to succeed',
290
+ actual: error.message
291
+ }
292
+ );
293
+ }
294
+ });
295
+
296
+ return new Series(transformed, {
297
+ index: this._index,
298
+ name: this._name
299
+ });
300
+ } catch (error) {
301
+ if (error instanceof OperationError) {
302
+ throw error;
303
+ }
304
+ throw new OperationError('Map operation failed', {
305
+ operation: 'map',
306
+ expected: 'valid transformation function'
307
+ });
308
+ }
309
+ }
310
+
311
+ /**
312
+ * Applies a transformation function to each element (alias for map).
313
+ * Returns a new Series with transformed values.
314
+ *
315
+ * @param {Function} fn - Transformation function that takes (value, index) and returns transformed value
316
+ * @returns {Series} A new Series with transformed values
317
+ *
318
+ * @throws {ValidationError} If fn is not a function
319
+ * @throws {OperationError} If transformation function throws an error
320
+ *
321
+ * @example
322
+ * const series = new Series([1, 2, 3]);
323
+ * const result = series.apply(x => x + 10);
324
+ * console.log(result); // Series([11, 12, 13])
325
+ */
326
+ apply(fn) {
327
+ return this.map(fn);
328
+ }
329
+
330
+ /**
331
+ * Replaces values matching a condition with a new value.
332
+ * Returns a new Series with replaced values.
333
+ *
334
+ * @param {*} oldValue - The value to replace (or a function that returns true for values to replace)
335
+ * @param {*} newValue - The value to replace with
336
+ * @returns {Series} A new Series with replaced values
337
+ *
338
+ * @example
339
+ * const series = new Series([1, 2, 3, 2, 1]);
340
+ * const replaced = series.replace(2, 99);
341
+ * console.log(replaced); // Series([1, 99, 3, 99, 1])
342
+ *
343
+ * @example
344
+ * const series = new Series([1, 2, 3, 4, 5]);
345
+ * const replaced = series.replace(x => x > 3, 0);
346
+ * console.log(replaced); // Series([1, 2, 3, 0, 0])
347
+ */
348
+ replace(oldValue, newValue) {
349
+ const isFunction = typeof oldValue === 'function';
350
+
351
+ const transformed = this._data.map(value => {
352
+ const shouldReplace = isFunction ? oldValue(value) : value === oldValue;
353
+ return shouldReplace ? newValue : value;
354
+ });
355
+
356
+ return new Series(transformed, {
357
+ index: this._index,
358
+ name: this._name
359
+ });
360
+ }
361
+
362
+ /**
363
+ * Computes the sum of all numeric values in the Series.
364
+ * Non-numeric values and null/undefined are excluded.
365
+ *
366
+ * @returns {number} The sum of all numeric values
367
+ *
368
+ * @throws {TypeErrorClass} If Series contains no numeric values
369
+ *
370
+ * @example
371
+ * const series = new Series([1, 2, 3, 4, 5]);
372
+ * console.log(series.sum()); // 15
373
+ *
374
+ * @example
375
+ * const series = new Series([1, null, 3, undefined, 5]);
376
+ * console.log(series.sum()); // 9 (null and undefined excluded)
377
+ */
378
+ sum() {
379
+ const numericValues = this._data
380
+ .map(v => toNumeric(v))
381
+ .filter(v => v !== null);
382
+
383
+ if (numericValues.length === 0) {
384
+ throw new TypeErrorClass('Cannot compute sum of non-numeric Series', {
385
+ operation: 'sum',
386
+ expected: 'numeric values',
387
+ actual: this._type
388
+ });
389
+ }
390
+
391
+ return numericValues.reduce((acc, val) => acc + val, 0);
392
+ }
393
+
394
+ /**
395
+ * Computes the count of non-null values in the Series.
396
+ *
397
+ * @returns {number} The count of non-null values
398
+ *
399
+ * @example
400
+ * const series = new Series([1, 2, null, 4, undefined, 6]);
401
+ * console.log(series.count()); // 4
402
+ */
403
+ count() {
404
+ return this._data.filter(v => !isNull(v)).length;
405
+ }
406
+
407
+ /**
408
+ * Computes the mean (average) of all numeric values in the Series.
409
+ * Non-numeric values and null/undefined are excluded.
410
+ *
411
+ * @returns {number} The mean of all numeric values
412
+ *
413
+ * @throws {TypeErrorClass} If Series contains no numeric values
414
+ *
415
+ * @example
416
+ * const series = new Series([1, 2, 3, 4, 5]);
417
+ * console.log(series.mean()); // 3
418
+ *
419
+ * @example
420
+ * const series = new Series([10, 20, null, 30]);
421
+ * console.log(series.mean()); // 20 (null excluded)
422
+ */
423
+ mean() {
424
+ const numericValues = this._data
425
+ .map(v => toNumeric(v))
426
+ .filter(v => v !== null);
427
+
428
+ if (numericValues.length === 0) {
429
+ throw new TypeErrorClass('Cannot compute mean of non-numeric Series', {
430
+ operation: 'mean',
431
+ expected: 'numeric values',
432
+ actual: this._type
433
+ });
434
+ }
435
+
436
+ return numericValues.reduce((acc, val) => acc + val, 0) / numericValues.length;
437
+ }
438
+
439
+ /**
440
+ * Computes the median of all numeric values in the Series.
441
+ * Non-numeric values and null/undefined are excluded.
442
+ *
443
+ * @returns {number} The median of all numeric values
444
+ *
445
+ * @throws {TypeErrorClass} If Series contains no numeric values
446
+ *
447
+ * @example
448
+ * const series = new Series([1, 2, 3, 4, 5]);
449
+ * console.log(series.median()); // 3
450
+ *
451
+ * @example
452
+ * const series = new Series([1, 2, 3, 4]);
453
+ * console.log(series.median()); // 2.5
454
+ */
455
+ median() {
456
+ const numericValues = this._data
457
+ .map(v => toNumeric(v))
458
+ .filter(v => v !== null)
459
+ .sort((a, b) => a - b);
460
+
461
+ if (numericValues.length === 0) {
462
+ throw new TypeErrorClass('Cannot compute median of non-numeric Series', {
463
+ operation: 'median',
464
+ expected: 'numeric values',
465
+ actual: this._type
466
+ });
467
+ }
468
+
469
+ const mid = Math.floor(numericValues.length / 2);
470
+ if (numericValues.length % 2 === 0) {
471
+ return (numericValues[mid - 1] + numericValues[mid]) / 2;
472
+ }
473
+ return numericValues[mid];
474
+ }
475
+
476
+ /**
477
+ * Computes the mode (most frequent value) of the Series.
478
+ * Returns the first mode if multiple modes exist.
479
+ *
480
+ * @returns {*} The most frequently occurring value
481
+ *
482
+ * @throws {DataFrameError} If Series is empty
483
+ *
484
+ * @example
485
+ * const series = new Series([1, 2, 2, 3, 3, 3]);
486
+ * console.log(series.mode()); // 3
487
+ *
488
+ * @example
489
+ * const series = new Series(['a', 'b', 'a', 'c', 'a']);
490
+ * console.log(series.mode()); // 'a'
491
+ */
492
+ mode() {
493
+ if (this._data.length === 0) {
494
+ throw new DataFrameError('Cannot compute mode of empty Series', {
495
+ operation: 'mode',
496
+ expected: 'non-empty Series'
497
+ });
498
+ }
499
+
500
+ const frequency = {};
501
+ let maxCount = 0;
502
+ let modeValue = null;
503
+
504
+ for (const value of this._data) {
505
+ if (!isNull(value)) {
506
+ const key = String(value);
507
+ frequency[key] = (frequency[key] || 0) + 1;
508
+ if (frequency[key] > maxCount) {
509
+ maxCount = frequency[key];
510
+ modeValue = value;
511
+ }
512
+ }
513
+ }
514
+
515
+ if (modeValue === null) {
516
+ throw new DataFrameError('Cannot compute mode of Series with only null values', {
517
+ operation: 'mode',
518
+ expected: 'at least one non-null value'
519
+ });
520
+ }
521
+
522
+ return modeValue;
523
+ }
524
+
525
+ /**
526
+ * Computes the minimum value in the Series.
527
+ * For numeric Series, returns the smallest number.
528
+ * For string Series, returns the lexicographically smallest value.
529
+ *
530
+ * @returns {*} The minimum value
531
+ *
532
+ * @throws {DataFrameError} If Series is empty or contains only null values
533
+ *
534
+ * @example
535
+ * const series = new Series([5, 2, 8, 1, 9]);
536
+ * console.log(series.min()); // 1
537
+ *
538
+ * @example
539
+ * const series = new Series(['zebra', 'apple', 'mango']);
540
+ * console.log(series.min()); // 'apple'
541
+ */
542
+ min() {
543
+ const nonNullValues = this._data.filter(v => !isNull(v));
544
+
545
+ if (nonNullValues.length === 0) {
546
+ throw new DataFrameError('Cannot compute min of empty or all-null Series', {
547
+ operation: 'min',
548
+ expected: 'at least one non-null value'
549
+ });
550
+ }
551
+
552
+ if (this._type === 'numeric') {
553
+ const numericValues = nonNullValues.map(v => toNumeric(v)).filter(v => v !== null);
554
+ if (numericValues.length === 0) {
555
+ throw new TypeErrorClass('Cannot compute min of non-numeric Series', {
556
+ operation: 'min',
557
+ expected: 'numeric values',
558
+ actual: this._type
559
+ });
560
+ }
561
+ return Math.min(...numericValues);
562
+ }
563
+
564
+ return nonNullValues.reduce((min, val) => val < min ? val : min);
565
+ }
566
+
567
+ /**
568
+ * Computes the maximum value in the Series.
569
+ * For numeric Series, returns the largest number.
570
+ * For string Series, returns the lexicographically largest value.
571
+ *
572
+ * @returns {*} The maximum value
573
+ *
574
+ * @throws {DataFrameError} If Series is empty or contains only null values
575
+ *
576
+ * @example
577
+ * const series = new Series([5, 2, 8, 1, 9]);
578
+ * console.log(series.max()); // 9
579
+ *
580
+ * @example
581
+ * const series = new Series(['zebra', 'apple', 'mango']);
582
+ * console.log(series.max()); // 'zebra'
583
+ */
584
+ max() {
585
+ const nonNullValues = this._data.filter(v => !isNull(v));
586
+
587
+ if (nonNullValues.length === 0) {
588
+ throw new DataFrameError('Cannot compute max of empty or all-null Series', {
589
+ operation: 'max',
590
+ expected: 'at least one non-null value'
591
+ });
592
+ }
593
+
594
+ if (this._type === 'numeric') {
595
+ const numericValues = nonNullValues.map(v => toNumeric(v)).filter(v => v !== null);
596
+ if (numericValues.length === 0) {
597
+ throw new TypeErrorClass('Cannot compute max of non-numeric Series', {
598
+ operation: 'max',
599
+ expected: 'numeric values',
600
+ actual: this._type
601
+ });
602
+ }
603
+ return Math.max(...numericValues);
604
+ }
605
+
606
+ return nonNullValues.reduce((max, val) => val > max ? val : max);
607
+ }
608
+
609
+ /**
610
+ * Computes the standard deviation of all numeric values in the Series.
611
+ * Non-numeric values and null/undefined are excluded.
612
+ * Uses sample standard deviation (divides by n-1).
613
+ *
614
+ * @returns {number} The standard deviation
615
+ *
616
+ * @throws {TypeErrorClass} If Series contains fewer than 2 numeric values
617
+ *
618
+ * @example
619
+ * const series = new Series([1, 2, 3, 4, 5]);
620
+ * console.log(series.std()); // ~1.58 (sample std dev)
621
+ */
622
+ std() {
623
+ const numericValues = this._data
624
+ .map(v => toNumeric(v))
625
+ .filter(v => v !== null);
626
+
627
+ if (numericValues.length < 2) {
628
+ throw new TypeErrorClass('Cannot compute std of Series with fewer than 2 numeric values', {
629
+ operation: 'std',
630
+ expected: 'at least 2 numeric values',
631
+ actual: `${numericValues.length} numeric values`
632
+ });
633
+ }
634
+
635
+ const mean = numericValues.reduce((acc, val) => acc + val, 0) / numericValues.length;
636
+ const variance = numericValues.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0) / (numericValues.length - 1);
637
+ return Math.sqrt(variance);
638
+ }
639
+
640
+ /**
641
+ * Computes the variance of all numeric values in the Series.
642
+ * Non-numeric values and null/undefined are excluded.
643
+ * Uses sample variance (divides by n-1).
644
+ *
645
+ * @returns {number} The variance
646
+ *
647
+ * @throws {TypeErrorClass} If Series contains fewer than 2 numeric values
648
+ *
649
+ * @example
650
+ * const series = new Series([1, 2, 3, 4, 5]);
651
+ * console.log(series.var()); // ~2.5 (sample variance)
652
+ */
653
+ var() {
654
+ const numericValues = this._data
655
+ .map(v => toNumeric(v))
656
+ .filter(v => v !== null);
657
+
658
+ if (numericValues.length < 2) {
659
+ throw new TypeErrorClass('Cannot compute var of Series with fewer than 2 numeric values', {
660
+ operation: 'var',
661
+ expected: 'at least 2 numeric values',
662
+ actual: `${numericValues.length} numeric values`
663
+ });
664
+ }
665
+
666
+ const mean = numericValues.reduce((acc, val) => acc + val, 0) / numericValues.length;
667
+ const variance = numericValues.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0) / (numericValues.length - 1);
668
+ return variance;
669
+ }
10
670
  }
11
671
 
12
- function Series(data) {
13
- let series = new NodeSeries(data)
14
- return series
672
+ /**
673
+ * Factory function to create a new Series instance.
674
+ * Provides a convenient way to create Series without using the new keyword.
675
+ *
676
+ * @param {Array} data - The data for the Series
677
+ * @param {Object} [options={}] - Configuration options
678
+ * @returns {Series} A new Series instance
679
+ *
680
+ * @example
681
+ * const series = Series([1, 2, 3]);
682
+ *
683
+ * @example
684
+ * const series = Series([10, 20, 30], {
685
+ * index: ['a', 'b', 'c'],
686
+ * name: 'values'
687
+ * });
688
+ */
689
+ function createSeries(data, options = {}) {
690
+ return new Series(data, options);
15
691
  }
16
692
 
17
- // Exporting Series so that it could be used by modules (i.e. they could import and use)
18
- module.exports = Series
693
+ module.exports = Series;
694
+ module.exports.createSeries = createSeries;