@jrc03c/js-math-tools
Version:
some math tools for JS
307 lines (245 loc) • 7.58 kB
JavaScript
import { assert } from "../assert.mjs"
import { copy } from "../copy.mjs"
import { filter } from "../filter.mjs"
import { forEach } from "../for-each.mjs"
import { isArray } from "../is-array.mjs"
import { isString } from "../is-string.mjs"
import { isUndefined } from "../is-undefined.mjs"
import { map } from "../map.mjs"
import { range } from "../range.mjs"
import { reverse } from "../reverse.mjs"
import { seriesAppend } from "./series-append.mjs"
import { seriesApply } from "./series-apply.mjs"
import { seriesDropMissing } from "./series-drop-missing.mjs"
import { seriesDropNaN } from "./series-drop-nan.mjs"
import { seriesFilter } from "./series-filter.mjs"
import { seriesGet } from "./series-get.mjs"
import { seriesGetSubsetByIndices } from "./series-get-subset-by-indices.mjs"
import { seriesGetSubsetByNames } from "./series-get-subset-by-names.mjs"
import { seriesPrint } from "./series-print.mjs"
import { seriesShuffle } from "./series-shuffle.mjs"
import { seriesSort } from "./series-sort.mjs"
import { seriesSortByIndex } from "./series-sort-by-index.mjs"
import { seriesToObject } from "./series-to-object.mjs"
import { shape } from "../shape.mjs"
import { transpose } from "../transpose.mjs"
const SERIES_SYMBOL = Symbol.for("@jrc03c/js-math-tools/series")
function createSeriesClass(DataFrame) {
class Series {
static [Symbol.hasInstance](x) {
try {
return !!x._symbol && x._symbol === SERIES_SYMBOL
} catch (e) {
return false
}
}
constructor(data) {
this.name = "data"
Object.defineProperty(this, "_symbol", {
configurable: false,
enumerable: false,
writable: false,
value: SERIES_SYMBOL,
})
Object.defineProperty(this, "_values", {
value: [],
configurable: true,
enumerable: false,
writable: true,
})
Object.defineProperty(this, "values", {
configurable: true,
enumerable: true,
get() {
return this._values
},
set(x) {
assert(isArray(x), "The new values must be a 1-dimensional array!")
const dataShape = shape(x)
assert(
dataShape.length === 1,
"The new array of values must be 1-dimensional!",
)
if (dataShape[0] < this._index.length) {
this._index = this._index.slice(0, dataShape[0])
} else if (dataShape[0] > this._index.length) {
const n = (x.length - 1).toString().length
this._index = this._index.concat(
range(this._index.length, dataShape[0]).map(i => {
return "item" + i.toString().padStart(n, "0")
}),
)
}
this._values = x
},
})
Object.defineProperty(this, "_index", {
value: [],
configurable: true,
enumerable: false,
writable: true,
})
Object.defineProperty(this, "index", {
configurable: true,
enumerable: true,
get() {
return this._index
},
set(x) {
assert(
isArray(x),
"The new index must be a 1-dimensional array of strings!",
)
assert(
x.length === this.shape[0],
"The new index must be the same length as the old index!",
)
assert(
shape(x).length === 1,
"The new index must be a 1-dimensional array of strings!",
)
forEach(x, value => {
assert(isString(value), "All of the row names must be strings!")
})
this._index = x
},
})
if (data) {
if (data instanceof Series) {
this.name = data.name
this.values = copy(data.values)
this.index = copy(data.index)
} else if (isArray(data)) {
const dataShape = shape(data)
assert(
dataShape.length === 1,
"When passing an array into the constructor of a Series, the array must be 1-dimensional!",
)
this.values = data
} else if (data instanceof Object) {
const keys = map(
Object.keys(data).concat(Object.getOwnPropertySymbols(data)),
v => v.toString(),
)
assert(
keys.length === 1,
"When passing an object into the constructor of a Series, the object must have only 1 key-value pair, where the key is the name of the data and the value is the 1-dimensional array of values!",
)
const name = keys[0]
const values = data[name]
assert(
shape(values).length === 1,
"When passing an object into the constructor of a Series, the object must have only 1 key-value pair, where the key is the name of the data and the value is the 1-dimensional array of values!",
)
this.name = name
this.values = values.slice()
}
}
}
get shape() {
return shape(this.values)
}
get length() {
return this.shape[0]
}
get isEmpty() {
return filter(this.values, v => !isUndefined(v)).length === 0
}
clear() {
const out = this.copy()
forEach(out.values, (v, i) => {
out.values[i] = undefined
})
return out
}
get(indices) {
return seriesGet(this, indices)
}
getSubsetByNames(indices) {
return seriesGetSubsetByNames(Series, this, indices)
}
getSubsetByIndices(indices) {
return seriesGetSubsetByIndices(this, indices)
}
loc(indices) {
return this.getSubsetByNames(indices)
}
iloc(indices) {
return this.getSubsetByIndices(indices)
}
reverse() {
const out = new Series(reverse(this.values))
out.index = reverse(this.index)
out.name = this.name
return out
}
resetIndex() {
const out = this.copy()
const n = (out.index.length - 1).toString().length
out.index = range(0, this.shape[0]).map(i => {
return "item" + i.toString().padStart(n, "0")
})
return out
}
copy() {
const out = new Series()
out._values = copy(this.values)
out._index = copy(this.index)
out.name = this.name
return out
}
append(x) {
return seriesAppend(Series, this, x)
}
apply(fn) {
return seriesApply(this, fn)
}
concat(x) {
return this.append(x)
}
dropMissing(condition, threshold) {
return seriesDropMissing(this, condition, threshold)
}
dropNaN() {
return seriesDropNaN(Series, this)
}
toObject() {
return seriesToObject(this)
}
print() {
return seriesPrint(this)
}
shuffle() {
return seriesShuffle(this)
}
sort(direction) {
return seriesSort(Series, this, direction)
}
sortByIndex() {
return seriesSortByIndex(Series, this)
}
filter(fn) {
return seriesFilter(Series, this, fn)
}
toDataFrame() {
const out = new DataFrame(transpose([this.values]))
out.columns = [this.name]
out.index = this.index
return out
}
transpose() {
const out = this.copy()
out.values = reverse(out.values)
out.index = reverse(out.index)
return out
}
getDummies() {
return this.toDataFrame().getDummies()
}
oneHotEncode() {
return this.getDummies()
}
}
return Series
}
export { createSeriesClass }