@nshiab/simple-data-analysis-core 2.1.9 → 2.1.11
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 +18 -4
- package/esm/class/SimpleTable.d.ts.map +1 -1
- package/esm/class/SimpleTable.js +19 -5
- package/esm/helpers/printTable.d.ts +2 -0
- package/esm/helpers/printTable.d.ts.map +1 -1
- package/esm/helpers/printTable.js +11 -5
- package/esm/helpers/splitGraphemes.d.ts +2 -0
- package/esm/helpers/splitGraphemes.d.ts.map +1 -0
- package/esm/helpers/splitGraphemes.js +6 -0
- package/esm/helpers/wrapString.d.ts +6 -4
- package/esm/helpers/wrapString.d.ts.map +1 -1
- package/esm/helpers/wrapString.js +24 -13
- package/esm/methods/umap.d.ts +1 -1
- package/esm/methods/umap.d.ts.map +1 -1
- package/esm/methods/umap.js +23 -42
- package/llm.md +20 -5
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/script/class/SimpleTable.d.ts +18 -4
- package/script/class/SimpleTable.d.ts.map +1 -1
- package/script/class/SimpleTable.js +19 -5
- package/script/helpers/printTable.d.ts +2 -0
- package/script/helpers/printTable.d.ts.map +1 -1
- package/script/helpers/printTable.js +11 -5
- package/script/helpers/splitGraphemes.d.ts +2 -0
- package/script/helpers/splitGraphemes.d.ts.map +1 -0
- package/script/helpers/splitGraphemes.js +9 -0
- package/script/helpers/wrapString.d.ts +6 -4
- package/script/helpers/wrapString.d.ts.map +1 -1
- package/script/helpers/wrapString.js +27 -13
- package/script/methods/umap.d.ts +1 -1
- package/script/methods/umap.d.ts.map +1 -1
- package/script/methods/umap.js +23 -42
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.default = wrapString;
|
|
7
|
+
const splitGraphemes_js_1 = __importDefault(require("./splitGraphemes.js"));
|
|
4
8
|
/**
|
|
5
9
|
* Wraps a string to a specified maximum width, attempting to break at word
|
|
6
10
|
* boundaries when possible. If a single word exceeds the maximum width, it
|
|
7
|
-
* will be broken at
|
|
8
|
-
* for preparing text to be displayed
|
|
11
|
+
* will be broken at a grapheme boundary, keeping combining accents with their
|
|
12
|
+
* letters. This function is primarily used for preparing text to be displayed
|
|
13
|
+
* in console tables. Widths count graphemes, not terminal columns for wide
|
|
14
|
+
* characters or emoji.
|
|
9
15
|
*
|
|
10
16
|
* @example
|
|
11
17
|
* ```typescript
|
|
@@ -29,56 +35,64 @@ exports.default = wrapString;
|
|
|
29
35
|
*
|
|
30
36
|
* @param str - The string to wrap.
|
|
31
37
|
*
|
|
32
|
-
* @param maxWidth - The maximum
|
|
38
|
+
* @param maxWidth - The maximum number of graphemes in each line.
|
|
33
39
|
*
|
|
34
40
|
* @param wordWrap - If true, attempts to break at word boundaries. If false,
|
|
35
|
-
* breaks at
|
|
41
|
+
* breaks at grapheme boundaries. Defaults to `true`.
|
|
36
42
|
*
|
|
37
43
|
* @returns The wrapped string with newline characters inserted at appropriate
|
|
38
44
|
* positions.
|
|
39
45
|
*/
|
|
40
46
|
function wrapString(str, maxWidth, wordWrap = true) {
|
|
41
|
-
|
|
47
|
+
const graphemes = (0, splitGraphemes_js_1.default)(str);
|
|
48
|
+
if (graphemes.length <= maxWidth) {
|
|
42
49
|
return str;
|
|
43
50
|
}
|
|
44
51
|
if (!wordWrap) {
|
|
45
|
-
// Simple
|
|
52
|
+
// Simple grapheme-based wrapping
|
|
46
53
|
const lines = [];
|
|
47
|
-
for (let i = 0; i <
|
|
48
|
-
lines.push(
|
|
54
|
+
for (let i = 0; i < graphemes.length; i += maxWidth) {
|
|
55
|
+
lines.push(graphemes.slice(i, i + maxWidth).join(""));
|
|
49
56
|
}
|
|
50
57
|
return lines.join("\n");
|
|
51
58
|
}
|
|
52
59
|
// Word-aware wrapping
|
|
53
60
|
const lines = [];
|
|
54
61
|
let currentLine = "";
|
|
62
|
+
let currentWidth = 0;
|
|
55
63
|
const words = str.split(/(\s+)/); // Split but keep whitespace
|
|
56
64
|
for (const word of words) {
|
|
65
|
+
const wordGraphemes = (0, splitGraphemes_js_1.default)(word);
|
|
66
|
+
const wordWidth = wordGraphemes.length;
|
|
57
67
|
// If adding this word would exceed maxWidth
|
|
58
|
-
if (
|
|
68
|
+
if (currentWidth + wordWidth > maxWidth) {
|
|
59
69
|
// If current line is not empty, save it
|
|
60
70
|
if (currentLine.length > 0) {
|
|
61
71
|
lines.push(currentLine.trimEnd());
|
|
62
72
|
currentLine = "";
|
|
73
|
+
currentWidth = 0;
|
|
63
74
|
}
|
|
64
75
|
// If the word itself is longer than maxWidth, break it
|
|
65
|
-
if (
|
|
66
|
-
for (let i = 0; i <
|
|
67
|
-
const chunk =
|
|
68
|
-
if (i + maxWidth <
|
|
76
|
+
if (wordWidth > maxWidth) {
|
|
77
|
+
for (let i = 0; i < wordWidth; i += maxWidth) {
|
|
78
|
+
const chunk = wordGraphemes.slice(i, i + maxWidth).join("");
|
|
79
|
+
if (i + maxWidth < wordWidth) {
|
|
69
80
|
lines.push(chunk);
|
|
70
81
|
}
|
|
71
82
|
else {
|
|
72
83
|
currentLine = chunk;
|
|
84
|
+
currentWidth = wordWidth - i;
|
|
73
85
|
}
|
|
74
86
|
}
|
|
75
87
|
}
|
|
76
88
|
else {
|
|
77
89
|
currentLine = word;
|
|
90
|
+
currentWidth = wordWidth;
|
|
78
91
|
}
|
|
79
92
|
}
|
|
80
93
|
else {
|
|
81
94
|
currentLine += word;
|
|
95
|
+
currentWidth += wordWidth;
|
|
82
96
|
}
|
|
83
97
|
}
|
|
84
98
|
// Add any remaining text
|
package/script/methods/umap.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type SimpleTable from "../class/SimpleTable.js";
|
|
2
2
|
type Options = NonNullable<Parameters<SimpleTable["umap"]>[1]>;
|
|
3
|
-
export default function umap(table: SimpleTable,
|
|
3
|
+
export default function umap(table: SimpleTable, columns: string | string[], options?: Options): void;
|
|
4
4
|
export {};
|
|
5
5
|
//# sourceMappingURL=umap.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"umap.d.ts","sourceRoot":"","sources":["../../src/methods/umap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"umap.d.ts","sourceRoot":"","sources":["../../src/methods/umap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAcvD,KAAK,OAAO,GAAG,WAAW,CAAC,UAAU,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE/D,MAAM,CAAC,OAAO,UAAU,IAAI,CAC1B,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,EAC1B,OAAO,GAAE,OAAY,QAUtB"}
|
package/script/methods/umap.js
CHANGED
|
@@ -9,22 +9,24 @@ const quoteIdentifier_js_1 = __importDefault(require("../helpers/quoteIdentifier
|
|
|
9
9
|
const queryDB_js_1 = __importDefault(require("../helpers/queryDB.js"));
|
|
10
10
|
const mergeOptions_js_1 = __importDefault(require("../helpers/mergeOptions.js"));
|
|
11
11
|
const buildUmapGraph_js_1 = __importDefault(require("../helpers/buildUmapGraph.js"));
|
|
12
|
+
const prepareNumericFeatures_js_1 = __importDefault(require("../helpers/prepareNumericFeatures.js"));
|
|
12
13
|
const prepareUmapLayoutOptions_js_1 = __importDefault(require("../helpers/prepareUmapLayoutOptions.js"));
|
|
13
14
|
const readScalarNumber_js_1 = __importDefault(require("../helpers/readScalarNumber.js"));
|
|
14
15
|
const optimizeUmapLayout_js_1 = __importDefault(require("../helpers/optimizeUmapLayout.js"));
|
|
15
16
|
const initialUmapCoordinates_js_1 = __importDefault(require("../helpers/initialUmapCoordinates.js"));
|
|
16
17
|
const appendColumnBatches_js_1 = __importDefault(require("../helpers/appendColumnBatches.js"));
|
|
17
18
|
const node_api_1 = require("@duckdb/node-api");
|
|
18
|
-
function umap(table,
|
|
19
|
+
function umap(table, columns, options = {}) {
|
|
20
|
+
const selected = typeof columns === "string" ? columns : [...columns];
|
|
19
21
|
const settings = structuredClone(options);
|
|
20
22
|
(0, queueOp_js_1.default)(table, {
|
|
21
23
|
kind: "barrier",
|
|
22
24
|
method: "umap()",
|
|
23
|
-
parameters: {
|
|
24
|
-
execute: () => execute(table,
|
|
25
|
+
parameters: { columns: selected, options: settings },
|
|
26
|
+
execute: () => execute(table, selected, settings),
|
|
25
27
|
});
|
|
26
28
|
}
|
|
27
|
-
async function execute(table,
|
|
29
|
+
async function execute(table, columns, options) {
|
|
28
30
|
const { neighbors = 15, metric = "euclidean" } = options;
|
|
29
31
|
if (!Number.isSafeInteger(neighbors) || neighbors < 2) {
|
|
30
32
|
throw new Error("neighbors must be a safe integer of at least 2.");
|
|
@@ -33,22 +35,10 @@ async function execute(table, column, options) {
|
|
|
33
35
|
if (!["euclidean", "cosine"].includes(metric)) {
|
|
34
36
|
throw new Error("metric must be euclidean or cosine.");
|
|
35
37
|
}
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
const resolve = (name) => {
|
|
39
|
-
const found = columns.find((c) => c.toLowerCase() === name.toLowerCase());
|
|
40
|
-
if (found === undefined)
|
|
41
|
-
throw new Error(`Column ${name} does not exist.`);
|
|
42
|
-
return found;
|
|
43
|
-
};
|
|
44
|
-
const vector = resolve(column);
|
|
45
|
-
if (columns.some((c) => ["umapx", "umapy"].includes(c.toLowerCase()))) {
|
|
38
|
+
const sourceColumns = await table.getColumns();
|
|
39
|
+
if (sourceColumns.some((c) => ["umapx", "umapy"].includes(c.toLowerCase()))) {
|
|
46
40
|
throw new Error("UMAP output columns umapX and umapY must not already exist.");
|
|
47
41
|
}
|
|
48
|
-
if (!/^(FLOAT|DOUBLE|TINYINT|SMALLINT|INTEGER|BIGINT|HUGEINT|UTINYINT|USMALLINT|UINTEGER|UBIGINT|UHUGEINT|DECIMAL\(\d+,\d+\))\[\d*\]$/
|
|
49
|
-
.test(types[vector])) {
|
|
50
|
-
throw new Error("UMAP requires a one-dimensional numeric LIST or ARRAY column.");
|
|
51
|
-
}
|
|
52
42
|
const connection = table.connection;
|
|
53
43
|
const [sourceOid, sourceTemporary] = (await connection.runAndReadAll(`SELECT table_oid,temporary FROM duckdb_tables()
|
|
54
44
|
WHERE lower(table_name)=lower($1) AND schema_name=current_schema()
|
|
@@ -58,10 +48,18 @@ async function execute(table, column, options) {
|
|
|
58
48
|
throw new Error("UMAP requires a materialized source table.");
|
|
59
49
|
}
|
|
60
50
|
const q = quoteIdentifier_js_1.default;
|
|
51
|
+
const count = await (0, readScalarNumber_js_1.default)(connection, `SELECT count(*) FROM ${q(table.name)}`);
|
|
52
|
+
if (count < 3 || count > 0x7fffffff) {
|
|
53
|
+
throw new Error("UMAP requires between 3 and 2147483647 rows.");
|
|
54
|
+
}
|
|
55
|
+
const prepared = await (0, prepareNumericFeatures_js_1.default)(table, typeof columns === "string"
|
|
56
|
+
? { kind: "vector", column: columns }
|
|
57
|
+
: { kind: "scalars", columns }, { method: "umap()" });
|
|
58
|
+
const dimensions = prepared.dimensions;
|
|
61
59
|
const suffix = crypto.randomUUID().replaceAll("-", "");
|
|
62
60
|
const name = (part) => `__sda_umap_${suffix}_${part}`;
|
|
63
|
-
const ordinal = q(
|
|
64
|
-
const source = q(table.name), snapshot = q(
|
|
61
|
+
const ordinal = q(prepared.rowIdColumn);
|
|
62
|
+
const source = q(table.name), snapshot = q(prepared.relation);
|
|
65
63
|
const layoutName = name("layout"), layout = q(layoutName);
|
|
66
64
|
const names = {
|
|
67
65
|
rows: q(name("rows")),
|
|
@@ -74,33 +72,15 @@ async function execute(table, column, options) {
|
|
|
74
72
|
graph: q(name("graph")),
|
|
75
73
|
};
|
|
76
74
|
const scratch = [
|
|
77
|
-
snapshot,
|
|
78
75
|
layout,
|
|
79
76
|
...Object.entries(names)
|
|
80
77
|
.filter(([key]) => key !== "hnsw").map(([, value]) => value),
|
|
81
78
|
];
|
|
82
79
|
try {
|
|
83
|
-
// Retain payloads in native DuckDB types. A private ordinal distinguishes
|
|
84
|
-
// duplicate rows and is independent of any user column named rowid.
|
|
85
|
-
await connection.run(`CREATE TEMP TABLE ${snapshot} AS
|
|
86
|
-
SELECT *, row_number() OVER () - 1 AS ${ordinal} FROM ${source}`);
|
|
87
|
-
const count = await (0, readScalarNumber_js_1.default)(connection, `SELECT count(*) FROM ${snapshot}`);
|
|
88
|
-
if (count < 3 || count > 0x7fffffff) {
|
|
89
|
-
throw new Error("UMAP requires between 3 and 2147483647 rows.");
|
|
90
|
-
}
|
|
91
|
-
if (await (0, readScalarNumber_js_1.default)(connection, `SELECT count(*) FROM ${snapshot}
|
|
92
|
-
WHERE ${q(vector)} IS NULL OR len(${q(vector)})=0
|
|
93
|
-
OR list_count(${q(vector)}) != len(${q(vector)})
|
|
94
|
-
OR NOT list_bool_and(list_transform(${q(vector)}, x -> isfinite(x::DOUBLE)))`)) {
|
|
95
|
-
throw new Error("Vectors must be nonempty, non-null and contain finite numbers only.");
|
|
96
|
-
}
|
|
97
|
-
if (await (0, readScalarNumber_js_1.default)(connection, `SELECT count(DISTINCT len(${q(vector)})) FROM ${snapshot}`) !== 1) {
|
|
98
|
-
throw new Error("Vectors must have equal dimensions.");
|
|
99
|
-
}
|
|
100
|
-
const dimensions = await (0, readScalarNumber_js_1.default)(connection, `SELECT len(${q(vector)}) FROM ${snapshot} LIMIT 1`);
|
|
101
80
|
await connection.run(`CREATE TEMP TABLE ${names.rows} AS
|
|
102
81
|
SELECT (row_number() OVER (ORDER BY ${ordinal})-1)::INTEGER AS vertex,
|
|
103
|
-
${ordinal} AS ordinal, ${q(
|
|
82
|
+
${ordinal} AS ordinal, ${q(prepared.vectorColumn)} AS vec FROM ${snapshot}`);
|
|
83
|
+
await connection.run(`ALTER TABLE ${snapshot} DROP COLUMN ${q(prepared.vectorColumn)}`);
|
|
104
84
|
if (await (0, readScalarNumber_js_1.default)(connection, `SELECT count(*) FROM ${names.rows}
|
|
105
85
|
WHERE NOT isfinite(array_inner_product(vec,vec)) OR array_inner_product(vec,vec)>1e300
|
|
106
86
|
${metric === "cosine" ? "OR array_inner_product(vec,vec)=0" : ""}`)) {
|
|
@@ -137,12 +117,12 @@ async function execute(table, column, options) {
|
|
|
137
117
|
await connection.run("BEGIN TRANSACTION");
|
|
138
118
|
try {
|
|
139
119
|
await (0, queryDB_js_1.default)(table, `CREATE OR REPLACE ${sourceTemporary ? "TEMP " : ""}TABLE ${source} AS
|
|
140
|
-
SELECT ${
|
|
120
|
+
SELECT ${prepared.sourceColumns.map((c) => `s.${q(c)}`).join(",")}, l.x AS "umapX", l.y AS "umapY"
|
|
141
121
|
FROM ${snapshot} s JOIN ${names.rows} r ON s.${ordinal}=r.ordinal
|
|
142
122
|
JOIN ${layout} l ON r.vertex=l.vertex ORDER BY s.${ordinal}`, (0, mergeOptions_js_1.default)(table, {
|
|
143
123
|
table: table.name,
|
|
144
124
|
method: "umap()",
|
|
145
|
-
parameters: {
|
|
125
|
+
parameters: { columns, options },
|
|
146
126
|
noClean: true,
|
|
147
127
|
}));
|
|
148
128
|
for (const sql of indexes)
|
|
@@ -158,5 +138,6 @@ async function execute(table, column, options) {
|
|
|
158
138
|
for (const relation of scratch) {
|
|
159
139
|
await connection.run(`DROP TABLE IF EXISTS ${relation}`);
|
|
160
140
|
}
|
|
141
|
+
await prepared.cleanup();
|
|
161
142
|
}
|
|
162
143
|
}
|