@nshiab/simple-data-analysis-core 2.0.3 → 2.0.5
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/esm/class/SimpleTable.d.ts +202 -8
- package/esm/class/SimpleTable.d.ts.map +1 -1
- package/esm/class/SimpleTable.js +211 -9
- package/esm/methods/addCharacterCount.d.ts +3 -0
- package/esm/methods/addCharacterCount.d.ts.map +1 -0
- package/esm/methods/addCharacterCount.js +15 -0
- package/esm/methods/addGeoNoise.d.ts +7 -0
- package/esm/methods/addGeoNoise.d.ts.map +1 -0
- package/esm/methods/addGeoNoise.js +135 -0
- package/esm/methods/addNoise.d.ts +5 -0
- package/esm/methods/addNoise.d.ts.map +1 -0
- package/esm/methods/addNoise.js +53 -0
- package/esm/methods/addWordCount.d.ts +3 -0
- package/esm/methods/addWordCount.d.ts.map +1 -0
- package/esm/methods/addWordCount.js +15 -0
- package/esm/methods/columnProportions.d.ts +1 -0
- package/esm/methods/columnProportions.d.ts.map +1 -1
- package/esm/methods/columnProportions.js +7 -2
- package/esm/methods/loadArray.d.ts +4 -2
- package/esm/methods/loadArray.d.ts.map +1 -1
- package/esm/methods/loadArray.js +113 -4
- package/esm/methods/loadYahooFinanceData.d.ts +10 -0
- package/esm/methods/loadYahooFinanceData.d.ts.map +1 -0
- package/esm/methods/loadYahooFinanceData.js +133 -0
- package/esm/methods/rowProportions.d.ts +1 -0
- package/esm/methods/rowProportions.d.ts.map +1 -1
- package/esm/methods/rowProportions.js +5 -1
- package/llm.md +291 -14
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/script/class/SimpleTable.d.ts +202 -8
- package/script/class/SimpleTable.d.ts.map +1 -1
- package/script/class/SimpleTable.js +211 -9
- package/script/methods/addCharacterCount.d.ts +3 -0
- package/script/methods/addCharacterCount.d.ts.map +1 -0
- package/script/methods/addCharacterCount.js +21 -0
- package/script/methods/addGeoNoise.d.ts +7 -0
- package/script/methods/addGeoNoise.d.ts.map +1 -0
- package/script/methods/addGeoNoise.js +141 -0
- package/script/methods/addNoise.d.ts +5 -0
- package/script/methods/addNoise.d.ts.map +1 -0
- package/script/methods/addNoise.js +59 -0
- package/script/methods/addWordCount.d.ts +3 -0
- package/script/methods/addWordCount.d.ts.map +1 -0
- package/script/methods/addWordCount.js +21 -0
- package/script/methods/columnProportions.d.ts +1 -0
- package/script/methods/columnProportions.d.ts.map +1 -1
- package/script/methods/columnProportions.js +7 -2
- package/script/methods/loadArray.d.ts +4 -2
- package/script/methods/loadArray.d.ts.map +1 -1
- package/script/methods/loadArray.js +112 -3
- package/script/methods/loadYahooFinanceData.d.ts +10 -0
- package/script/methods/loadYahooFinanceData.d.ts.map +1 -0
- package/script/methods/loadYahooFinanceData.js +139 -0
- package/script/methods/rowProportions.d.ts +1 -0
- package/script/methods/rowProportions.d.ts.map +1 -1
- package/script/methods/rowProportions.js +5 -1
package/esm/class/SimpleTable.js
CHANGED
|
@@ -17,6 +17,8 @@ import removeMissing from "../methods/removeMissing.js";
|
|
|
17
17
|
import getColumns from "../methods/getColumns.js";
|
|
18
18
|
import getRowCount from "../methods/getRowCount.js";
|
|
19
19
|
import getCharacterCount from "../methods/getCharacterCount.js";
|
|
20
|
+
import addCharacterCount from "../methods/addCharacterCount.js";
|
|
21
|
+
import addWordCount from "../methods/addWordCount.js";
|
|
20
22
|
import getTypes from "../methods/getTypes.js";
|
|
21
23
|
import getHash from "../methods/getHash.js";
|
|
22
24
|
import getValues from "../methods/getValues.js";
|
|
@@ -118,6 +120,7 @@ import length from "../methods/length.js";
|
|
|
118
120
|
import area from "../methods/area.js";
|
|
119
121
|
import reproject from "../methods/reproject.js";
|
|
120
122
|
import reducePrecision from "../methods/reducePrecision.js";
|
|
123
|
+
import addGeoNoise from "../methods/addGeoNoise.js";
|
|
121
124
|
import flipCoordinates from "../methods/flipCoordinates.js";
|
|
122
125
|
import addGeoType from "../methods/addGeoType.js";
|
|
123
126
|
import addGeoClosedStatus from "../methods/addGeoClosedStatus.js";
|
|
@@ -132,6 +135,7 @@ import getSchema from "../methods/getSchema.js";
|
|
|
132
135
|
import outliersIQR from "../methods/outliersIQR.js";
|
|
133
136
|
import bins from "../methods/bins.js";
|
|
134
137
|
import round from "../methods/round.js";
|
|
138
|
+
import addNoise from "../methods/addNoise.js";
|
|
135
139
|
import rowToText from "../methods/rowToText.js";
|
|
136
140
|
import replaceNulls from "../methods/replaceNulls.js";
|
|
137
141
|
import pad from "../methods/pad.js";
|
|
@@ -153,6 +157,7 @@ import insertRows from "../methods/insertRows.js";
|
|
|
153
157
|
import loadGeoData from "../methods/loadGeoData.js";
|
|
154
158
|
import loadOpenStreetMap from "../methods/loadOpenStreetMap.js";
|
|
155
159
|
import loadStatCanData from "../methods/loadStatCanData.js";
|
|
160
|
+
import loadYahooFinanceData from "../methods/loadYahooFinanceData.js";
|
|
156
161
|
import setTypes from "../methods/setTypes.js";
|
|
157
162
|
import flushAllTables from "../helpers/flushAllTables.js";
|
|
158
163
|
import queueOp from "../helpers/queueOp.js";
|
|
@@ -336,7 +341,7 @@ class SimpleTable extends Simple {
|
|
|
336
341
|
return this;
|
|
337
342
|
}
|
|
338
343
|
/**
|
|
339
|
-
* Loads an array of JavaScript objects into the table. This method queues the load; it runs when an async observer method (like `getData()` or `log()`) is awaited, or when `run()` is called.
|
|
344
|
+
* Loads an array of JavaScript objects into the table. Types can also be specified for individual columns instead of inferred from their values. This method queues the load; it runs when an async observer method (like `getData()` or `log()`) is awaited, or when `run()` is called.
|
|
340
345
|
*
|
|
341
346
|
* JavaScript `Date` values are inferred as DuckDB `TIMESTAMP` values. Their
|
|
342
347
|
* instant is preserved, but JavaScript `Date` does not retain the timezone or
|
|
@@ -344,6 +349,8 @@ class SimpleTable extends Simple {
|
|
|
344
349
|
* use `convert()` to parse them as temporal values.
|
|
345
350
|
*
|
|
346
351
|
* @param rows - An array of objects, where each object represents a row and its properties represent columns.
|
|
352
|
+
* @param options - Options for loading the array, captured when called.
|
|
353
|
+
* @param options.columnTypes - Types for specific columns; omitted columns are inferred. Values must be compatible with the selected type without losing information.
|
|
347
354
|
* @returns The table, so methods can be chained.
|
|
348
355
|
* @category Importing Data
|
|
349
356
|
*
|
|
@@ -359,6 +366,15 @@ class SimpleTable extends Simple {
|
|
|
359
366
|
*
|
|
360
367
|
* @example
|
|
361
368
|
* ```ts
|
|
369
|
+
* // Specify a numeric type for an all-null column
|
|
370
|
+
* await table.loadArray(
|
|
371
|
+
* [{ name: "A", value: null }, { name: "B", value: null }],
|
|
372
|
+
* { columnTypes: { value: "DOUBLE" } },
|
|
373
|
+
* ).log();
|
|
374
|
+
* ```
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* ```ts
|
|
362
378
|
* // The offset determines the instant; the loaded TIMESTAMP is returned as
|
|
363
379
|
* // the equivalent UTC JavaScript Date.
|
|
364
380
|
* await table.loadArray([{
|
|
@@ -366,8 +382,8 @@ class SimpleTable extends Simple {
|
|
|
366
382
|
* }]).log();
|
|
367
383
|
* ```
|
|
368
384
|
*/
|
|
369
|
-
loadArray(rows) {
|
|
370
|
-
loadArray(this, rows);
|
|
385
|
+
loadArray(rows, options = {}) {
|
|
386
|
+
loadArray(this, rows, options);
|
|
371
387
|
return this;
|
|
372
388
|
}
|
|
373
389
|
/**
|
|
@@ -498,6 +514,70 @@ class SimpleTable extends Simple {
|
|
|
498
514
|
loadStatCanData(this, pid, options);
|
|
499
515
|
return this;
|
|
500
516
|
}
|
|
517
|
+
/**
|
|
518
|
+
* Downloads historical market data from Yahoo Finance and loads it into this
|
|
519
|
+
* table as `datetime`, `open`, `high`, `low`, `close`, `adjustedClose`, and
|
|
520
|
+
* `volume` columns. `datetime` contains Yahoo's timestamps as JavaScript
|
|
521
|
+
* `Date` values. Unavailable values are preserved as `null`.
|
|
522
|
+
*
|
|
523
|
+
* All range boundaries are evaluated in UTC. Use ISO date-time strings ending
|
|
524
|
+
* in `Z`. Yahoo trading sessions may occur on a different UTC date from their
|
|
525
|
+
* exchange-local date, so choose the range appropriate for the market and
|
|
526
|
+
* interval. Avoid numeric constructors such as `new Date(2025, 2, 15)`, which
|
|
527
|
+
* use the runtime's local timezone.
|
|
528
|
+
*
|
|
529
|
+
* This method uses an undocumented Yahoo Finance endpoint and is not
|
|
530
|
+
* affiliated with or endorsed by Yahoo. It is provided for educational,
|
|
531
|
+
* research, and journalistic purposes. Before using it, review Yahoo's terms
|
|
532
|
+
* and any applicable data-provider restrictions.
|
|
533
|
+
*
|
|
534
|
+
* The method queues the download and load; they run when an async observer
|
|
535
|
+
* method (like `getData()` or `log()`) is awaited, or when `run()` is called.
|
|
536
|
+
*
|
|
537
|
+
* @param symbol - The stock or index symbol, such as `"AAPL"` or `"^GSPTSE"`.
|
|
538
|
+
* @param startDate - The inclusive UTC start of the requested range.
|
|
539
|
+
* @param endDate - The inclusive UTC end of the requested range. The UTC day,
|
|
540
|
+
* hour, or minute containing this instant is included, according to
|
|
541
|
+
* `interval`.
|
|
542
|
+
* @param interval - The interval between observations: daily, hourly, or every
|
|
543
|
+
* minute.
|
|
544
|
+
* @returns The table, so methods can be chained.
|
|
545
|
+
* @throws {RangeError} If either date is invalid or `endDate` is before `startDate`.
|
|
546
|
+
* @throws {Error} If Yahoo rejects the request or returns no data.
|
|
547
|
+
* @see https://legal.yahoo.com/us/en/yahoo/terms/otos/index.html
|
|
548
|
+
* @see https://help.yahoo.com/kb/finance/SLN2310.html
|
|
549
|
+
* @category Importing Data
|
|
550
|
+
*
|
|
551
|
+
* @example
|
|
552
|
+
* ```ts
|
|
553
|
+
* // Request daily observations using explicit UTC boundaries.
|
|
554
|
+
* await table
|
|
555
|
+
* .loadYahooFinanceData(
|
|
556
|
+
* "^GSPTSE",
|
|
557
|
+
* new Date("2025-03-01T00:00:00Z"),
|
|
558
|
+
* new Date("2025-03-15T00:00:00Z"),
|
|
559
|
+
* "1d",
|
|
560
|
+
* )
|
|
561
|
+
* .log();
|
|
562
|
+
* ```
|
|
563
|
+
*
|
|
564
|
+
* @example
|
|
565
|
+
* ```ts
|
|
566
|
+
* // Include the UTC hours from 13:00 through 16:00.
|
|
567
|
+
* await table
|
|
568
|
+
* .loadYahooFinanceData(
|
|
569
|
+
* "AAPL",
|
|
570
|
+
* new Date("2025-03-14T13:00:00Z"),
|
|
571
|
+
* new Date("2025-03-14T16:00:00Z"),
|
|
572
|
+
* "1h",
|
|
573
|
+
* )
|
|
574
|
+
* .log();
|
|
575
|
+
* ```
|
|
576
|
+
*/
|
|
577
|
+
loadYahooFinanceData(symbol, startDate, endDate, interval) {
|
|
578
|
+
loadYahooFinanceData(this, symbol, startDate, endDate, interval);
|
|
579
|
+
return this;
|
|
580
|
+
}
|
|
501
581
|
/**
|
|
502
582
|
* Loads geospatial data from an external file or URL into the table.
|
|
503
583
|
*
|
|
@@ -2690,6 +2770,37 @@ class SimpleTable extends Simple {
|
|
|
2690
2770
|
round(this, columns, options);
|
|
2691
2771
|
return this;
|
|
2692
2772
|
}
|
|
2773
|
+
/**
|
|
2774
|
+
* Adds independently generated uniform random noise to numeric values in one or more columns.
|
|
2775
|
+
* Each changed value receives an offset between `-max` and `max`. Selected integer and decimal columns become `DOUBLE` columns so fractional noise is retained. This method adds random jitter; it does not provide anonymization or differential privacy guarantees.
|
|
2776
|
+
*
|
|
2777
|
+
* This method queues the operation; it runs when an async observer method (like `getData()` or `log()`) is awaited, or when `run()` is called.
|
|
2778
|
+
*
|
|
2779
|
+
* @param columns - The numeric column name or array of numeric column names to which noise will be added. When multiple columns are provided, each value receives an independent random offset.
|
|
2780
|
+
* @param max - The maximum absolute offset, expressed in each column's units. Must be a finite number greater than or equal to `0`.
|
|
2781
|
+
* @param options - An optional object with configuration options:
|
|
2782
|
+
* @param options.onlyDuplicates - If `true`, adds noise only to values that occur more than once in their column. Each selected column is evaluated independently, and every occurrence of a duplicated value is changed. Defaults to `false`.
|
|
2783
|
+
* @returns The table, so methods can be chained.
|
|
2784
|
+
* @category Updating Data
|
|
2785
|
+
*
|
|
2786
|
+
* @example
|
|
2787
|
+
* ```ts
|
|
2788
|
+
* // Add an offset between -0.01 and 0.01 to every value
|
|
2789
|
+
* await table.addNoise("measurement", 0.01).log();
|
|
2790
|
+
* ```
|
|
2791
|
+
*
|
|
2792
|
+
* @example
|
|
2793
|
+
* ```ts
|
|
2794
|
+
* // Add noise independently to duplicated x values and duplicated y values
|
|
2795
|
+
* await table.addNoise(["x", "y"], 0.1, {
|
|
2796
|
+
* onlyDuplicates: true,
|
|
2797
|
+
* }).log();
|
|
2798
|
+
* ```
|
|
2799
|
+
*/
|
|
2800
|
+
addNoise(columns, max, options = {}) {
|
|
2801
|
+
addNoise(this, columns, max, options);
|
|
2802
|
+
return this;
|
|
2803
|
+
}
|
|
2693
2804
|
/**
|
|
2694
2805
|
* Updates values in a specified column using a SQL expression.
|
|
2695
2806
|
*
|
|
@@ -2873,11 +2984,18 @@ class SimpleTable extends Simple {
|
|
|
2873
2984
|
* | 2022 | 354 | 278 | 56 | 0.51 | 0.4 | 0.08 |
|
|
2874
2985
|
* | 2023 | 856 | 321 | 221 | 0.61 | 0.23 | 0.16 |
|
|
2875
2986
|
*
|
|
2987
|
+
* @example
|
|
2988
|
+
* ```ts
|
|
2989
|
+
* // Compute percentages that sum to 100 on each row before rounding
|
|
2990
|
+
* await table.rowProportions(["Men", "Women", "NonBinary"], { base: 100, decimals: 1 }).log();
|
|
2991
|
+
* ```
|
|
2992
|
+
*
|
|
2876
2993
|
* This method queues the operation; it runs when an async observer method (like `getData()` or `log()`) is awaited, or when `run()` is called.
|
|
2877
2994
|
*
|
|
2878
2995
|
* @param columns - An array of column names for which proportions will be computed on each row.
|
|
2879
2996
|
* @param options - An optional object with configuration options:
|
|
2880
2997
|
* @param options.suffix - A string suffix to append to the names of the new columns storing the computed proportions. Defaults to `"Perc"`.
|
|
2998
|
+
* @param options.base - A finite positive value that the proportions in each row sum to before rounding. Defaults to `1`.
|
|
2881
2999
|
* @param options.decimals - The number of decimal places to round the computed proportions. Defaults to `undefined` (no rounding).
|
|
2882
3000
|
* @returns The table, so methods can be chained.
|
|
2883
3001
|
* @category Analyzing Data
|
|
@@ -2942,6 +3060,7 @@ class SimpleTable extends Simple {
|
|
|
2942
3060
|
* @param newColumn - The name of the new column where the proportions will be stored.
|
|
2943
3061
|
* @param options - An optional object with configuration options:
|
|
2944
3062
|
* @param options.by - The column name or an array of column names to partition by. Proportions are calculated independently within each group.
|
|
3063
|
+
* @param options.base - A finite positive value that the proportions in the column or each group sum to before rounding. Defaults to `1`.
|
|
2945
3064
|
* @param options.decimals - The number of decimal places to round the computed proportions. Defaults to `undefined` (no rounding).
|
|
2946
3065
|
* @returns The table, so methods can be chained.
|
|
2947
3066
|
* @category Analyzing Data
|
|
@@ -2960,6 +3079,12 @@ class SimpleTable extends Simple {
|
|
|
2960
3079
|
*
|
|
2961
3080
|
* @example
|
|
2962
3081
|
* ```ts
|
|
3082
|
+
* // Compute percentages that sum to 100 before rounding
|
|
3083
|
+
* await table.columnProportions("sales", "sales_percentage", { base: 100, decimals: 1 }).log();
|
|
3084
|
+
* ```
|
|
3085
|
+
*
|
|
3086
|
+
* @example
|
|
3087
|
+
* ```ts
|
|
2963
3088
|
* // Compute proportions for 'sales' by 'region' and 'product_type'
|
|
2964
3089
|
* await table.columnProportions("sales", "sales_proportion", { by: ["region", "product_type"] }).log();
|
|
2965
3090
|
* ```
|
|
@@ -3470,17 +3595,15 @@ class SimpleTable extends Simple {
|
|
|
3470
3595
|
*
|
|
3471
3596
|
* @example
|
|
3472
3597
|
* ```ts
|
|
3473
|
-
* //
|
|
3474
|
-
* const segmenter = new Intl.Segmenter(undefined, { granularity: "word" });
|
|
3598
|
+
* // Extract hostnames with JavaScript's URL parser.
|
|
3475
3599
|
* const table = await sdb
|
|
3476
3600
|
* .newTable()
|
|
3477
|
-
* .loadData("
|
|
3601
|
+
* .loadData("websites.csv")
|
|
3478
3602
|
* .updateWithJS((rows) => {
|
|
3479
3603
|
* return rows.map((row) => ({
|
|
3480
3604
|
* ...row,
|
|
3481
|
-
*
|
|
3482
|
-
* ?
|
|
3483
|
-
* .length
|
|
3605
|
+
* hostname: typeof row.url === "string"
|
|
3606
|
+
* ? new URL(row.url).hostname
|
|
3484
3607
|
* : null,
|
|
3485
3608
|
* }));
|
|
3486
3609
|
* })
|
|
@@ -3629,8 +3752,52 @@ class SimpleTable extends Simple {
|
|
|
3629
3752
|
const result = (await getColumns(this)).length;
|
|
3630
3753
|
return result;
|
|
3631
3754
|
}
|
|
3755
|
+
/**
|
|
3756
|
+
* Adds a new column containing the number of characters in each string in the specified column.
|
|
3757
|
+
* Counts are based on Unicode code points, not grapheme clusters. A user-perceived character composed of multiple code points, such as some emoji or decomposed accented letters, counts as multiple characters.
|
|
3758
|
+
* `NULL` input values produce `NULL` counts.
|
|
3759
|
+
*
|
|
3760
|
+
* This method queues the operation; it runs when an async observer method (like `getData()` or `log()`) is awaited, or when `run()` is called.
|
|
3761
|
+
*
|
|
3762
|
+
* @param column - The name of the column containing the strings to count.
|
|
3763
|
+
* @param newColumn - The name of the new column where the character counts will be stored.
|
|
3764
|
+
* @returns The table, so methods can be chained.
|
|
3765
|
+
* @category Text Processing
|
|
3766
|
+
*
|
|
3767
|
+
* @example
|
|
3768
|
+
* ```ts
|
|
3769
|
+
* // Add a character count for each value in the 'name' column
|
|
3770
|
+
* await table.addCharacterCount("name", "nameCharacterCount").log();
|
|
3771
|
+
* ```
|
|
3772
|
+
*/
|
|
3773
|
+
addCharacterCount(column, newColumn) {
|
|
3774
|
+
addCharacterCount(this, column, newColumn);
|
|
3775
|
+
return this;
|
|
3776
|
+
}
|
|
3777
|
+
/**
|
|
3778
|
+
* Adds a new column containing the word count for each string in the specified column.
|
|
3779
|
+
* A word is any contiguous sequence of non-whitespace characters. Spaces, tabs, and line breaks separate words. Punctuation is not removed, so a standalone punctuation sequence counts as a word. Text without whitespace counts as one word, regardless of language. Empty or whitespace-only strings produce `0`, and `NULL` input values produce `NULL` counts.
|
|
3780
|
+
*
|
|
3781
|
+
* This method queues the operation; it runs when an async observer method (like `getData()` or `log()`) is awaited, or when `run()` is called.
|
|
3782
|
+
*
|
|
3783
|
+
* @param column - The name of the column containing the strings to count.
|
|
3784
|
+
* @param newColumn - The name of the new column where the word counts will be stored.
|
|
3785
|
+
* @returns The table, so methods can be chained.
|
|
3786
|
+
* @category Text Processing
|
|
3787
|
+
*
|
|
3788
|
+
* @example
|
|
3789
|
+
* ```ts
|
|
3790
|
+
* // Add a word count for each value in the 'article' column
|
|
3791
|
+
* await table.addWordCount("article", "wordCount").log();
|
|
3792
|
+
* ```
|
|
3793
|
+
*/
|
|
3794
|
+
addWordCount(column, newColumn) {
|
|
3795
|
+
addWordCount(this, column, newColumn);
|
|
3796
|
+
return this;
|
|
3797
|
+
}
|
|
3632
3798
|
/**
|
|
3633
3799
|
* Returns the total number of characters in a column storing strings.
|
|
3800
|
+
* Counts are based on Unicode code points, not grapheme clusters. A user-perceived character composed of multiple code points, such as some emoji or decomposed accented letters, counts as multiple characters.
|
|
3634
3801
|
*
|
|
3635
3802
|
* @param column - The name of the string column to count characters from.
|
|
3636
3803
|
* @returns A promise that resolves to the total number of characters across all rows in the specified column.
|
|
@@ -4460,6 +4627,41 @@ class SimpleTable extends Simple {
|
|
|
4460
4627
|
flipCoordinates(this, column);
|
|
4461
4628
|
return this;
|
|
4462
4629
|
}
|
|
4630
|
+
/**
|
|
4631
|
+
* Moves EPSG:4326 point geometries to random locations within a maximum great-circle distance of their original positions.
|
|
4632
|
+
*
|
|
4633
|
+
* Points are sampled uniformly within the requested distance using the spherical direct geodesic (destination-point) formula and the same spherical Earth model as DuckDB's `ST_Distance_Sphere()`. This accounts for longitude scale changing with latitude. This method adds random jitter; it does not provide anonymization or differential privacy guarantees.
|
|
4634
|
+
*
|
|
4635
|
+
* This method supports only `POINT` geometries in `EPSG:4326`. Null and empty geometries are preserved. It queues the operation; the operation runs when an async observer method (like `getData()` or `log()`) is awaited, or when `run()` is called.
|
|
4636
|
+
*
|
|
4637
|
+
* @param maxDistance - The maximum great-circle displacement in the selected unit. Must be a finite number greater than or equal to `0` and no greater than half Earth's circumference.
|
|
4638
|
+
* @param options - An optional object with configuration options:
|
|
4639
|
+
* @param options.column - The name of the EPSG:4326 point geometry column. If omitted, the method will automatically attempt to find a geometry column.
|
|
4640
|
+
* @param options.unit - The unit of `maxDistance`: `"m"` for metres or `"km"` for kilometres. Defaults to `"m"`.
|
|
4641
|
+
* @param options.onlyDuplicates - If `true`, moves only points whose complete original geometry occurs more than once in the selected geometry column. Every point in a duplicated group is moved. Defaults to `false`.
|
|
4642
|
+
* @returns The table, so methods can be chained.
|
|
4643
|
+
* @category Geospatial
|
|
4644
|
+
*
|
|
4645
|
+
* @example
|
|
4646
|
+
* ```ts
|
|
4647
|
+
* // Move every point by no more than 500 metres
|
|
4648
|
+
* await table.addGeoNoise(500).log();
|
|
4649
|
+
* ```
|
|
4650
|
+
*
|
|
4651
|
+
* @example
|
|
4652
|
+
* ```ts
|
|
4653
|
+
* // Separate duplicated points by up to 0.1 kilometres
|
|
4654
|
+
* await table.addGeoNoise(0.1, {
|
|
4655
|
+
* column: "geom",
|
|
4656
|
+
* unit: "km",
|
|
4657
|
+
* onlyDuplicates: true,
|
|
4658
|
+
* }).log();
|
|
4659
|
+
* ```
|
|
4660
|
+
*/
|
|
4661
|
+
addGeoNoise(maxDistance, options = {}) {
|
|
4662
|
+
addGeoNoise(this, maxDistance, options);
|
|
4663
|
+
return this;
|
|
4664
|
+
}
|
|
4463
4665
|
/**
|
|
4464
4666
|
* Reduces the precision of geometries in a specified column to a given number of decimal places.
|
|
4465
4667
|
*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addCharacterCount.d.ts","sourceRoot":"","sources":["../../src/methods/addCharacterCount.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAKvD,MAAM,CAAC,OAAO,UAAU,iBAAiB,CACvC,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,QAclB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import assertNewColumns from "../helpers/assertNewColumns.js";
|
|
2
|
+
import queueOp from "../helpers/queueOp.js";
|
|
3
|
+
import quoteIdentifier from "../helpers/quoteIdentifier.js";
|
|
4
|
+
export default function addCharacterCount(simpleTable, column, newColumn) {
|
|
5
|
+
queueOp(simpleTable, {
|
|
6
|
+
kind: "fusable",
|
|
7
|
+
method: "addCharacterCount()",
|
|
8
|
+
parameters: { column, newColumn },
|
|
9
|
+
needsSchema: true,
|
|
10
|
+
buildSelect: (input, types) => {
|
|
11
|
+
assertNewColumns(types, [newColumn], "addCharacterCount()");
|
|
12
|
+
return `SELECT *, LENGTH(${quoteIdentifier(column)}) AS ${quoteIdentifier(newColumn)} FROM ${input}`;
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type SimpleTable from "../class/SimpleTable.js";
|
|
2
|
+
export default function addGeoNoise(simpleTable: SimpleTable, maxDistance: number, options?: {
|
|
3
|
+
column?: string;
|
|
4
|
+
unit?: "m" | "km";
|
|
5
|
+
onlyDuplicates?: boolean;
|
|
6
|
+
}): void;
|
|
7
|
+
//# sourceMappingURL=addGeoNoise.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addGeoNoise.d.ts","sourceRoot":"","sources":["../../src/methods/addGeoNoise.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAKvD,MAAM,CAAC,OAAO,UAAU,WAAW,CACjC,WAAW,EAAE,WAAW,EACxB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;CACrB,QA2CP"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import findGeoColumnFromSchema from "../helpers/findGeoColumnFromSchema.js";
|
|
2
|
+
import queueOp from "../helpers/queueOp.js";
|
|
3
|
+
import quoteIdentifier from "../helpers/quoteIdentifier.js";
|
|
4
|
+
// This is the mean Earth radius used by DuckDB's ST_Distance_Sphere().
|
|
5
|
+
const EARTH_RADIUS_METERS = 6_371_000;
|
|
6
|
+
export default function addGeoNoise(simpleTable, maxDistance, options = {}) {
|
|
7
|
+
// This validation doesn't need the database, so it stays at call time.
|
|
8
|
+
if (!Number.isFinite(maxDistance) || maxDistance < 0) {
|
|
9
|
+
throw new Error("addGeoNoise() maxDistance must be a finite number greater than or equal to 0.");
|
|
10
|
+
}
|
|
11
|
+
if (options.unit !== undefined && options.unit !== "m" && options.unit !== "km") {
|
|
12
|
+
throw new Error('addGeoNoise() options.unit must be "m" or "km".');
|
|
13
|
+
}
|
|
14
|
+
options = structuredClone(options);
|
|
15
|
+
const maxDistanceMeters = options.unit === "km"
|
|
16
|
+
? maxDistance * 1_000
|
|
17
|
+
: maxDistance;
|
|
18
|
+
if (maxDistanceMeters > Math.PI * EARTH_RADIUS_METERS) {
|
|
19
|
+
throw new Error("addGeoNoise() maxDistance cannot exceed half the Earth's circumference.");
|
|
20
|
+
}
|
|
21
|
+
queueOp(simpleTable, {
|
|
22
|
+
kind: "fusable",
|
|
23
|
+
method: "addGeoNoise()",
|
|
24
|
+
parameters: { maxDistance, options },
|
|
25
|
+
needsSchema: true,
|
|
26
|
+
needsSpatial: true,
|
|
27
|
+
preservesSchema: true,
|
|
28
|
+
buildSelect: (input, schema) => {
|
|
29
|
+
const column = options.column ??
|
|
30
|
+
findGeoColumnFromSchema(schema, "addGeoNoise()", simpleTable.name);
|
|
31
|
+
return addGeoNoiseSelect(input, schema, column, maxDistanceMeters, options.onlyDuplicates === true);
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function addGeoNoiseSelect(input, schema, column, maxDistanceMeters, onlyDuplicates) {
|
|
36
|
+
const type = schema[column];
|
|
37
|
+
if (type === undefined) {
|
|
38
|
+
throw new Error(`addGeoNoise() could not find column ${quoteIdentifier(column)}.`);
|
|
39
|
+
}
|
|
40
|
+
if (type !== "GEOMETRY('EPSG:4326')") {
|
|
41
|
+
throw new Error(`addGeoNoise() requires an EPSG:4326 geometry column, but ${quoteIdentifier(column)} has type ${type}. Reproject it to EPSG:4326 first.`);
|
|
42
|
+
}
|
|
43
|
+
const internalNames = makeInternalNames(schema, input);
|
|
44
|
+
const quotedColumn = quoteIdentifier(column);
|
|
45
|
+
const pointError = `error('addGeoNoise() requires POINT geometries in column ${column.replaceAll("'", "''")}.')`;
|
|
46
|
+
const coordinateGuard = (coordinate) => `CASE
|
|
47
|
+
WHEN ${quotedColumn} IS NULL OR ST_IsEmpty(${quotedColumn}) THEN NULL
|
|
48
|
+
WHEN CAST(ST_GeometryType(${quotedColumn}) AS VARCHAR) <> 'POINT'
|
|
49
|
+
THEN ${pointError}
|
|
50
|
+
ELSE radians(ST_${coordinate}(${quotedColumn}))
|
|
51
|
+
END`;
|
|
52
|
+
const duplicateCount = onlyDuplicates
|
|
53
|
+
? `COUNT(*) OVER (PARTITION BY ${quotedColumn})`
|
|
54
|
+
: "2";
|
|
55
|
+
const maxAngularDistance = maxDistanceMeters / EARTH_RADIUS_METERS;
|
|
56
|
+
const latitudeExpression = `asin(
|
|
57
|
+
sin(${quoteIdentifier(internalNames.latitude)}) * cos(${quoteIdentifier(internalNames.angularDistance)}) +
|
|
58
|
+
cos(${quoteIdentifier(internalNames.latitude)}) * sin(${quoteIdentifier(internalNames.angularDistance)}) * cos(${quoteIdentifier(internalNames.bearing)})
|
|
59
|
+
)`;
|
|
60
|
+
const longitudeExpression = `${quoteIdentifier(internalNames.longitude)} + atan2(
|
|
61
|
+
sin(${quoteIdentifier(internalNames.bearing)}) * sin(${quoteIdentifier(internalNames.angularDistance)}) * cos(${quoteIdentifier(internalNames.latitude)}),
|
|
62
|
+
cos(${quoteIdentifier(internalNames.angularDistance)}) -
|
|
63
|
+
sin(${quoteIdentifier(internalNames.latitude)}) * sin(${quoteIdentifier(internalNames.destinationLatitude)})
|
|
64
|
+
)`;
|
|
65
|
+
const replacement = `CASE
|
|
66
|
+
WHEN ${quotedColumn} IS NULL OR ST_IsEmpty(${quotedColumn}) THEN ${quotedColumn}
|
|
67
|
+
WHEN ${quoteIdentifier(internalNames.duplicateCount)} <= 1 THEN ${quotedColumn}
|
|
68
|
+
ELSE ST_Point(
|
|
69
|
+
degrees(atan2(
|
|
70
|
+
sin(${quoteIdentifier(internalNames.destinationLongitude)}),
|
|
71
|
+
cos(${quoteIdentifier(internalNames.destinationLongitude)})
|
|
72
|
+
)),
|
|
73
|
+
degrees(${quoteIdentifier(internalNames.destinationLatitude)})
|
|
74
|
+
)::GEOMETRY('EPSG:4326')
|
|
75
|
+
END AS ${quotedColumn}`;
|
|
76
|
+
const outputColumns = Object.keys(schema).map((outputColumn) => outputColumn === column ? replacement : quoteIdentifier(outputColumn)).join(", ");
|
|
77
|
+
return `WITH ${quoteIdentifier(internalNames.randomValues)} AS MATERIALIZED (
|
|
78
|
+
SELECT *,
|
|
79
|
+
random() AS ${quoteIdentifier(internalNames.distanceRandom)},
|
|
80
|
+
random() AS ${quoteIdentifier(internalNames.bearingRandom)},
|
|
81
|
+
${duplicateCount} AS ${quoteIdentifier(internalNames.duplicateCount)}
|
|
82
|
+
FROM ${input}
|
|
83
|
+
),
|
|
84
|
+
${quoteIdentifier(internalNames.start)} AS (
|
|
85
|
+
SELECT *,
|
|
86
|
+
${coordinateGuard("Y")} AS ${quoteIdentifier(internalNames.latitude)},
|
|
87
|
+
${coordinateGuard("X")} AS ${quoteIdentifier(internalNames.longitude)},
|
|
88
|
+
2 * pi() * ${quoteIdentifier(internalNames.bearingRandom)} AS ${quoteIdentifier(internalNames.bearing)},
|
|
89
|
+
2 * asin(
|
|
90
|
+
sqrt(${quoteIdentifier(internalNames.distanceRandom)}) *
|
|
91
|
+
sin(${maxAngularDistance} / 2)
|
|
92
|
+
) AS ${quoteIdentifier(internalNames.angularDistance)}
|
|
93
|
+
FROM ${quoteIdentifier(internalNames.randomValues)}
|
|
94
|
+
),
|
|
95
|
+
${quoteIdentifier(internalNames.destinationLatitudeCte)} AS (
|
|
96
|
+
SELECT *, ${latitudeExpression} AS ${quoteIdentifier(internalNames.destinationLatitude)}
|
|
97
|
+
FROM ${quoteIdentifier(internalNames.start)}
|
|
98
|
+
),
|
|
99
|
+
${quoteIdentifier(internalNames.destination)} AS (
|
|
100
|
+
SELECT *, ${longitudeExpression} AS ${quoteIdentifier(internalNames.destinationLongitude)}
|
|
101
|
+
FROM ${quoteIdentifier(internalNames.destinationLatitudeCte)}
|
|
102
|
+
)
|
|
103
|
+
SELECT ${outputColumns}
|
|
104
|
+
FROM ${quoteIdentifier(internalNames.destination)}`;
|
|
105
|
+
}
|
|
106
|
+
function makeInternalNames(schema, input) {
|
|
107
|
+
const used = new Set(Object.keys(schema));
|
|
108
|
+
if (input.startsWith('"') && input.endsWith('"')) {
|
|
109
|
+
used.add(input.slice(1, -1).replaceAll('""', '"'));
|
|
110
|
+
}
|
|
111
|
+
const next = (base) => {
|
|
112
|
+
let name = base;
|
|
113
|
+
let suffix = 2;
|
|
114
|
+
while (used.has(name)) {
|
|
115
|
+
name = `${base}_${suffix++}`;
|
|
116
|
+
}
|
|
117
|
+
used.add(name);
|
|
118
|
+
return name;
|
|
119
|
+
};
|
|
120
|
+
return {
|
|
121
|
+
randomValues: next("__sda_add_geo_noise_random_values"),
|
|
122
|
+
start: next("__sda_add_geo_noise_start"),
|
|
123
|
+
destinationLatitudeCte: next("__sda_add_geo_noise_latitude_result"),
|
|
124
|
+
destination: next("__sda_add_geo_noise_result"),
|
|
125
|
+
distanceRandom: next("__sda_add_geo_noise_distance_random"),
|
|
126
|
+
bearingRandom: next("__sda_add_geo_noise_bearing_random"),
|
|
127
|
+
duplicateCount: next("__sda_add_geo_noise_duplicate_count"),
|
|
128
|
+
latitude: next("__sda_add_geo_noise_latitude"),
|
|
129
|
+
longitude: next("__sda_add_geo_noise_longitude"),
|
|
130
|
+
bearing: next("__sda_add_geo_noise_bearing"),
|
|
131
|
+
angularDistance: next("__sda_add_geo_noise_angular_distance"),
|
|
132
|
+
destinationLatitude: next("__sda_add_geo_noise_destination_latitude"),
|
|
133
|
+
destinationLongitude: next("__sda_add_geo_noise_destination_longitude"),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addNoise.d.ts","sourceRoot":"","sources":["../../src/methods/addNoise.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAEvD,MAAM,CAAC,OAAO,UAAU,QAAQ,CAC9B,WAAW,EAAE,WAAW,EACxB,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,EAC1B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,QAwC3C"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import assertColumnsExist from "../helpers/assertColumnsExist.js";
|
|
2
|
+
import keepNumericalColumns from "../helpers/keepNumericalColumns.js";
|
|
3
|
+
import queueOp from "../helpers/queueOp.js";
|
|
4
|
+
import quoteIdentifier from "../helpers/quoteIdentifier.js";
|
|
5
|
+
import stringToArray from "../helpers/stringToArray.js";
|
|
6
|
+
export default function addNoise(simpleTable, columns, max, options = {}) {
|
|
7
|
+
// This validation doesn't need the database, so it stays at call time.
|
|
8
|
+
if (!Number.isFinite(max) || max < 0) {
|
|
9
|
+
throw new Error("addNoise() max must be a finite number greater than or equal to 0.");
|
|
10
|
+
}
|
|
11
|
+
const cols = stringToArray(columns);
|
|
12
|
+
if (cols.length === 0) {
|
|
13
|
+
throw new Error("addNoise() requires at least one column.");
|
|
14
|
+
}
|
|
15
|
+
const duplicateColumns = cols.filter((column, index) => cols.indexOf(column) !== index);
|
|
16
|
+
if (duplicateColumns.length > 0) {
|
|
17
|
+
const duplicates = [...new Set(duplicateColumns)];
|
|
18
|
+
throw new Error(`addNoise() received duplicate column${duplicates.length === 1 ? "" : "s"} ${duplicates.map(quoteIdentifier).join(", ")}. Include each column only once.`);
|
|
19
|
+
}
|
|
20
|
+
options = structuredClone(options);
|
|
21
|
+
queueOp(simpleTable, {
|
|
22
|
+
kind: "fusable",
|
|
23
|
+
method: "addNoise()",
|
|
24
|
+
parameters: { columns: cols, max, options },
|
|
25
|
+
needsSchema: true,
|
|
26
|
+
outputSchema: (schema) => ({
|
|
27
|
+
...schema,
|
|
28
|
+
...Object.fromEntries(cols.map((column) => [column, "DOUBLE"])),
|
|
29
|
+
}),
|
|
30
|
+
buildSelect: (input, schema) => addNoiseSelect(input, schema, cols, max, options.onlyDuplicates === true),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function addNoiseSelect(input, schema, columns, max, onlyDuplicates) {
|
|
34
|
+
assertColumnsExist(schema, columns, "addNoise()");
|
|
35
|
+
const numericalColumns = new Set(keepNumericalColumns(schema));
|
|
36
|
+
const nonNumericalColumns = columns.filter((column) => !numericalColumns.has(column));
|
|
37
|
+
if (nonNumericalColumns.length > 0) {
|
|
38
|
+
throw new Error(`addNoise() requires numeric columns, but ${nonNumericalColumns.map((column) => `${quoteIdentifier(column)} has type ${schema[column]}`).join(", ")}. Convert ${nonNumericalColumns.length === 1 ? "it" : "them"} to a numeric type first.`);
|
|
39
|
+
}
|
|
40
|
+
const replacements = columns.map((column) => {
|
|
41
|
+
const quotedColumn = quoteIdentifier(column);
|
|
42
|
+
const duplicateCondition = onlyDuplicates
|
|
43
|
+
? `COUNT(*) OVER (PARTITION BY ${quotedColumn}) > 1`
|
|
44
|
+
: "TRUE";
|
|
45
|
+
return `CASE
|
|
46
|
+
WHEN ${quotedColumn} IS NULL THEN NULL
|
|
47
|
+
WHEN ${duplicateCondition}
|
|
48
|
+
THEN CAST(${quotedColumn} AS DOUBLE) + (random() * 2 - 1) * ${max}
|
|
49
|
+
ELSE CAST(${quotedColumn} AS DOUBLE)
|
|
50
|
+
END AS ${quotedColumn}`;
|
|
51
|
+
}).join(", ");
|
|
52
|
+
return `SELECT * REPLACE (${replacements}) FROM ${input}`;
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addWordCount.d.ts","sourceRoot":"","sources":["../../src/methods/addWordCount.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAKvD,MAAM,CAAC,OAAO,UAAU,YAAY,CAClC,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,QAclB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import assertNewColumns from "../helpers/assertNewColumns.js";
|
|
2
|
+
import queueOp from "../helpers/queueOp.js";
|
|
3
|
+
import quoteIdentifier from "../helpers/quoteIdentifier.js";
|
|
4
|
+
export default function addWordCount(simpleTable, column, newColumn) {
|
|
5
|
+
queueOp(simpleTable, {
|
|
6
|
+
kind: "fusable",
|
|
7
|
+
method: "addWordCount()",
|
|
8
|
+
parameters: { column, newColumn },
|
|
9
|
+
needsSchema: true,
|
|
10
|
+
buildSelect: (input, types) => {
|
|
11
|
+
assertNewColumns(types, [newColumn], "addWordCount()");
|
|
12
|
+
return `SELECT *, ARRAY_LENGTH(REGEXP_EXTRACT_ALL(${quoteIdentifier(column)}, '[^[:space:]]+')) AS ${quoteIdentifier(newColumn)} FROM ${input}`;
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type SimpleTable from "../class/SimpleTable.js";
|
|
2
2
|
export default function columnProportions(simpleTable: SimpleTable, column: string, newColumn: string, options?: {
|
|
3
3
|
by?: string | string[];
|
|
4
|
+
base?: number;
|
|
4
5
|
decimals?: number;
|
|
5
6
|
}): void;
|
|
6
7
|
//# sourceMappingURL=columnProportions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"columnProportions.d.ts","sourceRoot":"","sources":["../../src/methods/columnProportions.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAEvD,MAAM,CAAC,OAAO,UAAU,iBAAiB,CACvC,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IACP,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACd,
|
|
1
|
+
{"version":3,"file":"columnProportions.d.ts","sourceRoot":"","sources":["../../src/methods/columnProportions.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAEvD,MAAM,CAAC,OAAO,UAAU,iBAAiB,CACvC,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IACP,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACd,QAqCP"}
|
|
@@ -3,6 +3,10 @@ import assertNewColumns from "../helpers/assertNewColumns.js";
|
|
|
3
3
|
import queueOp from "../helpers/queueOp.js";
|
|
4
4
|
import stringToArray from "../helpers/stringToArray.js";
|
|
5
5
|
export default function columnProportions(simpleTable, column, newColumn, options = {}) {
|
|
6
|
+
if (options.base !== undefined &&
|
|
7
|
+
(!Number.isFinite(options.base) || options.base <= 0)) {
|
|
8
|
+
throw new Error("columnProportions() options.base must be a finite number greater than 0.");
|
|
9
|
+
}
|
|
6
10
|
options = structuredClone(options);
|
|
7
11
|
queueOp(simpleTable, {
|
|
8
12
|
kind: "fusable",
|
|
@@ -15,9 +19,10 @@ export default function columnProportions(simpleTable, column, newColumn, option
|
|
|
15
19
|
const partition = by.length === 0
|
|
16
20
|
? ""
|
|
17
21
|
: `PARTITION BY ${by.map((d) => `${quoteIdentifier(d)}`).join(",")}`;
|
|
22
|
+
const proportion = `${quoteIdentifier(column)} / sum(${quoteIdentifier(column)}) OVER(${partition}) * ${options.base ?? 1}`;
|
|
18
23
|
return typeof options.decimals === "number"
|
|
19
|
-
? `SELECT *, ROUND(${
|
|
20
|
-
: `SELECT *, ${
|
|
24
|
+
? `SELECT *, ROUND(${proportion}, ${options.decimals}) AS ${quoteIdentifier(newColumn)} FROM ${input}`
|
|
25
|
+
: `SELECT *, ${proportion} AS ${quoteIdentifier(newColumn)} FROM ${input}`;
|
|
21
26
|
},
|
|
22
27
|
});
|
|
23
28
|
}
|