From 8cfc9ffc9135ecd832809359ad9ea66b5a53b143 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 5 Apr 2024 07:28:31 +0000 Subject: [PATCH] Deployed d5cd0de to master with MkDocs 1.5.3 and mike 2.0.0 --- latest | 2 +- master/404.html | 23 - master/benchmarks/index.html | 23 - master/changelog/index.html | 23 - master/index.html | 23 - master/nimlite/funcs/groupby.nim | 754 +++ master/nimlite/funcs/imputation.nim | 16 +- master/nimlite/libnimlite.nim | 9 + master/nimlite/libnimlite.pyi | 2 + master/nimlite/libnimlite.so | Bin 1329664 -> 1428312 bytes master/nimlite/numpy.nim | 18 +- master/objects.inv | Bin 4135 -> 3828 bytes master/reference/base/index.html | 23 - master/reference/config/index.html | 23 - master/reference/core/index.html | 25 +- master/reference/datasets/index.html | 23 - master/reference/datatypes/index.html | 23 - master/reference/diff/index.html | 23 - master/reference/export_utils/index.html | 23 - master/reference/file_reader_utils/index.html | 23 - master/reference/groupby_utils/index.html | 4513 ++--------------- master/reference/groupbys/groupbys.md | 1 - master/reference/groupbys/index.html | 1798 ------- master/reference/import_utils/index.html | 25 +- master/reference/imputation/index.html | 23 - master/reference/joins/index.html | 23 - master/reference/lookup/index.html | 23 - master/reference/match/index.html | 23 - master/reference/merge/index.html | 23 - master/reference/mp_utils/index.html | 23 - master/reference/nimlite/index.html | 69 +- master/reference/pivots/index.html | 183 +- master/reference/redux/index.html | 23 - master/reference/reindex/index.html | 23 - master/reference/sort_utils/index.html | 23 - master/reference/sortation/index.html | 23 - master/reference/tools/index.html | 23 - master/reference/utils/index.html | 23 - master/reference/version/index.html | 23 - master/search/search_index.json | 2 +- master/sitemap.xml | 63 +- master/sitemap.xml.gz | Bin 395 -> 395 bytes master/tablite/core.py | 4 +- master/tablite/groupby_utils.py | 212 +- master/tablite/groupbys.py | 201 - master/tablite/nimlite.py | 5 +- master/tablite/pivots.py | 22 +- master/tests/test_groupby_and_pivot.py | 134 +- master/tests/test_groupby_utils.py | 121 - master/tutorial/index.html | 23 - versions.json | 8 +- 51 files changed, 1583 insertions(+), 7179 deletions(-) create mode 100644 master/nimlite/funcs/groupby.nim delete mode 100644 master/reference/groupbys/groupbys.md delete mode 100644 master/reference/groupbys/index.html delete mode 100644 master/tablite/groupbys.py delete mode 100644 master/tests/test_groupby_utils.py diff --git a/latest b/latest index 3d7a689d..8b25206f 120000 --- a/latest +++ b/latest @@ -1 +1 @@ -2023.10.15 \ No newline at end of file +master \ No newline at end of file diff --git a/master/404.html b/master/404.html index 751f4fa5..aa79babc 100644 --- a/master/404.html +++ b/master/404.html @@ -458,8 +458,6 @@ - - @@ -691,27 +689,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/benchmarks/index.html b/master/benchmarks/index.html index b6ed6378..b852f523 100644 --- a/master/benchmarks/index.html +++ b/master/benchmarks/index.html @@ -663,8 +663,6 @@ - - @@ -896,27 +894,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/changelog/index.html b/master/changelog/index.html index ed80c861..f1803776 100644 --- a/master/changelog/index.html +++ b/master/changelog/index.html @@ -484,8 +484,6 @@ - - @@ -717,27 +715,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/index.html b/master/index.html index 67b74ea1..7292056d 100644 --- a/master/index.html +++ b/master/index.html @@ -472,8 +472,6 @@ - - @@ -705,27 +703,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/nimlite/funcs/groupby.nim b/master/nimlite/funcs/groupby.nim new file mode 100644 index 00000000..4d750d74 --- /dev/null +++ b/master/nimlite/funcs/groupby.nim @@ -0,0 +1,754 @@ +import nimpy +import std/[math, tables, strutils, strformat, sequtils, enumerate, sugar, options, algorithm] +import ../[pytypes, numpy, pymodules, nimpyext] +import ./imputation + +type Accumulator = enum + Max, Min, Sum, First, Last, Product, Count, + CountUnique, Average, StandardDeviation, Median, Mode + +proc str2Accumulator*(str: string): Accumulator = + let lower = str.toLower() + case lower: + of "max": result = Max + of "min": result = Min + of "sum": result = Sum + of "first": result = First + of "last": result = Last + of "product": result = Product + of "count": result = Count + of "count_unique": result = CountUnique + of "avg": result = Average + of "stdev": result = StandardDeviation + of "median": result = Median + of "mode": result = Mode + else: + raise newException(ValueError, &"Unrecognized groupby accumulator - {str}.") + +# ============================================================= +type GroupByFunction = ref object of RootObj + val: Option[PY_ObjectND] + +method `value=`*(self: GroupByFunction, value: Option[PY_ObjectND]) {. base .}= + self.val = value +method value*(self: GroupByFunction): Option[PY_ObjectND] {. base .}= + return self.val + +method update(self: GroupByFunction, value: Option[PY_ObjectND]) {.base.} = + raise newException(Defect, "not implemented.") + +proc constructGroupByFunction[T: GroupByFunction](self: T): T = + result = self + +proc newGroupByFunction(): GroupByFunction = + result = GroupByFunction().constructGroupByFunction() +# ============================================================= + +# ============================================================= +type Limit = ref object of GroupByFunction + +proc constructLimit[T: Limit](self: T): T = + result = self.constructGroupByFunction() + +proc newLimit(acc: Accumulator): Limit = + result = Limit().constructLimit() + +method run(self: Limit, value: Option[PY_ObjectND]) {.base.} = + raise newException(FieldDefect, "not implemented") + +method update(self: Limit, value: Option[PY_ObjectND]) = + if value.isNone(): + discard + elif self.value.isNone(): + self.value = value + else: + self.run(value) +# ============================================================= + +# ============================================================= +type GroupbyMax = ref object of Limit + +proc constructGroupbyMax[T: GroupbyMax](self: T): T = + result = self.constructLimit() + +proc newGroupbyMax(): GroupbyMax = + result = GroupbyMax().constructGroupbyMax() + +method run(self: GroupbyMax, value: Option[PY_ObjectND]) = + var v = self.value.get() + var vv = value.get() + if v.kind == vv.kind: + if vv > v: + self.value = some(vv) + else: + raise newException(Defect, "cannot find max between mixed types.") +# ============================================================= + +# ============================================================= +type GroupbyMin = ref object of Limit + +proc constructGroupbyMin[T: GroupbyMin](self: T): T = + result = self.constructLimit() + +proc newGroupbyMin(): GroupbyMin = + result = GroupbyMin().constructGroupbyMin() + +method run(self: GroupbyMin, value: Option[PY_ObjectND]) = + var v = self.value.get() + var vv = value.get() + if v.kind == vv.kind: + if vv < v: + self.value = some(vv) + else: + raise newException(Defect, "cannot find min between mixed types.") +# ============================================================= + +# ============================================================= +type GroupBySum = ref object of GroupByFunction + +proc constructGroupBySum[T: GroupBySum](self: T): T = + result = self.constructGroupByFunction() + result.value = some(newPY_Object(0.0)) + +proc newGroupBySum(): GroupBySum = + result = GroupBySum().constructGroupBySum() + +method update(self: GroupBySum, value: Option[PY_ObjectND]) = + var unSupportedTypes = @[K_DATE, K_TIME, K_DATETIME, K_STRING] + if value.isNone() or value.get().kind in unSupportedTypes: + raise newException(ValueError, &"Sum of {value.get().kind} doesn't make sense.") + + var v: float = PY_Float(self.value.get()).value + var vv: float + if value.get().kind == K_INT: + vv = float PY_Int(value.get()).value + elif value.get().kind == K_FLOAT: + vv = PY_Float(value.get()).value + self.value = some(newPY_Object(v + vv)) +# ============================================================= + +# ============================================================= +type GroupByProduct = ref object of GroupByFunction + +proc constructGroupByProduct[T: GroupByProduct](self: T): T = + result = self.constructGroupByFunction() + result.value = some(newPY_Object(1.0)) + +proc newGroupByProduct(): GroupByProduct = + result = GroupByProduct().constructGroupByProduct() + +method update(self: GroupByProduct, value: Option[PY_ObjectND]) = + var unSupportedTypes = @[K_DATE, K_TIME, K_DATETIME, K_STRING] + if value.isNone() or value.get().kind in unSupportedTypes: + raise newException(ValueError, &"Product of {value.get().kind} doesn't make sense.") + + var v: float = PY_Float(self.value.get()).value + var vv: float + if value.get().kind == K_INT: + vv = float PY_Int(value.get()).value + elif value.get().kind == K_FLOAT: + vv = PY_Float(value.get()).value + self.value = some(newPY_Object(v * vv)) +# ============================================================= + +# ============================================================= +type GroupByFirst = ref object of GroupByFunction + isSet: bool = false + +proc constructGroupByFirst[T: GroupByFirst](self: T): T = + result = self.constructGroupByFunction() + +proc newGroupByFirst(): GroupByFirst = + result = GroupByFirst().constructGroupByFirst() + +method update(self: GroupByFirst, value: Option[PY_ObjectND]) = + if not self.isSet: + self.value = value + self.isSet = true +# ============================================================= + +# ============================================================= +type GroupByLast = ref object of GroupByFunction + +proc constructGroupByLast[T: GroupByLast](self: T): T = + result = self.constructGroupByFunction() + result.value = some(newPY_Object()) + +proc newGroupByLast(): GroupByLast = + result = GroupByLast().constructGroupByLast() + +method update(self: GroupByLast, value: Option[PY_ObjectND]) = + self.value = value +# ============================================================= + +# ============================================================= +type GroupByCount = ref object of GroupByFunction + +proc constructGroupByCount[T: GroupByCount](self: T): T = + result = self.constructGroupByFunction() + result.value = some(newPY_Object(0)) + +proc newGroupByCount(): GroupByCount = + result = GroupByCount().constructGroupByCount() + +method update(self: GroupByCount, value: Option[PY_ObjectND]) = + var v: int = int PY_Int(self.value.get()).value + self.value = some(newPY_Object(v + 1)) +# ============================================================= + +# ============================================================= +type GroupByCountUnique = ref object of GroupByFunction + items: seq[PY_ObjectND] = @[] + +proc constructGroupByCountUnique[T: GroupByCountUnique](self: T): T = + result = self.constructGroupByFunction() + result.value = some(newPY_Object(0)) + +proc newGroupByCountUnique(): GroupByCountUnique = + result = GroupByCountUnique().constructGroupByCountUnique() + +method update(self: GroupByCountUnique, value: Option[PY_ObjectND]) = + if value.get() notin self.items: + self.items.add(value.get()) + self.value = some(newPY_Object(len(self.items))) +# ============================================================= + +# ============================================================= +type GroupByAverage = ref object of GroupByFunction + sum: float = 0 + count: int = 0 + +proc constructGroupByAverage[T: GroupByAverage](self: T): T = + result = self.constructGroupByFunction() + result.value = some(newPY_Object(0.0)) + +proc newGroupByAverage(): GroupByAverage = + result = GroupByAverage().constructGroupByAverage() + +method update(self: GroupByAverage, value: Option[PY_ObjectND]) = + var unSupportedTypes = @[K_DATE, K_TIME, K_DATETIME, K_STRING] + if value.isNone() or value.get().kind in unSupportedTypes: + raise newException(ValueError, &"Average of {value.get().kind} doesn't make sense.") + var v: float + if value.get().kind == K_INT: + v = float PY_Int(value.get()).value + else: + v = PY_Float(value.get()).value + self.sum += v + self.count += 1 + self.value = some(newPY_Object(self.sum / float self.count)) +# ============================================================= + +# ============================================================= +type GroupByStandardDeviation = ref object of GroupByFunction + count: int = 0 + mean: float = 0.0 + c: float = 0.0 +#[ + Uses J.P. Welfords (1962) algorithm. + For details see https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm +]# +proc constructGroupByStandardDeviation[T: GroupByStandardDeviation](self: T): T = + result = self.constructGroupByFunction() + result.value = some(newPY_Object(0.0)) + +proc newGroupByStandardDeviation(): GroupByStandardDeviation = + result = GroupByStandardDeviation().constructGroupByStandardDeviation() + +method value*(self: GroupByStandardDeviation): Option[PY_ObjectND] = + if self.count <= 1: + return some(newPY_Object(0.0)) + var variance = self.c / float(self.count - 1) + return some(newPY_Object(pow(variance, (1/2)))) + +method update(self: GroupByStandardDeviation, value: Option[PY_ObjectND]) = + var unSupportedTypes = @[K_DATE, K_TIME, K_DATETIME, K_STRING] + if value.isNone() or value.get().kind in unSupportedTypes: + raise newException(ValueError, &"Std.dev. of {value.get().kind} doesn't make sense.") + self.count += 1 + var v: float + if value.get().kind == K_INT: + v = float(PY_Int(value.get()).value) + else: + v = PY_Float(value.get()).value + var dt: float = v - self.mean + self.mean += dt / float(self.count) + self.c += dt * (v - self.mean) +# ============================================================= + +# ============================================================= +const PARITY_TABLE = { + K_NONETYPE: 0, + K_BOOLEAN: 1, + K_INT: 2, + K_FLOAT: 2, + K_TIME: 3, + K_DATE: 4, + K_DATETIME: 5, +}.toTable() + +type Histogram = ref object of GroupByFunction + hist*: OrderedTable[PY_ObjectND, int] = initOrderedTable[PY_ObjectND, int]() + +proc constructHistogram[T: Histogram](self: T): T = + result = self.constructGroupByFunction() + +proc newHistogram(): Histogram = + result = Histogram().constructHistogram() + +proc cmpNonText(this, other: (PY_ObjectND, int)): int = + let typ = system.cmp[int](PARITY_TABLE[this[0].kind], PARITY_TABLE[other[0].kind]) + if typ == 0: + let val = system.cmp[float](this[0].toFloat(), other[0].toFloat()) + if val == 0: + return system.cmp[int](this[1], other[1]) + return val + return typ + +proc cmpText(this, other: (PY_String, int)): int = + let val = system.cmp[string](this[0].value, other[0].value) + if val == 0: + return system.cmp[int](this[1], other[1]) + return val + +method sortedHistogram(self: Histogram): seq[(PY_ObjectND, int)] {.base.} = + var text: seq[(PY_String, int)] = @[] + var nonText: seq[(PY_ObjectND, int)] = @[] + + for (k, v) in self.hist.pairs: + if k.kind == K_STRING: + text.add((PY_String(k), v)) + else: + nonText.add((k, v)) + + nonText.sort(cmpNonText) + text.sort(cmpText) + + var res: seq[(PY_ObjectND, int)] = @[] + for (v, c) in nonText: + res.add((v, c)) + for (v, c) in text: + res.add((v, c)) + return res + +method update(self: Histogram, value: Option[PY_ObjectND]) = + var v = value.get() + if self.hist.hasKey(v): + self.hist[v] += 1 + else: + self.hist[v] = 1 +# ============================================================= + +# ============================================================= +type GroupByMedian = ref object of Histogram + +proc constructGroupByMedian[T: GroupByMedian](self: T): T = + result = self.constructHistogram() + +proc newGroupByMedian(): GroupByMedian = + result = GroupByMedian().constructGroupByMedian() + +template castToFloat(n: PY_ObjectND): float = + if n.kind == K_INT: + float PY_Int(n).value + else: + PY_Float(n).value + +method value*(self: GroupByMedian): Option[PY_ObjectND] = + var keys = len(self.hist) + if keys == 1: + for k in self.hist.keys(): + return some(k) + elif keys mod 2 == 0: + var + A: PY_ObjectND + B: PY_ObjectND + total: int = 0 + counts: seq[int] = collect: + for v in self.hist.values: + v + midpoint: float = sum(counts) / 2 + for (k, v) in self.sortedHistogram(): + total += v + A = B + B = k + if float(total) > midpoint: + var s = @[K_INT, K_FLOAT] + if A.kind notin s or B.kind notin s: + raise newException(ValueError, "Can't find median of non numbers.") + return some(newPY_Object((castToFloat(A) + castToFloat(B)) / 2.0)) + else: + var + counts: seq[int] = collect: + for v in self.hist.values: + v + midpoint: float = sum(counts) / 2 + total = 0 + for (k, v) in self.sortedHistogram(): + total += v + if float(total) > midpoint: + return some(k) +# ============================================================= + +# ============================================================= +type GroupByMode = ref object of Histogram + +proc constructGroupByMode[T: GroupByMode](self: T): T = + result = self.constructHistogram() + +proc newGroupByMode(): GroupByMode = + result = GroupByMode().constructGroupByMode() + +proc cmpNonText(this, other: (int, PY_ObjectND)): int = + let cnt = system.cmp[int](this[0], other[0]) + if cnt == 0: + let typ = system.cmp[int](PARITY_TABLE[this[1].kind], PARITY_TABLE[other[1].kind]) + if typ == 0: + return system.cmp[float](this[1].toFloat(), other[1].toFloat()) + return typ + return cnt + +proc cmpText(this, other: (int, PY_String)): int = + let cnt = system.cmp[int](this[0], other[0]) + if cnt == 0: + return system.cmp[string](this[1].value, other[1].value) + return cnt + +proc sortedHistogramReversed(self: Histogram, order: SortOrder = SortOrder.Ascending): seq[(int, PY_ObjectND)] = + var text: seq[(int, PY_String)] = @[] + var nonText: seq[(int, PY_ObjectND)] = @[] + + for (k, cnt) in self.hist.pairs: + if k.kind == K_STRING: + text.add((cnt, PY_String(k))) + else: + nonText.add((cnt, k)) + + nonText.sort(cmpNonText, order) + text.sort(cmpText, order) + + var res: seq[(int, PY_ObjectND)] = @[] + for (cnt, v) in nonText: + res.add((cnt, v)) + for (cnt, v) in text: + res.add((cnt, v)) + return res + +method value*(self: GroupByMode): Option[PY_ObjectND] = + var hist = self.sortedHistogramReversed(SortOrder.Descending) + var (_, mostFreq) = hist[0] + return some(mostFreq) +# ============================================================= + + +# ============================================================= +# ============================================================= +# ============================================================= + +proc getGroupByFunction(acc: Accumulator): GroupByFunction = + case acc: + of Accumulator.Max: + return newGroupbyMax() + of Accumulator.Min: + return newGroupbyMin() + of Accumulator.Sum: + return newGroupBySum() + of Accumulator.Product: + return newGroupByProduct() + of Accumulator.First: + return newGroupByFirst() + of Accumulator.Last: + return newGroupByLast() + of Accumulator.Count: + return newGroupByCount() + of Accumulator.CountUnique: + return newGroupByCountUnique() + of Accumulator.Average: + return newGroupByAverage() + of Accumulator.StandardDeviation: + return newGroupByStandardDeviation() + of Accumulator.Median: + return newGroupByMedian() + of Accumulator.Mode: + return newGroupByMode() + else: + raise newException(ValueError, &"unknown accumulator - {acc}") + +proc getPages(indices: seq[seq[PY_ObjectND]], columnIndex: int): seq[nimpy.PyObject] = + let + m = modules() + tabliteConfig = m.tablite.modules.config.classes.Config + pageSize = tabliteConfig.PAGE_SIZE.to(int) + pageCount = int ceil(len(indices) / pageSize) + wpid = tabliteConfig.pid.to(string) + tablitDir = m.builtins.toStr(tabliteConfig.workdir) + workdir = &"{tablitDir}/{wpid}" + + var + s: seq[PY_ObjectND] = newSeq[PY_ObjectND](pageSize) + pages: seq[nimpy.PyObject] = newSeqOfCap[nimpy.PyObject](pageCount) + ix = 0 + + proc save(values: seq[PY_ObjectND], pageLen: int) = + let pid = m.tablite.modules.base.classes.SimplePageClass.next_id(workdir).to(string) + var ndarr = newNDArray(values[0 ..< pageLen]) + ndarr.save(workdir, pid) + var page = newPyPage(ndarr, workdir, pid) + pages.add(page) + + for arr in indices: + s[ix] = arr[columnIndex] + inc ix + if ix == pageSize: # create page + save(s, ix) + ix = 0 + if ix > 0: + save(s, ix) + + return pages + +proc getPages(values: seq[PY_ObjectND]): seq[nimpy.PyObject] = + let + m = modules() + tabliteConfig = m.tablite.modules.config.classes.Config + pageSize = tabliteConfig.PAGE_SIZE.to(int) + pageCount = int ceil(len(values) / pageSize) + wpid = tabliteConfig.pid.to(string) + tablitDir = m.builtins.toStr(tabliteConfig.workdir) + workdir = &"{tablitDir}/{wpid}" + + var + s: seq[PY_ObjectND] = newSeq[PY_ObjectND](pageSize) + pages: seq[nimpy.PyObject] = newSeqOfCap[nimpy.PyObject](pageCount) + ix = 0 + + proc save(values: seq[PY_ObjectND], pageLen: int) = + let pid = m.tablite.modules.base.classes.SimplePageClass.next_id(workdir).to(string) + var ndarr = newNDArray(values[0 ..< pageLen]) + ndarr.save(workdir, pid) + var page = newPyPage(ndarr, workdir, pid) + pages.add(page) + + for v in values: + s[ix] = v + inc ix + if ix == pageSize: # create page + save(s, ix) + ix = 0 + if ix > 0: + save(s, ix) + + return pages + +iterator pageZipper[T](iters: OrderedTable[string, seq[T]]): seq[T] = + var allIters = newSeq[iterator(): T]() + + proc makeIterable(iterable: seq[T]): auto = + return iterator(): auto = + for v in iterable: + yield v + + var res: seq[T] = @[] + var finished = false + + for it in iters.values: + let i = makeIterable(it) + + allIters.add(i) + + res.add(i()) + finished = finished or finished(i) + + while not finished: + yield res + + res = newSeqOfCap[T](allIters.len) + + for i in allIters: + res.add(i()) + finished = finished or finished(i) + +iterator iteratePages(paths: seq[string]): seq[PY_ObjectND] = + let pages = collect: (for p in paths: readNumpy(p)) + + var allIters = newSeq[iterator(): PY_ObjectND]() + var res: seq[PY_ObjectND] = @[] + var finished = false + + proc makeIterable(page: BaseNDArray): auto = + return iterator(): auto = + for v in page.iterateObjectPage: + yield v + + for pg in pages: + let i = makeIterable(pg) + + allIters.add(i) + + res.add(i()) + finished = finished or finished(i) + + while not finished: + yield res + + res = newSeqOfCap[PY_ObjectND](allIters.len) + + for i in allIters: + res.add(i()) + finished = finished or finished(i) + +proc groupby*(T: nimpy.PyObject, keys: seq[string], functions: seq[(string, Accumulator)], tqdm: nimpy.PyObject = modules().tqdm.classes.TqdmClass): nimpy.PyObject = + let + m = modules() + tabliteBase = m.tablite.modules.base + tabliteConfig = m.tablite.modules.config.classes.Config + pid = tabliteConfig.pid.to(string) + workDir = m.builtins.toStr(tabliteConfig.workdir) + pidDir = &"{workDir}/{pid}" + + if (len(keys) + len(functions)) == 0: + raise newException(ValueError, "No keys or functions?") + + var unique_keys: seq[string] = @[] + for k in keys: + if k notin unique_keys: + unique_keys.add(k) + else: + raise newException(ValueError, "duplicate keys found across rows and columns.") + + # only keys will produce unique values for each key group. + if len(keys) > 0 and len(functions) == 0: + var indexes: seq[seq[PY_ObjectND]] = collect: + for a in T.index(keys).keys(): + a + + var newTable = m.tablite.classes.TableClass!() + for (i, k) in enumerate(keys): + var pages = getPages(indexes, i) + var column = tabliteBase.classes.ColumnClass!(pidDir) + for p in pages: + discard column.pages.append(p) + newTable[keys[i]] = column + return newTable + + # grouping is required... + # 1. Aggregate data. + var columnNames: seq[string] = keys + for (cn, acc) in functions: + if cn notin columnNames: + columnNames.add(cn) + + # var relevantT = T.slice(columnNames) + var columnsPaths: OrderedTable[string, seq[string]] = collect(initOrderedTable()): + for cn in columnNames: + {cn: tabliteBase.collectPages(T[cn])} + + for cn in columnNames: + var pages: seq[string] = tabliteBase.collectPages(T[cn]) + columnsPaths[cn] = pages + + var aggregationFuncs = initOrderedTable[seq[PY_ObjectND], seq[(string, GroupByFunction)]]() + for pagesZipped in pageZipper(columnsPaths): + for row in iteratePages(pagesZipped): + var d = collect(initOrderedTable()): + for (i, v) in enumerate(row): + {columnNames[i]: v} + var key: seq[PY_ObjectND] = collect: + for k in keys: + d[k] + var aggFuncs: seq[(string, GroupByFunction)] + if aggregationFuncs.hasKey(key): + aggFuncs = aggregationFuncs[key] + else: + aggregationFuncs[key] = collect: + for (cn, acc) in functions: + (cn, getGroupByFunction(acc)) + aggFuncs = aggregationFuncs[key] + for (cn, fun) in aggFuncs: + fun.update(some(d[cn])) + + var keysFuncCols = keys + for (cn, acc) in functions: + keysFuncCols.add(cn) + var cols: seq[seq[PY_ObjectND]] = collect: + for _ in keysFuncCols: + newSeq[PY_ObjectND]() + for (keyTup, funcs) in aggregationFuncs.pairs: + for (ix, keyVal) in enumerate(keyTup): + cols[ix].add(keyVal) + var start: int = len(keys) + for (ix, p) in enumerate(start, funcs): + var f: GroupByFunction = p[1] + cols[ix].add(f.value.get()) + + var newNames: seq[string] = keys + for (cn, f) in functions: + newNames.add(&"{f}({cn})") + var newTable = m.tablite.classes.TableClass!() + + for (cn, data) in zip(newNames, cols): + var column = tabliteBase.classes.ColumnClass!(pidDir) + var pages = getPages(data) + for p in pages: + discard column.pages.append(p) + newTable[cn] = column + + return newTable + +# when appType != "lib": +# modules().tablite.modules.config.classes.Config.PAGE_SIZE = 1 +# let columns = modules().builtins.classes.DictClass!() + + + # columns["A"] = @[nimValueToPy(nil), nimValueToPy(2), nimValueToPy(2), nimValueToPy(4), nimValueToPy(nil)] + # columns["B"] = @[nimValueToPy(2), nimValueToPy(3), nimValueToPy(4), nimValueToPy(7), nimValueToPy(6)] + + # columns["a"] = @[0, 1, 2, 3, 4, 0, 1, 2, 3, 4] + # columns["b"] = @[0, 1, 2, 3, 4, 0, 1, 2, 3, 4] + # columns["c"] = @[0, 1, 2, 3, 4, 0, 1, 2, 3, 4] + # columns["d"] = @[0, 1, 2, 3, 4, 0, 1, 2, 3, 4] + # columns["e"] = @[0, 1, 2, 3, 4, 0, 1, 2, 3, 4] + # columns["f"] = @[1, 4, 5, 10, 13, 1, 4, 7, 10, 13] + # columns["g"] = @[0, 1, 8, 27, 64, 0, 1, 8, 27, 64] + + # columns["a"] = @[1, 1, 1, 1, 1, 1] + # columns["b"] = @[-2, -1, 0, 1, 2, 3] + + # let table = modules().tablite.classes.TableClass!(columns = columns) + + # discard table.show() + + # var r = table.groupby(keys = @["A"], functions = @[]) # None, 2, 4 + # var r = table.groupby(keys = @["A", "B"], functions = @[]) # just like original + # var r = table.groupby(keys = @["A", "B"], functions = @[("A", Accumulator.Min)]) # Min(A) None, 2, 2, 4, None + # var r = table.groupby(keys = @["A", "B"], functions = @[("A", Accumulator.Max)]) # Max(A) None, 2, 2, 4, None + # var r = table.groupby(keys = @["A"], functions = @[("B", Accumulator.Sum)]) # 8, 7, 7 + # var r = table.groupby(keys = @["A"], functions = @[("B", Accumulator.Product)]) # 12, 12, 7 + # var r = table.groupby(keys = @["A"], functions = @[("B", Accumulator.First)]) # 2, 3, 7 + # var r = table.groupby(keys = @["A"], functions = @[("B", Accumulator.Last)]) # 6, 4, 7 + # var r = table.groupby(keys = @["A"], functions = @[("B", Accumulator.Count)]) # 2, 2, 1 + # var r = table.groupby(keys = @["A"], functions = @[("B", Accumulator.CountUnique)]) # 2, 2, 1 + # var r = table.groupby(keys = @["A"], functions = @[("B", Accumulator.Average)]) # 4, 3.5, 7 + # var r = table.groupby(keys = @["A"], functions = @[("B", Accumulator.StandardDeviation)]) # 2.8284, 0.7071, 0.0 + # var r = table.groupby(keys = @["A"], functions = @[("B", Accumulator.Median)]) # 4, 3.5, 7 + # var r = table.groupby(keys = @["A"], functions = @[("B", Accumulator.Mode)]) # 6, 4, 7 + + # var r = table.groupby(keys = @["a", "b"], functions = @[ + # ("f", Accumulator.Max), + # ("f", Accumulator.Min), + # ("f", Accumulator.Sum), + # ("f", Accumulator.Product), + # ("f", Accumulator.First), + # ("f", Accumulator.Last), + # ("f", Accumulator.Count), + # ("f", Accumulator.CountUnique), + # ("f", Accumulator.Average), + # ("f", Accumulator.StandardDeviation), + # ("a", Accumulator.StandardDeviation), + # ("f", Accumulator.Median), + # ("f", Accumulator.Mode), + # ("g", Accumulator.Median), + # ]) + + # var r = table.groupby(keys = @["a"], functions = @[("b", Accumulator.Max)]) + # discard r.show() diff --git a/master/nimlite/funcs/imputation.nim b/master/nimlite/funcs/imputation.nim index 35e41975..ca12d314 100644 --- a/master/nimlite/funcs/imputation.nim +++ b/master/nimlite/funcs/imputation.nim @@ -25,14 +25,14 @@ proc uniqueColumnValues(pagePaths: seq[string]): seq[PY_ObjectND] = uniqueVals.add(v) result = uniqueVals -method toFloat(self: PY_ObjectND): float {.base, inline.} = implement("PY_ObjectND.`toFloat` must be implemented by inheriting class: " & $self.kind) -method toFloat(self: PY_NoneType): float = -Inf -method toFloat(self: PY_Boolean): float = float(self.value) -method toFloat(self: PY_Int): float = float(self.value) -method toFloat(self: PY_Float): float = self.value -method toFloat(self: PY_Date): float = self.value.toTime().toUnixFloat() -method toFloat(self: PY_Time): float = self.value.duration2Seconds() -method toFloat(self: PY_DateTime): float = self.value.toTime().toUnixFloat() +method toFloat*(self: PY_ObjectND): float {.base, inline.} = implement("PY_ObjectND.`toFloat` must be implemented by inheriting class: " & $self.kind) +method toFloat*(self: PY_NoneType): float = -Inf +method toFloat*(self: PY_Boolean): float = float(self.value) +method toFloat*(self: PY_Int): float = float(self.value) +method toFloat*(self: PY_Float): float = self.value +method toFloat*(self: PY_Date): float = self.value.toTime().toUnixFloat() +method toFloat*(self: PY_Time): float = self.value.duration2Seconds() +method toFloat*(self: PY_DateTime): float = self.value.toTime().toUnixFloat() proc cmpNonText(this, other: PY_ObjectND): int = let r = system.cmp[int](PARITY_TABLE[this.kind], PARITY_TABLE[other.kind]) diff --git a/master/nimlite/libnimlite.nim b/master/nimlite/libnimlite.nim index 937b562c..aeb34d91 100644 --- a/master/nimlite/libnimlite.nim +++ b/master/nimlite/libnimlite.nim @@ -152,3 +152,12 @@ when isLib: raise newException(ValueError, "unrecognized type.") return nearestNeighbourImputation(T, sources, miss, targets, tqdm) # -------- IMPUTATION ----------- + + # -------- GROUPBY ----------- + import funcs/groupby as gb + proc groupby(T: nimpy.PyObject, keys: seq[string], functions: seq[(string, string)], tqdm: nimpy.PyObject): nimpy.PyObject {. exportpy .} = + var funcs = collect: + for (cn, fn) in functions: + (cn, str2Accumulator(fn)) + return gb.groupby(T, keys, funcs, tqdm) + # -------- GROUPBY ----------- \ No newline at end of file diff --git a/master/nimlite/libnimlite.pyi b/master/nimlite/libnimlite.pyi index f251eb2d..eaee6a06 100644 --- a/master/nimlite/libnimlite.pyi +++ b/master/nimlite/libnimlite.pyi @@ -33,6 +33,8 @@ def collect_text_reader_page_info_task(task_info, task): def nearest_neighbour(T, sources, missing, targets, tqdm): pass +def groupby(T, keys, functions, tqdm): + pass def filter(table, expressions, type, tqdm): pass diff --git a/master/nimlite/libnimlite.so b/master/nimlite/libnimlite.so index f142d22ae508e1e64d328c173c5ce85a59cce2e9..2c4e3dfe082fe89288ee0fd0279c68451b4b729d 100644 GIT binary patch literal 1428312 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�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%0A5@BepQ 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

    cJjG;861$Up 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

    eq*NpCB;|9MA?ykA*_^! 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

  • 3R^*m+c!1iHzKAb^bK`7FkCS^P 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

    T|@I% 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 qG{%~&uQ(b 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

    hrm2=@^TVK_U@}QB)L}U`O++<=@1}!(eyg_`- zla~n%wW%IkOxG8Kjx|t{81p&Kt~qqfv0vhSzxC+%$;#!x)Q@qb`>M~@(Mhv5fk z7lm`;V-eNMkdL)t=a(HHJIXNw7Ek>26*?V* zkF_MeOK^jaCF7PSYn|W*A4?@ZPjG{e4JSTb;X!?OW#hv7Y%RA6xoa+48Xo9~gY>o2xY+yKCrw z#mD;NYMO9#jK@E@bEy839;p@kViXro$1dm-mX3WDLdUj_IVU>SvE=fcO@OD0=;IhhnJWd(w^|NAg3gO;1G#+-qVT{)JCIO?%CIc9w^k4!I$k1hHf z{a!)YsSQ3hKt-$GTSqhbn3v(dgRSvg^<(Mf%aD(K%uWpusB^Yg4hTxF1bnOsB9;Xo z`wip5sZUAx*et~SZ}ej?J*VZhVGoCowO`C~o`H`YJ6#n!Ep-GLx48x1;a8Ns@=D2U zad$^ReqMykf(3-~5F{Y)Wokb5ze&2Vk0E|3_}Kdr{U%12Nu@8c=PGxXe7jVA3*}?p z6_Ag4^^|;=eC#>m(*-y9*fioff*XA74dO!tH~82r;#~zd_}E9d@7-!8xWUI35qAr2 z@UdTrS5tTpAM+DGT9@U=`xlNq@i6gif*XA71o2gZ8+va=~I)BHO0*q>ikoR z`*z!Zte^HDgS!~qIWpbp^c?F)53f{==vcvLRu4&i?9`i<7 zU{Dmdv7sz6y~QfHm9tpDQ(mpO(6Th5*I1Ox$BRWNRk2zVm0iINhp}pK1xSj9M@h5j z+!P*-ffKx=5j!aq>=~aP_fOr4feQ*YgJso+g(mS=IE$&Da2A``)udqbfm6<6n~^!v z_OLy&cSio{x^jip6&JVC4PTtbqD$s9c7wsfI?`#ZpLQBMAF>*x6x%;avoO>Pc?Ijl zKANB_HVdUD2~$3MO0u=j*dr*mH5;Z#|6u$Uuwx~TZfx_g)+aDCs*{s%0XrKWb`?@G z{l=-Q{TbjoryJP-9n7Ivl?-`#c<{uR(dbA^UZanbGEB zW8_0Se!%%Bka^LMjf)TFW2)_4<9x!$q9WBDc<|F>`stEP1e1}e(>)LT>3;}r_>Xxc z+~zx+aHsJ&fxls}|5%)an~QKZAN!8sW(D!F=Z@(`2F=XjSwjD@CC}(aX>VjQ44K%= zO+U5BSiMJwEH^hj8pOw3^D`ltrbp>Tus91JyPb|;4U}BQG!|$4IO=`XtLbPaA3MRzc~VsPLiWmLj;EO6Ki0xQofBB~t(_2$GV>p6z|Jqs$C{PM zf2>o$ed{}pkw9`K;A5{KVo`hy2QUMt%Xko=u>V;1Kmoc06`&-1td1%{Vf`48kaxP0 zaL%s;7v)9e>LEMzc888Foy&IO!T|R%xh;<_WTyAnpd%$`W7D73c{Npe?N;7notT%f zF%geMNJ9`CtBXqxI48Iz{=SYZA6CSYodKvP(LZ2xnNl{+(QIt(cwMyT)VElFZ%`If z-+ThMJ6Ue^&d}j-8 z@Uh0kCkt-yvChOZ6&}RLdJrEVxWUKnA)Y3Ij}0eYS8#)mjm8Zh~{@xzrxOn~8pSPLhEa1r>svlc2>sK{!0Q`f3i}235LtEqJTosVsXzbbv3gK!NR_$K=3@ut z!vcQrB{X|Bz{mcA`u?o-V>>08bS5K{=j8XmpF;fk7fLvf5pD>>h3Lm7Nw_$Kv-wyG z!v*WdB5})|6n8gYN>BrV#SOzUP?+*#$V}a&V_wAkZ}ej$pVWd}`BsOJHF<~S zJcEAh2Y<;K*?E%L%WWM2S@t$E3lQpnc1nkG=Pv$;Ya_UABB|+a!aJU3!7$WAE@(9avM@^D)=* zl=QHK%=RDa{g{@5uD4*Q7Ej3TNe)ZMLjA|C?0-&#tSJAn-v)&cvd``-hLDXLt*i9} z${dc6DgUu7m?e$#m7OoN`B?4UHXnNe*xBgGLiyP5aKO-ftg1ROc^2m@uRT>Y!tzmB zg4r^dkG+IXT2J;6-S>r$b$*xfW0gH0^NiAsmz0FYv(Kb*Uh69#8uEiLVMb1XkKK1k zSUwisf2@-vv$Hjlk*Tx78-@8;BMGLSJF6%@R^=V^d)Epfd~6p- zy07{W9nIupTQG%LZWaFA_}B`FMVaxj@*G2D$H(Szj0BP^0UvuD5zB&)T^Ce4O8Dj`Ph~>As-bY3-PgEi0_NgxWUK##J>~V z;A4l0FBRP2V<(8u6x`rrk;EqmZt$@x#4`jp_*gvg-U<)WkF_M8BDlfFl8HAJ+~8xW z#A^v|@UhOsPZw}KS?xFYSP$M_ze{j~kKIFjo#2X(9lWtvK4!rpQv!P>&2X?+Ui5~^ z$1>rMVcRP`zOL?mG}77kUF?KG2nIg=+_+dR^@UmdK*QxB27qX7)TMb!`{V66m0!=({F#;-Wyc^|$^zq|q=0hTUWw2b<+kw!CFT8;1p z3NSHgM!P25!rJ5g^6ECr_-H$un)jI2R(7!9TgUiXLGUdO`l*(h!}U0SvxPIx^QR%D zfGyRRc>lk+0UOzYPpI>OFsT-CyFWn1a$KwYlc$3d6FKE8b& zy?_2rR)H18RR!wN&Cmp~xB?iBh06xxH^oY>8V_3j;g5gEsdW4&|kPf;dE zkG2QHLh7rK>haYY%LxtSISF>JlnuC1WLT7|ahf|FzjTUrHJxKGHH^DPQUPIU1w zUA3C58it;_h*F!I)t#U)H>;m8H^<#z66h!-pv=vSm^kh1R^K~=jLdk8BkcW?xS6Ez zZAGr}YncuvTi#4!8kuL^9m1S*alTw?Lz8;Y1(Rt*b#+ouRuX5H5>>}^Or=~#Ppds1 z0NC3lU|(Pp#7dlK#<7F)!!`2Z?*z6@LOS*ulnmU*kpQy;ZXQbxWHI7xxrbw|+Gwh# zZ6whbBD&4%w!H{mccEh+h4f4th-7`${~W*{XhzDnDz++lGf!TpC~Hb(R9TZOWrX~i z(&LNiGtwkk+HbL9>K48Dp}&jjfeXXk!|~{K_EIOtpk}bd1TA&09^ittP~2Aov$vSSaeplVQW>7%L!*`CPFHy7+w;r*B*t?O0z zLVT}8_RtO6&`4#af2*kmM_aiLETDgD=2_(2rx6wWf?$w2QaQOe_Rv)vQl;U0d5CE6 zy=C_6fxJ%x_RtLT^*||re(ANgz8Zutz48_AHbDg_3E!)tN^lPLP?TH9ng%KYl z0@b={wpUByZ;NO7O3e1EOY~P6UD)2r=QZ0~G*~yoSoJN|kIMjp?4kZ?kdIoJ#zM|- z9wvVJn8pphcY^pX!419_`7-czf*X9V3h{Y@8+@+@)0r-~!T0JA&k@|ipqZ@V!+Q_}-wEm#s!NfJAfVh-q0HYV@WrDU{3E|D zdY54J)h{9|U(9-pBEAJt@ekIU1dD!-zTWT}(C=G=Srxu-`O(pe(|YpDGd071^6NW7 zsO<;07fo%4_t$McxC!#M8!*uLc3Z zvvC(l5_^02*Fn5CoIN@`uWb^tsCVxivZy!j2wpo9qi>*kA1?h~-Ov$6{oHxrvkMeI zO~sUMO;#=s`n-km?@LkgRIjNnxyfhiF#G`RA`yE2wM2Y2V$+ENWjFRL3i~j2R7viK zYs0}1AfV^WXOE(-oa9QNzg&Wd2A>VO9}ax>to{2Q2`WHI_-sp6g8zWeHtMIx(ZYrf zpWT+rb}F9F=6+RjKD)H9&MQsjrTJ_+^9ts(cVnan@!3}GHJ?q9_|*`<6nr*OqOZS# zS?bfTho9Db*55}L?IZOq)(?$a5TC7s>D@}fycNP{>l1G(xWQ)|6R#z>!Dr)%pWds} zG5Bmt;=2Sl_-yjCz}E?G@Yz)2^8`2e>~P}K6&}QAM-$Hx+~Bigi4PIn;Im%hT?IGz z>~q9h32yM&X+Gd?!3{q92JvcwD?WP-S~%36+>JNHsq1?Caiu-`>w3E*IMkQDCYW^g zQ_vmTy-%H}Kp?Eha^J4&sOF=4aA_TxCRRuB&+Edx!T4gi0Fn?;(}>ka1%rx1?%R!Z z`5H5%5)go~TB)KmWBpTdvB>gOT%_EIL7<+ruM^oW?50C+J=WT~F%H3Qs4L@e#!D+% z{1w_P^%L5xI~oYP>AyMfCJP?w8~2kc0B%XJX28{j!jeyS>&l+IX1IPm+WK+=E4N0w zVB<^_3Np&ma&u-yHUsRQtTtX~z;C_j#~!n|0MhKs+#GGC<4k~EZe9k-w&S`x6$>TF z)FhNBu%2^;&qV+{igJi0!Y!|{>e69#F>$PG!jD*N@k+5LI}@)pWT2^$G5wG8GToz6 zU2{%b8(}ijU$MewbmJ2{Q62KG?l5luld17r_nt5BFMS@F+H!kF^VGbIpK)XBv5ftm zybJj2gR}%k3k+q<*Kzaj^F|l+=1XpTfr% z3daHC%U+n-72~>0E1XD;%xirQAeQ|C7g8DqQm^&CQ*d1!+U`jxf>V7@p#KgaH9WxE zFWR~injuv*^#U8rmSE!uz zDsdZf<|^Tzrs?+s@QAZMfvFTdIncTPHbY5N^$Gp$I*ExE&DMf3vQ>;#=XC$=+A`!f z{n>}X{Du)PL?htk$d9CA}IhHb7U zX3W!6)?qkk{XFM-t2LyE=Ni=2<(N}(mNe-QW-e^zioYEC=WaT)p(-;yZl7mn7`GJM z@$$VRzL$c%jFIRrMi=(N_Y#2xm9^xg6MI~+ zBd`M?Tqd#m1>2#qTw+}W`$}VziM1ANfyQQx1J*>aH#Ih!SZ%>3YHTyH@(Nq+Z?Cak z#13K(p)s_%#tsqNDp&)JohFtqm`h_(EX&7&or3le^{B;guM4(EV{ydt1p5}4^&uK5 zh=4>SpDX{^nr-rr#4*M3kBNIV|F|Eg-Gcc?S1zGv#XqiONQ_}Dq`~|Hu4ta33CK1f zAnUs5k-F+)442{w$i|Dp6Ob*c1SBArCWj*+sX46O*%OeKf7=A);GYx^O`J>uQd2fvR!9OYZQxWncjF;}9EeKG(3RhWOgcUU*#s8aKfcRTAbGNX>s+?;Ec%ZI1= z0r&r7<0SlJHPrp&ANPmlAAjGB@CN_biRMA+BYN9w^;4e64KT0C`OZm_O&qeZ`A5oQ zA^fAkPXH>L{9~*lUW$O1mVaC*Ilmptob^2Z&ADIx#3(9FIh zx|Qwd=+wObf`5F~Q43M83myJ3W+aAEG5ljWq^MNK31jf8;E2SjBKMoN)1-T_A{NpsSJ%S1U zhtC`SMBo9I7qv1c&< zCBk)xgr^^d{sS%=Ve}u5myCWaK48<2^gk#ah3UuC z+jOBPp+w>6M``sRN03~Aew3~LqZWkB<{v%w*!<)5`IJ4S{{U7B{l||s|CoYvi`0MM z2woxmhhMj1>G?;aRNdZjHH`Kq|7a~AD)R&MA9n`$M_ZULiqU`Efba(YNJsOa^eej9 zYjrUHh?Z<-L_7Rr)$kDh@#e-t`VYlk&xwD$=+QO5LglRa$8E?tNdGYkv&tvmtDZFY z$7Cf@)vE>Sdc0!%=EZ@UY&y#{fB(-i0`G~A7doCi_vZU z2a@NU&_uo+sm5=P{$m8j{KQR^k^uimC-%5tw*G_I{es#04`N*ev-KauS_@|DKQe$d z5zN+q5UVYit^XiaUSR?Lv5VM2%ps=!gVKej$>@{h!!#qp1cKZ*V$E|hNWg;ZIOH{YO;{Y@2^<`Pt?lmwu?mJM|yXWR#qL z9E693w!g&GQT*fIU4{9_d+?cO6PBKTyqlzl$c!kXu{qZ)mk&?#1N0x*`q=spSQp4Y z2BXFG&t0b&fJ!$X{x2jV0b+_47a`?wW#wf31oD=_; zF{cdq$8V~01DXe4O8Mw>(SIPd((sSFRlGncO0EAevoDeU10DSz=|2*+5cR6)@Q*PA zF_Z!`bYc6;a!66B_(vbf>_7!)++M)UZ2gCP{~F&*!9NOG>AFm0bX)&%pXfj2+au~* ztp9$|f56Yma(ziD3Gk0Y#7;qOP5lS4J%ZW#kB5MLE10eSAhujETmQkh?+a$@KZv~~ zn63XHmaVV={}@1Qm|(X4gIF)YZ2bqZc7oaZ4`SB|X6rv51a_%lw*G@yRl&6W1C3P1 z{G-KzT)_YYm?|daG71O{-Z4HFUwM*idKNw_LsC9^-xMFhoMwF{pcJSo_;v? zm)9GHV;^fDC>i@$wB2SO!?sX73bT)wTFUrGiNdjuQrcfOz%=KukFvDCR4NZ4v-w94 z9KR6%kGCj$O8)_@Ji;YmK|KeiR-A6IVHjaYjA(Wr%P?zjk}xye6T%ZJMR z0R2ZwfPb`wb)jhc%Sifr81!Qjng^v%YGbd}A^H!=W=6sB0RLFkFNA-*xdK3t{UuUy z*K^_@FE-aTze44#_mQ_DXT?9n{_?>6m{mSmrFzofAM=zzRiAK+T_=Zsv|x;E72}-v zN8CGQ$UpkC56e#ffz(RFKenTx%R>L5k|~Y;Lq+^g^dAX&C~ZB>L99JK3+`r{+dIU< z{G&Cbs8syp=j(N5Lse#a+&<6Dgnx{e?;Y{I6#Qe1M0YW|t^YvsoD-VJwpzI)38wWQXrwacA6xG+ z`A6d2#qp1cYeoNobKb%Hqvu)cKbi&UKX5vKt>c4w#MLt`*U*7z1qimN@~GV&(ShX0 z>*2cm1jbD9BxK$3;{``tmz?y3c%klcbRdH-4aY*-+*>jha^3ef3-NCVNU6G27C9*= zsv1)2DlByv9mol`Od&6j4QJAU+y(=k!$SI+I*>b{1955pMq3ZkxNk z_so$GBl*FXaOkE0AEAAMeB|!%`j8vyac*?(+A2LbnLw@RmS?2PkswWLT zay_~p=dxM_s^svIcNycz5;ZrUi$Ch5*UFHOe5*P)pous~eaJYZRvJFiLB$J{qO|&u zN@n&Y(TA)@NB=kakR?sE5Vb$*@R4D6YSF|&0Swc^d}I!!s8oEUqh$8;5og?%V`jof zjy2JRS%&YW;3Eem`WQwRKJsQ)%|~95Z~fJ`SU;{ph8!e*2frw*05WRuk)6c$3MPDH zAF&?=6FzdB*yn-?AE^M}F>8)s!bhSR?iIm=kJKghv|z$VJj5PRSb&doCDvCk(TDUS z)?P5-BSVNa7fkrbC}Ir+6F!pD6PQad;UklXom!w~3cunbm!Oe~V1wunatK%@zP|k6fR_+Lvtb&{V?0K2)LFzEfDwyd! z+4zQKF{5W1PVLH3n2z8eHJ$rRY#vg?d6+?-u;*cpp@9SXkkr*S|Cl&gjYaT}5x`2$ zKl*-R^N(=_D84UdC{6~3`h#4QuiJ1C(#IJyVW^P)Zv!_i*Ed6?LSy0xPZ8m&$K zagBWV`vAvJLi%+9{z3Z$`Ntse503j$7+G+P!icf{n1}Lc?xC)#dGk+3j`{f7Q0}45 zA2FK<$z~F=vH8cm+k^QBf~~w0Be1SYJ>NeX98=ydnmW7}BqvfpzF;U^_J4}54!8S<0sRObhnfz3~fyWhqLXa z5_%fg%s%M6Om}ObnSDw4$yF*M#!UJ09&h=47_IJZcr|8dOuM1yWr}$0qp0&T7rXVC zdVjCOPrgahqKkP7!?iF!aY4#F<(sNUn@VzAW=;d0S8J7*rq%tKm(Y{Dp{#d*gB2fM=V;fg&G@v2e8wa4oHD!YU~MOmSB@KHjCJI3R~?@(by-%RtVNiV=IZx z7Obwu))Sj5Sd_-L5X%wls6HC=duL$jg6-7UVPd@nTgT%vW6(%J^du=F`7Yzc%omum z$xxEK8SdU#vRe zP*`$s?NSkUZuH?@cya<1LAv8wj(#`++U2;%+v$)e`($O;_}&PTyl^#%Nq$@VvW$$g)A=VlaGCU8K!yibV>c;Hj6YMGJf#3K7+&~aZ{9KHsGh$f<1?m*6 zH2H#JM=ZlbT?n3|&goj$;!B$E@+P=dQI*1b(Bqru&XOQ3N}BgqmShGYlx{A5@}Ffb zLvAR^AOxU1p#a|#%5M|P7U7!CE;iQmXW7AHS8;sU2V9f;e$F0lu_nbmMyYddxl~Ky zjeEHMF4b2@Lal?N&^wtbjzX)SUZv2&EpTYqHNWdc(HNX#Jb5)hEM3bo-D9k3$%)9OZ7P7lOw)zy{LS*4LU>q>j9UXypCfTRO* z!I8QFA21@BkL!?((%!RA6fAe48kiL17&pF;a#v?>g&i8n04)kW0zl0aR_&VWMwVFJHc?pPZ z`5N5fruumE558EN(F1DJ8(H3dJQGs0f)`G5wV+5ihobfMWc%ys27lsrhk?9tD~Acr zQ+sg%j*ebfqIC2RCHl=q^m`FKdExi0tRrckmRFK?Gr9$mw&eg@?V#20O-$yvmz0uH zF03Hs>|*az&>&R%FiP9TAH&G&8IcCYofN;7a;i2r0DEEJg_lI(>|MK*p< zYB9v!%at<ED$pSh~=^ zsosa2=;1F>{rX}Zy$_<>EAUf@p1hEE7&#~Et8b4~{o6-%bHa&zmC+Xmt#)s4?4$Md z>y+p+tQB1_d-X>JK998S2S-)=&6L~>ch96;hV9Rf3^J}qLDJJGmM^if2X#^czXP9)rRgG64}B7CHGc*HgO{cU zvEEn^oi_M+9P7^aoJ*XgiPr@3O2Tf&()1jHBrlBQ@N@)bLWFA2>gANcyt`Km%(2(m zU}(lv?5g!1X103;n#-#DKYqbXAZtRJ@2V)9uvzzr2j2^QTsFYqi2ks!y9q4hxWhR?vOd-P&(XSacdN$a1G?KBR-|( zb&E~)R>EJ;{6Xow6{6PF9cTStE~|py0AYM@%4q4yy9G`D@FCn}cW3ny9?#+?Fd%M2ufeT8mc)C=@k4QS4+Dj>pI{c}`Xrhx?M!Cej; z24iq1qDX3R_oNia;8s@_hu(Fjmw@+c>(;vo<+N4neUsP)`bG{!Eo${hm#7g&R5zpg zVqyv^LnhX<%^aB+^$*)F;T1~@gMYf-3LL&rmtfJ)>_4!Kl06ytheUsj(PfwMT5Fy3 zi}J0H`WEZ=Vq|K&L{ip2T{F(Zf8$)^7C}I?xyidvEl5xtMRaJn+cmBen7e-lDEK7e zEdWROKR;Gb5Rdat{qxlq!;0dUI z$OQBv=at|Ih(y4gfbKw$>aA~=*QLvQ z96lR*Onrz?MtS|arM#7l@)k3u?uyYpx@Vg+rmEh|F?A>8aT5v6Etp8}m8yy`*QMCh zm8i3gs2dU~h*g3o*e0h`{bs9bCbUx}JZMdM8TISPjzL-*YUyJ1Q)y|}gB+yg?}$l^ z0p>`6)<%H-41h<1RDfqCz$Hd$ZeajEh|3fnk^ui~b!u9l0a7^!+T;e;?|}FCVX!K& zK1Nc|0~z}_?WQ+WPrc-qydy z<4Zb*c`(lP^ru*Z)XL$*EhLN4QrxI`cc{DJ`JZ&_tHUUSWiQ; zE#$>O=C-gRgUAenxtc?S?@VJ$1rL2SKwdtH;ygK(M5Y!=nmf(|!kuJ&Rd5>P5f@Ui zB^ssW4RAN}j6LN7O>;f{4JLpNz9$M&vkR)ap00yb<2N1qy!?o}JPRB5p7_~cje1X1 zZR}aJ@RD(RJ1@MXqGy@l<@$welsNdRH` zU+c4z1#8+yRrF-9LOW!-W34uAFi_E8-ZhqWl1(yMZBwy{tffpEu5p*LkH@a!2sJj5 z&b!;m*hJW!zx-FU_KGJ|=lc`w<-RXMqw0sWN|*I<+fLO?2j-Hs8t>} zWg3gTYy5GHOM5AA?kwd9X6}dTQ69HPYmt9wGuIM^z(PK$C5Dyzqjr$~!ye1uUSOoinBqfqtGy+N@8kuFu#&eHI{uS)bZxSDz>-M(qA6ras9o z-8UP5aO(3{bF)4kvp%=|rMkvKwX9_@koX_KVjSeZagkG>b~_Qu^SJ7t8aLVfvj-t` zeFm!jX$Qg4{j)3MvQ$ii=%5U@VueuKTG&wm(!9SKrht$-h1-8WbiS_BKi?bke^8xv znss^<(v3yA66^HG3#gM%(diT=;3q)8?fx0L10hB|Mh5>kH4?sx7puAqp?SK3+qz15 znXOb>yv`^psU>uNkbS7~B2Z0C^%Wa&iPRcny`3G77wNI3U6T|Y;i`e^ac|rSSZ2kz zR4?|ldAPO>dWcRKa4H^v)JR;;rZ=lt7^qGNBGbsw7`BQ`_pGj)3;F-J}= zS=DsSqJwI7Eeht}2w4l*sk%!z>sw)zL=pl^$W_rye+zK&fcrt(pW|qM{&J(J9H%N3B!=RMp4>}E#UHai1SePB%cO=&QiGWk_Y-{4gQN#m z&&ldV9Aj}igrZ<^V>!9F;rc&4UoxRal2DwP5I?k*50zD66SA<&5OpB7celXWfbHGY z)KN5w$f#&VLeNz~^^v)9c3J&{V92-gV`DTzS;)IQd4n49Nrvi5OGKb}C7@MBx7{eT zkiCVQey!dCO_p!H@y+9Hq^@$du0gqt-r{;^@X@jR8eu}GJt4T)Vu9*jYr%rDAu37g z|88KfEp?|;Dz)yI_bO6%tbU^Vsv?Ntz&MDXsUH4V&0dge5Z1gFLGA*_Zz7?AHYWRc zIoJ3#Y#d+Ww_x0Amb7||P}BV_H`u$DRjXVAuP!YweKkQ<*K07pP`b} z7jr!|%)W%Tx3Dk=t)n!XaMY5m$8S`!gS5=O(np?-VU? z+?U^{-QX~7}erIPOA z_Fsq04lqqjc5{pyvqKy#0+<~r4?)y7Nsay}Nh-og3Yr>@Z2rTf!~AMWpsnjAzmjP; zs!8w}hMh=CHK4?{pcJ1Jqq#5&oX#9tww z>L=v$!F5_b)A}Du^Uia7@EL}4*SH6Zme1}jO!?f(I{Ol@t6|IMqT%eq#Q(e2me20^ zWM~y$_(9}zZUzFLSyL4$m3a;&wWqmHuA(>vG*yL#Q_J<53c97b8BrfY)Ivi0Jyekp z+A-7+`w|X+uZ6ZX^~7O>HV$1)VYPoY!{64uzd~psta=*`d7YrXQ;zwa8r&6VDp%tP z>L=xQ1OB#dJgnumpVs3-c+&%N`?cC;w-K@3fX%)V+a8YCChM8x%yN6VBe&2=gcICk z*Nbb~s|P5$W}_w*$Np|_Nk>+YE$LAFq9ixPHGeOqS4-|)8Iau1hOyfd^6%bTe$>b94&cQ6b+K3gmfBWV04B^YO^sKI(xazzaYsS=9^pz6?? znh40F($xGNUM!M^LoD7O!v0Ul!0#d!YiEjB%=n}TvA`k*olWFN{JbhoEoEHB{2kOD zhE0R&C$0pY*JG@m$|EtosjYR}jZYo&MktLNB)R@ZF@xR=8Kv@#u2I)wn|r8(?@8f9 zx2g@Wo;D{IR8bdTUz*P`rZo_48gU~R7-O7qsgw)d!gZCXyVfwOzPmaKQMLZXR@q_i z)*rJA##p2pY6)XDpi!}OuZNYxe~r?`gm-^J>w8Hc_EcEMm-;gS`_uHLq)YO;iYeM zJ%7(eJ^zPY9IA5yWLnp6%mb=^?_O!w?-EtNC+vRgf!L~kk3MAAuOOqPqj1XLh4>`5 zTH~b6WS{+`D|rV>%6)mP+LvcsQK*{XbW!6Za*V2`ZlRWlTBtiG|E8*Wt?Jc;xnJvQ z{tIQVKb0Hoz<$S9nrz zy{mM6qg8!#a0EAG_=crBi7=+;|Q?)T!{ zAp+yK4mKCMe_tP@>KhfP@6GsRjNj2nM%DL440K)JV&!ztDP7f(pF5>}#a*bP;pB9- zL~W&_YB}wPsD&!J1R6tBw1?{7gdf-Gie5ez6@_(x*WOL?jJksT#rk}RPfmUQ_)_Xqxv2WY6|PU06T0DlTcPU{r|MJj%=Ia!>Z8Rh zP@hETL(x0BlM;HMKs39*14XC5DGgh!&mxd~U7yzXss3qnxm};1@hLU050${E-9>hN znnMoRKXgqLbLQh}8MOUB^fH)xTvsI)Rk0_7R`r8AhE$95h-sVhV%f5XS)d{rKdXvd zp!z3a(pp`SpJ6faA3kKvkKDaQOTIIfJNd4PL&)uFVa(#X$Z*!*x_qOIaMKy?ccqmK zgj*ruIvL?I7_OUAL4?s*|-WARosHH7>0x7-z@LgRV=nr{{_}37POHh z7-uHP53S`xWmSWOEbJG^>GF^R^nY+`$XSswKA_&zCExppD*5C%qvW+w@>1ygj{l__ zFc-~aZ-i%Gq8nkRd>evq+V2ipKUYqq>fsa+>qjUve_{YxjZykQeUrPYH^95V6iqdP z?nCqklxaT(^<#H@wOYOh#yv1{C1c82Qa zp?g(h-Ouy^U}T`5{m`Q_qvs?eFEeubIq(clXO`cW_~m`Jy|mdqoDLFB0DvM1<`k>r zG$Qb}vfuvx=Mqr#Tx5_8M?`3hFj81iW297Ms$*BNarj=hQWc{;S)d3h&~a2NP#}J& zEgyF9gD;_b1kF*aWSm?CR|UGf-GZ)BwdN)_7frx_)>2Egz7+fYu0dEKEP z-kNc~auZcZ@fzYaM|9tGLqY5j^H^QooDa*l8(B#EF(GP^auyi;+qwzHLjPMFH-`~$ zlA$kF{tJVkBExoV27b`VO6l%BX&4**(qNZrkvqYif|g6lo$YR`+STxJ=#Dt?D{|Jl z#wEd;Pp@XzW$Nr)3?Ie8_OqUxoy&3G%1%Ilui?l;o!|{$U<;MkA2)_Qh=B?XKeu5y zJ)#i`jX4PoiN-~fHFZNr3Byxdns)$<{rIh!zX#GX^o8_?Z_mzrY zA3+D_Ivm!8Z@b7Cvh00k0MFVtRq}R8?RHRy2j!J`v^I{*N{R6Y3^pRl><7+i(8s{*xr@%F0?=FTxsuU zY44m#Xz#40O6W111|pPeevXp;Tk=!TBlyQtaO;%tRR0%FI(Ys{j=+ikMU_iY!>*Wi z;N*h$Fkv#tsF0fXgvt(vq7`VS^7skVQyyq`{$*Arw1;xVCymXE7?|wMs06Q*3*oID z3GT-v;%N59w0~g@n1Io+uy^-^h$*yuB?N+fq!PSvhgGaoKMLb?0M0Bd8XfmY#>0b$ z-tX~xqQx6LHg51xxcYnQDoZ+ZsHbw+m2pLCUNw02XI^-fxP$Ab)RU~^Xns!CsVX0R z+$`DK)hr>a6Niq}CDbpmqlC*imWZmD$@wUuecsHInq3Lw1xET?4@KSP8*&_@2Ieol z9u)Pe`TYHQK-~I?Gaw$g5Ob+_M1=o)7^x>=gw4XdYkdbF1hl_DQ>_I_6pxRHFBN=% zgD)aJQ}8qgUrBs|;MY6&TH+akQ$D5NH`WE-Tkr}FzJ)m7A7Od+DQcSpH@A2&XPC={ zm#WdlIm~}PZS4VjerooCNV;@RxE&EOh(F;Ve!2$z+D81w$z~h>vl}zcHrtY>W}KNw z%ARp3D>KyJ6Ws6Or}a`U3zKgxuX(b-Kb|&wG|!)Vb=&^&u`#vsLzR8{!$yAaCENyf zkA5R%bYWd56~K-8a8r|I4hxu?jFAx~Wfq5kFXkxRdz^>jthZn~@pnNq)egNNRn{H2 zT#tz%7Zh%X_1Q&SOkLxTTELxU<+=N;QjLXwBe9#YOl5|F~#R z5^Snq*69d5K#bH=p3*Dvx|P8z@gTJlPpqMnij!zuiJA05B+Ds)YjQf*VY2D(C^XBpK)Osk2X*Rt*2ZKucz3$v9hR#BfG+IimbX@bI0p$ z-i+ZQYs(s}EmbhreT20or#r1P{u*|D#~B+hid;Kd#_Ud>rI|8gm1?<{HVPp_vCi z+$tZ==LcWH)8O|a!f7}rMyY!kqnaYzj*Uw~&r;zZMfiXc{Fu&k0y7@1&f<5B)Y+Vp zj#g8ywmFR|vT$l$;2P6s8AciLX zVRUCyO2sV*>_5|y1k@$lkAp5a7?FLVvQ~7wzDKw1HFMZ~+F`m&G?#@2cCW=BLtj}p z|8NpBj?ruNzCFq1p4=W4-3h+}d%5@c>SfVAt3N1dzTH!A#oUYZ1YImM+N65X{=%nUP71+W_f3K;DB;tS0_j69ceiSN!d5Lsu);xaBxA;NOS; z;OpF4__`HlSPV~pf6NKMmJ-Vsj2r^k7sNgm>?VzUN9=XM8ft7Ou{^yrrg4(dGoQ-jK(2?e%CgY92=}y1reW%mMRb!o-@C-AT zc^(otXr*tWG~+jW4!->x@R?mGsKcKWzrZO%n`eccS7>11mZ;8)8f@kO{Ij;3oHi+ zUxVR$(~E*0mzogpJpNV=fBdhvyPy7}Jf=A{Gjb!$v(+jHCI$==?1?bF`9{^Z{_IR< zwp8(hac;x$){&T*^xW2n?Q{i( z7EX2=(&UFjOqP=0TM_r{PVftr^;7VM6`pNDm}~YZWxy_EUqDD z^GvjhH^ZK8arr?|m2e-3Q-l^QLKm|LdXMJqYDed6&DM=mdo0e@fi2p_9NU2<#kmng zlikouL5g@pxA5^&J#|*vsYUt4cbtWJaTG>~VhG;4=zk$g<@I?|Ax&fem_hos=bRW@ zA}aU74xMEKmE~?_1Zc-BLFIylHXbq>G<`&W1+I;O94kree49;!>8oTeIyEHv8;maN z#p=phY5D2Tx^&~zw^;wFzm%x`a6Uw*zA_T8^2;?IzY?o1Sf-w7KO}ZS8CQcuvLQRs z7vtI+B9c!s{91$;k&M7dDZNN0e@aea_2{StlZkm2g%!-G{G=!)lPTUoICg$Uq4aPB zKZhO*2$7O-EtJa|#tnRU*h6s*A@FOG;D)5qJI}5QC9UZ7tvX;w~vl7gw)abLho+FqkJWGt}0(1T( zbO(97VpHlCC`W}-6s3A(n;wDXXF98E!-^u3N=P7*k}&r#6h#U}d5QPco23$p(_3}6 z6Bu2DVr~U36z|HnboDLP-&C2L5`U?R!CQ?k7SNmAtm(&oV#fz*>`INDAht`e3pG{| zOPaMouyPu!L2RjDaMR)%SC4Vu73?RCH6}J$ur(UHlUNooQmdyl_RD#|h6uKmS_@y| zeq!AP%hy;-#!V6IRvou3v1Wo@tFg|+>I!y=#_lE-CD?fy8_Yb8!c~D~IR@vfU)hb(bz^}nSx!cvF%lW4HB%f#{9&( z3U=ggRlWqqO%iN}#v+L|73?dGT}3Qbum!*rf2+lh*pyNg!GH1lO=XOK=mC7jiV`qB z_z%(w1!Rz|OYk3qp1?Zz#__@SKlID}NsX#d@;hw$1NoNP1QSWK0<2-L#1 z|Gw@hELa@J{`=N?HbYFu@?6kt`|n6S{6|Z=!zz}II0yW5KCHHKJ4HJ*nfYmV%mRU*`UTKy#05`I@JRA zp=EPC`){iE^qtCS z0D37bcxSNx=1wop{QCx!u+%O^S1`@L592Bc^^~sGi!;DC4{D`z^Y=Kh21ZauT3TKs zXnvkd!8SBHek<(>R-0$AgT$$5eOlzs__gZfw(^$mwETa_k$1~@vR4m+r|nC)go>WF zyrOeW2PlGKJ3!4Gtu~AgL?bAMMJ`5^7?d%Sa)DPQ8&ZI2GuXm>7pB>OKAZaC64{sE z`&Rea|GmP&^Pkz5A9>l7MOqw`a`-Im%N400;wB_SOS^xQF8uFEJs=WgZ(m+nQv33h zzcGiv3{YbG@@#k#QzP)*2~OGPW?!E45N4)0&P*@f%v7mHTMCSu%QM@TPj1vr`^+>n ztuVk3PvUn+jdpuqTa7ki7K?i}_T^J3fORaL!IgdarU)>I3zJ#CGuoH$mm>W=)tO!{ zW|6|$mz$l9eR9RJ`|{gKrLiwZKnTLv zmlxq~C@Nda%)UGoBcgQn9j z%bkh$7TmBe_aL4kxM5$uhj>%L4g2z7;^$+*)e)%_R1o zwl5z!%-=)o%Rl4fc94Df(|M+S`AD#Rc_)wQn)c;yc%9Ehn7Xjnv+YD6sb^_ zoU48Le2kqi_T_xIxs|zn`TsCfoV-e6Umm1l+S=K|n$fejFAvPu+sNTQIRNPbF4fFvq@}9A7&7^6=HVy(4{2 zr(b@6Z69J^j)w3C8Hf(f4R2rG1xg|EN{d1vzm=lw%X?Pol8l_-^!zLP)w~jHsv7@s zNJ;KPN$mY=uzlIO4X14VHL<^idHIPLKkdu3$HU&73VZX!<%Yevwcp`+McA7U7HeVLTS)h%z}pu1(J8^CqM^F#K`?6u(=V zoUCoglk=4-qCI<_mC0EZp*D*^Q*)q7JM)YxmB8fu zF5>-XCg&GcYME-_r5x$Cy4@elkWOi5G&%2U%4ssZ$+;+#TAXA#H`kd^zVlP0Lru=h z=7h-ctFyJq`K=YY)DIyEMcztga(+#sU(M)J={ryYQnTCS+Xd=dtlzIq&i!$}kM$wu z3R;oG`cx@IdnxV(}R1`I7dF_?PgX~z*0!Uo17yiy2-SEAQpbP)=^|3INg zkZ=Fw@LR0Ejy5?D-H*XL?KU-dZIkn3#Ks9GCg<_Qh6^Sp=jVytEtr^`Un7<(n3$a3 z!yP?Vf?#5Do=>d4U}AFql~{FP6i_ia4<&XYRbyguevH`ff{Dp_4Y7@aiOG2ru}=jP zlk?BSW(g)H=Rb%|5ll?Z$KY6EjTKBx&gB^HA;HAttjgCzFflo+^0gIAOwQvOH(oF? zIp0C79x%h?+=o~d!NlbJ0L${1M~4%W^P|Lm5ll?ZpAlOxn3$ZOCALT~F*z?L_Lg7) zlXH#YP0qawnVh#3GCBW7lXGS^OwLQk^X_D7WFm~$Z&pxfaa$eM_6!)4acN)@@4{@x z(}yima?{)$2Y8I*h6@oK@AJKncS%}R8F_(r-K&oKDin`Oaff-IuWx6uH@$?`f{}WZ zIxEWqzERrvgXzZmqkSs&qg^}>tUS-#ztQ@d*8vaGT@bL}mhL^efoRphdvuGs*=C7?%PASWw)uBp zxS#>5tG3R$x9~T<+`CB6GJj@qcG5R3T=*-}&$q9{OI&cPz5Uugi7gqkrZ!MjOP7A;^Q-Va7q+f2j-`yNbP@=C$k-YPh&DOSdo@Rh~ceRY>*t zYK>)LNQk$i;z2}E#tqR&T*GkG1zm(+JUH($!Oh6{qf;DjAfi?97?SKq6XXWb8y_}YdHEHGLL>fT7~Pc|6iO|W5v2j#dbg8tkmc2I9{-K3qHo< z3Lq;JJ_!)9fc)|iqmJC7{fpN)(8m)#w~Ow)3wRup6z=O*s}{Wv>E%n7fimMgFKRE! zN9t3O^!)`2k6;_M{epgHc*2B`DxGFb9@i* zdyItCyh=K;@VhZN!pYsCbArno>v~{P}2Lb<#xQgOu)31G$$NnD#99d^?*S$fYb2Pw2bfB%mSFN8;GM)G z1e5mw@4=Nn);>%J%tPJ-e2Ca)!DLrAjo2!Mt@fvBAHuhY%@wSL#y%o8T`;%CJ|&hb zShU9063Y>xUk3SCzvJvhs4qZ`wo~j42=|ee{Z+sJH-dDbbRQQohBdpXKQhM z=ys3hLk*wBI6EUBdSh!@@}UMjk>`KKhq|F*1AJ&!2b&Katg9w#EJoi0J4-%v2Nvsr z13lKSBON|eX-g0vdMDB5L+hVrOUBH{vA$3~bnor>^#3P5v=Q`$4YT%9OasO9p&v#R z%ZHwRM^S^FZ`ss9=)gozI6l;J2O9EBeCYZ+3iF|z)WU?}Ls3$Z2v(#7d}srx3>)AJ z)GiQ@^Xne@Foz#}35^z`Uf40yU+|$*?`h%5d)VPabH1e@sSQh5K6DV$QyM-rQS!P}<)!&h8|Ec^sJVPE7|#6R zic>Oeb2Ev)g3*N!?b@XI(6(8+nlsh603V8hdybXVJctj~C)QLj;X_vuixo`xP)lMJ z1QR~gme?VPKDm$Zq0YFf-r6RZ@S(ejeIc0eAuq9c3JdU|Da2+7CVc1(ViN=tKJ)>x zQGy8{T10GsV8Vx1ZwA&$FyTWRiM0|;@u9wGq)wYg;Aw^VOCq!;ptA@&5u;pj^^^}FBI z{XK6Ohm!qj$`2bT{_T|NK_)QMrX!g{c%#=t?)fPd8GzR{Ou-pY!=X z-|zEmV;jH!Ugde7_xF6y_k7R!oXz)>6lbN0 zFf8~YS3HTbkGSe+VcYB$gOEY5A?@H#Pr6(cz^!J zHaKwQpeG`sdw8|5gDpJ`3l4g^S7{8XX(@ix2R&zFCzt%2h<`z}BwfGVGx2wn1b5*Q zBGN=gBwfCp6j(Hi$1kz`f(m^q1DP_1CcoUH{7u_z#GKy^XL6i3iQTk0pTSQBIU&Fm zinNbKI_SBp=5mM%OS7J(p?n?mq`YgMZ}UJ|8{pOb3l0q)0#7Nt>u4M`TG%%CAL~Vl zurO>|%gn+nJvo2jW$~!mVV-P1zIwBcSMp4pjfd*fv{z85yQ8uuo)fS@X~RI ztMbhvuG%f1XQ#&`;vEs5fK7L{yXiiuajekfvb&j< z2*O2-$>B?AfSBXx_z>cEkc=nuK#CFbk4Fp{IgX4}IcDy_WjD^YbjTui`I{p-gQVFr z_G9sMgWQmKI+4(JVnTALl!lfoGx4uTEl>36kS9`G!B-8meK3mjO!f}?#USr{>e1(6wrmNk34)*nk)G4#Z0(+O=hFew*9_SqCb#%skS^Gxfdbt)A>#a2;pUhU4T~ zTR=QHQ*7Rga-6--2yc2!EXw&?Ur(dIJ>xgvHPOQO7Y$GqYXn=nWDi5b;I5*FZ6mMnKLW+}zhyQG@k-gpnoEg`+&6Gn!3_W_BFheePUx)?kV= z1lMb8+aUSGHcv;}ddA)f0+4)Z#@Cm>!HII8QF|Bd+3XZ9COe zlF_zLkCRGjKOS{)Myys+G*3vdRoC&)T&im?f7q%k+TRX~d(~g6lLi7&c^nVXglKHX zRNBf?IIj&&6A!u`F)jV6(5t#?clmrlR* zXDFk9PXCxcJa@5QBN?hlq>1B;zdmx1c)uSkvTNe@no<)f5+

    ~~xeb=Dydb|I=W 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 z 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 V35aVuqRfM`K(f-EjC&L||(_S2_aY2ZW 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%0An#Py0U*X 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 v?L5Q6Fpcn{W?{vq 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%0Atv9m*LF)UFaQMOo%T9-_WON;kwjXe1Gr%gzHL>x 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 Jgoj^0{vYr!^nd^W diff --git a/master/nimlite/numpy.nim b/master/nimlite/numpy.nim index 4f153805..532f000a 100644 --- a/master/nimlite/numpy.nim +++ b/master/nimlite/numpy.nim @@ -1,4 +1,4 @@ -import std/[os, unicode, strutils, sugar, times, tables, enumerate, sequtils, paths, hashes] +import std/[os, unicode, strutils, sugar, times, tables, enumerate, sequtils, paths, hashes, strformat] from std/macros import bindSym from std/typetraits import name from std/math import ceil @@ -1429,6 +1429,22 @@ method toHash(self: PY_String): Hash = hash((self.kind, self.value)) proc hash*(self: PY_ObjectND): Hash = self.toHash() proc hash*(self: seq[PY_ObjectND]): Hash = hash(self, 0, self.high) +proc slice*(table: nimpy.PyObject, columnNames: openArray[string]): nimpy.PyObject = + let + m = modules() + tabliteBase = m.tablite.modules.base + tabliteConf = m.tablite.modules.config.classes.Config + pid: string = tabliteConf.pid.to(string) + workDir: string = m.toStr(tabliteConf.workdir) + pidDir: string = &"{workDir}/{pid}" + + var t = m.tablite.classes.TableClass!() + for name in columnNames: + var c = tabliteBase.classes.ColumnClass!(pidDir) + for p in table[name].pages: + discard c.pages.append(p) + t[name] = c + return t proc index*(table: nimpy.PyObject, columnNames: openArray[string]): TableIndices = var d = initOrderedTable[seq[PY_ObjectND], seq[int]]() diff --git a/master/objects.inv b/master/objects.inv index 11f4cff2d768d8e1468a99dc5eac616562523ad4..6d942a2e4f154d740e7a4c1763467d8e9a81d3aa 100644 GIT binary patch delta 3740 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 Gcq`z8T~AH` delta 4049 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 DIf~a_ diff --git a/master/reference/base/index.html b/master/reference/base/index.html index ad7c8e92..3b3e2119 100644 --- a/master/reference/base/index.html +++ b/master/reference/base/index.html @@ -473,8 +473,6 @@ - - @@ -1829,27 +1827,6 @@ - - - - - - - - - - -
  • diff --git a/master/reference/config/index.html b/master/reference/config/index.html index dc533948..66cb0258 100644 --- a/master/reference/config/index.html +++ b/master/reference/config/index.html @@ -473,8 +473,6 @@ - - @@ -971,27 +969,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/reference/core/index.html b/master/reference/core/index.html index 7a6af2b7..1eec60bc 100644 --- a/master/reference/core/index.html +++ b/master/reference/core/index.html @@ -473,8 +473,6 @@ - - @@ -1526,27 +1524,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • @@ -8275,7 +8252,7 @@
    https://github.com/root-11/tablite/blob/master/tests/test_groupby.py """ - return groupbys.groupby(self, keys, functions, tqdm=tqdm, pbar=pbar) + return _groupby(self, keys, functions, tqdm) diff --git a/master/reference/datasets/index.html b/master/reference/datasets/index.html index b5984cce..bbc322c9 100644 --- a/master/reference/datasets/index.html +++ b/master/reference/datasets/index.html @@ -473,8 +473,6 @@ - - @@ -791,27 +789,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/reference/datatypes/index.html b/master/reference/datatypes/index.html index 45daec42..c13e276a 100644 --- a/master/reference/datatypes/index.html +++ b/master/reference/datatypes/index.html @@ -473,8 +473,6 @@ - - @@ -1436,27 +1434,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/reference/diff/index.html b/master/reference/diff/index.html index f4edd8d6..9cf2f00b 100644 --- a/master/reference/diff/index.html +++ b/master/reference/diff/index.html @@ -473,8 +473,6 @@ - - @@ -791,27 +789,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/reference/export_utils/index.html b/master/reference/export_utils/index.html index 74c304df..1f25ef37 100644 --- a/master/reference/export_utils/index.html +++ b/master/reference/export_utils/index.html @@ -473,8 +473,6 @@ - - @@ -887,27 +885,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/reference/file_reader_utils/index.html b/master/reference/file_reader_utils/index.html index b5498c1d..068e1783 100644 --- a/master/reference/file_reader_utils/index.html +++ b/master/reference/file_reader_utils/index.html @@ -473,8 +473,6 @@ - - @@ -1001,27 +999,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/reference/groupby_utils/index.html b/master/reference/groupby_utils/index.html index aa224e97..3354ee59 100644 --- a/master/reference/groupby_utils/index.html +++ b/master/reference/groupby_utils/index.html @@ -16,7 +16,7 @@ - + @@ -473,8 +473,6 @@ - - @@ -749,26 +747,17 @@ - - -
  • - -
  • - - -  Max - - - - -
  • - + -  Min +  avg -
  • - -
  • - -
  • - - - Functions - - - -
  • - - - + + Import utils + -
  • - - - - - Groupbys - - - - -
  • 6LPvmzmJ)Wbh*? zQDx&tqV&Cz@FPF_w2YGSBMbF~lAM=$T+^0-A4$c9XR1dBMH8tMB@;7sIwz*^BZH7_ z;7Rcw$*hSEyRnB%Bjrc3h+VFj@*__WtF4&wBh!ghR80AiS;Y2tm;96;Sr7$mhhoZ) ztRS{dG37@rVhauC@FRZ`o1vKUBM~ekS25*BP9run_>ol+ zEuSiFoe161qRT2Eich#kE&^f@exFKeFI1beBJA0|KQ8KhnMtfyAQ#xDCXKsHA~JOo&tl6nTVx z`u_z#GGu!QKXT1h)V45wWPerZ?SYd$v#D5qq#3uTvDn*K_nNpH+~?j8tYrMiUM=(Z zlPFVFwHt8&*TawGJ?-p&UuHWM%a2@%(yeFd)h_(VL|BTgUmN4^;`xzoTAzNXkG+X| z>mch^27Y8oB}V`k}@hRCqqNbZl$Kzwh*s&Gc8AcT?QKhl{Agdh2& z3dYcEGr&v6k6d;YaxEu6(tAtp%4F1jD@6@KI*eb(1JbNG?v6)`z_ zx0j8W9p9DMZHg&BvXIyfiYY(x0kO*zQ-0(VVzm@geq`Gzz)n$2`H`Q9{eFv-tNh57 zeD@oJIsPL}iLF&k`H|Md-cd~Xk%i3fMa7gKxsmC{E2jL&%}h5`G37_P5W7pU5PoEN zn?m`KU2hiTM=%Aj^4#>ZcbUXW9L*9Uq=X=T#H3HWR?@rt$S)O;ep5TsI_2O;1|*ye zek490+ZBqcQpOWGa6;ZI2|u!0)4G?faaAfJp2!qC^Af}pxmVMcfFG&Pn_xtb4&q0C zK2g<#qpSZoCnnSioUTbjwl+V~F#;WSL8=UA;k^aPv+$`yo9~n;UC&iQ> z8BJ`XV#<$9%C7=@lw!({JWp(nV#<%KCpN`k4nOiGu}2kCeqhQ?bx=!;6_0UNWhL>%rtocXShc&n+5x0=d$tH!^;bQS)zRUH}8)9I>?af9?Gn7h@1 zWX0doRo9~**i}UkByAoG>MDmI$tli(WY;By6C?*yef2fwYLmI;q)1|V6r@P*9xdC6 za#18R;G5!XCyWb;|5o&vKWR3CrwBz-zb=8quK*}Tat>I36KY>BlIcd*eX~`@oCq01!nheD*eO@r;>ZeDgzf(tf=97maY3aI< zOcQ5-(Hw$U6O+#7HZ>N%TQ{1x9Nh1=0_NH87U4&-N(cl}7FkG1!~ zCnmA6aB-3ych!3j3+NNG(e+1HLFv|m^mZ4XvMdb_cMjN?tN6_5`lzCwXQHy4Ua}`4_Q^IDXau z^Nb(xB`IhFMojd!LrDD9L8I+)vUP%t6NfMHj+3DHtN#qgP-)!>$^Fp@xVtAjUs9I| zgfDsSFvikQGtiYUS%8lTe20HL@kh*9)QGA!S7PqL*%w8A+8# zdWKj4y@AHb;*jBR+oXwhjc}(}-1p!&t_=1knXBoRdeZ nwXZ#&+_{(sYxM&K4nG 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 z�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 z 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 literal 1329664 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

  • - -
  • - - -  Product - - + + +
  • - + + + - - -
  • - - -  First - - + + + + + - -
  • - -
  • - - -  Last - - - -
  • - -
  • - - -  Count - - - -
  • +
  • + -
  • - - -  CountUnique - - - + + Mp utils + -
  • - -
  • - - -  Average - - + + +
  • - + + + + + + + + + + + + + +
    +
    +
    + + + - - + - - - -
  • - - -  Histogram - - - - - -
  • - -
  • - - -  Median - - - - - -
  • - -
  • - - -  Mode - - - - - -
  • - -
  • - - -  GroupBy - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - - - -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - - -
  • - - - - - Import utils - - - - -
  • - - - - - - - - - - -
  • - - - - - Imputation - - - - -
  • - - - - - - - - - - -
  • - - - - - Joins - - - - -
  • - - - - - - - - - - -
  • - - - - - Lookup - - - - -
  • - - - - - - - - - - -
  • - - - - - Match - - - - -
  • - - - - - - - - - - -
  • - - - - - Merge - - - - -
  • - - - - - - - - - - -
  • - - - - - Mp utils - - - - -
  • - - - - - - - - - - -
  • - - - - - Nimlite - - - - -
  • - - - - - - - - - - -
  • - - - - - Pivots - - - - -
  • - - - - - - - - - - -
  • - - - - - Redux - - - - -
  • - - - - - - - - - - -
  • - - - - - Reindex - - - - -
  • - - - - - - - - - - -
  • - - - - - Sort utils - - - - -
  • - - - - - - - - - - -
  • - - - - - Sortation - - - - -
  • - - - - - - - - - - -
  • - - - - - Tools - - - - -
  • - - - - - - - - - - -
  • - - - - - Utils - - - - -
  • - - - - - - - - - - -
  • - - - - - Version - - - - -
  • - - - - - - - - - - - - - -
    -
    -
    - - - -
    -
    -
    - - - -
    -
    -
    - - - -
    -
    - - - - -

    Groupby utils

    - -
    - - - -

    - tablite.groupby_utils - - -

    - -
    - - - -
    - - - - - - -

    Classes

    - -
    - - - -

    - tablite.groupby_utils.GroupbyFunction - - -

    - - -
    -

    - Bases: object

    - - - -
    - - -
    - -
    - - - -

    - tablite.groupby_utils.Limit() - -

    - - -
    -

    - Bases: GroupbyFunction

    - - -
    - Source code in tablite/groupby_utils.py -
    10
    -11
    -12
    def __init__(self):
    -    self.value = None
    -    self.f = None
    -
    -
    - - - -
    - - - - - -
    Attributes
    - -
    - - - -
    - tablite.groupby_utils.Limit.value = None - - - instance-attribute - - -
    - - -
    -
    - -
    - -
    - - - -
    - tablite.groupby_utils.Limit.f = None - - - instance-attribute - - -
    - - -
    -
    - -
    - -
    Functions
    - - -
    - - - -
    - tablite.groupby_utils.Limit.update(value) - -
    - - -
    - -
    - Source code in tablite/groupby_utils.py -
    14
    -15
    -16
    -17
    -18
    -19
    -20
    def update(self, value):
    -    if value is None:
    -        pass
    -    elif self.value is None:
    -        self.value = value
    -    else:
    -        self.value = self.f((value, self.value))
    -
    -
    -
    - -
    - - - -
    - -
    - - -
    - -
    - - - -

    - tablite.groupby_utils.Max() - -

    - - -
    -

    - Bases: Limit

    - - -
    - Source code in tablite/groupby_utils.py -
    24
    -25
    -26
    def __init__(self):
    -    super().__init__()
    -    self.f = max
    -
    -
    - - - -
    - - - - - -
    Attributes
    - -
    - - - -
    - tablite.groupby_utils.Max.value = None - - - instance-attribute - - -
    - - -
    -
    - -
    - -
    - - - -
    - tablite.groupby_utils.Max.f = max - - - instance-attribute - - -
    - - -
    -
    - -
    - -
    Functions
    - - -
    - - - -
    - tablite.groupby_utils.Max.update(value) - -
    - - -
    - -
    - Source code in tablite/groupby_utils.py -
    14
    -15
    -16
    -17
    -18
    -19
    -20
    def update(self, value):
    -    if value is None:
    -        pass
    -    elif self.value is None:
    -        self.value = value
    -    else:
    -        self.value = self.f((value, self.value))
    -
    -
    -
    - -
    - - - -
    - -
    - - -
    - -
    - - - -

    - tablite.groupby_utils.Min() - -

    - - -
    -

    - Bases: Limit

    - - -
    - Source code in tablite/groupby_utils.py -
    30
    -31
    -32
    def __init__(self):
    -    super().__init__()
    -    self.f = min
    -
    -
    - - - -
    - - - - - -
    Attributes
    - -
    - - - -
    - tablite.groupby_utils.Min.value = None - - - instance-attribute - - -
    - - -
    -
    - -
    - -
    - - - -
    - tablite.groupby_utils.Min.f = min - - - instance-attribute - - -
    - - -
    -
    - -
    - -
    Functions
    - - -
    - - - -
    - tablite.groupby_utils.Min.update(value) - -
    - - -
    - -
    - Source code in tablite/groupby_utils.py -
    14
    -15
    -16
    -17
    -18
    -19
    -20
    def update(self, value):
    -    if value is None:
    -        pass
    -    elif self.value is None:
    -        self.value = value
    -    else:
    -        self.value = self.f((value, self.value))
    -
    -
    -
    - -
    - - - -
    - -
    - - -
    - -
    - - - -

    - tablite.groupby_utils.Sum() - -

    - - -
    -

    - Bases: GroupbyFunction

    - - -
    - Source code in tablite/groupby_utils.py -
    36
    -37
    def __init__(self):
    -    self.value = 0
    -
    -
    - - - -
    - - - - - -
    Attributes
    - -
    - - - -
    - tablite.groupby_utils.Sum.value = 0 - - - instance-attribute - - -
    - - -
    -
    - -
    - -
    Functions
    - - -
    - - - -
    - tablite.groupby_utils.Sum.update(value) - -
    - - -
    - -
    - Source code in tablite/groupby_utils.py -
    39
    -40
    -41
    -42
    def update(self, value):
    -    if isinstance(value, (type(None), date, time, datetime, str)):
    -        raise ValueError(f"Sum of {type(value)} doesn't make sense.")
    -    self.value += value
    -
    -
    -
    - -
    - - - -
    - -
    - - -
    - -
    - - - -

    - tablite.groupby_utils.Product() - -

    - - -
    -

    - Bases: GroupbyFunction

    - - -
    - Source code in tablite/groupby_utils.py -
    46
    -47
    def __init__(self) -> None:
    -    self.value = 1
    -
    -
    - - - -
    - - - - - -
    Attributes
    - -
    - - - -
    - tablite.groupby_utils.Product.value = 1 - - - instance-attribute - - -
    - - -
    -
    - -
    - -
    Functions
    - - -
    - - - -
    - tablite.groupby_utils.Product.update(value) - -
    - - -
    - -
    - Source code in tablite/groupby_utils.py -
    49
    -50
    def update(self, value):
    -    self.value *= value
    -
    -
    -
    - -
    - - - -
    - -
    - - -
    - -
    - - - -

    - tablite.groupby_utils.First() - -

    - - -
    -

    - Bases: GroupbyFunction

    - - -
    - Source code in tablite/groupby_utils.py -
    59
    -60
    def __init__(self):
    -    self.value = self.empty
    -
    -
    - - - -
    - - - - - -
    Attributes
    - -
    - - - -
    - tablite.groupby_utils.First.empty = (None) - - - class-attribute - instance-attribute - - -
    - - -
    -
    - -
    - -
    - - - -
    - tablite.groupby_utils.First.value = self.empty - - - instance-attribute - - -
    - - -
    -
    - -
    - -
    Functions
    - - -
    - - - -
    - tablite.groupby_utils.First.update(value) - -
    - - -
    - -
    - Source code in tablite/groupby_utils.py -
    62
    -63
    -64
    def update(self, value):
    -    if self.value is First.empty:
    -        self.value = value
    -
    -
    -
    - -
    - - - -
    - -
    - - -
    - -
    - - - -

    - tablite.groupby_utils.Last() - -

    - - -
    -

    - Bases: GroupbyFunction

    - - -
    - Source code in tablite/groupby_utils.py -
    68
    -69
    def __init__(self):
    -    self.value = None
    -
    -
    - - - -
    - - - - - -
    Attributes
    - -
    - - - -
    - tablite.groupby_utils.Last.value = None - - - instance-attribute - - -
    - - -
    -
    - -
    - -
    Functions
    - - -
    - - - -
    - tablite.groupby_utils.Last.update(value) - -
    - - -
    - -
    - Source code in tablite/groupby_utils.py -
    71
    -72
    def update(self, value):
    -    self.value = value
    -
    -
    -
    - -
    - - - -
    - -
    - - -
    - -
    - - - -

    - tablite.groupby_utils.Count() - -

    - - -
    -

    - Bases: GroupbyFunction

    - - -
    - Source code in tablite/groupby_utils.py -
    76
    -77
    def __init__(self):
    -    self.value = 0
    -
    -
    - - - -
    - - - - - -
    Attributes
    - -
    - - - -
    - tablite.groupby_utils.Count.value = 0 - - - instance-attribute - - -
    - - -
    -
    - -
    - -
    Functions
    - - -
    - - - -
    - tablite.groupby_utils.Count.update(value) - -
    - - -
    - -
    - Source code in tablite/groupby_utils.py -
    79
    -80
    def update(self, value):
    -    self.value += 1
    -
    -
    -
    - -
    - - - -
    - -
    - - -
    - -
    - - - -

    - tablite.groupby_utils.CountUnique() - -

    - - -
    -

    - Bases: GroupbyFunction

    - - -
    - Source code in tablite/groupby_utils.py -
    84
    -85
    -86
    def __init__(self):
    -    self.items = set()
    -    self.value = None
    -
    -
    - - - -
    - - - - - -
    Attributes
    - -
    - - - -
    - tablite.groupby_utils.CountUnique.items = set() - - - instance-attribute - - -
    - - -
    -
    - -
    - -
    - - - -
    - tablite.groupby_utils.CountUnique.value = None - - - instance-attribute - - -
    - - -
    -
    - -
    - -
    Functions
    - - -
    - - - -
    - tablite.groupby_utils.CountUnique.update(value) - -
    - - -
    - -
    - Source code in tablite/groupby_utils.py -
    88
    -89
    -90
    def update(self, value):
    -    self.items.add(value)
    -    self.value = len(self.items)
    -
    -
    -
    - -
    - - - -
    - -
    - - -
    - -
    - - - -

    - tablite.groupby_utils.Average() - -

    - - -
    -

    - Bases: GroupbyFunction

    - - -
    - Source code in tablite/groupby_utils.py -
    94
    -95
    -96
    -97
    def __init__(self):
    -    self.sum = 0
    -    self.count = 0
    -    self.value = 0
    -
    -
    - - - -
    - - - - - -
    Attributes
    - -
    - - - -
    - tablite.groupby_utils.Average.sum = 0 - - - instance-attribute - - -
    - - -
    -
    - -
    - -
    - - - -
    - tablite.groupby_utils.Average.count = 0 - - - instance-attribute - - -
    - - -
    -
    - -
    - -
    - - - -
    - tablite.groupby_utils.Average.value = 0 - - - instance-attribute - - -
    - - -
    -
    - -
    - -
    Functions
    - - -
    - - - -
    - tablite.groupby_utils.Average.update(value) - -
    - - -
    - -
    - Source code in tablite/groupby_utils.py -
     99
    -100
    -101
    -102
    -103
    -104
    -105
    def update(self, value):
    -    if isinstance(value, (date, time, datetime, str)):
    -        raise ValueError(f"Sum of {type(value)} doesn't make sense.")
    -    if value is not None:
    -        self.sum += value
    -        self.count += 1
    -        self.value = self.sum / self.count
    -
    -
    -
    - -
    - - - -
    - -
    - - -
    - -
    - - - -

    - tablite.groupby_utils.StandardDeviation() - -

    - - -
    -

    - Bases: GroupbyFunction

    - - -

    Uses J.P. Welfords (1962) algorithm. -For details see https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm

    - -
    - Source code in tablite/groupby_utils.py -
    114
    -115
    -116
    -117
    def __init__(self):
    -    self.count = 0
    -    self.mean = 0
    -    self.c = 0.0
    -
    -
    - - - -
    - - - - - -
    Attributes
    - -
    - - - -
    - tablite.groupby_utils.StandardDeviation.count = 0 - - - instance-attribute - - -
    - - -
    -
    - -
    - -
    - - - -
    - tablite.groupby_utils.StandardDeviation.mean = 0 - - - instance-attribute - - -
    - - -
    -
    - -
    - -
    - - - -
    - tablite.groupby_utils.StandardDeviation.c = 0.0 - - - instance-attribute - - -
    - - -
    -
    - -
    - -
    - - - -
    - tablite.groupby_utils.StandardDeviation.value - - - property - - -
    - - -
    -
    - -
    - -
    Functions
    - - -
    - - - -
    - tablite.groupby_utils.StandardDeviation.update(value) - -
    - - -
    - -
    - Source code in tablite/groupby_utils.py -
    119
    -120
    -121
    -122
    -123
    -124
    -125
    -126
    def update(self, value):
    -    if isinstance(value, (date, time, datetime, str)):
    -        raise ValueError(f"Std.dev. of {type(value)} doesn't make sense.")
    -    if value is not None:
    -        self.count += 1
    -        dt = value - self.mean
    -        self.mean += dt / self.count
    -        self.c += dt * (value - self.mean)
    -
    -
    -
    - -
    - - - -
    - -
    - - -
    - -
    - - - -

    - tablite.groupby_utils.Histogram() - -

    - - -
    -

    - Bases: GroupbyFunction

    - - -
    - Source code in tablite/groupby_utils.py -
    137
    -138
    def __init__(self):
    -    self.hist = defaultdict(int)
    -
    -
    - - - -
    - - - - - -
    Attributes
    - -
    - - - -
    - tablite.groupby_utils.Histogram.hist = defaultdict(int) +  median + + - - instance-attribute - - -
    - - -
    -
    - -
    - -
    Functions
    - - -
    - - - -
    - tablite.groupby_utils.Histogram.update(value) - -
    - - -
    - -
    - Source code in tablite/groupby_utils.py -
    140
    -141
    def update(self, value):
    -    self.hist[value] += 1
    -
    -
    -
    - -
    - - - -
    - -
    - - -
    - -
    - - - -

    - tablite.groupby_utils.Median() - -

    - - -
    -

    - Bases: Histogram

    - - -
    - Source code in tablite/groupby_utils.py -
    145
    -146
    def __init__(self):
    -    super().__init__()
    -
    -
    - + + +
  • + + +  mode + + - -
    - - - - - -
    Attributes
    - -
    - - - -
    - tablite.groupby_utils.Median.hist = defaultdict(int) +
  • + + + - - instance-attribute - - - - - -
    -
    - -
    - -
    - - - -
    - tablite.groupby_utils.Median.value + + + + - - property - - -
    - - -
    -
    - -
    - -
    Functions
    - - -
    - - - -
    - tablite.groupby_utils.Median.update(value) - -
    - - -
    - -
    - Source code in tablite/groupby_utils.py -
    140
    -141
    def update(self, value):
    -    self.hist[value] += 1
    -
    -
    -
    - -
    - - - -
    - -
    - - -
    - -
    - - - -

    - tablite.groupby_utils.Mode() - -

    - - -
    -

    - Bases: Histogram

    - - -
    - Source code in tablite/groupby_utils.py -
    174
    -175
    def __init__(self):
    -    super().__init__()
    -
    -
    - + + + + - -
    - - - - - -
    Attributes
    - -
    - - - -
    - tablite.groupby_utils.Mode.hist = defaultdict(int) + + + + - - instance-attribute - - -
    - - -
    -
    - -
    - -
    - - - -
    - tablite.groupby_utils.Mode.value + + + - - property - - -
    - - -
    -
    - -
    + +
    +
    +
    + + + +
    +
    + + -
    Functions
    +

    Groupby utils

    -
    +
    -
    - tablite.groupby_utils.Mode.update(value) +

    + tablite.groupby_utils -

    + -
    +
    -
    - Source code in tablite/groupby_utils.py -
    140
    -141
    def update(self, value):
    -    self.hist[value] += 1
    -
    -
    -
    + -
    +
    -
    -
    -
    +

    Classes

    @@ -5115,7 +1510,7 @@
    Attributes
    - tablite.groupby_utils.GroupBy.max = Max + tablite.groupby_utils.GroupBy.max = 'max' class-attribute @@ -5135,7 +1530,7 @@
    - tablite.groupby_utils.GroupBy.min = Min + tablite.groupby_utils.GroupBy.min = 'min' class-attribute @@ -5155,7 +1550,7 @@
    - tablite.groupby_utils.GroupBy.sum = Sum + tablite.groupby_utils.GroupBy.sum = 'sum' class-attribute @@ -5175,7 +1570,7 @@
    - tablite.groupby_utils.GroupBy.product = Product + tablite.groupby_utils.GroupBy.product = 'product' class-attribute @@ -5195,7 +1590,7 @@
    - tablite.groupby_utils.GroupBy.first = First + tablite.groupby_utils.GroupBy.first = 'first' class-attribute @@ -5215,7 +1610,7 @@
    - tablite.groupby_utils.GroupBy.last = Last + tablite.groupby_utils.GroupBy.last = 'last' class-attribute @@ -5235,7 +1630,7 @@
    - tablite.groupby_utils.GroupBy.count = Count + tablite.groupby_utils.GroupBy.count = 'count' class-attribute @@ -5255,7 +1650,7 @@
    - tablite.groupby_utils.GroupBy.count_unique = CountUnique + tablite.groupby_utils.GroupBy.count_unique = 'count_unique' class-attribute @@ -5275,7 +1670,7 @@
    - tablite.groupby_utils.GroupBy.avg = Average + tablite.groupby_utils.GroupBy.avg = 'avg' class-attribute @@ -5295,7 +1690,7 @@
    - tablite.groupby_utils.GroupBy.stdev = StandardDeviation + tablite.groupby_utils.GroupBy.stdev = 'stdev' class-attribute @@ -5315,7 +1710,7 @@
    - tablite.groupby_utils.GroupBy.median = Median + tablite.groupby_utils.GroupBy.median = 'median' class-attribute @@ -5335,47 +1730,7 @@
    - tablite.groupby_utils.GroupBy.mode = Mode - - - class-attribute - instance-attribute - - -
    - - -
    -
    - -
    - -
    - - - -
    - tablite.groupby_utils.GroupBy.functions = [Max, Min, Sum, First, Last, Product, Count, CountUnique, Average, StandardDeviation, Median, Mode] - - - class-attribute - instance-attribute - - -
    - - -
    -
    - -
    - -
    - - - -
    - tablite.groupby_utils.GroupBy.function_names = {f.__name__: ffor f in functions} + tablite.groupby_utils.GroupBy.mode = 'mode' class-attribute diff --git a/master/reference/groupbys/groupbys.md b/master/reference/groupbys/groupbys.md deleted file mode 100644 index 904ba04a..00000000 --- a/master/reference/groupbys/groupbys.md +++ /dev/null @@ -1 +0,0 @@ -::: tablite.groupbys diff --git a/master/reference/groupbys/index.html b/master/reference/groupbys/index.html deleted file mode 100644 index dd291720..00000000 --- a/master/reference/groupbys/index.html +++ /dev/null @@ -1,1798 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Groupbys - Tablite - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - - - - - -
    - - - - - - - -
    - -
    - - - - -
    -
    - - - -
    -
    -
    - - - - - - - -
    -
    -
    - - - -
    -
    -
    - - - -
    -
    -
    - - - -
    -
    - - - - -

    Groupbys

    - -
    - - - -

    - tablite.groupbys - - -

    - -
    - - - -
    - - - - - - -

    Classes

    -

    Functions

    - - -
    - - - -

    - tablite.groupbys.groupby(T, keys, functions, tqdm=_tqdm, pbar=None) - -

    - - -
    - -

    keys: column names for grouping. -functions: [optional] list of column names and group functions (See GroupyBy class) -returns: table

    -

    Example:

    -
    >>> t = Table()
    ->>> t.add_column('A', data=[1, 1, 2, 2, 3, 3] * 2)
    ->>> t.add_column('B', data=[1, 2, 3, 4, 5, 6] * 2)
    ->>> t.add_column('C', data=[6, 5, 4, 3, 2, 1] * 2)
    ->>> t.show()
    -+=====+=====+=====+
    -|  A  |  B  |  C  |
    -| int | int | int |
    -+-----+-----+-----+
    -|    1|    1|    6|
    -|    1|    2|    5|
    -|    2|    3|    4|
    -|    2|    4|    3|
    -|    3|    5|    2|
    -|    3|    6|    1|
    -|    1|    1|    6|
    -|    1|    2|    5|
    -|    2|    3|    4|
    -|    2|    4|    3|
    -|    3|    5|    2|
    -|    3|    6|    1|
    -+=====+=====+=====+
    ->>> g = t.groupby(keys=['A', 'C'], functions=[('B', gb.sum)])
    ->>> g.show()
    -+===+===+===+======+
    -| # | A | C |Sum(B)|
    -|row|int|int| int  |
    -+---+---+---+------+
    -|0  |  1|  6|     2|
    -|1  |  1|  5|     4|
    -|2  |  2|  4|     6|
    -|3  |  2|  3|     8|
    -|4  |  3|  2|    10|
    -|5  |  3|  1|    12|
    -+===+===+===+======+
    -
    -

    Cheat sheet:

    -

    list of unique values

    -
    >>> g1 = t.groupby(keys=['A'], functions=[])
    ->>> g1['A'][:]
    -[1,2,3]
    -
    -

    alternatively:

    -
    >>> t['A'].unique()
    -[1,2,3]
    -
    -

    list of unique values, grouped by longest combination.

    -
    >>> g2 = t.groupby(keys=['A', 'B'], functions=[])
    ->>> g2['A'][:], g2['B'][:]
    -([1,1,2,2,3,3], [1,2,3,4,5,6])
    -
    -

    alternatively use:

    -
    >>> list(zip(*t.index('A', 'B').keys()))
    -[(1,1,2,2,3,3) (1,2,3,4,5,6)]
    -
    -

    A key (unique values) and count hereof.

    -
    >>> g3 = t.groupby(keys=['A'], functions=[('A', gb.count)])
    ->>> g3['A'][:], g3['Count(A)'][:]
    -([1,2,3], [4,4,4])
    -
    -

    alternatively use:

    -
    >>> t['A'].histogram()
    -([1,2,3], [4,4,4])
    -
    -

    for more examples see: https://github.com/root-11/tablite/blob/master/tests/test_groupby.py

    - -
    - Source code in tablite/groupbys.py -
     11
    - 12
    - 13
    - 14
    - 15
    - 16
    - 17
    - 18
    - 19
    - 20
    - 21
    - 22
    - 23
    - 24
    - 25
    - 26
    - 27
    - 28
    - 29
    - 30
    - 31
    - 32
    - 33
    - 34
    - 35
    - 36
    - 37
    - 38
    - 39
    - 40
    - 41
    - 42
    - 43
    - 44
    - 45
    - 46
    - 47
    - 48
    - 49
    - 50
    - 51
    - 52
    - 53
    - 54
    - 55
    - 56
    - 57
    - 58
    - 59
    - 60
    - 61
    - 62
    - 63
    - 64
    - 65
    - 66
    - 67
    - 68
    - 69
    - 70
    - 71
    - 72
    - 73
    - 74
    - 75
    - 76
    - 77
    - 78
    - 79
    - 80
    - 81
    - 82
    - 83
    - 84
    - 85
    - 86
    - 87
    - 88
    - 89
    - 90
    - 91
    - 92
    - 93
    - 94
    - 95
    - 96
    - 97
    - 98
    - 99
    -100
    -101
    -102
    -103
    -104
    -105
    -106
    -107
    -108
    -109
    -110
    -111
    -112
    -113
    -114
    -115
    -116
    -117
    -118
    -119
    -120
    -121
    -122
    -123
    -124
    -125
    -126
    -127
    -128
    -129
    -130
    -131
    -132
    -133
    -134
    -135
    -136
    -137
    -138
    -139
    -140
    -141
    -142
    -143
    -144
    -145
    -146
    -147
    -148
    -149
    -150
    -151
    -152
    -153
    -154
    -155
    -156
    -157
    -158
    -159
    -160
    -161
    -162
    -163
    -164
    -165
    -166
    -167
    -168
    -169
    -170
    -171
    -172
    -173
    -174
    -175
    -176
    -177
    -178
    -179
    -180
    -181
    -182
    -183
    -184
    -185
    -186
    -187
    -188
    -189
    -190
    -191
    -192
    -193
    -194
    -195
    -196
    -197
    -198
    -199
    -200
    -201
    def groupby(
    -    T, keys, functions, tqdm=_tqdm, pbar=None
    -):  # TODO: This is single core code.
    -    """
    -    keys: column names for grouping.
    -    functions: [optional] list of column names and group functions (See GroupyBy class)
    -    returns: table
    -
    -    Example:
    -    ```
    -    >>> t = Table()
    -    >>> t.add_column('A', data=[1, 1, 2, 2, 3, 3] * 2)
    -    >>> t.add_column('B', data=[1, 2, 3, 4, 5, 6] * 2)
    -    >>> t.add_column('C', data=[6, 5, 4, 3, 2, 1] * 2)
    -    >>> t.show()
    -    +=====+=====+=====+
    -    |  A  |  B  |  C  |
    -    | int | int | int |
    -    +-----+-----+-----+
    -    |    1|    1|    6|
    -    |    1|    2|    5|
    -    |    2|    3|    4|
    -    |    2|    4|    3|
    -    |    3|    5|    2|
    -    |    3|    6|    1|
    -    |    1|    1|    6|
    -    |    1|    2|    5|
    -    |    2|    3|    4|
    -    |    2|    4|    3|
    -    |    3|    5|    2|
    -    |    3|    6|    1|
    -    +=====+=====+=====+
    -    >>> g = t.groupby(keys=['A', 'C'], functions=[('B', gb.sum)])
    -    >>> g.show()
    -    +===+===+===+======+
    -    | # | A | C |Sum(B)|
    -    |row|int|int| int  |
    -    +---+---+---+------+
    -    |0  |  1|  6|     2|
    -    |1  |  1|  5|     4|
    -    |2  |  2|  4|     6|
    -    |3  |  2|  3|     8|
    -    |4  |  3|  2|    10|
    -    |5  |  3|  1|    12|
    -    +===+===+===+======+
    -    ```
    -
    -    Cheat sheet:
    -
    -    list of unique values
    -    ```
    -    >>> g1 = t.groupby(keys=['A'], functions=[])
    -    >>> g1['A'][:]
    -    [1,2,3]
    -    ```
    -    alternatively:
    -    ```
    -    >>> t['A'].unique()
    -    [1,2,3]
    -    ```
    -    list of unique values, grouped by longest combination.
    -    ```
    -    >>> g2 = t.groupby(keys=['A', 'B'], functions=[])
    -    >>> g2['A'][:], g2['B'][:]
    -    ([1,1,2,2,3,3], [1,2,3,4,5,6])
    -    ```
    -    alternatively use:
    -    ```
    -    >>> list(zip(*t.index('A', 'B').keys()))
    -    [(1,1,2,2,3,3) (1,2,3,4,5,6)]
    -    ```
    -
    -    A key (unique values) and count hereof.
    -    ```
    -    >>> g3 = t.groupby(keys=['A'], functions=[('A', gb.count)])
    -    >>> g3['A'][:], g3['Count(A)'][:]
    -    ([1,2,3], [4,4,4])
    -    ```
    -    alternatively use:
    -    ```
    -    >>> t['A'].histogram()
    -    ([1,2,3], [4,4,4])
    -    ```
    -    for more examples see: https://github.com/root-11/tablite/blob/master/tests/test_groupby.py
    -
    -    """
    -    if not isinstance(keys, list):
    -        raise TypeError("expected keys as a list of column names")
    -
    -    if keys:
    -        if len(set(keys)) != len(keys):
    -            duplicates = [k for k in keys if keys.count(k) > 1]
    -            s = "" if len(duplicates) > 1 else "s"
    -            raise ValueError(
    -                f"duplicate key{s} found across rows and columns: {duplicates}"
    -            )
    -
    -    if not isinstance(functions, list):
    -        raise TypeError(
    -            f"Expected functions to be a list of tuples. Got {type(functions)}"
    -        )
    -
    -    if not keys + functions:
    -        raise ValueError("No keys or functions?")
    -
    -    if not all(len(i) == 2 for i in functions):
    -        raise ValueError(
    -            f"Expected each tuple in functions to be of length 2. \nGot {functions}"
    -        )
    -
    -    if not all(isinstance(a, str) for a, _ in functions):
    -        L = [(a, type(a)) for a, _ in functions if not isinstance(a, str)]
    -        raise ValueError(
    -            f"Expected column names in functions to be strings. Found: {L}"
    -        )
    -
    -    if not all(
    -        issubclass(b, GroupbyFunction) and b in GroupBy.functions for _, b in functions
    -    ):
    -        L = [b for _, b in functions if b not in GroupBy._functions]
    -        if len(L) == 1:
    -            singular = f"function {L[0]} is not in GroupBy.functions"
    -            raise ValueError(singular)
    -        else:
    -            plural = f"the functions {L} are not in GroupBy.functions"
    -            raise ValueError(plural)
    -
    -    # only keys will produce unique values for each key group.
    -    if keys and not functions:
    -        cols = list(zip(*T.index(*keys)))
    -        result = T.__class__()
    -
    -        pbar = tqdm(total=len(keys), desc="groupby") if pbar is None else pbar
    -
    -        for col_name, col in zip(keys, cols):
    -            result[col_name] = col
    -
    -            pbar.update(1)
    -        return result
    -
    -    # grouping is required...
    -    # 1. Aggregate data.
    -    aggregation_functions = defaultdict(dict)
    -    cols = keys + [col_name for col_name, _ in functions]
    -    seen, L = set(), []
    -    for c in cols:  # maintains order of appearance.
    -        if c not in seen:
    -            seen.add(c)
    -            L.append(c)
    -
    -    # there's a table of values.
    -    data = T[L]
    -    if isinstance(data, Column):
    -        tbl = BaseTable()
    -        tbl[L[0]] = data
    -    else:
    -        tbl = data
    -
    -    pbar = (
    -        tqdm(desc="groupby", total=len(tbl), disable=Config.TQDM_DISABLE)
    -        if pbar is None
    -        else pbar
    -    )
    -
    -    for row in tbl.rows:
    -        d = {col_name: value for col_name, value in zip(L, row)}
    -        key = tuple([d[k] for k in keys])
    -        agg_functions = aggregation_functions.get(key)
    -        if not agg_functions:
    -            aggregation_functions[key] = agg_functions = [
    -                (col_name, f()) for col_name, f in functions
    -            ]
    -        for col_name, f in agg_functions:
    -            f.update(d[col_name])
    -
    -        pbar.update(1)
    -
    -    # 2. make dense table.
    -    cols = [[] for _ in cols]
    -    for key_tuple, funcs in aggregation_functions.items():
    -        for ix, key_value in enumerate(key_tuple):
    -            cols[ix].append(key_value)
    -        for ix, (_, f) in enumerate(funcs, start=len(keys)):
    -            cols[ix].append(f.value)
    -
    -    new_names = keys + [f"{f.__name__}({col_name})" for col_name, f in functions]
    -    result = type(T)()  # New Table.
    -    for ix, (col_name, data) in enumerate(zip(new_names, cols)):
    -        revised_name = unique_name(col_name, result.columns)
    -        result[revised_name] = data
    -    return result
    -
    -
    -
    - -
    - - - -
    - -
    - -
    - - - - - - - - - - - - - -
    -
    - - - -
    - - - -
    - -
    - - -
    - -
    -
    -
    -
    - - - - - - - - - - \ No newline at end of file diff --git a/master/reference/import_utils/index.html b/master/reference/import_utils/index.html index e262d22c..db30a8f1 100644 --- a/master/reference/import_utils/index.html +++ b/master/reference/import_utils/index.html @@ -13,7 +13,7 @@ - + @@ -473,8 +473,6 @@ - - @@ -705,27 +703,6 @@ - - -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - diff --git a/master/reference/imputation/index.html b/master/reference/imputation/index.html index ec81f53f..910ebff7 100644 --- a/master/reference/imputation/index.html +++ b/master/reference/imputation/index.html @@ -473,8 +473,6 @@ - - @@ -707,27 +705,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/reference/joins/index.html b/master/reference/joins/index.html index 8d416ef7..e3f5e84a 100644 --- a/master/reference/joins/index.html +++ b/master/reference/joins/index.html @@ -473,8 +473,6 @@ - - @@ -707,27 +705,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/reference/lookup/index.html b/master/reference/lookup/index.html index 86dba773..7aa67969 100644 --- a/master/reference/lookup/index.html +++ b/master/reference/lookup/index.html @@ -473,8 +473,6 @@ - - @@ -707,27 +705,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/reference/match/index.html b/master/reference/match/index.html index a5e65459..73d3c14e 100644 --- a/master/reference/match/index.html +++ b/master/reference/match/index.html @@ -473,8 +473,6 @@ - - @@ -707,27 +705,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/reference/merge/index.html b/master/reference/merge/index.html index 497d435c..aeb4dc4a 100644 --- a/master/reference/merge/index.html +++ b/master/reference/merge/index.html @@ -473,8 +473,6 @@ - - @@ -707,27 +705,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/reference/mp_utils/index.html b/master/reference/mp_utils/index.html index 1a54908e..13118d91 100644 --- a/master/reference/mp_utils/index.html +++ b/master/reference/mp_utils/index.html @@ -473,8 +473,6 @@ - - @@ -707,27 +705,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/reference/nimlite/index.html b/master/reference/nimlite/index.html index 06ac04a6..7c725404 100644 --- a/master/reference/nimlite/index.html +++ b/master/reference/nimlite/index.html @@ -473,8 +473,6 @@ - - @@ -707,27 +705,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • @@ -1103,6 +1080,15 @@ +
  • + +
  • + + +  groupby + + +
  • @@ -1518,6 +1504,15 @@ +
  • + +
  • + + +  groupby + + +
  • @@ -2370,6 +2365,30 @@

    +

    + tablite.nimlite.groupby(T, keys, functions, tqdm=_tqdm) + +

    + + +
    + +
    + Source code in tablite/nimlite.py +
    310
    +311
    def groupby(T, keys, functions, tqdm=_tqdm):
    +    return nl.groupby(T, keys, functions, tqdm)
    +
    +
    +
    + +
  • + + +
    + + +

    tablite.nimlite.filter(table: BaseTable, expressions: list[FilterDict], type: FilterType, tqdm=_tqdm) @@ -2380,8 +2399,8 @@

    Source code in tablite/nimlite.py -
    310
    -311
    def filter(table: BaseTable, expressions: list[FilterDict], type: FilterType, tqdm = _tqdm):
    +            
    313
    +314
    def filter(table: BaseTable, expressions: list[FilterDict], type: FilterType, tqdm = _tqdm):
         return nl.filter(table, expressions, type, tqdm)
     
    diff --git a/master/reference/pivots/index.html b/master/reference/pivots/index.html index fd16c5d6..928c7512 100644 --- a/master/reference/pivots/index.html +++ b/master/reference/pivots/index.html @@ -473,8 +473,6 @@ - - @@ -707,27 +705,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • @@ -967,6 +944,15 @@
  • + + +
    + + +

    tablite.pivots.pivot(T, rows, columns, functions, values_as_rows=True, tqdm=_tqdm, pbar=None) @@ -1351,21 +1390,7 @@

    Source code in tablite/pivots.py -
     12
    - 13
    - 14
    - 15
    - 16
    - 17
    - 18
    - 19
    - 20
    - 21
    - 22
    - 23
    - 24
    - 25
    - 26
    +            
     26
      27
      28
      29
    @@ -1522,7 +1547,21 @@ 

    180 181 182 -183

    def pivot(T, rows, columns, functions, values_as_rows=True, tqdm=_tqdm, pbar=None):
    +183
    +184
    +185
    +186
    +187
    +188
    +189
    +190
    +191
    +192
    +193
    +194
    +195
    +196
    +197
    def pivot(T, rows, columns, functions, values_as_rows=True, tqdm=_tqdm, pbar=None):
         """
         param: rows: column names to keep as rows
         param: columns: column names to keep as columns
    @@ -1597,7 +1636,7 @@ 

    pbar = tqdm(total=total, desc="pivot") - grpby = groupby(T, keys, functions, tqdm=tqdm, pbar=pbar) + grpby = groupby(T, keys, functions, tqdm=tqdm) Constr = type(T) if len(grpby) == 0: # return empty table. This must be a test? @@ -1642,7 +1681,7 @@

    cols = [[] for _ in range(n)] for row, ix in row_key_index.items(): for col_name, f in functions: - cols[-1].append(f"{f.__name__}({col_name})") + cols[-1].append(f"{acc2Name(f)}({col_name})") for col_ix, v in enumerate(row): cols[col_ix].append(v) @@ -1681,7 +1720,7 @@

    for f, v in zip(functions, func_key): agg_col, func = f terms = ",".join([agg_col] + [f"{col_name}={value}" for col_name, value in zip(columns, col_key)]) - col_name = f"{func.__name__}({terms})" + col_name = f"{acc2Name(func)}({terms})" col_name = unique_name(col_name, result.columns) names.append(col_name) cols.append([None for _ in range(col_length)]) @@ -1717,20 +1756,20 @@

    Source code in tablite/pivots.py -
    186
    -187
    -188
    -189
    -190
    -191
    -192
    -193
    -194
    -195
    -196
    -197
    -198
    -199
    def transpose(T, tqdm=_tqdm):
    +            
    200
    +201
    +202
    +203
    +204
    +205
    +206
    +207
    +208
    +209
    +210
    +211
    +212
    +213
    def transpose(T, tqdm=_tqdm):
         """performs a CCW matrix rotation of the table."""
         sub_cls_check(T, BaseTable)
     
    @@ -1855,21 +1894,7 @@ 

    Source code in tablite/pivots.py -
    202
    -203
    -204
    -205
    -206
    -207
    -208
    -209
    -210
    -211
    -212
    -213
    -214
    -215
    -216
    +            
    216
     217
     218
     219
    @@ -1942,7 +1967,21 @@ 

    286 287 288 -289

    def pivot_transpose(T, columns, keep=None, column_name="transpose", value_name="value", tqdm=_tqdm):
    +289
    +290
    +291
    +292
    +293
    +294
    +295
    +296
    +297
    +298
    +299
    +300
    +301
    +302
    +303
    def pivot_transpose(T, columns, keep=None, column_name="transpose", value_name="value", tqdm=_tqdm):
         """Transpose a selection of columns to rows.
     
         Args:
    diff --git a/master/reference/redux/index.html b/master/reference/redux/index.html
    index ba210c5e..1d59f8a4 100644
    --- a/master/reference/redux/index.html
    +++ b/master/reference/redux/index.html
    @@ -473,8 +473,6 @@
             
           
             
    -      
    -        
           
             
           
    @@ -707,27 +705,6 @@
       
       
       
    -    
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/reference/reindex/index.html b/master/reference/reindex/index.html index 17b56b47..c3fc1db6 100644 --- a/master/reference/reindex/index.html +++ b/master/reference/reindex/index.html @@ -473,8 +473,6 @@ - - @@ -707,27 +705,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/reference/sort_utils/index.html b/master/reference/sort_utils/index.html index de1e9dc5..00325657 100644 --- a/master/reference/sort_utils/index.html +++ b/master/reference/sort_utils/index.html @@ -473,8 +473,6 @@ - - @@ -707,27 +705,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/reference/sortation/index.html b/master/reference/sortation/index.html index 60b3e567..d0e1e801 100644 --- a/master/reference/sortation/index.html +++ b/master/reference/sortation/index.html @@ -473,8 +473,6 @@ - - @@ -707,27 +705,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/reference/tools/index.html b/master/reference/tools/index.html index 4e480165..ae308412 100644 --- a/master/reference/tools/index.html +++ b/master/reference/tools/index.html @@ -473,8 +473,6 @@ - - @@ -707,27 +705,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/reference/utils/index.html b/master/reference/utils/index.html index 4875d3e7..14da0995 100644 --- a/master/reference/utils/index.html +++ b/master/reference/utils/index.html @@ -473,8 +473,6 @@ - - @@ -707,27 +705,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/reference/version/index.html b/master/reference/version/index.html index 5919e846..f7451a0b 100644 --- a/master/reference/version/index.html +++ b/master/reference/version/index.html @@ -471,8 +471,6 @@ - - @@ -705,27 +703,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/master/search/search_index.json b/master/search/search_index.json index a6ee5820..4ebd5d4b 100644 --- a/master/search/search_index.json +++ b/master/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Tablite","text":""},{"location":"#contents","title":"Contents","text":"
    • introduction
    • installation
    • feature overview
    • api
    • tutorial
    • latest updates
    • credits
    "},{"location":"#introduction","title":"Introduction","text":"

    Tablite seeks to be the go-to library for manipulating tabular data with an api that is as close in syntax to pure python as possible.

    "},{"location":"#even-smaller-memory-footprint","title":"Even smaller memory footprint","text":"

    Tablite uses numpys fileformat as a backend with strong abstraction, so that copy, append & repetition of data is handled in pages. This is imperative for incremental data processing.

    Tablite tests for memory footprint. One test compares the memory footprint of 10,000,000 integers where tablite will use < 1 Mb RAM in contrast to python which will require around 133.7 Mb of RAM (1M lists with 10 integers). Tablite also tests to assure that working with 1Tb of data is tolerable.

    Tablite achieves this minimal memory footprint by using a temporary storage set in config.Config.workdir as tempfile.gettempdir()/tablite-tmp. If your OS (windows/linux/mac) sits on a SSD this will benefit from high IOPS and permit slices of 9,000,000,000 rows in less than a second.

    "},{"location":"#multiprocessing-enabled-by-default","title":"Multiprocessing enabled by default","text":"

    Tablite uses numpy whereever possible and applies multiprocessing for bypassing the GIL on all major operations. CSV import is performed in C through using nims compiler and is as fast the hardware allows.

    "},{"location":"#all-algorithms-have-been-reworked-to-respect-memory-limits","title":"All algorithms have been reworked to respect memory limits","text":"

    Tablite respects the limits of free memory by tagging the free memory and defining task size before each memory intensive task is initiated (join, groupby, data import, etc). If you still run out of memory you may try to reduce the config.Config.PAGE_SIZE and rerun your program.

    "},{"location":"#100-support-for-all-python-datatypes","title":"100% support for all python datatypes","text":"

    Tablite wants to make it easy for you to work with data. tablite.Table's behave like a dict with lists:

    my_table[column name] = [... data ...].

    Tablite uses datatype mapping to native numpy types where possible and uses type mapping for non-native types such as timedelta, None, date, time\u2026 e.g. what you put in, is what you get out. This is inspired by bank python.

    "},{"location":"#light-weight","title":"Light weight","text":"

    Tablite is ~200 kB.

    "},{"location":"#helpful","title":"Helpful","text":"

    Tablite wants you to be productive, so a number of helpers are available.

    • Table.import_file to import csv*, tsv, txt, xls, xlsx, xlsm, ods, zip and logs. There is automatic type detection (see tutorial.ipynb )
    • To peek into any supported file use get_headers which shows the first 10 rows.
    • Use mytable.rows and mytable.columns to iterate over rows or columns.
    • Create multi-key .index for quick lookups.
    • Perform multi-key .sort,
    • Filter using .any and .all to select specific rows.
    • use multi-key .lookup and .join to find data across tables.
    • Perform .groupby and reorganise data as a .pivot table with max, min, sum, first, last, count, unique, average, st.deviation, median and mode
    • Append / concatenate tables with += which automatically sorts out the columns - even if they're not in perfect order.
    • Should you tables be similar but not the identical you can use .stack to \"stack\" tables on top of each other

    If you're still missing something add it to the wishlist

    "},{"location":"#installation","title":"Installation","text":"

    Get it from pypi:

    Install: pip install tablite Usage: >>> from tablite import Table

    "},{"location":"#build-test","title":"Build & test","text":"

    install nim >= 2.0.0

    run: chmod +x ./build_nim.sh run: ./build_nim.sh

    Should the default nim not be your desired taste, please use nims environment manager (atlas) and run source nim-2.0.0/activate.sh on UNIX or nim-2.0.0/activate.bat on windows.

    install python >= 3.8\npython -m venv /your/venv/dir\nactivate /your/venv/dir\npip install -r requirements.txt\npip install -r requirements_for_testing.py\npytest ./tests\n
    "},{"location":"#feature-overview","title":"Feature overview","text":"want to... this way... loop over rows [ row for row in table.rows ] loop over columns [ table[col_name] for col_name in table.columns ] slice myslice = table['A', 'B', slice(0,None,15)] get column by name my_table['A'] get row by index my_table[9_000_000_001] value update mytable['A'][2] = new value update w. list comprehension mytable['A'] = [ x*x for x in mytable['A'] if x % 2 != 0 ] join a_join = numbers.join(letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter'], kind='left') lookup travel_plan = friends.lookup(bustable, (DataTypes.time(21, 10), \"<=\", 'time'), ('stop', \"==\", 'stop')) groupby group_by = table.groupby(keys=['C', 'B'], functions=[('A', gb.count)]) pivot table my_pivot = t.pivot(rows=['C'], columns=['A'], functions=[('B', gb.sum), ('B', gb.count)], values_as_rows=False) index indices = old_table.index(*old_table.columns) sort lookup1_sorted = lookup_1.sort(**{'time': True, 'name':False, \"sort_mode\":'unix'}) filter true, false = unfiltered.filter( [{\"column1\": 'a', \"criteria\":\">=\", 'value2':3}, ... more criteria ... ], filter_type='all' ) find any any_even_rows = mytable.any('A': lambda x : x%2==0, 'B': lambda x > 0) find all all_even_rows = mytable.all('A': lambda x : x%2==0, 'B': lambda x > 0) to json json_str = my_table.to_json() from json Table.from_json(json_str)"},{"location":"#api","title":"API","text":"

    To view the detailed API see api

    "},{"location":"#tutorial","title":"Tutorial","text":"

    To learn more see the tutorial.ipynb (Jupyter notebook)

    "},{"location":"#latest-updates","title":"Latest updates","text":"

    See changelog.md

    "},{"location":"#credits","title":"Credits","text":"
    • Eugene Antonov - the api documentation.
    • Audrius Kulikajevas - Edge case testing / various bugs, Jupyter notebook integration.
    • Ovidijus Grigas - various bugs, documentation.
    • Martynas Kaunas - GroupBy functionality.
    • Sergej Sinkarenko - various bugs.
    • Lori Cooper - spell checking.
    "},{"location":"benchmarks/","title":"Benchmarks","text":"In\u00a0[2]: Copied!
    import psutil, os, gc, shutil, tempfile\nfrom pathlib import Path\nfrom time import perf_counter, time\nfrom tablite import Table\nfrom tablite.datasets import synthetic_order_data\nfrom tablite.config import Config\n\nConfig.TQDM_DISABLE = True\n
    import psutil, os, gc, shutil, tempfile from pathlib import Path from time import perf_counter, time from tablite import Table from tablite.datasets import synthetic_order_data from tablite.config import Config Config.TQDM_DISABLE = True In\u00a0[3]: Copied!
    process = psutil.Process(os.getpid())\n\ndef make_tables(sizes=[1,2,5,10,20,50]):\n    # The last tables are too big for RAM (~24Gb), so I create subtables of 1M rows and append them.\n    t = synthetic_order_data(Config.PAGE_SIZE)\n    real, flat = t.nbytes()\n    print(f\"Table {len(t):,} rows is {real/1e6:,.0f} Mb on disk\")\n\n    tables = [t]  # 1M rows.\n\n    last = 1\n    t2 = t.copy()\n    for i in sizes[1:]:\n        t2 = t2.copy()\n        for _ in range(i-last):\n            t2 += synthetic_order_data(Config.PAGE_SIZE)  # these are all unique\n        last = i\n        real, flat = t2.nbytes()\n        tables.append(t2)\n        print(f\"Table {len(t2):,} rows is {real/1e6:,.0f} Mb on disk\")\n    return tables\n\ntables = make_tables()\n
    process = psutil.Process(os.getpid()) def make_tables(sizes=[1,2,5,10,20,50]): # The last tables are too big for RAM (~24Gb), so I create subtables of 1M rows and append them. t = synthetic_order_data(Config.PAGE_SIZE) real, flat = t.nbytes() print(f\"Table {len(t):,} rows is {real/1e6:,.0f} Mb on disk\") tables = [t] # 1M rows. last = 1 t2 = t.copy() for i in sizes[1:]: t2 = t2.copy() for _ in range(i-last): t2 += synthetic_order_data(Config.PAGE_SIZE) # these are all unique last = i real, flat = t2.nbytes() tables.append(t2) print(f\"Table {len(t2):,} rows is {real/1e6:,.0f} Mb on disk\") return tables tables = make_tables()
    Table 1,000,000 rows is 256 Mb on disk\nTable 2,000,000 rows is 512 Mb on disk\nTable 5,000,000 rows is 1,280 Mb on disk\nTable 10,000,000 rows is 2,560 Mb on disk\nTable 20,000,000 rows is 5,120 Mb on disk\nTable 50,000,000 rows is 12,800 Mb on disk\n

    The values in the tables above are all unique!

    In\u00a0[4]: Copied!
    tables[-1]\n
    tables[-1] Out[4]: ~#1234567891011 0114014953182952021-10-06T00:00:0050814119375C3-4HGQ21\u00b0XYZ1.244647268201734421.367107051830455 129320231372182021-08-26T00:00:005007718568C5-5FZU0\u00b00.55294485347516132.6980406874392537 2312569602250812021-12-21T00:00:0050197029074C2-3GTK6\u00b0XYZ1.99739754559065617.513164305723787 3414012777817432021-08-23T00:00:0050818024969C4-3BYP6\u00b0XYZ0.047497125538289577.388171617130485 459426667674262021-07-31T00:00:0050307113074C5-2CCC21\u00b0ABC1.0219215027612885.21324123446987 5612186131851272021-12-01T00:00:0050484117249C5-4WGT21\u00b00.2038764258434556712.190974436133764 676070424343982021-11-29T00:00:0050578011564C2-3LUL0\u00b0XYZ2.2367835158480444.340628097363572.......................................49,999,9939999946602693775472021-09-17T00:00:005015409706C4-3AHQ21\u00b0XYZ0.083216645843125856.56780297752790549,999,9949999955709798646952021-08-01T00:00:0050149125006C1-2FWH6\u00b01.04763923662266419.50710544462706549,999,9959999963551956078252021-07-29T00:00:0050007026992C4-3GVG21\u00b02.20440816560941411.2706443974284949,999,99699999720762240577282021-10-16T00:00:0050950113339C5-4NKS0\u00b02.1593110498135494.21575620046596149,999,9979999986577247891352021-12-21T00:00:0050069114747C2-4LYGNone1.64809640191698683.094420483625827349,999,9989999999775312438842021-12-02T00:00:0050644129345C2-5DRH6\u00b02.30911421692753110.82706867207146849,999,999100000012290713920652021-08-23T00:00:0050706119732C4-5AGB6\u00b00.488871405593691630.8580085696389939 In\u00a0[5]: Copied!
    def save_load_benchmarks(tables):\n    tmp = Path(tempfile.gettempdir()) / \"junk\"\n    tmp.mkdir(exist_ok=True)\n\n    results = Table()\n    results.add_columns('rows', 'save (sec)', 'load (sec)')\n    for t in tables:\n        fn = tmp / f'{len(t)}.tpz'\n        start = perf_counter()\n        t.save(fn)\n        end = perf_counter()\n        save = round(end-start,3)\n        assert fn.exists()\n        \n        \n        start = perf_counter()\n        t2 = Table.load(fn)\n        end = perf_counter()\n        load = round(end-start,3)\n        print(f\"saving {len(t):,} rows ({fn.stat().st_size/1e6:,.0f} Mb) took {save:,.3f} seconds. loading took {load:,.3f} seconds\")\n        del t2\n        fn.unlink()\n        results.add_rows(len(t), save, load)\n    \n    r = results\n    r['save r/sec'] = [int(a/b) if b!=0  else \"nil\" for a,b in zip(r['rows'], r['save (sec)']) ]\n    r['load r/sec'] = [int(a/b) if b!=0  else \"nil\" for a,b in zip(r['rows'], r['load (sec)'])]\n\n    return results\n
    def save_load_benchmarks(tables): tmp = Path(tempfile.gettempdir()) / \"junk\" tmp.mkdir(exist_ok=True) results = Table() results.add_columns('rows', 'save (sec)', 'load (sec)') for t in tables: fn = tmp / f'{len(t)}.tpz' start = perf_counter() t.save(fn) end = perf_counter() save = round(end-start,3) assert fn.exists() start = perf_counter() t2 = Table.load(fn) end = perf_counter() load = round(end-start,3) print(f\"saving {len(t):,} rows ({fn.stat().st_size/1e6:,.0f} Mb) took {save:,.3f} seconds. loading took {load:,.3f} seconds\") del t2 fn.unlink() results.add_rows(len(t), save, load) r = results r['save r/sec'] = [int(a/b) if b!=0 else \"nil\" for a,b in zip(r['rows'], r['save (sec)']) ] r['load r/sec'] = [int(a/b) if b!=0 else \"nil\" for a,b in zip(r['rows'], r['load (sec)'])] return results In\u00a0[6]: Copied!
    slb = save_load_benchmarks(tables)\n
    slb = save_load_benchmarks(tables)
    saving 1,000,000 rows (49 Mb) took 2.148 seconds. loading took 0.922 seconds\nsaving 2,000,000 rows (98 Mb) took 4.267 seconds. loading took 1.820 seconds\nsaving 5,000,000 rows (246 Mb) took 10.618 seconds. loading took 4.482 seconds\nsaving 10,000,000 rows (492 Mb) took 21.291 seconds. loading took 8.944 seconds\nsaving 20,000,000 rows (984 Mb) took 42.603 seconds. loading took 17.821 seconds\nsaving 50,000,000 rows (2,461 Mb) took 106.644 seconds. loading took 44.600 seconds\n
    In\u00a0[7]: Copied!
    slb\n
    slb Out[7]: #rowssave (sec)load (sec)save r/secload r/sec 010000002.1480.9224655491084598 120000004.2671.824687131098901 2500000010.6184.4824708981115573 31000000021.2918.9444696821118067 42000000042.60317.8214694501122271 550000000106.64444.64688491121076

    With various compression options

    In\u00a0[8]: Copied!
    def save_compression_benchmarks(t):\n    tmp = Path(tempfile.gettempdir()) / \"junk\"\n    tmp.mkdir(exist_ok=True)\n\n    import zipfile  # https://docs.python.org/3/library/zipfile.html#zipfile.ZipFile\n    methods = [(None, zipfile.ZIP_STORED, \"zip stored\"), (None, zipfile.ZIP_LZMA, \"zip lzma\")]\n    methods += [(i, zipfile.ZIP_DEFLATED, \"zip deflated\") for i in range(0,10)]\n    methods += [(i, zipfile.ZIP_BZIP2, \"zip bzip2\") for i in range(1,10)]\n\n    results = Table()\n    results.add_columns('file size (Mb)', 'method', 'write (sec)', 'read (sec)')\n    for level, method, name in methods:\n        fn = tmp / f'{len(t)}.tpz'\n        start = perf_counter()  \n        t.save(fn, compression_method=method, compression_level=level)\n        end = perf_counter()\n        write = round(end-start,3)\n        assert fn.exists()\n        size = int(fn.stat().st_size/1e6)\n        # print(f\"{name}(level={level}): {len(t):,} rows ({size} Mb) took {write:,.3f} secconds to save\", end='')\n        \n        start = perf_counter()\n        t2 = Table.load(fn)\n        end = perf_counter()\n        read = round(end-start,3)\n        # print(f\" and {end-start:,.3} seconds to load\")\n        print(\".\", end='')\n        \n        del t2\n        fn.unlink()\n        results.add_rows(size, f\"{name}(level={level})\", write, read)\n        \n    \n    r = results\n    r.sort({'write (sec)':True})\n    r['write (rps)'] = [int(1_000_000/b) for b in r['write (sec)']]\n    r['read (rps)'] = [int(1_000_000/b) for b in r['read (sec)']]\n    return results\n
    def save_compression_benchmarks(t): tmp = Path(tempfile.gettempdir()) / \"junk\" tmp.mkdir(exist_ok=True) import zipfile # https://docs.python.org/3/library/zipfile.html#zipfile.ZipFile methods = [(None, zipfile.ZIP_STORED, \"zip stored\"), (None, zipfile.ZIP_LZMA, \"zip lzma\")] methods += [(i, zipfile.ZIP_DEFLATED, \"zip deflated\") for i in range(0,10)] methods += [(i, zipfile.ZIP_BZIP2, \"zip bzip2\") for i in range(1,10)] results = Table() results.add_columns('file size (Mb)', 'method', 'write (sec)', 'read (sec)') for level, method, name in methods: fn = tmp / f'{len(t)}.tpz' start = perf_counter() t.save(fn, compression_method=method, compression_level=level) end = perf_counter() write = round(end-start,3) assert fn.exists() size = int(fn.stat().st_size/1e6) # print(f\"{name}(level={level}): {len(t):,} rows ({size} Mb) took {write:,.3f} secconds to save\", end='') start = perf_counter() t2 = Table.load(fn) end = perf_counter() read = round(end-start,3) # print(f\" and {end-start:,.3} seconds to load\") print(\".\", end='') del t2 fn.unlink() results.add_rows(size, f\"{name}(level={level})\", write, read) r = results r.sort({'write (sec)':True}) r['write (rps)'] = [int(1_000_000/b) for b in r['write (sec)']] r['read (rps)'] = [int(1_000_000/b) for b in r['read (sec)']] return results In\u00a0[9]: Copied!
    scb = save_compression_benchmarks(tables[0])\n
    scb = save_compression_benchmarks(tables[0])
    .....................
    creating sort index:   0%|          | 0/1 [00:00<?, ?it/s]\rcreating sort index: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 1/1 [00:00<00:00, 268.92it/s]\n
    In\u00a0[10]: Copied!
    scb[0:20]\n
    scb[0:20] Out[10]: #file size (Mb)methodwrite (sec)read (sec)write (rps)read (rps) 0256zip stored(level=None)0.3960.47525252522105263 129zip lzma(level=None)95.1372.22810511448833 2256zip deflated(level=0)0.5350.59518691581680672 349zip deflated(level=1)2.150.9224651161084598 447zip deflated(level=2)2.2640.9124416961096491 543zip deflated(level=3)3.0490.833279761204819 644zip deflated(level=4)2.920.8623424651160092 742zip deflated(level=5)4.0340.8692478921150747 840zip deflated(level=6)8.5580.81168491250000 939zip deflated(level=7)13.6950.7787301912853471038zip deflated(level=8)56.9720.7921755212626261138zip deflated(level=9)122.6230.791815512642221229zip bzip2(level=1)15.1214.065661332460021329zip bzip2(level=2)16.0474.214623162373041429zip bzip2(level=3)16.8584.409593192268081529zip bzip2(level=4)17.6485.141566631945141629zip bzip2(level=5)18.6746.009535501664171729zip bzip2(level=6)19.4056.628515331508751829zip bzip2(level=7)19.9546.714501151489421929zip bzip2(level=8)20.5956.96148555143657

    Conclusions

    • Fastest: zip stored with no compression takes handles
    In\u00a0[11]: Copied!
    def to_sql_benchmark(t, rows=1_000_000):\n    t2 = t[:rows]\n    write_start = time()\n    _ = t2.to_sql(name='1')\n    write_end = time()\n    write = round(write_end-write_start,3)\n    return ( t.to_sql.__name__, write, 0, len(t2), \"\" , \"\" )\n
    def to_sql_benchmark(t, rows=1_000_000): t2 = t[:rows] write_start = time() _ = t2.to_sql(name='1') write_end = time() write = round(write_end-write_start,3) return ( t.to_sql.__name__, write, 0, len(t2), \"\" , \"\" ) In\u00a0[12]: Copied!
    def to_json_benchmark(t, rows=1_000_000):\n    t2 = t[:rows]\n\n    tmp = Path(tempfile.gettempdir()) / \"junk\"\n    tmp.mkdir(exist_ok=True)\n    path = tmp / \"1.json\" \n    \n    write_start = time()\n    bytestr = t2.to_json()\n    with path.open('w') as fo:\n        fo.write(bytestr)\n    write_end = time()\n    write = round(write_end-write_start,3)\n\n    read_start = time()\n    with path.open('r') as fi:\n        _ = Table.from_json(fi.read())  # <-- JSON\n    read_end = time()\n    read = round(read_end-read_start,3)\n\n    return ( t.to_json.__name__, write, read, len(t2), int(path.stat().st_size/1e6), \"\" )\n
    def to_json_benchmark(t, rows=1_000_000): t2 = t[:rows] tmp = Path(tempfile.gettempdir()) / \"junk\" tmp.mkdir(exist_ok=True) path = tmp / \"1.json\" write_start = time() bytestr = t2.to_json() with path.open('w') as fo: fo.write(bytestr) write_end = time() write = round(write_end-write_start,3) read_start = time() with path.open('r') as fi: _ = Table.from_json(fi.read()) # <-- JSON read_end = time() read = round(read_end-read_start,3) return ( t.to_json.__name__, write, read, len(t2), int(path.stat().st_size/1e6), \"\" ) In\u00a0[13]: Copied!
    def f(t, args):\n    rows, c1, c1_kw, c2, c2_kw = args\n    t2 = t[:rows]\n\n    call = getattr(t2, c1)\n    assert callable(call)\n\n    write_start = time()\n    call(**c1_kw)\n    write_end = time()\n    write = round(write_end-write_start,3)\n\n    for _ in range(10):\n        gc.collect()\n\n    read_start = time()\n    if callable(c2):\n        c2(**c2_kw)\n    read_end = time()\n    read = round(read_end-read_start,3)\n\n    fn = c2_kw['path']\n    assert fn.exists()\n    fs = int(fn.stat().st_size/1e6)\n    config = {k:v for k,v in c2_kw.items() if k!= 'path'}\n\n    return ( c1, write, read, len(t2), fs , str(config))\n
    def f(t, args): rows, c1, c1_kw, c2, c2_kw = args t2 = t[:rows] call = getattr(t2, c1) assert callable(call) write_start = time() call(**c1_kw) write_end = time() write = round(write_end-write_start,3) for _ in range(10): gc.collect() read_start = time() if callable(c2): c2(**c2_kw) read_end = time() read = round(read_end-read_start,3) fn = c2_kw['path'] assert fn.exists() fs = int(fn.stat().st_size/1e6) config = {k:v for k,v in c2_kw.items() if k!= 'path'} return ( c1, write, read, len(t2), fs , str(config)) In\u00a0[14]: Copied!
    def import_export_benchmarks(tables):\n    Config.PROCESSING_MODE = Config.FALSE\n        \n    t = sorted(tables, key=lambda x: len(x), reverse=True)[0]\n    \n    tmp = Path(tempfile.gettempdir()) / \"junk\"\n    tmp.mkdir(exist_ok=True)   \n\n    args = [\n        (   100_000, \"to_xlsx\", {'path': tmp/'1.xlsx'}, Table.from_file, {\"path\":tmp/'1.xlsx', \"sheet\":\"pyexcel_sheet1\"}),\n        (    50_000,  \"to_ods\",  {'path': tmp/'1.ods'}, Table.from_file, {\"path\":tmp/'1.ods', \"sheet\":\"pyexcel_sheet1\"} ),  # 50k rows, otherwise MemoryError.\n        ( 1_000_000,  \"to_csv\",  {'path': tmp/'1.csv'}, Table.from_file, {\"path\":tmp/'1.csv'}                           ),\n        ( 1_000_000,  \"to_csv\",  {'path': tmp/'1.csv'}, Table.from_file, {\"path\":tmp/'1.csv', \"guess_datatypes\":False}),\n        (10_000_000,  \"to_csv\",  {'path': tmp/'1.csv'}, Table.from_file, {\"path\":tmp/'1.csv', \"guess_datatypes\":False}),\n        ( 1_000_000,  \"to_tsv\",  {'path': tmp/'1.tsv'}, Table.from_file, {\"path\":tmp/'1.tsv'}                           ),\n        ( 1_000_000, \"to_text\",  {'path': tmp/'1.txt'}, Table.from_file, {\"path\":tmp/'1.txt'}                           ),\n        ( 1_000_000, \"to_html\", {'path': tmp/'1.html'}, Table.from_file, {\"path\":tmp/'1.html'}                          ),\n        ( 1_000_000, \"to_hdf5\", {'path': tmp/'1.hdf5'}, Table.from_file, {\"path\":tmp/'1.hdf5'}                          )\n    ]\n\n    results = Table()\n    results.add_columns('method', 'write (s)', 'read (s)', 'rows', 'size (Mb)', 'config')\n\n    results.add_rows( to_sql_benchmark(t) )\n    results.add_rows( to_json_benchmark(t) )\n\n    for arg in args:\n        if len(t)<arg[0]:\n            continue\n        print(\".\", end='')\n        try:\n            results.add_rows( f(t, arg) )\n        except MemoryError:\n            results.add_rows( arg[1], \"Memory Error\", \"NIL\", args[0], \"NIL\", \"N/A\")\n    \n    r = results\n    r['read r/sec'] = [int(a/b) if b!=0  else \"nil\" for a,b in zip(r['rows'], r['read (s)']) ]\n    r['write r/sec'] = [int(a/b) if b!=0  else \"nil\" for a,b in zip(r['rows'], r['write (s)'])]\n\n    shutil.rmtree(tmp)\n    return results\n
    def import_export_benchmarks(tables): Config.PROCESSING_MODE = Config.FALSE t = sorted(tables, key=lambda x: len(x), reverse=True)[0] tmp = Path(tempfile.gettempdir()) / \"junk\" tmp.mkdir(exist_ok=True) args = [ ( 100_000, \"to_xlsx\", {'path': tmp/'1.xlsx'}, Table.from_file, {\"path\":tmp/'1.xlsx', \"sheet\":\"pyexcel_sheet1\"}), ( 50_000, \"to_ods\", {'path': tmp/'1.ods'}, Table.from_file, {\"path\":tmp/'1.ods', \"sheet\":\"pyexcel_sheet1\"} ), # 50k rows, otherwise MemoryError. ( 1_000_000, \"to_csv\", {'path': tmp/'1.csv'}, Table.from_file, {\"path\":tmp/'1.csv'} ), ( 1_000_000, \"to_csv\", {'path': tmp/'1.csv'}, Table.from_file, {\"path\":tmp/'1.csv', \"guess_datatypes\":False}), (10_000_000, \"to_csv\", {'path': tmp/'1.csv'}, Table.from_file, {\"path\":tmp/'1.csv', \"guess_datatypes\":False}), ( 1_000_000, \"to_tsv\", {'path': tmp/'1.tsv'}, Table.from_file, {\"path\":tmp/'1.tsv'} ), ( 1_000_000, \"to_text\", {'path': tmp/'1.txt'}, Table.from_file, {\"path\":tmp/'1.txt'} ), ( 1_000_000, \"to_html\", {'path': tmp/'1.html'}, Table.from_file, {\"path\":tmp/'1.html'} ), ( 1_000_000, \"to_hdf5\", {'path': tmp/'1.hdf5'}, Table.from_file, {\"path\":tmp/'1.hdf5'} ) ] results = Table() results.add_columns('method', 'write (s)', 'read (s)', 'rows', 'size (Mb)', 'config') results.add_rows( to_sql_benchmark(t) ) results.add_rows( to_json_benchmark(t) ) for arg in args: if len(t) In\u00a0[15]: Copied!
    ieb = import_export_benchmarks(tables)\n
    ieb = import_export_benchmarks(tables)
    .........writing 12,000,000 records to /tmp/junk/1.hdf5... done\n
    In\u00a0[16]: Copied!
    ieb\n
    ieb Out[16]: #methodwrite (s)read (s)rowssize (Mb)configread r/secwrite r/sec 0to_sql12.34501000000nil81004 1to_json10.8144.406100000014222696392472 2to_xlsx10.56921.5721000009{'sheet': 'pyexcel_sheet1'}46359461 3to_ods29.17529.487500003{'sheet': 'pyexcel_sheet1'}16951713 4to_csv14.31515.7311000000108{}6356869856 5to_csv14.4388.1691000000108{'guess_datatypes': False}12241469261 6to_csv140.64599.45100000001080{'guess_datatypes': False}10055371100 7to_tsv13.83415.7631000000108{}6343972285 8to_text13.93715.6821000000108{}6376771751 9to_html12.5780.531000000228{}18867927950310to_hdf55.0112.3451000000316{}81004199600

    Conclusions

    Best:

    • to/from JSON wins with 2.3M rps read
    • to/from CSV/TSV/TEXT comes 2nd with config guess_datatypes=False with ~ 100k rps

    Worst:

    • to/from ods burst the memory footprint and hence had to be reduced to 100k rows. It also had the slowest read rate with 1450 rps.
    In\u00a0[17]: Copied!
    def contains_benchmark(table):\n    results = Table()\n    results.add_columns( \"column\", \"time (s)\" )\n    for name,col in table.columns.items():\n        n = len(col)\n        start,stop,step = int(n*0.02), int(n*0.98), int(n/100)\n        selection = col[start:stop:step]\n        total_time = 0.0\n        for v in selection:\n            start_time = perf_counter()\n            v in col  # <--- test!\n            end_time = perf_counter()\n            total_time += (end_time - start_time)\n        avg_time = total_time / len(selection)\n        results.add_rows( name, round(avg_time,3) )\n\n    return results\n
    def contains_benchmark(table): results = Table() results.add_columns( \"column\", \"time (s)\" ) for name,col in table.columns.items(): n = len(col) start,stop,step = int(n*0.02), int(n*0.98), int(n/100) selection = col[start:stop:step] total_time = 0.0 for v in selection: start_time = perf_counter() v in col # <--- test! end_time = perf_counter() total_time += (end_time - start_time) avg_time = total_time / len(selection) results.add_rows( name, round(avg_time,3) ) return results In\u00a0[18]: Copied!
    has_it = contains_benchmark(tables[-1])\nhas_it\n
    has_it = contains_benchmark(tables[-1]) has_it Out[18]: #columntime (s) 0#0.001 110.043 220.032 330.001 440.001 550.001 660.006 770.003 880.006 990.00710100.04311110.655 In\u00a0[19]: Copied!
    def slicing_benchmark(table):\n    n = len(table)\n    start,stop,step = int(0.02*n), int(0.98*n), int(n / 20)  # from 2% to 98% in 20 large steps\n    start_time = perf_counter()\n    snip = table[start:stop:step]\n    end_time = perf_counter()\n    print(f\"reading {len(table):,} rows to find {len(snip):,} rows took {end_time-start_time:.3f} sec\")\n    return snip\n
    def slicing_benchmark(table): n = len(table) start,stop,step = int(0.02*n), int(0.98*n), int(n / 20) # from 2% to 98% in 20 large steps start_time = perf_counter() snip = table[start:stop:step] end_time = perf_counter() print(f\"reading {len(table):,} rows to find {len(snip):,} rows took {end_time-start_time:.3f} sec\") return snip In\u00a0[20]: Copied!
    slice_it = slicing_benchmark(tables[-1])\n
    slice_it = slicing_benchmark(tables[-1])
    reading 50,000,000 rows to find 20 rows took 1.435 sec\n
    In\u00a0[22]: Copied!
    def column_selection_benchmark(tables):\n    results = Table()\n    results.add_columns( 'rows')\n    results.add_columns(*[f\"n cols={i}\" for i,_ in enumerate(tables[0].columns,start=1)])\n\n    for table in tables:\n        rr = [len(table)]\n        for ix, name in enumerate(table.columns):\n            cols = list(table.columns)[:ix+1]\n            start_time = perf_counter()\n            table[cols]\n            end_time = perf_counter()\n            rr.append(f\"{end_time-start_time:.5f}\")\n        results.add_rows( rr )\n    return results\n
    def column_selection_benchmark(tables): results = Table() results.add_columns( 'rows') results.add_columns(*[f\"n cols={i}\" for i,_ in enumerate(tables[0].columns,start=1)]) for table in tables: rr = [len(table)] for ix, name in enumerate(table.columns): cols = list(table.columns)[:ix+1] start_time = perf_counter() table[cols] end_time = perf_counter() rr.append(f\"{end_time-start_time:.5f}\") results.add_rows( rr ) return results In\u00a0[23]: Copied!
    csb = column_selection_benchmark(tables)\nprint(\"times below are are in seconds\")\ncsb\n
    csb = column_selection_benchmark(tables) print(\"times below are are in seconds\") csb
    times below are are in seconds\n
    Out[23]: #rowsn cols=1n cols=2n cols=3n cols=4n cols=5n cols=6n cols=7n cols=8n cols=9n cols=10n cols=11n cols=12 010000000.000010.000060.000040.000040.000040.000040.000040.000040.000040.000040.000040.00004 120000000.000010.000080.000030.000030.000030.000030.000030.000030.000030.000030.000040.00004 250000000.000010.000050.000040.000040.000040.000040.000040.000040.000040.000040.000040.00004 3100000000.000020.000050.000040.000040.000040.000040.000070.000050.000050.000050.000050.00005 4200000000.000030.000060.000050.000050.000050.000050.000060.000060.000060.000060.000060.00006 5500000000.000090.000110.000100.000090.000090.000090.000090.000090.000090.000090.000100.00009 In\u00a0[33]: Copied!
    def iterrows_benchmark(table):\n    results = Table()\n    results.add_columns( 'n columns', 'time (s)')\n\n    columns = ['1']\n    for column in list(table.columns):\n        columns.append(column)\n        snip = table[columns, slice(500_000,1_500_000)]\n        start_time = perf_counter()\n        counts = 0\n        for row in snip.rows:\n            counts += 1\n        end_time = perf_counter()\n        results.add_rows( len(columns), round(end_time-start_time,3))\n\n    return results\n
    def iterrows_benchmark(table): results = Table() results.add_columns( 'n columns', 'time (s)') columns = ['1'] for column in list(table.columns): columns.append(column) snip = table[columns, slice(500_000,1_500_000)] start_time = perf_counter() counts = 0 for row in snip.rows: counts += 1 end_time = perf_counter() results.add_rows( len(columns), round(end_time-start_time,3)) return results In\u00a0[34]: Copied!
    iterb = iterrows_benchmark(tables[-1])\niterb\n
    iterb = iterrows_benchmark(tables[-1]) iterb Out[34]: #n columnstime (s) 029.951 139.816 249.859 359.93 469.985 579.942 689.958 799.867 8109.96 9119.93210129.8311139.861 In\u00a0[35]: Copied!
    import matplotlib.pyplot as plt\nplt.plot(iterb['n columns'], iterb['time (s)'])\nplt.show()\n
    import matplotlib.pyplot as plt plt.plot(iterb['n columns'], iterb['time (s)']) plt.show() In\u00a0[28]: Copied!
    tables[-1].types()\n
    tables[-1].types() Out[28]:
    {'#': {int: 50000000},\n '1': {int: 50000000},\n '2': {str: 50000000},\n '3': {int: 50000000},\n '4': {int: 50000000},\n '5': {int: 50000000},\n '6': {str: 50000000},\n '7': {str: 50000000},\n '8': {str: 50000000},\n '9': {str: 50000000},\n '10': {float: 50000000},\n '11': {str: 50000000}}
    In\u00a0[29]: Copied!
    def dtypes_benchmark(tables):\n    dtypes_results = Table()\n    dtypes_results.add_columns(\"rows\", \"time (s)\")\n\n    for table in tables:\n        start_time = perf_counter()\n        dt = table.types()\n        end_time = perf_counter()\n        assert isinstance(dt, dict) and len(dt) != 0\n        dtypes_results.add_rows( len(table), round(end_time-start_time, 3) )\n\n    return dtypes_results\n
    def dtypes_benchmark(tables): dtypes_results = Table() dtypes_results.add_columns(\"rows\", \"time (s)\") for table in tables: start_time = perf_counter() dt = table.types() end_time = perf_counter() assert isinstance(dt, dict) and len(dt) != 0 dtypes_results.add_rows( len(table), round(end_time-start_time, 3) ) return dtypes_results In\u00a0[30]: Copied!
    dtype_b = dtypes_benchmark(tables)\ndtype_b\n
    dtype_b = dtypes_benchmark(tables) dtype_b Out[30]: #rowstime (s) 010000000.0 120000000.0 250000000.0 3100000000.0 4200000000.0 5500000000.001 In\u00a0[31]: Copied!
    def any_benchmark(tables):\n    results = Table()\n    results.add_columns(\"rows\", *list(tables[0].columns))\n\n    for table in tables:\n        tmp = [len(table)]\n        for column in list(table.columns):\n            v = table[column][0]\n            start_time = perf_counter()\n            _ = table.any(**{column: v})\n            end_time = perf_counter()           \n            tmp.append(round(end_time-start_time,3))\n\n        results.add_rows( tmp )\n    return results\n
    def any_benchmark(tables): results = Table() results.add_columns(\"rows\", *list(tables[0].columns)) for table in tables: tmp = [len(table)] for column in list(table.columns): v = table[column][0] start_time = perf_counter() _ = table.any(**{column: v}) end_time = perf_counter() tmp.append(round(end_time-start_time,3)) results.add_rows( tmp ) return results In\u00a0[32]: Copied!
    anyb = any_benchmark(tables)\nanyb\n
    anyb = any_benchmark(tables) anyb Out[32]: ~rows#1234567891011 010000000.1330.1330.1780.1330.2920.1470.1690.1430.2270.2590.1460.17 120000000.2680.2630.3430.2650.5670.2940.3350.2750.4640.5230.2890.323 250000000.6690.6530.9140.6691.4360.7230.8380.6941.1741.3350.6780.818 3100000001.3141.351.7451.3362.9021.491.6831.4142.3542.6181.3431.536 4200000002.5562.5343.3372.6025.6452.8273.2252.6464.5145.082.6933.083 5500000006.5716.4238.4556.69914.4847.9897.7986.25910.98912.486.7327.767 In\u00a0[36]: Copied!
    def all_benchmark(tables):\n    results = Table()\n    results.add_columns(\"rows\", *list(tables[0].columns))\n\n    for table in tables:\n        tmp = [len(table)]\n        for column in list(table.columns):\n            v = table[column][0]\n            start_time = perf_counter()\n            _ = table.all(**{column: v})\n            end_time = perf_counter()           \n            tmp.append(round(end_time-start_time,3))\n\n        results.add_rows( tmp )\n    return results\n
    def all_benchmark(tables): results = Table() results.add_columns(\"rows\", *list(tables[0].columns)) for table in tables: tmp = [len(table)] for column in list(table.columns): v = table[column][0] start_time = perf_counter() _ = table.all(**{column: v}) end_time = perf_counter() tmp.append(round(end_time-start_time,3)) results.add_rows( tmp ) return results In\u00a0[37]: Copied!
    allb = all_benchmark(tables)\nallb\n
    allb = all_benchmark(tables) allb Out[37]: ~rows#1234567891011 010000000.120.1210.1620.1220.2640.1380.1550.1270.2090.2370.1330.151 120000000.2370.2350.3110.2380.520.2660.2970.3410.4510.530.2610.285 250000000.6750.6980.9520.5941.6050.6590.8120.7191.2241.3530.6640.914 3100000001.3141.3321.7071.3323.0911.4631.7811.3662.3582.6381.4091.714 4200000002.5762.3133.112.3965.2072.5732.9212.4034.0414.6582.4632.808 5500000005.8965.827.735.95612.9097.457.275.98110.18311.5766.3727.414 In\u00a0[\u00a0]: Copied!
    \n
    In\u00a0[38]: Copied!
    def unique_benchmark(tables):\n    results = Table()\n    results.add_columns(\"rows\", *list(tables[0].columns))\n    \n    for table in tables:\n        length = len(table)\n\n        tmp = [len(table)]\n        for column in list(table.columns):\n            start_time = perf_counter()\n            try:\n                L = table[column].unique()\n                dt = perf_counter() - start_time\n            except MemoryError:\n                dt = -1\n            tmp.append(round(dt,3))\n            assert 0 < len(L) <= length    \n\n        results.add_rows( tmp )\n    return results\n
    def unique_benchmark(tables): results = Table() results.add_columns(\"rows\", *list(tables[0].columns)) for table in tables: length = len(table) tmp = [len(table)] for column in list(table.columns): start_time = perf_counter() try: L = table[column].unique() dt = perf_counter() - start_time except MemoryError: dt = -1 tmp.append(round(dt,3)) assert 0 < len(L) <= length results.add_rows( tmp ) return results In\u00a0[39]: Copied!
    ubm = unique_benchmark(tables)\nubm\n
    ubm = unique_benchmark(tables) ubm Out[39]: ~rows#1234567891011 010000000.0220.0810.2480.0440.0160.0610.1150.1360.0960.0850.0940.447 120000000.1760.2710.5050.0870.0310.1240.2290.2790.1980.170.3051.471 250000000.1980.4991.2630.2180.0760.3110.570.6850.4740.4250.5952.744 3100000000.5021.1232.5350.4330.1550.6151.1281.3750.960.851.3165.826 4200000000.9562.3365.0350.8830.3191.2292.2682.7481.9131.7462.73311.883 5500000002.3956.01912.4992.1780.7643.0735.6086.8194.8284.2797.09730.511 In\u00a0[40]: Copied!
    def index_benchmark(tables):\n    results = Table()\n    results.add_columns(\"rows\", *list(tables[0].columns))\n    \n    for table in tables:\n\n        tmp = [len(table)]\n        for column in list(table.columns):\n            start_time = perf_counter()\n            try:\n                _ = table.index(column)\n                dt = perf_counter() - start_time\n            except MemoryError:\n                dt = -1\n            tmp.append(round(dt,3))\n            \n        results.add_rows( tmp )\n    return results\n
    def index_benchmark(tables): results = Table() results.add_columns(\"rows\", *list(tables[0].columns)) for table in tables: tmp = [len(table)] for column in list(table.columns): start_time = perf_counter() try: _ = table.index(column) dt = perf_counter() - start_time except MemoryError: dt = -1 tmp.append(round(dt,3)) results.add_rows( tmp ) return results In\u00a0[41]: Copied!
    ibm = index_benchmark(tables)\nibm\n
    ibm = index_benchmark(tables) ibm Out[41]: ~rows#1234567891011 010000001.9491.7931.4321.1061.0511.231.3381.4931.4111.3031.9992.325 120000002.8833.5172.8562.2172.1242.4622.6762.9862.7092.6064.0494.461 250000006.3829.0497.0965.6285.3536.3126.6497.5216.716.45910.2710.747 31000000012.55318.50613.9511.33510.72412.50913.3315.05113.50212.89919.76921.999 42000000024.71737.89628.56822.66621.47226.32727.15730.06427.33225.82238.31143.399 55000000063.01697.07772.00755.60954.09961.79768.23675.0769.02266.15299.183109.969

    Multi-column index next:

    In\u00a0[42]: Copied!
    def multi_column_index_benchmark(tables):\n    \n    selection = [\"4\", \"7\", \"8\", \"9\"]\n    results = Table()\n    results.add_columns(\"rows\", *range(1,len(selection)+1))\n    \n    for table in tables:\n\n        tmp = [len(table)]\n        for index in range(1,5):\n            start_time = perf_counter()\n            try:\n                _ = table.index(*selection[:index])\n                dt = perf_counter() - start_time\n            except MemoryError:\n                dt = -1\n            tmp.append(round(dt,3))\n            print('.', end='')\n            \n        results.add_rows( tmp )\n    return results\n
    def multi_column_index_benchmark(tables): selection = [\"4\", \"7\", \"8\", \"9\"] results = Table() results.add_columns(\"rows\", *range(1,len(selection)+1)) for table in tables: tmp = [len(table)] for index in range(1,5): start_time = perf_counter() try: _ = table.index(*selection[:index]) dt = perf_counter() - start_time except MemoryError: dt = -1 tmp.append(round(dt,3)) print('.', end='') results.add_rows( tmp ) return results In\u00a0[43]: Copied!
    mcib = multi_column_index_benchmark(tables)\nmcib\n
    mcib = multi_column_index_benchmark(tables) mcib
    ........................
    Out[43]: #rows1234 010000001.0582.1333.2154.052 120000002.124.2786.5468.328 250000005.30310.8916.69320.793 31000000010.58122.40733.46241.91 42000000021.06445.95467.78184.828 55000000052.347109.551166.6211.053 In\u00a0[44]: Copied!
    def drop_duplicates_benchmark(tables):\n    results = Table()\n    results.add_columns(\"rows\", *list(tables[0].columns))\n    \n    for table in tables:\n        result = [len(table)]\n        cols = []\n        for name in list(table.columns):\n            cols.append(name)\n            start_time = perf_counter()\n            try:\n                _ = table.drop_duplicates(*cols)\n                dt = perf_counter() - start_time\n            except MemoryError:\n                dt = -1\n            result.append(round(dt,3))\n            print('.', end='')\n        \n        results.add_rows( result )\n    return results\n
    def drop_duplicates_benchmark(tables): results = Table() results.add_columns(\"rows\", *list(tables[0].columns)) for table in tables: result = [len(table)] cols = [] for name in list(table.columns): cols.append(name) start_time = perf_counter() try: _ = table.drop_duplicates(*cols) dt = perf_counter() - start_time except MemoryError: dt = -1 result.append(round(dt,3)) print('.', end='') results.add_rows( result ) return results In\u00a0[45]: Copied!
    ddb = drop_duplicates_benchmark(tables)\nddb\n
    ddb = drop_duplicates_benchmark(tables) ddb
    ........................................................................
    Out[45]: ~rows#1234567891011 010000001.7612.3583.3133.9014.6154.9615.8356.5347.4548.1088.8039.682 120000003.0114.936.9347.979.26410.26812.00613.51714.9216.63117.93219.493 250000006.82713.85318.63721.23724.54827.1131.15735.02638.99243.53146.02250.433 31000000013.23831.74641.14146.91753.17258.24167.99274.65182.7491.45897.666104.82 42000000025.93277.75100.34109.314123.514131.874148.432163.57179.121196.047208.686228.059 55000000064.237312.222364.886388.249429.724466.685494.418535.367581.666607.306634.343683.858"},{"location":"benchmarks/#benchmarks","title":"Benchmarks\u00b6","text":"

    These benchmarks seek to establish the performance of tablite as a user sees it.

    Overview

    Input/Output Various column functions Base functions Core functions - Save / Load .tpz format- Save tables to various formats- Import data from various formats - Setitem / getitem- iter- equal, not equal- copy- t += t- t *= t- contains- remove all- replace- index- unique- histogram- statistics- count - Setitem / getitem- iter / rows- equal, not equal- load- save- copy- stack- types- display_dict- show- to_dict- as_json_serializable- index - expression- filter- sort_index- reindex- drop_duplicates- sort- is_sorted- any- all- drop - replace- groupby- pivot- joins- lookup- replace missing values- transpose- pivot_transpose- diff"},{"location":"benchmarks/#input-output","title":"Input / Output\u00b6","text":""},{"location":"benchmarks/#create-tables-from-synthetic-data","title":"Create tables from synthetic data.\u00b6","text":""},{"location":"benchmarks/#save-load-tpz-format","title":"Save / Load .tpz format\u00b6","text":"

    Without default compression settings (10% slower than uncompressed, 20% of uncompressed filesize)

    "},{"location":"benchmarks/#save-load-tables-to-from-various-formats","title":"Save / load tables to / from various formats\u00b6","text":"

    The handlers for saving / export are:

    • to_sql
    • to_json
    • to_xls
    • to_ods
    • to_csv
    • to_tsv
    • to_text
    • to_html
    • to_hdf5
    "},{"location":"benchmarks/#various-column-functions","title":"Various column functions\u00b6","text":"
    • Setitem / getitem
    • iter
    • equal, not equal
    • copy
    • t += t
    • t *= t
    • contains
    • remove all
    • replace
    • index
    • unique
    • histogram
    • statistics
    • count
    "},{"location":"benchmarks/#various-table-functions","title":"Various table functions\u00b6","text":""},{"location":"benchmarks/#slicing","title":"Slicing\u00b6","text":"

    Slicing operations are used in many places.

    "},{"location":"benchmarks/#tabletypes","title":"Table.types()\u00b6","text":"

    Table.types() is implemented for near constant speed lookup.

    Here is an example:

    "},{"location":"benchmarks/#tableany","title":"Table.any\u00b6","text":""},{"location":"benchmarks/#tableall","title":"Table.all\u00b6","text":""},{"location":"benchmarks/#tablefilter","title":"Table.filter\u00b6","text":""},{"location":"benchmarks/#tableunique","title":"Table.unique\u00b6","text":""},{"location":"benchmarks/#tableindex","title":"Table.index\u00b6","text":"

    Single column index first:

    "},{"location":"benchmarks/#drop-duplicates","title":"drop duplicates\u00b6","text":""},{"location":"changelog/","title":"Changelog","text":"Version Change 2023.9.0 Adding Table.match operation. 2023.8.0 Nim backend for csv importer.Improve excel importer.Improve slicing consistency.Logical cores re-enabled on *nix based systems.Filter is now type safe.Added merge utility.Various bugfixes. 2023.6.5 Fix issues with get_headers falling back to text reading when reading 0 lines of excel, fix issue where reading excel file would ignore file count, excel file reader now has parity for linecount selection. 2023.6.4 Fix a logic bug in get_headers that caused one extra line to be returned than requested. 2023.6.3 Updated the way reference counting works. Tablite now tracks references to used pages and cleans them up based on number of references to those pages in the current process. This change allows to handle deep table clones when sending tables via processes (pickling/unpickling), whereas previous implementation would corrupt all tables using same pages due to reference counting asserting that all tables are shallow copies to the same object. 2023.6.2 Updated mplite dependency, changed to soft version requirement to prevent pipeline freezes due to small bugfixes in mplite. 2023.6.1 Major change of the backend processes. Speed up of ~6x. For more see the release notes 2022.11.19 Fixed some memory leaks. 2022.11.18 copy, filter, sort, any, all methods now properly respects the table subclass.Filter for tables with under SINGLE_PROCESSING_LIMIT rows will run on same process to reduce overhead.Errors within child processes now properly propagate to parent.Table.reset_storage(include_imports=True) now allows the user to reset the storage but exclude any imported files by setting include_imports=False during Table.reset(...).Bug: A column with 1,None,2 would be written to csv & tsv as \"1,None,2\". Now it is written \"1,,2\" where None means absent.Fix mp join producing mismatched columns lengths when different table lengths are used as an input or when join product is longer than the input table. 2022.11.17 Table.load now properly subclassess the table instead of always resulting in tablite.Table.Table.from_* methods now respect subclassess, fixed some from_* methods which were instance methods and not class methods.Fixed Table.from_dict only accepting list and tuple but not tablite.Column which is an equally valid type.Fix lookup parity in single process and multiple process outputs.Fix an issue with multiprocess lookup where no matches would throw instead of producing None.Fix an issue with filtering an empty table. 2022.11.16 Changed join to process 1M rows per task to avoid potential OOM on lower memory systems. Added mp_merge_columns to MemoryManager that merges column pages into a single column.Fix join parity in single process and multiple process outputs.Fix an issue with multiprocess join where no matches would throw instead of producing None. 2022.11.15 Bump mplite to avoid deadlock issues OS kill the process. 2022.11.14 Improve locking mechanism to allow retries when opening file as the previous solution could cause deadlocks when running multiple threads. 2022.11.13 Fix an issue with copying empty pages. 2022.11.12 Tablite now is now able to create it's own temporary directory. 2022.11.11 text_reader tqdm tracks the entire process now. text_reader properly respects free memory in *nix based systems. text_reader no longer discriminates against hyperthreaded cores. 2022.11.10 get_headers now uses plain openpyxl instead of pyexcel wrapper to speed up fetch times ~10x on certain files. 2022.11.9 get_headers can fail safe on unrecognized characters. 2022.11.8 Fix a bug with task size calculation on single core systems. 2022.11.7 Added TABLITE_TMPDIR environment variable for setting tablite work directory. Characters that fail to be read text reader due to improper encoding will be skipped. Fixed an issue where single column text files with no column delimiters would be imported as empty tables. 2022.11.6 Date inference fix 2022.11.5 Fixed negative slicing issues 2022.11.4 Transpose API changes: table.transpose(...) was renamed to table.pivot_transpose(...) new table.transpose() and table.T were added, it's functionality acts similarly to numpy.T, the column headers are used the first row in the table when transposing. 2022.11.3 Bugfix for non-ascii encoded strings during t.add_rows(...) 2022.11.2 As utf-8 is ascii compatible, the file reader utils selects utf-8 instead of ascii as a default. 2022.11.1 bugfix in datatypes.infer() where 1 was inferred as int, not float. 2022.11.0 New table features: Table.diff(other, columns=...), table.remove_duplicates_rows(), table.drop_na(*arg),table.replace(target,replacement), table.imputation(sources, targets, methods=...), table.to_pandas() and Table.from_pandas(pd.DataFrame),table.to_dict(columns, slice), Table.from_dict(),table.transpose(columns, keep, ...), New column features: Column.count(item), Column[:] is guaranteed to return a python list.Column.to_numpy(slice) returns np.ndarray. new tools library: from tablite import tools with: date_range(start,end), xround(value, multiple, up=None), and, guess as short-cut for Datatypes.guess(...). bugfixes: __eq__ was updated but missed __ne__.in operator in filter would crash if datatypes were not strings. 2022.10.11 filter now accepts any expression (str) that can be compiled by pythons compiler 2022.10.11 Bugfix for .any and .all. The code now executes much faster 2022.10.10 Bugfix for Table.import_file: import_as has been removed from keywords. 2022.10.10 All Table functions now have tqdm progressbar. 2022.10.10 More robust calculation for task size for multiprocessing. 2022.10.10 Dependency update: mplite==1.2.0 is now required. 2022.10.9 Bugfix for Table.import_file: files with duplicate header names would only have last duplicate name imported.Now the headers are made unique using name_x where x is a number. 2022.10.8 Bugfix for groupby: Where keys are empty error should have been raised.Where there are no functions, unique keypairs are returned. 2022.10.7 Bugfix for Column.statistics() for an empty column 2022.10.6 Bugfix for __setitem__: tbl['a'] = [] is now seen as tbl.add_column('a')Bugfix for __getitem__: calling a missing key raises keyerror. 2022.10.5 Bugfix for summary statistics. 2022.10.4 Bugfix for join shortcut. 2022.10.3 Bugfix for DataTypes where bool was evaluated wrongly 2022.10.0 Added ability to reindex in table.reindex(index=[0,1...,n,n-1]) 2022.9.0 Added ability to store python objects (example).Added warning when user iterates over non-rectangular dataset. 2022.8.0 Added table.export(path) which exports tablite Tables to file format given by the file extension. For example my_table.export('example.xlsx').supported formats are: json, html, xlsx, xls, csv, tsv, txt, ods and sql. 2022.7.8 Added ability to forward tqdm progressbar into Table.import_file(..., tqdm=your_tqdm), so that Jupyter notebook can use it in display-methods. 2022.7.7 Added method Table.to_sql() for export to ANSI-92 SQL enginesBugfix on to_json for timedelta. Jupyter notebook provides nice view using Table._repr_html_() JS-users can use .as_json_serializable where suitable. 2022.7.6 get_headers now takes argument (path, linecount=10) 2022.7.5 added helper Table.as_json_serializable as Jupyterkernel compat. 2022.7.4 adder helper Table.to_dict, and updated Table.to_json 2022.7.3 table.to_json now takes kwargs: row_count, columns, slice_, start_on 2022.7.2 documentation update. 2022.7.1 minor bugfix. 2022.7.0 BREAKING CHANGES- Tablite now uses HDF5 as backend. - Has multiprocessing enabled by default. - Is 20x faster. - Completely new API. 2022.6.0 DataTypes.guess([list of strings]) returns the best matching python datatype."},{"location":"tutorial/","title":"Tutorial","text":"In\u00a0[1]: Copied!
    from tablite import Table\n\n## To create a tablite table is as simple as populating a dictionary:\nt = Table({'A':[1,2,3], 'B':['a','b','c']})\n
    from tablite import Table ## To create a tablite table is as simple as populating a dictionary: t = Table({'A':[1,2,3], 'B':['a','b','c']}) In\u00a0[2]: Copied!
    ## In this notebook we can show tables in the HTML style:\nt\n
    ## In this notebook we can show tables in the HTML style: t Out[2]: #AB 01a 12b 23c In\u00a0[3]: Copied!
    ## or the ascii style:\nt.show()\n
    ## or the ascii style: t.show()
    +==+=+=+\n|# |A|B|\n+--+-+-+\n| 0|1|a|\n| 1|2|b|\n| 2|3|c|\n+==+=+=+\n
    In\u00a0[4]: Copied!
    ## or if you'd like to inspect the table, use:\nprint(str(t))\n
    ## or if you'd like to inspect the table, use: print(str(t))
    Table(2 columns, 3 rows)\n
    In\u00a0[5]: Copied!
    ## You can also add all columns at once (slower) if you prefer. \nt2 = Table(headers=('A','B'), rows=((1,'a'),(2,'b'),(3,'c')))\nassert t==t2\n
    ## You can also add all columns at once (slower) if you prefer. t2 = Table(headers=('A','B'), rows=((1,'a'),(2,'b'),(3,'c'))) assert t==t2 In\u00a0[6]: Copied!
    ## or load data:\nt3 = Table.from_file('tests/data/book1.csv')\n\n## to view any table in the notebook just let jupyter show the table. If you're using the terminal use .show(). \n## Note that show gives either first and last 7 rows or the whole table if it is less than 20 rows.\nt3\n
    ## or load data: t3 = Table.from_file('tests/data/book1.csv') ## to view any table in the notebook just let jupyter show the table. If you're using the terminal use .show(). ## Note that show gives either first and last 7 rows or the whole table if it is less than 20 rows. t3
    Collecting tasks: 'tests/data/book1.csv'\nDumping tasks: 'tests/data/book1.csv'\n
    importing file: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 1/1 [00:00<00:00, 487.82it/s]\n
    Out[6]: #abcdef 010.0606060610.0909090910.1212121210.1515151520.181818182 120.1212121210.2424242420.4848484850.969696971.939393939 230.2424242420.4848484850.969696971.9393939393.878787879 340.4848484850.969696971.9393939393.8787878797.757575758 450.969696971.9393939393.8787878797.75757575815.51515152 561.9393939393.8787878797.75757575815.5151515231.03030303 673.8787878797.75757575815.5151515231.0303030362.06060606.....................383916659267088.033318534175.066637068350.0133274000000.0266548000000.0394033318534175.066637068350.0133274000000.0266548000000.0533097000000.0404166637068350.0133274000000.0266548000000.0533097000000.01066190000000.04142133274000000.0266548000000.0533097000000.01066190000000.02132390000000.04243266548000000.0533097000000.01066190000000.02132390000000.04264770000000.04344533097000000.01066190000000.02132390000000.04264770000000.08529540000000.044451066190000000.02132390000000.04264770000000.08529540000000.017059100000000.0 In\u00a0[7]: Copied!
    ## should you however want to select the headers instead of importing everything\n## (which maybe timeconsuming), simply use get_headers(path)\nfrom tablite.tools import get_headers\nfrom pathlib import Path\npath = Path('tests/data/book1.csv')\nsample = get_headers(path, linecount=5)\nprint(f\"sample is of type {type(sample)} and has the following entries:\")\nfor k,v in sample.items():\n    print(k)\n    if isinstance(v,list):\n        for r in sample[k]:\n            print(\"\\t\", r)\n
    ## should you however want to select the headers instead of importing everything ## (which maybe timeconsuming), simply use get_headers(path) from tablite.tools import get_headers from pathlib import Path path = Path('tests/data/book1.csv') sample = get_headers(path, linecount=5) print(f\"sample is of type {type(sample)} and has the following entries:\") for k,v in sample.items(): print(k) if isinstance(v,list): for r in sample[k]: print(\"\\t\", r)
    sample is of type <class 'dict'> and has the following entries:\ndelimiter\nbook1.csv\n\t ['a', 'b', 'c', 'd', 'e', 'f']\n\t ['1', '0.060606061', '0.090909091', '0.121212121', '0.151515152', '0.181818182']\n\t ['2', '0.121212121', '0.242424242', '0.484848485', '0.96969697', '1.939393939']\n\t ['3', '0.242424242', '0.484848485', '0.96969697', '1.939393939', '3.878787879']\n\t ['4', '0.484848485', '0.96969697', '1.939393939', '3.878787879', '7.757575758']\n\t ['5', '0.96969697', '1.939393939', '3.878787879', '7.757575758', '15.51515152']\n
    In\u00a0[8]: Copied!
    ## to extend a table by adding columns, use t[new] = [new values]\nt['C'] = [4,5,6]\n## but make sure the column has the same length as the rest of the table!\nt\n
    ## to extend a table by adding columns, use t[new] = [new values] t['C'] = [4,5,6] ## but make sure the column has the same length as the rest of the table! t Out[8]: #ABC 01a4 12b5 23c6 In\u00a0[9]: Copied!
    ## should you want to mix datatypes, tablite will not complain:\nfrom datetime import datetime, date,time,timedelta\nimport numpy as np\n## What you put in ...\nt4 = Table()\nt4['mixed'] = [\n    -1,0,1,  # regular integers\n    -12345678909876543211234567890987654321,  # very very large integer\n    None,np.nan,  # null values \n    \"one\", \"\",  # strings\n    True,False,  # booleans\n    float('inf'), 0.01,  # floats\n    date(2000,1,1),   # date\n    datetime(2002,2,3,23,0,4,6660),  # datetime\n    time(12,12,12),  # time\n    timedelta(days=3, seconds=5678)  # timedelta\n]\n## ... is exactly what you get out:\nt4\n
    ## should you want to mix datatypes, tablite will not complain: from datetime import datetime, date,time,timedelta import numpy as np ## What you put in ... t4 = Table() t4['mixed'] = [ -1,0,1, # regular integers -12345678909876543211234567890987654321, # very very large integer None,np.nan, # null values \"one\", \"\", # strings True,False, # booleans float('inf'), 0.01, # floats date(2000,1,1), # date datetime(2002,2,3,23,0,4,6660), # datetime time(12,12,12), # time timedelta(days=3, seconds=5678) # timedelta ] ## ... is exactly what you get out: t4 Out[9]: #mixed 0-1 10 21 3-12345678909876543211234567890987654321 4None 5nan 6one 7 8True 9False10inf110.01122000-01-01132002-02-03 23:00:04.0066601412:12:12153 days, 1:34:38 In\u00a0[10]: Copied!
    ## also if you claim the values back as a python list:\nfor item in list(t4['mixed']):\n    print(item)\n
    ## also if you claim the values back as a python list: for item in list(t4['mixed']): print(item)
    -1\n0\n1\n-12345678909876543211234567890987654321\nNone\nnan\none\n\nTrue\nFalse\ninf\n0.01\n2000-01-01\n2002-02-03 23:00:04.006660\n12:12:12\n3 days, 1:34:38\n

    The column itself (__repr__) shows us the pid, file location and the entries, so you know exactly what you're working with.

    In\u00a0[11]: Copied!
    t4['mixed']\n
    t4['mixed'] Out[11]:
    Column(/tmp/tablite-tmp/pid-54911, [-1 0 1 -12345678909876543211234567890987654321 None nan 'one' '' True\n False inf 0.01 datetime.date(2000, 1, 1)\n datetime.datetime(2002, 2, 3, 23, 0, 4, 6660) datetime.time(12, 12, 12)\n datetime.timedelta(days=3, seconds=5678)])
    In\u00a0[12]: Copied!
    ## to view the datatypes in a column, use Column.types()\ntype_dict = t4['mixed'].types()\nfor k,v in type_dict.items():\n    print(k,v)\n
    ## to view the datatypes in a column, use Column.types() type_dict = t4['mixed'].types() for k,v in type_dict.items(): print(k,v)
    <class 'int'> 4\n<class 'NoneType'> 1\n<class 'float'> 3\n<class 'str'> 2\n<class 'bool'> 2\n<class 'datetime.date'> 1\n<class 'datetime.datetime'> 1\n<class 'datetime.time'> 1\n<class 'datetime.timedelta'> 1\n
    In\u00a0[13]: Copied!
    ## You may have noticed that all datatypes in t3 where identified as floats, despite their origin from a text type file.\n## This is because tablite guesses the most probable datatype using the `.guess` function on each column.\n## You can use the .guess function like this:\nfrom tablite import DataTypes\nt3['a'] = DataTypes.guess(t3['a'])\n## You can also convert the datatype using a list comprehension\nt3['b'] = [float(v) for v in t3['b']]\nt3\n
    ## You may have noticed that all datatypes in t3 where identified as floats, despite their origin from a text type file. ## This is because tablite guesses the most probable datatype using the `.guess` function on each column. ## You can use the .guess function like this: from tablite import DataTypes t3['a'] = DataTypes.guess(t3['a']) ## You can also convert the datatype using a list comprehension t3['b'] = [float(v) for v in t3['b']] t3 Out[13]: #abcdef 010.0606060610.0909090910.1212121210.1515151520.181818182 120.1212121210.2424242420.4848484850.969696971.939393939 230.2424242420.4848484850.969696971.9393939393.878787879 340.4848484850.969696971.9393939393.8787878797.757575758 450.969696971.9393939393.8787878797.75757575815.51515152 561.9393939393.8787878797.75757575815.5151515231.03030303 673.8787878797.75757575815.5151515231.0303030362.06060606.....................383916659267088.033318534175.066637068350.0133274000000.0266548000000.0394033318534175.066637068350.0133274000000.0266548000000.0533097000000.0404166637068350.0133274000000.0266548000000.0533097000000.01066190000000.04142133274000000.0266548000000.0533097000000.01066190000000.02132390000000.04243266548000000.0533097000000.01066190000000.02132390000000.04264770000000.04344533097000000.01066190000000.02132390000000.04264770000000.08529540000000.044451066190000000.02132390000000.04264770000000.08529540000000.017059100000000.0 In\u00a0[14]: Copied!
    t = Table()\nfor column_name in 'abcde':\n    t[column_name] =[i for i in range(5)]\n
    t = Table() for column_name in 'abcde': t[column_name] =[i for i in range(5)]

    (2) we want to add two new columns using the functions:

    In\u00a0[15]: Copied!
    def f1(a,b,c):\n    return a+b+c+1\ndef f2(b,c,d):\n    return b*c*d\n
    def f1(a,b,c): return a+b+c+1 def f2(b,c,d): return b*c*d

    (3) and we want to compute two new columns f and g:

    In\u00a0[16]: Copied!
    t.add_columns('f', 'g')\n
    t.add_columns('f', 'g')

    (4) we can now use the filter, to iterate over the table, and add the values to the two new columns:

    In\u00a0[17]: Copied!
    f,g=[],[]\nfor row in t['a', 'b', 'c', 'd'].rows:\n    a, b, c, d = row\n\n    f.append(f1(a, b, c))\n    g.append(f2(b, c, d))\nt['f'] = f\nt['g'] = g\n\nassert len(t) == 5\nassert list(t.columns) == list('abcdefg')\nt\n
    f,g=[],[] for row in t['a', 'b', 'c', 'd'].rows: a, b, c, d = row f.append(f1(a, b, c)) g.append(f2(b, c, d)) t['f'] = f t['g'] = g assert len(t) == 5 assert list(t.columns) == list('abcdefg') t Out[17]: #abcdefg 00000010 11111141 22222278 3333331027 4444441364

    Take note that if your dataset is assymmetric, a warning will be show:

    In\u00a0[18]: Copied!
    assymmetric_table = Table({'a':[1,2,3], 'b':[1,2]})\nfor row in assymmetric_table.rows:\n    print(row)\n## warning at the bottom ---v\n
    assymmetric_table = Table({'a':[1,2,3], 'b':[1,2]}) for row in assymmetric_table.rows: print(row) ## warning at the bottom ---v
    [1, 1]\n[2, 2]\n[3, None]\n
    /home/bjorn/github/tablite/tablite/base.py:1188: UserWarning: Column b has length 2 / 3. None will appear as fill value.\n  warnings.warn(f\"Column {name} has length {len(column)} / {n_max}. None will appear as fill value.\")\n
    In\u00a0[19]: Copied!
    table7 = Table(columns={\n'A': [1,1,2,2,3,4],\n'B': [1,1,2,2,30,40],\n'C': [-1,-2,-3,-4,-5,-6]\n})\nindex = table7.index('A', 'B')\nfor k, v in index.items():\n    print(\"key\", k, \"indices\", v)\n
    table7 = Table(columns={ 'A': [1,1,2,2,3,4], 'B': [1,1,2,2,30,40], 'C': [-1,-2,-3,-4,-5,-6] }) index = table7.index('A', 'B') for k, v in index.items(): print(\"key\", k, \"indices\", v)
    key (1, 1) indices [0, 1]\nkey (2, 2) indices [2, 3]\nkey (3, 30) indices [4]\nkey (4, 40) indices [5]\n

    The keys are created for each unique column-key-pair, and the value is the index where the key is found. To fetch all rows for key (2,2), we can use:

    In\u00a0[20]: Copied!
    for ix, row in enumerate(table7.rows):\n    if ix in index[(2,2)]:\n        print(row)\n
    for ix, row in enumerate(table7.rows): if ix in index[(2,2)]: print(row)
    [2, 2, -3]\n[2, 2, -4]\n
    In\u00a0[21]: Copied!
    ## to append one table to another, use + or += \nprint('length before:', len(t3))  # length before: 45\nt5 = t3 + t3  \nprint('length after +', len(t5))  # length after + 90\nt5 += t3 \nprint('length after +=', len(t5))  # length after += 135\n## if you need a lot of numbers for a test, you can repeat a table using * and *=\nt5 *= 1_000\nprint('length after +=', len(t5))  # length after += 135000\n
    ## to append one table to another, use + or += print('length before:', len(t3)) # length before: 45 t5 = t3 + t3 print('length after +', len(t5)) # length after + 90 t5 += t3 print('length after +=', len(t5)) # length after += 135 ## if you need a lot of numbers for a test, you can repeat a table using * and *= t5 *= 1_000 print('length after +=', len(t5)) # length after += 135000
    length before: 45\nlength after + 90\nlength after += 135\nlength after += 135000\n
    In\u00a0[22]: Copied!
    t5\n
    t5 Out[22]: #abcdef 010.0606060610.0909090910.1212121210.1515151520.181818182 120.1212121210.2424242420.4848484850.969696971.939393939 230.2424242420.4848484850.969696971.9393939393.878787879 340.4848484850.969696971.9393939393.8787878797.757575758 450.969696971.9393939393.8787878797.75757575815.51515152 561.9393939393.8787878797.75757575815.5151515231.03030303 673.8787878797.75757575815.5151515231.0303030362.06060606..................... 134,9933916659267088.033318534175.066637068350.0133274000000.0266548000000.0 134,9944033318534175.066637068350.0133274000000.0266548000000.0533097000000.0 134,9954166637068350.0133274000000.0266548000000.0533097000000.01066190000000.0 134,99642133274000000.0266548000000.0533097000000.01066190000000.02132390000000.0 134,99743266548000000.0533097000000.01066190000000.02132390000000.04264770000000.0 134,99844533097000000.01066190000000.02132390000000.04264770000000.08529540000000.0 134,999451066190000000.02132390000000.04264770000000.08529540000000.017059100000000.0 In\u00a0[23]: Copied!
    ## if your are in doubt whether your tables will be the same you can use .stack(other)\nassert t.columns != t2.columns  # compares list of column names.\nt6 = t.stack(t2)\nt6\n
    ## if your are in doubt whether your tables will be the same you can use .stack(other) assert t.columns != t2.columns # compares list of column names. t6 = t.stack(t2) t6 Out[23]: #abcdefgAB 00000010NoneNone 11111141NoneNone 22222278NoneNone 3333331027NoneNone 4444441364NoneNone 5NoneNoneNoneNoneNoneNoneNone1a 6NoneNoneNoneNoneNoneNoneNone2b 7NoneNoneNoneNoneNoneNoneNone3c In\u00a0[24]: Copied!
    ## As you can see above, t6['C'] is padded with \"None\" where t2 was missing the columns.\n\n## if you need a more detailed view of the columns you can iterate:\nfor name in t.columns:\n    col_from_t = t[name]\n    if name in t2.columns:\n        col_from_t2 = t2[name]\n        print(name, col_from_t == col_from_t2)\n    else:\n        print(name, \"not in t2\")\n
    ## As you can see above, t6['C'] is padded with \"None\" where t2 was missing the columns. ## if you need a more detailed view of the columns you can iterate: for name in t.columns: col_from_t = t[name] if name in t2.columns: col_from_t2 = t2[name] print(name, col_from_t == col_from_t2) else: print(name, \"not in t2\")
    a not in t2\nb not in t2\nc not in t2\nd not in t2\ne not in t2\nf not in t2\ng not in t2\n
    In\u00a0[25]: Copied!
    ## to make a copy of a table, use table.copy()\nt3_copy = t3.copy()\n\n## you can also perform multi criteria selections using getitem [ ... ]\nt3_slice = t3['a','b','d', 5:25:5]\nt3_slice\n
    ## to make a copy of a table, use table.copy() t3_copy = t3.copy() ## you can also perform multi criteria selections using getitem [ ... ] t3_slice = t3['a','b','d', 5:25:5] t3_slice Out[25]: #abd 061.9393939397.757575758 11162.06060606248.2424242 2161985.9393947943.757576 32163550.06061254200.2424 In\u00a0[26]: Copied!
    ##deleting items also works the same way:\ndel t3_slice[1:3]  # delete row number 2 & 3 \nt3_slice\n
    ##deleting items also works the same way: del t3_slice[1:3] # delete row number 2 & 3 t3_slice Out[26]: #abd 061.9393939397.757575758 12163550.06061254200.2424 In\u00a0[27]: Copied!
    ## to wipe a table, use .clear:\nt3_slice.clear()\nt3_slice\n
    ## to wipe a table, use .clear: t3_slice.clear() t3_slice Out[27]: Empty Table In\u00a0[28]: Copied!
    ## tablite uses .npy for storage because it is fast.\n## this means you can make a table persistent using .save\nlocal_file = Path(\"local_file.tpz\")\nt5.save(local_file)\n\nold_t5 = Table.load(local_file)\nprint(\"the t5 table had\", len(old_t5), \"rows\")  # the t5 table had 135000 rows\n\ndel old_t5  # only removes the in-memory object\n\nprint(\"old_t5 still exists?\", local_file.exists())\nprint(\"path:\", local_file)\n\nimport os\nos.remove(local_file)\n
    ## tablite uses .npy for storage because it is fast. ## this means you can make a table persistent using .save local_file = Path(\"local_file.tpz\") t5.save(local_file) old_t5 = Table.load(local_file) print(\"the t5 table had\", len(old_t5), \"rows\") # the t5 table had 135000 rows del old_t5 # only removes the in-memory object print(\"old_t5 still exists?\", local_file.exists()) print(\"path:\", local_file) import os os.remove(local_file)
    loading 'local_file.tpz' file:  55%|\u2588\u2588\u2588\u2588\u2588\u258d    | 9851/18000 [00:02<00:01, 4386.96it/s]
    loading 'local_file.tpz' file: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 18000/18000 [00:04<00:00, 4417.27it/s]\n
    the t5 table had 135000 rows\nold_t5 still exists? True\npath: local_file.tpz\n

    If you want to save a table from one session to another use save=True. This tells the garbage collector to leave the tablite Table on disk, so you can load it again without changing your code.

    For example:

    First time you run t = Table.import_file(....big.csv) it may take a minute or two.

    If you then add t.save=True and restart python, the second time you run t = Table.import_file(....big.csv) it will take a few milliseconds instead of minutes.

    In\u00a0[29]: Copied!
    unfiltered = Table({'a':[1,2,3,4], 'b':[10,20,30,40]})\n
    unfiltered = Table({'a':[1,2,3,4], 'b':[10,20,30,40]}) In\u00a0[30]: Copied!
    true,false = unfiltered.filter(\n    [\n        {\"column1\": 'a', \"criteria\":\">=\", 'value2':3}\n    ], filter_type='all'\n)\n
    true,false = unfiltered.filter( [ {\"column1\": 'a', \"criteria\":\">=\", 'value2':3} ], filter_type='all' ) In\u00a0[31]: Copied!
    true\n
    true Out[31]: #ab 0330 1440 In\u00a0[32]: Copied!
    false.show()  # using show here to show that terminal users can have a nice view too.\n
    false.show() # using show here to show that terminal users can have a nice view too.
    +==+=+==+\n|# |a|b |\n+--+-+--+\n| 0|1|10|\n| 1|2|20|\n+==+=+==+\n
    In\u00a0[33]: Copied!
    ty = Table({'a':[1,2,3,4],'b': [10,20,30,40]})\n
    ty = Table({'a':[1,2,3,4],'b': [10,20,30,40]}) In\u00a0[34]: Copied!
    ## typical python\nany(i > 3 for i in ty['a'])\n
    ## typical python any(i > 3 for i in ty['a']) Out[34]:
    True
    In\u00a0[35]: Copied!
    ## hereby you can do:\nany( ty.any(**{'a':lambda x:x>3}).rows )\n
    ## hereby you can do: any( ty.any(**{'a':lambda x:x>3}).rows ) Out[35]:
    True
    In\u00a0[36]: Copied!
    ## if you have multiple criteria this also works:\nall( ty.all(**{'a': lambda x:x>=2, 'b': lambda x:x<=30}).rows )\n
    ## if you have multiple criteria this also works: all( ty.all(**{'a': lambda x:x>=2, 'b': lambda x:x<=30}).rows ) Out[36]:
    True
    In\u00a0[37]: Copied!
    ## or this if you want to see the table.\nty.all(a=lambda x:x>2, b=lambda x:x<=30)\n
    ## or this if you want to see the table. ty.all(a=lambda x:x>2, b=lambda x:x<=30) Out[37]: #ab 0330 In\u00a0[38]: Copied!
    ## As `all` and `any` returns tables, this also means that you can chain operations:\nty.any(a=lambda x:x>2).any(b=30)\n
    ## As `all` and `any` returns tables, this also means that you can chain operations: ty.any(a=lambda x:x>2).any(b=30) Out[38]: #ab 0330 In\u00a0[39]: Copied!
    table = Table({\n    'A':[ 1, None, 8, 3, 4, 6,  5,  7,  9],\n    'B':[10,'100', 1, 1, 1, 1, 10, 10, 10],\n    'C':[ 0,    1, 0, 1, 0, 1,  0,  1,  0],\n})\ntable\n
    table = Table({ 'A':[ 1, None, 8, 3, 4, 6, 5, 7, 9], 'B':[10,'100', 1, 1, 1, 1, 10, 10, 10], 'C':[ 0, 1, 0, 1, 0, 1, 0, 1, 0], }) table Out[39]: #ABC 01100 1None1001 2810 3311 4410 5611 65100 77101 89100 In\u00a0[40]: Copied!
    sort_order = {'B': False, 'C': False, 'A': False}\nassert not table.is_sorted(mapping=sort_order)\n\nsorted_table = table.sort(mapping=sort_order)\nsorted_table\n
    sort_order = {'B': False, 'C': False, 'A': False} assert not table.is_sorted(mapping=sort_order) sorted_table = table.sort(mapping=sort_order) sorted_table
    creating sort index: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 3/3 [00:00<00:00, 2719.45it/s]\ncreating sort index: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 3/3 [00:00<00:00, 3434.20it/s]\njoin: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 3/3 [00:00<00:00, 1902.47it/s]\n

    Sort is reasonable effective as it uses multiprocessing above a million fields.

    Hint: You can set this limit in tablite.config, like this:

    In\u00a0[41]: Copied!
    from tablite.config import Config\nprint(f\"multiprocessing is used above {Config.SINGLE_PROCESSING_LIMIT:,} fields\")\n
    from tablite.config import Config print(f\"multiprocessing is used above {Config.SINGLE_PROCESSING_LIMIT:,} fields\")
    multiprocessing is used above 1,000,000 fields\n
    In\u00a0[42]: Copied!
    import math\nn = math.ceil(1_000_000 / (9*3))\n\ntable = Table({\n    'A':[ 1, None, 8, 3, 4, 6,  5,  7,  9]*n,\n    'B':[10,'100', 1, 1, 1, 1, 10, 10, 10]*n,\n    'C':[ 0,    1, 0, 1, 0, 1,  0,  1,  0]*n,\n})\ntable\n
    import math n = math.ceil(1_000_000 / (9*3)) table = Table({ 'A':[ 1, None, 8, 3, 4, 6, 5, 7, 9]*n, 'B':[10,'100', 1, 1, 1, 1, 10, 10, 10]*n, 'C':[ 0, 1, 0, 1, 0, 1, 0, 1, 0]*n, }) table Out[42]: #ABC 01100 1None1001 2810 3311 4410 5611 65100............ 333,335810 333,336311 333,337410 333,338611 333,3395100 333,3407101 333,3419100 In\u00a0[43]: Copied!
    import time as cputime\nstart = cputime.time()\nsort_order = {'B': False, 'C': False, 'A': False}\nsorted_table = table.sort(mapping=sort_order)  # sorts 1M values.\nprint(\"table sorting took \", round(cputime.time() - start,3), \"secs\")\nsorted_table\n
    import time as cputime start = cputime.time() sort_order = {'B': False, 'C': False, 'A': False} sorted_table = table.sort(mapping=sort_order) # sorts 1M values. print(\"table sorting took \", round(cputime.time() - start,3), \"secs\") sorted_table
    creating sort index: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 3/3 [00:00<00:00,  4.20it/s]\njoin: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 3/3 [00:00<00:00, 18.17it/s]
    table sorting took  0.913 secs\n
    \n
    In\u00a0[44]: Copied!
    n = math.ceil(1_000_000 / (9*3))\n\ntable = Table({\n    'A':[ 1, None, 8, 3, 4, 6,  5,  7,  9]*n,\n    'B':[10,'100', 1, 1, 1, 1, 10, 10, 10]*n,\n    'C':[ 0,    1, 0, 1, 0, 1,  0,  1,  0]*n,\n})\ntable\n
    n = math.ceil(1_000_000 / (9*3)) table = Table({ 'A':[ 1, None, 8, 3, 4, 6, 5, 7, 9]*n, 'B':[10,'100', 1, 1, 1, 1, 10, 10, 10]*n, 'C':[ 0, 1, 0, 1, 0, 1, 0, 1, 0]*n, }) table Out[44]: #ABC 01100 1None1001 2810 3311 4410 5611 65100............ 333,335810 333,336311 333,337410 333,338611 333,3395100 333,3407101 333,3419100 In\u00a0[45]: Copied!
    from tablite import GroupBy as gb\ngrpby = table.groupby(keys=['C', 'B'], functions=[('A', gb.count)])\ngrpby\n
    from tablite import GroupBy as gb grpby = table.groupby(keys=['C', 'B'], functions=[('A', gb.count)]) grpby
    groupby: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 333342/333342 [00:00<00:00, 427322.50it/s]\n
    Out[45]: #CBCount(A) 0010111114 1110037038 20174076 31174076 411037038

    Here is the list of groupby functions:

    class GroupBy(object):    \n    max = Max  # shortcuts to avoid having to type a long list of imports.\n    min = Min\n    sum = Sum\n    product = Product\n    first = First\n    last = Last\n    count = Count\n    count_unique = CountUnique\n    avg = Average\n    stdev = StandardDeviation\n    median = Median\n    mode = Mode\n
    In\u00a0[46]: Copied!
    t = Table({\n    'A':[1, 1, 2, 2, 3, 3] * 2,\n    'B':[1, 2, 3, 4, 5, 6] * 2,\n    'C':[6, 5, 4, 3, 2, 1] * 2,\n})\nt\n
    t = Table({ 'A':[1, 1, 2, 2, 3, 3] * 2, 'B':[1, 2, 3, 4, 5, 6] * 2, 'C':[6, 5, 4, 3, 2, 1] * 2, }) t Out[46]: #ABC 0116 1125 2234 3243 4352 5361 6116 7125 8234 92431035211361 In\u00a0[47]: Copied!
    t2 = t.pivot(rows=['C'], columns=['A'], functions=[('B', gb.sum), ('B', gb.count)], values_as_rows=False)\nt2\n
    t2 = t.pivot(rows=['C'], columns=['A'], functions=[('B', gb.sum), ('B', gb.count)], values_as_rows=False) t2
    pivot: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 14/14 [00:00<00:00, 3643.83it/s]\n
    Out[47]: #CSum(B,A=1)Count(B,A=1)Sum(B,A=2)Count(B,A=2)Sum(B,A=3)Count(B,A=3) 0622NoneNoneNoneNone 1542NoneNoneNoneNone 24NoneNone62NoneNone 33NoneNone82NoneNone 42NoneNoneNoneNone102 51NoneNoneNoneNone122 In\u00a0[48]: Copied!
    numbers = Table()\nnumbers.add_column('number', data=[      1,      2,       3,       4,   None])\nnumbers.add_column('colour', data=['black', 'blue', 'white', 'white', 'blue'])\n\nletters = Table()\nletters.add_column('letter', data=[  'a',     'b',      'c',     'd',   None])\nletters.add_column('color', data=['blue', 'white', 'orange', 'white', 'blue'])\n
    numbers = Table() numbers.add_column('number', data=[ 1, 2, 3, 4, None]) numbers.add_column('colour', data=['black', 'blue', 'white', 'white', 'blue']) letters = Table() letters.add_column('letter', data=[ 'a', 'b', 'c', 'd', None]) letters.add_column('color', data=['blue', 'white', 'orange', 'white', 'blue']) In\u00a0[49]: Copied!
    ## left join\n## SELECT number, letter FROM numbers LEFT JOIN letters ON numbers.colour == letters.color\nleft_join = numbers.left_join(letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter'])\nleft_join\n
    ## left join ## SELECT number, letter FROM numbers LEFT JOIN letters ON numbers.colour == letters.color left_join = numbers.left_join(letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter']) left_join
    join: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 2/2 [00:00<00:00, 1221.94it/s]\n
    Out[49]: #numberletter 01None 12a 22None 3Nonea 4NoneNone 53b 63d 74b 84d In\u00a0[50]: Copied!
    ## inner join\n## SELECT number, letter FROM numbers JOIN letters ON numbers.colour == letters.color\ninner_join = numbers.inner_join(letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter'])\ninner_join\n
    ## inner join ## SELECT number, letter FROM numbers JOIN letters ON numbers.colour == letters.color inner_join = numbers.inner_join(letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter']) inner_join
    join: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 2/2 [00:00<00:00, 1121.77it/s]\n
    Out[50]: #numberletter 02a 12None 2Nonea 3NoneNone 43b 53d 64b 74d In\u00a0[51]: Copied!
    # outer join\n## SELECT number, letter FROM numbers OUTER JOIN letters ON numbers.colour == letters.color\nouter_join = numbers.outer_join(letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter'])\nouter_join\n
    # outer join ## SELECT number, letter FROM numbers OUTER JOIN letters ON numbers.colour == letters.color outer_join = numbers.outer_join(letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter']) outer_join
    join: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 2/2 [00:00<00:00, 1585.15it/s]\n
    Out[51]: #numberletter 01None 12a 22None 3Nonea 4NoneNone 53b 63d 74b 84d 9Nonec

    Q: But ...I think there's a bug in the join... A: Venn diagrams do not explain joins.

    A Venn diagram is a widely-used diagram style that shows the logical relation between sets, popularised by John Venn in the 1880s. The diagrams are used to teach elementary set theory, and to illustrate simple set relationshipssource: en.wikipedia.org

    Joins operate over rows and when there are duplicate rows, these will be replicated in the output. Many beginners are surprised by this, because they didn't read the SQL standard.

    Q: So what do I do? A: If you want to get rid of duplicates using tablite, use the index functionality across all columns and pick the first row from each index. Here's the recipe that starts with plenty of duplicates:

    In\u00a0[52]: Copied!
    old_table = Table({\n'A':[1,1,1,2,2,2,3,3,3],\n'B':[1,1,4,2,2,5,3,3,6],\n})\nold_table\n
    old_table = Table({ 'A':[1,1,1,2,2,2,3,3,3], 'B':[1,1,4,2,2,5,3,3,6], }) old_table Out[52]: #AB 011 111 214 322 422 525 633 733 836 In\u00a0[53]: Copied!
    ## CREATE TABLE OF UNIQUE ENTRIES (a.k.a. DEDUPLICATE)\nnew_table = old_table.drop_duplicates()\nnew_table\n
    ## CREATE TABLE OF UNIQUE ENTRIES (a.k.a. DEDUPLICATE) new_table = old_table.drop_duplicates() new_table
    9it [00:00, 11329.15it/s]\njoin: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 2/2 [00:00<00:00, 1819.26it/s]\n
    Out[53]: #AB 011 114 222 325 433 536

    You can also use groupby; We'll get to that in a minute.

    Lookup is a special case of a search loop: Say for example you are planning a concert and want to make sure that your friends can make it home using public transport: You would have to find the first departure after the concert ends towards their home. A join would only give you a direct match on the time.

    Lookup allows you \"to iterate through a list of data and find the first match given a set of criteria.\"

    Here's an example:

    First we have our list of friends and their stops.

    In\u00a0[54]: Copied!
    friends = Table({\n\"name\":['Alice', 'Betty', 'Charlie', 'Dorethy', 'Edward', 'Fred'],\n\"stop\":['Downtown-1', 'Downtown-2', 'Hillside View', 'Hillside Crescent', 'Downtown-2', 'Chicago'],\n})\nfriends\n
    friends = Table({ \"name\":['Alice', 'Betty', 'Charlie', 'Dorethy', 'Edward', 'Fred'], \"stop\":['Downtown-1', 'Downtown-2', 'Hillside View', 'Hillside Crescent', 'Downtown-2', 'Chicago'], }) friends Out[54]: #namestop 0AliceDowntown-1 1BettyDowntown-2 2CharlieHillside View 3DorethyHillside Crescent 4EdwardDowntown-2 5FredChicago

    Next we need a list of bus routes and their time and stops. I don't have that, so I'm making one up:

    In\u00a0[55]: Copied!
    import random\nrandom.seed(11)\ntable_size = 40\n\ntimes = [DataTypes.time(random.randint(21, 23), random.randint(0, 59)) for i in range(table_size)]\nstops = ['Stadium', 'Hillside', 'Hillside View', 'Hillside Crescent', 'Downtown-1', 'Downtown-2',\n            'Central station'] * 2 + [f'Random Road-{i}' for i in range(table_size)]\nroute = [random.choice([1, 2, 3]) for i in stops]\n
    import random random.seed(11) table_size = 40 times = [DataTypes.time(random.randint(21, 23), random.randint(0, 59)) for i in range(table_size)] stops = ['Stadium', 'Hillside', 'Hillside View', 'Hillside Crescent', 'Downtown-1', 'Downtown-2', 'Central station'] * 2 + [f'Random Road-{i}' for i in range(table_size)] route = [random.choice([1, 2, 3]) for i in stops] In\u00a0[56]: Copied!
    bus_table = Table({\n\"time\":times,\n\"stop\":stops[:table_size],\n\"route\":route[:table_size],\n})\nbus_table.sort(mapping={'time': False})\n\nprint(\"Departures from Concert Hall towards ...\")\nbus_table[0:10]\n
    bus_table = Table({ \"time\":times, \"stop\":stops[:table_size], \"route\":route[:table_size], }) bus_table.sort(mapping={'time': False}) print(\"Departures from Concert Hall towards ...\") bus_table[0:10]
    creating sort index: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 1/1 [00:00<00:00, 1459.90it/s]\njoin: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 3/3 [00:00<00:00, 2421.65it/s]\n
    Departures from Concert Hall towards ...\n
    Out[56]: #timestoproute 021:02:00Random Road-62 121:05:00Hillside Crescent2 221:06:00Hillside1 321:25:00Random Road-241 421:29:00Random Road-161 521:32:00Random Road-211 621:33:00Random Road-121 721:36:00Random Road-233 821:38:00Central station2 921:38:00Random Road-82

    Let's say the concerts ends at 21:00 and it takes a 10 minutes to get to the bus-stop. Earliest departure must then be 21:10 - goodbye hugs included.

    In\u00a0[57]: Copied!
    lookup_1 = friends.lookup(bus_table, (DataTypes.time(21, 10), \"<=\", 'time'), ('stop', \"==\", 'stop'))\nlookup1_sorted = lookup_1.sorted(mapping={'time': False, 'name':False}, sort_mode='unix')\nlookup1_sorted\n
    lookup_1 = friends.lookup(bus_table, (DataTypes.time(21, 10), \"<=\", 'time'), ('stop', \"==\", 'stop')) lookup1_sorted = lookup_1.sorted(mapping={'time': False, 'name':False}, sort_mode='unix') lookup1_sorted
    100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 6/6 [00:00<00:00, 1513.92it/s]\njoin: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 3/3 [00:00<00:00, 2003.65it/s]\ncreating sort index: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 2/2 [00:00<00:00, 2589.88it/s]\njoin: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 5/5 [00:00<00:00, 2034.29it/s]\n
    Out[57]: #namestoptimestop_1route 0FredChicagoNoneNoneNone 1BettyDowntown-221:51:00Downtown-21 2EdwardDowntown-221:51:00Downtown-21 3CharlieHillside View22:19:00Hillside View2 4AliceDowntown-123:12:00Downtown-13 5DorethyHillside Crescent23:54:00Hillside Crescent1

    Lookup's ability to custom criteria is thereby far more versatile than SQL joins.

    But with great power comes great responsibility.

    In\u00a0[58]: Copied!
    materials = Table({\n    'bom_id': [1, 2, 3, 4, 5, 6, 7, 8, 9], \n    'partial_of': [1, 2, 3, 4, 5, 6, 7, 4, 6], \n    'sku': ['A', 'irrelevant', 'empty carton', 'pkd carton', 'empty pallet', 'pkd pallet', 'pkd irrelevant', 'ppkd carton', 'ppkd pallet'], \n    'material_id': [None, None, None, 3, None, 5, 3, 3, 5], \n    'quantity': [10, 20, 30, 40, 50, 60, 70, 80, 90]\n})\n    # 9 is a partially packed pallet of 6\n\n## multiple values.\nlooking_for = Table({\n    'bom_id': [3,4,6], \n    'moq': [1,2,3]\n    })\n
    materials = Table({ 'bom_id': [1, 2, 3, 4, 5, 6, 7, 8, 9], 'partial_of': [1, 2, 3, 4, 5, 6, 7, 4, 6], 'sku': ['A', 'irrelevant', 'empty carton', 'pkd carton', 'empty pallet', 'pkd pallet', 'pkd irrelevant', 'ppkd carton', 'ppkd pallet'], 'material_id': [None, None, None, 3, None, 5, 3, 3, 5], 'quantity': [10, 20, 30, 40, 50, 60, 70, 80, 90] }) # 9 is a partially packed pallet of 6 ## multiple values. looking_for = Table({ 'bom_id': [3,4,6], 'moq': [1,2,3] })

    Our goals is now to find the quantity from the materials table based on the items in the looking_for table.

    This requires two steps:

    1. lookup
    2. filter for all by dropping items that didn't match.
    In\u00a0[59]: Copied!
    ## step 1/2:\nproducts_lookup = materials.lookup(looking_for, (\"bom_id\", \"==\", \"bom_id\"), (\"partial_of\", \"==\", \"bom_id\"), all=False)   \nproducts_lookup\n
    ## step 1/2: products_lookup = materials.lookup(looking_for, (\"bom_id\", \"==\", \"bom_id\"), (\"partial_of\", \"==\", \"bom_id\"), all=False) products_lookup
    100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 9/9 [00:00<00:00, 3651.81it/s]\njoin: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 2/2 [00:00<00:00, 1625.38it/s]\n
    Out[59]: #bom_idpartial_ofskumaterial_idquantitybom_id_1moq 011ANone10NoneNone 122irrelevantNone20NoneNone 233empty cartonNone3031 344pkd carton34042 455empty palletNone50NoneNone 566pkd pallet56063 677pkd irrelevant370NoneNone 784ppkd carton38042 896ppkd pallet59063 In\u00a0[60]: Copied!
    ## step 2/2:\nproducts = products_lookup.all(bom_id_1=lambda x: x is not None)\nproducts\n
    ## step 2/2: products = products_lookup.all(bom_id_1=lambda x: x is not None) products Out[60]: #bom_idpartial_ofskumaterial_idquantitybom_id_1moq 033empty cartonNone3031 144pkd carton34042 266pkd pallet56063 384ppkd carton38042 496ppkd pallet59063

    The faster way to solve this problem is to use match!

    Here is the example:

    In\u00a0[61]: Copied!
    products_matched = materials.match(looking_for, (\"bom_id\", \"==\", \"bom_id\"), (\"partial_of\", \"==\", \"bom_id\"))\nproducts_matched\n
    products_matched = materials.match(looking_for, (\"bom_id\", \"==\", \"bom_id\"), (\"partial_of\", \"==\", \"bom_id\")) products_matched Out[61]: #bom_idpartial_ofskumaterial_idquantitybom_id_1moq 033empty cartonNone3031 144pkd carton34042 266pkd pallet56063 384ppkd carton38042 496ppkd pallet59063 In\u00a0[62]: Copied!
    assert products == products_matched\n
    assert products == products_matched In\u00a0[63]: Copied!
    from tablite import Table\nt = Table()  # create table\nt.add_columns('row','A','B','C')  # add columns\n
    from tablite import Table t = Table() # create table t.add_columns('row','A','B','C') # add columns

    The following examples are all valid and append the row (1,2,3) to the table.

    In\u00a0[64]: Copied!
    t.add_rows(1, 1, 2, 3)  # individual values\nt.add_rows([2, 1, 2, 3])  # list of values\nt.add_rows((3, 1, 2, 3))  # tuple of values\nt.add_rows(*(4, 1, 2, 3))  # unpacked tuple\nt.add_rows(row=5, A=1, B=2, C=3)   # keyword - args\nt.add_rows(**{'row': 6, 'A': 1, 'B': 2, 'C': 3})  # dict / json.\n
    t.add_rows(1, 1, 2, 3) # individual values t.add_rows([2, 1, 2, 3]) # list of values t.add_rows((3, 1, 2, 3)) # tuple of values t.add_rows(*(4, 1, 2, 3)) # unpacked tuple t.add_rows(row=5, A=1, B=2, C=3) # keyword - args t.add_rows(**{'row': 6, 'A': 1, 'B': 2, 'C': 3}) # dict / json.

    The following examples add two rows to the table

    In\u00a0[65]: Copied!
    t.add_rows((7, 1, 2, 3), (8, 4, 5, 6))  # two (or more) tuples.\nt.add_rows([9, 1, 2, 3], [10, 4, 5, 6])  # two or more lists\nt.add_rows({'row': 11, 'A': 1, 'B': 2, 'C': 3},\n          {'row': 12, 'A': 4, 'B': 5, 'C': 6})  # two (or more) dicts as args.\nt.add_rows(*[{'row': 13, 'A': 1, 'B': 2, 'C': 3},\n            {'row': 14, 'A': 1, 'B': 2, 'C': 3}])  # list of dicts.\n
    t.add_rows((7, 1, 2, 3), (8, 4, 5, 6)) # two (or more) tuples. t.add_rows([9, 1, 2, 3], [10, 4, 5, 6]) # two or more lists t.add_rows({'row': 11, 'A': 1, 'B': 2, 'C': 3}, {'row': 12, 'A': 4, 'B': 5, 'C': 6}) # two (or more) dicts as args. t.add_rows(*[{'row': 13, 'A': 1, 'B': 2, 'C': 3}, {'row': 14, 'A': 1, 'B': 2, 'C': 3}]) # list of dicts. In\u00a0[66]: Copied!
    t\n
    t Out[66]: #rowABC 01123 12123 23123 34123 45123 56123 67123 78456 89123 9104561011123111245612131231314123

    As the row incremented from 1 in the first of these examples, and finished with row: 14, you can now see the whole table above

    In\u00a0[67]: Copied!
    from pathlib import Path\npath = Path('tests/data/book1.csv')\ntx = Table.from_file(path)\ntx\n
    from pathlib import Path path = Path('tests/data/book1.csv') tx = Table.from_file(path) tx
    Collecting tasks: 'tests/data/book1.csv'\nDumping tasks: 'tests/data/book1.csv'\n
    importing file: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 1/1 [00:00<00:00, 444.08it/s]\n
    Out[67]: #abcdef 010.0606060610.0909090910.1212121210.1515151520.181818182 120.1212121210.2424242420.4848484850.969696971.939393939 230.2424242420.4848484850.969696971.9393939393.878787879 340.4848484850.969696971.9393939393.8787878797.757575758 450.969696971.9393939393.8787878797.75757575815.51515152 561.9393939393.8787878797.75757575815.5151515231.03030303 673.8787878797.75757575815.5151515231.0303030362.06060606.....................383916659267088.033318534175.066637068350.0133274000000.0266548000000.0394033318534175.066637068350.0133274000000.0266548000000.0533097000000.0404166637068350.0133274000000.0266548000000.0533097000000.01066190000000.04142133274000000.0266548000000.0533097000000.01066190000000.02132390000000.04243266548000000.0533097000000.01066190000000.02132390000000.04264770000000.04344533097000000.01066190000000.02132390000000.04264770000000.08529540000000.044451066190000000.02132390000000.04264770000000.08529540000000.017059100000000.0

    Note that you can also add start, limit and chunk_size to the file reader. Here's an example:

    In\u00a0[68]: Copied!
    path = Path('tests/data/book1.csv')\ntx2 = Table.from_file(path, start=2, limit=15)\ntx2\n
    path = Path('tests/data/book1.csv') tx2 = Table.from_file(path, start=2, limit=15) tx2
    Collecting tasks: 'tests/data/book1.csv'\n
    importing file: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 1/1 [00:00<00:00, 391.22it/s]
    Dumping tasks: 'tests/data/book1.csv'\n
    \n
    Out[68]: #abcdef 030.2424242420.4848484850.969696971.9393939393.878787879 140.4848484850.969696971.9393939393.8787878797.757575758 250.969696971.9393939393.8787878797.75757575815.51515152 361.9393939393.8787878797.75757575815.5151515231.03030303 473.8787878797.75757575815.5151515231.0303030362.06060606 587.75757575815.5151515231.0303030362.06060606124.1212121 6915.5151515231.0303030362.06060606124.1212121248.2424242 71031.0303030362.06060606124.1212121248.2424242496.4848485 81162.06060606124.1212121248.2424242496.4848485992.969697 912124.1212121248.2424242496.4848485992.9696971985.9393941013248.2424242496.4848485992.9696971985.9393943971.8787881114496.4848485992.9696971985.9393943971.8787887943.7575761215992.9696971985.9393943971.8787887943.75757615887.5151513161985.9393943971.8787887943.75757615887.5151531775.030314173971.8787887943.75757615887.5151531775.030363550.06061

    How good is the file_reader?

    I've included all formats in the test suite that are publicly available from the Alan Turing institute, dateutils) and Python's csv reader.

    What about MM-DD-YYYY formats? Some users from the US ask why the csv reader doesn't read the month-day-year format.

    The answer is simple: It's not an iso8601 format. The US month-day-year format is a locale that may be used a lot in the US, but it isn't an international standard.

    If you need to work with MM-DD-YYYY you will find that the file_reader will import the values as text (str). You can then reformat it with a custom function like:

    In\u00a0[69]: Copied!
    s = \"03-21-1998\"\nfrom datetime import date\nf = lambda s: date(int(s[-4:]), int(s[:2]), int(s[3:5]))\nf(s)\n
    s = \"03-21-1998\" from datetime import date f = lambda s: date(int(s[-4:]), int(s[:2]), int(s[3:5])) f(s) Out[69]:
    datetime.date(1998, 3, 21)
    In\u00a0[70]: Copied!
    from tablite.import_utils import file_readers\nfor k,v in file_readers.items():\n    print(k,v)\n
    from tablite.import_utils import file_readers for k,v in file_readers.items(): print(k,v)
    fods <function excel_reader at 0x7f36a3ef8c10>\njson <function excel_reader at 0x7f36a3ef8c10>\nhtml <function from_html at 0x7f36a3ef8b80>\nhdf5 <function from_hdf5 at 0x7f36a3ef8a60>\nsimple <function excel_reader at 0x7f36a3ef8c10>\nrst <function excel_reader at 0x7f36a3ef8c10>\nmediawiki <function excel_reader at 0x7f36a3ef8c10>\nxlsx <function excel_reader at 0x7f36a3ef8c10>\nxls <function excel_reader at 0x7f36a3ef8c10>\nxlsm <function excel_reader at 0x7f36a3ef8c10>\ncsv <function text_reader at 0x7f36a3ef9000>\ntsv <function text_reader at 0x7f36a3ef9000>\ntxt <function text_reader at 0x7f36a3ef9000>\nods <function ods_reader at 0x7f36a3ef8ca0>\n

    (2) define your new file reader

    In\u00a0[71]: Copied!
    def my_magic_reader(path, **kwargs):   # define your new file reader.\n    print(\"do magic with {path}\")\n    return\n
    def my_magic_reader(path, **kwargs): # define your new file reader. print(\"do magic with {path}\") return

    (3) add it to the list of readers.

    In\u00a0[72]: Copied!
    file_readers['my_special_format'] = my_magic_reader\n
    file_readers['my_special_format'] = my_magic_reader

    The file_readers are all in tablite.core so if you intend to extend the readers, I recommend that you start here.

    In\u00a0[73]: Copied!
    file = Path('example.xlsx')\ntx2.to_xlsx(file)\nos.remove(file)\n
    file = Path('example.xlsx') tx2.to_xlsx(file) os.remove(file)

    In\u00a0[74]: Copied!
    from tablite import Table\n\nt = Table({\n'a':[1, 2, 8, 3, 4, 6, 5, 7, 9],\n'b':[10, 100, 3, 4, 16, -1, 10, 10, 10],\n})\nt.sort(mapping={\"a\":False})\nt\n
    from tablite import Table t = Table({ 'a':[1, 2, 8, 3, 4, 6, 5, 7, 9], 'b':[10, 100, 3, 4, 16, -1, 10, 10, 10], }) t.sort(mapping={\"a\":False}) t
    creating sort index: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 1/1 [00:00<00:00, 1674.37it/s]\njoin: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 2/2 [00:00<00:00, 1701.89it/s]\n
    Out[74]: #ab 0110 12100 234 3416 4510 56-1 6710 783 8910 In\u00a0[75]: Copied!
    %pip install matplotlib -q\n
    %pip install matplotlib -q
    Note: you may need to restart the kernel to use updated packages.\n
    In\u00a0[76]: Copied!
    import matplotlib.pyplot as plt\nplt.plot(t['a'], t['b'])\nplt.ylabel('Hello Figure')\nplt.show()\n
    import matplotlib.pyplot as plt plt.plot(t['a'], t['b']) plt.ylabel('Hello Figure') plt.show() In\u00a0[77]: Copied!
    ## Let's monitor the memory and record the observations into a table!\nimport psutil, os, gc\nfrom time import process_time,sleep\nprocess = psutil.Process(os.getpid())\n\ndef mem_time():  # go and check taskmanagers memory usage.\n    return process.memory_info().rss, process_time()\n\ndigits = 1_000_000\n\nrecords = Table({'method':[], 'memory':[], 'time':[]})\n
    ## Let's monitor the memory and record the observations into a table! import psutil, os, gc from time import process_time,sleep process = psutil.Process(os.getpid()) def mem_time(): # go and check taskmanagers memory usage. return process.memory_info().rss, process_time() digits = 1_000_000 records = Table({'method':[], 'memory':[], 'time':[]})

    The row based format: 1 million 10-tuples

    In\u00a0[78]: Copied!
    before, start = mem_time()\nL = [tuple([11 for _ in range(10)]) for _ in range(digits)]\nafter, end = mem_time()  \ndel L\ngc.collect()\n\nrecords.add_rows(*('1e6 lists w. 10 integers', after - before, round(end-start,4)))\nrecords\n
    before, start = mem_time() L = [tuple([11 for _ in range(10)]) for _ in range(digits)] after, end = mem_time() del L gc.collect() records.add_rows(*('1e6 lists w. 10 integers', after - before, round(end-start,4))) records Out[78]: #methodmemorytime 01e6 lists w. 10 integers1190543360.5045

    The column based format: 10 columns with 1M values:

    In\u00a0[79]: Copied!
    before, start = mem_time()\nL = [[11 for i2 in range(digits)] for i1 in range(10)]\nafter,end = mem_time()\n\ndel L\ngc.collect()\nrecords.add_rows(('10 lists with 1e6 integers', after - before, round(end-start,4)))\n
    before, start = mem_time() L = [[11 for i2 in range(digits)] for i1 in range(10)] after,end = mem_time() del L gc.collect() records.add_rows(('10 lists with 1e6 integers', after - before, round(end-start,4)))

    We've thereby saved 50 Mb by avoiding the overhead from managing 1 million lists.

    Q: But why didn't I just use an array? It would have even lower memory footprint.

    A: First, array's don't handle None's and we get that frequently in dirty csv data.

    Second, Table needs even less memory.

    Let's try with an array:

    In\u00a0[80]: Copied!
    import array\n\nbefore, start = mem_time()\nL = [array.array('i', [11 for _ in range(digits)]) for _ in range(10)]\nafter,end = mem_time()\n\ndel L\ngc.collect()\nrecords.add_rows(('10 lists with 1e6 integers in arrays', after - before, round(end-start,4)))\nrecords\n
    import array before, start = mem_time() L = [array.array('i', [11 for _ in range(digits)]) for _ in range(10)] after,end = mem_time() del L gc.collect() records.add_rows(('10 lists with 1e6 integers in arrays', after - before, round(end-start,4))) records Out[80]: #methodmemorytime 01e6 lists w. 10 integers1190543360.5045 110 lists with 1e6 integers752762880.1906 210 lists with 1e6 integers in arrays398336000.3633

    Finally let's use a tablite.Table:

    In\u00a0[81]: Copied!
    before,start = mem_time()\nt = Table(columns={str(i1): [11 for i2 in range(digits)] for i1 in range(10)})\nafter,end = mem_time()\n\nrecords.add_rows(('Table with 10 columns with 1e6 integers', after - before, round(end-start,4)))\n\nbefore,start = mem_time()\nt2 = t.copy()\nafter,end = mem_time()\n\nrecords.add_rows(('2 Tables with 10 columns with 1e6 integers each', after - before, round(end-start,4)))\n\n## Let's show it, so we know nobody's cheating:\nt2\n
    before,start = mem_time() t = Table(columns={str(i1): [11 for i2 in range(digits)] for i1 in range(10)}) after,end = mem_time() records.add_rows(('Table with 10 columns with 1e6 integers', after - before, round(end-start,4))) before,start = mem_time() t2 = t.copy() after,end = mem_time() records.add_rows(('2 Tables with 10 columns with 1e6 integers each', after - before, round(end-start,4))) ## Let's show it, so we know nobody's cheating: t2 Out[81]: #0123456789 011111111111111111111 111111111111111111111 211111111111111111111 311111111111111111111 411111111111111111111 511111111111111111111 611111111111111111111................................. 999,99311111111111111111111 999,99411111111111111111111 999,99511111111111111111111 999,99611111111111111111111 999,99711111111111111111111 999,99811111111111111111111 999,99911111111111111111111 In\u00a0[82]: Copied!
    records\n
    records Out[82]: #methodmemorytime 01e6 lists w. 10 integers1190543360.5045 110 lists with 1e6 integers752762880.1906 210 lists with 1e6 integers in arrays398336000.3633 3Table with 10 columns with 1e6 integers01.9569 42 Tables with 10 columns with 1e6 integers each00.0001

    Conclusion: whilst the common worst case (1M lists with 10 integers) take up 118 Mb of RAM, Tablite's tables vanish in the noise of memory measurement.

    Pandas also permits the usage of namedtuples, which are unpacked upon entry.

    from collections import namedtuple\nPoint = namedtuple(\"Point\", \"x y\")\npoints = [Point(0, 0), Point(0, 3)]\npd.DataFrame(points)\n

    Doing that in tablite is a bit different. To unpack the named tuple, you should do so explicitly:

    t = Table({'x': [p.x for p in points], 'y': [p.y for p in points]})\n

    However should you want to keep the points as namedtuple, you can do so in tablite:

    t = Table()\nt['points'] = points\n

    Tablite will store a serialised version of the points, so your memory overhead will be close to zero.

    "},{"location":"tutorial/#tablite","title":"Tablite\u00b6","text":""},{"location":"tutorial/#introduction","title":"Introduction\u00b6","text":"

    Tablite fills the data-science space where incremental data processing based on:

    • Datasets are larger than memory.
    • You don't want to worry about datatypes.

    Tablite thereby competes with:

    • Pandas, but saves the memory overhead.
    • Numpy, but spares you from worrying about lower level data types
    • SQlite, by sheer speed.
    • Polars, by working beyond RAM.
    • Other libraries for data cleaning thanks to tablites powerful datatypes module.

    Install: pip install tablite

    Usage: >>> from tablite import Table

    Upgrade: pip install tablite --no-cache --upgrade

    "},{"location":"tutorial/#overview","title":"Overview\u00b6","text":"

    (Version 2023.6.0 and later. For older version see this)

    • Tablite handles all Python datatypes: str, float, bool, int, date, datetime, time, timedelta and None.
    • you can select:
      • all rows in a column as table['A']
      • rows across all columns as table[4:8]
      • or a slice as table['A', 'B', slice(4,8) ].
    • you to update with table['A'][2] = new value
    • you can store or send data using json, by:
      • dumping to json: json_str = table.to_json(), or
      • you can load it with Table.from_json(json_str).
    • you can iterate over rows using for row in Table.rows.
    • you can ask column_xyz in Table.colums ?
    • load from files with new_table = Table.from_file('this.csv') which has automatic datatype detection
    • perform inner, outer & left sql join between tables as simple as table_1.inner_join(table2, keys=['A', 'B'])
    • summarise using table.groupby( ... )
    • create pivot tables using groupby.pivot( ... )
    • perform multi-criteria lookup in tables using table1.lookup(table2, criteria=.....
    • and of course a large selection of tools in from tablite.tools import *
    "},{"location":"tutorial/#examples","title":"Examples\u00b6","text":"

    Here are some examples:

    "},{"location":"tutorial/#api-examples","title":"API Examples\u00b6","text":"

    In the following sections, example are given of the Tablite API's power features:

    • Iteration
    • Append
    • Sort
    • Filter
    • Index
    • Search All
    • Search Any
    • Lookup
    • Join inner, outer,
    • GroupBy
    • Pivot table
    "},{"location":"tutorial/#iteration","title":"ITERATION!\u00b6","text":"

    Iteration supports for loops and list comprehension at the speed of light:

    Just use [r for r in table.rows], or:

    for row in table.rows:\n    row ...

    Here's a more practical use case:

    (1) Imagine a table with columns a,b,c,d,e (all integers) like this:

    "},{"location":"tutorial/#create-index-indices","title":"Create Index / Indices\u00b6","text":"

    Index supports multi-key indexing using args such as: index = table.index('B','C').

    Here's an example:

    "},{"location":"tutorial/#append","title":"APPEND\u00b6","text":""},{"location":"tutorial/#save","title":"SAVE\u00b6","text":""},{"location":"tutorial/#filter","title":"FILTER!\u00b6","text":""},{"location":"tutorial/#any-all","title":"Any! All?\u00b6","text":"

    Any and All are cousins of the filter. They're there so you can use them in the same way as you'd use any and all in python - as boolean evaluators:

    "},{"location":"tutorial/#sort","title":"SORT!\u00b6","text":""},{"location":"tutorial/#groupby","title":"GROUPBY !\u00b6","text":""},{"location":"tutorial/#did-i-say-pivot-table-yes","title":"Did I say pivot table? Yes.\u00b6","text":"

    Pivot Table is included in the groupby functionality - so yes - you can pivot the groupby on any column that is used for grouping. Here's a simple example:

    "},{"location":"tutorial/#join","title":"JOIN!\u00b6","text":""},{"location":"tutorial/#lookup","title":"LOOKUP!\u00b6","text":""},{"location":"tutorial/#match","title":"Match\u00b6","text":"

    If you're looking to do a join where you afterwards remove the empty rows, match is the faster choice.

    Here is an example.

    Let's start with two tables:

    "},{"location":"tutorial/#are-there-other-ways-i-can-add-data","title":"Are there other ways I can add data?\u00b6","text":"

    Yes - but row based operations cause a lot of IO, so it'll work but be slower:

    "},{"location":"tutorial/#okay-great-how-do-i-load-data","title":"Okay, great. How do I load data?\u00b6","text":"

    Easy. Use file_reader. Here's an example:

    "},{"location":"tutorial/#sweet-what-formats-are-supported-can-i-add-my-own-file-reader","title":"Sweet. What formats are supported? Can I add my own file reader?\u00b6","text":"

    Yes! This is very good for special log files or custom json formats. Here's how you do it:

    (1) Go to all existing readers in the tablite.core and find the closest match.

    "},{"location":"tutorial/#very-nice-how-about-exporting-data","title":"Very nice. How about exporting data?\u00b6","text":"

    Just use .export

    "},{"location":"tutorial/#cool-does-it-play-well-with-plotting-packages","title":"Cool. Does it play well with plotting packages?\u00b6","text":"

    Yes. Here's an example you can copy and paste:

    "},{"location":"tutorial/#i-like-sql-can-tablite-understand-sql","title":"I like sql. Can tablite understand SQL?\u00b6","text":"

    Almost. You can use table.to_sql and tablite will return ANSI-92 compliant SQL.

    You can also create a table using Table.from_sql and tablite will consume ANSI-92 compliant SQL.

    "},{"location":"tutorial/#but-what-do-i-do-if-im-about-to-run-out-of-memory","title":"But what do I do if I'm about to run out of memory?\u00b6","text":"

    You wont. Every tablite table is backed by disk. The memory footprint of a table is only the metadata required to know the relationships between variable names and the datastructures.

    Let's do a comparison:

    "},{"location":"tutorial/#conclusions","title":"Conclusions\u00b6","text":"

    This concludes the mega-tutorial to tablite. There's nothing more to it. But oh boy it'll save a lot of time.

    Here's a summary of features:

    • Everything a list can do.
    • import csv*, fods, json, html, simple, rst, mediawiki, xlsx, xls, xlsm, csv, tsv, txt, ods using Table.from_file(...)
    • Iterate over rows or columns
    • Create multikey index, sort, use filter, any and all to select. Perform lookup across tables including using custom functions.
    • Perform multikey joins with other tables.
    • Perform groupby and reorganise data as a pivot table with max, min, sum, first, last, count, unique, average, standard deviation, median and mode.
    • Update tables with += which automatically sorts out the columns - even if they're not in perfect order.
    "},{"location":"tutorial/#faq","title":"FAQ\u00b6","text":"Question Answer I'm not in a notebook. Is there a nice way to view tables? Yes. table.show() prints the ascii version I'm looking for the equivalent to apply in pandas. Just use list comprehensions: table[column] = [f(x) for x in table[column] What about map? Just use the python function: mapping = map(f, table[column name]) Is there a where function? It's called any or all like in python: table.any(column_name > 0). I like sql and sqlite. Can I use sql? Yes. Call table.to_sql() returns ANSI-92 SQL compliant table definition.You can use this in any SQL compliant engine.

    | sometimes i need to clean up data with datetimes. Is there any tool to help with that? | Yes. Look at DataTypes.DataTypes.round(value, multiple) allows rounding of datetime.

    "},{"location":"tutorial/#coming-to-tablite-from-pandas","title":"Coming to Tablite from Pandas\u00b6","text":"

    If you're coming to Tablite from Pandas you will notice some differences.

    Here's the ultra short comparison to the documentation from Pandas called 10 minutes intro to pandas

    The tutorials provide the generic overview:

    • pandas tutorial
    • tablite tutorial

    Some key differences

    topic Tablite Viewing data Just use table.show() in print outs, or if you're in a jupyter notebook just use the variable name table Selection Slicing works both on columns and rows, and you can filter using any or all:table['A','B', 2:30:3].any(A=lambda x:x>3) to copy a table use: t2 = t.copy()This is a very fast deep copy, that has no memory overhead as tablites memory manager keeps track of the data. Missing data Tablite uses mixed column format for any format that isn't uniformTo get rid of rows with Nones and np.nans use any:table.drop_na(None, np.nan) Alternatively you can use replace: table.replace(None,5) following the syntax: table.replace_missing_values(sources, target) Operations Descriptive statistics are on a colum by column basis:table['a'].statistics() the pandas function df.apply doesn't exist in tablite. Use a list comprehension instead. For example: df.apply(np.cumsum) is just np.cumsum(t['A']) \"histogramming\" in tablite is per column: table['a'].histogram() string methods? Just use a list comprehensions: table['A', 'B'].any(A=lambda x: \"hello\" in x, B=lambda x: \"world\" in x) Merge Concatenation: Just use + or += as in t1 = t2 + t3 += t4. If the columns are out of order, tablite will sort the headers according to the order in the first table.If you're worried that the header mismatch use t1.stack(t2) Joins are ANSI92 compliant: t1.join(t2, <...args...>, join_type=...). Grouping Tablite supports multikey groupby using from tablite import Groupby as gb. table.groupby(keys, functions) Reshaping To reshape a table use transpose. to perform pivot table like operations, use: table.pivot(rows, columns, functions) subtotals aside tablite will give you everything Excels pivot table can do. Time series To convert time series use a list comprehension.t1['GMT'] = [timedelta(hours=1) + v for v in t1['date'] ] to generate a date range use:from Tablite import dateranget['date'] = date_range(start=2022/1/1, stop=2023/1/1, step=timedelta(days=1)) Categorical Pandas only seems to use this for sorting and grouping. Tablite table has .sort, .groupby and .pivot to achieve the same task. Plotting Import your favorite plotting package and feed it the values, such as:import matplotlib.pyplot as plt plt.plot(t['a'],t['b']) plt.showw() Import/Export Tablite supports the same import/export options as pandas.Tablite pegs the free memory before IO and can therefore process larger-than-RAM files. Tablite also guesses the datatypes for all ISOformats and uses multiprocessing and may therefore be faster. Should you want to inspect how guess works, use from tools import guess and try the function out. Gotchas None really. Should you come across something non-pythonic, then please post it on the issue list."},{"location":"reference/base/","title":"Base","text":""},{"location":"reference/base/#tablite.base","title":"tablite.base","text":""},{"location":"reference/base/#tablite.base-attributes","title":"Attributes","text":""},{"location":"reference/base/#tablite.base.log","title":"tablite.base.log = logging.getLogger(__name__) module-attribute","text":""},{"location":"reference/base/#tablite.base.file_registry","title":"tablite.base.file_registry = set() module-attribute","text":""},{"location":"reference/base/#tablite.base-classes","title":"Classes","text":""},{"location":"reference/base/#tablite.base.SimplePage","title":"tablite.base.SimplePage(id, path, len, py_dtype)","text":"

    Bases: object

    Source code in tablite/base.py
    def __init__(self, id, path, len, py_dtype) -> None:\n    self.path = Path(path) / \"pages\" / f\"{id}.npy\"\n    self.len = len\n    self.dtype = py_dtype\n\n    self._incr_refcount()\n
    "},{"location":"reference/base/#tablite.base.SimplePage-attributes","title":"Attributes","text":""},{"location":"reference/base/#tablite.base.SimplePage.ids","title":"tablite.base.SimplePage.ids = count(start=1) class-attribute instance-attribute","text":""},{"location":"reference/base/#tablite.base.SimplePage.refcounts","title":"tablite.base.SimplePage.refcounts = {} class-attribute instance-attribute","text":""},{"location":"reference/base/#tablite.base.SimplePage.autocleanup","title":"tablite.base.SimplePage.autocleanup = True class-attribute instance-attribute","text":""},{"location":"reference/base/#tablite.base.SimplePage.path","title":"tablite.base.SimplePage.path = Path(path) / 'pages' / f'{id}.npy' instance-attribute","text":""},{"location":"reference/base/#tablite.base.SimplePage.len","title":"tablite.base.SimplePage.len = len instance-attribute","text":""},{"location":"reference/base/#tablite.base.SimplePage.dtype","title":"tablite.base.SimplePage.dtype = py_dtype instance-attribute","text":""},{"location":"reference/base/#tablite.base.SimplePage-functions","title":"Functions","text":""},{"location":"reference/base/#tablite.base.SimplePage.__setstate__","title":"tablite.base.SimplePage.__setstate__(state)","text":"

    when an object is unpickled, say in a case of multi-processing, object.setstate(state) is called instead of init, this means we need to update page refcount as if constructor had been called

    Source code in tablite/base.py
    def __setstate__(self, state):\n    \"\"\"\n    when an object is unpickled, say in a case of multi-processing,\n    object.__setstate__(state) is called instead of __init__, this means\n    we need to update page refcount as if constructor had been called\n    \"\"\"\n    self.__dict__.update(state)\n\n    self._incr_refcount()\n
    "},{"location":"reference/base/#tablite.base.SimplePage.next_id","title":"tablite.base.SimplePage.next_id(path) classmethod","text":"Source code in tablite/base.py
    @classmethod\ndef next_id(cls, path):\n    path = Path(path)\n\n    while True:\n        _id = f\"{os.getpid()}-{next(cls.ids)}\"\n        _path = path / \"pages\" / f\"{_id}.npy\"\n\n        if not _path.exists():\n            break  # make sure we don't override existing pages if they are created outside of main thread\n\n    return _id\n
    "},{"location":"reference/base/#tablite.base.SimplePage.__len__","title":"tablite.base.SimplePage.__len__()","text":"Source code in tablite/base.py
    def __len__(self):\n    return self.len\n
    "},{"location":"reference/base/#tablite.base.SimplePage.__repr__","title":"tablite.base.SimplePage.__repr__() -> str","text":"Source code in tablite/base.py
    def __repr__(self) -> str:\n    try:\n        return f\"{self.__class__.__name__}({self.path}, {self.get()})\"\n    except FileNotFoundError as e:\n        return f\"{self.__class__.__name__}({self.path}, <{type(e).__name__}>)\"\n    except Exception as e:\n        return f\"{self.__class__.__name__}({self.path}, <{e}>)\"\n
    "},{"location":"reference/base/#tablite.base.SimplePage.__hash__","title":"tablite.base.SimplePage.__hash__() -> int","text":"Source code in tablite/base.py
    def __hash__(self) -> int:\n    return hash(self.path)\n
    "},{"location":"reference/base/#tablite.base.SimplePage.owns","title":"tablite.base.SimplePage.owns()","text":"Source code in tablite/base.py
    def owns(self):\n    parts = self.path.parts\n\n    return all((p in parts for p in Path(Config.pid).parts))\n
    "},{"location":"reference/base/#tablite.base.SimplePage.__del__","title":"tablite.base.SimplePage.__del__()","text":"

    When python's reference count for an object is 0, python uses it's garbage collector to remove the object and free the memory. As tablite tables have columns and columns have page and pages have data stored on disk, the space on disk must be freed up as well. This del override assures the cleanup of stored data.

    Source code in tablite/base.py
    def __del__(self):\n    \"\"\"When python's reference count for an object is 0, python uses\n    it's garbage collector to remove the object and free the memory.\n    As tablite tables have columns and columns have page and pages have\n    data stored on disk, the space on disk must be freed up as well.\n    This __del__ override assures the cleanup of stored data.\n    \"\"\"\n    if not self.owns():\n        return\n\n    refcount = self.refcounts[self.path] = max(\n        self.refcounts.get(self.path, 0) - 1, 0\n    )\n\n    if refcount > 0:\n        return\n\n    if self.autocleanup:\n        self.path.unlink(True)\n\n    del self.refcounts[self.path]\n
    "},{"location":"reference/base/#tablite.base.SimplePage.get","title":"tablite.base.SimplePage.get()","text":"

    loads stored data

    RETURNS DESCRIPTION

    np.ndarray: stored data.

    Source code in tablite/base.py
    def get(self):\n    \"\"\"loads stored data\n\n    Returns:\n        np.ndarray: stored data.\n    \"\"\"\n    array = load_numpy(self.path)\n    return MetaArray(array, array.dtype, py_dtype=self.dtype)\n
    "},{"location":"reference/base/#tablite.base.Page","title":"tablite.base.Page(path, array)","text":"

    Bases: SimplePage

    PARAMETER DESCRIPTION path

    working directory.

    TYPE: Path

    array

    data

    TYPE: array

    Source code in tablite/base.py
    def __init__(self, path, array) -> None:\n    \"\"\"\n    Args:\n        path (Path): working directory.\n        array (np.array): data\n    \"\"\"\n    _id = self.next_id(path)\n\n    type_check(array, np.ndarray)\n\n    if Config.DISK_LIMIT <= 0:\n        pass\n    else:\n        _, _, free = shutil.disk_usage(path)\n        if free - array.nbytes < Config.DISK_LIMIT:\n            msg = \"\\n\".join(\n                [\n                    f\"Disk limit reached: Config.DISK_LIMIT = {Config.DISK_LIMIT:,} bytes.\",\n                    f\"array requires {array.nbytes:,} bytes, but only {free:,} bytes are free.\",\n                    \"To disable this check, use:\",\n                    \">>> from tablite.config import Config\",\n                    \">>> Config.DISK_LIMIT = 0\",\n                    \"To free space, clean up Config.workdir:\",\n                    f\"{Config.workdir}\",\n                ]\n            )\n            raise OSError(msg)\n\n    _len = len(array)\n    # type_check(array, MetaArray)\n    if not hasattr(array, \"metadata\"):\n        raise ValueError\n    _dtype = array.metadata[\"py_dtype\"]\n\n    super().__init__(_id, path, _len, _dtype)\n\n    np.save(self.path, array, allow_pickle=True, fix_imports=False)\n    log.debug(f\"Page saved: {self.path}\")\n
    "},{"location":"reference/base/#tablite.base.Page-attributes","title":"Attributes","text":""},{"location":"reference/base/#tablite.base.Page.ids","title":"tablite.base.Page.ids = count(start=1) class-attribute instance-attribute","text":""},{"location":"reference/base/#tablite.base.Page.refcounts","title":"tablite.base.Page.refcounts = {} class-attribute instance-attribute","text":""},{"location":"reference/base/#tablite.base.Page.autocleanup","title":"tablite.base.Page.autocleanup = True class-attribute instance-attribute","text":""},{"location":"reference/base/#tablite.base.Page.path","title":"tablite.base.Page.path = Path(path) / 'pages' / f'{id}.npy' instance-attribute","text":""},{"location":"reference/base/#tablite.base.Page.len","title":"tablite.base.Page.len = len instance-attribute","text":""},{"location":"reference/base/#tablite.base.Page.dtype","title":"tablite.base.Page.dtype = py_dtype instance-attribute","text":""},{"location":"reference/base/#tablite.base.Page-functions","title":"Functions","text":""},{"location":"reference/base/#tablite.base.Page.__setstate__","title":"tablite.base.Page.__setstate__(state)","text":"

    when an object is unpickled, say in a case of multi-processing, object.setstate(state) is called instead of init, this means we need to update page refcount as if constructor had been called

    Source code in tablite/base.py
    def __setstate__(self, state):\n    \"\"\"\n    when an object is unpickled, say in a case of multi-processing,\n    object.__setstate__(state) is called instead of __init__, this means\n    we need to update page refcount as if constructor had been called\n    \"\"\"\n    self.__dict__.update(state)\n\n    self._incr_refcount()\n
    "},{"location":"reference/base/#tablite.base.Page.next_id","title":"tablite.base.Page.next_id(path) classmethod","text":"Source code in tablite/base.py
    @classmethod\ndef next_id(cls, path):\n    path = Path(path)\n\n    while True:\n        _id = f\"{os.getpid()}-{next(cls.ids)}\"\n        _path = path / \"pages\" / f\"{_id}.npy\"\n\n        if not _path.exists():\n            break  # make sure we don't override existing pages if they are created outside of main thread\n\n    return _id\n
    "},{"location":"reference/base/#tablite.base.Page.__len__","title":"tablite.base.Page.__len__()","text":"Source code in tablite/base.py
    def __len__(self):\n    return self.len\n
    "},{"location":"reference/base/#tablite.base.Page.__repr__","title":"tablite.base.Page.__repr__() -> str","text":"Source code in tablite/base.py
    def __repr__(self) -> str:\n    try:\n        return f\"{self.__class__.__name__}({self.path}, {self.get()})\"\n    except FileNotFoundError as e:\n        return f\"{self.__class__.__name__}({self.path}, <{type(e).__name__}>)\"\n    except Exception as e:\n        return f\"{self.__class__.__name__}({self.path}, <{e}>)\"\n
    "},{"location":"reference/base/#tablite.base.Page.__hash__","title":"tablite.base.Page.__hash__() -> int","text":"Source code in tablite/base.py
    def __hash__(self) -> int:\n    return hash(self.path)\n
    "},{"location":"reference/base/#tablite.base.Page.owns","title":"tablite.base.Page.owns()","text":"Source code in tablite/base.py
    def owns(self):\n    parts = self.path.parts\n\n    return all((p in parts for p in Path(Config.pid).parts))\n
    "},{"location":"reference/base/#tablite.base.Page.__del__","title":"tablite.base.Page.__del__()","text":"

    When python's reference count for an object is 0, python uses it's garbage collector to remove the object and free the memory. As tablite tables have columns and columns have page and pages have data stored on disk, the space on disk must be freed up as well. This del override assures the cleanup of stored data.

    Source code in tablite/base.py
    def __del__(self):\n    \"\"\"When python's reference count for an object is 0, python uses\n    it's garbage collector to remove the object and free the memory.\n    As tablite tables have columns and columns have page and pages have\n    data stored on disk, the space on disk must be freed up as well.\n    This __del__ override assures the cleanup of stored data.\n    \"\"\"\n    if not self.owns():\n        return\n\n    refcount = self.refcounts[self.path] = max(\n        self.refcounts.get(self.path, 0) - 1, 0\n    )\n\n    if refcount > 0:\n        return\n\n    if self.autocleanup:\n        self.path.unlink(True)\n\n    del self.refcounts[self.path]\n
    "},{"location":"reference/base/#tablite.base.Page.get","title":"tablite.base.Page.get()","text":"

    loads stored data

    RETURNS DESCRIPTION

    np.ndarray: stored data.

    Source code in tablite/base.py
    def get(self):\n    \"\"\"loads stored data\n\n    Returns:\n        np.ndarray: stored data.\n    \"\"\"\n    array = load_numpy(self.path)\n    return MetaArray(array, array.dtype, py_dtype=self.dtype)\n
    "},{"location":"reference/base/#tablite.base.Column","title":"tablite.base.Column(path, value=None)","text":"

    Bases: object

    Create Column

    PARAMETER DESCRIPTION path

    path of table.yml (defaults: Config.pid_dir)

    TYPE: Path

    value

    Data to store. Defaults to None.

    TYPE: Iterable DEFAULT: None

    Source code in tablite/base.py
    def __init__(self, path, value=None) -> None:\n    \"\"\"Create Column\n\n    Args:\n        path (Path): path of table.yml (defaults: Config.pid_dir)\n        value (Iterable, optional): Data to store. Defaults to None.\n    \"\"\"\n    self.path = path\n    self.pages = []  # keeps pointers to instances of Page\n    if value is not None:\n        self.extend(value)\n
    "},{"location":"reference/base/#tablite.base.Column-attributes","title":"Attributes","text":""},{"location":"reference/base/#tablite.base.Column.path","title":"tablite.base.Column.path = path instance-attribute","text":""},{"location":"reference/base/#tablite.base.Column.pages","title":"tablite.base.Column.pages = [] instance-attribute","text":""},{"location":"reference/base/#tablite.base.Column-functions","title":"Functions","text":""},{"location":"reference/base/#tablite.base.Column.__len__","title":"tablite.base.Column.__len__()","text":"Source code in tablite/base.py
    def __len__(self):\n    return sum(len(p) for p in self.pages)\n
    "},{"location":"reference/base/#tablite.base.Column.__repr__","title":"tablite.base.Column.__repr__()","text":"Source code in tablite/base.py
    def __repr__(self):\n    return f\"{self.__class__.__name__}({self.path}, {self[:]})\"\n
    "},{"location":"reference/base/#tablite.base.Column.repaginate","title":"tablite.base.Column.repaginate()","text":"

    resizes pages to Config.PAGE_SIZE

    Source code in tablite/base.py
    def repaginate(self):\n    \"\"\"resizes pages to Config.PAGE_SIZE\"\"\"\n    from tablite.nimlite import repaginate as _repaginate\n\n    _repaginate(self)\n
    "},{"location":"reference/base/#tablite.base.Column.extend","title":"tablite.base.Column.extend(value)","text":"

    extends the column.

    PARAMETER DESCRIPTION value

    data

    TYPE: ndarray

    Source code in tablite/base.py
    def extend(self, value):  # USER FUNCTION.\n    \"\"\"extends the column.\n\n    Args:\n        value (np.ndarray): data\n    \"\"\"\n    if isinstance(value, Column):\n        self.pages.extend(value.pages[:])\n        return\n    elif isinstance(value, np.ndarray):\n        pass\n    elif isinstance(value, (list, tuple)):\n        value = list_to_np_array(value)\n    else:\n        raise TypeError(f\"Cannot extend Column with {type(value)}\")\n    type_check(value, np.ndarray)\n    for array in self._paginate(value):\n        self.pages.append(Page(path=self.path, array=array))\n
    "},{"location":"reference/base/#tablite.base.Column.clear","title":"tablite.base.Column.clear()","text":"

    clears the column. Like list().clear()

    Source code in tablite/base.py
    def clear(self):\n    \"\"\"\n    clears the column. Like list().clear()\n    \"\"\"\n    self.pages.clear()\n
    "},{"location":"reference/base/#tablite.base.Column.getpages","title":"tablite.base.Column.getpages(item)","text":"

    public non-user function to identify any pages + slices of data to be retrieved given a slice (item)

    PARAMETER DESCRIPTION item

    target slice of data

    TYPE: (int, slice)

    RETURNS DESCRIPTION

    list of pages/np.ndarrays.

    Example: [Page(1), Page(2), np.ndarray([4,5,6], int64)] This helps, for example when creating a copy, as the copy can reference the pages 1 and 2 and only need to store the np.ndarray that is unique to it.

    Source code in tablite/base.py
    def getpages(self, item):\n    \"\"\"public non-user function to identify any pages + slices\n    of data to be retrieved given a slice (item)\n\n    Args:\n        item (int,slice): target slice of data\n\n    Returns:\n        list of pages/np.ndarrays.\n\n    Example: [Page(1), Page(2), np.ndarray([4,5,6], int64)]\n    This helps, for example when creating a copy, as the copy\n    can reference the pages 1 and 2 and only need to store\n    the np.ndarray that is unique to it.\n    \"\"\"\n    # internal function\n    if isinstance(item, int):\n        if item < 0:\n            item = len(self) + item\n        item = slice(item, item + 1, 1)\n\n    type_check(item, slice)\n    is_reversed = False if (item.step is None or item.step > 0) else True\n\n    length = len(self)\n    scan_item = slice(*item.indices(length))\n    range_item = range(*item.indices(length))\n\n    pages = []\n    start, end = 0, 0\n    for page in self.pages:\n        start, end = end, end + page.len\n        if is_reversed:\n            if start > scan_item.start:\n                break\n            if end < scan_item.stop:\n                continue\n        else:\n            if start > scan_item.stop:\n                break\n            if end < scan_item.start:\n                continue\n        ro = intercept(range(start, end), range_item)\n        if len(ro) == 0:\n            continue\n        elif len(ro) == page.len:  # share the whole immutable page\n            pages.append(page)\n        else:  # fetch the slice and filter it.\n            search_slice = slice(ro.start - start, ro.stop - start, ro.step)\n            np_arr = load_numpy(page.path)\n            match = np_arr[search_slice]\n            pages.append(match)\n\n    if is_reversed:\n        pages.reverse()\n        for ix, page in enumerate(pages):\n            if isinstance(page, SimplePage):\n                data = page.get()\n                pages[ix] = np.flip(data)\n            else:\n                pages[ix] = np.flip(page)\n\n    return pages\n
    "},{"location":"reference/base/#tablite.base.Column.iter_by_page","title":"tablite.base.Column.iter_by_page()","text":"

    iterates over the column, page by page. This method minimizes the number of reads.

    RETURNS DESCRIPTION

    generator of tuple: start: int end: int data: np.ndarray

    Source code in tablite/base.py
    def iter_by_page(self):\n    \"\"\"iterates over the column, page by page.\n    This method minimizes the number of reads.\n\n    Returns:\n        generator of tuple:\n            start: int\n            end: int\n            data: np.ndarray\n    \"\"\"\n    start, end = 0, 0\n    for page in self.pages:\n        start, end = end, end + page.len\n        yield start, end, page\n
    "},{"location":"reference/base/#tablite.base.Column.__getitem__","title":"tablite.base.Column.__getitem__(item)","text":"

    gets numpy array.

    PARAMETER DESCRIPTION item

    slice of column

    TYPE: int OR slice

    RETURNS DESCRIPTION

    np.ndarray: results as numpy array.

    Remember:

    >>> R = np.array([0,1,2,3,4,5])\n>>> R[3]\n3\n>>> R[3:4]\narray([3])\n
    Source code in tablite/base.py
    def __getitem__(self, item):  # USER FUNCTION.\n    \"\"\"gets numpy array.\n\n    Args:\n        item (int OR slice): slice of column\n\n    Returns:\n        np.ndarray: results as numpy array.\n\n    Remember:\n    ```\n    >>> R = np.array([0,1,2,3,4,5])\n    >>> R[3]\n    3\n    >>> R[3:4]\n    array([3])\n    ```\n    \"\"\"\n    result = []\n    for element in self.getpages(item):\n        if isinstance(element, SimplePage):\n            result.append(element.get())\n        else:\n            result.append(element)\n\n    if result:\n        arr = np_type_unify(result)\n    else:\n        arr = np.array([])\n\n    if isinstance(item, int):\n        if len(arr) == 0:\n            raise IndexError(\n                f\"index {item} is out of bounds for axis 0 with size {len(self)}\"\n            )\n        return numpy_to_python(arr[0])\n    else:\n        return arr\n
    "},{"location":"reference/base/#tablite.base.Column.__setitem__","title":"tablite.base.Column.__setitem__(key, value)","text":"

    sets values.

    PARAMETER DESCRIPTION key

    selector

    TYPE: (int, slice)

    value

    values to insert

    TYPE: any

    RAISES DESCRIPTION KeyError

    Following normal slicing rules

    Source code in tablite/base.py
    def __setitem__(self, key, value):  # USER FUNCTION.\n    \"\"\"sets values.\n\n    Args:\n        key (int,slice): selector\n        value (any): values to insert\n\n    Raises:\n        KeyError: Following normal slicing rules\n    \"\"\"\n    if isinstance(key, int):\n        self._setitem_integer_key(key, value)\n\n    elif isinstance(key, slice):\n        if not isinstance(value, np.ndarray):\n            value = list_to_np_array(value)\n        type_check(value, np.ndarray)\n\n        if key.start is None and key.stop is None and key.step in (None, 1):\n            self._setitem_replace_all(key, value)\n        elif key.start is not None and key.stop is None and key.step in (None, 1):\n            self._setitem_extend(key, value)\n        elif key.stop is not None and key.start is None and key.step in (None, 1):\n            self._setitem_prextend(key, value)\n        elif (\n            key.step in (None, 1) and key.start is not None and key.stop is not None\n        ):\n            self._setitem_insert(key, value)\n        elif key.step not in (None, 1):\n            self._setitem_update(key, value)\n        else:\n            raise KeyError(f\"bad key: {key}\")\n    else:\n        raise KeyError(f\"bad key: {key}\")\n
    "},{"location":"reference/base/#tablite.base.Column.__delitem__","title":"tablite.base.Column.__delitem__(key)","text":"

    deletes items selected by key

    PARAMETER DESCRIPTION key

    selector

    TYPE: (int, slice)

    RAISES DESCRIPTION KeyError

    following normal slicing rules.

    Source code in tablite/base.py
    def __delitem__(self, key):  # USER FUNCTION\n    \"\"\"deletes items selected by key\n\n    Args:\n        key (int,slice): selector\n\n    Raises:\n        KeyError: following normal slicing rules.\n    \"\"\"\n    if isinstance(key, int):\n        self._del_by_int(key)\n    elif isinstance(key, slice):\n        self._del_by_slice(key)\n    else:\n        raise KeyError(f\"bad key: {key}\")\n
    "},{"location":"reference/base/#tablite.base.Column.get_by_indices","title":"tablite.base.Column.get_by_indices(indices: Union[List[int], np.ndarray]) -> np.ndarray","text":"

    retrieves values from column given a set of indices.

    PARAMETER DESCRIPTION indices

    targets

    TYPE: array

    This method uses np.take, is faster than iterating over rows. Examples:

    >>> indices = np.array(list(range(3,700_700, 426)))\n>>> arr = np.array(list(range(2_000_000)))\nPythonic:\n>>> [v for i,v in enumerate(arr) if i in indices]\nNumpyionic:\n>>> np.take(arr, indices)\n
    Source code in tablite/base.py
    def get_by_indices(self, indices: Union[List[int], np.ndarray]) -> np.ndarray:\n    \"\"\"retrieves values from column given a set of indices.\n\n    Args:\n        indices (np.array): targets\n\n    This method uses np.take, is faster than iterating over rows.\n    Examples:\n    ```\n    >>> indices = np.array(list(range(3,700_700, 426)))\n    >>> arr = np.array(list(range(2_000_000)))\n    Pythonic:\n    >>> [v for i,v in enumerate(arr) if i in indices]\n    Numpyionic:\n    >>> np.take(arr, indices)\n    ```\n    \"\"\"\n    type_check(indices, np.ndarray)\n\n    dtypes = set()\n    values = np.empty(\n        indices.shape, dtype=object\n    )  # placeholder for the indexed values.\n\n    for start, end, page in self.iter_by_page():\n        range_match = np.asarray(((indices >= start) & (indices < end)) | (indices == -1)).nonzero()[0]\n        if len(range_match):\n            # only fetch the data if there's a range match!\n            data = page.get() \n            sub_index = np.take(indices, range_match)\n            # sub_index2 otherwise will raise index error where len(data) > (-1 - start)\n            # so the clause below is required:\n            if len(data) > (-1 - start):\n                sub_index = np.where(sub_index == -1, -1, sub_index - start)\n            arr = np.take(data, sub_index)\n            dtypes.add(arr.dtype)\n            np.put(values, range_match, arr)\n\n    if len(dtypes) == 1:  # simplify the datatype\n        dtype = next(iter(dtypes))\n        values = np.array(values, dtype=dtype)\n    return values\n
    "},{"location":"reference/base/#tablite.base.Column.__iter__","title":"tablite.base.Column.__iter__()","text":"Source code in tablite/base.py
    def __iter__(self):  # USER FUNCTION.\n    for page in self.pages:\n        data = page.get()\n        for value in data:\n            yield value\n
    "},{"location":"reference/base/#tablite.base.Column.__eq__","title":"tablite.base.Column.__eq__(other)","text":"

    compares two columns. Like list1 == list2

    Source code in tablite/base.py
    def __eq__(self, other):  # USER FUNCTION.\n    \"\"\"\n    compares two columns. Like `list1 == list2`\n    \"\"\"\n    if len(self) != len(other):  # quick cheap check.\n        return False\n\n    if isinstance(other, (list, tuple)):\n        return all(a == b for a, b in zip(self[:], other))\n\n    elif isinstance(other, Column):\n        if self.pages == other.pages:  # special case.\n            return True\n\n        # are the pages of same size?\n        if len(self.pages) == len(other.pages):\n            if [p.len for p in self.pages] == [p.len for p in other.pages]:\n                for a, b in zip(self.pages, other.pages):\n                    if not (a.get() == b.get()).all():\n                        return False\n                return True\n        # to bad. Element comparison it is then:\n        for a, b in zip(iter(self), iter(other)):\n            if a != b:\n                return False\n        return True\n\n    elif isinstance(other, np.ndarray):\n        start, end = 0, 0\n        for p in self.pages:\n            start, end = end, end + p.len\n            if not (p.get() == other[start:end]).all():\n                return False\n        return True\n    else:\n        raise TypeError(f\"Cannot compare {self.__class__} with {type(other)}\")\n
    "},{"location":"reference/base/#tablite.base.Column.__ne__","title":"tablite.base.Column.__ne__(other)","text":"

    compares two columns. Like list1 != list2

    Source code in tablite/base.py
    def __ne__(self, other):  # USER FUNCTION\n    \"\"\"\n    compares two columns. Like `list1 != list2`\n    \"\"\"\n    if len(self) != len(other):  # quick cheap check.\n        return True\n\n    if isinstance(other, (list, tuple)):\n        return any(a != b for a, b in zip(self[:], other))\n\n    elif isinstance(other, Column):\n        if self.pages == other.pages:  # special case.\n            return False\n\n        # are the pages of same size?\n        if len(self.pages) == len(other.pages):\n            if [p.len for p in self.pages] == [p.len for p in other.pages]:\n                for a, b in zip(self.pages, other.pages):\n                    if not (a.get() == b.get()).all():\n                        return True\n                return False\n        # to bad. Element comparison it is then:\n        for a, b in zip(iter(self), iter(other)):\n            if a != b:\n                return True\n        return False\n\n    elif isinstance(other, np.ndarray):\n        start, end = 0, 0\n        for p in self.pages:\n            start, end = end, end + p.len\n            if (p.get() != other[start:end]).any():\n                return True\n        return False\n    else:\n        raise TypeError(f\"Cannot compare {self.__class__} with {type(other)}\")\n
    "},{"location":"reference/base/#tablite.base.Column.copy","title":"tablite.base.Column.copy()","text":"

    returns deep=copy of Column

    RETURNS DESCRIPTION

    Column

    Source code in tablite/base.py
    def copy(self):\n    \"\"\"returns deep=copy of Column\n\n    Returns:\n        Column\n    \"\"\"\n    cp = Column(path=self.path)\n    cp.pages = self.pages[:]\n    return cp\n
    "},{"location":"reference/base/#tablite.base.Column.__copy__","title":"tablite.base.Column.__copy__()","text":"

    see copy

    Source code in tablite/base.py
    def __copy__(self):\n    \"\"\"see copy\"\"\"\n    return self.copy()\n
    "},{"location":"reference/base/#tablite.base.Column.__imul__","title":"tablite.base.Column.__imul__(other)","text":"

    Repeats instance of column N times. Like list() * N

    Example:

    >>> one = Column(data=[1,2])\n>>> one *= 5\n>>> one\n[1,2, 1,2, 1,2, 1,2, 1,2]\n
    Source code in tablite/base.py
    def __imul__(self, other):\n    \"\"\"\n    Repeats instance of column N times. Like list() * N\n\n    Example:\n    ```\n    >>> one = Column(data=[1,2])\n    >>> one *= 5\n    >>> one\n    [1,2, 1,2, 1,2, 1,2, 1,2]\n    ```\n    \"\"\"\n    if not (isinstance(other, int) and other > 0):\n        raise TypeError(\n            f\"a column can be repeated an integer number of times, not {type(other)} number of times\"\n        )\n    self.pages = self.pages[:] * other\n    return self\n
    "},{"location":"reference/base/#tablite.base.Column.__mul__","title":"tablite.base.Column.__mul__(other)","text":"

    Repeats instance of column N times. Like list() * N

    Example:

    >>> one = Column(data=[1,2])\n>>> two = one * 5\n>>> two\n[1,2, 1,2, 1,2, 1,2, 1,2]\n
    Source code in tablite/base.py
    def __mul__(self, other):\n    \"\"\"\n    Repeats instance of column N times. Like list() * N\n\n    Example:\n    ```\n    >>> one = Column(data=[1,2])\n    >>> two = one * 5\n    >>> two\n    [1,2, 1,2, 1,2, 1,2, 1,2]\n    ```\n    \"\"\"\n    if not isinstance(other, int):\n        raise TypeError(\n            f\"a column can be repeated an integer number of times, not {type(other)} number of times\"\n        )\n    cp = self.copy()\n    cp *= other\n    return cp\n
    "},{"location":"reference/base/#tablite.base.Column.__iadd__","title":"tablite.base.Column.__iadd__(other)","text":"Source code in tablite/base.py
    def __iadd__(self, other):\n    if isinstance(other, (list, tuple)):\n        other = list_to_np_array(other)\n        self.extend(other)\n    elif isinstance(other, Column):\n        self.pages.extend(other.pages[:])\n    else:\n        raise TypeError(f\"{type(other)} not supported.\")\n    return self\n
    "},{"location":"reference/base/#tablite.base.Column.__contains__","title":"tablite.base.Column.__contains__(item)","text":"

    determines if item is in the Column. Similar to 'x' in ['a','b','c'] returns boolean

    PARAMETER DESCRIPTION item

    value to search for

    TYPE: any

    RETURNS DESCRIPTION bool

    True if item exists in column.

    Source code in tablite/base.py
    def __contains__(self, item):\n    \"\"\"determines if item is in the Column.\n    Similar to `'x' in ['a','b','c']`\n    returns boolean\n\n    Args:\n        item (any): value to search for\n\n    Returns:\n        bool: True if item exists in column.\n    \"\"\"\n    for page in set(self.pages):\n        if item in page.get():  # x in np.ndarray([...]) uses np.any(arr, value)\n            return True\n    return False\n
    "},{"location":"reference/base/#tablite.base.Column.remove_all","title":"tablite.base.Column.remove_all(*values)","text":"

    removes all values of values

    Source code in tablite/base.py
    def remove_all(self, *values):\n    \"\"\"\n    removes all values of `values`\n    \"\"\"\n    type_check(values, tuple)\n    if isinstance(values[0], tuple):\n        values = values[0]\n    to_remove = list_to_np_array(values)\n    for index, page in enumerate(self.pages):\n        data = page.get()\n        bitmask = np.isin(data, to_remove)  # identify elements to remove.\n        if bitmask.any():\n            bitmask = np.invert(bitmask)  # turn bitmask around to keep.\n            new_data = np.compress(bitmask, data)\n            new_page = Page(self.path, new_data)\n            self.pages[index] = new_page\n
    "},{"location":"reference/base/#tablite.base.Column.replace","title":"tablite.base.Column.replace(mapping)","text":"

    replaces values using a mapping.

    PARAMETER DESCRIPTION mapping

    {value to replace: new value, ...}

    TYPE: dict

    Example:

    >>> t = Table(columns={'A': [1,2,3,4]})\n>>> t['A'].replace({2:20,4:40})\n>>> t[:]\nnp.ndarray([1,20,3,40])\n
    Source code in tablite/base.py
    def replace(self, mapping):\n    \"\"\"\n    replaces values using a mapping.\n\n    Args:\n        mapping (dict): {value to replace: new value, ...}\n\n    Example:\n    ```\n    >>> t = Table(columns={'A': [1,2,3,4]})\n    >>> t['A'].replace({2:20,4:40})\n    >>> t[:]\n    np.ndarray([1,20,3,40])\n    ```\n    \"\"\"\n    type_check(mapping, dict)\n    to_replace = np.array(list(mapping.keys()))\n    for index, page in enumerate(self.pages):\n        data = page.get()\n        bitmask = np.isin(data, to_replace)  # identify elements to replace.\n        if bitmask.any():\n            warray = np.compress(bitmask, data)\n            py_dtype = page.dtype\n            for ix, v in enumerate(warray):\n                old_py_val = numpy_to_python(v)\n                new_py_val = mapping[old_py_val]\n                old_dt = type(old_py_val)\n                new_dt = type(new_py_val)\n\n                warray[ix] = new_py_val\n\n                py_dtype[new_dt] = py_dtype.get(new_dt, 0) + 1\n                py_dtype[old_dt] = py_dtype.get(old_dt, 0) - 1\n\n                if py_dtype[old_dt] <= 0:\n                    del py_dtype[old_dt]\n\n            data[bitmask] = warray\n            self.pages[index] = Page(path=self.path, array=data)\n
    "},{"location":"reference/base/#tablite.base.Column.types","title":"tablite.base.Column.types()","text":"

    returns dict with python datatypes

    RETURNS DESCRIPTION dict

    frequency of occurrence of python datatypes

    Source code in tablite/base.py
    def types(self):\n    \"\"\"\n    returns dict with python datatypes\n\n    Returns:\n        dict: frequency of occurrence of python datatypes\n    \"\"\"\n    d = Counter()\n    for page in self.pages:\n        assert isinstance(page.dtype, dict)\n        d += page.dtype\n    return dict(d)\n
    "},{"location":"reference/base/#tablite.base.Column.index","title":"tablite.base.Column.index()","text":"

    returns dict with { unique entry : list of indices }

    example:

    >>> c = Column(data=['a','b','a','c','b'])\n>>> c.index()\n{'a':[0,2], 'b': [1,4], 'c': [3]}\n
    Source code in tablite/base.py
    def index(self):\n    \"\"\"\n    returns dict with { unique entry : list of indices }\n\n    example:\n    ```\n    >>> c = Column(data=['a','b','a','c','b'])\n    >>> c.index()\n    {'a':[0,2], 'b': [1,4], 'c': [3]}\n    ```\n    \"\"\"\n    d = defaultdict(list)\n    for ix, v in enumerate(self.__iter__()):\n        d[v].append(ix)\n    return dict(d)\n
    "},{"location":"reference/base/#tablite.base.Column.unique","title":"tablite.base.Column.unique()","text":"

    returns unique list of values.

    example:

    >>> c = Column(data=['a','b','a','c','b'])\n>>> c.unqiue()\n['a','b','c']\n
    Source code in tablite/base.py
    def unique(self):\n    \"\"\"\n    returns unique list of values.\n\n    example:\n    ```\n    >>> c = Column(data=['a','b','a','c','b'])\n    >>> c.unqiue()\n    ['a','b','c']\n    ```\n    \"\"\"\n    arrays = []\n    for page in set(self.pages):\n        try:  # when it works, numpy is fast...\n            arrays.append(np.unique(page.get()))\n        except TypeError:  # ...but np.unique cannot handle Nones.\n            arrays.append(multitype_set(page.get()))\n    union = np_type_unify(arrays)\n    try:\n        return np.unique(union)\n    except MemoryError:\n        return np.array(set(union))\n    except TypeError:\n        return multitype_set(union)\n
    "},{"location":"reference/base/#tablite.base.Column.histogram","title":"tablite.base.Column.histogram()","text":"

    returns 2 arrays: unique elements and count of each element

    example:

    >>> c = Column(data=['a','b','a','c','b'])\n>>> c.histogram()\n{'a':2,'b':2,'c':1}\n
    Source code in tablite/base.py
    def histogram(self):\n    \"\"\"\n    returns 2 arrays: unique elements and count of each element\n\n    example:\n    ```\n    >>> c = Column(data=['a','b','a','c','b'])\n    >>> c.histogram()\n    {'a':2,'b':2,'c':1}\n    ```\n    \"\"\"\n    d = defaultdict(int)\n    for page in self.pages:\n        try:\n            uarray, carray = np.unique(page.get(), return_counts=True)\n        except TypeError:\n            uarray = page.get()\n            carray = repeat(1, len(uarray))\n\n        for i, c in zip(uarray, carray):\n            v = numpy_to_python(i)\n            d[(type(v), v)] += numpy_to_python(c)\n    u = [v for _, v in d.keys()]\n    c = list(d.values())\n    return u, c  # unique, counts\n
    "},{"location":"reference/base/#tablite.base.Column.statistics","title":"tablite.base.Column.statistics()","text":"

    provides summary statistics.

    RETURNS DESCRIPTION dict

    returns dict with:

    • min (int/float, length of str, date)
    • max (int/float, length of str, date)
    • mean (int/float, length of str, date)
    • median (int/float, length of str, date)
    • stdev (int/float, length of str, date)
    • mode (int/float, length of str, date)
    • distinct (int/float, length of str, date)
    • iqr (int/float, length of str, date)
    • sum (int/float, length of str, date)
    • histogram (see .histogram)
    Source code in tablite/base.py
    def statistics(self):\n    \"\"\"provides summary statistics.\n\n    Returns:\n        dict: returns dict with:\n        - min (int/float, length of str, date)\n        - max (int/float, length of str, date)\n        - mean (int/float, length of str, date)\n        - median (int/float, length of str, date)\n        - stdev (int/float, length of str, date)\n        - mode (int/float, length of str, date)\n        - distinct (int/float, length of str, date)\n        - iqr (int/float, length of str, date)\n        - sum (int/float, length of str, date)\n        - histogram (see .histogram)\n    \"\"\"\n    values, counts = self.histogram()\n    return summary_statistics(values, counts)\n
    "},{"location":"reference/base/#tablite.base.Column.count","title":"tablite.base.Column.count(item)","text":"

    counts appearances of item in column.

    Note that in python, True == 1 and False == 0, whereby the following difference occurs:

    in python:

    >>> L = [1, True]\n>>> L.count(True)\n2\n

    in tablite:

    >>> t = Table({'L': [1,True]})\n>>> t['L'].count(True)\n1\n
    PARAMETER DESCRIPTION item

    target item

    TYPE: Any

    RETURNS DESCRIPTION int

    number of occurrences of item.

    Source code in tablite/base.py
    def count(self, item):\n    \"\"\"counts appearances of item in column.\n\n    Note that in python, `True == 1` and `False == 0`,\n    whereby the following difference occurs:\n\n    in python:\n    ```\n    >>> L = [1, True]\n    >>> L.count(True)\n    2\n    ```\n    in tablite:\n    ```\n    >>> t = Table({'L': [1,True]})\n    >>> t['L'].count(True)\n    1\n    ```\n\n    Args:\n        item (Any): target item\n\n    Returns:\n        int: number of occurrences of item.\n    \"\"\"\n    result = 0\n    for page in self.pages:\n        data = page.get()\n        if data.dtype != \"O\":\n            result += np.nonzero(page.get() == item)[0].shape[0]\n            # what happens here ---^ below:\n            # arr = page.get()\n            # >>> arr\n            # array([1,2,3,4,3], int64)\n            # >>> (arr == 3)\n            # array([False, False,  True, False,  True])\n            # >>> np.nonzero(arr==3)\n            # (array([2,4], dtype=int64), )  <-- tuple!\n            # >>> np.nonzero(page.get() == item)[0]\n            # array([2,4])\n            # >>> np.nonzero(page.get() == item)[0].shape\n            # (2, )\n            # >>> np.nonzero(page.get() == item)[0].shape[0]\n            # 2\n        else:\n            result += sum(1 for i in data if type(i) == type(item) and i == item)\n    return result\n
    "},{"location":"reference/base/#tablite.base.BaseTable","title":"tablite.base.BaseTable(columns: [dict, None] = None, headers: [list, None] = None, rows: [list, None] = None, _path: [Path, None] = None)","text":"

    Bases: object

    creates Table

    PARAMETER DESCRIPTION EITHER

    columns (dict, optional): dict with column names as keys, values as lists. Example: t = Table(columns={\"a\": [1, 2], \"b\": [3, 4]})

    _path

    path to main process working directory.

    TYPE: Path DEFAULT: None

    Source code in tablite/base.py
    def __init__(\n    self,\n    columns: [dict, None] = None,\n    headers: [list, None] = None,\n    rows: [list, None] = None,\n    _path: [Path, None] = None,\n) -> None:\n    \"\"\"creates Table\n\n    Args:\n        EITHER:\n            columns (dict, optional): dict with column names as keys, values as lists.\n            Example: t = Table(columns={\"a\": [1, 2], \"b\": [3, 4]})\n        OR\n            headers (list of strings, optional): list of column names.\n            rows (list of tuples or lists, optional): values for columns\n            Example: t = Table(headers=[\"a\", \"b\"], rows=[[1,3], [2,4]])\n\n        _path (pathlib.Path, optional): path to main process working directory.\n    \"\"\"\n    if _path is None:\n        if self._pid_dir is None:\n            self._pid_dir = Path(Config.workdir) / Config.pid\n            if not self._pid_dir.exists():\n                self._pid_dir.mkdir()\n                (self._pid_dir / \"pages\").mkdir()\n            register(self._pid_dir)\n\n        _path = Path(self._pid_dir)\n        # if path exists under the given PID it will be overwritten.\n        # this can only happen if the process previously was SIGKILLed.\n    type_check(_path, Path)\n    self.path = _path  # filename used during multiprocessing.\n    self.columns = {}  # maps colunn names to instances of Column.\n\n    # user friendly features.\n    if columns and any((headers, rows)):\n        raise ValueError(\"Either columns as dict OR headers and rows. Not both.\")\n\n    if headers and rows:\n        rotated = list(zip(*rows))\n        columns = {k: v for k, v in zip(headers, rotated)}\n\n    if columns:\n        type_check(columns, dict)\n        for k, v in columns.items():\n            self.__setitem__(k, v)\n
    "},{"location":"reference/base/#tablite.base.BaseTable-attributes","title":"Attributes","text":""},{"location":"reference/base/#tablite.base.BaseTable.path","title":"tablite.base.BaseTable.path = _path instance-attribute","text":""},{"location":"reference/base/#tablite.base.BaseTable.columns","title":"tablite.base.BaseTable.columns = {} instance-attribute","text":""},{"location":"reference/base/#tablite.base.BaseTable.rows","title":"tablite.base.BaseTable.rows property","text":"

    enables row based iteration in python types.

    Example:

    for row in Table.rows:\n    print(row)\n

    Yields: tuple: values is same order as columns.

    "},{"location":"reference/base/#tablite.base.BaseTable-functions","title":"Functions","text":""},{"location":"reference/base/#tablite.base.BaseTable.__str__","title":"tablite.base.BaseTable.__str__()","text":"Source code in tablite/base.py
    def __str__(self):  # USER FUNCTION.\n    return f\"{self.__class__.__name__}({len(self.columns):,} columns, {len(self):,} rows)\"\n
    "},{"location":"reference/base/#tablite.base.BaseTable.__repr__","title":"tablite.base.BaseTable.__repr__()","text":"Source code in tablite/base.py
    def __repr__(self):\n    return self.__str__()\n
    "},{"location":"reference/base/#tablite.base.BaseTable.nbytes","title":"tablite.base.BaseTable.nbytes()","text":"

    finds the total bytes of the table on disk

    RETURNS DESCRIPTION tuple

    int: real bytes used on disk int: total bytes used if flattened

    Source code in tablite/base.py
    def nbytes(self):  # USER FUNCTION.\n    \"\"\"finds the total bytes of the table on disk\n\n    Returns:\n        tuple:\n            int: real bytes used on disk\n            int: total bytes used if flattened\n    \"\"\"\n    real = {}\n    total = 0\n    for column in self.columns.values():\n        for page in set(column.pages):\n            real[page] = page.path.stat().st_size\n        for page in column.pages:\n            total += real[page]\n    return sum(real.values()), total\n
    "},{"location":"reference/base/#tablite.base.BaseTable.items","title":"tablite.base.BaseTable.items()","text":"

    returns table as dict

    RETURNS DESCRIPTION dict

    Table as dict {column_name: [values], ...}

    Source code in tablite/base.py
    def items(self):  # USER FUNCTION.\n    \"\"\"returns table as dict\n\n    Returns:\n        dict: Table as dict `{column_name: [values], ...}`\n    \"\"\"\n    return {\n        name: column[:].tolist() for name, column in self.columns.items()\n    }.items()\n
    "},{"location":"reference/base/#tablite.base.BaseTable.__delitem__","title":"tablite.base.BaseTable.__delitem__(key)","text":"

    Examples:

    >>> del table['a']  # removes column 'a'\n>>> del table[-3:]  # removes last 3 rows from all columns.\n
    Source code in tablite/base.py
    def __delitem__(self, key):  # USER FUNCTION.\n    \"\"\"\n    Examples:\n    ```\n    >>> del table['a']  # removes column 'a'\n    >>> del table[-3:]  # removes last 3 rows from all columns.\n    ```\n    \"\"\"\n    if isinstance(key, (int, slice)):\n        for column in self.columns.values():\n            del column[key]\n    elif key in self.columns:\n        del self.columns[key]\n    else:\n        raise KeyError(f\"Key not found: {key}\")\n
    "},{"location":"reference/base/#tablite.base.BaseTable.__setitem__","title":"tablite.base.BaseTable.__setitem__(key, value)","text":"

    table behaves like a dict. Args: key (str or hashable): column name value (iterable): list, tuple or nd.array with values.

    As Table now accepts the keyword columns as a dict:

    >>> t = Table(columns={'b':[4,5,6], 'c':[7,8,9]})\n

    and the header/data combinations:

    >>> t = Table(header=['b','c'], data=[[4,5,6],[7,8,9]])\n

    This has the side-benefit that tuples now can be used as headers.

    Source code in tablite/base.py
    def __setitem__(self, key, value):  # USER FUNCTION\n    \"\"\"table behaves like a dict.\n    Args:\n        key (str or hashable): column name\n        value (iterable): list, tuple or nd.array with values.\n\n    As Table now accepts the keyword `columns` as a dict:\n    ```\n    >>> t = Table(columns={'b':[4,5,6], 'c':[7,8,9]})\n    ```\n    and the header/data combinations:\n    ```\n    >>> t = Table(header=['b','c'], data=[[4,5,6],[7,8,9]])\n    ```\n    This has the side-benefit that tuples now can be used as headers.\n    \"\"\"\n    if value is None:\n        self.columns[key] = Column(self.path, value=None)\n    elif isinstance(value, (list, tuple)):\n        value = list_to_np_array(value)\n        self.columns[key] = Column(self.path, value)\n    elif isinstance(value, (np.ndarray)):\n        self.columns[key] = Column(self.path, value)\n    elif isinstance(value, Column):\n        self.columns[key] = value\n    else:\n        raise TypeError(f\"{type(value)} not supported.\")\n
    "},{"location":"reference/base/#tablite.base.BaseTable.__getitem__","title":"tablite.base.BaseTable.__getitem__(keys)","text":"

    Enables selection of columns and rows

    PARAMETER DESCRIPTION keys

    TYPE: column name, integer or slice

    Examples

    >>>

    10] selects first 10 rows from all columns

    TYPE: table[

    >>>

    20:3] selects column 'b' and 'c' and 'a' twice for a slice.

    TYPE: table['b', 'a', 'a', 'c', 2

    Raises: KeyError: if key is not found. TypeError: if key is not a string, integer or slice.

    RETURNS DESCRIPTION Table

    returns columns in same order as selection.

    Source code in tablite/base.py
    def __getitem__(self, keys):  # USER FUNCTION\n    \"\"\"\n    Enables selection of columns and rows\n\n    Args:\n        keys (column name, integer or slice):\n        Examples:\n        ```\n        >>> table['a']                        selects column 'a'\n        >>> table[3]                          selects row 3 as a tuple.\n        >>> table[:10]                        selects first 10 rows from all columns\n        >>> table['a','b', slice(3,20,2)]     selects a slice from columns 'a' and 'b'\n        >>> table['b', 'a', 'a', 'c', 2:20:3] selects column 'b' and 'c' and 'a' twice for a slice.\n        >>> table[('b', 'a', 'a', 'c')]       selects columns 'b', 'a', 'a', and 'c' using a tuple.\n        ```\n    Raises:\n        KeyError: if key is not found.\n        TypeError: if key is not a string, integer or slice.\n\n    Returns:\n        Table: returns columns in same order as selection.\n    \"\"\"\n\n    if not isinstance(keys, tuple):\n        if isinstance(keys, list):\n            keys = tuple(keys)\n        else:\n            keys = (keys,)\n    if isinstance(keys[0], tuple):\n        keys = tuple(list(chain(*keys)))\n\n    integers = [i for i in keys if isinstance(i, int)]\n    if len(integers) == len(keys) == 1:  # return a single tuple.\n        keys = [slice(keys[0])]\n\n    column_names = [i for i in keys if isinstance(i, str)]\n    column_names = list(self.columns) if not column_names else column_names\n    not_found = [name for name in column_names if name not in self.columns]\n    if not_found:\n        raise KeyError(f\"keys not found: {', '.join(not_found)}\")\n\n    slices = [i for i in keys if isinstance(i, slice)]\n    slc = slice(0, len(self)) if not slices else slices[0]\n\n    if (\n        len(slices) == 0 and len(column_names) == 1\n    ):  # e.g. tbl['a'] or tbl['a'][:10]\n        col = self.columns[column_names[0]]\n        if slices:\n            return col[slc]  # return slice from column as list of values\n        else:\n            return col  # return whole column\n\n    elif len(integers) == 1:  # return a single tuple.\n        row_no = integers[0]\n        slc = slice(row_no, row_no + 1)\n        return tuple(self.columns[name][slc].tolist()[0] for name in column_names)\n\n    elif not slices:  # e.g. new table with N whole columns.\n        return self.__class__(\n            columns={name: self.columns[name] for name in column_names}\n        )\n\n    else:  # e.g. new table from selection of columns and slices.\n        t = self.__class__()\n        for name in column_names:\n            column = self.columns[name]\n\n            new_column = Column(t.path)  # create new Column.\n            for item in column.getpages(slc):\n                if isinstance(item, np.ndarray):\n                    new_column.extend(item)  # extend subslice (expensive)\n                elif isinstance(item, SimplePage):\n                    new_column.pages.append(item)  # extend page (cheap)\n                else:\n                    raise TypeError(f\"Bad item: {item}\")\n\n            # below:\n            # set the new column directly on t.columns.\n            # Do not use t[name] as that triggers __setitem__ again.\n            t.columns[name] = new_column\n\n        return t\n
    "},{"location":"reference/base/#tablite.base.BaseTable.__len__","title":"tablite.base.BaseTable.__len__()","text":"Source code in tablite/base.py
    def __len__(self):  # USER FUNCTION.\n    if not self.columns:\n        return 0\n    return max(len(c) for c in self.columns.values())\n
    "},{"location":"reference/base/#tablite.base.BaseTable.__eq__","title":"tablite.base.BaseTable.__eq__(other) -> bool","text":"

    Determines if two tables have identical content.

    PARAMETER DESCRIPTION other

    table for comparison

    TYPE: Table

    RETURNS DESCRIPTION bool

    True if tables are identical.

    TYPE: bool

    Source code in tablite/base.py
    def __eq__(self, other) -> bool:  # USER FUNCTION.\n    \"\"\"Determines if two tables have identical content.\n\n    Args:\n        other (Table): table for comparison\n\n    Returns:\n        bool: True if tables are identical.\n    \"\"\"\n    if isinstance(other, dict):\n        return self.items() == other.items()\n    if not isinstance(other, BaseTable):\n        return False\n    if id(self) == id(other):\n        return True\n    if len(self) != len(other):\n        return False\n    if len(self) == len(other) == 0:\n        return True\n    if self.columns.keys() != other.columns.keys():\n        return False\n    for name, col in self.columns.items():\n        if not (col == other.columns[name]):\n            return False\n    return True\n
    "},{"location":"reference/base/#tablite.base.BaseTable.clear","title":"tablite.base.BaseTable.clear()","text":"

    clears the table. Like dict().clear()

    Source code in tablite/base.py
    def clear(self):  # USER FUNCTION.\n    \"\"\"clears the table. Like dict().clear()\"\"\"\n    self.columns.clear()\n
    "},{"location":"reference/base/#tablite.base.BaseTable.save","title":"tablite.base.BaseTable.save(path, compression_method=zipfile.ZIP_DEFLATED, compression_level=1)","text":"

    saves table to compressed tpz file.

    PARAMETER DESCRIPTION path

    file destination.

    TYPE: Path

    compression_method

    See zipfile compression methods. Defaults to ZIP_DEFLATED.

    DEFAULT: ZIP_DEFLATED

    compression_level

    See zipfile compression levels. Defaults to 1.

    DEFAULT: 1

    The file format is as follows: .tpz is a gzip archive with table metadata captured as table.yml and the necessary set of pages saved as .npy files.

    The zip contains table.yml which provides an overview of the data:

    --------------------------------------\n%YAML 1.2                              yaml version\ncolumns:                               start of columns section.\n    name: \u201c\u5217 1\u201d                       name of column 1.\n        pages: [p1b1, p1b2]            list of pages in column 1.\n    name: \u201c\u5217 2\u201d                       name of column 2\n        pages: [p2b1, p2b2]            list of pages in column 2.\n----------------------------------------\n
    Source code in tablite/base.py
    def save(\n    self, path, compression_method=zipfile.ZIP_DEFLATED, compression_level=1\n):  # USER FUNCTION.\n    \"\"\"saves table to compressed tpz file.\n\n    Args:\n        path (Path): file destination.\n        compression_method: See zipfile compression methods. Defaults to ZIP_DEFLATED.\n        compression_level: See zipfile compression levels. Defaults to 1.\n        The default settings produce 80% compression at 10% slowdown.\n\n    The file format is as follows:\n    .tpz is a gzip archive with table metadata captured as table.yml\n    and the necessary set of pages saved as .npy files.\n\n    The zip contains table.yml which provides an overview of the data:\n    ```\n    --------------------------------------\n    %YAML 1.2                              yaml version\n    columns:                               start of columns section.\n        name: \u201c\u5217 1\u201d                       name of column 1.\n            pages: [p1b1, p1b2]            list of pages in column 1.\n        name: \u201c\u5217 2\u201d                       name of column 2\n            pages: [p2b1, p2b2]            list of pages in column 2.\n    ----------------------------------------\n    ```\n    \"\"\"\n    if isinstance(path, str):\n        path = Path(path)\n    type_check(path, Path)\n    if path.is_dir():\n        raise TypeError(f\"filename needed: {path}\")\n    if path.suffix != \".tpz\":\n        path = path.parent / (path.parts[-1] + \".tpz\")\n\n    # create yaml document\n    _page_counter = 0\n    d = {}\n    cols = {}\n    for name, col in self.columns.items():\n        type_check(col, Column)\n        cols[name] = {\"pages\": [p.path.name for p in col.pages]}\n        _page_counter += len(col.pages)\n    d[\"columns\"] = cols\n    yml = yaml.safe_dump(\n        d, sort_keys=False, allow_unicode=True, default_flow_style=None\n    )\n\n    _file_counter = 0\n    with zipfile.ZipFile(\n        path, \"w\", compression=compression_method, compresslevel=compression_level\n    ) as f:\n        log.debug(f\"writing .tpz to {path} with\\n{yml}\")\n        f.writestr(\"table.yml\", yml)\n        for name, col in self.columns.items():\n            for page in set(\n                col.pages\n            ):  # set of pages! remember t *= 1000 repeats t 1000x\n                with open(page.path, \"rb\", buffering=0) as raw_io:\n                    f.writestr(page.path.name, raw_io.read())\n                _file_counter += 1\n                log.debug(f\"adding Page {page.path}\")\n\n        _fields = len(self) * len(self.columns)\n        _avg = _fields // _page_counter\n        log.debug(\n            f\"Wrote {_fields:,} on {_page_counter:,} pages in {_file_counter} files: {_avg} fields/page\"\n        )\n
    "},{"location":"reference/base/#tablite.base.BaseTable.load","title":"tablite.base.BaseTable.load(path, tqdm=_tqdm) classmethod","text":"

    loads a table from .tpz file. See also Table.save for details on the file format.

    PARAMETER DESCRIPTION path

    source file

    TYPE: Path

    RETURNS DESCRIPTION Table

    table in read-only mode.

    Source code in tablite/base.py
    @classmethod\ndef load(cls, path, tqdm=_tqdm):  # USER FUNCTION.\n    \"\"\"loads a table from .tpz file.\n    See also Table.save for details on the file format.\n\n    Args:\n        path (Path): source file\n\n    Returns:\n        Table: table in read-only mode.\n    \"\"\"\n    path = Path(path)\n    log.debug(f\"loading {path}\")\n    with zipfile.ZipFile(path, \"r\") as f:\n        yml = f.read(\"table.yml\")\n        metadata = yaml.safe_load(yml)\n        t = cls()\n\n        page_count = sum([len(c[\"pages\"]) for c in metadata[\"columns\"].values()])\n\n        with tqdm(\n            total=page_count,\n            desc=f\"loading '{path.name}' file\",\n            disable=Config.TQDM_DISABLE,\n        ) as pbar:\n            for name, d in metadata[\"columns\"].items():\n                column = Column(t.path)\n                for page in d[\"pages\"]:\n                    bytestream = io.BytesIO(f.read(page))\n                    data = np.load(bytestream, allow_pickle=True, fix_imports=False)\n                    column.extend(data)\n                    pbar.update(1)\n                t.columns[name] = column\n    update_access_time(path)\n    return t\n
    "},{"location":"reference/base/#tablite.base.BaseTable.copy","title":"tablite.base.BaseTable.copy()","text":"Source code in tablite/base.py
    def copy(self):\n    cls = type(self)\n    t = cls()\n    for name, column in self.columns.items():\n        new = Column(t.path)\n        new.pages = column.pages[:]\n        t.columns[name] = new\n    return t\n
    "},{"location":"reference/base/#tablite.base.BaseTable.__imul__","title":"tablite.base.BaseTable.__imul__(other)","text":"

    Repeats instance of table N times.

    Like list: t = t * N

    PARAMETER DESCRIPTION other

    multiplier

    TYPE: int

    Source code in tablite/base.py
    def __imul__(self, other):\n    \"\"\"Repeats instance of table N times.\n\n    Like list: `t = t * N`\n\n    Args:\n        other (int): multiplier\n    \"\"\"\n    if not (isinstance(other, int) and other > 0):\n        raise TypeError(\n            f\"a table can be repeated an integer number of times, not {type(other)} number of times\"\n        )\n    for col in self.columns.values():\n        col *= other\n    return self\n
    "},{"location":"reference/base/#tablite.base.BaseTable.__mul__","title":"tablite.base.BaseTable.__mul__(other)","text":"

    Repeat table N times. Like list: new = old * N

    PARAMETER DESCRIPTION other

    multiplier

    TYPE: int

    RETURNS DESCRIPTION

    Table

    Source code in tablite/base.py
    def __mul__(self, other):\n    \"\"\"Repeat table N times.\n    Like list: `new = old * N`\n\n    Args:\n        other (int): multiplier\n\n    Returns:\n        Table\n    \"\"\"\n    new = self.copy()\n    return new.__imul__(other)\n
    "},{"location":"reference/base/#tablite.base.BaseTable.__iadd__","title":"tablite.base.BaseTable.__iadd__(other)","text":"

    Concatenates tables with same column names.

    Like list: table_1 += table_2

    RAISES DESCRIPTION ValueError

    If column names don't match.

    RETURNS DESCRIPTION None

    self is updated.

    Source code in tablite/base.py
    def __iadd__(self, other):\n    \"\"\"Concatenates tables with same column names.\n\n    Like list: `table_1 += table_2`\n\n    Args:\n        other (Table)\n\n    Raises:\n        ValueError: If column names don't match.\n\n    Returns:\n        None: self is updated.\n    \"\"\"\n    type_check(other, BaseTable)\n    for name in self.columns.keys():\n        if name not in other.columns:\n            raise ValueError(f\"{name} not in other\")\n    for name in other.columns.keys():\n        if name not in self.columns:\n            raise ValueError(f\"{name} missing from self\")\n\n    for name, column in self.columns.items():\n        other_col = other.columns.get(name, None)\n        column.pages.extend(other_col.pages[:])\n    return self\n
    "},{"location":"reference/base/#tablite.base.BaseTable.__add__","title":"tablite.base.BaseTable.__add__(other)","text":"

    Concatenates tables with same column names.

    Like list: table_3 = table_1 + table_2

    RAISES DESCRIPTION ValueError

    If column names don't match.

    RETURNS DESCRIPTION

    Table

    Source code in tablite/base.py
    def __add__(self, other):\n    \"\"\"Concatenates tables with same column names.\n\n    Like list: `table_3 = table_1 + table_2`\n\n    Args:\n        other (Table)\n\n    Raises:\n        ValueError: If column names don't match.\n\n    Returns:\n        Table\n    \"\"\"\n    type_check(other, BaseTable)\n    cp = self.copy()\n    cp += other\n    return cp\n
    "},{"location":"reference/base/#tablite.base.BaseTable.add_rows","title":"tablite.base.BaseTable.add_rows(*args, **kwargs)","text":"

    its more efficient to add many rows at once.

    if both args and kwargs, then args are added first, followed by kwargs.

    supported cases:

    >>> t = Table()\n>>> t.add_columns('row','A','B','C')\n>>> t.add_rows(1, 1, 2, 3)                              # (1) individual values as args\n>>> t.add_rows([2, 1, 2, 3])                            # (2) list of values as args\n>>> t.add_rows((3, 1, 2, 3))                            # (3) tuple of values as args\n>>> t.add_rows(*(4, 1, 2, 3))                           # (4) unpacked tuple becomes arg like (1)\n>>> t.add_rows(row=5, A=1, B=2, C=3)                    # (5) kwargs\n>>> t.add_rows(**{'row': 6, 'A': 1, 'B': 2, 'C': 3})    # (6) dict / json interpreted a kwargs\n>>> t.add_rows((7, 1, 2, 3), (8, 4, 5, 6))              # (7) two (or more) tuples as args\n>>> t.add_rows([9, 1, 2, 3], [10, 4, 5, 6])             # (8) two or more lists as rgs\n>>> t.add_rows(\n    {'row': 11, 'A': 1, 'B': 2, 'C': 3},\n    {'row': 12, 'A': 4, 'B': 5, 'C': 6}\n    )                                                   # (9) two (or more) dicts as args - roughly comma sep'd json.\n>>> t.add_rows( *[\n    {'row': 13, 'A': 1, 'B': 2, 'C': 3},\n    {'row': 14, 'A': 1, 'B': 2, 'C': 3}\n    ])                                                  # (10) list of dicts as args\n>>> t.add_rows(row=[15,16], A=[1,1], B=[2,2], C=[3,3])  # (11) kwargs with lists as values\n
    Source code in tablite/base.py
    def add_rows(self, *args, **kwargs):\n    \"\"\"its more efficient to add many rows at once.\n\n    if both args and kwargs, then args are added first, followed by kwargs.\n\n    supported cases:\n    ```\n    >>> t = Table()\n    >>> t.add_columns('row','A','B','C')\n    >>> t.add_rows(1, 1, 2, 3)                              # (1) individual values as args\n    >>> t.add_rows([2, 1, 2, 3])                            # (2) list of values as args\n    >>> t.add_rows((3, 1, 2, 3))                            # (3) tuple of values as args\n    >>> t.add_rows(*(4, 1, 2, 3))                           # (4) unpacked tuple becomes arg like (1)\n    >>> t.add_rows(row=5, A=1, B=2, C=3)                    # (5) kwargs\n    >>> t.add_rows(**{'row': 6, 'A': 1, 'B': 2, 'C': 3})    # (6) dict / json interpreted a kwargs\n    >>> t.add_rows((7, 1, 2, 3), (8, 4, 5, 6))              # (7) two (or more) tuples as args\n    >>> t.add_rows([9, 1, 2, 3], [10, 4, 5, 6])             # (8) two or more lists as rgs\n    >>> t.add_rows(\n        {'row': 11, 'A': 1, 'B': 2, 'C': 3},\n        {'row': 12, 'A': 4, 'B': 5, 'C': 6}\n        )                                                   # (9) two (or more) dicts as args - roughly comma sep'd json.\n    >>> t.add_rows( *[\n        {'row': 13, 'A': 1, 'B': 2, 'C': 3},\n        {'row': 14, 'A': 1, 'B': 2, 'C': 3}\n        ])                                                  # (10) list of dicts as args\n    >>> t.add_rows(row=[15,16], A=[1,1], B=[2,2], C=[3,3])  # (11) kwargs with lists as values\n    ```\n\n    \"\"\"\n    if not BaseTable._add_row_slow_warning:\n        warnings.warn(\n            \"add_rows is slow. Consider using add_columns and then assigning values to the columns directly.\"\n        )\n        BaseTable._add_row_slow_warning = True\n\n    if args:\n        if not all(isinstance(i, (list, tuple, dict)) for i in args):  # 1,4\n            args = [args]\n\n        if all(isinstance(i, (list, tuple, dict)) for i in args):  # 2,3,7,8\n            # 1. turn the data into columns:\n\n            d = {n: [] for n in self.columns}\n            for arg in args:\n                if len(arg) != len(self.columns):\n                    raise ValueError(\n                        f\"len({arg})== {len(arg)}, but there are {len(self.columns)} columns\"\n                    )\n\n                if isinstance(arg, dict):\n                    for k, v in arg.items():  # 7,8\n                        d[k].append(v)\n\n                elif isinstance(arg, (list, tuple)):  # 2,3\n                    for n, v in zip(self.columns, arg):\n                        d[n].append(v)\n\n                else:\n                    raise TypeError(f\"{arg}?\")\n            # 2. extend the columns\n            for n, values in d.items():\n                col = self.columns[n]\n                col.extend(list_to_np_array(values))\n\n    if kwargs:\n        if isinstance(kwargs, dict):\n            if all(isinstance(v, (list, tuple)) for v in kwargs.values()):\n                for k, v in kwargs.items():\n                    col = self.columns[k]\n                    col.extend(list_to_np_array(v))\n            else:\n                for k, v in kwargs.items():\n                    col = self.columns[k]\n                    col.extend(np.array([v]))\n        else:\n            raise ValueError(f\"format not recognised: {kwargs}\")\n\n    return\n
    "},{"location":"reference/base/#tablite.base.BaseTable.add_columns","title":"tablite.base.BaseTable.add_columns(*names)","text":"

    Adds column names to table.

    Source code in tablite/base.py
    def add_columns(self, *names):\n    \"\"\"Adds column names to table.\"\"\"\n    for name in names:\n        self.columns[name] = Column(self.path)\n
    "},{"location":"reference/base/#tablite.base.BaseTable.add_column","title":"tablite.base.BaseTable.add_column(name, data=None)","text":"

    verbose alias for table[name] = data, that checks if name already exists

    PARAMETER DESCRIPTION name

    column name

    TYPE: str

    data

    values. Defaults to None.

    TYPE: list,tuple) DEFAULT: None

    RAISES DESCRIPTION TypeError

    name isn't string

    ValueError

    name already exists

    Source code in tablite/base.py
    def add_column(self, name, data=None):\n    \"\"\"verbose alias for table[name] = data, that checks if name already exists\n\n    Args:\n        name (str): column name\n        data ((list,tuple), optional): values. Defaults to None.\n\n    Raises:\n        TypeError: name isn't string\n        ValueError: name already exists\n    \"\"\"\n    if not isinstance(name, str):\n        raise TypeError(\"expected name as string\")\n    if name in self.columns:\n        raise ValueError(f\"{name} already in {self.columns}\")\n    self.__setitem__(name, data)\n
    "},{"location":"reference/base/#tablite.base.BaseTable.stack","title":"tablite.base.BaseTable.stack(other)","text":"

    returns the joint stack of tables with overlapping column names. Example:

    | Table A|  +  | Table B| = |  Table AB |\n| A| B| C|     | A| B| D|   | A| B| C| -|\n                            | A| B| -| D|\n
    Source code in tablite/base.py
    def stack(self, other):\n    \"\"\"\n    returns the joint stack of tables with overlapping column names.\n    Example:\n    ```\n    | Table A|  +  | Table B| = |  Table AB |\n    | A| B| C|     | A| B| D|   | A| B| C| -|\n                                | A| B| -| D|\n    ```\n    \"\"\"\n    if not isinstance(other, BaseTable):\n        raise TypeError(f\"stack only works for Table, not {type(other)}\")\n\n    cp = self.copy()\n    for name, col2 in other.columns.items():\n        if name not in cp.columns:\n            cp[name] = [None] * len(self)\n        cp[name].pages.extend(col2.pages[:])\n\n    for name in self.columns:\n        if name not in other.columns:\n            if len(cp) > 0:\n                cp[name].extend(np.array([None] * len(other)))\n    return cp\n
    "},{"location":"reference/base/#tablite.base.BaseTable.types","title":"tablite.base.BaseTable.types()","text":"

    returns nested dict of data types in the form: {column name: {python type class: number of instances }, ... }

    example:

    >>> t.types()\n{\n    'A': {<class 'str'>: 7},\n    'B': {<class 'int'>: 7}\n}\n
    Source code in tablite/base.py
    def types(self):\n    \"\"\"\n    returns nested dict of data types in the form:\n    `{column name: {python type class: number of instances }, ... }`\n\n    example:\n    ```\n    >>> t.types()\n    {\n        'A': {<class 'str'>: 7},\n        'B': {<class 'int'>: 7}\n    }\n    ```\n    \"\"\"\n    d = {}\n    for name, col in self.columns.items():\n        assert isinstance(col, Column)\n        d[name] = col.types()\n    return d\n
    "},{"location":"reference/base/#tablite.base.BaseTable.display_dict","title":"tablite.base.BaseTable.display_dict(slice_=None, blanks=None, dtype=False)","text":"

    helper for creating dict for display.

    PARAMETER DESCRIPTION slice_

    python slice. Defaults to None.

    TYPE: slice DEFAULT: None

    blanks

    fill value for None. Defaults to None.

    TYPE: optional DEFAULT: None

    dtype

    Adds datatype to each column. Defaults to False.

    TYPE: bool DEFAULT: False

    RAISES DESCRIPTION TypeError

    slice_ must be None or slice.

    RETURNS DESCRIPTION dict

    from Table.

    Source code in tablite/base.py
    def display_dict(self, slice_=None, blanks=None, dtype=False):\n    \"\"\"helper for creating dict for display.\n\n    Args:\n        slice_ (slice, optional): python slice. Defaults to None.\n        blanks (optional): fill value for `None`. Defaults to None.\n        dtype (bool, optional): Adds datatype to each column. Defaults to False.\n\n    Raises:\n        TypeError: slice_ must be None or slice.\n\n    Returns:\n        dict: from Table.\n    \"\"\"\n    if not self.columns:\n        print(\"Empty Table\")\n        return\n\n    def datatype(col):  # PRIVATE\n        \"\"\"creates label for column datatype.\"\"\"\n        types = col.types()\n        if len(types) == 0:\n            typ = \"empty\"\n        elif len(types) == 1:\n            dt, _ = types.popitem()\n            typ = dt.__name__\n        else:\n            typ = \"mixed\"\n        return typ\n\n    row_count_tags = [\"#\", \"~\", \"*\"]\n    cols = set(self.columns)\n    for n, tag in product(range(1, 6), row_count_tags):\n        if n * tag not in cols:\n            tag = n * tag\n            break\n\n    if not isinstance(slice_, (slice, type(None))):\n        raise TypeError(f\"slice_ must be None or slice, not {type(slice_)}\")\n    if isinstance(slice_, slice):\n        slc = slice_\n    if slice_ is None:\n        if len(self) <= 20:\n            slc = slice(0, 20, 1)\n        else:\n            slc = None\n\n    n = len(self)\n    if slc:  # either we want slc or we want everything.\n        row_no = list(range(*slc.indices(len(self))))\n        data = {tag: [f\"{i:,}\".rjust(2) for i in row_no]}\n        for name, col in self.columns.items():\n            data[name] = list(chain(iter(col), repeat(blanks, times=n - len(col))))[\n                slc\n            ]\n    else:\n        data = {}\n        j = int(math.ceil(math.log10(n)) / 3) + len(str(n))\n        row_no = (\n            [f\"{i:,}\".rjust(j) for i in range(7)]\n            + [\"...\"]\n            + [f\"{i:,}\".rjust(j) for i in range(n - 7, n)]\n        )\n        data = {tag: row_no}\n\n        for name, col in self.columns.items():\n            if len(col) == n:\n                row = col[:7].tolist() + [\"...\"] + col[-7:].tolist()\n            else:\n                empty = [blanks] * 7\n                head = (col[:7].tolist() + empty)[:7]\n                tail = (col[n - 7 :].tolist() + empty)[-7:]\n                row = head + [\"...\"] + tail\n            data[name] = row\n\n    if dtype:\n        for name, values in data.items():\n            if name in self.columns:\n                col = self.columns[name]\n                values.insert(0, datatype(col))\n            else:\n                values.insert(0, \"row\")\n\n    return data\n
    "},{"location":"reference/base/#tablite.base.BaseTable.to_ascii","title":"tablite.base.BaseTable.to_ascii(slice_=None, blanks=None, dtype=False)","text":"

    returns ascii view of table as string.

    PARAMETER DESCRIPTION slice_

    slice to determine table snippet.

    TYPE: slice DEFAULT: None

    blanks

    value for whitespace. Defaults to None.

    TYPE: str DEFAULT: None

    dtype

    adds subheader with datatype for column. Defaults to False.

    TYPE: bool DEFAULT: False

    Source code in tablite/base.py
    def to_ascii(self, slice_=None, blanks=None, dtype=False):\n    \"\"\"returns ascii view of table as string.\n\n    Args:\n        slice_ (slice, optional): slice to determine table snippet.\n        blanks (str, optional): value for whitespace. Defaults to None.\n        dtype (bool, optional): adds subheader with datatype for column. Defaults to False.\n    \"\"\"\n\n    def adjust(v, length):  # PRIVATE FUNCTION\n        \"\"\"whitespace justifies field values based on datatype\"\"\"\n        if v is None:\n            return str(blanks).ljust(length)\n        elif isinstance(v, str):\n            return v.ljust(length)\n        else:\n            return str(v).rjust(length)\n\n    if not self.columns:\n        return str(self)\n\n    d = {}\n    for name, values in self.display_dict(\n        slice_=slice_, blanks=blanks, dtype=dtype\n    ).items():\n        as_text = [str(v) for v in values] + [str(name)]\n        width = max(len(i) for i in as_text)\n        new_name = name.center(width, \" \")\n        if dtype:\n            values[0] = values[0].center(width, \" \")\n        d[new_name] = [adjust(v, width) for v in values]\n\n    rows = dict_to_rows(d)\n    s = []\n    s.append(\"+\" + \"+\".join([\"=\" * len(n) for n in rows[0]]) + \"+\")\n    s.append(\"|\" + \"|\".join(rows[0]) + \"|\")  # column names\n    start = 1\n    if dtype:\n        s.append(\"|\" + \"|\".join(rows[1]) + \"|\")  # datatypes\n        start = 2\n\n    s.append(\"+\" + \"+\".join([\"-\" * len(n) for n in rows[0]]) + \"+\")\n    for row in rows[start:]:\n        s.append(\"|\" + \"|\".join(row) + \"|\")\n    s.append(\"+\" + \"+\".join([\"=\" * len(n) for n in rows[0]]) + \"+\")\n\n    if len(set(len(c) for c in self.columns.values())) != 1:\n        warning = f\"Warning: Columns have different lengths. {blanks} is used as fill value.\"\n        s.append(warning)\n\n    return \"\\n\".join(s)\n
    "},{"location":"reference/base/#tablite.base.BaseTable.show","title":"tablite.base.BaseTable.show(slice_=None, blanks=None, dtype=False)","text":"

    prints ascii view of table.

    PARAMETER DESCRIPTION slice_

    slice to determine table snippet.

    TYPE: slice DEFAULT: None

    blanks

    value for whitespace. Defaults to None.

    TYPE: str DEFAULT: None

    dtype

    adds subheader with datatype for column. Defaults to False.

    TYPE: bool DEFAULT: False

    Source code in tablite/base.py
    def show(self, slice_=None, blanks=None, dtype=False):\n    \"\"\"prints ascii view of table.\n\n    Args:\n        slice_ (slice, optional): slice to determine table snippet.\n        blanks (str, optional): value for whitespace. Defaults to None.\n        dtype (bool, optional): adds subheader with datatype for column. Defaults to False.\n    \"\"\"\n    print(self.to_ascii(slice_=slice_, blanks=blanks, dtype=dtype))\n
    "},{"location":"reference/base/#tablite.base.BaseTable.to_dict","title":"tablite.base.BaseTable.to_dict(columns=None, slice_=None)","text":"

    columns: list of column names. Default is None == all columns. slice_: slice. Default is None == all rows.

    returns: dict with columns as keys and lists of values.

    Example:

    >>> t.show()\n+===+===+===+\n| # | a | b |\n|row|int|int|\n+---+---+---+\n| 0 |  1|  3|\n| 1 |  2|  4|\n+===+===+===+\n>>> t.to_dict()\n{'a':[1,2], 'b':[3,4]}\n
    Source code in tablite/base.py
    def to_dict(self, columns=None, slice_=None):\n    \"\"\"\n    columns: list of column names. Default is None == all columns.\n    slice_: slice. Default is None == all rows.\n\n    returns: dict with columns as keys and lists of values.\n\n    Example:\n    ```\n    >>> t.show()\n    +===+===+===+\n    | # | a | b |\n    |row|int|int|\n    +---+---+---+\n    | 0 |  1|  3|\n    | 1 |  2|  4|\n    +===+===+===+\n    >>> t.to_dict()\n    {'a':[1,2], 'b':[3,4]}\n    ```\n\n    \"\"\"\n    if slice_ is None:\n        slice_ = slice(0, len(self))\n    assert isinstance(slice_, slice)\n\n    if columns is None:\n        columns = list(self.columns.keys())\n    if not isinstance(columns, list):\n        raise TypeError(\"expected columns as list of strings\")\n\n    return {name: list(self.columns[name][slice_]) for name in columns}\n
    "},{"location":"reference/base/#tablite.base.BaseTable.as_json_serializable","title":"tablite.base.BaseTable.as_json_serializable(row_count='row id', start_on=1, columns=None, slice_=None)","text":"

    provides a JSON compatible format of the table.

    PARAMETER DESCRIPTION row_count

    Label for row counts. Defaults to \"row id\".

    TYPE: str DEFAULT: 'row id'

    start_on

    row counts starts by default on 1.

    TYPE: int DEFAULT: 1

    columns

    Column names. Defaults to None which returns all columns.

    TYPE: list of str DEFAULT: None

    slice_

    selector. Defaults to None which returns [:]

    TYPE: slice DEFAULT: None

    RETURNS DESCRIPTION

    JSON serializable dict: All python datatypes have been converted to JSON compliant data.

    Source code in tablite/base.py
    def as_json_serializable(\n    self, row_count=\"row id\", start_on=1, columns=None, slice_=None\n):\n    \"\"\"provides a JSON compatible format of the table.\n\n    Args:\n        row_count (str, optional): Label for row counts. Defaults to \"row id\".\n        start_on (int, optional): row counts starts by default on 1.\n        columns (list of str, optional): Column names.\n            Defaults to None which returns all columns.\n        slice_ (slice, optional): selector. Defaults to None which returns [:]\n\n    Returns:\n        JSON serializable dict: All python datatypes have been converted to JSON compliant data.\n    \"\"\"\n    if slice_ is None:\n        slice_ = slice(0, len(self))\n\n    assert isinstance(slice_, slice)\n    new = {\"columns\": {}, \"total_rows\": len(self)}\n    if row_count is not None:\n        new[\"columns\"][row_count] = [\n            i + start_on for i in range(*slice_.indices(len(self)))\n        ]\n\n    d = self.to_dict(columns, slice_=slice_)\n    for k, data in d.items():\n        new_k = unique_name(\n            k, new[\"columns\"]\n        )  # used to avoid overwriting the `row id` key.\n        new[\"columns\"][new_k] = [\n            DataTypes.to_json(v) for v in data\n        ]  # deal with non-json datatypes.\n    return new\n
    "},{"location":"reference/base/#tablite.base.BaseTable.index","title":"tablite.base.BaseTable.index(*args)","text":"

    param: *args: column names returns multikey index on the columns as d[(key tuple, )] = {index1, index2, ...}

    Examples:

    >>> table6 = Table()\n>>> table6['A'] = ['Alice', 'Bob', 'Bob', 'Ben', 'Charlie', 'Ben','Albert']\n>>> table6['B'] = ['Alison', 'Marley', 'Dylan', 'Affleck', 'Hepburn', 'Barnes', 'Einstein']\n
    >>> table6.index('A')  # single key.\n{('Alice',): [0],\n ('Bob',): [1, 2],\n ('Ben',): [3, 5],\n ('Charlie',): [4],\n ('Albert',): [6]})\n
    >>> table6.index('A', 'B')  # multiple keys.\n{('Alice', 'Alison'): [0],\n ('Bob', 'Marley'): [1],\n ('Bob', 'Dylan'): [2],\n ('Ben', 'Affleck'): [3],\n ('Charlie', 'Hepburn'): [4],\n ('Ben', 'Barnes'): [5],\n ('Albert', 'Einstein'): [6]})\n
    Source code in tablite/base.py
    def index(self, *args):\n    \"\"\"\n    param: *args: column names\n    returns multikey index on the columns as d[(key tuple, )] = {index1, index2, ...}\n\n    Examples:\n        ```\n        >>> table6 = Table()\n        >>> table6['A'] = ['Alice', 'Bob', 'Bob', 'Ben', 'Charlie', 'Ben','Albert']\n        >>> table6['B'] = ['Alison', 'Marley', 'Dylan', 'Affleck', 'Hepburn', 'Barnes', 'Einstein']\n        ```\n\n        ```\n        >>> table6.index('A')  # single key.\n        {('Alice',): [0],\n         ('Bob',): [1, 2],\n         ('Ben',): [3, 5],\n         ('Charlie',): [4],\n         ('Albert',): [6]})\n        ```\n\n        ```\n        >>> table6.index('A', 'B')  # multiple keys.\n        {('Alice', 'Alison'): [0],\n         ('Bob', 'Marley'): [1],\n         ('Bob', 'Dylan'): [2],\n         ('Ben', 'Affleck'): [3],\n         ('Charlie', 'Hepburn'): [4],\n         ('Ben', 'Barnes'): [5],\n         ('Albert', 'Einstein'): [6]})\n        ```\n\n    \"\"\"\n    idx = defaultdict(list)\n    iterators = [iter(self.columns[c]) for c in args]\n    for ix, key in enumerate(zip(*iterators)):\n        key = tuple(numpy_to_python(k) for k in key)\n        idx[key].append(ix)\n    return idx\n
    "},{"location":"reference/base/#tablite.base.BaseTable.unique_index","title":"tablite.base.BaseTable.unique_index(*args, tqdm=_tqdm)","text":"

    generates the index of unique rows given a list of column names

    PARAMETER DESCRIPTION *args

    columns names

    TYPE: any DEFAULT: ()

    tqdm

    Defaults to _tqdm.

    TYPE: tqdm DEFAULT: tqdm

    RETURNS DESCRIPTION

    np.array(int64): indices of unique records.

    Source code in tablite/base.py
    def unique_index(self, *args, tqdm=_tqdm):\n    \"\"\"generates the index of unique rows given a list of column names\n\n    Args:\n        *args (any): columns names\n        tqdm (tqdm, optional): Defaults to _tqdm.\n\n    Returns:\n        np.array(int64): indices of unique records.\n    \"\"\"\n    if not args:\n        raise ValueError(\"*args (column names) is required\")\n    seen = set()\n    unique = set()\n    iterators = [iter(self.columns[c]) for c in args]\n    for ix, key in tqdm(enumerate(zip(*iterators)), disable=Config.TQDM_DISABLE):\n        key_hash = hash(tuple(numpy_to_python(k) for k in key))\n        if key_hash in seen:\n            continue\n        else:\n            seen.add(key_hash)\n            unique.add(ix)\n    return np.array(sorted(unique))\n
    "},{"location":"reference/base/#tablite.base-functions","title":"Functions","text":""},{"location":"reference/base/#tablite.base.register","title":"tablite.base.register(path)","text":"

    registers path in file_registry

    The method is used by Table during init when the working directory path is set, so that python can clean all temporary files up at exit.

    PARAMETER DESCRIPTION path

    typically tmp/tablite-tmp/PID-{os.getpid()}

    TYPE: Path

    Source code in tablite/base.py
    def register(path):\n    \"\"\"registers path in file_registry\n\n    The method is used by Table during init when the working directory path\n    is set, so that python can clean all temporary files up at exit.\n\n    Args:\n        path (Path): typically tmp/tablite-tmp/PID-{os.getpid()}\n    \"\"\"\n    global file_registry\n    file_registry.add(path)\n
    "},{"location":"reference/base/#tablite.base.shutdown","title":"tablite.base.shutdown()","text":"

    method to clean up temporary files triggered at shutdown.

    Source code in tablite/base.py
    def shutdown():\n    \"\"\"method to clean up temporary files triggered at shutdown.\"\"\"\n    for path in file_registry:\n        if Config.pid in str(path):  # safety feature to prevent rm -rf /\n            log.debug(f\"shutdown: running rmtree({path})\")\n            shutil.rmtree(path)\n
    "},{"location":"reference/config/","title":"Config","text":""},{"location":"reference/config/#tablite.config","title":"tablite.config","text":""},{"location":"reference/config/#tablite.config-classes","title":"Classes","text":""},{"location":"reference/config/#tablite.config.Config","title":"tablite.config.Config","text":"

    Bases: object

    Config class for Tablite Tables.

    The default location for the storage is loaded as

    Config.workdir = pathlib.Path(os.environ.get(\"TABLITE_TMPDIR\", f\"{tempfile.gettempdir()}/tablite-tmp\"))\n

    to overwrite, first import the config class, then set the new workdir.

    >>> from tablite import config\n>>> from pathlib import Path\n>>> config.workdir = Path(\"/this/new/location\")\n

    the new path will now be used for every new table.

    PAGE_SIZE = 1_000_000 sets the page size limit.

    Multiprocessing is enabled in one of three modes: AUTO = \"auto\" FALSE = \"sp\" FORCE = \"mp\"

    MULTIPROCESSING_MODE = AUTO is default.

    SINGLE_PROCESSING_LIMIT = 1_000_000 when the number of fields (rows x columns) exceed this value, multiprocessing is used.

    "},{"location":"reference/config/#tablite.config.Config-attributes","title":"Attributes","text":""},{"location":"reference/config/#tablite.config.Config.USE_NIMPORTER","title":"tablite.config.Config.USE_NIMPORTER = os.environ.get('USE_NIMPORTER', 'true').lower() in ['1', 't', 'true', 'y', 'yes'] class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.ALLOW_CSV_READER_FALLTHROUGH","title":"tablite.config.Config.ALLOW_CSV_READER_FALLTHROUGH = os.environ.get('ALLOW_CSV_READER_FALLTHROUGH', 'true').lower() in ['1', 't', 'true', 'y', 'yes'] class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.NIM_SUPPORTED_CONV_TYPES","title":"tablite.config.Config.NIM_SUPPORTED_CONV_TYPES = ['Windows-1252', 'ISO-8859-1'] class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.workdir","title":"tablite.config.Config.workdir = pathlib.Path(os.environ.get('TABLITE_TMPDIR', f'{tempfile.gettempdir()}/tablite-tmp')) class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.pid","title":"tablite.config.Config.pid = f'pid-{os.getpid()}' class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.PAGE_SIZE","title":"tablite.config.Config.PAGE_SIZE = 1000000 class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.ENCODING","title":"tablite.config.Config.ENCODING = 'UTF-8' class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.DISK_LIMIT","title":"tablite.config.Config.DISK_LIMIT = int(10000000000.0) class-attribute instance-attribute","text":"

    10e9 (10Gb) on 100 Gb disk means raise at 90 Gb disk usage. if DISK_LIMIT <= 0, the check is turned off.

    "},{"location":"reference/config/#tablite.config.Config.SINGLE_PROCESSING_LIMIT","title":"tablite.config.Config.SINGLE_PROCESSING_LIMIT = 1000000 class-attribute instance-attribute","text":"

    when the number of fields (rows x columns) exceed this value, multiprocessing is used.

    "},{"location":"reference/config/#tablite.config.Config.vpus","title":"tablite.config.Config.vpus = max(os.cpu_count() - 1, 1) class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.AUTO","title":"tablite.config.Config.AUTO = 'auto' class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.FALSE","title":"tablite.config.Config.FALSE = 'sp' class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.FORCE","title":"tablite.config.Config.FORCE = 'mp' class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.MULTIPROCESSING_MODE","title":"tablite.config.Config.MULTIPROCESSING_MODE = AUTO class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.TQDM_DISABLE","title":"tablite.config.Config.TQDM_DISABLE = False class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config-functions","title":"Functions","text":""},{"location":"reference/config/#tablite.config.Config.reset","title":"tablite.config.Config.reset() classmethod","text":"

    Resets the config class to original values.

    Source code in tablite/config.py
    @classmethod\ndef reset(cls):\n    \"\"\"Resets the config class to original values.\"\"\"\n    for k, v in _default_values.items():\n        setattr(Config, k, v)\n
    "},{"location":"reference/config/#tablite.config.Config.page_steps","title":"tablite.config.Config.page_steps(length) classmethod","text":"

    an iterator that yield start and end in page sizes

    YIELDS DESCRIPTION tuple

    start:int, end:int

    Source code in tablite/config.py
    @classmethod\ndef page_steps(cls, length):\n    \"\"\"an iterator that yield start and end in page sizes\n\n    Yields:\n        tuple: start:int, end:int\n    \"\"\"\n    start, end = 0, 0\n    for _ in range(0, length + 1, cls.PAGE_SIZE):\n        start, end = end, min(end + cls.PAGE_SIZE, length)\n        yield start, end\n        if end == length:\n            return\n
    "},{"location":"reference/core/","title":"Core","text":""},{"location":"reference/core/#tablite.core","title":"tablite.core","text":""},{"location":"reference/core/#tablite.core-attributes","title":"Attributes","text":""},{"location":"reference/core/#tablite.core.log","title":"tablite.core.log = logging.getLogger(__name__) module-attribute","text":""},{"location":"reference/core/#tablite.core-classes","title":"Classes","text":""},{"location":"reference/core/#tablite.core.Table","title":"tablite.core.Table(columns=None, headers=None, rows=None, _path=None)","text":"

    Bases: BaseTable

    creates Table

    PARAMETER DESCRIPTION EITHER

    columns (dict, optional): dict with column names as keys, values as lists. Example: t = Table(columns={\"a\": [1, 2], \"b\": [3, 4]})

    Source code in tablite/core.py
    def __init__(self, columns=None, headers=None, rows=None, _path=None) -> None:\n    \"\"\"creates Table\n\n    Args:\n        EITHER:\n            columns (dict, optional): dict with column names as keys, values as lists.\n            Example: t = Table(columns={\"a\": [1, 2], \"b\": [3, 4]})\n        OR\n            headers (list of strings, optional): list of column names.\n            rows (list of tuples or lists, optional): values for columns\n            Example: t = Table(headers=[\"a\", \"b\"], rows=[[1,3], [2,4]])\n    \"\"\"\n    super().__init__(columns, headers, rows, _path)\n
    "},{"location":"reference/core/#tablite.core.Table-attributes","title":"Attributes","text":""},{"location":"reference/core/#tablite.core.Table.path","title":"tablite.core.Table.path = _path instance-attribute","text":""},{"location":"reference/core/#tablite.core.Table.columns","title":"tablite.core.Table.columns = {} instance-attribute","text":""},{"location":"reference/core/#tablite.core.Table.rows","title":"tablite.core.Table.rows property","text":"

    enables row based iteration in python types.

    Example:

    for row in Table.rows:\n    print(row)\n

    Yields: tuple: values is same order as columns.

    "},{"location":"reference/core/#tablite.core.Table-functions","title":"Functions","text":""},{"location":"reference/core/#tablite.core.Table.__str__","title":"tablite.core.Table.__str__()","text":"Source code in tablite/base.py
    def __str__(self):  # USER FUNCTION.\n    return f\"{self.__class__.__name__}({len(self.columns):,} columns, {len(self):,} rows)\"\n
    "},{"location":"reference/core/#tablite.core.Table.__repr__","title":"tablite.core.Table.__repr__()","text":"Source code in tablite/base.py
    def __repr__(self):\n    return self.__str__()\n
    "},{"location":"reference/core/#tablite.core.Table.nbytes","title":"tablite.core.Table.nbytes()","text":"

    finds the total bytes of the table on disk

    RETURNS DESCRIPTION tuple

    int: real bytes used on disk int: total bytes used if flattened

    Source code in tablite/base.py
    def nbytes(self):  # USER FUNCTION.\n    \"\"\"finds the total bytes of the table on disk\n\n    Returns:\n        tuple:\n            int: real bytes used on disk\n            int: total bytes used if flattened\n    \"\"\"\n    real = {}\n    total = 0\n    for column in self.columns.values():\n        for page in set(column.pages):\n            real[page] = page.path.stat().st_size\n        for page in column.pages:\n            total += real[page]\n    return sum(real.values()), total\n
    "},{"location":"reference/core/#tablite.core.Table.items","title":"tablite.core.Table.items()","text":"

    returns table as dict

    RETURNS DESCRIPTION dict

    Table as dict {column_name: [values], ...}

    Source code in tablite/base.py
    def items(self):  # USER FUNCTION.\n    \"\"\"returns table as dict\n\n    Returns:\n        dict: Table as dict `{column_name: [values], ...}`\n    \"\"\"\n    return {\n        name: column[:].tolist() for name, column in self.columns.items()\n    }.items()\n
    "},{"location":"reference/core/#tablite.core.Table.__delitem__","title":"tablite.core.Table.__delitem__(key)","text":"

    Examples:

    >>> del table['a']  # removes column 'a'\n>>> del table[-3:]  # removes last 3 rows from all columns.\n
    Source code in tablite/base.py
    def __delitem__(self, key):  # USER FUNCTION.\n    \"\"\"\n    Examples:\n    ```\n    >>> del table['a']  # removes column 'a'\n    >>> del table[-3:]  # removes last 3 rows from all columns.\n    ```\n    \"\"\"\n    if isinstance(key, (int, slice)):\n        for column in self.columns.values():\n            del column[key]\n    elif key in self.columns:\n        del self.columns[key]\n    else:\n        raise KeyError(f\"Key not found: {key}\")\n
    "},{"location":"reference/core/#tablite.core.Table.__setitem__","title":"tablite.core.Table.__setitem__(key, value)","text":"

    table behaves like a dict. Args: key (str or hashable): column name value (iterable): list, tuple or nd.array with values.

    As Table now accepts the keyword columns as a dict:

    >>> t = Table(columns={'b':[4,5,6], 'c':[7,8,9]})\n

    and the header/data combinations:

    >>> t = Table(header=['b','c'], data=[[4,5,6],[7,8,9]])\n

    This has the side-benefit that tuples now can be used as headers.

    Source code in tablite/base.py
    def __setitem__(self, key, value):  # USER FUNCTION\n    \"\"\"table behaves like a dict.\n    Args:\n        key (str or hashable): column name\n        value (iterable): list, tuple or nd.array with values.\n\n    As Table now accepts the keyword `columns` as a dict:\n    ```\n    >>> t = Table(columns={'b':[4,5,6], 'c':[7,8,9]})\n    ```\n    and the header/data combinations:\n    ```\n    >>> t = Table(header=['b','c'], data=[[4,5,6],[7,8,9]])\n    ```\n    This has the side-benefit that tuples now can be used as headers.\n    \"\"\"\n    if value is None:\n        self.columns[key] = Column(self.path, value=None)\n    elif isinstance(value, (list, tuple)):\n        value = list_to_np_array(value)\n        self.columns[key] = Column(self.path, value)\n    elif isinstance(value, (np.ndarray)):\n        self.columns[key] = Column(self.path, value)\n    elif isinstance(value, Column):\n        self.columns[key] = value\n    else:\n        raise TypeError(f\"{type(value)} not supported.\")\n
    "},{"location":"reference/core/#tablite.core.Table.__getitem__","title":"tablite.core.Table.__getitem__(keys)","text":"

    Enables selection of columns and rows

    PARAMETER DESCRIPTION keys

    TYPE: column name, integer or slice

    Examples

    >>>

    10] selects first 10 rows from all columns

    TYPE: table[

    >>>

    20:3] selects column 'b' and 'c' and 'a' twice for a slice.

    TYPE: table['b', 'a', 'a', 'c', 2

    Raises: KeyError: if key is not found. TypeError: if key is not a string, integer or slice.

    RETURNS DESCRIPTION Table

    returns columns in same order as selection.

    Source code in tablite/base.py
    def __getitem__(self, keys):  # USER FUNCTION\n    \"\"\"\n    Enables selection of columns and rows\n\n    Args:\n        keys (column name, integer or slice):\n        Examples:\n        ```\n        >>> table['a']                        selects column 'a'\n        >>> table[3]                          selects row 3 as a tuple.\n        >>> table[:10]                        selects first 10 rows from all columns\n        >>> table['a','b', slice(3,20,2)]     selects a slice from columns 'a' and 'b'\n        >>> table['b', 'a', 'a', 'c', 2:20:3] selects column 'b' and 'c' and 'a' twice for a slice.\n        >>> table[('b', 'a', 'a', 'c')]       selects columns 'b', 'a', 'a', and 'c' using a tuple.\n        ```\n    Raises:\n        KeyError: if key is not found.\n        TypeError: if key is not a string, integer or slice.\n\n    Returns:\n        Table: returns columns in same order as selection.\n    \"\"\"\n\n    if not isinstance(keys, tuple):\n        if isinstance(keys, list):\n            keys = tuple(keys)\n        else:\n            keys = (keys,)\n    if isinstance(keys[0], tuple):\n        keys = tuple(list(chain(*keys)))\n\n    integers = [i for i in keys if isinstance(i, int)]\n    if len(integers) == len(keys) == 1:  # return a single tuple.\n        keys = [slice(keys[0])]\n\n    column_names = [i for i in keys if isinstance(i, str)]\n    column_names = list(self.columns) if not column_names else column_names\n    not_found = [name for name in column_names if name not in self.columns]\n    if not_found:\n        raise KeyError(f\"keys not found: {', '.join(not_found)}\")\n\n    slices = [i for i in keys if isinstance(i, slice)]\n    slc = slice(0, len(self)) if not slices else slices[0]\n\n    if (\n        len(slices) == 0 and len(column_names) == 1\n    ):  # e.g. tbl['a'] or tbl['a'][:10]\n        col = self.columns[column_names[0]]\n        if slices:\n            return col[slc]  # return slice from column as list of values\n        else:\n            return col  # return whole column\n\n    elif len(integers) == 1:  # return a single tuple.\n        row_no = integers[0]\n        slc = slice(row_no, row_no + 1)\n        return tuple(self.columns[name][slc].tolist()[0] for name in column_names)\n\n    elif not slices:  # e.g. new table with N whole columns.\n        return self.__class__(\n            columns={name: self.columns[name] for name in column_names}\n        )\n\n    else:  # e.g. new table from selection of columns and slices.\n        t = self.__class__()\n        for name in column_names:\n            column = self.columns[name]\n\n            new_column = Column(t.path)  # create new Column.\n            for item in column.getpages(slc):\n                if isinstance(item, np.ndarray):\n                    new_column.extend(item)  # extend subslice (expensive)\n                elif isinstance(item, SimplePage):\n                    new_column.pages.append(item)  # extend page (cheap)\n                else:\n                    raise TypeError(f\"Bad item: {item}\")\n\n            # below:\n            # set the new column directly on t.columns.\n            # Do not use t[name] as that triggers __setitem__ again.\n            t.columns[name] = new_column\n\n        return t\n
    "},{"location":"reference/core/#tablite.core.Table.__len__","title":"tablite.core.Table.__len__()","text":"Source code in tablite/base.py
    def __len__(self):  # USER FUNCTION.\n    if not self.columns:\n        return 0\n    return max(len(c) for c in self.columns.values())\n
    "},{"location":"reference/core/#tablite.core.Table.__eq__","title":"tablite.core.Table.__eq__(other) -> bool","text":"

    Determines if two tables have identical content.

    PARAMETER DESCRIPTION other

    table for comparison

    TYPE: Table

    RETURNS DESCRIPTION bool

    True if tables are identical.

    TYPE: bool

    Source code in tablite/base.py
    def __eq__(self, other) -> bool:  # USER FUNCTION.\n    \"\"\"Determines if two tables have identical content.\n\n    Args:\n        other (Table): table for comparison\n\n    Returns:\n        bool: True if tables are identical.\n    \"\"\"\n    if isinstance(other, dict):\n        return self.items() == other.items()\n    if not isinstance(other, BaseTable):\n        return False\n    if id(self) == id(other):\n        return True\n    if len(self) != len(other):\n        return False\n    if len(self) == len(other) == 0:\n        return True\n    if self.columns.keys() != other.columns.keys():\n        return False\n    for name, col in self.columns.items():\n        if not (col == other.columns[name]):\n            return False\n    return True\n
    "},{"location":"reference/core/#tablite.core.Table.clear","title":"tablite.core.Table.clear()","text":"

    clears the table. Like dict().clear()

    Source code in tablite/base.py
    def clear(self):  # USER FUNCTION.\n    \"\"\"clears the table. Like dict().clear()\"\"\"\n    self.columns.clear()\n
    "},{"location":"reference/core/#tablite.core.Table.save","title":"tablite.core.Table.save(path, compression_method=zipfile.ZIP_DEFLATED, compression_level=1)","text":"

    saves table to compressed tpz file.

    PARAMETER DESCRIPTION path

    file destination.

    TYPE: Path

    compression_method

    See zipfile compression methods. Defaults to ZIP_DEFLATED.

    DEFAULT: ZIP_DEFLATED

    compression_level

    See zipfile compression levels. Defaults to 1.

    DEFAULT: 1

    The file format is as follows: .tpz is a gzip archive with table metadata captured as table.yml and the necessary set of pages saved as .npy files.

    The zip contains table.yml which provides an overview of the data:

    --------------------------------------\n%YAML 1.2                              yaml version\ncolumns:                               start of columns section.\n    name: \u201c\u5217 1\u201d                       name of column 1.\n        pages: [p1b1, p1b2]            list of pages in column 1.\n    name: \u201c\u5217 2\u201d                       name of column 2\n        pages: [p2b1, p2b2]            list of pages in column 2.\n----------------------------------------\n
    Source code in tablite/base.py
    def save(\n    self, path, compression_method=zipfile.ZIP_DEFLATED, compression_level=1\n):  # USER FUNCTION.\n    \"\"\"saves table to compressed tpz file.\n\n    Args:\n        path (Path): file destination.\n        compression_method: See zipfile compression methods. Defaults to ZIP_DEFLATED.\n        compression_level: See zipfile compression levels. Defaults to 1.\n        The default settings produce 80% compression at 10% slowdown.\n\n    The file format is as follows:\n    .tpz is a gzip archive with table metadata captured as table.yml\n    and the necessary set of pages saved as .npy files.\n\n    The zip contains table.yml which provides an overview of the data:\n    ```\n    --------------------------------------\n    %YAML 1.2                              yaml version\n    columns:                               start of columns section.\n        name: \u201c\u5217 1\u201d                       name of column 1.\n            pages: [p1b1, p1b2]            list of pages in column 1.\n        name: \u201c\u5217 2\u201d                       name of column 2\n            pages: [p2b1, p2b2]            list of pages in column 2.\n    ----------------------------------------\n    ```\n    \"\"\"\n    if isinstance(path, str):\n        path = Path(path)\n    type_check(path, Path)\n    if path.is_dir():\n        raise TypeError(f\"filename needed: {path}\")\n    if path.suffix != \".tpz\":\n        path = path.parent / (path.parts[-1] + \".tpz\")\n\n    # create yaml document\n    _page_counter = 0\n    d = {}\n    cols = {}\n    for name, col in self.columns.items():\n        type_check(col, Column)\n        cols[name] = {\"pages\": [p.path.name for p in col.pages]}\n        _page_counter += len(col.pages)\n    d[\"columns\"] = cols\n    yml = yaml.safe_dump(\n        d, sort_keys=False, allow_unicode=True, default_flow_style=None\n    )\n\n    _file_counter = 0\n    with zipfile.ZipFile(\n        path, \"w\", compression=compression_method, compresslevel=compression_level\n    ) as f:\n        log.debug(f\"writing .tpz to {path} with\\n{yml}\")\n        f.writestr(\"table.yml\", yml)\n        for name, col in self.columns.items():\n            for page in set(\n                col.pages\n            ):  # set of pages! remember t *= 1000 repeats t 1000x\n                with open(page.path, \"rb\", buffering=0) as raw_io:\n                    f.writestr(page.path.name, raw_io.read())\n                _file_counter += 1\n                log.debug(f\"adding Page {page.path}\")\n\n        _fields = len(self) * len(self.columns)\n        _avg = _fields // _page_counter\n        log.debug(\n            f\"Wrote {_fields:,} on {_page_counter:,} pages in {_file_counter} files: {_avg} fields/page\"\n        )\n
    "},{"location":"reference/core/#tablite.core.Table.load","title":"tablite.core.Table.load(path, tqdm=_tqdm) classmethod","text":"

    loads a table from .tpz file. See also Table.save for details on the file format.

    PARAMETER DESCRIPTION path

    source file

    TYPE: Path

    RETURNS DESCRIPTION Table

    table in read-only mode.

    Source code in tablite/base.py
    @classmethod\ndef load(cls, path, tqdm=_tqdm):  # USER FUNCTION.\n    \"\"\"loads a table from .tpz file.\n    See also Table.save for details on the file format.\n\n    Args:\n        path (Path): source file\n\n    Returns:\n        Table: table in read-only mode.\n    \"\"\"\n    path = Path(path)\n    log.debug(f\"loading {path}\")\n    with zipfile.ZipFile(path, \"r\") as f:\n        yml = f.read(\"table.yml\")\n        metadata = yaml.safe_load(yml)\n        t = cls()\n\n        page_count = sum([len(c[\"pages\"]) for c in metadata[\"columns\"].values()])\n\n        with tqdm(\n            total=page_count,\n            desc=f\"loading '{path.name}' file\",\n            disable=Config.TQDM_DISABLE,\n        ) as pbar:\n            for name, d in metadata[\"columns\"].items():\n                column = Column(t.path)\n                for page in d[\"pages\"]:\n                    bytestream = io.BytesIO(f.read(page))\n                    data = np.load(bytestream, allow_pickle=True, fix_imports=False)\n                    column.extend(data)\n                    pbar.update(1)\n                t.columns[name] = column\n    update_access_time(path)\n    return t\n
    "},{"location":"reference/core/#tablite.core.Table.copy","title":"tablite.core.Table.copy()","text":"Source code in tablite/base.py
    def copy(self):\n    cls = type(self)\n    t = cls()\n    for name, column in self.columns.items():\n        new = Column(t.path)\n        new.pages = column.pages[:]\n        t.columns[name] = new\n    return t\n
    "},{"location":"reference/core/#tablite.core.Table.__imul__","title":"tablite.core.Table.__imul__(other)","text":"

    Repeats instance of table N times.

    Like list: t = t * N

    PARAMETER DESCRIPTION other

    multiplier

    TYPE: int

    Source code in tablite/base.py
    def __imul__(self, other):\n    \"\"\"Repeats instance of table N times.\n\n    Like list: `t = t * N`\n\n    Args:\n        other (int): multiplier\n    \"\"\"\n    if not (isinstance(other, int) and other > 0):\n        raise TypeError(\n            f\"a table can be repeated an integer number of times, not {type(other)} number of times\"\n        )\n    for col in self.columns.values():\n        col *= other\n    return self\n
    "},{"location":"reference/core/#tablite.core.Table.__mul__","title":"tablite.core.Table.__mul__(other)","text":"

    Repeat table N times. Like list: new = old * N

    PARAMETER DESCRIPTION other

    multiplier

    TYPE: int

    RETURNS DESCRIPTION

    Table

    Source code in tablite/base.py
    def __mul__(self, other):\n    \"\"\"Repeat table N times.\n    Like list: `new = old * N`\n\n    Args:\n        other (int): multiplier\n\n    Returns:\n        Table\n    \"\"\"\n    new = self.copy()\n    return new.__imul__(other)\n
    "},{"location":"reference/core/#tablite.core.Table.__iadd__","title":"tablite.core.Table.__iadd__(other)","text":"

    Concatenates tables with same column names.

    Like list: table_1 += table_2

    RAISES DESCRIPTION ValueError

    If column names don't match.

    RETURNS DESCRIPTION None

    self is updated.

    Source code in tablite/base.py
    def __iadd__(self, other):\n    \"\"\"Concatenates tables with same column names.\n\n    Like list: `table_1 += table_2`\n\n    Args:\n        other (Table)\n\n    Raises:\n        ValueError: If column names don't match.\n\n    Returns:\n        None: self is updated.\n    \"\"\"\n    type_check(other, BaseTable)\n    for name in self.columns.keys():\n        if name not in other.columns:\n            raise ValueError(f\"{name} not in other\")\n    for name in other.columns.keys():\n        if name not in self.columns:\n            raise ValueError(f\"{name} missing from self\")\n\n    for name, column in self.columns.items():\n        other_col = other.columns.get(name, None)\n        column.pages.extend(other_col.pages[:])\n    return self\n
    "},{"location":"reference/core/#tablite.core.Table.__add__","title":"tablite.core.Table.__add__(other)","text":"

    Concatenates tables with same column names.

    Like list: table_3 = table_1 + table_2

    RAISES DESCRIPTION ValueError

    If column names don't match.

    RETURNS DESCRIPTION

    Table

    Source code in tablite/base.py
    def __add__(self, other):\n    \"\"\"Concatenates tables with same column names.\n\n    Like list: `table_3 = table_1 + table_2`\n\n    Args:\n        other (Table)\n\n    Raises:\n        ValueError: If column names don't match.\n\n    Returns:\n        Table\n    \"\"\"\n    type_check(other, BaseTable)\n    cp = self.copy()\n    cp += other\n    return cp\n
    "},{"location":"reference/core/#tablite.core.Table.add_rows","title":"tablite.core.Table.add_rows(*args, **kwargs)","text":"

    its more efficient to add many rows at once.

    if both args and kwargs, then args are added first, followed by kwargs.

    supported cases:

    >>> t = Table()\n>>> t.add_columns('row','A','B','C')\n>>> t.add_rows(1, 1, 2, 3)                              # (1) individual values as args\n>>> t.add_rows([2, 1, 2, 3])                            # (2) list of values as args\n>>> t.add_rows((3, 1, 2, 3))                            # (3) tuple of values as args\n>>> t.add_rows(*(4, 1, 2, 3))                           # (4) unpacked tuple becomes arg like (1)\n>>> t.add_rows(row=5, A=1, B=2, C=3)                    # (5) kwargs\n>>> t.add_rows(**{'row': 6, 'A': 1, 'B': 2, 'C': 3})    # (6) dict / json interpreted a kwargs\n>>> t.add_rows((7, 1, 2, 3), (8, 4, 5, 6))              # (7) two (or more) tuples as args\n>>> t.add_rows([9, 1, 2, 3], [10, 4, 5, 6])             # (8) two or more lists as rgs\n>>> t.add_rows(\n    {'row': 11, 'A': 1, 'B': 2, 'C': 3},\n    {'row': 12, 'A': 4, 'B': 5, 'C': 6}\n    )                                                   # (9) two (or more) dicts as args - roughly comma sep'd json.\n>>> t.add_rows( *[\n    {'row': 13, 'A': 1, 'B': 2, 'C': 3},\n    {'row': 14, 'A': 1, 'B': 2, 'C': 3}\n    ])                                                  # (10) list of dicts as args\n>>> t.add_rows(row=[15,16], A=[1,1], B=[2,2], C=[3,3])  # (11) kwargs with lists as values\n
    Source code in tablite/base.py
    def add_rows(self, *args, **kwargs):\n    \"\"\"its more efficient to add many rows at once.\n\n    if both args and kwargs, then args are added first, followed by kwargs.\n\n    supported cases:\n    ```\n    >>> t = Table()\n    >>> t.add_columns('row','A','B','C')\n    >>> t.add_rows(1, 1, 2, 3)                              # (1) individual values as args\n    >>> t.add_rows([2, 1, 2, 3])                            # (2) list of values as args\n    >>> t.add_rows((3, 1, 2, 3))                            # (3) tuple of values as args\n    >>> t.add_rows(*(4, 1, 2, 3))                           # (4) unpacked tuple becomes arg like (1)\n    >>> t.add_rows(row=5, A=1, B=2, C=3)                    # (5) kwargs\n    >>> t.add_rows(**{'row': 6, 'A': 1, 'B': 2, 'C': 3})    # (6) dict / json interpreted a kwargs\n    >>> t.add_rows((7, 1, 2, 3), (8, 4, 5, 6))              # (7) two (or more) tuples as args\n    >>> t.add_rows([9, 1, 2, 3], [10, 4, 5, 6])             # (8) two or more lists as rgs\n    >>> t.add_rows(\n        {'row': 11, 'A': 1, 'B': 2, 'C': 3},\n        {'row': 12, 'A': 4, 'B': 5, 'C': 6}\n        )                                                   # (9) two (or more) dicts as args - roughly comma sep'd json.\n    >>> t.add_rows( *[\n        {'row': 13, 'A': 1, 'B': 2, 'C': 3},\n        {'row': 14, 'A': 1, 'B': 2, 'C': 3}\n        ])                                                  # (10) list of dicts as args\n    >>> t.add_rows(row=[15,16], A=[1,1], B=[2,2], C=[3,3])  # (11) kwargs with lists as values\n    ```\n\n    \"\"\"\n    if not BaseTable._add_row_slow_warning:\n        warnings.warn(\n            \"add_rows is slow. Consider using add_columns and then assigning values to the columns directly.\"\n        )\n        BaseTable._add_row_slow_warning = True\n\n    if args:\n        if not all(isinstance(i, (list, tuple, dict)) for i in args):  # 1,4\n            args = [args]\n\n        if all(isinstance(i, (list, tuple, dict)) for i in args):  # 2,3,7,8\n            # 1. turn the data into columns:\n\n            d = {n: [] for n in self.columns}\n            for arg in args:\n                if len(arg) != len(self.columns):\n                    raise ValueError(\n                        f\"len({arg})== {len(arg)}, but there are {len(self.columns)} columns\"\n                    )\n\n                if isinstance(arg, dict):\n                    for k, v in arg.items():  # 7,8\n                        d[k].append(v)\n\n                elif isinstance(arg, (list, tuple)):  # 2,3\n                    for n, v in zip(self.columns, arg):\n                        d[n].append(v)\n\n                else:\n                    raise TypeError(f\"{arg}?\")\n            # 2. extend the columns\n            for n, values in d.items():\n                col = self.columns[n]\n                col.extend(list_to_np_array(values))\n\n    if kwargs:\n        if isinstance(kwargs, dict):\n            if all(isinstance(v, (list, tuple)) for v in kwargs.values()):\n                for k, v in kwargs.items():\n                    col = self.columns[k]\n                    col.extend(list_to_np_array(v))\n            else:\n                for k, v in kwargs.items():\n                    col = self.columns[k]\n                    col.extend(np.array([v]))\n        else:\n            raise ValueError(f\"format not recognised: {kwargs}\")\n\n    return\n
    "},{"location":"reference/core/#tablite.core.Table.add_columns","title":"tablite.core.Table.add_columns(*names)","text":"

    Adds column names to table.

    Source code in tablite/base.py
    def add_columns(self, *names):\n    \"\"\"Adds column names to table.\"\"\"\n    for name in names:\n        self.columns[name] = Column(self.path)\n
    "},{"location":"reference/core/#tablite.core.Table.add_column","title":"tablite.core.Table.add_column(name, data=None)","text":"

    verbose alias for table[name] = data, that checks if name already exists

    PARAMETER DESCRIPTION name

    column name

    TYPE: str

    data

    values. Defaults to None.

    TYPE: list,tuple) DEFAULT: None

    RAISES DESCRIPTION TypeError

    name isn't string

    ValueError

    name already exists

    Source code in tablite/base.py
    def add_column(self, name, data=None):\n    \"\"\"verbose alias for table[name] = data, that checks if name already exists\n\n    Args:\n        name (str): column name\n        data ((list,tuple), optional): values. Defaults to None.\n\n    Raises:\n        TypeError: name isn't string\n        ValueError: name already exists\n    \"\"\"\n    if not isinstance(name, str):\n        raise TypeError(\"expected name as string\")\n    if name in self.columns:\n        raise ValueError(f\"{name} already in {self.columns}\")\n    self.__setitem__(name, data)\n
    "},{"location":"reference/core/#tablite.core.Table.stack","title":"tablite.core.Table.stack(other)","text":"

    returns the joint stack of tables with overlapping column names. Example:

    | Table A|  +  | Table B| = |  Table AB |\n| A| B| C|     | A| B| D|   | A| B| C| -|\n                            | A| B| -| D|\n
    Source code in tablite/base.py
    def stack(self, other):\n    \"\"\"\n    returns the joint stack of tables with overlapping column names.\n    Example:\n    ```\n    | Table A|  +  | Table B| = |  Table AB |\n    | A| B| C|     | A| B| D|   | A| B| C| -|\n                                | A| B| -| D|\n    ```\n    \"\"\"\n    if not isinstance(other, BaseTable):\n        raise TypeError(f\"stack only works for Table, not {type(other)}\")\n\n    cp = self.copy()\n    for name, col2 in other.columns.items():\n        if name not in cp.columns:\n            cp[name] = [None] * len(self)\n        cp[name].pages.extend(col2.pages[:])\n\n    for name in self.columns:\n        if name not in other.columns:\n            if len(cp) > 0:\n                cp[name].extend(np.array([None] * len(other)))\n    return cp\n
    "},{"location":"reference/core/#tablite.core.Table.types","title":"tablite.core.Table.types()","text":"

    returns nested dict of data types in the form: {column name: {python type class: number of instances }, ... }

    example:

    >>> t.types()\n{\n    'A': {<class 'str'>: 7},\n    'B': {<class 'int'>: 7}\n}\n
    Source code in tablite/base.py
    def types(self):\n    \"\"\"\n    returns nested dict of data types in the form:\n    `{column name: {python type class: number of instances }, ... }`\n\n    example:\n    ```\n    >>> t.types()\n    {\n        'A': {<class 'str'>: 7},\n        'B': {<class 'int'>: 7}\n    }\n    ```\n    \"\"\"\n    d = {}\n    for name, col in self.columns.items():\n        assert isinstance(col, Column)\n        d[name] = col.types()\n    return d\n
    "},{"location":"reference/core/#tablite.core.Table.display_dict","title":"tablite.core.Table.display_dict(slice_=None, blanks=None, dtype=False)","text":"

    helper for creating dict for display.

    PARAMETER DESCRIPTION slice_

    python slice. Defaults to None.

    TYPE: slice DEFAULT: None

    blanks

    fill value for None. Defaults to None.

    TYPE: optional DEFAULT: None

    dtype

    Adds datatype to each column. Defaults to False.

    TYPE: bool DEFAULT: False

    RAISES DESCRIPTION TypeError

    slice_ must be None or slice.

    RETURNS DESCRIPTION dict

    from Table.

    Source code in tablite/base.py
    def display_dict(self, slice_=None, blanks=None, dtype=False):\n    \"\"\"helper for creating dict for display.\n\n    Args:\n        slice_ (slice, optional): python slice. Defaults to None.\n        blanks (optional): fill value for `None`. Defaults to None.\n        dtype (bool, optional): Adds datatype to each column. Defaults to False.\n\n    Raises:\n        TypeError: slice_ must be None or slice.\n\n    Returns:\n        dict: from Table.\n    \"\"\"\n    if not self.columns:\n        print(\"Empty Table\")\n        return\n\n    def datatype(col):  # PRIVATE\n        \"\"\"creates label for column datatype.\"\"\"\n        types = col.types()\n        if len(types) == 0:\n            typ = \"empty\"\n        elif len(types) == 1:\n            dt, _ = types.popitem()\n            typ = dt.__name__\n        else:\n            typ = \"mixed\"\n        return typ\n\n    row_count_tags = [\"#\", \"~\", \"*\"]\n    cols = set(self.columns)\n    for n, tag in product(range(1, 6), row_count_tags):\n        if n * tag not in cols:\n            tag = n * tag\n            break\n\n    if not isinstance(slice_, (slice, type(None))):\n        raise TypeError(f\"slice_ must be None or slice, not {type(slice_)}\")\n    if isinstance(slice_, slice):\n        slc = slice_\n    if slice_ is None:\n        if len(self) <= 20:\n            slc = slice(0, 20, 1)\n        else:\n            slc = None\n\n    n = len(self)\n    if slc:  # either we want slc or we want everything.\n        row_no = list(range(*slc.indices(len(self))))\n        data = {tag: [f\"{i:,}\".rjust(2) for i in row_no]}\n        for name, col in self.columns.items():\n            data[name] = list(chain(iter(col), repeat(blanks, times=n - len(col))))[\n                slc\n            ]\n    else:\n        data = {}\n        j = int(math.ceil(math.log10(n)) / 3) + len(str(n))\n        row_no = (\n            [f\"{i:,}\".rjust(j) for i in range(7)]\n            + [\"...\"]\n            + [f\"{i:,}\".rjust(j) for i in range(n - 7, n)]\n        )\n        data = {tag: row_no}\n\n        for name, col in self.columns.items():\n            if len(col) == n:\n                row = col[:7].tolist() + [\"...\"] + col[-7:].tolist()\n            else:\n                empty = [blanks] * 7\n                head = (col[:7].tolist() + empty)[:7]\n                tail = (col[n - 7 :].tolist() + empty)[-7:]\n                row = head + [\"...\"] + tail\n            data[name] = row\n\n    if dtype:\n        for name, values in data.items():\n            if name in self.columns:\n                col = self.columns[name]\n                values.insert(0, datatype(col))\n            else:\n                values.insert(0, \"row\")\n\n    return data\n
    "},{"location":"reference/core/#tablite.core.Table.to_ascii","title":"tablite.core.Table.to_ascii(slice_=None, blanks=None, dtype=False)","text":"

    returns ascii view of table as string.

    PARAMETER DESCRIPTION slice_

    slice to determine table snippet.

    TYPE: slice DEFAULT: None

    blanks

    value for whitespace. Defaults to None.

    TYPE: str DEFAULT: None

    dtype

    adds subheader with datatype for column. Defaults to False.

    TYPE: bool DEFAULT: False

    Source code in tablite/base.py
    def to_ascii(self, slice_=None, blanks=None, dtype=False):\n    \"\"\"returns ascii view of table as string.\n\n    Args:\n        slice_ (slice, optional): slice to determine table snippet.\n        blanks (str, optional): value for whitespace. Defaults to None.\n        dtype (bool, optional): adds subheader with datatype for column. Defaults to False.\n    \"\"\"\n\n    def adjust(v, length):  # PRIVATE FUNCTION\n        \"\"\"whitespace justifies field values based on datatype\"\"\"\n        if v is None:\n            return str(blanks).ljust(length)\n        elif isinstance(v, str):\n            return v.ljust(length)\n        else:\n            return str(v).rjust(length)\n\n    if not self.columns:\n        return str(self)\n\n    d = {}\n    for name, values in self.display_dict(\n        slice_=slice_, blanks=blanks, dtype=dtype\n    ).items():\n        as_text = [str(v) for v in values] + [str(name)]\n        width = max(len(i) for i in as_text)\n        new_name = name.center(width, \" \")\n        if dtype:\n            values[0] = values[0].center(width, \" \")\n        d[new_name] = [adjust(v, width) for v in values]\n\n    rows = dict_to_rows(d)\n    s = []\n    s.append(\"+\" + \"+\".join([\"=\" * len(n) for n in rows[0]]) + \"+\")\n    s.append(\"|\" + \"|\".join(rows[0]) + \"|\")  # column names\n    start = 1\n    if dtype:\n        s.append(\"|\" + \"|\".join(rows[1]) + \"|\")  # datatypes\n        start = 2\n\n    s.append(\"+\" + \"+\".join([\"-\" * len(n) for n in rows[0]]) + \"+\")\n    for row in rows[start:]:\n        s.append(\"|\" + \"|\".join(row) + \"|\")\n    s.append(\"+\" + \"+\".join([\"=\" * len(n) for n in rows[0]]) + \"+\")\n\n    if len(set(len(c) for c in self.columns.values())) != 1:\n        warning = f\"Warning: Columns have different lengths. {blanks} is used as fill value.\"\n        s.append(warning)\n\n    return \"\\n\".join(s)\n
    "},{"location":"reference/core/#tablite.core.Table.show","title":"tablite.core.Table.show(slice_=None, blanks=None, dtype=False)","text":"

    prints ascii view of table.

    PARAMETER DESCRIPTION slice_

    slice to determine table snippet.

    TYPE: slice DEFAULT: None

    blanks

    value for whitespace. Defaults to None.

    TYPE: str DEFAULT: None

    dtype

    adds subheader with datatype for column. Defaults to False.

    TYPE: bool DEFAULT: False

    Source code in tablite/base.py
    def show(self, slice_=None, blanks=None, dtype=False):\n    \"\"\"prints ascii view of table.\n\n    Args:\n        slice_ (slice, optional): slice to determine table snippet.\n        blanks (str, optional): value for whitespace. Defaults to None.\n        dtype (bool, optional): adds subheader with datatype for column. Defaults to False.\n    \"\"\"\n    print(self.to_ascii(slice_=slice_, blanks=blanks, dtype=dtype))\n
    "},{"location":"reference/core/#tablite.core.Table.to_dict","title":"tablite.core.Table.to_dict(columns=None, slice_=None)","text":"

    columns: list of column names. Default is None == all columns. slice_: slice. Default is None == all rows.

    returns: dict with columns as keys and lists of values.

    Example:

    >>> t.show()\n+===+===+===+\n| # | a | b |\n|row|int|int|\n+---+---+---+\n| 0 |  1|  3|\n| 1 |  2|  4|\n+===+===+===+\n>>> t.to_dict()\n{'a':[1,2], 'b':[3,4]}\n
    Source code in tablite/base.py
    def to_dict(self, columns=None, slice_=None):\n    \"\"\"\n    columns: list of column names. Default is None == all columns.\n    slice_: slice. Default is None == all rows.\n\n    returns: dict with columns as keys and lists of values.\n\n    Example:\n    ```\n    >>> t.show()\n    +===+===+===+\n    | # | a | b |\n    |row|int|int|\n    +---+---+---+\n    | 0 |  1|  3|\n    | 1 |  2|  4|\n    +===+===+===+\n    >>> t.to_dict()\n    {'a':[1,2], 'b':[3,4]}\n    ```\n\n    \"\"\"\n    if slice_ is None:\n        slice_ = slice(0, len(self))\n    assert isinstance(slice_, slice)\n\n    if columns is None:\n        columns = list(self.columns.keys())\n    if not isinstance(columns, list):\n        raise TypeError(\"expected columns as list of strings\")\n\n    return {name: list(self.columns[name][slice_]) for name in columns}\n
    "},{"location":"reference/core/#tablite.core.Table.as_json_serializable","title":"tablite.core.Table.as_json_serializable(row_count='row id', start_on=1, columns=None, slice_=None)","text":"

    provides a JSON compatible format of the table.

    PARAMETER DESCRIPTION row_count

    Label for row counts. Defaults to \"row id\".

    TYPE: str DEFAULT: 'row id'

    start_on

    row counts starts by default on 1.

    TYPE: int DEFAULT: 1

    columns

    Column names. Defaults to None which returns all columns.

    TYPE: list of str DEFAULT: None

    slice_

    selector. Defaults to None which returns [:]

    TYPE: slice DEFAULT: None

    RETURNS DESCRIPTION

    JSON serializable dict: All python datatypes have been converted to JSON compliant data.

    Source code in tablite/base.py
    def as_json_serializable(\n    self, row_count=\"row id\", start_on=1, columns=None, slice_=None\n):\n    \"\"\"provides a JSON compatible format of the table.\n\n    Args:\n        row_count (str, optional): Label for row counts. Defaults to \"row id\".\n        start_on (int, optional): row counts starts by default on 1.\n        columns (list of str, optional): Column names.\n            Defaults to None which returns all columns.\n        slice_ (slice, optional): selector. Defaults to None which returns [:]\n\n    Returns:\n        JSON serializable dict: All python datatypes have been converted to JSON compliant data.\n    \"\"\"\n    if slice_ is None:\n        slice_ = slice(0, len(self))\n\n    assert isinstance(slice_, slice)\n    new = {\"columns\": {}, \"total_rows\": len(self)}\n    if row_count is not None:\n        new[\"columns\"][row_count] = [\n            i + start_on for i in range(*slice_.indices(len(self)))\n        ]\n\n    d = self.to_dict(columns, slice_=slice_)\n    for k, data in d.items():\n        new_k = unique_name(\n            k, new[\"columns\"]\n        )  # used to avoid overwriting the `row id` key.\n        new[\"columns\"][new_k] = [\n            DataTypes.to_json(v) for v in data\n        ]  # deal with non-json datatypes.\n    return new\n
    "},{"location":"reference/core/#tablite.core.Table.index","title":"tablite.core.Table.index(*args)","text":"

    param: *args: column names returns multikey index on the columns as d[(key tuple, )] = {index1, index2, ...}

    Examples:

    >>> table6 = Table()\n>>> table6['A'] = ['Alice', 'Bob', 'Bob', 'Ben', 'Charlie', 'Ben','Albert']\n>>> table6['B'] = ['Alison', 'Marley', 'Dylan', 'Affleck', 'Hepburn', 'Barnes', 'Einstein']\n
    >>> table6.index('A')  # single key.\n{('Alice',): [0],\n ('Bob',): [1, 2],\n ('Ben',): [3, 5],\n ('Charlie',): [4],\n ('Albert',): [6]})\n
    >>> table6.index('A', 'B')  # multiple keys.\n{('Alice', 'Alison'): [0],\n ('Bob', 'Marley'): [1],\n ('Bob', 'Dylan'): [2],\n ('Ben', 'Affleck'): [3],\n ('Charlie', 'Hepburn'): [4],\n ('Ben', 'Barnes'): [5],\n ('Albert', 'Einstein'): [6]})\n
    Source code in tablite/base.py
    def index(self, *args):\n    \"\"\"\n    param: *args: column names\n    returns multikey index on the columns as d[(key tuple, )] = {index1, index2, ...}\n\n    Examples:\n        ```\n        >>> table6 = Table()\n        >>> table6['A'] = ['Alice', 'Bob', 'Bob', 'Ben', 'Charlie', 'Ben','Albert']\n        >>> table6['B'] = ['Alison', 'Marley', 'Dylan', 'Affleck', 'Hepburn', 'Barnes', 'Einstein']\n        ```\n\n        ```\n        >>> table6.index('A')  # single key.\n        {('Alice',): [0],\n         ('Bob',): [1, 2],\n         ('Ben',): [3, 5],\n         ('Charlie',): [4],\n         ('Albert',): [6]})\n        ```\n\n        ```\n        >>> table6.index('A', 'B')  # multiple keys.\n        {('Alice', 'Alison'): [0],\n         ('Bob', 'Marley'): [1],\n         ('Bob', 'Dylan'): [2],\n         ('Ben', 'Affleck'): [3],\n         ('Charlie', 'Hepburn'): [4],\n         ('Ben', 'Barnes'): [5],\n         ('Albert', 'Einstein'): [6]})\n        ```\n\n    \"\"\"\n    idx = defaultdict(list)\n    iterators = [iter(self.columns[c]) for c in args]\n    for ix, key in enumerate(zip(*iterators)):\n        key = tuple(numpy_to_python(k) for k in key)\n        idx[key].append(ix)\n    return idx\n
    "},{"location":"reference/core/#tablite.core.Table.unique_index","title":"tablite.core.Table.unique_index(*args, tqdm=_tqdm)","text":"

    generates the index of unique rows given a list of column names

    PARAMETER DESCRIPTION *args

    columns names

    TYPE: any DEFAULT: ()

    tqdm

    Defaults to _tqdm.

    TYPE: tqdm DEFAULT: tqdm

    RETURNS DESCRIPTION

    np.array(int64): indices of unique records.

    Source code in tablite/base.py
    def unique_index(self, *args, tqdm=_tqdm):\n    \"\"\"generates the index of unique rows given a list of column names\n\n    Args:\n        *args (any): columns names\n        tqdm (tqdm, optional): Defaults to _tqdm.\n\n    Returns:\n        np.array(int64): indices of unique records.\n    \"\"\"\n    if not args:\n        raise ValueError(\"*args (column names) is required\")\n    seen = set()\n    unique = set()\n    iterators = [iter(self.columns[c]) for c in args]\n    for ix, key in tqdm(enumerate(zip(*iterators)), disable=Config.TQDM_DISABLE):\n        key_hash = hash(tuple(numpy_to_python(k) for k in key))\n        if key_hash in seen:\n            continue\n        else:\n            seen.add(key_hash)\n            unique.add(ix)\n    return np.array(sorted(unique))\n
    "},{"location":"reference/core/#tablite.core.Table.from_file","title":"tablite.core.Table.from_file(path, columns=None, first_row_has_headers=True, header_row_index=0, encoding=None, start=0, limit=sys.maxsize, sheet=None, guess_datatypes=True, newline='\\n', text_qualifier=None, delimiter=None, strip_leading_and_tailing_whitespace=True, text_escape_openings='', text_escape_closures='', skip_empty: ValidSkipEmpty = 'NONE', tqdm=_tqdm) -> Table classmethod","text":"
        reads path and imports 1 or more tables\n\n    REQUIRED\n    --------\n    path: pathlib.Path or str\n        selection of filereader uses path.suffix.\n        See `filereaders`.\n\n    OPTIONAL\n    --------\n    columns:\n        None: (default) All columns will be imported.\n        List: only column names from list will be imported (if present in file)\n              e.g. ['A', 'B', 'C', 'D']\n\n              datatype is detected using Datatypes.guess(...)\n              You can try it out with:\n              >> from tablite.datatypes import DataTypes\n              >> DataTypes.guess(['001','100'])\n              [1,100]\n\n              if the format cannot be achieved the read type is kept.\n        Excess column names are ignored.\n\n        HINT: To get the head of file use:\n        >>> from tablite.tools import head\n        >>> head = head(path)\n\n    first_row_has_headers: boolean\n        True: (default) first row is used as column names.\n        False: integers are used as column names.\n\n    encoding: str. Defaults to None (autodetect using n bytes).\n        n is declared in filereader_utils as ENCODING_GUESS_BYTES\n\n    start: the first line to be read (default: 0)\n\n    limit: the number of lines to be read from start (default sys.maxint ~ 2**63)\n\n    OPTIONAL FOR EXCEL AND ODS READERS\n    ----------------------------------\n\n    sheet: sheet name to import  (applicable to excel- and ods-reader only)\n        e.g. 'sheet_1'\n        sheets not found excess names are ignored.\n\n    OPTIONAL FOR TEXT READERS\n    -------------------------\n    guess_datatype: bool\n        True: (default) datatypes are guessed using DataTypes.guess(...)\n        False: all data is imported as strings.\n\n    newline: newline character (applicable to text_reader only)\n        str: '\n

    ' (default) or ' '

        text_qualifier: character (applicable to text_reader only)\n        None: No text qualifier is used.\n        str: \" or '\n\n    delimiter: character (applicable to text_reader only)\n        None: file suffix is used to determine field delimiter:\n            .txt: \"|\"\n            .csv: \",\",\n            .ssv: \";\"\n            .tsv: \" \" (tab)\n\n    strip_leading_and_tailing_whitespace: bool:\n        True: default\n\n    text_escape_openings: (applicable to text_reader only)\n        None: default\n        str: list of characters such as ([{\n\n    text_escape_closures: (applicable to text_reader only)\n        None: default\n        str: list of characters such as }])\n
    Source code in tablite/core.py
    @classmethod\ndef from_file(\n    cls,\n    path,\n    columns=None,\n    first_row_has_headers=True,\n    header_row_index=0,\n    encoding=None,\n    start=0,\n    limit=sys.maxsize,\n    sheet=None,\n    guess_datatypes=True,\n    newline=\"\\n\",\n    text_qualifier=None,\n    delimiter=None,\n    strip_leading_and_tailing_whitespace=True,\n    text_escape_openings=\"\",\n    text_escape_closures=\"\",\n    skip_empty: ValidSkipEmpty=\"NONE\",\n    tqdm=_tqdm,\n) -> \"Table\":\n    \"\"\"\n    reads path and imports 1 or more tables\n\n    REQUIRED\n    --------\n    path: pathlib.Path or str\n        selection of filereader uses path.suffix.\n        See `filereaders`.\n\n    OPTIONAL\n    --------\n    columns:\n        None: (default) All columns will be imported.\n        List: only column names from list will be imported (if present in file)\n              e.g. ['A', 'B', 'C', 'D']\n\n              datatype is detected using Datatypes.guess(...)\n              You can try it out with:\n              >> from tablite.datatypes import DataTypes\n              >> DataTypes.guess(['001','100'])\n              [1,100]\n\n              if the format cannot be achieved the read type is kept.\n        Excess column names are ignored.\n\n        HINT: To get the head of file use:\n        >>> from tablite.tools import head\n        >>> head = head(path)\n\n    first_row_has_headers: boolean\n        True: (default) first row is used as column names.\n        False: integers are used as column names.\n\n    encoding: str. Defaults to None (autodetect using n bytes).\n        n is declared in filereader_utils as ENCODING_GUESS_BYTES\n\n    start: the first line to be read (default: 0)\n\n    limit: the number of lines to be read from start (default sys.maxint ~ 2**63)\n\n    OPTIONAL FOR EXCEL AND ODS READERS\n    ----------------------------------\n\n    sheet: sheet name to import  (applicable to excel- and ods-reader only)\n        e.g. 'sheet_1'\n        sheets not found excess names are ignored.\n\n    OPTIONAL FOR TEXT READERS\n    -------------------------\n    guess_datatype: bool\n        True: (default) datatypes are guessed using DataTypes.guess(...)\n        False: all data is imported as strings.\n\n    newline: newline character (applicable to text_reader only)\n        str: '\\n' (default) or '\\r\\n'\n\n    text_qualifier: character (applicable to text_reader only)\n        None: No text qualifier is used.\n        str: \" or '\n\n    delimiter: character (applicable to text_reader only)\n        None: file suffix is used to determine field delimiter:\n            .txt: \"|\"\n            .csv: \",\",\n            .ssv: \";\"\n            .tsv: \"\\t\" (tab)\n\n    strip_leading_and_tailing_whitespace: bool:\n        True: default\n\n    text_escape_openings: (applicable to text_reader only)\n        None: default\n        str: list of characters such as ([{\n\n    text_escape_closures: (applicable to text_reader only)\n        None: default\n        str: list of characters such as }])\n\n    \"\"\"\n    if isinstance(path, str):\n        path = Path(path)\n    type_check(path, Path)\n\n    if not path.exists():\n        raise FileNotFoundError(f\"file not found: {path}\")\n\n    if not isinstance(start, int) or not 0 <= start <= sys.maxsize:\n        raise ValueError(f\"start {start} not in range(0,{sys.maxsize})\")\n\n    if not isinstance(limit, int) or not 0 < limit <= sys.maxsize:\n        raise ValueError(f\"limit {limit} not in range(0,{sys.maxsize})\")\n\n    if not isinstance(first_row_has_headers, bool):\n        raise TypeError(\"first_row_has_headers is not bool\")\n\n    import_as = path.suffix\n    if import_as.startswith(\".\"):\n        import_as = import_as[1:]\n\n    reader = import_utils.file_readers.get(import_as, None)\n    if reader is None:\n        raise ValueError(f\"{import_as} is not in supported format: {import_utils.valid_readers}\")\n\n    additional_configs = {\"tqdm\": tqdm}\n    if reader == import_utils.text_reader:\n        # here we inject tqdm, if tqdm is not provided, use generic iterator\n        # fmt:off\n        config = (path, columns, first_row_has_headers, header_row_index, encoding, start, limit, newline,\n                  guess_datatypes, text_qualifier, strip_leading_and_tailing_whitespace, skip_empty,\n                  delimiter, text_escape_openings, text_escape_closures)\n        # fmt:on\n\n    elif reader == import_utils.from_html:\n        config = (path,)\n    elif reader == import_utils.from_hdf5:\n        config = (path,)\n\n    elif reader == import_utils.excel_reader:\n        # config = path, first_row_has_headers, sheet, columns, start, limit\n        config = (\n            path,\n            first_row_has_headers,\n            header_row_index,\n            sheet,\n            columns,\n            skip_empty,\n            start,\n            limit,\n        )  # if file length changes - re-import.\n\n    if reader == import_utils.ods_reader:\n        # path, first_row_has_headers=True, sheet=None, columns=None, start=0, limit=sys.maxsize,\n        config = (\n            str(path),\n            first_row_has_headers,\n            header_row_index,\n            sheet,\n            columns,\n            skip_empty,\n            start,\n            limit,\n        )  # if file length changes - re-import.\n\n    # At this point the import config seems valid.\n    # Now we check if the file already has been imported.\n\n    # publish the settings\n    return reader(cls, *config, **additional_configs)\n
    "},{"location":"reference/core/#tablite.core.Table.from_pandas","title":"tablite.core.Table.from_pandas(df) classmethod","text":"

    Creates Table using pd.to_dict('list')

    similar to:

    >>> import pandas as pd\n>>> df = pd.DataFrame({'a':[1,2,3], 'b':[4,5,6]})\n>>> df\n    a  b\n    0  1  4\n    1  2  5\n    2  3  6\n>>> df.to_dict('list')\n{'a': [1, 2, 3], 'b': [4, 5, 6]}\n>>> t = Table.from_dict(df.to_dict('list))\n>>> t.show()\n    +===+===+===+\n    | # | a | b |\n    |row|int|int|\n    +---+---+---+\n    | 0 |  1|  4|\n    | 1 |  2|  5|\n    | 2 |  3|  6|\n    +===+===+===+\n
    Source code in tablite/core.py
    @classmethod\ndef from_pandas(cls, df):\n    \"\"\"\n    Creates Table using pd.to_dict('list')\n\n    similar to:\n    ```\n    >>> import pandas as pd\n    >>> df = pd.DataFrame({'a':[1,2,3], 'b':[4,5,6]})\n    >>> df\n        a  b\n        0  1  4\n        1  2  5\n        2  3  6\n    >>> df.to_dict('list')\n    {'a': [1, 2, 3], 'b': [4, 5, 6]}\n    >>> t = Table.from_dict(df.to_dict('list))\n    >>> t.show()\n        +===+===+===+\n        | # | a | b |\n        |row|int|int|\n        +---+---+---+\n        | 0 |  1|  4|\n        | 1 |  2|  5|\n        | 2 |  3|  6|\n        +===+===+===+\n    ```\n    \"\"\"\n    return import_utils.from_pandas(cls, df)\n
    "},{"location":"reference/core/#tablite.core.Table.from_hdf5","title":"tablite.core.Table.from_hdf5(path) classmethod","text":"

    imports an exported hdf5 table.

    Source code in tablite/core.py
    @classmethod\ndef from_hdf5(cls, path):\n    \"\"\"\n    imports an exported hdf5 table.\n    \"\"\"\n    return import_utils.from_hdf5(cls, path)\n
    "},{"location":"reference/core/#tablite.core.Table.from_json","title":"tablite.core.Table.from_json(jsn) classmethod","text":"

    Imports table exported using .to_json

    Source code in tablite/core.py
    @classmethod\ndef from_json(cls, jsn):\n    \"\"\"\n    Imports table exported using .to_json\n    \"\"\"\n    return import_utils.from_json(cls, jsn)\n
    "},{"location":"reference/core/#tablite.core.Table.to_hdf5","title":"tablite.core.Table.to_hdf5(path)","text":"

    creates a copy of the table as hdf5

    Source code in tablite/core.py
    def to_hdf5(self, path):\n    \"\"\"\n    creates a copy of the table as hdf5\n    \"\"\"\n    export_utils.to_hdf5(self, path)\n
    "},{"location":"reference/core/#tablite.core.Table.to_pandas","title":"tablite.core.Table.to_pandas()","text":"

    returns pandas.DataFrame

    Source code in tablite/core.py
    def to_pandas(self):\n    \"\"\"\n    returns pandas.DataFrame\n    \"\"\"\n    return export_utils.to_pandas(self)\n
    "},{"location":"reference/core/#tablite.core.Table.to_sql","title":"tablite.core.Table.to_sql(name)","text":"

    generates ANSI-92 compliant SQL.

    Source code in tablite/core.py
    def to_sql(self, name):\n    \"\"\"\n    generates ANSI-92 compliant SQL.\n    \"\"\"\n    return export_utils.to_sql(self, name)  # remove after update to test suite.\n
    "},{"location":"reference/core/#tablite.core.Table.to_json","title":"tablite.core.Table.to_json()","text":"

    returns JSON

    Source code in tablite/core.py
    def to_json(self):\n    \"\"\"\n    returns JSON\n    \"\"\"\n    return export_utils.to_json(self)\n
    "},{"location":"reference/core/#tablite.core.Table.to_xlsx","title":"tablite.core.Table.to_xlsx(path)","text":"

    exports table to path

    Source code in tablite/core.py
    def to_xlsx(self, path):\n    \"\"\"\n    exports table to path\n    \"\"\"\n    export_utils.path_suffix_check(path, \".xlsx\")\n    export_utils.excel_writer(self, path)\n
    "},{"location":"reference/core/#tablite.core.Table.to_ods","title":"tablite.core.Table.to_ods(path)","text":"

    exports table to path

    Source code in tablite/core.py
    def to_ods(self, path):\n    \"\"\"\n    exports table to path\n    \"\"\"\n    export_utils.path_suffix_check(path, \".ods\")\n    export_utils.excel_writer(self, path)\n
    "},{"location":"reference/core/#tablite.core.Table.to_csv","title":"tablite.core.Table.to_csv(path)","text":"

    exports table to path

    Source code in tablite/core.py
    def to_csv(self, path):\n    \"\"\"\n    exports table to path\n    \"\"\"\n    export_utils.path_suffix_check(path, \".csv\")\n    export_utils.text_writer(self, path)\n
    "},{"location":"reference/core/#tablite.core.Table.to_tsv","title":"tablite.core.Table.to_tsv(path)","text":"

    exports table to path

    Source code in tablite/core.py
    def to_tsv(self, path):\n    \"\"\"\n    exports table to path\n    \"\"\"\n    export_utils.path_suffix_check(path, \".tsv\")\n    export_utils.text_writer(self, path)\n
    "},{"location":"reference/core/#tablite.core.Table.to_text","title":"tablite.core.Table.to_text(path)","text":"

    exports table to path

    Source code in tablite/core.py
    def to_text(self, path):\n    \"\"\"\n    exports table to path\n    \"\"\"\n    export_utils.path_suffix_check(path, \".txt\")\n    export_utils.text_writer(self, path)\n
    "},{"location":"reference/core/#tablite.core.Table.to_html","title":"tablite.core.Table.to_html(path)","text":"

    exports table to path

    Source code in tablite/core.py
    def to_html(self, path):\n    \"\"\"\n    exports table to path\n    \"\"\"\n    export_utils.path_suffix_check(path, \".html\")\n    export_utils.to_html(self, path)\n
    "},{"location":"reference/core/#tablite.core.Table.expression","title":"tablite.core.Table.expression(expression)","text":"

    filters based on an expression, such as:

    \"all((A==B, C!=4, 200<D))\"\n

    which is interpreted using python's compiler to:

    def _f(A,B,C,D):\n    return all((A==B, C!=4, 200<D))\n
    Source code in tablite/core.py
    def expression(self, expression):\n    \"\"\"\n    filters based on an expression, such as:\n\n        \"all((A==B, C!=4, 200<D))\"\n\n    which is interpreted using python's compiler to:\n\n        def _f(A,B,C,D):\n            return all((A==B, C!=4, 200<D))\n    \"\"\"\n    return redux._filter_using_expression(self, expression)\n
    "},{"location":"reference/core/#tablite.core.Table.filter","title":"tablite.core.Table.filter(expressions, filter_type='all', tqdm=_tqdm)","text":"

    enables filtering across columns for multiple criteria.

    expressions:

    str: Expression that can be compiled and executed row by row.\n    exampLe: \"all((A==B and C!=4 and 200<D))\"\n\nlist of dicts: (example):\n\n    L = [\n        {'column1':'A', 'criteria': \"==\", 'column2': 'B'},\n        {'column1':'C', 'criteria': \"!=\", \"value2\": '4'},\n        {'value1': 200, 'criteria': \"<\", column2: 'D' }\n    ]\n\naccepted dictionary keys: 'column1', 'column2', 'criteria', 'value1', 'value2'\n

    filter_type: 'all' or 'any'

    Source code in tablite/core.py
    def filter(self, expressions, filter_type=\"all\", tqdm=_tqdm):\n    \"\"\"\n    enables filtering across columns for multiple criteria.\n\n    expressions:\n\n        str: Expression that can be compiled and executed row by row.\n            exampLe: \"all((A==B and C!=4 and 200<D))\"\n\n        list of dicts: (example):\n\n            L = [\n                {'column1':'A', 'criteria': \"==\", 'column2': 'B'},\n                {'column1':'C', 'criteria': \"!=\", \"value2\": '4'},\n                {'value1': 200, 'criteria': \"<\", column2: 'D' }\n            ]\n\n        accepted dictionary keys: 'column1', 'column2', 'criteria', 'value1', 'value2'\n\n    filter_type: 'all' or 'any'\n    \"\"\"\n    return redux.filter(self, expressions, filter_type, tqdm)\n
    "},{"location":"reference/core/#tablite.core.Table.sort_index","title":"tablite.core.Table.sort_index(sort_mode='excel', tqdm=_tqdm, pbar=None, **kwargs)","text":"

    helper for methods sort and is_sorted

    param: sort_mode: str: \"alphanumeric\", \"unix\", or, \"excel\" (default) param: **kwargs: sort criteria. See Table.sort()

    Source code in tablite/core.py
    def sort_index(self, sort_mode=\"excel\", tqdm=_tqdm, pbar=None, **kwargs):\n    \"\"\"\n    helper for methods `sort` and `is_sorted`\n\n    param: sort_mode: str: \"alphanumeric\", \"unix\", or, \"excel\" (default)\n    param: **kwargs: sort criteria. See Table.sort()\n    \"\"\"\n    return sortation.sort_index(self, sort_mode, tqdm=tqdm, pbar=pbar, **kwargs)\n
    "},{"location":"reference/core/#tablite.core.Table.reindex","title":"tablite.core.Table.reindex(index)","text":"

    index: list of integers that declare sort order.

    Examples:

    Table:  ['a','b','c','d','e','f','g','h']\nindex:  [0,2,4,6]\nresult: ['b','d','f','h']\n\nTable:  ['a','b','c','d','e','f','g','h']\nindex:  [0,2,4,6,1,3,5,7]\nresult: ['a','c','e','g','b','d','f','h']\n
    Source code in tablite/core.py
    def reindex(self, index):\n    \"\"\"\n    index: list of integers that declare sort order.\n\n    Examples:\n\n        Table:  ['a','b','c','d','e','f','g','h']\n        index:  [0,2,4,6]\n        result: ['b','d','f','h']\n\n        Table:  ['a','b','c','d','e','f','g','h']\n        index:  [0,2,4,6,1,3,5,7]\n        result: ['a','c','e','g','b','d','f','h']\n\n    \"\"\"\n    if isinstance(index, list):\n        index = np.array(index)\n    return _reindex.reindex(self, index)\n
    "},{"location":"reference/core/#tablite.core.Table.drop_duplicates","title":"tablite.core.Table.drop_duplicates(*args)","text":"

    removes duplicate rows based on column names

    args: (optional) column_names if no args, all columns are used.

    Source code in tablite/core.py
    def drop_duplicates(self, *args):\n    \"\"\"\n    removes duplicate rows based on column names\n\n    args: (optional) column_names\n    if no args, all columns are used.\n    \"\"\"\n    if not args:\n        args = self.columns\n    index = self.unique_index(*args)\n    return self.reindex(index)\n
    "},{"location":"reference/core/#tablite.core.Table.sort","title":"tablite.core.Table.sort(mapping, sort_mode='excel', tqdm=_tqdm, pbar: _tqdm = None)","text":"

    Perform multi-pass sorting with precedence given order of column names.

    PARAMETER DESCRIPTION mapping

    keys as columns, values as boolean for 'reverse'

    TYPE: dict

    sort_mode

    str: \"alphanumeric\", \"unix\", or, \"excel\"

    DEFAULT: 'excel'

    RETURNS DESCRIPTION None

    Table.sort is sorted inplace

    Examples: Table.sort(mappinp={A':False}) means sort by 'A' in ascending order. Table.sort(mapping={'A':True, 'B':False}) means sort 'A' in descending order, then (2nd priority) sort B in ascending order.

    Source code in tablite/core.py
    def sort(self, mapping, sort_mode=\"excel\", tqdm=_tqdm, pbar: _tqdm = None):\n    \"\"\"Perform multi-pass sorting with precedence given order of column names.\n\n    Args:\n        mapping (dict): keys as columns,\n                        values as boolean for 'reverse'\n        sort_mode: str: \"alphanumeric\", \"unix\", or, \"excel\"\n\n    Returns:\n        None: Table.sort is sorted inplace\n\n    Examples:\n    Table.sort(mappinp={A':False}) means sort by 'A' in ascending order.\n    Table.sort(mapping={'A':True, 'B':False}) means sort 'A' in descending order, then (2nd priority)\n    sort B in ascending order.\n    \"\"\"\n    new = sortation.sort(self, mapping, sort_mode, tqdm=tqdm, pbar=pbar)\n    self.columns = new.columns\n
    "},{"location":"reference/core/#tablite.core.Table.sorted","title":"tablite.core.Table.sorted(mapping, sort_mode='excel', tqdm=_tqdm, pbar: _tqdm = None)","text":"

    See sort. Sorted returns a new table in contrast to \"sort\", which is in-place.

    RETURNS DESCRIPTION

    Table.

    Source code in tablite/core.py
    def sorted(self, mapping, sort_mode=\"excel\", tqdm=_tqdm, pbar: _tqdm = None):\n    \"\"\"See sort.\n    Sorted returns a new table in contrast to \"sort\", which is in-place.\n\n    Returns:\n        Table.\n    \"\"\"\n    return sortation.sort(self, mapping, sort_mode, tqdm=tqdm, pbar=pbar)\n
    "},{"location":"reference/core/#tablite.core.Table.is_sorted","title":"tablite.core.Table.is_sorted(mapping, sort_mode='excel')","text":"

    Performs multi-pass sorting check with precedence given order of column names. **kwargs: optional: sort criteria. See Table.sort() :return bool

    Source code in tablite/core.py
    def is_sorted(self, mapping, sort_mode=\"excel\"):\n    \"\"\"Performs multi-pass sorting check with precedence given order of column names.\n    **kwargs: optional: sort criteria. See Table.sort()\n    :return bool\n    \"\"\"\n    return sortation.is_sorted(self, mapping, sort_mode)\n
    "},{"location":"reference/core/#tablite.core.Table.any","title":"tablite.core.Table.any(**kwargs)","text":"

    returns Table for rows where ANY kwargs match :param kwargs: dictionary with headers and values / boolean callable

    Source code in tablite/core.py
    def any(self, **kwargs):\n    \"\"\"\n    returns Table for rows where ANY kwargs match\n    :param kwargs: dictionary with headers and values / boolean callable\n    \"\"\"\n    return redux.filter_any(self, **kwargs)\n
    "},{"location":"reference/core/#tablite.core.Table.all","title":"tablite.core.Table.all(**kwargs)","text":"

    returns Table for rows where ALL kwargs match :param kwargs: dictionary with headers and values / boolean callable

    Examples:

    t = Table()\nt['a'] = [1,2,3,4]\nt['b'] = [10,20,30,40]\n\ndef f(x):\n    return x == 4\ndef g(x):\n    return x < 20\n\nt2 = t.any( **{\"a\":f, \"b\":g})\nassert [r for r in t2.rows] == [[1, 10], [4, 40]]\n\nt2 = t.any(a=f,b=g)\nassert [r for r in t2.rows] == [[1, 10], [4, 40]]\n\ndef h(x):\n    return x>=2\n\ndef i(x):\n    return x<=30\n\nt2 = t.all(a=h,b=i)\nassert [r for r in t2.rows] == [[2,20], [3, 30]]\n
    Source code in tablite/core.py
    def all(self, **kwargs):\n    \"\"\"\n    returns Table for rows where ALL kwargs match\n    :param kwargs: dictionary with headers and values / boolean callable\n\n    Examples:\n\n        t = Table()\n        t['a'] = [1,2,3,4]\n        t['b'] = [10,20,30,40]\n\n        def f(x):\n            return x == 4\n        def g(x):\n            return x < 20\n\n        t2 = t.any( **{\"a\":f, \"b\":g})\n        assert [r for r in t2.rows] == [[1, 10], [4, 40]]\n\n        t2 = t.any(a=f,b=g)\n        assert [r for r in t2.rows] == [[1, 10], [4, 40]]\n\n        def h(x):\n            return x>=2\n\n        def i(x):\n            return x<=30\n\n        t2 = t.all(a=h,b=i)\n        assert [r for r in t2.rows] == [[2,20], [3, 30]]\n\n\n    \"\"\"\n    return redux.filter_all(self, **kwargs)\n
    "},{"location":"reference/core/#tablite.core.Table.drop","title":"tablite.core.Table.drop(*args)","text":"

    removes all rows where args are present.

    Exmaple:

    t = Table() t['A'] = [1,2,3,None] t['B'] = [None,2,3,4] t2 = t.drop(None) t2'A', t2'B' ([2,3], [2,3])

    Source code in tablite/core.py
    def drop(self, *args):\n    \"\"\"\n    removes all rows where args are present.\n\n    Exmaple:\n    >>> t = Table()\n    >>> t['A'] = [1,2,3,None]\n    >>> t['B'] = [None,2,3,4]\n    >>> t2 = t.drop(None)\n    >>> t2['A'][:], t2['B'][:]\n    ([2,3], [2,3])\n\n    \"\"\"\n    if not args:\n        raise ValueError(\"What to drop? None? np.nan? \")\n    return redux.drop(self, *args)\n
    "},{"location":"reference/core/#tablite.core.Table.replace","title":"tablite.core.Table.replace(mapping, columns=None, tqdm=_tqdm, pbar=None)","text":"

    replaces all mapped keys with values from named columns

    PARAMETER DESCRIPTION mapping

    keys are targets for replacement, values are replacements.

    TYPE: dict

    columns

    target columns. Defaults to None (all columns)

    TYPE: list or str DEFAULT: None

    RAISES DESCRIPTION ValueError

    description

    Source code in tablite/core.py
    def replace(self, mapping, columns=None, tqdm=_tqdm, pbar=None):\n    \"\"\"replaces all mapped keys with values from named columns\n\n    Args:\n        mapping (dict): keys are targets for replacement,\n                        values are replacements.\n        columns (list or str, optional): target columns.\n            Defaults to None (all columns)\n\n    Raises:\n        ValueError: _description_\n    \"\"\"\n    if columns is None:\n        columns = list(self.columns)\n    if not isinstance(columns, list) and columns in self.columns:\n        columns = [columns]\n    type_check(columns, list)\n    for n in columns:\n        if n not in self.columns:\n            raise ValueError(f\"column not found: {n}\")\n\n    if pbar is None:\n        total = len(columns)\n        pbar = tqdm(total=total, desc=\"replace\", disable=Config.TQDM_DISABLE)\n\n    for name in columns:\n        col = self.columns[name]\n        col.replace(mapping)\n        pbar.update(1)\n
    "},{"location":"reference/core/#tablite.core.Table.groupby","title":"tablite.core.Table.groupby(keys, functions, tqdm=_tqdm, pbar=None)","text":"

    keys: column names for grouping. functions: [optional] list of column names and group functions (See GroupyBy class) returns: table

    Example:

    t = Table()\nt.add_column('A', data=[1, 1, 2, 2, 3, 3] * 2)\nt.add_column('B', data=[1, 2, 3, 4, 5, 6] * 2)\nt.add_column('C', data=[6, 5, 4, 3, 2, 1] * 2)\n\nt.show()\n+=====+=====+=====+\n|  A  |  B  |  C  |\n| int | int | int |\n+-----+-----+-----+\n|    1|    1|    6|\n|    1|    2|    5|\n|    2|    3|    4|\n|    2|    4|    3|\n|    3|    5|    2|\n|    3|    6|    1|\n|    1|    1|    6|\n|    1|    2|    5|\n|    2|    3|    4|\n|    2|    4|    3|\n|    3|    5|    2|\n|    3|    6|    1|\n+=====+=====+=====+\n\ng = t.groupby(keys=['A', 'C'], functions=[('B', gb.sum)])\ng.show()\n+===+===+===+======+\n| # | A | C |Sum(B)|\n|row|int|int| int  |\n+---+---+---+------+\n|0  |  1|  6|     2|\n|1  |  1|  5|     4|\n|2  |  2|  4|     6|\n|3  |  2|  3|     8|\n|4  |  3|  2|    10|\n|5  |  3|  1|    12|\n+===+===+===+======+\n

    Cheat sheet:

    list of unique values

    >>> g1 = t.groupby(keys=['A'], functions=[])\n>>> g1['A'][:]\n[1,2,3]\n

    alternatively:

    t['A'].unique() [1,2,3]

    list of unique values, grouped by longest combination.

    >>> g2 = t.groupby(keys=['A', 'B'], functions=[])\n>>> g2['A'][:], g2['B'][:]\n([1,1,2,2,3,3], [1,2,3,4,5,6])\n

    alternatively:

    >>> list(zip(*t.index('A', 'B').keys()))\n[(1,1,2,2,3,3) (1,2,3,4,5,6)]\n

    A key (unique values) and count hereof.

    >>> g3 = t.groupby(keys=['A'], functions=[('A', gb.count)])\n>>> g3['A'][:], g3['Count(A)'][:]\n([1,2,3], [4,4,4])\n

    alternatively:

    >>> t['A'].histogram()\n([1,2,3], [4,4,4])\n

    for more exmaples see: https://github.com/root-11/tablite/blob/master/tests/test_groupby.py

    Source code in tablite/core.py
    def groupby(self, keys, functions, tqdm=_tqdm, pbar=None):\n    \"\"\"\n    keys: column names for grouping.\n    functions: [optional] list of column names and group functions (See GroupyBy class)\n    returns: table\n\n    Example:\n    ```\n    t = Table()\n    t.add_column('A', data=[1, 1, 2, 2, 3, 3] * 2)\n    t.add_column('B', data=[1, 2, 3, 4, 5, 6] * 2)\n    t.add_column('C', data=[6, 5, 4, 3, 2, 1] * 2)\n\n    t.show()\n    +=====+=====+=====+\n    |  A  |  B  |  C  |\n    | int | int | int |\n    +-----+-----+-----+\n    |    1|    1|    6|\n    |    1|    2|    5|\n    |    2|    3|    4|\n    |    2|    4|    3|\n    |    3|    5|    2|\n    |    3|    6|    1|\n    |    1|    1|    6|\n    |    1|    2|    5|\n    |    2|    3|    4|\n    |    2|    4|    3|\n    |    3|    5|    2|\n    |    3|    6|    1|\n    +=====+=====+=====+\n\n    g = t.groupby(keys=['A', 'C'], functions=[('B', gb.sum)])\n    g.show()\n    +===+===+===+======+\n    | # | A | C |Sum(B)|\n    |row|int|int| int  |\n    +---+---+---+------+\n    |0  |  1|  6|     2|\n    |1  |  1|  5|     4|\n    |2  |  2|  4|     6|\n    |3  |  2|  3|     8|\n    |4  |  3|  2|    10|\n    |5  |  3|  1|    12|\n    +===+===+===+======+\n    ```\n    Cheat sheet:\n\n    list of unique values\n    ```\n    >>> g1 = t.groupby(keys=['A'], functions=[])\n    >>> g1['A'][:]\n    [1,2,3]\n    ```\n    alternatively:\n    >>> t['A'].unique()\n    [1,2,3]\n\n    list of unique values, grouped by longest combination.\n    ```\n    >>> g2 = t.groupby(keys=['A', 'B'], functions=[])\n    >>> g2['A'][:], g2['B'][:]\n    ([1,1,2,2,3,3], [1,2,3,4,5,6])\n    ```\n    alternatively:\n    ```\n    >>> list(zip(*t.index('A', 'B').keys()))\n    [(1,1,2,2,3,3) (1,2,3,4,5,6)]\n    ```\n    A key (unique values) and count hereof.\n    ```\n    >>> g3 = t.groupby(keys=['A'], functions=[('A', gb.count)])\n    >>> g3['A'][:], g3['Count(A)'][:]\n    ([1,2,3], [4,4,4])\n    ```\n    alternatively:\n    ```\n    >>> t['A'].histogram()\n    ([1,2,3], [4,4,4])\n    ```\n    for more exmaples see:\n        https://github.com/root-11/tablite/blob/master/tests/test_groupby.py\n\n    \"\"\"\n    return groupbys.groupby(self, keys, functions, tqdm=tqdm, pbar=pbar)\n
    "},{"location":"reference/core/#tablite.core.Table.pivot","title":"tablite.core.Table.pivot(rows, columns, functions, values_as_rows=True, tqdm=_tqdm, pbar=None)","text":"

    param: rows: column names to keep as rows param: columns: column names to keep as columns param: functions: aggregation functions from the Groupby class as

    example:

    t.show()\n+=====+=====+=====+\n|  A  |  B  |  C  |\n| int | int | int |\n+-----+-----+-----+\n|    1|    1|    6|\n|    1|    2|    5|\n|    2|    3|    4|\n|    2|    4|    3|\n|    3|    5|    2|\n|    3|    6|    1|\n|    1|    1|    6|\n|    1|    2|    5|\n|    2|    3|    4|\n|    2|    4|    3|\n|    3|    5|    2|\n|    3|    6|    1|\n+=====+=====+=====+\n\nt2 = t.pivot(rows=['C'], columns=['A'], functions=[('B', gb.sum)])\nt2.show()\n+===+===+========+=====+=====+=====+\n| # | C |function|(A=1)|(A=2)|(A=3)|\n|row|int|  str   |mixed|mixed|mixed|\n+---+---+--------+-----+-----+-----+\n|0  |  6|Sum(B)  |    2|None |None |\n|1  |  5|Sum(B)  |    4|None |None |\n|2  |  4|Sum(B)  |None |    6|None |\n|3  |  3|Sum(B)  |None |    8|None |\n|4  |  2|Sum(B)  |None |None |   10|\n|5  |  1|Sum(B)  |None |None |   12|\n+===+===+========+=====+=====+=====+\n
    Source code in tablite/core.py
    def pivot(self, rows, columns, functions, values_as_rows=True, tqdm=_tqdm, pbar=None):\n    \"\"\"\n    param: rows: column names to keep as rows\n    param: columns: column names to keep as columns\n    param: functions: aggregation functions from the Groupby class as\n\n    example:\n    ```\n    t.show()\n    +=====+=====+=====+\n    |  A  |  B  |  C  |\n    | int | int | int |\n    +-----+-----+-----+\n    |    1|    1|    6|\n    |    1|    2|    5|\n    |    2|    3|    4|\n    |    2|    4|    3|\n    |    3|    5|    2|\n    |    3|    6|    1|\n    |    1|    1|    6|\n    |    1|    2|    5|\n    |    2|    3|    4|\n    |    2|    4|    3|\n    |    3|    5|    2|\n    |    3|    6|    1|\n    +=====+=====+=====+\n\n    t2 = t.pivot(rows=['C'], columns=['A'], functions=[('B', gb.sum)])\n    t2.show()\n    +===+===+========+=====+=====+=====+\n    | # | C |function|(A=1)|(A=2)|(A=3)|\n    |row|int|  str   |mixed|mixed|mixed|\n    +---+---+--------+-----+-----+-----+\n    |0  |  6|Sum(B)  |    2|None |None |\n    |1  |  5|Sum(B)  |    4|None |None |\n    |2  |  4|Sum(B)  |None |    6|None |\n    |3  |  3|Sum(B)  |None |    8|None |\n    |4  |  2|Sum(B)  |None |None |   10|\n    |5  |  1|Sum(B)  |None |None |   12|\n    +===+===+========+=====+=====+=====+\n    ```\n    \"\"\"\n    return pivots.pivot(self, rows, columns, functions, values_as_rows, tqdm=tqdm, pbar=pbar)\n
    "},{"location":"reference/core/#tablite.core.Table.merge","title":"tablite.core.Table.merge(left, right, new, criteria)","text":"

    takes from LEFT where criteria is True else RIGHT. :param: T: Table :param: criteria: np.array(bool): if True take left column else take right column :param left: (str) column name :param right: (str) column name :param new: (str) new name

    :returns: T

    Example:

    >>> c.show()\n+==+====+====+====+====+\n| #| A  | B  | C  | D  |\n+--+----+----+----+----+\n| 0|   1|  10|   1|  11|\n| 1|   2|  20|   2|  12|\n| 2|   3|None|   3|  13|\n| 3|None|  40|None|None|\n| 4|   5|  50|None|None|\n| 5|None|None|   6|  16|\n| 6|None|None|   7|  17|\n+==+====+====+====+====+\n\n>>> c.merge(\"A\", \"C\", new=\"E\", criteria=[v != None for v in c['A']])\n>>> c.show()\n+==+====+====+====+\n| #| B  | D  | E  |\n+--+----+----+----+\n| 0|  10|  11|   1|\n| 1|  20|  12|   2|\n| 2|None|  13|   3|\n| 3|  40|None|None|\n| 4|  50|None|   5|\n| 5|None|  16|   6|\n| 6|None|  17|   7|\n+==+====+====+====+\n
    Source code in tablite/core.py
    def merge(self, left, right, new, criteria):\n    \"\"\" takes from LEFT where criteria is True else RIGHT.\n    :param: T: Table\n    :param: criteria: np.array(bool): \n            if True take left column\n            else take right column\n    :param left: (str) column name\n    :param right: (str) column name\n    :param new: (str) new name\n\n    :returns: T\n\n    Example:\n    ```\n    >>> c.show()\n    +==+====+====+====+====+\n    | #| A  | B  | C  | D  |\n    +--+----+----+----+----+\n    | 0|   1|  10|   1|  11|\n    | 1|   2|  20|   2|  12|\n    | 2|   3|None|   3|  13|\n    | 3|None|  40|None|None|\n    | 4|   5|  50|None|None|\n    | 5|None|None|   6|  16|\n    | 6|None|None|   7|  17|\n    +==+====+====+====+====+\n\n    >>> c.merge(\"A\", \"C\", new=\"E\", criteria=[v != None for v in c['A']])\n    >>> c.show()\n    +==+====+====+====+\n    | #| B  | D  | E  |\n    +--+----+----+----+\n    | 0|  10|  11|   1|\n    | 1|  20|  12|   2|\n    | 2|None|  13|   3|\n    | 3|  40|None|None|\n    | 4|  50|None|   5|\n    | 5|None|  16|   6|\n    | 6|None|  17|   7|\n    +==+====+====+====+\n    ```\n    \"\"\"\n    return merge.where(self, criteria,left,right,new)\n
    "},{"location":"reference/core/#tablite.core.Table.column_select","title":"tablite.core.Table.column_select(cols: list[ColumnSelectorDict], tqdm=_tqdm, TaskManager=_TaskManager)","text":"

    type-casts columns from a given table to specified type(s)

    cols

    list of dicts: (example):

    cols = [\n    {'column':'A', 'type': 'bool'},\n    {'column':'B', 'type': 'int', 'allow_empty': True},\n    {'column':'B', 'type': 'float', 'allow_empty': False, 'rename': 'C'},\n]\n

    'column' : column name of the input table that we want to type-cast 'type' : type that we want to type-cast the specified column to 'allow_empty': should we allow empty values (None, str('')) through (Default: False) 'rename' : new name of the column, if None will keep the original name, in case of duplicates suffix will be added (Default: None)

    supported types: 'bool', 'int', 'float', 'str', 'date', 'time', 'datetime'

    if any of the columns is rejected, entire row is rejected

    tqdm: progressbar constructor TaskManager: TaskManager constructor

    (TABLE, TABLE) DESCRIPTION

    first table contains the rows that were successfully cast to desired types

    second table contains rows that failed to cast + rejection reason

    Source code in tablite/core.py
    def column_select(self, cols: list[ColumnSelectorDict], tqdm=_tqdm, TaskManager=_TaskManager):\n    \"\"\"\n    type-casts columns from a given table to specified type(s)\n\n    cols:\n        list of dicts: (example):\n\n            cols = [\n                {'column':'A', 'type': 'bool'},\n                {'column':'B', 'type': 'int', 'allow_empty': True},\n                {'column':'B', 'type': 'float', 'allow_empty': False, 'rename': 'C'},\n            ]\n\n        'column'     : column name of the input table that we want to type-cast\n        'type'       : type that we want to type-cast the specified column to\n        'allow_empty': should we allow empty values (None, str('')) through (Default: False)\n        'rename'     : new name of the column, if None will keep the original name, in case of duplicates suffix will be added (Default: None)\n\n        supported types: 'bool', 'int', 'float', 'str', 'date', 'time', 'datetime'\n\n        if any of the columns is rejected, entire row is rejected\n\n    tqdm: progressbar constructor\n    TaskManager: TaskManager constructor\n\n    returns: (Table, Table)\n        first table contains the rows that were successfully cast to desired types\n        second table contains rows that failed to cast + rejection reason\n    \"\"\"\n    return _column_select(self, cols, tqdm, TaskManager)\n
    "},{"location":"reference/core/#tablite.core.Table.join","title":"tablite.core.Table.join(other, left_keys, right_keys, left_columns=None, right_columns=None, kind='inner', merge_keys=False, tqdm=_tqdm, pbar=None)","text":"

    short-cut for all join functions. kind: 'inner', 'left', 'outer', 'cross'

    Source code in tablite/core.py
    def join(self, other, left_keys, right_keys, left_columns=None, right_columns=None, kind=\"inner\", merge_keys=False, tqdm=_tqdm, pbar=None):\n    \"\"\"\n    short-cut for all join functions.\n    kind: 'inner', 'left', 'outer', 'cross'\n    \"\"\"\n    kinds = {\n        \"inner\": self.inner_join,\n        \"left\": self.left_join,\n        \"outer\": self.outer_join,\n        \"cross\": self.cross_join,\n    }\n    if kind not in kinds:\n        raise ValueError(f\"join type unknown: {kind}\")\n    f = kinds.get(kind, None)\n    return f(other, left_keys, right_keys, left_columns, right_columns, merge_keys=merge_keys, tqdm=tqdm, pbar=pbar)\n
    "},{"location":"reference/core/#tablite.core.Table.left_join","title":"tablite.core.Table.left_join(other, left_keys, right_keys, left_columns=None, right_columns=None, merge_keys=False, tqdm=_tqdm, pbar=None)","text":"

    :param other: self, other = (left, right) :param left_keys: list of keys for the join :param right_keys: list of keys for the join :param left_columns: list of left columns to retain, if None, all are retained. :param right_columns: list of right columns to retain, if None, all are retained. :return: new Table Example:

    SQL:   SELECT number, letter FROM numbers LEFT JOIN letters ON numbers.colour == letters.color\nTablite: left_join = numbers.left_join(\n    letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter']\n)\n
    Source code in tablite/core.py
    def left_join(self, other, left_keys, right_keys, left_columns=None, right_columns=None, merge_keys=False, tqdm=_tqdm, pbar=None):\n    \"\"\"\n    :param other: self, other = (left, right)\n    :param left_keys: list of keys for the join\n    :param right_keys: list of keys for the join\n    :param left_columns: list of left columns to retain, if None, all are retained.\n    :param right_columns: list of right columns to retain, if None, all are retained.\n    :return: new Table\n    Example:\n    ```\n    SQL:   SELECT number, letter FROM numbers LEFT JOIN letters ON numbers.colour == letters.color\n    Tablite: left_join = numbers.left_join(\n        letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter']\n    )\n    ```\n    \"\"\"\n    return joins.left_join(self, other, left_keys, right_keys, left_columns, right_columns, merge_keys=merge_keys, tqdm=tqdm, pbar=pbar)\n
    "},{"location":"reference/core/#tablite.core.Table.inner_join","title":"tablite.core.Table.inner_join(other, left_keys, right_keys, left_columns=None, right_columns=None, merge_keys=False, tqdm=_tqdm, pbar=None)","text":"

    :param other: self, other = (left, right) :param left_keys: list of keys for the join :param right_keys: list of keys for the join :param left_columns: list of left columns to retain, if None, all are retained. :param right_columns: list of right columns to retain, if None, all are retained. :return: new Table Example:

    SQL:   SELECT number, letter FROM numbers JOIN letters ON numbers.colour == letters.color\nTablite: inner_join = numbers.inner_join(\n    letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter']\n    )\n
    Source code in tablite/core.py
    def inner_join(self, other, left_keys, right_keys, left_columns=None, right_columns=None, merge_keys=False, tqdm=_tqdm, pbar=None):\n    \"\"\"\n    :param other: self, other = (left, right)\n    :param left_keys: list of keys for the join\n    :param right_keys: list of keys for the join\n    :param left_columns: list of left columns to retain, if None, all are retained.\n    :param right_columns: list of right columns to retain, if None, all are retained.\n    :return: new Table\n    Example:\n    ```\n    SQL:   SELECT number, letter FROM numbers JOIN letters ON numbers.colour == letters.color\n    Tablite: inner_join = numbers.inner_join(\n        letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter']\n        )\n    ```\n    \"\"\"\n    return joins.inner_join(self, other, left_keys, right_keys, left_columns, right_columns, merge_keys=merge_keys, tqdm=tqdm, pbar=pbar)\n
    "},{"location":"reference/core/#tablite.core.Table.outer_join","title":"tablite.core.Table.outer_join(other, left_keys, right_keys, left_columns=None, right_columns=None, merge_keys=False, tqdm=_tqdm, pbar=None)","text":"

    :param other: self, other = (left, right) :param left_keys: list of keys for the join :param right_keys: list of keys for the join :param left_columns: list of left columns to retain, if None, all are retained. :param right_columns: list of right columns to retain, if None, all are retained. :return: new Table Example:

    SQL:   SELECT number, letter FROM numbers OUTER JOIN letters ON numbers.colour == letters.color\nTablite: outer_join = numbers.outer_join(\n    letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter']\n    )\n
    Source code in tablite/core.py
    def outer_join(self, other, left_keys, right_keys, left_columns=None, right_columns=None, merge_keys=False, tqdm=_tqdm, pbar=None):\n    \"\"\"\n    :param other: self, other = (left, right)\n    :param left_keys: list of keys for the join\n    :param right_keys: list of keys for the join\n    :param left_columns: list of left columns to retain, if None, all are retained.\n    :param right_columns: list of right columns to retain, if None, all are retained.\n    :return: new Table\n    Example:\n    ```\n    SQL:   SELECT number, letter FROM numbers OUTER JOIN letters ON numbers.colour == letters.color\n    Tablite: outer_join = numbers.outer_join(\n        letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter']\n        )\n    ```\n    \"\"\"\n    return joins.outer_join(self, other, left_keys, right_keys, left_columns, right_columns, merge_keys=merge_keys, tqdm=tqdm, pbar=pbar)\n
    "},{"location":"reference/core/#tablite.core.Table.cross_join","title":"tablite.core.Table.cross_join(other, left_keys, right_keys, left_columns=None, right_columns=None, merge_keys=False, tqdm=_tqdm, pbar=None)","text":"

    CROSS JOIN returns the Cartesian product of rows from tables in the join. In other words, it will produce rows which combine each row from the first table with each row from the second table

    Source code in tablite/core.py
    def cross_join(self, other, left_keys, right_keys, left_columns=None, right_columns=None, merge_keys=False, tqdm=_tqdm, pbar=None):\n    \"\"\"\n    CROSS JOIN returns the Cartesian product of rows from tables in the join.\n    In other words, it will produce rows which combine each row from the first table\n    with each row from the second table\n    \"\"\"\n    return joins.cross_join(self, other, left_keys, right_keys, left_columns, right_columns, merge_keys=merge_keys, tqdm=tqdm, pbar=pbar)\n
    "},{"location":"reference/core/#tablite.core.Table.lookup","title":"tablite.core.Table.lookup(other, *criteria, all=True, tqdm=_tqdm)","text":"

    function for looking up values in other according to criteria in ascending order. :param: other: Table sorted in ascending search order. :param: criteria: Each criteria must be a tuple with value comparisons in the form: (LEFT, OPERATOR, RIGHT) :param: all: boolean: True=ALL, False=Any

    OPERATOR must be a callable that returns a boolean LEFT must be a value that the OPERATOR can compare. RIGHT must be a value that the OPERATOR can compare.

    Examples:

    ('column A', \"==\", 'column B')  # comparison of two columns\n('Date', \"<\", DataTypes.date(24,12) )  # value from column 'Date' is before 24/12.\nf = lambda L,R: all( ord(L) < ord(R) )  # uses custom function.\n('text 1', f, 'text 2') value from column 'text 1' is compared with value from column 'text 2'\n
    Source code in tablite/core.py
    def lookup(self, other, *criteria, all=True, tqdm=_tqdm):\n    \"\"\"function for looking up values in `other` according to criteria in ascending order.\n    :param: other: Table sorted in ascending search order.\n    :param: criteria: Each criteria must be a tuple with value comparisons in the form:\n        (LEFT, OPERATOR, RIGHT)\n    :param: all: boolean: True=ALL, False=Any\n\n    OPERATOR must be a callable that returns a boolean\n    LEFT must be a value that the OPERATOR can compare.\n    RIGHT must be a value that the OPERATOR can compare.\n\n    Examples:\n    ```\n    ('column A', \"==\", 'column B')  # comparison of two columns\n    ('Date', \"<\", DataTypes.date(24,12) )  # value from column 'Date' is before 24/12.\n    f = lambda L,R: all( ord(L) < ord(R) )  # uses custom function.\n    ('text 1', f, 'text 2') value from column 'text 1' is compared with value from column 'text 2'\n    ```\n    \"\"\"\n    return lookup.lookup(self, other, *criteria, all=all, tqdm=tqdm)\n
    "},{"location":"reference/core/#tablite.core.Table.match","title":"tablite.core.Table.match(other, *criteria, keep_left=None, keep_right=None)","text":"

    performs inner join where T matches other and removes rows that do not match.

    :param: T: Table :param: other: Table :param: criteria: Each criteria must be a tuple with value comparisons in the form:

    (LEFT, OPERATOR, RIGHT), where operator must be \"==\"\n\nExample:\n    ('column A', \"==\", 'column B')\n\nThis syntax follows the lookup syntax. See Lookup for details.\n

    :param: keep_left: list of columns to keep. :param: keep_right: list of right columns to keep.

    Source code in tablite/core.py
    def match(self, other, *criteria, keep_left=None, keep_right=None):\n    \"\"\"\n    performs inner join where `T` matches `other` and removes rows that do not match.\n\n    :param: T: Table\n    :param: other: Table\n    :param: criteria: Each criteria must be a tuple with value comparisons in the form:\n\n        (LEFT, OPERATOR, RIGHT), where operator must be \"==\"\n\n        Example:\n            ('column A', \"==\", 'column B')\n\n        This syntax follows the lookup syntax. See Lookup for details.\n\n    :param: keep_left: list of columns to keep.\n    :param: keep_right: list of right columns to keep.\n    \"\"\"\n    return match.match(self, other, *criteria, keep_left=keep_left, keep_right=keep_right)\n
    "},{"location":"reference/core/#tablite.core.Table.replace_missing_values","title":"tablite.core.Table.replace_missing_values(*args, **kwargs)","text":"Source code in tablite/core.py
    def replace_missing_values(self, *args, **kwargs):\n    raise AttributeError(\"See imputation\")\n
    "},{"location":"reference/core/#tablite.core.Table.imputation","title":"tablite.core.Table.imputation(targets, missing=None, method='carry forward', sources=None, tqdm=_tqdm)","text":"

    In statistics, imputation is the process of replacing missing data with substituted values.

    See more: https://en.wikipedia.org/wiki/Imputation_(statistics)

    PARAMETER DESCRIPTION table

    source table.

    TYPE: Table

    targets

    column names to find and replace missing values

    TYPE: str or list of strings

    missing

    values to be replaced.

    TYPE: None or iterable DEFAULT: None

    method

    method to be used for replacement. Options:

    'carry forward': takes the previous value, and carries forward into fields where values are missing. +: quick. Realistic on time series. -: Can produce strange outliers.

    'mean': calculates the column mean (exclude missing) and copies the mean in as replacement. +: quick -: doesn't work on text. Causes data set to drift towards the mean.

    'mode': calculates the column mode (exclude missing) and copies the mean in as replacement. +: quick -: most frequent value becomes over-represented in the sample

    'nearest neighbour': calculates normalised distance between items in source columns selects nearest neighbour and copies value as replacement. +: works for any datatype. -: computationally intensive (e.g. slow)

    TYPE: str DEFAULT: 'carry forward'

    sources

    NEAREST NEIGHBOUR ONLY column names to be used during imputation. if None or empty, all columns will be used.

    TYPE: list of strings DEFAULT: None

    RETURNS DESCRIPTION table

    table with replaced values.

    Source code in tablite/core.py
    def imputation(self, targets, missing=None, method=\"carry forward\", sources=None, tqdm=_tqdm):\n    \"\"\"\n    In statistics, imputation is the process of replacing missing data with substituted values.\n\n    See more: https://en.wikipedia.org/wiki/Imputation_(statistics)\n\n    Args:\n        table (Table): source table.\n\n        targets (str or list of strings): column names to find and\n            replace missing values\n\n        missing (None or iterable): values to be replaced.\n\n        method (str): method to be used for replacement. Options:\n\n            'carry forward':\n                takes the previous value, and carries forward into fields\n                where values are missing.\n                +: quick. Realistic on time series.\n                -: Can produce strange outliers.\n\n            'mean':\n                calculates the column mean (exclude `missing`) and copies\n                the mean in as replacement.\n                +: quick\n                -: doesn't work on text. Causes data set to drift towards the mean.\n\n            'mode':\n                calculates the column mode (exclude `missing`) and copies\n                the mean in as replacement.\n                +: quick\n                -: most frequent value becomes over-represented in the sample\n\n            'nearest neighbour':\n                calculates normalised distance between items in source columns\n                selects nearest neighbour and copies value as replacement.\n                +: works for any datatype.\n                -: computationally intensive (e.g. slow)\n\n        sources (list of strings): NEAREST NEIGHBOUR ONLY\n            column names to be used during imputation.\n            if None or empty, all columns will be used.\n\n    Returns:\n        table: table with replaced values.\n    \"\"\"\n    return imputation.imputation(self, targets, missing, method, sources, tqdm=tqdm)\n
    "},{"location":"reference/core/#tablite.core.Table.transpose","title":"tablite.core.Table.transpose(tqdm=_tqdm)","text":"Source code in tablite/core.py
    def transpose(self, tqdm=_tqdm):\n    return pivots.transpose(self, tqdm)\n
    "},{"location":"reference/core/#tablite.core.Table.pivot_transpose","title":"tablite.core.Table.pivot_transpose(columns, keep=None, column_name='transpose', value_name='value', tqdm=_tqdm)","text":"

    Transpose a selection of columns to rows.

    PARAMETER DESCRIPTION columns

    column names to transpose

    TYPE: list of column names

    keep

    column names to keep (repeat)

    TYPE: list of column names DEFAULT: None

    RETURNS DESCRIPTION Table

    with columns transposed to rows

    Example

    transpose columns 1,2 and 3 and transpose the remaining columns, except sum.

    Input:

    | col1 | col2 | col3 | sun | mon | tue | ... | sat | sum  |\n|------|------|------|-----|-----|-----|-----|-----|------|\n| 1234 | 2345 | 3456 | 456 | 567 |     | ... |     | 1023 |\n| 1244 | 2445 | 4456 |     |   7 |     | ... |     |    7 |\n| ...  |      |      |     |     |     |     |     |      |\n\nt.transpose(keep=[col1, col2, col3], transpose=[sun,mon,tue,wed,thu,fri,sat])`\n\nOutput:\n\n|col1| col2| col3| transpose| value|\n|----|-----|-----|----------|------|\n|1234| 2345| 3456| sun      |   456|\n|1234| 2345| 3456| mon      |   567|\n|1244| 2445| 4456| mon      |     7|\n
    Source code in tablite/core.py
    def pivot_transpose(self, columns, keep=None, column_name=\"transpose\", value_name=\"value\", tqdm=_tqdm):\n    \"\"\"Transpose a selection of columns to rows.\n\n    Args:\n        columns (list of column names): column names to transpose\n        keep (list of column names): column names to keep (repeat)\n\n    Returns:\n        Table: with columns transposed to rows\n\n    Example:\n        transpose columns 1,2 and 3 and transpose the remaining columns, except `sum`.\n\n    Input:\n    ```\n    | col1 | col2 | col3 | sun | mon | tue | ... | sat | sum  |\n    |------|------|------|-----|-----|-----|-----|-----|------|\n    | 1234 | 2345 | 3456 | 456 | 567 |     | ... |     | 1023 |\n    | 1244 | 2445 | 4456 |     |   7 |     | ... |     |    7 |\n    | ...  |      |      |     |     |     |     |     |      |\n\n    t.transpose(keep=[col1, col2, col3], transpose=[sun,mon,tue,wed,thu,fri,sat])`\n\n    Output:\n\n    |col1| col2| col3| transpose| value|\n    |----|-----|-----|----------|------|\n    |1234| 2345| 3456| sun      |   456|\n    |1234| 2345| 3456| mon      |   567|\n    |1244| 2445| 4456| mon      |     7|\n    ```\n    \"\"\"\n    return pivots.pivot_transpose(self, columns, keep, column_name, value_name, tqdm=tqdm)\n
    "},{"location":"reference/core/#tablite.core.Table.diff","title":"tablite.core.Table.diff(other, columns=None)","text":"

    compares table self with table other

    PARAMETER DESCRIPTION self

    Table

    TYPE: Table

    other

    Table

    TYPE: Table

    columns

    list of column names to include in comparison. Defaults to None.

    TYPE: List DEFAULT: None

    RETURNS DESCRIPTION Table

    diff of self and other with diff in columns 1st and 2nd.

    Source code in tablite/core.py
    def diff(self, other, columns=None):\n    \"\"\"compares table self with table other\n\n    Args:\n        self (Table): Table\n        other (Table): Table\n        columns (List, optional): list of column names to include in comparison. Defaults to None.\n\n    Returns:\n        Table: diff of self and other with diff in columns 1st and 2nd.\n    \"\"\"\n    return diff.diff(self, other, columns)\n
    "},{"location":"reference/core/#tablite.core-functions","title":"Functions","text":""},{"location":"reference/core/#tablite.core-modules","title":"Modules","text":""},{"location":"reference/datasets/","title":"Datasets","text":""},{"location":"reference/datasets/#tablite.datasets","title":"tablite.datasets","text":""},{"location":"reference/datasets/#tablite.datasets-classes","title":"Classes","text":""},{"location":"reference/datasets/#tablite.datasets-functions","title":"Functions","text":""},{"location":"reference/datasets/#tablite.datasets.synthetic_order_data","title":"tablite.datasets.synthetic_order_data(rows=100000)","text":"

    Creates a synthetic dataset for testing that looks like this: (depending on number of rows)

    +=========+=======+=============+===================+=====+===+=====+====+===+=====+=====+===================+==================+\n|    ~    |   #   |      1      |         2         |  3  | 4 |  5  | 6  | 7 |  8  |  9  |         10        |        11        |\n|   row   |  int  |     int     |      datetime     | int |int| int |str |str|mixed|mixed|       float       |      float       |\n+---------+-------+-------------+-------------------+-----+---+-----+----+---+-----+-----+-------------------+------------------+\n|0        |      1|1478158906743|2021-10-27 00:00:00|50764|  1|29990|C4-5|APP|21\u00b0  |None | 2.0434376837650046|1.3371665497020444|\n|1        |      2|2271295805011|2021-09-13 00:00:00|50141|  0|10212|C4-5|TAE|None |None |  1.010318612835485| 20.94821610676901|\n|2        |      3|1598726492913|2021-08-19 00:00:00|50527|  0|19416|C3-5|QPV|21\u00b0  |None |  1.463459515469516|  17.4133659842749|\n|3        |      4|1413615572689|2021-11-05 00:00:00|50181|  1|18637|C4-2|GCL|6\u00b0   |ABC  |  2.084002469706324| 0.489481411683505|\n|4        |      5| 245266998048|2021-09-25 00:00:00|50378|  0|29756|C5-4|LGY|6\u00b0   |XYZ  | 0.5141579343276079| 8.550780816571438|\n|5        |      6| 947994853644|2021-10-14 00:00:00|50511|  0| 7890|C2-4|BET|0\u00b0   |XYZ  | 1.1725893606177542| 7.447314130260951|\n|6        |      7|2230693047809|2021-10-07 00:00:00|50987|  1|26742|C1-3|CFP|0\u00b0   |XYZ  | 1.0921267279498004|11.009210185311993|\n|...      |...    |...          |...                |...  |...|...  |... |...|...  |...  |...                |...               |\n|7,999,993|7999994|2047223556745|2021-09-03 00:00:00|50883|  1|15687|C3-1|RFR|None |XYZ  | 1.3467185981566827|17.023443485654845|\n|7,999,994|7999995|1814140654790|2021-08-02 00:00:00|50152|  0|16556|C4-2|WTC|None |ABC  | 1.1517593924478968| 8.201818634721487|\n|7,999,995|7999996| 155308171103|2021-10-14 00:00:00|50008|  1|14590|C1-3|WYM|0\u00b0   |None | 2.1273836233717978|23.295943554889195|\n|7,999,996|7999997|1620451532911|2021-12-12 00:00:00|50173|  1|20744|C2-1|ZYO|6\u00b0   |ABC  |  2.482509134693724| 22.25375464857266|\n|7,999,997|7999998|1248987682094|2021-12-20 00:00:00|50052|  1|28298|C5-4|XAW|None |XYZ  |0.17923757926558143|23.728160892974252|\n|7,999,998|7999999|1382206732187|2021-11-13 00:00:00|50993|  1|24832|C5-2|UDL|None |ABC  |0.08425329763360942|12.707735293126758|\n|7,999,999|8000000| 600688069780|2021-09-28 00:00:00|50510|  0|15819|C3-4|IGY|None |ABC  |  1.066241687256579|13.862069804070295|\n+=========+=======+=============+===================+=====+===+=====+====+===+=====+=====+===================+==================+\n
    PARAMETER DESCRIPTION rows

    number of rows wanted. Defaults to 100_000.

    TYPE: int DEFAULT: 100000

    RETURNS DESCRIPTION Table

    Populated table.

    TYPE: Table

    Source code in tablite/datasets.py
    def synthetic_order_data(rows=100_000):\n    \"\"\"Creates a synthetic dataset for testing that looks like this:\n    (depending on number of rows)\n\n    ```\n    +=========+=======+=============+===================+=====+===+=====+====+===+=====+=====+===================+==================+\n    |    ~    |   #   |      1      |         2         |  3  | 4 |  5  | 6  | 7 |  8  |  9  |         10        |        11        |\n    |   row   |  int  |     int     |      datetime     | int |int| int |str |str|mixed|mixed|       float       |      float       |\n    +---------+-------+-------------+-------------------+-----+---+-----+----+---+-----+-----+-------------------+------------------+\n    |0        |      1|1478158906743|2021-10-27 00:00:00|50764|  1|29990|C4-5|APP|21\u00b0  |None | 2.0434376837650046|1.3371665497020444|\n    |1        |      2|2271295805011|2021-09-13 00:00:00|50141|  0|10212|C4-5|TAE|None |None |  1.010318612835485| 20.94821610676901|\n    |2        |      3|1598726492913|2021-08-19 00:00:00|50527|  0|19416|C3-5|QPV|21\u00b0  |None |  1.463459515469516|  17.4133659842749|\n    |3        |      4|1413615572689|2021-11-05 00:00:00|50181|  1|18637|C4-2|GCL|6\u00b0   |ABC  |  2.084002469706324| 0.489481411683505|\n    |4        |      5| 245266998048|2021-09-25 00:00:00|50378|  0|29756|C5-4|LGY|6\u00b0   |XYZ  | 0.5141579343276079| 8.550780816571438|\n    |5        |      6| 947994853644|2021-10-14 00:00:00|50511|  0| 7890|C2-4|BET|0\u00b0   |XYZ  | 1.1725893606177542| 7.447314130260951|\n    |6        |      7|2230693047809|2021-10-07 00:00:00|50987|  1|26742|C1-3|CFP|0\u00b0   |XYZ  | 1.0921267279498004|11.009210185311993|\n    |...      |...    |...          |...                |...  |...|...  |... |...|...  |...  |...                |...               |\n    |7,999,993|7999994|2047223556745|2021-09-03 00:00:00|50883|  1|15687|C3-1|RFR|None |XYZ  | 1.3467185981566827|17.023443485654845|\n    |7,999,994|7999995|1814140654790|2021-08-02 00:00:00|50152|  0|16556|C4-2|WTC|None |ABC  | 1.1517593924478968| 8.201818634721487|\n    |7,999,995|7999996| 155308171103|2021-10-14 00:00:00|50008|  1|14590|C1-3|WYM|0\u00b0   |None | 2.1273836233717978|23.295943554889195|\n    |7,999,996|7999997|1620451532911|2021-12-12 00:00:00|50173|  1|20744|C2-1|ZYO|6\u00b0   |ABC  |  2.482509134693724| 22.25375464857266|\n    |7,999,997|7999998|1248987682094|2021-12-20 00:00:00|50052|  1|28298|C5-4|XAW|None |XYZ  |0.17923757926558143|23.728160892974252|\n    |7,999,998|7999999|1382206732187|2021-11-13 00:00:00|50993|  1|24832|C5-2|UDL|None |ABC  |0.08425329763360942|12.707735293126758|\n    |7,999,999|8000000| 600688069780|2021-09-28 00:00:00|50510|  0|15819|C3-4|IGY|None |ABC  |  1.066241687256579|13.862069804070295|\n    +=========+=======+=============+===================+=====+===+=====+====+===+=====+=====+===================+==================+\n    ```\n\n    Args:\n        rows (int, optional): number of rows wanted. Defaults to 100_000.\n\n    Returns:\n        Table (Table): Populated table.\n    \"\"\"  # noqa\n    rows = int(rows)\n\n    L1 = [\"None\", \"0\u00b0\", \"6\u00b0\", \"21\u00b0\"]\n    L2 = [\"ABC\", \"XYZ\", \"\"]\n\n    t = Table()\n    assert isinstance(t, Table)\n    for page_n in range(math.ceil(rows / Config.PAGE_SIZE)):  # n pages\n        start = (page_n * Config.PAGE_SIZE)\n        end = min(start + Config.PAGE_SIZE, rows)\n        ro = range(start, end)\n\n        t2 = Table()\n        t2[\"#\"] = [v+1 for v in ro]\n        # 1 - mock orderid\n        t2[\"1\"] = [random.randint(18_778_628_504, 2277_772_117_504) for i in ro]\n        # 2 - mock delivery date.\n        t2[\"2\"] = [datetime.fromordinal(random.randint(738000, 738150)).isoformat() for i in ro]\n        # 3 - mock store id.\n        t2[\"3\"] = [random.randint(50000, 51000) for _ in ro]\n        # 4 - random bit.\n        t2[\"4\"] = [random.randint(0, 1) for _ in ro]\n        # 5 - mock product id\n        t2[\"5\"] = [random.randint(3000, 30000) for _ in ro]\n        # 6 - random weird string\n        t2[\"6\"] = [f\"C{random.randint(1, 5)}-{random.randint(1, 5)}\" for _ in ro]\n        # 7 - # random category\n        t2[\"7\"] = [\"\".join(random.choice(ascii_uppercase) for _ in range(3)) for _ in ro]\n        # 8 -random temperature group.\n        t2[\"8\"] = [random.choice(L1) for _ in ro]\n        # 9 - random choice of category\n        t2[\"9\"] = [random.choice(L2) for _ in ro]\n        # 10 - volume?\n        t2[\"10\"] = [random.uniform(0.01, 2.5) for _ in ro]\n        # 11 - units?\n        t2[\"11\"] = [f\"{random.uniform(0.1, 25)}\" for _ in ro]\n\n        if len(t) == 0:\n            t = t2\n        else:\n            t += t2\n\n    return t\n
    "},{"location":"reference/datatypes/","title":"Datatypes","text":""},{"location":"reference/datatypes/#tablite.datatypes","title":"tablite.datatypes","text":""},{"location":"reference/datatypes/#tablite.datatypes-attributes","title":"Attributes","text":""},{"location":"reference/datatypes/#tablite.datatypes.matched_types","title":"tablite.datatypes.matched_types = {int: DataTypes._infer_int, str: DataTypes._infer_str, float: DataTypes._infer_float, bool: DataTypes._infer_bool, date: DataTypes._infer_date, datetime: DataTypes._infer_datetime, time: DataTypes._infer_time} module-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes-classes","title":"Classes","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes","title":"tablite.datatypes.DataTypes","text":"

    Bases: object

    DataTypes is the conversion library for all datatypes.

    It supports any / all python datatypes.

    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes-attributes","title":"Attributes","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.int","title":"tablite.datatypes.DataTypes.int = int class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.str","title":"tablite.datatypes.DataTypes.str = str class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.float","title":"tablite.datatypes.DataTypes.float = float class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.bool","title":"tablite.datatypes.DataTypes.bool = bool class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.date","title":"tablite.datatypes.DataTypes.date = date class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.datetime","title":"tablite.datatypes.DataTypes.datetime = datetime class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.time","title":"tablite.datatypes.DataTypes.time = time class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.timedelta","title":"tablite.datatypes.DataTypes.timedelta = timedelta class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.numeric_types","title":"tablite.datatypes.DataTypes.numeric_types = {int, float, date, time, datetime} class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.epoch","title":"tablite.datatypes.DataTypes.epoch = datetime(2000, 1, 1, 0, 0, 0, 0, timezone.utc) class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.epoch_no_tz","title":"tablite.datatypes.DataTypes.epoch_no_tz = datetime(2000, 1, 1, 0, 0, 0, 0) class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.digits","title":"tablite.datatypes.DataTypes.digits = '1234567890' class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.decimals","title":"tablite.datatypes.DataTypes.decimals = set('1234567890-+eE.') class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.integers","title":"tablite.datatypes.DataTypes.integers = set('1234567890-+') class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.nones","title":"tablite.datatypes.DataTypes.nones = {'null', 'Null', 'NULL', '#N/A', '#n/a', '', 'None', None, np.nan} class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.none_type","title":"tablite.datatypes.DataTypes.none_type = type(None) class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.bytes_functions","title":"tablite.datatypes.DataTypes.bytes_functions = {type(None): b_none, bool: b_bool, int: b_int, float: b_float, str: b_str, bytes: b_bytes, datetime: b_datetime, date: b_date, time: b_time, timedelta: b_timedelta} class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.type_code_functions","title":"tablite.datatypes.DataTypes.type_code_functions = {1: _none, 2: _bool, 3: _int, 4: _float, 5: _str, 6: _bytes, 7: _datetime, 8: _date, 9: _time, 10: _timedelta, 11: _unpickle} class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.pytype_from_type_code","title":"tablite.datatypes.DataTypes.pytype_from_type_code = {1: type(None), 2: bool, 3: int, 4: float, 5: str, 6: bytes, 7: datetime, 8: date, 9: time, 10: timedelta, 11: 'pickled object'} class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.date_formats","title":"tablite.datatypes.DataTypes.date_formats = {'NNNN-NN-NN': lambda x: date(*int(i) for i in x.split('-')), 'NNNN-N-NN': lambda x: date(*int(i) for i in x.split('-')), 'NNNN-NN-N': lambda x: date(*int(i) for i in x.split('-')), 'NNNN-N-N': lambda x: date(*int(i) for i in x.split('-')), 'NN-NN-NNNN': lambda x: date(*[int(i) for i in x.split('-')][::-1]), 'N-NN-NNNN': lambda x: date(*[int(i) for i in x.split('-')][::-1]), 'NN-N-NNNN': lambda x: date(*[int(i) for i in x.split('-')][::-1]), 'N-N-NNNN': lambda x: date(*[int(i) for i in x.split('-')][::-1]), 'NNNN.NN.NN': lambda x: date(*int(i) for i in x.split('.')), 'NNNN.N.NN': lambda x: date(*int(i) for i in x.split('.')), 'NNNN.NN.N': lambda x: date(*int(i) for i in x.split('.')), 'NNNN.N.N': lambda x: date(*int(i) for i in x.split('.')), 'NN.NN.NNNN': lambda x: date(*[int(i) for i in x.split('.')][::-1]), 'N.NN.NNNN': lambda x: date(*[int(i) for i in x.split('.')][::-1]), 'NN.N.NNNN': lambda x: date(*[int(i) for i in x.split('.')][::-1]), 'N.N.NNNN': lambda x: date(*[int(i) for i in x.split('.')][::-1]), 'NNNN/NN/NN': lambda x: date(*int(i) for i in x.split('/')), 'NNNN/N/NN': lambda x: date(*int(i) for i in x.split('/')), 'NNNN/NN/N': lambda x: date(*int(i) for i in x.split('/')), 'NNNN/N/N': lambda x: date(*int(i) for i in x.split('/')), 'NN/NN/NNNN': lambda x: date(*[int(i) for i in x.split('/')][::-1]), 'N/NN/NNNN': lambda x: date(*[int(i) for i in x.split('/')][::-1]), 'NN/N/NNNN': lambda x: date(*[int(i) for i in x.split('/')][::-1]), 'N/N/NNNN': lambda x: date(*[int(i) for i in x.split('/')][::-1]), 'NNNN NN NN': lambda x: date(*int(i) for i in x.split(' ')), 'NNNN N NN': lambda x: date(*int(i) for i in x.split(' ')), 'NNNN NN N': lambda x: date(*int(i) for i in x.split(' ')), 'NNNN N N': lambda x: date(*int(i) for i in x.split(' ')), 'NN NN NNNN': lambda x: date(*[int(i) for i in x.split(' ')][::-1]), 'N N NNNN': lambda x: date(*[int(i) for i in x.split(' ')][::-1]), 'NN N NNNN': lambda x: date(*[int(i) for i in x.split(' ')][::-1]), 'N NN NNNN': lambda x: date(*[int(i) for i in x.split(' ')][::-1]), 'NNNNNNNN': lambda x: date(*(int(x[:4]), int(x[4:6]), int(x[6:])))} class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.datetime_formats","title":"tablite.datatypes.DataTypes.datetime_formats = {'NNNN-NN-NNTNN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x), 'NNNN-NN-NNTNN:NN': lambda x: DataTypes.pattern_to_datetime(x), 'NNNN-NN-NN NN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, T=' '), 'NNNN-NN-NN NN:NN': lambda x: DataTypes.pattern_to_datetime(x, T=' '), 'NNNN/NN/NNTNN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/'), 'NNNN/NN/NNTNN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/'), 'NNNN/NN/NN NN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/', T=' '), 'NNNN/NN/NN NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/', T=' '), 'NNNN NN NNTNN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd=' '), 'NNNN NN NNTNN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd=' '), 'NNNN NN NN NN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd=' ', T=' '), 'NNNN NN NN NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd=' ', T=' '), 'NNNN.NN.NNTNN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='.'), 'NNNN.NN.NNTNN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='.'), 'NNNN.NN.NN NN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='.', T=' '), 'NNNN.NN.NN NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='.', T=' '), 'NN-NN-NNNNTNN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='-', T=' ', day_first=True), 'NN-NN-NNNNTNN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='-', T=' ', day_first=True), 'NN-NN-NNNN NN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='-', T=' ', day_first=True), 'NN-NN-NNNN NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='-', T=' ', day_first=True), 'NN/NN/NNNNTNN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/', day_first=True), 'NN/NN/NNNNTNN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/', day_first=True), 'NN/NN/NNNN NN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/', T=' ', day_first=True), 'NN/NN/NNNN NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/', T=' ', day_first=True), 'NN NN NNNNTNN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/', day_first=True), 'NN NN NNNNTNN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/', day_first=True), 'NN NN NNNN NN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/', day_first=True), 'NN NN NNNN NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/', day_first=True), 'NN.NN.NNNNTNN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='.', day_first=True), 'NN.NN.NNNNTNN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='.', day_first=True), 'NN.NN.NNNN NN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='.', day_first=True), 'NN.NN.NNNN NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='.', day_first=True), 'NNNNNNNNTNNNNNN': lambda x: DataTypes.pattern_to_datetime(x, compact=1), 'NNNNNNNNTNNNN': lambda x: DataTypes.pattern_to_datetime(x, compact=1), 'NNNNNNNNTNN': lambda x: DataTypes.pattern_to_datetime(x, compact=1), 'NNNNNNNNNN': lambda x: DataTypes.pattern_to_datetime(x, compact=2), 'NNNNNNNNNNNN': lambda x: DataTypes.pattern_to_datetime(x, compact=2), 'NNNNNNNNNNNNNN': lambda x: DataTypes.pattern_to_datetime(x, compact=2), 'NNNNNNNNTNN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, compact=3)} class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.types","title":"tablite.datatypes.DataTypes.types = [datetime, date, time, int, bool, float, str] class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes-functions","title":"Functions","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.type_code","title":"tablite.datatypes.DataTypes.type_code(value) classmethod","text":"Source code in tablite/datatypes.py
    @classmethod\ndef type_code(cls, value):\n    if type(value) in cls._type_codes:\n        return cls._type_codes[type(value)]\n    elif hasattr(value, \"dtype\"):\n        dtype = pytype(value)\n        return cls._type_codes[dtype]\n    else:\n        return cls._type_codes[\"pickle\"]\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_none","title":"tablite.datatypes.DataTypes.b_none(v)","text":"Source code in tablite/datatypes.py
    def b_none(v):\n    return b\"None\"\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_bool","title":"tablite.datatypes.DataTypes.b_bool(v)","text":"Source code in tablite/datatypes.py
    def b_bool(v):\n    return bytes(str(v), encoding=\"utf-8\")\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_int","title":"tablite.datatypes.DataTypes.b_int(v)","text":"Source code in tablite/datatypes.py
    def b_int(v):\n    return bytes(str(v), encoding=\"utf-8\")\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_float","title":"tablite.datatypes.DataTypes.b_float(v)","text":"Source code in tablite/datatypes.py
    def b_float(v):\n    return bytes(str(v), encoding=\"utf-8\")\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_str","title":"tablite.datatypes.DataTypes.b_str(v)","text":"Source code in tablite/datatypes.py
    def b_str(v):\n    return v.encode(\"utf-8\")\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_bytes","title":"tablite.datatypes.DataTypes.b_bytes(v)","text":"Source code in tablite/datatypes.py
    def b_bytes(v):\n    return v\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_datetime","title":"tablite.datatypes.DataTypes.b_datetime(v)","text":"Source code in tablite/datatypes.py
    def b_datetime(v):\n    return bytes(v.isoformat(), encoding=\"utf-8\")\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_date","title":"tablite.datatypes.DataTypes.b_date(v)","text":"Source code in tablite/datatypes.py
    def b_date(v):\n    return bytes(v.isoformat(), encoding=\"utf-8\")\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_time","title":"tablite.datatypes.DataTypes.b_time(v)","text":"Source code in tablite/datatypes.py
    def b_time(v):\n    return bytes(v.isoformat(), encoding=\"utf-8\")\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_timedelta","title":"tablite.datatypes.DataTypes.b_timedelta(v)","text":"Source code in tablite/datatypes.py
    def b_timedelta(v):\n    return bytes(str(float(v.days + (v.seconds / (24 * 60 * 60)))), \"utf-8\")\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_pickle","title":"tablite.datatypes.DataTypes.b_pickle(v)","text":"Source code in tablite/datatypes.py
    def b_pickle(v):\n    return pickle.dumps(v, protocol=0)\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.to_bytes","title":"tablite.datatypes.DataTypes.to_bytes(v) classmethod","text":"Source code in tablite/datatypes.py
    @classmethod\ndef to_bytes(cls, v):\n    if type(v) in cls.bytes_functions:  # it's a python native type\n        f = cls.bytes_functions[type(v)]\n    elif hasattr(v, \"dtype\"):  # it's a numpy/c type.\n        dtype = pytype(v)\n        f = cls.bytes_functions[dtype]\n    else:\n        f = cls.b_pickle\n    return f(v)\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.from_type_code","title":"tablite.datatypes.DataTypes.from_type_code(value, code) classmethod","text":"Source code in tablite/datatypes.py
    @classmethod\ndef from_type_code(cls, value, code):\n    f = cls.type_code_functions[code]\n    return f(value)\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.pattern_to_datetime","title":"tablite.datatypes.DataTypes.pattern_to_datetime(iso_string, ymd=None, T=None, compact=0, day_first=False) staticmethod","text":"Source code in tablite/datatypes.py
    @staticmethod\ndef pattern_to_datetime(iso_string, ymd=None, T=None, compact=0, day_first=False):\n    assert isinstance(iso_string, str)\n    if compact:\n        s = iso_string\n        if compact == 1:  # has T\n            slices = [\n                (0, 4, \"-\"),\n                (4, 6, \"-\"),\n                (6, 8, \"T\"),\n                (9, 11, \":\"),\n                (11, 13, \":\"),\n                (13, len(s), \"\"),\n            ]\n        elif compact == 2:  # has no T.\n            slices = [\n                (0, 4, \"-\"),\n                (4, 6, \"-\"),\n                (6, 8, \"T\"),\n                (8, 10, \":\"),\n                (10, 12, \":\"),\n                (12, len(s), \"\"),\n            ]\n        elif compact == 3:  # has T and :\n            slices = [\n                (0, 4, \"-\"),\n                (4, 6, \"-\"),\n                (6, 8, \"T\"),\n                (9, 11, \":\"),\n                (12, 14, \":\"),\n                (15, len(s), \"\"),\n            ]\n        else:\n            raise TypeError\n        iso_string = \"\".join([s[a:b] + c for a, b, c in slices if b <= len(s)])\n        iso_string = iso_string.rstrip(\":\")\n\n    if day_first:\n        s = iso_string\n        iso_string = \"\".join((s[6:10], \"-\", s[3:5], \"-\", s[0:2], s[10:]))\n\n    if \",\" in iso_string:\n        iso_string = iso_string.replace(\",\", \".\")\n\n    dot = iso_string[::-1].find(\".\")\n    if 0 < dot < 10:\n        ix = len(iso_string) - dot\n        microsecond = int(float(f\"0{iso_string[ix - 1:]}\") * 10**6)\n        # fmt:off\n        iso_string = iso_string[: len(iso_string) - dot] + str(microsecond).rjust(6, \"0\")\n        # fmt:on\n    if ymd:\n        iso_string = iso_string.replace(ymd, \"-\", 2)\n    if T:\n        iso_string = iso_string.replace(T, \"T\")\n    return datetime.fromisoformat(iso_string)\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.round","title":"tablite.datatypes.DataTypes.round(value, multiple, up=None) classmethod","text":"

    a nicer way to round numbers.

    PARAMETER DESCRIPTION value

    value to be rounded

    TYPE: (float, integer, datetime)

    multiple

    value to be used as the based of rounding. 1) multiple = 1 is the same as rounding to whole integers. 2) multiple = 0.001 is the same as rounding to 3 digits precision. 3) mulitple = 3.1415 is rounding to nearest multiplier of 3.1415 4) value = datetime(2022,8,18,11,14,53,440) 5) multiple = timedelta(hours=0.5) 6) xround(value,multiple) is datetime(2022,8,18,11,0)

    TYPE: (float, integer, timedelta)

    up

    None (default) or boolean rounds half, up or down. round(1.6, 1) rounds to 2. round(1.4, 1) rounds to 1. round(1.5, 1, up=True) rounds to 2. round(1.5, 1, up=False) rounds to 1.

    TYPE: (None, bool) DEFAULT: None

    RETURNS DESCRIPTION

    float,integer,datetime: rounded value in same type as input.

    Source code in tablite/datatypes.py
    @classmethod\ndef round(cls, value, multiple, up=None):\n    \"\"\"a nicer way to round numbers.\n\n    Args:\n        value (float,integer,datetime): value to be rounded\n\n        multiple (float,integer,timedelta): value to be used as the based of rounding.\n            1) multiple = 1 is the same as rounding to whole integers.\n            2) multiple = 0.001 is the same as rounding to 3 digits precision.\n            3) mulitple = 3.1415 is rounding to nearest multiplier of 3.1415\n            4) value = datetime(2022,8,18,11,14,53,440)\n            5) multiple = timedelta(hours=0.5)\n            6) xround(value,multiple) is datetime(2022,8,18,11,0)\n\n        up (None, bool, optional):\n            None (default) or boolean rounds half, up or down.\n            round(1.6, 1) rounds to 2.\n            round(1.4, 1) rounds to 1.\n            round(1.5, 1, up=True) rounds to 2.\n            round(1.5, 1, up=False) rounds to 1.\n\n    Returns:\n        float,integer,datetime: rounded value in same type as input.\n    \"\"\"\n    epoch = 0\n    if isinstance(value, (datetime)) and isinstance(multiple, timedelta):\n        if value.tzinfo is None:\n            epoch = cls.epoch_no_tz\n        else:\n            epoch = cls.epoch\n\n    value2 = value - epoch\n    if value2 == 0:\n        return value2\n\n    low = (value2 // multiple) * multiple\n    high = low + multiple\n    if up is True:\n        return high + epoch\n    elif up is False:\n        return low + epoch\n    else:\n        if abs((high + epoch) - value) < abs(value - (low + epoch)):\n            return high + epoch\n        else:\n            return low + epoch\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.to_json","title":"tablite.datatypes.DataTypes.to_json(v) staticmethod","text":"

    converts any python type to json.

    PARAMETER DESCRIPTION v

    value to convert to json

    TYPE: any

    RETURNS DESCRIPTION

    json compatible value from v

    Source code in tablite/datatypes.py
    @staticmethod\ndef to_json(v):\n    \"\"\"converts any python type to json.\n\n    Args:\n        v (any): value to convert to json\n\n    Returns:\n        json compatible value from v\n    \"\"\"\n    if hasattr(v, \"dtype\"):\n        v = numpy_to_python(v)\n    if v is None:\n        return v\n    elif v is False:\n        # using isinstance(v, bool): won't work as False also is int of zero.\n        return str(v)\n    elif v is True:\n        return str(v)\n    elif isinstance(v, int):\n        return v\n    elif isinstance(v, str):\n        return v\n    elif isinstance(v, float):\n        return v\n    elif isinstance(v, datetime):\n        return v.isoformat()\n    elif isinstance(v, time):\n        return v.isoformat()\n    elif isinstance(v, date):\n        return v.isoformat()\n    elif isinstance(v, timedelta):\n        return f\"P{v.days}DT{v.seconds + (v.microseconds / 1e6)}S\"\n    else:\n        raise TypeError(f\"The datatype {type(v)} is not supported.\")\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.from_json","title":"tablite.datatypes.DataTypes.from_json(v, dtype) staticmethod","text":"

    converts json to python datatype

    PARAMETER DESCRIPTION v

    value

    TYPE: any

    dtype

    any python type

    TYPE: python type

    RETURNS DESCRIPTION

    python type of value v

    Source code in tablite/datatypes.py
    @staticmethod\ndef from_json(v, dtype):\n    \"\"\"converts json to python datatype\n\n    Args:\n        v (any): value\n        dtype (python type): any python type\n\n    Returns:\n        python type of value v\n    \"\"\"\n    if v in DataTypes.nones:\n        if dtype is str and v == \"\":\n            return \"\"\n        else:\n            return None\n    if dtype is int:\n        return int(v)\n    elif dtype is str:\n        return str(v)\n    elif dtype is float:\n        return float(v)\n    elif dtype is bool:\n        if v == \"False\":\n            return False\n        elif v == \"True\":\n            return True\n        else:\n            raise ValueError(v)\n    elif dtype is date:\n        return date.fromisoformat(v)\n    elif dtype is datetime:\n        return datetime.fromisoformat(v)\n    elif dtype is time:\n        return time.fromisoformat(v)\n    elif dtype is timedelta:\n        L = v.split(\"DT\")\n        days = int(L[0].lstrip(\"P\"))\n        seconds = float(L[1].rstrip(\"S\"))\n        return timedelta(days, seconds)\n    else:\n        raise TypeError(f\"The datatype {str(dtype)} is not supported.\")\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.guess_types","title":"tablite.datatypes.DataTypes.guess_types(*values) staticmethod","text":"

    Attempts to guess the datatype for *values returns dict with matching datatypes and probabilities

    RETURNS DESCRIPTION dict

    {key: type, value: probability}

    Source code in tablite/datatypes.py
    @staticmethod\ndef guess_types(*values):\n    \"\"\"Attempts to guess the datatype for *values\n    returns dict with matching datatypes and probabilities\n\n    Returns:\n        dict: {key: type, value: probability}\n    \"\"\"\n    d = defaultdict(int)\n    probability = Rank(DataTypes.types[:])\n\n    for value in values:\n        if hasattr(value, \"dtype\"):\n            value = numpy_to_python(value)\n\n        for dtype in probability:\n            try:\n                _ = DataTypes.infer(value, dtype)\n                d[dtype] += 1\n                probability.match(dtype)\n                break\n            except (ValueError, TypeError):\n                pass\n    if not d:\n        d[str] = len(values)\n    return {k: round(v / len(values), 3) for k, v in d.items()}\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.guess","title":"tablite.datatypes.DataTypes.guess(*values) staticmethod","text":"

    Makes a best guess the datatype for *values returns list of native python values

    RETURNS DESCRIPTION list

    list of native python values

    Source code in tablite/datatypes.py
    @staticmethod\ndef guess(*values):\n    \"\"\"Makes a best guess the datatype for *values\n    returns list of native python values\n\n    Returns:\n        list: list of native python values\n    \"\"\"\n    probability = Rank(*DataTypes.types[:])\n    matches = [None for _ in values[0]]\n\n    for ix, value in enumerate(values[0]):\n        if hasattr(value, \"dtype\"):\n            value = numpy_to_python(value)\n        for dtype in probability:\n            try:\n                matches[ix] = DataTypes.infer(value, dtype)\n                probability.match(dtype)\n                break\n            except (ValueError, TypeError):\n                pass\n    return matches\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.infer","title":"tablite.datatypes.DataTypes.infer(v, dtype) classmethod","text":"Source code in tablite/datatypes.py
    @classmethod\ndef infer(cls, v, dtype):\n    if isinstance(v, str) and dtype == str:\n        # we got a string, we're trying to infer it to string, we shouldn't check for None-ness\n        return v\n\n    if v in DataTypes.nones:\n        return None\n\n    if dtype not in matched_types:\n        raise TypeError(f\"The datatype {str(dtype)} is not supported.\")\n\n    return matched_types[dtype](v)\n
    "},{"location":"reference/datatypes/#tablite.datatypes.Rank","title":"tablite.datatypes.Rank(*items)","text":"

    Bases: object

    Source code in tablite/datatypes.py
    def __init__(self, *items):\n    self.items = {i: ix for i, ix in zip(items, range(len(items)))}\n    self.ranks = [0 for _ in items]\n    self.items_list = [i for i in items]\n
    "},{"location":"reference/datatypes/#tablite.datatypes.Rank-attributes","title":"Attributes","text":""},{"location":"reference/datatypes/#tablite.datatypes.Rank.items","title":"tablite.datatypes.Rank.items = {i: ixfor (i, ix) in zip(items, range(len(items)))} instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.Rank.ranks","title":"tablite.datatypes.Rank.ranks = [0 for _ in items] instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.Rank.items_list","title":"tablite.datatypes.Rank.items_list = [i for i in items] instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.Rank-functions","title":"Functions","text":""},{"location":"reference/datatypes/#tablite.datatypes.Rank.match","title":"tablite.datatypes.Rank.match(k)","text":"Source code in tablite/datatypes.py
    def match(self, k):  # k+=1\n    ix = self.items[k]\n    r = self.ranks\n    r[ix] += 1\n\n    if ix > 0:\n        p = self.items_list\n        while (\n            r[ix] > r[ix - 1] and ix > 0\n        ):  # use a simple bubble sort to maintain rank\n            r[ix], r[ix - 1] = r[ix - 1], r[ix]\n            p[ix], p[ix - 1] = p[ix - 1], p[ix]\n            old = p[ix]\n            self.items[old] = ix\n            self.items[k] = ix - 1\n            ix -= 1\n
    "},{"location":"reference/datatypes/#tablite.datatypes.Rank.__iter__","title":"tablite.datatypes.Rank.__iter__()","text":"Source code in tablite/datatypes.py
    def __iter__(self):\n    return iter(self.items_list)\n
    "},{"location":"reference/datatypes/#tablite.datatypes.MetaArray","title":"tablite.datatypes.MetaArray","text":"

    Bases: ndarray

    Array with metadata.

    "},{"location":"reference/datatypes/#tablite.datatypes.MetaArray-functions","title":"Functions","text":""},{"location":"reference/datatypes/#tablite.datatypes.MetaArray.__new__","title":"tablite.datatypes.MetaArray.__new__(array, dtype=None, order=None, **kwargs)","text":"Source code in tablite/datatypes.py
    def __new__(cls, array, dtype=None, order=None, **kwargs):\n    obj = np.asarray(array, dtype=dtype, order=order).view(cls)\n    obj.metadata = kwargs\n    return obj\n
    "},{"location":"reference/datatypes/#tablite.datatypes.MetaArray.__array_finalize__","title":"tablite.datatypes.MetaArray.__array_finalize__(obj)","text":"Source code in tablite/datatypes.py
    def __array_finalize__(self, obj):\n    if obj is None:\n        return\n    self.metadata = getattr(obj, \"metadata\", None)\n
    "},{"location":"reference/datatypes/#tablite.datatypes-functions","title":"Functions","text":""},{"location":"reference/datatypes/#tablite.datatypes.numpy_to_python","title":"tablite.datatypes.numpy_to_python(obj: Any) -> Any","text":"

    Converts numpy types to python types.

    See https://numpy.org/doc/stable/reference/arrays.scalars.html

    PARAMETER DESCRIPTION obj

    A numpy object

    TYPE: Any

    RETURNS DESCRIPTION Any

    python object: A python object

    Source code in tablite/datatypes.py
    def numpy_to_python(obj: Any) -> Any:\n    \"\"\"Converts numpy types to python types.\n\n    See https://numpy.org/doc/stable/reference/arrays.scalars.html\n\n    Args:\n        obj (Any): A numpy object\n\n    Returns:\n        python object: A python object\n    \"\"\"\n    if isinstance(obj, np.generic):\n        return obj.item()\n    return obj\n
    "},{"location":"reference/datatypes/#tablite.datatypes.pytype","title":"tablite.datatypes.pytype(obj)","text":"

    Returns the python type of any object

    PARAMETER DESCRIPTION obj

    any numpy or python object

    TYPE: Any

    RETURNS DESCRIPTION type

    type of obj

    Source code in tablite/datatypes.py
    def pytype(obj):\n    \"\"\"Returns the python type of any object\n\n    Args:\n        obj (Any): any numpy or python object\n\n    Returns:\n        type: type of obj\n    \"\"\"\n    if isinstance(obj, np.generic):\n        return type(obj.item())\n    return type(obj)\n
    "},{"location":"reference/datatypes/#tablite.datatypes.pytype_from_iterable","title":"tablite.datatypes.pytype_from_iterable(iterable: {tuple, list}) -> {np.dtype, dict}","text":"

    helper to make correct np array from python types.

    PARAMETER DESCRIPTION iterable

    values to be converted to numpy array.

    TYPE: (tuple, list)

    RAISES DESCRIPTION NotImplementedError

    if datatype is not supported.

    RETURNS DESCRIPTION {dtype, dict}

    np.dtype: python type of the iterable.

    Source code in tablite/datatypes.py
    def pytype_from_iterable(iterable: {tuple, list}) -> {np.dtype, dict}:\n    \"\"\"helper to make correct np array from python types.\n\n    Args:\n        iterable (tuple,list): values to be converted to numpy array.\n\n    Raises:\n        NotImplementedError: if datatype is not supported.\n\n    Returns:\n        np.dtype: python type of the iterable.\n    \"\"\"\n    py_types = {}\n    if isinstance(iterable, (tuple, list)):\n        type_counter = Counter((pytype(v) for v in iterable))\n\n        for k, v in type_counter.items():\n            py_types[k] = v\n\n        if len(py_types) == 0:\n            np_dtype, py_dtype = object, bool\n        elif len(py_types) == 1:\n            py_dtype = list(py_types.keys())[0]\n            if py_dtype == datetime:\n                np_dtype = np.datetime64\n            elif py_dtype == date:\n                np_dtype = np.datetime64\n            elif py_dtype == timedelta:\n                np_dtype = np.timedelta64\n            else:\n                np_dtype = None\n        else:\n            np_dtype = object\n    elif isinstance(iterable, np.ndarray):\n        if iterable.dtype == object:\n            np_dtype = object\n            py_types = dict(Counter((pytype(v) for v in iterable)))\n        else:\n            np_dtype = iterable.dtype\n            if len(iterable) > 0:\n                py_types = {pytype(iterable[0]): len(iterable)}\n            else:\n                py_types = {pytype(np_dtype.type()): len(iterable)}\n    else:\n        raise NotImplementedError(f\"No handler for {type(iterable)}\")\n\n    return np_dtype, py_types\n
    "},{"location":"reference/datatypes/#tablite.datatypes.list_to_np_array","title":"tablite.datatypes.list_to_np_array(iterable)","text":"

    helper to make correct np array from python types. Example of problem where numpy turns mixed types into strings.

    np.array([4, '5']) np.ndarray(['4', '5'])

    RETURNS DESCRIPTION

    np.array

    datatypes

    Source code in tablite/datatypes.py
    def list_to_np_array(iterable):\n    \"\"\"helper to make correct np array from python types.\n    Example of problem where numpy turns mixed types into strings.\n    >>> np.array([4, '5'])\n    np.ndarray(['4', '5'])\n\n    returns:\n        np.array\n        datatypes\n    \"\"\"\n    np_dtype, py_dtype = pytype_from_iterable(iterable)\n\n    value = MetaArray(iterable, dtype=np_dtype, py_dtype=py_dtype)\n    return value\n
    "},{"location":"reference/datatypes/#tablite.datatypes.np_type_unify","title":"tablite.datatypes.np_type_unify(arrays)","text":"

    unifies numpy types.

    PARAMETER DESCRIPTION arrays

    List of numpy arrays

    TYPE: list

    RETURNS DESCRIPTION

    np.ndarray: numpy array of a single type.

    Source code in tablite/datatypes.py
    def np_type_unify(arrays):\n    \"\"\"unifies numpy types.\n\n    Args:\n        arrays (list): List of numpy arrays\n\n    Returns:\n        np.ndarray: numpy array of a single type.\n    \"\"\"\n    dtypes = {arr.dtype: len(arr) for arr in arrays}\n    if len(dtypes) == 1:\n        dtype, _ = dtypes.popitem()\n    else:\n        for ix, arr in enumerate(arrays):\n            arrays[ix] = np.array(arr, dtype=object)\n        dtype = object\n    return np.concatenate(arrays, dtype=dtype)\n
    "},{"location":"reference/datatypes/#tablite.datatypes.multitype_set","title":"tablite.datatypes.multitype_set(arr)","text":"

    prevents loss of True, False when calling sets.

    python looses values when called returning a set. Example:

    {1, True, 0, False}

    PARAMETER DESCRIPTION arr

    iterable of mixed types.

    TYPE: Iterable

    RETURNS DESCRIPTION

    np.array: with unique values.

    Source code in tablite/datatypes.py
    def multitype_set(arr):\n    \"\"\"prevents loss of True, False when calling sets.\n\n    python looses values when called returning a set. Example:\n    >>> {1, True, 0, False}\n    {0,1}\n\n    Args:\n        arr (Iterable): iterable of mixed types.\n\n    Returns:\n        np.array: with unique values.\n    \"\"\"\n    L = [(type(v), v) for v in arr]\n    L = list(set(L))\n    L = [v for _, v in L]\n    return np.array(L, dtype=object)\n
    "},{"location":"reference/diff/","title":"Diff","text":""},{"location":"reference/diff/#tablite.diff","title":"tablite.diff","text":""},{"location":"reference/diff/#tablite.diff-classes","title":"Classes","text":""},{"location":"reference/diff/#tablite.diff-functions","title":"Functions","text":""},{"location":"reference/diff/#tablite.diff.diff","title":"tablite.diff.diff(T, other, columns=None)","text":"

    compares table self with table other

    PARAMETER DESCRIPTION self

    Table

    TYPE: Table

    other

    Table

    TYPE: Table

    columns

    list of column names to include in comparison. Defaults to None.

    TYPE: List DEFAULT: None

    RETURNS DESCRIPTION Table

    diff of self and other with diff in columns 1st and 2nd.

    Source code in tablite/diff.py
    def diff(T, other, columns=None):\n    \"\"\"compares table self with table other\n\n    Args:\n        self (Table): Table\n        other (Table): Table\n        columns (List, optional): list of column names to include in comparison. Defaults to None.\n\n    Returns:\n        Table: diff of self and other with diff in columns 1st and 2nd.\n    \"\"\"\n    sub_cls_check(T, BaseTable)\n    sub_cls_check(other, BaseTable)\n    if columns is None:\n        columns = [name for name in T.columns if name in other.columns]\n    elif isinstance(columns, list) and all(isinstance(i, str) for i in columns):\n        for name in columns:\n            if name not in T.columns:\n                raise ValueError(f\"column '{name}' not found\")\n            if name not in other.columns:\n                raise ValueError(f\"column '{name}' not found\")\n    else:\n        raise TypeError(\"Expected list of column names\")\n\n    t1 = T[columns]\n    if issubclass(type(t1), BaseTable):\n        t1 = [tuple(r) for r in T.rows]\n    else:\n        t1 = list(T)\n    t2 = other[columns]\n    if issubclass(type(t2), BaseTable):\n        t2 = [tuple(r) for r in other.rows]\n    else:\n        t2 = list(other)\n\n    sm = difflib.SequenceMatcher(None, t1, t2)\n    new = type(T)()\n    first = unique_name(\"1st\", columns)\n    second = unique_name(\"2nd\", columns)\n    new.add_columns(*columns + [first, second])\n\n    news = {n: [] for n in new.columns}  # Cache for Work in progress.\n\n    for opc, t1a, t1b, t2a, t2b in sm.get_opcodes():\n        if opc == \"insert\":\n            for name, col in zip(columns, zip(*t2[t2a:t2b])):\n                news[name].extend(col)\n            news[first] += [\"-\"] * (t2b - t2a)\n            news[second] += [\"+\"] * (t2b - t2a)\n\n        elif opc == \"delete\":\n            for name, col in zip(columns, zip(*t1[t1a:t1b])):\n                news[name].extend(col)\n            news[first] += [\"+\"] * (t1b - t1a)\n            news[second] += [\"-\"] * (t1b - t1a)\n\n        elif opc == \"equal\":\n            for name, col in zip(columns, zip(*t2[t2a:t2b])):\n                news[name].extend(col)\n            news[first] += [\"=\"] * (t2b - t2a)\n            news[second] += [\"=\"] * (t2b - t2a)\n\n        elif opc == \"replace\":\n            for name, col in zip(columns, zip(*t2[t2a:t2b])):\n                news[name].extend(col)\n            news[first] += [\"r\"] * (t2b - t2a)\n            news[second] += [\"r\"] * (t2b - t2a)\n\n        else:\n            pass\n\n        # Clear cache to free up memory.\n        if len(news[first]) > Config.PAGE_SIZE == 0:\n            for name, L in news.items():\n                new[name].extend(np.array(L))\n                L.clear()\n\n    for name, L in news.items():\n        new[name].extend(np.array(L))\n        L.clear()\n    return new\n
    "},{"location":"reference/export_utils/","title":"Export utils","text":""},{"location":"reference/export_utils/#tablite.export_utils","title":"tablite.export_utils","text":""},{"location":"reference/export_utils/#tablite.export_utils-classes","title":"Classes","text":""},{"location":"reference/export_utils/#tablite.export_utils-functions","title":"Functions","text":""},{"location":"reference/export_utils/#tablite.export_utils.to_sql","title":"tablite.export_utils.to_sql(table, name)","text":"

    generates ANSI-92 compliant SQL.

    PARAMETER DESCRIPTION name

    name of SQL table.

    TYPE: str

    Source code in tablite/export_utils.py
    def to_sql(table, name):\n    \"\"\"\n    generates ANSI-92 compliant SQL.\n\n    args:\n        name (str): name of SQL table.\n    \"\"\"\n    sub_cls_check(table, BaseTable)\n    type_check(name, str)\n\n    prefix = name\n    name = \"T1\"\n    create_table = \"\"\"CREATE TABLE {} ({})\"\"\"\n    columns = []\n    for name, col in table.columns.items():\n        dtype = col.types()\n        if len(dtype) == 1:\n            dtype, _ = dtype.popitem()\n            if dtype is int:\n                dtype = \"INTEGER\"\n            elif dtype is float:\n                dtype = \"REAL\"\n            else:\n                dtype = \"TEXT\"\n        else:\n            dtype = \"TEXT\"\n        definition = f\"{name} {dtype}\"\n        columns.append(definition)\n\n    create_table = create_table.format(prefix, \", \".join(columns))\n\n    # return create_table\n    row_inserts = []\n    for row in table.rows:\n        row_inserts.append(str(tuple([i if i is not None else \"NULL\" for i in row])))\n    row_inserts = f\"INSERT INTO {prefix} VALUES \" + \",\".join(row_inserts)\n    return \"begin; {}; {}; commit;\".format(create_table, row_inserts)\n
    "},{"location":"reference/export_utils/#tablite.export_utils.to_pandas","title":"tablite.export_utils.to_pandas(table)","text":"

    returns pandas.DataFrame

    Source code in tablite/export_utils.py
    def to_pandas(table):\n    \"\"\"\n    returns pandas.DataFrame\n    \"\"\"\n    sub_cls_check(table, BaseTable)\n    try:\n        return pd.DataFrame(table.to_dict())  # noqa\n    except ImportError:\n        import pandas as pd  # noqa\n    return pd.DataFrame(table.to_dict())  # noqa\n
    "},{"location":"reference/export_utils/#tablite.export_utils.to_hdf5","title":"tablite.export_utils.to_hdf5(table, path)","text":"

    creates a copy of the table as hdf5

    Note that some loss of type information is to be expected in columns of mixed type:

    t.show(dtype=True) +===+===+=====+=====+====+=====+=====+===================+==========+========+===============+===+=========================+=====+===+ | # | A | B | C | D | E | F | G | H | I | J | K | L | M | O | |row|int|mixed|float|str |mixed| bool| datetime | date | time | timedelta |str| int |float|int| +---+---+-----+-----+----+-----+-----+-------------------+----------+--------+---------------+---+-------------------------+-----+---+ | 0 | -1|None | -1.1| |None |False|2023-06-09 09:12:06|2023-06-09|09:12:06| 1 day, 0:00:00|b |-100000000000000000000000| inf| 11| | 1 | 1| 1| 1.1|1000|1 | True|2023-06-09 09:12:06|2023-06-09|09:12:06|2 days, 0:06:40|\u55e8 | 100000000000000000000000| -inf|-11| +===+===+=====+=====+====+=====+=====+===================+==========+========+===============+===+=========================+=====+===+ t.to_hdf5(filename) t2 = Table.from_hdf5(filename) t2.show(dtype=True) +===+===+=====+=====+=====+=====+=====+===================+===================+========+===============+===+=========================+=====+===+ | # | A | B | C | D | E | F | G | H | I | J | K | L | M | O | |row|int|mixed|float|mixed|mixed| bool| datetime | datetime | time | str |str| int |float|int| +---+---+-----+-----+-----+-----+-----+-------------------+-------------------+--------+---------------+---+-------------------------+-----+---+ | 0 | -1|None | -1.1|None |None |False|2023-06-09 09:12:06|2023-06-09 00:00:00|09:12:06|1 day, 0:00:00 |b |-100000000000000000000000| inf| 11| | 1 | 1| 1| 1.1| 1000| 1| True|2023-06-09 09:12:06|2023-06-09 00:00:00|09:12:06|2 days, 0:06:40|\u55e8 | 100000000000000000000000| -inf|-11| +===+===+=====+=====+=====+=====+=====+===================+===================+========+===============+===+=========================+=====+===+

    Source code in tablite/export_utils.py
    def to_hdf5(table, path):\n    # fmt: off\n    \"\"\"\n    creates a copy of the table as hdf5\n\n    Note that some loss of type information is to be expected in columns of mixed type:\n    >>> t.show(dtype=True)\n    +===+===+=====+=====+====+=====+=====+===================+==========+========+===============+===+=========================+=====+===+\n    | # | A |  B  |  C  | D  |  E  |  F  |         G         |    H     |   I    |       J       | K |            L            |  M  | O |\n    |row|int|mixed|float|str |mixed| bool|      datetime     |   date   |  time  |   timedelta   |str|           int           |float|int|\n    +---+---+-----+-----+----+-----+-----+-------------------+----------+--------+---------------+---+-------------------------+-----+---+\n    | 0 | -1|None | -1.1|    |None |False|2023-06-09 09:12:06|2023-06-09|09:12:06| 1 day, 0:00:00|b  |-100000000000000000000000|  inf| 11|\n    | 1 |  1|    1|  1.1|1000|1    | True|2023-06-09 09:12:06|2023-06-09|09:12:06|2 days, 0:06:40|\u55e8  | 100000000000000000000000| -inf|-11|\n    +===+===+=====+=====+====+=====+=====+===================+==========+========+===============+===+=========================+=====+===+\n    >>> t.to_hdf5(filename)\n    >>> t2 = Table.from_hdf5(filename)\n    >>> t2.show(dtype=True)\n    +===+===+=====+=====+=====+=====+=====+===================+===================+========+===============+===+=========================+=====+===+\n    | # | A |  B  |  C  |  D  |  E  |  F  |         G         |         H         |   I    |       J       | K |            L            |  M  | O |\n    |row|int|mixed|float|mixed|mixed| bool|      datetime     |      datetime     |  time  |      str      |str|           int           |float|int|\n    +---+---+-----+-----+-----+-----+-----+-------------------+-------------------+--------+---------------+---+-------------------------+-----+---+\n    | 0 | -1|None | -1.1|None |None |False|2023-06-09 09:12:06|2023-06-09 00:00:00|09:12:06|1 day, 0:00:00 |b  |-100000000000000000000000|  inf| 11|\n    | 1 |  1|    1|  1.1| 1000|    1| True|2023-06-09 09:12:06|2023-06-09 00:00:00|09:12:06|2 days, 0:06:40|\u55e8  | 100000000000000000000000| -inf|-11|\n    +===+===+=====+=====+=====+=====+=====+===================+===================+========+===============+===+=========================+=====+===+\n    \"\"\"\n    # fmt: in\n    import h5py\n\n    sub_cls_check(table, BaseTable)\n    type_check(path, Path)\n\n    total = f\"{len(table.columns) * len(table):,}\"  # noqa\n    print(f\"writing {total} records to {path}\", end=\"\")\n\n    with h5py.File(path, \"w\") as f:\n        n = 0\n        for name, col in table.items():\n            try:\n                f.create_dataset(name, data=col[:])  # stored in hdf5 as '/name'\n            except TypeError:\n                f.create_dataset(name, data=[str(i) for i in col[:]])  # stored in hdf5 as '/name'\n            n += 1\n    print(\"... done\")\n
    "},{"location":"reference/export_utils/#tablite.export_utils.excel_writer","title":"tablite.export_utils.excel_writer(table, path)","text":"

    writer for excel files.

    This can create xlsx files beyond Excels. If you're using pyexcel to read the data, you'll see the data is there. If you're using Excel, Excel will stop loading after 1,048,576 rows.

    See pyexcel for more details: http://docs.pyexcel.org/

    Source code in tablite/export_utils.py
    def excel_writer(table, path):\n    \"\"\"\n    writer for excel files.\n\n    This can create xlsx files beyond Excels.\n    If you're using pyexcel to read the data, you'll see the data is there.\n    If you're using Excel, Excel will stop loading after 1,048,576 rows.\n\n    See pyexcel for more details:\n    http://docs.pyexcel.org/\n    \"\"\"\n    import pyexcel\n\n    sub_cls_check(table, BaseTable)\n    type_check(path, Path)\n\n    def gen(table):  # local helper\n        yield table.columns\n        for row in table.rows:\n            yield row\n\n    data = list(gen(table))\n    if path.suffix in [\".xls\", \".ods\"]:\n        data = [\n            [str(v) if (isinstance(v, (int, float)) and abs(v) > 2**32 - 1) else DataTypes.to_json(v) for v in row]\n            for row in data\n        ]\n\n    pyexcel.save_as(array=data, dest_file_name=str(path))\n
    "},{"location":"reference/export_utils/#tablite.export_utils.to_json","title":"tablite.export_utils.to_json(table, *args, **kwargs)","text":"Source code in tablite/export_utils.py
    def to_json(table, *args, **kwargs):\n    import json\n\n    sub_cls_check(table, BaseTable)\n    return json.dumps(table.as_json_serializable())\n
    "},{"location":"reference/export_utils/#tablite.export_utils.path_suffix_check","title":"tablite.export_utils.path_suffix_check(path, kind)","text":"Source code in tablite/export_utils.py
    def path_suffix_check(path, kind):\n    if not path.suffix == kind:\n        raise ValueError(f\"Suffix mismatch: Expected {kind}, got {path.suffix} in {path.name}\")\n    if not path.parent.exists():\n        raise FileNotFoundError(f\"directory {path.parent} not found.\")\n
    "},{"location":"reference/export_utils/#tablite.export_utils.text_writer","title":"tablite.export_utils.text_writer(table, path, tqdm=_tqdm)","text":"

    exports table to csv, tsv or txt dependening on path suffix. follows the JSON norm. text escape is ON for all strings.

    "},{"location":"reference/export_utils/#tablite.export_utils.text_writer--note","title":"Note:","text":"

    If the delimiter is present in a string when the string is exported, text-escape is required, as the format otherwise is corrupted. When the file is being written, it is unknown whether any string in a column contrains the delimiter. As text escaping the few strings that may contain the delimiter would lead to an assymmetric format, the safer guess is to text escape all strings.

    Source code in tablite/export_utils.py
    def text_writer(table, path, tqdm=_tqdm):\n    \"\"\"exports table to csv, tsv or txt dependening on path suffix.\n    follows the JSON norm. text escape is ON for all strings.\n\n    Note:\n    ----------------------\n    If the delimiter is present in a string when the string is exported,\n    text-escape is required, as the format otherwise is corrupted.\n    When the file is being written, it is unknown whether any string in\n    a column contrains the delimiter. As text escaping the few strings\n    that may contain the delimiter would lead to an assymmetric format,\n    the safer guess is to text escape all strings.\n    \"\"\"\n    sub_cls_check(table, BaseTable)\n    type_check(path, Path)\n\n    def txt(value):  # helper for text writer\n        if value is None:\n            return \"\"  # A column with 1,None,2 must be \"1,,2\".\n        elif isinstance(value, str):\n            # if not (value.startswith('\"') and value.endswith('\"')):\n            #     return f'\"{value}\"'  # this must be escape: \"the quick fox, jumped over the comma\"\n            # else:\n            return value  # this would for example be an empty string: \"\"\n        else:\n            return str(DataTypes.to_json(value))  # this handles datetimes, timedelta, etc.\n\n    delimiters = {\".csv\": \",\", \".tsv\": \"\\t\", \".txt\": \"|\"}\n    delimiter = delimiters.get(path.suffix)\n\n    with path.open(\"w\", encoding=\"utf-8\") as fo:\n        fo.write(delimiter.join(c for c in table.columns) + \"\\n\")\n        for row in tqdm(table.rows, total=len(table), disable=Config.TQDM_DISABLE):\n            fo.write(delimiter.join(txt(c) for c in row) + \"\\n\")\n
    "},{"location":"reference/export_utils/#tablite.export_utils.sql_writer","title":"tablite.export_utils.sql_writer(table, path)","text":"Source code in tablite/export_utils.py
    def sql_writer(table, path):\n    type_check(table, BaseTable)\n    type_check(path, Path)\n    with path.open(\"w\", encoding=\"utf-8\") as fo:\n        fo.write(to_sql(table))\n
    "},{"location":"reference/export_utils/#tablite.export_utils.json_writer","title":"tablite.export_utils.json_writer(table, path)","text":"Source code in tablite/export_utils.py
    def json_writer(table, path):\n    type_check(table, BaseTable)\n    type_check(path, Path)\n    with path.open(\"w\") as fo:\n        fo.write(to_json(table))\n
    "},{"location":"reference/export_utils/#tablite.export_utils.to_html","title":"tablite.export_utils.to_html(table, path)","text":"Source code in tablite/export_utils.py
    def to_html(table, path):\n    type_check(table, BaseTable)\n    type_check(path, Path)\n    with path.open(\"w\", encoding=\"utf-8\") as fo:\n        fo.write(table._repr_html_(slice(0, len(table))))\n
    "},{"location":"reference/file_reader_utils/","title":"File reader utils","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils","title":"tablite.file_reader_utils","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils-attributes","title":"Attributes","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.ENCODING_GUESS_BYTES","title":"tablite.file_reader_utils.ENCODING_GUESS_BYTES = 10000 module-attribute","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.header_readers","title":"tablite.file_reader_utils.header_readers = {'fods': excel_reader_headers, 'json': excel_reader_headers, 'simple': excel_reader_headers, 'rst': excel_reader_headers, 'mediawiki': excel_reader_headers, 'xlsx': excel_reader_headers, 'xlsm': excel_reader_headers, 'csv': text_reader_headers, 'tsv': text_reader_headers, 'txt': text_reader_headers, 'ods': ods_reader_headers} module-attribute","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils-classes","title":"Classes","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.TextEscape","title":"tablite.file_reader_utils.TextEscape(openings='({[', closures=']})', text_qualifier='\"', delimiter=',', strip_leading_and_tailing_whitespace=False)","text":"

    Bases: object

    enables parsing of CSV with respecting brackets and text marks.

    Example: text_escape = TextEscape() # set up the instance. for line in somefile.readlines(): list_of_words = text_escape(line) # use the instance. ...

    As an example, the Danes and Germans use \" for inches and ' for feet, so we will see data that contains nail (75 x 4 mm, 3\" x 3/12\"), so for this case ( and ) are valid escapes, but \" and ' aren't.

    Source code in tablite/file_reader_utils.py
    def __init__(\n    self,\n    openings=\"({[\",\n    closures=\"]})\",\n    text_qualifier='\"',\n    delimiter=\",\",\n    strip_leading_and_tailing_whitespace=False,\n):\n    \"\"\"\n    As an example, the Danes and Germans use \" for inches and ' for feet,\n    so we will see data that contains nail (75 x 4 mm, 3\" x 3/12\"), so\n    for this case ( and ) are valid escapes, but \" and ' aren't.\n\n    \"\"\"\n    if openings is None:\n        openings = [None]\n    elif isinstance(openings, str):\n        self.openings = {c for c in openings}\n    else:\n        raise TypeError(f\"expected str, got {type(openings)}\")\n\n    if closures is None:\n        closures = [None]\n    elif isinstance(closures, str):\n        self.closures = {c for c in closures}\n    else:\n        raise TypeError(f\"expected str, got {type(closures)}\")\n\n    if not isinstance(delimiter, str):\n        raise TypeError(f\"expected str, got {type(delimiter)}\")\n    self.delimiter = delimiter\n    self._delimiter_length = len(delimiter)\n    self.strip_leading_and_tailing_whitespace = strip_leading_and_tailing_whitespace\n\n    if text_qualifier is None:\n        pass\n    elif text_qualifier in openings + closures:\n        raise ValueError(\"It's a bad idea to have qoute character appears in openings or closures.\")\n    else:\n        self.qoute = text_qualifier\n\n    if not text_qualifier:\n        if not self.strip_leading_and_tailing_whitespace:\n            self.c = self._call_1\n        else:\n            self.c = self._call_2\n    else:\n        self.c = self._call_3\n
    "},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.TextEscape-attributes","title":"Attributes","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.TextEscape.openings","title":"tablite.file_reader_utils.TextEscape.openings = {c for c in openings} instance-attribute","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.TextEscape.closures","title":"tablite.file_reader_utils.TextEscape.closures = {c for c in closures} instance-attribute","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.TextEscape.delimiter","title":"tablite.file_reader_utils.TextEscape.delimiter = delimiter instance-attribute","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.TextEscape.strip_leading_and_tailing_whitespace","title":"tablite.file_reader_utils.TextEscape.strip_leading_and_tailing_whitespace = strip_leading_and_tailing_whitespace instance-attribute","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.TextEscape.qoute","title":"tablite.file_reader_utils.TextEscape.qoute = text_qualifier instance-attribute","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.TextEscape.c","title":"tablite.file_reader_utils.TextEscape.c = self._call_1 instance-attribute","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.TextEscape-functions","title":"Functions","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.TextEscape.__call__","title":"tablite.file_reader_utils.TextEscape.__call__(s)","text":"Source code in tablite/file_reader_utils.py
    def __call__(self, s):\n    return self.c(s)\n
    "},{"location":"reference/file_reader_utils/#tablite.file_reader_utils-functions","title":"Functions","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.split_by_sequence","title":"tablite.file_reader_utils.split_by_sequence(text, sequence)","text":"

    helper to split text according to a split sequence.

    Source code in tablite/file_reader_utils.py
    def split_by_sequence(text, sequence):\n    \"\"\"helper to split text according to a split sequence.\"\"\"\n    chunks = tuple()\n    for element in sequence:\n        idx = text.find(element)\n        if idx < 0:\n            raise ValueError(f\"'{element}' not in row\")\n        chunk, text = text[:idx], text[len(element) + idx :]\n        chunks += (chunk,)\n    chunks += (text,)  # the remaining text.\n    return chunks\n
    "},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.detect_seperator","title":"tablite.file_reader_utils.detect_seperator(text)","text":"

    :param path: pathlib.Path objects :param encoding: file encoding. :return: 1 character.

    Source code in tablite/file_reader_utils.py
    def detect_seperator(text):\n    \"\"\"\n    :param path: pathlib.Path objects\n    :param encoding: file encoding.\n    :return: 1 character.\n    \"\"\"\n    # After reviewing the logic in the CSV sniffer, I concluded that all it\n    # really does is to look for a non-text character. As the separator is\n    # determined by the first line, which almost always is a line of headers,\n    # the text characters will be utf-8,16 or ascii letters plus white space.\n    # This leaves the characters ,;:| and \\t as potential separators, with one\n    # exception: files that use whitespace as separator. My logic is therefore\n    # to (1) find the set of characters that intersect with ',;:|\\t' which in\n    # practice is a single character, unless (2) it is empty whereby it must\n    # be whitespace.\n    if len(text) == 0:\n        return None\n    seps = {\",\", \"\\t\", \";\", \":\", \"|\"}.intersection(text)\n    if not seps:\n        if \" \" in text:\n            return \" \"\n        if \"\\n\" in text:\n            return \"\\n\"\n        else:\n            raise ValueError(\"separator not detected\")\n    if len(seps) == 1:\n        return seps.pop()\n    else:\n        frq = [(text.count(i), i) for i in seps]\n        frq.sort(reverse=True)  # most frequent first.\n        return frq[0][-1]\n
    "},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.text_reader_headers","title":"tablite.file_reader_utils.text_reader_headers(path, delimiter, header_row_index, text_qualifier, linecount)","text":"Source code in tablite/file_reader_utils.py
    def text_reader_headers(path, delimiter, header_row_index, text_qualifier, linecount):\n    d = {}\n    delimiters = {\n        \".csv\": \",\",\n        \".tsv\": \"\\t\",\n        \".txt\": None,\n    }\n\n    try:\n        with path.open(\"rb\") as fi:\n            rawdata = fi.read(ENCODING_GUESS_BYTES)\n            encoding = chardet.detect(rawdata)[\"encoding\"]\n\n        if delimiter is None:\n            with path.open(\"r\", encoding=encoding, errors=\"ignore\") as fi:\n                lines = []\n                for n, line in enumerate(fi, -header_row_index):\n                    if n < 0:\n                        continue\n                    line = line.rstrip(\"\\n\")\n                    lines.append(line)\n                    if n >= linecount:\n                        break  # break on first\n                try:\n                    d[\"delimiter\"] = delimiter = detect_seperator(\"\\n\".join(lines))\n                except ValueError as e:\n                    if e.args == (\"separator not detected\", ):\n                        d[\"delimiter\"] = delimiter = None # this will handle the case of 1 column, 1 row\n                    else:\n                        raise e\n\n        if delimiter is None:\n            d[\"delimiter\"] = delimiter = delimiters[path.suffix]  # pickup the default one\n            d[path.name] = [lines]\n            d[\"is_empty\"] = True  # mark as empty to return an empty table instead of throwing\n        else:\n            kwargs = {}\n\n            if text_qualifier is not None:\n                kwargs[\"text_qualifier\"] = text_qualifier\n                kwargs[\"quoting\"] = \"QUOTE_MINIMAL\"\n            else:\n                kwargs[\"quoting\"] = \"QUOTE_NONE\"\n\n            d[path.name] = _get_headers(\n                str(path), py_to_nim_encoding(encoding), header_row_index=header_row_index,\n                delimiter=delimiter,\n                linecount=linecount,\n                **kwargs\n            )\n        return d\n    except Exception as e:\n        raise ValueError(f\"can't read {path.suffix}\")\n
    "},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.excel_reader_headers","title":"tablite.file_reader_utils.excel_reader_headers(path, delimiter, header_row_index, text_qualifier, linecount)","text":"Source code in tablite/file_reader_utils.py
    def excel_reader_headers(path, delimiter, header_row_index, text_qualifier, linecount):\n    d = {}\n    book = openpyxl.open(str(path), read_only=True)\n\n    try:\n        all_sheets = book.sheetnames\n\n        for sheet_name, sheet in ((name, book[name]) for name in all_sheets):\n            fixup_worksheet(sheet)\n            if sheet.max_row is None:\n                max_rows = 0\n            else:\n                max_rows = min(sheet.max_row, linecount + 1)\n            container = [None] * max_rows\n            padding_ends = 0\n            max_column = sheet.max_column\n\n            for i, row_data in enumerate(sheet.iter_rows(0, header_row_index + max_rows, values_only=True), start=-header_row_index):\n                if i < 0:\n                    # NOTE: for some reason `iter_rows` specifying a start row starts reading cells as binary, instead skip the rows that are before our first read row\n                    continue\n\n                # NOTE: text readers do not cast types and give back strings, neither should xlsx reader, can't find documentation if it's possible to ignore this via `iter_rows` instead of casting back to string\n                container[i] = [DataTypes.to_json(v) for v in row_data]\n\n                for j, cell in enumerate(reversed(row_data)):\n                    if cell is None:\n                        continue\n\n                    padding_ends = max(padding_ends, max_column - j)\n\n                    break\n\n            d[sheet_name] = [None if c is None else c[0:padding_ends] for c in container]\n            d[\"delimiter\"] = None\n    finally:\n        book.close()\n\n    return d\n
    "},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.ods_reader_headers","title":"tablite.file_reader_utils.ods_reader_headers(path, delimiter, header_row_index, text_qualifier, linecount)","text":"Source code in tablite/file_reader_utils.py
    def ods_reader_headers(path, delimiter, header_row_index, text_qualifier, linecount):\n    d = {\n        \"delimiter\": None\n    }\n    sheets = pyexcel.get_book_dict(file_name=str(path))\n\n    for sheet_name, data in sheets.items():\n        lines = [[DataTypes.to_json(v) for v in row] for row in data[header_row_index:header_row_index+linecount]]\n\n        d[sheet_name] = lines\n\n    return d\n
    "},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.get_headers","title":"tablite.file_reader_utils.get_headers(path, delimiter=None, header_row_index=0, text_qualifier=None, linecount=10)","text":"

    file format definition csv comma separated values tsv tab separated values csvz a zip file that contains one or many csv files tsvz a zip file that contains one or many tsv files xls a spreadsheet file format created by MS-Excel 97-2003 xlsx MS-Excel Extensions to the Office Open XML SpreadsheetML File Format. xlsm an MS-Excel Macro-Enabled Workbook file ods open document spreadsheet fods flat open document spreadsheet json java script object notation html html table of the data structure simple simple presentation rst rStructured Text presentation of the data mediawiki media wiki table

    Source code in tablite/file_reader_utils.py
    def get_headers(path, delimiter=None, header_row_index=0, text_qualifier=None, linecount=10):\n    \"\"\"\n    file format\tdefinition\n    csv\t    comma separated values\n    tsv\t    tab separated values\n    csvz\ta zip file that contains one or many csv files\n    tsvz\ta zip file that contains one or many tsv files\n    xls\t    a spreadsheet file format created by MS-Excel 97-2003\n    xlsx\tMS-Excel Extensions to the Office Open XML SpreadsheetML File Format.\n    xlsm\tan MS-Excel Macro-Enabled Workbook file\n    ods\t    open document spreadsheet\n    fods\tflat open document spreadsheet\n    json\tjava script object notation\n    html\thtml table of the data structure\n    simple\tsimple presentation\n    rst\t    rStructured Text presentation of the data\n    mediawiki\tmedia wiki table\n    \"\"\"\n    if isinstance(path, str):\n        path = Path(path)\n    if not isinstance(path, Path):\n        raise TypeError(\"expected pathlib path.\")\n    if not path.exists():\n        raise FileNotFoundError(str(path))\n    if delimiter is not None:\n        if not isinstance(delimiter, str):\n            raise TypeError(f\"expected str or None, not {type(delimiter)}\")\n\n    kwargs = {\n        \"path\": path,\n        \"delimiter\": delimiter,\n        \"header_row_index\": header_row_index,\n        \"text_qualifier\": text_qualifier,\n        \"linecount\": linecount\n   }\n\n    reader = header_readers.get(path.suffix[1:], None)\n\n    if reader is None:\n        raise TypeError(f\"file format for headers not supported: {path.suffix}\")\n\n    result = reader(**kwargs)\n\n    return result\n
    "},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.get_encoding","title":"tablite.file_reader_utils.get_encoding(path, nbytes=ENCODING_GUESS_BYTES)","text":"Source code in tablite/file_reader_utils.py
    def get_encoding(path, nbytes=ENCODING_GUESS_BYTES):\n    nbytes = min(nbytes, path.stat().st_size)\n    with path.open(\"rb\") as fi:\n        rawdata = fi.read(nbytes)\n        encoding = chardet.detect(rawdata)[\"encoding\"]\n        if encoding == \"ascii\":  # utf-8 is backwards compatible with ascii\n            return \"utf-8\"  # --   so should the first 10k chars not be enough,\n        return encoding  # --      the utf-8 encoding will still get it right.\n
    "},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.get_delimiter","title":"tablite.file_reader_utils.get_delimiter(path, encoding)","text":"Source code in tablite/file_reader_utils.py
    def get_delimiter(path, encoding):\n    with path.open(\"r\", encoding=encoding, errors=\"ignore\") as fi:\n        lines = []\n        for n, line in enumerate(fi):\n            line = line.rstrip(\"\\n\")\n            lines.append(line)\n            if n > 10:\n                break  # break on first\n        delimiter = detect_seperator(\"\\n\".join(lines))\n        if delimiter is None:\n            raise ValueError(\"Delimiter could not be determined\")\n        return delimiter\n
    "},{"location":"reference/groupby_utils/","title":"Groupby utils","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils","title":"tablite.groupby_utils","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils-classes","title":"Classes","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupbyFunction","title":"tablite.groupby_utils.GroupbyFunction","text":"

    Bases: object

    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Limit","title":"tablite.groupby_utils.Limit()","text":"

    Bases: GroupbyFunction

    Source code in tablite/groupby_utils.py
    def __init__(self):\n    self.value = None\n    self.f = None\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Limit-attributes","title":"Attributes","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Limit.value","title":"tablite.groupby_utils.Limit.value = None instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Limit.f","title":"tablite.groupby_utils.Limit.f = None instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Limit-functions","title":"Functions","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Limit.update","title":"tablite.groupby_utils.Limit.update(value)","text":"Source code in tablite/groupby_utils.py
    def update(self, value):\n    if value is None:\n        pass\n    elif self.value is None:\n        self.value = value\n    else:\n        self.value = self.f((value, self.value))\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Max","title":"tablite.groupby_utils.Max()","text":"

    Bases: Limit

    Source code in tablite/groupby_utils.py
    def __init__(self):\n    super().__init__()\n    self.f = max\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Max-attributes","title":"Attributes","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Max.value","title":"tablite.groupby_utils.Max.value = None instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Max.f","title":"tablite.groupby_utils.Max.f = max instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Max-functions","title":"Functions","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Max.update","title":"tablite.groupby_utils.Max.update(value)","text":"Source code in tablite/groupby_utils.py
    def update(self, value):\n    if value is None:\n        pass\n    elif self.value is None:\n        self.value = value\n    else:\n        self.value = self.f((value, self.value))\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Min","title":"tablite.groupby_utils.Min()","text":"

    Bases: Limit

    Source code in tablite/groupby_utils.py
    def __init__(self):\n    super().__init__()\n    self.f = min\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Min-attributes","title":"Attributes","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Min.value","title":"tablite.groupby_utils.Min.value = None instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Min.f","title":"tablite.groupby_utils.Min.f = min instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Min-functions","title":"Functions","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Min.update","title":"tablite.groupby_utils.Min.update(value)","text":"Source code in tablite/groupby_utils.py
    def update(self, value):\n    if value is None:\n        pass\n    elif self.value is None:\n        self.value = value\n    else:\n        self.value = self.f((value, self.value))\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Sum","title":"tablite.groupby_utils.Sum()","text":"

    Bases: GroupbyFunction

    Source code in tablite/groupby_utils.py
    def __init__(self):\n    self.value = 0\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Sum-attributes","title":"Attributes","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Sum.value","title":"tablite.groupby_utils.Sum.value = 0 instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Sum-functions","title":"Functions","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Sum.update","title":"tablite.groupby_utils.Sum.update(value)","text":"Source code in tablite/groupby_utils.py
    def update(self, value):\n    if isinstance(value, (type(None), date, time, datetime, str)):\n        raise ValueError(f\"Sum of {type(value)} doesn't make sense.\")\n    self.value += value\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Product","title":"tablite.groupby_utils.Product()","text":"

    Bases: GroupbyFunction

    Source code in tablite/groupby_utils.py
    def __init__(self) -> None:\n    self.value = 1\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Product-attributes","title":"Attributes","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Product.value","title":"tablite.groupby_utils.Product.value = 1 instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Product-functions","title":"Functions","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Product.update","title":"tablite.groupby_utils.Product.update(value)","text":"Source code in tablite/groupby_utils.py
    def update(self, value):\n    self.value *= value\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.First","title":"tablite.groupby_utils.First()","text":"

    Bases: GroupbyFunction

    Source code in tablite/groupby_utils.py
    def __init__(self):\n    self.value = self.empty\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.First-attributes","title":"Attributes","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.First.empty","title":"tablite.groupby_utils.First.empty = (None) class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.First.value","title":"tablite.groupby_utils.First.value = self.empty instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.First-functions","title":"Functions","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.First.update","title":"tablite.groupby_utils.First.update(value)","text":"Source code in tablite/groupby_utils.py
    def update(self, value):\n    if self.value is First.empty:\n        self.value = value\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Last","title":"tablite.groupby_utils.Last()","text":"

    Bases: GroupbyFunction

    Source code in tablite/groupby_utils.py
    def __init__(self):\n    self.value = None\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Last-attributes","title":"Attributes","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Last.value","title":"tablite.groupby_utils.Last.value = None instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Last-functions","title":"Functions","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Last.update","title":"tablite.groupby_utils.Last.update(value)","text":"Source code in tablite/groupby_utils.py
    def update(self, value):\n    self.value = value\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Count","title":"tablite.groupby_utils.Count()","text":"

    Bases: GroupbyFunction

    Source code in tablite/groupby_utils.py
    def __init__(self):\n    self.value = 0\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Count-attributes","title":"Attributes","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Count.value","title":"tablite.groupby_utils.Count.value = 0 instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Count-functions","title":"Functions","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Count.update","title":"tablite.groupby_utils.Count.update(value)","text":"Source code in tablite/groupby_utils.py
    def update(self, value):\n    self.value += 1\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.CountUnique","title":"tablite.groupby_utils.CountUnique()","text":"

    Bases: GroupbyFunction

    Source code in tablite/groupby_utils.py
    def __init__(self):\n    self.items = set()\n    self.value = None\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.CountUnique-attributes","title":"Attributes","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.CountUnique.items","title":"tablite.groupby_utils.CountUnique.items = set() instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.CountUnique.value","title":"tablite.groupby_utils.CountUnique.value = None instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.CountUnique-functions","title":"Functions","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.CountUnique.update","title":"tablite.groupby_utils.CountUnique.update(value)","text":"Source code in tablite/groupby_utils.py
    def update(self, value):\n    self.items.add(value)\n    self.value = len(self.items)\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Average","title":"tablite.groupby_utils.Average()","text":"

    Bases: GroupbyFunction

    Source code in tablite/groupby_utils.py
    def __init__(self):\n    self.sum = 0\n    self.count = 0\n    self.value = 0\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Average-attributes","title":"Attributes","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Average.sum","title":"tablite.groupby_utils.Average.sum = 0 instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Average.count","title":"tablite.groupby_utils.Average.count = 0 instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Average.value","title":"tablite.groupby_utils.Average.value = 0 instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Average-functions","title":"Functions","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Average.update","title":"tablite.groupby_utils.Average.update(value)","text":"Source code in tablite/groupby_utils.py
    def update(self, value):\n    if isinstance(value, (date, time, datetime, str)):\n        raise ValueError(f\"Sum of {type(value)} doesn't make sense.\")\n    if value is not None:\n        self.sum += value\n        self.count += 1\n        self.value = self.sum / self.count\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.StandardDeviation","title":"tablite.groupby_utils.StandardDeviation()","text":"

    Bases: GroupbyFunction

    Uses J.P. Welfords (1962) algorithm. For details see https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm

    Source code in tablite/groupby_utils.py
    def __init__(self):\n    self.count = 0\n    self.mean = 0\n    self.c = 0.0\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.StandardDeviation-attributes","title":"Attributes","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.StandardDeviation.count","title":"tablite.groupby_utils.StandardDeviation.count = 0 instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.StandardDeviation.mean","title":"tablite.groupby_utils.StandardDeviation.mean = 0 instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.StandardDeviation.c","title":"tablite.groupby_utils.StandardDeviation.c = 0.0 instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.StandardDeviation.value","title":"tablite.groupby_utils.StandardDeviation.value property","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.StandardDeviation-functions","title":"Functions","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.StandardDeviation.update","title":"tablite.groupby_utils.StandardDeviation.update(value)","text":"Source code in tablite/groupby_utils.py
    def update(self, value):\n    if isinstance(value, (date, time, datetime, str)):\n        raise ValueError(f\"Std.dev. of {type(value)} doesn't make sense.\")\n    if value is not None:\n        self.count += 1\n        dt = value - self.mean\n        self.mean += dt / self.count\n        self.c += dt * (value - self.mean)\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Histogram","title":"tablite.groupby_utils.Histogram()","text":"

    Bases: GroupbyFunction

    Source code in tablite/groupby_utils.py
    def __init__(self):\n    self.hist = defaultdict(int)\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Histogram-attributes","title":"Attributes","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Histogram.hist","title":"tablite.groupby_utils.Histogram.hist = defaultdict(int) instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Histogram-functions","title":"Functions","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Histogram.update","title":"tablite.groupby_utils.Histogram.update(value)","text":"Source code in tablite/groupby_utils.py
    def update(self, value):\n    self.hist[value] += 1\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Median","title":"tablite.groupby_utils.Median()","text":"

    Bases: Histogram

    Source code in tablite/groupby_utils.py
    def __init__(self):\n    super().__init__()\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Median-attributes","title":"Attributes","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Median.hist","title":"tablite.groupby_utils.Median.hist = defaultdict(int) instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Median.value","title":"tablite.groupby_utils.Median.value property","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Median-functions","title":"Functions","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Median.update","title":"tablite.groupby_utils.Median.update(value)","text":"Source code in tablite/groupby_utils.py
    def update(self, value):\n    self.hist[value] += 1\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Mode","title":"tablite.groupby_utils.Mode()","text":"

    Bases: Histogram

    Source code in tablite/groupby_utils.py
    def __init__(self):\n    super().__init__()\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.Mode-attributes","title":"Attributes","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Mode.hist","title":"tablite.groupby_utils.Mode.hist = defaultdict(int) instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Mode.value","title":"tablite.groupby_utils.Mode.value property","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Mode-functions","title":"Functions","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.Mode.update","title":"tablite.groupby_utils.Mode.update(value)","text":"Source code in tablite/groupby_utils.py
    def update(self, value):\n    self.hist[value] += 1\n
    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy","title":"tablite.groupby_utils.GroupBy","text":"

    Bases: object

    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy-attributes","title":"Attributes","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.max","title":"tablite.groupby_utils.GroupBy.max = Max class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.min","title":"tablite.groupby_utils.GroupBy.min = Min class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.sum","title":"tablite.groupby_utils.GroupBy.sum = Sum class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.product","title":"tablite.groupby_utils.GroupBy.product = Product class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.first","title":"tablite.groupby_utils.GroupBy.first = First class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.last","title":"tablite.groupby_utils.GroupBy.last = Last class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.count","title":"tablite.groupby_utils.GroupBy.count = Count class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.count_unique","title":"tablite.groupby_utils.GroupBy.count_unique = CountUnique class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.avg","title":"tablite.groupby_utils.GroupBy.avg = Average class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.stdev","title":"tablite.groupby_utils.GroupBy.stdev = StandardDeviation class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.median","title":"tablite.groupby_utils.GroupBy.median = Median class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.mode","title":"tablite.groupby_utils.GroupBy.mode = Mode class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.functions","title":"tablite.groupby_utils.GroupBy.functions = [Max, Min, Sum, First, Last, Product, Count, CountUnique, Average, StandardDeviation, Median, Mode] class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.function_names","title":"tablite.groupby_utils.GroupBy.function_names = {f.__name__: ffor f in functions} class-attribute instance-attribute","text":""},{"location":"reference/groupbys/","title":"Groupbys","text":""},{"location":"reference/groupbys/#tablite.groupbys","title":"tablite.groupbys","text":""},{"location":"reference/groupbys/#tablite.groupbys-classes","title":"Classes","text":""},{"location":"reference/groupbys/#tablite.groupbys-functions","title":"Functions","text":""},{"location":"reference/groupbys/#tablite.groupbys.groupby","title":"tablite.groupbys.groupby(T, keys, functions, tqdm=_tqdm, pbar=None)","text":"

    keys: column names for grouping. functions: [optional] list of column names and group functions (See GroupyBy class) returns: table

    Example:

    >>> t = Table()\n>>> t.add_column('A', data=[1, 1, 2, 2, 3, 3] * 2)\n>>> t.add_column('B', data=[1, 2, 3, 4, 5, 6] * 2)\n>>> t.add_column('C', data=[6, 5, 4, 3, 2, 1] * 2)\n>>> t.show()\n+=====+=====+=====+\n|  A  |  B  |  C  |\n| int | int | int |\n+-----+-----+-----+\n|    1|    1|    6|\n|    1|    2|    5|\n|    2|    3|    4|\n|    2|    4|    3|\n|    3|    5|    2|\n|    3|    6|    1|\n|    1|    1|    6|\n|    1|    2|    5|\n|    2|    3|    4|\n|    2|    4|    3|\n|    3|    5|    2|\n|    3|    6|    1|\n+=====+=====+=====+\n>>> g = t.groupby(keys=['A', 'C'], functions=[('B', gb.sum)])\n>>> g.show()\n+===+===+===+======+\n| # | A | C |Sum(B)|\n|row|int|int| int  |\n+---+---+---+------+\n|0  |  1|  6|     2|\n|1  |  1|  5|     4|\n|2  |  2|  4|     6|\n|3  |  2|  3|     8|\n|4  |  3|  2|    10|\n|5  |  3|  1|    12|\n+===+===+===+======+\n

    Cheat sheet:

    list of unique values

    >>> g1 = t.groupby(keys=['A'], functions=[])\n>>> g1['A'][:]\n[1,2,3]\n

    alternatively:

    >>> t['A'].unique()\n[1,2,3]\n

    list of unique values, grouped by longest combination.

    >>> g2 = t.groupby(keys=['A', 'B'], functions=[])\n>>> g2['A'][:], g2['B'][:]\n([1,1,2,2,3,3], [1,2,3,4,5,6])\n

    alternatively use:

    >>> list(zip(*t.index('A', 'B').keys()))\n[(1,1,2,2,3,3) (1,2,3,4,5,6)]\n

    A key (unique values) and count hereof.

    >>> g3 = t.groupby(keys=['A'], functions=[('A', gb.count)])\n>>> g3['A'][:], g3['Count(A)'][:]\n([1,2,3], [4,4,4])\n

    alternatively use:

    >>> t['A'].histogram()\n([1,2,3], [4,4,4])\n

    for more examples see: https://github.com/root-11/tablite/blob/master/tests/test_groupby.py

    Source code in tablite/groupbys.py
    def groupby(\n    T, keys, functions, tqdm=_tqdm, pbar=None\n):  # TODO: This is single core code.\n    \"\"\"\n    keys: column names for grouping.\n    functions: [optional] list of column names and group functions (See GroupyBy class)\n    returns: table\n\n    Example:\n    ```\n    >>> t = Table()\n    >>> t.add_column('A', data=[1, 1, 2, 2, 3, 3] * 2)\n    >>> t.add_column('B', data=[1, 2, 3, 4, 5, 6] * 2)\n    >>> t.add_column('C', data=[6, 5, 4, 3, 2, 1] * 2)\n    >>> t.show()\n    +=====+=====+=====+\n    |  A  |  B  |  C  |\n    | int | int | int |\n    +-----+-----+-----+\n    |    1|    1|    6|\n    |    1|    2|    5|\n    |    2|    3|    4|\n    |    2|    4|    3|\n    |    3|    5|    2|\n    |    3|    6|    1|\n    |    1|    1|    6|\n    |    1|    2|    5|\n    |    2|    3|    4|\n    |    2|    4|    3|\n    |    3|    5|    2|\n    |    3|    6|    1|\n    +=====+=====+=====+\n    >>> g = t.groupby(keys=['A', 'C'], functions=[('B', gb.sum)])\n    >>> g.show()\n    +===+===+===+======+\n    | # | A | C |Sum(B)|\n    |row|int|int| int  |\n    +---+---+---+------+\n    |0  |  1|  6|     2|\n    |1  |  1|  5|     4|\n    |2  |  2|  4|     6|\n    |3  |  2|  3|     8|\n    |4  |  3|  2|    10|\n    |5  |  3|  1|    12|\n    +===+===+===+======+\n    ```\n\n    Cheat sheet:\n\n    list of unique values\n    ```\n    >>> g1 = t.groupby(keys=['A'], functions=[])\n    >>> g1['A'][:]\n    [1,2,3]\n    ```\n    alternatively:\n    ```\n    >>> t['A'].unique()\n    [1,2,3]\n    ```\n    list of unique values, grouped by longest combination.\n    ```\n    >>> g2 = t.groupby(keys=['A', 'B'], functions=[])\n    >>> g2['A'][:], g2['B'][:]\n    ([1,1,2,2,3,3], [1,2,3,4,5,6])\n    ```\n    alternatively use:\n    ```\n    >>> list(zip(*t.index('A', 'B').keys()))\n    [(1,1,2,2,3,3) (1,2,3,4,5,6)]\n    ```\n\n    A key (unique values) and count hereof.\n    ```\n    >>> g3 = t.groupby(keys=['A'], functions=[('A', gb.count)])\n    >>> g3['A'][:], g3['Count(A)'][:]\n    ([1,2,3], [4,4,4])\n    ```\n    alternatively use:\n    ```\n    >>> t['A'].histogram()\n    ([1,2,3], [4,4,4])\n    ```\n    for more examples see: https://github.com/root-11/tablite/blob/master/tests/test_groupby.py\n\n    \"\"\"\n    if not isinstance(keys, list):\n        raise TypeError(\"expected keys as a list of column names\")\n\n    if keys:\n        if len(set(keys)) != len(keys):\n            duplicates = [k for k in keys if keys.count(k) > 1]\n            s = \"\" if len(duplicates) > 1 else \"s\"\n            raise ValueError(\n                f\"duplicate key{s} found across rows and columns: {duplicates}\"\n            )\n\n    if not isinstance(functions, list):\n        raise TypeError(\n            f\"Expected functions to be a list of tuples. Got {type(functions)}\"\n        )\n\n    if not keys + functions:\n        raise ValueError(\"No keys or functions?\")\n\n    if not all(len(i) == 2 for i in functions):\n        raise ValueError(\n            f\"Expected each tuple in functions to be of length 2. \\nGot {functions}\"\n        )\n\n    if not all(isinstance(a, str) for a, _ in functions):\n        L = [(a, type(a)) for a, _ in functions if not isinstance(a, str)]\n        raise ValueError(\n            f\"Expected column names in functions to be strings. Found: {L}\"\n        )\n\n    if not all(\n        issubclass(b, GroupbyFunction) and b in GroupBy.functions for _, b in functions\n    ):\n        L = [b for _, b in functions if b not in GroupBy._functions]\n        if len(L) == 1:\n            singular = f\"function {L[0]} is not in GroupBy.functions\"\n            raise ValueError(singular)\n        else:\n            plural = f\"the functions {L} are not in GroupBy.functions\"\n            raise ValueError(plural)\n\n    # only keys will produce unique values for each key group.\n    if keys and not functions:\n        cols = list(zip(*T.index(*keys)))\n        result = T.__class__()\n\n        pbar = tqdm(total=len(keys), desc=\"groupby\") if pbar is None else pbar\n\n        for col_name, col in zip(keys, cols):\n            result[col_name] = col\n\n            pbar.update(1)\n        return result\n\n    # grouping is required...\n    # 1. Aggregate data.\n    aggregation_functions = defaultdict(dict)\n    cols = keys + [col_name for col_name, _ in functions]\n    seen, L = set(), []\n    for c in cols:  # maintains order of appearance.\n        if c not in seen:\n            seen.add(c)\n            L.append(c)\n\n    # there's a table of values.\n    data = T[L]\n    if isinstance(data, Column):\n        tbl = BaseTable()\n        tbl[L[0]] = data\n    else:\n        tbl = data\n\n    pbar = (\n        tqdm(desc=\"groupby\", total=len(tbl), disable=Config.TQDM_DISABLE)\n        if pbar is None\n        else pbar\n    )\n\n    for row in tbl.rows:\n        d = {col_name: value for col_name, value in zip(L, row)}\n        key = tuple([d[k] for k in keys])\n        agg_functions = aggregation_functions.get(key)\n        if not agg_functions:\n            aggregation_functions[key] = agg_functions = [\n                (col_name, f()) for col_name, f in functions\n            ]\n        for col_name, f in agg_functions:\n            f.update(d[col_name])\n\n        pbar.update(1)\n\n    # 2. make dense table.\n    cols = [[] for _ in cols]\n    for key_tuple, funcs in aggregation_functions.items():\n        for ix, key_value in enumerate(key_tuple):\n            cols[ix].append(key_value)\n        for ix, (_, f) in enumerate(funcs, start=len(keys)):\n            cols[ix].append(f.value)\n\n    new_names = keys + [f\"{f.__name__}({col_name})\" for col_name, f in functions]\n    result = type(T)()  # New Table.\n    for ix, (col_name, data) in enumerate(zip(new_names, cols)):\n        revised_name = unique_name(col_name, result.columns)\n        result[revised_name] = data\n    return result\n
    "},{"location":"reference/import_utils/","title":"Import utils","text":""},{"location":"reference/import_utils/#tablite.import_utils","title":"tablite.import_utils","text":""},{"location":"reference/import_utils/#tablite.import_utils-attributes","title":"Attributes","text":""},{"location":"reference/import_utils/#tablite.import_utils.file_readers","title":"tablite.import_utils.file_readers = {'fods': excel_reader, 'json': excel_reader, 'html': from_html, 'hdf5': from_hdf5, 'simple': excel_reader, 'rst': excel_reader, 'mediawiki': excel_reader, 'xlsx': excel_reader, 'xls': excel_reader, 'xlsm': excel_reader, 'csv': text_reader, 'tsv': text_reader, 'txt': text_reader, 'ods': ods_reader} module-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.valid_readers","title":"tablite.import_utils.valid_readers = ','.join(list(file_readers.keys())) module-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils-classes","title":"Classes","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig","title":"tablite.import_utils.TRconfig(source, destination, start, end, guess_datatypes, delimiter, text_qualifier, text_escape_openings, text_escape_closures, strip_leading_and_tailing_whitespace, encoding, newline_offsets, fields)","text":"

    Bases: object

    Source code in tablite/import_utils.py
    def __init__(\n    self,\n    source,\n    destination,\n    start,\n    end,\n    guess_datatypes,\n    delimiter,\n    text_qualifier,\n    text_escape_openings,\n    text_escape_closures,\n    strip_leading_and_tailing_whitespace,\n    encoding,\n    newline_offsets,\n    fields\n) -> None:\n    self.source = source\n    self.destination = destination\n    self.start = start\n    self.end = end\n    self.guess_datatypes = guess_datatypes\n    self.delimiter = delimiter\n    self.text_qualifier = text_qualifier\n    self.text_escape_openings = text_escape_openings\n    self.text_escape_closures = text_escape_closures\n    self.strip_leading_and_tailing_whitespace = strip_leading_and_tailing_whitespace\n    self.encoding = encoding\n    self.newline_offsets = newline_offsets\n    self.fields = fields\n    type_check(start, int),\n    type_check(end, int),\n    type_check(delimiter, str),\n    type_check(text_qualifier, (str, type(None))),\n    type_check(text_escape_openings, str),\n    type_check(text_escape_closures, str),\n    type_check(encoding, str),\n    type_check(strip_leading_and_tailing_whitespace, bool),\n    type_check(newline_offsets, list)\n    type_check(fields, dict)\n
    "},{"location":"reference/import_utils/#tablite.import_utils.TRconfig-attributes","title":"Attributes","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.source","title":"tablite.import_utils.TRconfig.source = source instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.destination","title":"tablite.import_utils.TRconfig.destination = destination instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.start","title":"tablite.import_utils.TRconfig.start = start instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.end","title":"tablite.import_utils.TRconfig.end = end instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.guess_datatypes","title":"tablite.import_utils.TRconfig.guess_datatypes = guess_datatypes instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.delimiter","title":"tablite.import_utils.TRconfig.delimiter = delimiter instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.text_qualifier","title":"tablite.import_utils.TRconfig.text_qualifier = text_qualifier instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.text_escape_openings","title":"tablite.import_utils.TRconfig.text_escape_openings = text_escape_openings instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.text_escape_closures","title":"tablite.import_utils.TRconfig.text_escape_closures = text_escape_closures instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.strip_leading_and_tailing_whitespace","title":"tablite.import_utils.TRconfig.strip_leading_and_tailing_whitespace = strip_leading_and_tailing_whitespace instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.encoding","title":"tablite.import_utils.TRconfig.encoding = encoding instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.newline_offsets","title":"tablite.import_utils.TRconfig.newline_offsets = newline_offsets instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.fields","title":"tablite.import_utils.TRconfig.fields = fields instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig-functions","title":"Functions","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.copy","title":"tablite.import_utils.TRconfig.copy()","text":"Source code in tablite/import_utils.py
    def copy(self):\n    return TRconfig(**self.dict())\n
    "},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.dict","title":"tablite.import_utils.TRconfig.dict()","text":"Source code in tablite/import_utils.py
    def dict(self):\n    return {k: v for k, v in self.__dict__.items() if not (k.startswith(\"_\") or callable(v))}\n
    "},{"location":"reference/import_utils/#tablite.import_utils-functions","title":"Functions","text":""},{"location":"reference/import_utils/#tablite.import_utils.from_pandas","title":"tablite.import_utils.from_pandas(T, df)","text":"

    Creates Table using pd.to_dict('list')

    similar to:

    import pandas as pd df = pd.DataFrame({'a':[1,2,3], 'b':[4,5,6]}) df a b 0 1 4 1 2 5 2 3 6 df.to_dict('list')

    t = Table.from_dict(df.to_dict('list)) t.show() +===+===+===+ | # | a | b | |row|int|int| +---+---+---+ | 0 | 1| 4| | 1 | 2| 5| | 2 | 3| 6| +===+===+===+

    Source code in tablite/import_utils.py
    def from_pandas(T, df):\n    \"\"\"\n    Creates Table using pd.to_dict('list')\n\n    similar to:\n    >>> import pandas as pd\n    >>> df = pd.DataFrame({'a':[1,2,3], 'b':[4,5,6]})\n    >>> df\n        a  b\n        0  1  4\n        1  2  5\n        2  3  6\n    >>> df.to_dict('list')\n    {'a': [1, 2, 3], 'b': [4, 5, 6]}\n\n    >>> t = Table.from_dict(df.to_dict('list))\n    >>> t.show()\n        +===+===+===+\n        | # | a | b |\n        |row|int|int|\n        +---+---+---+\n        | 0 |  1|  4|\n        | 1 |  2|  5|\n        | 2 |  3|  6|\n        +===+===+===+\n    \"\"\"\n    if not issubclass(T, BaseTable):\n        raise TypeError(\"Expected subclass of Table\")\n\n    return T(columns=df.to_dict(\"list\"))  # noqa\n
    "},{"location":"reference/import_utils/#tablite.import_utils.from_hdf5","title":"tablite.import_utils.from_hdf5(T, path, tqdm=_tqdm, pbar=None)","text":"

    imports an exported hdf5 table.

    Note that some loss of type information is to be expected in columns of mixed type:

    t.show(dtype=True) +===+===+=====+=====+====+=====+=====+===================+==========+========+===============+===+=========================+=====+===+ | # | A | B | C | D | E | F | G | H | I | J | K | L | M | O | |row|int|mixed|float|str |mixed| bool| datetime | date | time | timedelta |str| int |float|int| +---+---+-----+-----+----+-----+-----+-------------------+----------+--------+---------------+---+-------------------------+-----+---+ | 0 | -1|None | -1.1| |None |False|2023-06-09 09:12:06|2023-06-09|09:12:06| 1 day, 0:00:00|b |-100000000000000000000000| inf| 11| | 1 | 1| 1| 1.1|1000|1 | True|2023-06-09 09:12:06|2023-06-09|09:12:06|2 days, 0:06:40|\u55e8 | 100000000000000000000000| -inf|-11| +===+===+=====+=====+====+=====+=====+===================+==========+========+===============+===+=========================+=====+===+ t.to_hdf5(filename) t2 = Table.from_hdf5(filename) t2.show(dtype=True) +===+===+=====+=====+=====+=====+=====+===================+===================+========+===============+===+=========================+=====+===+ | # | A | B | C | D | E | F | G | H | I | J | K | L | M | O | |row|int|mixed|float|mixed|mixed| bool| datetime | datetime | time | str |str| int |float|int| +---+---+-----+-----+-----+-----+-----+-------------------+-------------------+--------+---------------+---+-------------------------+-----+---+ | 0 | -1|None | -1.1|None |None |False|2023-06-09 09:12:06|2023-06-09 00:00:00|09:12:06|1 day, 0:00:00 |b |-100000000000000000000000| inf| 11| | 1 | 1| 1| 1.1| 1000| 1| True|2023-06-09 09:12:06|2023-06-09 00:00:00|09:12:06|2 days, 0:06:40|\u55e8 | 100000000000000000000000| -inf|-11| +===+===+=====+=====+=====+=====+=====+===================+===================+========+===============+===+=========================+=====+===+

    Source code in tablite/import_utils.py
    def from_hdf5(T, path, tqdm=_tqdm, pbar=None):\n    \"\"\"\n    imports an exported hdf5 table.\n\n    Note that some loss of type information is to be expected in columns of mixed type:\n    >>> t.show(dtype=True)\n    +===+===+=====+=====+====+=====+=====+===================+==========+========+===============+===+=========================+=====+===+\n    | # | A |  B  |  C  | D  |  E  |  F  |         G         |    H     |   I    |       J       | K |            L            |  M  | O |\n    |row|int|mixed|float|str |mixed| bool|      datetime     |   date   |  time  |   timedelta   |str|           int           |float|int|\n    +---+---+-----+-----+----+-----+-----+-------------------+----------+--------+---------------+---+-------------------------+-----+---+\n    | 0 | -1|None | -1.1|    |None |False|2023-06-09 09:12:06|2023-06-09|09:12:06| 1 day, 0:00:00|b  |-100000000000000000000000|  inf| 11|\n    | 1 |  1|    1|  1.1|1000|1    | True|2023-06-09 09:12:06|2023-06-09|09:12:06|2 days, 0:06:40|\u55e8 | 100000000000000000000000| -inf|-11|\n    +===+===+=====+=====+====+=====+=====+===================+==========+========+===============+===+=========================+=====+===+\n    >>> t.to_hdf5(filename)\n    >>> t2 = Table.from_hdf5(filename)\n    >>> t2.show(dtype=True)\n    +===+===+=====+=====+=====+=====+=====+===================+===================+========+===============+===+=========================+=====+===+\n    | # | A |  B  |  C  |  D  |  E  |  F  |         G         |         H         |   I    |       J       | K |            L            |  M  | O |\n    |row|int|mixed|float|mixed|mixed| bool|      datetime     |      datetime     |  time  |      str      |str|           int           |float|int|\n    +---+---+-----+-----+-----+-----+-----+-------------------+-------------------+--------+---------------+---+-------------------------+-----+---+\n    | 0 | -1|None | -1.1|None |None |False|2023-06-09 09:12:06|2023-06-09 00:00:00|09:12:06|1 day, 0:00:00 |b  |-100000000000000000000000|  inf| 11|\n    | 1 |  1|    1|  1.1| 1000|    1| True|2023-06-09 09:12:06|2023-06-09 00:00:00|09:12:06|2 days, 0:06:40|\u55e8 | 100000000000000000000000| -inf|-11|\n    +===+===+=====+=====+=====+=====+=====+===================+===================+========+===============+===+=========================+=====+===+\n    \"\"\"\n    if not issubclass(T, BaseTable):\n        raise TypeError(\"Expected subclass of Table\")\n    import h5py\n\n    type_check(path, Path)\n    t = T()\n    with h5py.File(path, \"r\") as h5:\n        for col_name in h5.keys():\n            dset = h5[col_name]\n            arr = np.array(dset[:])\n            if arr.dtype == object:\n                arr = np.array(DataTypes.guess([v.decode(\"utf-8\") for v in arr]))\n            t[col_name] = arr\n    return t\n
    "},{"location":"reference/import_utils/#tablite.import_utils.from_json","title":"tablite.import_utils.from_json(T, jsn)","text":"

    Imports tables exported using .to_json

    Source code in tablite/import_utils.py
    def from_json(T, jsn):\n    \"\"\"\n    Imports tables exported using .to_json\n    \"\"\"\n    if not issubclass(T, BaseTable):\n        raise TypeError(\"Expected subclass of Table\")\n    import json\n\n    type_check(jsn, str)\n    d = json.loads(jsn)\n    return T(columns=d[\"columns\"])\n
    "},{"location":"reference/import_utils/#tablite.import_utils.from_html","title":"tablite.import_utils.from_html(T, path, tqdm=_tqdm, pbar=None)","text":"Source code in tablite/import_utils.py
    def from_html(T, path, tqdm=_tqdm, pbar=None):\n    if not issubclass(T, BaseTable):\n        raise TypeError(\"Expected subclass of Table\")\n    type_check(path, Path)\n\n    if pbar is None:\n        total = path.stat().st_size\n        pbar = tqdm(total=total, desc=\"from_html\", disable=Config.TQDM_DISABLE)\n\n    row_start, row_end = \"<tr>\", \"</tr>\"\n    value_start, value_end = \"<th>\", \"</th>\"\n    chunk = \"\"\n    t = None  # will be T()\n    start, end = 0, 0\n    data = {}\n    with path.open(\"r\") as fi:\n        while True:\n            start = chunk.find(row_start, start)  # row tag start\n            end = chunk.find(row_end, end)  # row tag end\n            if start == -1 or end == -1:\n                new = fi.read(100_000)\n                pbar.update(len(new))\n                if new == \"\":\n                    break\n                chunk += new\n                continue\n            # get indices from chunk\n            row = chunk[start + len(row_start) : end]\n            fields = [v.rstrip(value_end) for v in row.split(value_start)]\n            if not data:\n                headers = fields[:]\n                data = {f: [] for f in headers}\n                continue\n            else:\n                for field, header in zip(fields, headers):\n                    data[header].append(field)\n\n            chunk = chunk[end + len(row_end) :]\n\n            if len(data[headers[0]]) == Config.PAGE_SIZE:\n                if t is None:\n                    t = T(columns=data)\n                else:\n                    for k, v in data.items():\n                        t[k].extend(DataTypes.guess(v))\n                data = {f: [] for f in headers}\n\n    for k, v in data.items():\n        t[k].extend(DataTypes.guess(v))\n    return t\n
    "},{"location":"reference/import_utils/#tablite.import_utils.excel_reader","title":"tablite.import_utils.excel_reader(T, path, first_row_has_headers=True, header_row_index=0, sheet=None, columns=None, skip_empty='NONE', start=0, limit=sys.maxsize, tqdm=_tqdm, **kwargs)","text":"

    returns Table from excel

    **kwargs are excess arguments that are ignored.

    Source code in tablite/import_utils.py
    def excel_reader(T, path, first_row_has_headers=True, header_row_index=0, sheet=None, columns=None, skip_empty=\"NONE\", start=0, limit=sys.maxsize, tqdm=_tqdm, **kwargs):\n    \"\"\"\n    returns Table from excel\n\n    **kwargs are excess arguments that are ignored.\n    \"\"\"\n    if not issubclass(T, BaseTable):\n        raise TypeError(\"Expected subclass of Table\")\n\n    book = openpyxl.load_workbook(path, read_only=True, data_only=True)\n\n    if sheet is None:  # help the user.\n        \"\"\"\n            If no sheet specified, assume first sheet.\n\n            Reasoning:\n                Pandas ODS reader does that, so this preserves parity and it might be expected by users.\n                If we don't know the sheet name but only have single sheet,\n                    we would need to take extra steps to find out the name of the sheet.\n                We already make assumptions in case of column selection,\n                    when columns are None, we import all of them.\n        \"\"\"\n        sheet = book.sheetnames[0]\n    elif sheet not in book.sheetnames:\n        raise ValueError(f\"sheet not found: {sheet}\")\n\n    if not (isinstance(start, int) and start >= 0):\n        raise ValueError(\"expected start as an integer >=0\")\n    if not (isinstance(limit, int) and limit > 0):\n        raise ValueError(\"expected limit as integer > 0\")\n\n    worksheet = book[sheet]\n    fixup_worksheet(worksheet)\n\n    try:\n        it_header = worksheet.iter_rows(min_row=header_row_index + 1)\n        while True:\n            # get the first row to know our headers or the number of columns\n            row = [c.value for c in next(it_header)]\n            break\n        fields = [str(c) if c is not None else \"\" for c in row] # excel is offset by 1\n    except StopIteration:\n        # excel was empty, return empty table\n        return T()\n\n    if not first_row_has_headers:\n        # since the first row did not contain headers, we use the column count to populate header names\n        fields = [str(i) for i in range(len(fields))]\n\n    if columns is None:\n        # no columns were specified by user to import, that means we import all of the them\n        columns = []\n\n        for f in fields:\n            # fixup the duplicate column names\n            columns.append(unique_name(f, columns))\n\n        field_dict = {k: i for i, k in enumerate(columns)}\n    else:\n        field_dict = {}\n\n        for k, i in ((k, fields.index(k)) for k in columns):\n            # fixup the duplicate column names\n            field_dict[unique_name(k, field_dict.keys())] = i\n\n    # calculate our data rows iterator offset\n    it_offset = start + (1 if first_row_has_headers else 0) + header_row_index + 1\n\n    # attempt to fetch number of rows in the sheet\n    total_rows = worksheet.max_row\n    real_tqdm = True\n\n    if total_rows is None:\n        # i don't know what causes it but max_row can be None in some cases, so we don't know how large the dataset is\n        total_rows = it_offset + limit\n        real_tqdm = False\n\n    # create the actual data rows iterator\n    it_rows = worksheet.iter_rows(min_row=it_offset, max_row=min(it_offset+limit, total_rows))\n    it_used_indices = list(field_dict.values())\n\n    # filter columns that we're not going to use\n    it_rows_filtered = ([row[idx].value for idx in it_used_indices] for row in it_rows)\n\n    # create page directory\n    workdir = Path(Config.workdir) / Config.pid\n    pagesdir = workdir/\"pages\"\n    pagesdir.mkdir(exist_ok=True, parents=True)\n\n    field_names = list(field_dict.keys())\n    column_count = len(field_names)\n\n    page_fhs = None\n\n    # prepopulate the table with columns\n    table = T()\n    for name in field_names:\n        table[name] = Column(table.path)\n\n    pbar_fname = path.name\n    if len(pbar_fname) > 20:\n        pbar_fname = pbar_fname[0:10] + \"...\" + pbar_fname[-7:]\n\n    if real_tqdm:\n        # we can create a true tqdm progress bar, make one\n        tqdm_iter = tqdm(it_rows_filtered, total=total_rows, desc=f\"importing excel: {pbar_fname}\")\n    else:\n        \"\"\"\n            openpyxls was unable to precalculate the size of the excel for whatever reason\n            forcing recalc would require parsing entire file\n            drop the progress bar in that case, just show iterations\n\n            as an alternative we can use \u03a3=1/x but it just doesn't look good, show iterations per second instead\n        \"\"\"\n        tqdm_iter = tqdm(it_rows_filtered, desc=f\"importing excel: {pbar_fname}\")\n\n    tqdm_iter = iter(tqdm_iter)\n\n    idx = 0\n\n    while True:\n        try:\n            row = next(tqdm_iter)\n        except StopIteration:\n            break # because in some cases we can't know the size of excel to set the upper iterator limit we loop until stop iteration is encountered\n\n        if skip_empty == \"ALL\" and all(v is None for v in row):\n            continue\n        elif skip_empty == \"ANY\" and any(v is None for v in row):\n            continue\n\n        if idx % Config.PAGE_SIZE == 0:\n            if page_fhs is not None:\n                # we reached the max page file size, fix the pages\n                [_fix_xls_page(table, c, fh) for c, fh in zip(field_names, page_fhs)]\n\n            page_fhs = [None] * column_count\n\n            for cidx in range(column_count):\n                # allocate new pages\n                pg_path = pagesdir / f\"{next(Page.ids)}.npy\"\n                page_fhs[cidx] = open(pg_path, \"wb\")\n\n        for fh, value in zip(page_fhs, row):\n            \"\"\"\n                since excel types are already cast into appropriate type we're going to do two passes per page\n\n                we create our temporary custom format:\n                packed type|packed byte count|packed bytes|...\n\n                available types:\n                    * q - int64\n                    * d - float64\n                    * s - string\n                    * b - boolean\n                    * n - none\n                    * p - pickled (date, time, datetime)\n            \"\"\"\n            dtype = type(value)\n\n            if dtype == int:\n                ptype, bytes_ = b'q', struct.pack('q', value) # pack int as int64\n            elif dtype == float:\n                ptype, bytes_ = b'd', struct.pack('d', value) # pack float as float64\n            elif dtype == str:\n                ptype, bytes_ = b's', value.encode(\"utf-8\")   # pack string\n            elif dtype == bool:\n                ptype, bytes_ = b'b', b'1' if value else b'0' # pack boolean\n            elif value is None:\n                ptype, bytes_ = b'n', b''                     # pack none\n            elif dtype in [date, time, datetime]:\n                ptype, bytes_ = b'p', pkl.dumps(value)        # pack object types via pickle\n            else:\n                raise NotImplementedError()\n\n            byte_count = struct.pack('I', len(bytes_))        # pack our payload size, i doubt payload size can be over uint32\n\n            # dump object to file\n            fh.write(ptype)\n            fh.write(byte_count)\n            fh.write(bytes_)\n\n        idx = idx + 1\n\n    if page_fhs is not None:\n        # we reached end of the loop, fix the pages\n        [_fix_xls_page(table, c, fh) for c, fh in zip(field_names, page_fhs)]\n\n    return table\n
    "},{"location":"reference/import_utils/#tablite.import_utils.ods_reader","title":"tablite.import_utils.ods_reader(T, path, first_row_has_headers=True, header_row_index=0, sheet=None, columns=None, skip_empty='NONE', start=0, limit=sys.maxsize, **kwargs)","text":"

    returns Table from .ODS

    Source code in tablite/import_utils.py
    def ods_reader(T, path, first_row_has_headers=True, header_row_index=0, sheet=None, columns=None, skip_empty=\"NONE\", start=0, limit=sys.maxsize, **kwargs):\n    \"\"\"\n    returns Table from .ODS\n    \"\"\"\n    if not issubclass(T, BaseTable):\n        raise TypeError(\"Expected subclass of Table\")\n\n    if sheet is None:\n        data = read_excel(str(path), header=None) # selects first sheet\n    else:\n        data = read_excel(str(path), sheet_name=sheet, header=None)\n\n    data[isna(data)] = None  # convert any empty cells to None\n    data = data.to_numpy().tolist() # convert pandas to list\n\n    if skip_empty == \"ALL\" or skip_empty == \"ANY\":\n        \"\"\" filter out all rows based on predicate that come after header row \"\"\"\n        fn_filter = any if skip_empty == \"ALL\" else all # this is intentional\n        data = [\n            row\n            for ridx, row in enumerate(data)\n            if ridx < header_row_index + (1 if first_row_has_headers else 0) or fn_filter(not (v is None or isinstance(v, str) and len(v) == 0) for v in row)\n        ]\n\n    data = np.array(data, dtype=np.object_) # cast back to numpy array for slicing but don't try to convert datatypes\n\n    if not (isinstance(start, int) and start >= 0):\n        raise ValueError(\"expected start as an integer >=0\")\n    if not (isinstance(limit, int) and limit > 0):\n        raise ValueError(\"expected limit as integer > 0\")\n\n    t = T()\n\n    used_columns_names = set()\n    for ix, value in enumerate(data[header_row_index]):\n        if first_row_has_headers:\n            header, start_row_pos = \"\" if value is None else str(value), (1 + header_row_index)\n        else:\n            header, start_row_pos = f\"_{ix + 1}\", (0 + header_row_index)\n\n        if columns is not None:\n            if header not in columns:\n                continue\n\n        unique_column_name = unique_name(str(header), used_columns_names)\n        used_columns_names.add(unique_column_name)\n\n        column_values = data[start_row_pos : start_row_pos + limit, ix]\n\n        t[unique_column_name] = column_values\n    return t\n
    "},{"location":"reference/import_utils/#tablite.import_utils.text_reader_task","title":"tablite.import_utils.text_reader_task(source, destination, start, end, guess_datatypes, delimiter, text_qualifier, text_escape_openings, text_escape_closures, strip_leading_and_tailing_whitespace, encoding, newline_offsets, fields)","text":"

    PARALLEL TASK FUNCTION reads columnsname + path[start:limit] into hdf5.

    source: csv or txt file destination: filename for page. start: int: start of page. end: int: end of page. guess_datatypes: bool: if True datatypes will be inferred by datatypes.Datatypes.guess delimiter: ',' ';' or '|' text_qualifier: str: commonly \" text_escape_openings: str: default: \"({[ text_escape_closures: str: default: ]})\" strip_leading_and_tailing_whitespace: bool encoding: chardet encoding ('utf-8, 'ascii', ..., 'ISO-22022-CN')

    Source code in tablite/import_utils.py
    def text_reader_task(\n    source,\n    destination,\n    start,\n    end,\n    guess_datatypes,\n    delimiter,\n    text_qualifier,\n    text_escape_openings,\n    text_escape_closures,\n    strip_leading_and_tailing_whitespace,\n    encoding,\n    newline_offsets,\n    fields\n):\n    \"\"\"PARALLEL TASK FUNCTION\n    reads columnsname + path[start:limit] into hdf5.\n\n    source: csv or txt file\n    destination: filename for page.\n    start: int: start of page.\n    end: int: end of page.\n    guess_datatypes: bool: if True datatypes will be inferred by datatypes.Datatypes.guess\n    delimiter: ',' ';' or '|'\n    text_qualifier: str: commonly \\\"\n    text_escape_openings: str: default: \"({[\n    text_escape_closures: str: default: ]})\"\n    strip_leading_and_tailing_whitespace: bool\n    encoding: chardet encoding ('utf-8, 'ascii', ..., 'ISO-22022-CN')\n    \"\"\"\n    if isinstance(source, str):\n        source = Path(source)\n    type_check(source, Path)\n    if not source.exists():\n        raise FileNotFoundError(f\"File not found: {source}\")\n    type_check(destination, list)\n\n    # declare CSV dialect.\n    delim = delimiter\n\n    class Dialect(csv.Dialect):\n        delimiter = delim\n        quotechar = '\"' if text_qualifier is None else text_qualifier\n        escapechar = '\\\\'\n        doublequote = True\n        quoting = csv.QUOTE_MINIMAL\n        skipinitialspace = False if strip_leading_and_tailing_whitespace is None else strip_leading_and_tailing_whitespace\n        lineterminator = \"\\n\"\n\n    with source.open(\"r\", encoding=encoding, errors=\"ignore\") as fi:  # --READ\n        fi.seek(newline_offsets[start])\n        reader = csv.reader(fi, dialect=Dialect)\n\n        # if there's an issue with file handlers on windows, we can make a special case for windows where the file is opened on demand and appended instead of opening all handlers at once\n        page_file_handlers = [open(f, mode=\"wb\") for f in destination]\n\n        # identify longest str\n        longest_str = [1 for _ in range(len(destination))]\n        for row in (next(reader) for _ in range(end - start)):\n            for idx, c in ((fields[idx], c) for idx, c in filter(lambda t: t[0] in fields, enumerate(row))):\n                longest_str[idx] = max(longest_str[idx], len(c))\n\n        column_formats = [f\"<U{i}\" for i in longest_str]\n        for idx, cf in enumerate(column_formats):\n            _create_numpy_header(cf, (end - start, ), page_file_handlers[idx])\n\n        # write page arrays to files\n        fi.seek(newline_offsets[start])\n        for row in (next(reader) for _ in range(end - start)):\n            for idx, c in ((fields[idx], c) for idx, c in filter(lambda t: t[0] in fields, enumerate(row))):\n                cbytes = np.asarray(c, dtype=column_formats[idx]).tobytes()\n                page_file_handlers[idx].write(cbytes)\n\n        [phf.close() for phf in page_file_handlers]\n
    "},{"location":"reference/import_utils/#tablite.import_utils.text_reader","title":"tablite.import_utils.text_reader(T, path, columns, first_row_has_headers, header_row_index, encoding, start, limit, newline, guess_datatypes, text_qualifier, strip_leading_and_tailing_whitespace, skip_empty, delimiter, text_escape_openings, text_escape_closures, tqdm=_tqdm, **kwargs)","text":"Source code in tablite/import_utils.py
    def text_reader(\n    T,\n    path,\n    columns,\n    first_row_has_headers,\n    header_row_index,\n    encoding,\n    start,\n    limit,\n    newline,\n    guess_datatypes,\n    text_qualifier,\n    strip_leading_and_tailing_whitespace,\n    skip_empty,\n    delimiter,\n    text_escape_openings,\n    text_escape_closures,\n    tqdm=_tqdm,\n    **kwargs,\n):\n    if encoding is None:\n        encoding = get_encoding(path, nbytes=ENCODING_GUESS_BYTES)\n\n    enc = py_to_nim_encoding(encoding)\n    pid = Config.workdir / Config.pid\n    kwargs = {}\n\n    if first_row_has_headers is not None:\n        kwargs[\"first_row_has_headers\"] = first_row_has_headers\n    if header_row_index is not None:\n        kwargs[\"header_row_index\"] = header_row_index\n    if columns is not None:\n        kwargs[\"columns\"] = columns\n    if start is not None:\n        kwargs[\"start\"] = start\n    if limit is not None and limit != sys.maxsize:\n        kwargs[\"limit\"] = limit\n    if guess_datatypes is not None:\n        kwargs[\"guess_datatypes\"] = guess_datatypes\n    if newline is not None:\n        kwargs[\"newline\"] = newline\n    if delimiter is not None:\n        kwargs[\"delimiter\"] = delimiter\n    if text_qualifier is not None:\n        kwargs[\"text_qualifier\"] = text_qualifier\n        kwargs[\"quoting\"] = \"QUOTE_MINIMAL\"\n    else:\n        kwargs[\"quoting\"] = \"QUOTE_NONE\"\n    if strip_leading_and_tailing_whitespace is not None:\n        kwargs[\"strip_leading_and_tailing_whitespace\"] = strip_leading_and_tailing_whitespace\n\n    if skip_empty is None:\n        kwargs[\"skip_empty\"] = \"NONE\"\n    else:\n        kwargs[\"skip_empty\"] = skip_empty\n\n    return nimlite.text_reader(\n        T, pid, path, enc,\n        **kwargs,\n        tqdm=tqdm\n    )\n
    "},{"location":"reference/import_utils/#tablite.import_utils-modules","title":"Modules","text":""},{"location":"reference/imputation/","title":"Imputation","text":""},{"location":"reference/imputation/#tablite.imputation","title":"tablite.imputation","text":""},{"location":"reference/imputation/#tablite.imputation-classes","title":"Classes","text":""},{"location":"reference/imputation/#tablite.imputation-functions","title":"Functions","text":""},{"location":"reference/imputation/#tablite.imputation.imputation","title":"tablite.imputation.imputation(T, targets, missing=None, method='carry forward', sources=None, tqdm=_tqdm, pbar=None)","text":"

    In statistics, imputation is the process of replacing missing data with substituted values.

    See more: https://en.wikipedia.org/wiki/Imputation_(statistics)

    PARAMETER DESCRIPTION table

    source table.

    TYPE: Table

    targets

    column names to find and replace missing values

    TYPE: str or list of strings

    missing

    values to be replaced.

    TYPE: None or iterable DEFAULT: None

    method

    method to be used for replacement. Options:

    'carry forward': takes the previous value, and carries forward into fields where values are missing. +: quick. Realistic on time series. -: Can produce strange outliers.

    'mean': calculates the column mean (exclude missing) and copies the mean in as replacement. +: quick -: doesn't work on text. Causes data set to drift towards the mean.

    'mode': calculates the column mode (exclude missing) and copies the mean in as replacement. +: quick -: most frequent value becomes over-represented in the sample

    'nearest neighbour': calculates normalised distance between items in source columns selects nearest neighbour and copies value as replacement. +: works for any datatype. -: computationally intensive (e.g. slow)

    TYPE: str DEFAULT: 'carry forward'

    sources

    NEAREST NEIGHBOUR ONLY column names to be used during imputation. if None or empty, all columns will be used.

    TYPE: list of strings DEFAULT: None

    RETURNS DESCRIPTION table

    table with replaced values.

    Source code in tablite/imputation.py
    def imputation(T, targets, missing=None, method=\"carry forward\", sources=None, tqdm=_tqdm, pbar=None):\n    \"\"\"\n    In statistics, imputation is the process of replacing missing data with substituted values.\n\n    See more: https://en.wikipedia.org/wiki/Imputation_(statistics)\n\n    Args:\n        table (Table): source table.\n\n        targets (str or list of strings): column names to find and\n            replace missing values\n\n        missing (None or iterable): values to be replaced.\n\n        method (str): method to be used for replacement. Options:\n\n            'carry forward':\n                takes the previous value, and carries forward into fields\n                where values are missing.\n                +: quick. Realistic on time series.\n                -: Can produce strange outliers.\n\n            'mean':\n                calculates the column mean (exclude `missing`) and copies\n                the mean in as replacement.\n                +: quick\n                -: doesn't work on text. Causes data set to drift towards the mean.\n\n            'mode':\n                calculates the column mode (exclude `missing`) and copies\n                the mean in as replacement.\n                +: quick\n                -: most frequent value becomes over-represented in the sample\n\n            'nearest neighbour':\n                calculates normalised distance between items in source columns\n                selects nearest neighbour and copies value as replacement.\n                +: works for any datatype.\n                -: computationally intensive (e.g. slow)\n\n        sources (list of strings): NEAREST NEIGHBOUR ONLY\n            column names to be used during imputation.\n            if None or empty, all columns will be used.\n\n    Returns:\n        table: table with replaced values.\n    \"\"\"\n    sub_cls_check(T, BaseTable)\n\n    if isinstance(targets, str) and targets not in T.columns:\n        targets = [targets]\n    if isinstance(targets, list):\n        for name in targets:\n            if not isinstance(name, str):\n                raise TypeError(f\"expected str, not {type(name)}\")\n            if name not in T.columns:\n                raise ValueError(f\"target item {name} not a column name in T.columns:\\n{T.columns}\")\n    else:\n        raise TypeError(\"Expected source as list of column names\")\n\n    if missing is None:\n        missing = {None}\n    else:\n        missing = set(missing)\n\n    if method == \"nearest neighbour\":\n        if sources in (None, []):\n            sources = list(T.columns)\n        if isinstance(sources, str):\n            sources = [sources]\n        if isinstance(sources, list):\n            for name in sources:\n                if not isinstance(name, str):\n                    raise TypeError(f\"expected str, not {type(name)}\")\n                if name not in T.columns:\n                    raise ValueError(f\"source item {name} not a column name in T.columns:\\n{T.columns}\")\n        else:\n            raise TypeError(\"Expected source as list of column names\")\n\n    methods = [\"nearest neighbour\", \"mean\", \"mode\", \"carry forward\"]\n\n    if method == \"carry forward\":\n        return carry_forward(T, targets, missing, tqdm=tqdm, pbar=pbar)\n    elif method in {\"mean\", \"mode\"}:\n        return stats_method(T, targets, missing, method, tqdm=tqdm, pbar=pbar)\n    elif method == \"nearest neighbour\":\n        return nearest_neighbour(T, sources, missing, targets, tqdm=tqdm)\n    else:\n        raise ValueError(f\"method {method} not recognised amonst known methods: {list(methods)})\")\n
    "},{"location":"reference/imputation/#tablite.imputation.carry_forward","title":"tablite.imputation.carry_forward(T, targets, missing, tqdm=_tqdm, pbar=None)","text":"Source code in tablite/imputation.py
    def carry_forward(T, targets, missing, tqdm=_tqdm, pbar=None):\n    assert isinstance(missing, set)\n\n    if pbar is None:\n        total = len(targets) * len(T)\n        pbar = tqdm(total=total, desc=\"imputation.carry_forward\", disable=Config.TQDM_DISABLE)\n\n    new = T.copy()\n    for name in T.columns:\n        if name in targets:\n            data = T[name][:]  # create copy\n            last_value = None\n            for ix, v in enumerate(data):\n                if v in missing:  # perform replacement\n                    data[ix] = last_value\n                else:  # keep last value.\n                    last_value = v\n                pbar.update(1)\n            new[name] = data\n        else:\n            new[name] = T[name]\n\n    return new\n
    "},{"location":"reference/imputation/#tablite.imputation.stats_method","title":"tablite.imputation.stats_method(T, targets, missing, method, tqdm=_tqdm, pbar=None)","text":"Source code in tablite/imputation.py
    def stats_method(T, targets, missing, method, tqdm=_tqdm, pbar=None):\n    assert isinstance(missing, set)\n\n    if pbar is None:\n        total = len(targets)\n        pbar = tqdm(total=total, desc=f\"imputation.{method}\", disable=Config.TQDM_DISABLE)\n\n    new = T.copy()\n    for name in T.columns:\n        if name in targets:\n            col = T.columns[name]\n            assert isinstance(col, Column)\n\n            hist_values, hist_counts = col.histogram()\n\n            for m in missing:\n                try:\n                    idx = hist_values.index(m)\n                    hist_counts[idx] = 0\n                except ValueError:\n                    pass\n\n            stats = summary_statistics(hist_values, hist_counts)\n\n            new_value = stats[method]\n            col.replace(mapping={m: new_value for m in missing})\n            new[name] = col\n            pbar.update(1)\n        else:\n            new[name] = T[name]  # no entropy, keep as is.\n\n    return new\n
    "},{"location":"reference/imputation/#tablite.imputation-modules","title":"Modules","text":""},{"location":"reference/joins/","title":"Joins","text":""},{"location":"reference/joins/#tablite.joins","title":"tablite.joins","text":""},{"location":"reference/joins/#tablite.joins-classes","title":"Classes","text":""},{"location":"reference/joins/#tablite.joins-functions","title":"Functions","text":""},{"location":"reference/joins/#tablite.joins.join","title":"tablite.joins.join(T: BaseTable, other: BaseTable, left_keys: List[str], right_keys: List[str], left_columns: Union[List[str], None], right_columns: Union[List[str], None], kind: str = 'inner', merge_keys: bool = False, tqdm=_tqdm, pbar=None)","text":"

    short-cut for all join functions.

    PARAMETER DESCRIPTION T

    left table

    TYPE: Table

    other

    right table

    TYPE: Table

    left_keys

    list of keys for the join from left table.

    TYPE: list

    right_keys

    list of keys for the join from right table.

    TYPE: list

    left_columns

    list of columns names to retain from left table. If None, all are retained.

    TYPE: list

    right_columns

    list of columns names to retain from right table. If None, all are retained.

    TYPE: list

    kind

    'inner', 'left', 'outer', 'cross'. Defaults to \"inner\".

    TYPE: str DEFAULT: 'inner'

    tqdm

    tqdm progress counter. Defaults to _tqdm.

    TYPE: tqdm DEFAULT: tqdm

    pbar

    tqdm.progressbar. Defaults to None.

    TYPE: pbar DEFAULT: None

    RAISES DESCRIPTION ValueError

    if join type is unknown.

    RETURNS DESCRIPTION Table

    joined table.

    Example: \"inner\"

    SQL:   SELECT number, letter FROM numbers JOIN letters ON numbers.colour == letters.color\n

    Tablite:

    >>> inner_join = numbers.inner_join(\n    letters, \n    left_keys=['colour'], \n    right_keys=['color'], \n    left_columns=['number'], \n    right_columns=['letter']\n)\n

    Example: \"left\"

    SQL:   SELECT number, letter FROM numbers LEFT JOIN letters ON numbers.colour == letters.color\n

    Tablite:

    >>> left_join = numbers.left_join(\n    letters, \n    left_keys=['colour'], \n    right_keys=['color'], \n    left_columns=['number'], \n    right_columns=['letter']\n)\n

    Example: \"outer\"

    SQL:   SELECT number, letter FROM numbers OUTER JOIN letters ON numbers.colour == letters.color\n

    Tablite:

    >>> outer_join = numbers.outer_join(\n    letters, \n    left_keys=['colour'], \n    right_keys=['color'], \n    left_columns=['number'], \n    right_columns=['letter']\n    )\n

    Example: \"cross\"

    CROSS JOIN returns the Cartesian product of rows from tables in the join. In other words, it will produce rows which combine each row from the first table with each row from the second table

    Source code in tablite/joins.py
    def join(\n    T: BaseTable,\n    other: BaseTable,\n    left_keys: List[str],\n    right_keys: List[str],\n    left_columns: Union[List[str], None],\n    right_columns: Union[List[str], None],\n    kind: str = \"inner\",\n    merge_keys: bool = False,\n    tqdm=_tqdm,\n    pbar=None,\n):\n    \"\"\"short-cut for all join functions.\n\n    Args:\n        T (Table): left table\n        other (Table): right table\n        left_keys (list): list of keys for the join from left table.\n        right_keys (list): list of keys for the join from right table.\n        left_columns (list): list of columns names to retain from left table.\n            If None, all are retained.\n        right_columns (list): list of columns names to retain from right table.\n            If None, all are retained.\n        kind (str, optional): 'inner', 'left', 'outer', 'cross'. Defaults to \"inner\".\n        tqdm (tqdm, optional): tqdm progress counter. Defaults to _tqdm.\n        pbar (tqdm.pbar, optional): tqdm.progressbar. Defaults to None.\n\n    Raises:\n        ValueError: if join type is unknown.\n\n    Returns:\n        Table: joined table.\n\n    Example: \"inner\"\n    ```\n    SQL:   SELECT number, letter FROM numbers JOIN letters ON numbers.colour == letters.color\n    ```\n    Tablite: \n    ```\n    >>> inner_join = numbers.inner_join(\n        letters, \n        left_keys=['colour'], \n        right_keys=['color'], \n        left_columns=['number'], \n        right_columns=['letter']\n    )\n    ```\n\n    Example: \"left\" \n    ```\n    SQL:   SELECT number, letter FROM numbers LEFT JOIN letters ON numbers.colour == letters.color\n    ```\n    Tablite: \n    ```\n    >>> left_join = numbers.left_join(\n        letters, \n        left_keys=['colour'], \n        right_keys=['color'], \n        left_columns=['number'], \n        right_columns=['letter']\n    )\n    ```\n\n    Example: \"outer\"\n    ```\n    SQL:   SELECT number, letter FROM numbers OUTER JOIN letters ON numbers.colour == letters.color\n    ```\n\n    Tablite: \n    ```\n    >>> outer_join = numbers.outer_join(\n        letters, \n        left_keys=['colour'], \n        right_keys=['color'], \n        left_columns=['number'], \n        right_columns=['letter']\n        )\n    ```\n\n    Example: \"cross\"\n\n    CROSS JOIN returns the Cartesian product of rows from tables in the join.\n    In other words, it will produce rows which combine each row from the first table\n    with each row from the second table\n    \"\"\"\n    if left_columns is None:\n        left_columns = list(T.columns)\n    if right_columns is None:\n        right_columns = list(other.columns)\n    assert merge_keys in {True,False}\n\n    _jointype_check(T, other, left_keys, right_keys, left_columns, right_columns)\n\n    return _join(kind, T,other,left_keys, right_keys, left_columns, right_columns, merge_keys=merge_keys,\n             tqdm=tqdm, pbar=pbar)\n
    "},{"location":"reference/joins/#tablite.joins.inner_join","title":"tablite.joins.inner_join(T: BaseTable, other: BaseTable, left_keys: List[str], right_keys: List[str], left_columns: Union[List[str], None], right_columns: Union[List[str], None], merge_keys: bool = False, tqdm=_tqdm, pbar=None)","text":"Source code in tablite/joins.py
    def inner_join(T: BaseTable, other: BaseTable, left_keys: List[str], right_keys: List[str], \n              left_columns: Union[List[str], None], right_columns: Union[List[str], None],\n              merge_keys: bool = False, tqdm=_tqdm, pbar=None):\n    return join(T, other, left_keys, right_keys, left_columns, right_columns, kind=\"inner\", merge_keys=merge_keys, tqdm=tqdm,pbar=pbar)\n
    "},{"location":"reference/joins/#tablite.joins.left_join","title":"tablite.joins.left_join(T: BaseTable, other: BaseTable, left_keys: List[str], right_keys: List[str], left_columns: Union[List[str], None], right_columns: Union[List[str], None], merge_keys: bool = False, tqdm=_tqdm, pbar=None)","text":"Source code in tablite/joins.py
    def left_join(T: BaseTable, other: BaseTable, left_keys: List[str], right_keys: List[str], \n              left_columns: Union[List[str], None], right_columns: Union[List[str], None],\n              merge_keys: bool = False, tqdm=_tqdm, pbar=None):\n    return join(T, other, left_keys, right_keys, left_columns, right_columns, kind=\"left\", merge_keys=merge_keys, tqdm=tqdm,pbar=pbar)\n
    "},{"location":"reference/joins/#tablite.joins.outer_join","title":"tablite.joins.outer_join(T: BaseTable, other: BaseTable, left_keys: List[str], right_keys: List[str], left_columns: Union[List[str], None], right_columns: Union[List[str], None], merge_keys: bool = False, tqdm=_tqdm, pbar=None)","text":"Source code in tablite/joins.py
    def outer_join(T: BaseTable, other: BaseTable, left_keys: List[str], right_keys: List[str], \n              left_columns: Union[List[str], None], right_columns: Union[List[str], None],\n              merge_keys: bool = False, tqdm=_tqdm, pbar=None):\n    return join(T, other, left_keys, right_keys, left_columns, right_columns, kind=\"outer\", merge_keys=merge_keys, tqdm=tqdm,pbar=pbar)\n
    "},{"location":"reference/joins/#tablite.joins.cross_join","title":"tablite.joins.cross_join(T: BaseTable, other: BaseTable, left_keys: List[str], right_keys: List[str], left_columns: Union[List[str], None], right_columns: Union[List[str], None], merge_keys: bool = False, tqdm=_tqdm, pbar=None)","text":"Source code in tablite/joins.py
    def cross_join(T: BaseTable, other: BaseTable, left_keys: List[str], right_keys: List[str], \n              left_columns: Union[List[str], None], right_columns: Union[List[str], None],\n              merge_keys: bool = False, tqdm=_tqdm, pbar=None):\n    return join(T, other, left_keys, right_keys, left_columns, right_columns, kind=\"cross\", merge_keys=merge_keys, tqdm=tqdm,pbar=pbar)\n
    "},{"location":"reference/lookup/","title":"Lookup","text":""},{"location":"reference/lookup/#tablite.lookup","title":"tablite.lookup","text":""},{"location":"reference/lookup/#tablite.lookup-attributes","title":"Attributes","text":""},{"location":"reference/lookup/#tablite.lookup-classes","title":"Classes","text":""},{"location":"reference/lookup/#tablite.lookup-functions","title":"Functions","text":""},{"location":"reference/lookup/#tablite.lookup.lookup","title":"tablite.lookup.lookup(T, other, *criteria, all=True, tqdm=_tqdm)","text":"

    function for looking up values in other according to criteria in ascending order. :param: T: Table :param: other: Table sorted in ascending search order. :param: criteria: Each criteria must be a tuple with value comparisons in the form: (LEFT, OPERATOR, RIGHT) :param: all: boolean: True=ALL, False=ANY

    OPERATOR must be a callable that returns a boolean LEFT must be a value that the OPERATOR can compare. RIGHT must be a value that the OPERATOR can compare.

    Examples:

    comparison of two columns:

    ('column A', \"==\", 'column B')\n

    compare value from column 'Date' with date 24/12.

    ('Date', \"<\", DataTypes.date(24,12) )\n

    uses custom function to compare value from column 'text 1' with value from column 'text 2'

    f = lambda L,R: all( ord(L) < ord(R) )\n('text 1', f, 'text 2')\n
    Source code in tablite/lookup.py
    def lookup(T, other, *criteria, all=True, tqdm=_tqdm):\n    \"\"\"function for looking up values in `other` according to criteria in ascending order.\n    :param: T: Table \n    :param: other: Table sorted in ascending search order.\n    :param: criteria: Each criteria must be a tuple with value comparisons in the form:\n        (LEFT, OPERATOR, RIGHT)\n    :param: all: boolean: True=ALL, False=ANY\n\n    OPERATOR must be a callable that returns a boolean\n    LEFT must be a value that the OPERATOR can compare.\n    RIGHT must be a value that the OPERATOR can compare.\n\n    Examples:\n        comparison of two columns:\n\n            ('column A', \"==\", 'column B')\n\n        compare value from column 'Date' with date 24/12.\n\n            ('Date', \"<\", DataTypes.date(24,12) )\n\n        uses custom function to compare value from column\n        'text 1' with value from column 'text 2'\n\n            f = lambda L,R: all( ord(L) < ord(R) )\n            ('text 1', f, 'text 2')\n\n    \"\"\"\n    sub_cls_check(T, BaseTable)\n    sub_cls_check(other, BaseTable)\n\n    all = all\n    any = not all\n\n    ops = lookup_ops\n\n    functions, left_criteria, right_criteria = [], set(), set()\n\n    for left, op, right in criteria:\n        left_criteria.add(left)\n        right_criteria.add(right)\n        if callable(op):\n            pass  # it's a custom function.\n        else:\n            op = ops.get(op, None)\n            if not callable(op):\n                raise ValueError(f\"{op} not a recognised operator for comparison.\")\n\n        functions.append((op, left, right))\n    left_columns = [n for n in left_criteria if n in T.columns]\n    right_columns = [n for n in right_criteria if n in other.columns]\n\n    result_index = np.empty(shape=(len(T)), dtype=np.int64)\n    cache = {}\n    left = T[left_columns]\n    Constr = type(T)\n    if isinstance(left, Column):\n        tmp, left = left, Constr()\n        left[left_columns[0]] = tmp\n    right = other[right_columns]\n    if isinstance(right, Column):\n        tmp, right = right, Constr()\n        right[right_columns[0]] = tmp\n    assert isinstance(left, BaseTable)\n    assert isinstance(right, BaseTable)\n\n    for ix, row1 in tqdm(enumerate(left.rows), total=len(T), disable=Config.TQDM_DISABLE):\n        row1_tup = tuple(row1)\n        row1d = {name: value for name, value in zip(left_columns, row1)}\n        row1_hash = hash(row1_tup)\n\n        match_found = True if row1_hash in cache else False\n\n        if not match_found:  # search.\n            for row2ix, row2 in enumerate(right.rows):\n                row2d = {name: value for name, value in zip(right_columns, row2)}\n\n                evaluations = {op(row1d.get(left, left), row2d.get(right, right)) for op, left, right in functions}\n                # The evaluations above does a neat trick:\n                # as L is a dict, L.get(left, L) will return a value\n                # from the columns IF left is a column name. If it isn't\n                # the function will treat left as a value.\n                # The same applies to right.\n                all_ = all and (False not in evaluations)\n                any_ = any and True in evaluations\n                if all_ or any_:\n                    match_found = True\n                    cache[row1_hash] = row2ix\n                    break\n\n        if not match_found:  # no match found.\n            cache[row1_hash] = -1  # -1 is replacement for None in the index as numpy can't handle Nones.\n\n        result_index[ix] = cache[row1_hash]\n\n    f = select_processing_method(2 * max(len(T), len(other)), _sp_lookup, _mp_lookup)\n    return f(T, other, result_index)\n
    "},{"location":"reference/match/","title":"Match","text":""},{"location":"reference/match/#tablite.match","title":"tablite.match","text":""},{"location":"reference/match/#tablite.match-classes","title":"Classes","text":""},{"location":"reference/match/#tablite.match-functions","title":"Functions","text":""},{"location":"reference/match/#tablite.match.match","title":"tablite.match.match(T, other, *criteria, keep_left=None, keep_right=None)","text":"

    performs inner join where T matches other and removes rows that do not match.

    :param: T: Table :param: other: Table :param: criteria: Each criteria must be a tuple with value comparisons in the form:

    (LEFT, OPERATOR, RIGHT), where operator must be \"==\"\n\nExample:\n    ('column A', \"==\", 'column B')\n\nThis syntax follows the lookup syntax. See Lookup for details.\n

    :param: keep_left: list of columns to keep. :param: keep_right: list of right columns to keep.

    Source code in tablite/match.py
    def match(T, other, *criteria, keep_left=None, keep_right=None):  # lookup and filter combined - drops unmatched rows.\n    \"\"\"\n    performs inner join where `T` matches `other` and removes rows that do not match.\n\n    :param: T: Table\n    :param: other: Table\n    :param: criteria: Each criteria must be a tuple with value comparisons in the form:\n\n        (LEFT, OPERATOR, RIGHT), where operator must be \"==\"\n\n        Example:\n            ('column A', \"==\", 'column B')\n\n        This syntax follows the lookup syntax. See Lookup for details.\n\n    :param: keep_left: list of columns to keep.\n    :param: keep_right: list of right columns to keep.\n    \"\"\"\n    assert isinstance(T, BaseTable)\n    assert isinstance(other, BaseTable)\n    if keep_left is None:\n        keep_left = [n for n in T.columns]\n    else:\n        type_check(keep_left, list)\n        name_check(T.columns, *keep_left)\n\n    if keep_right is None:\n        keep_right = [n for n in other.columns]\n    else:\n        type_check(keep_right, list)\n        name_check(other.columns, *keep_right)\n\n    indices = np.full(shape=(len(T),), fill_value=-1, dtype=np.int64)\n    for arg in criteria:\n        b,_,a = arg\n        if _ != \"==\":\n            raise ValueError(\"match requires A == B. For other logic visit `lookup`\")\n        if b not in T.columns:\n            raise ValueError(f\"Column {b} not found in T for criteria: {arg}\")\n        if a not in other.columns:\n            raise ValueError(f\"Column {a} not found in T for criteria: {arg}\")\n\n        index_update = find_indices(other[a][:], T[b][:], fill_value=-1)\n        indices = merge_indices(indices, index_update)\n\n    cls = type(T)\n    new = cls()\n    for name in T.columns:\n        if name in keep_left:\n            new[name] = np.compress(indices != -1, T[name][:])\n\n    for name in other.columns:\n        if name in keep_right:\n            new_name = unique_name(name, new.columns)\n            primary = np.compress(indices != -1, indices)\n            new[new_name] = np.take(other[name][:], primary)\n\n    return new\n
    "},{"location":"reference/match/#tablite.match.find_indices","title":"tablite.match.find_indices(x, y, fill_value=-1)","text":"

    finds index of y in x

    Source code in tablite/match.py
    def find_indices(x,y, fill_value=-1):  # fast.\n    \"\"\"\n    finds index of y in x\n    \"\"\"\n    # disassembly of numpy:\n    # import numpy as np\n    # x = np.array([3, 5, 7,  1,   9, 8, 6, 6])\n    # y = np.array([2, 1, 5, 10, 100, 6])\n    index = np.argsort(x)  # array([3, 0, 1, 6, 7, 2, 5, 4])\n    sorted_x = x[index]  # array([1, 3, 5, 6, 6, 7, 8, 9])\n    sorted_index = np.searchsorted(sorted_x, y)  # array([1, 0, 2, 8, 8, 3])\n    yindex = np.take(index, sorted_index, mode=\"clip\")  # array([0, 3, 1, 4, 4, 6])\n    mask = x[yindex] != y  # array([ True, False, False,  True,  True, False])\n    indices = np.ma.array(yindex, mask=mask, fill_value=fill_value)  \n    # masked_array(data=[--, 3, 1, --, --, 6], mask=[ True, False, False,  True,  True, False], fill_value=999999)\n    # --: y[0] not in x\n    # 3 : y[1] == x[3]\n    # 1 : y[2] == x[1]\n    # --: y[3] not in x\n    # --: y[4] not in x\n    # --: y[5] == x[6]\n    result = np.where(~indices.mask, indices.data, -1)  \n    return result  # array([-1,  3,  1, -1, -1,  6])\n
    "},{"location":"reference/match/#tablite.match.merge_indices","title":"tablite.match.merge_indices(x1, *args, fill_value=-1)","text":"

    merges x1 and x2 where

    Source code in tablite/match.py
    def merge_indices(x1, *args, fill_value=-1):\n    \"\"\"\n    merges x1 and x2 where \n    \"\"\"\n    # dis:\n    # >>> AA = array([-1,  3, -1, 5])\n    # >>> BB = array([-1, -1,  4, 5])\n    new = x1[:]  # = AA\n    for arg in args:\n        mask = (new == fill_value)  # array([True, False, True, False])\n        new = np.where(mask, arg, new)  # array([-1, 3, 4, 5])\n    return new   # array([-1, 3, 4, 5])\n
    "},{"location":"reference/merge/","title":"Merge","text":""},{"location":"reference/merge/#tablite.merge","title":"tablite.merge","text":""},{"location":"reference/merge/#tablite.merge-classes","title":"Classes","text":""},{"location":"reference/merge/#tablite.merge-functions","title":"Functions","text":""},{"location":"reference/merge/#tablite.merge.where","title":"tablite.merge.where(T, criteria, left, right, new)","text":"

    takes from LEFT where criteria is True else RIGHT and creates a single new column.

    :param: T: Table :param: criteria: np.array(bool): if True take left column else take right column :param left: (str) column name :param right: (str) column name :param new: (str) new name

    :returns: T

    Source code in tablite/merge.py
    def where(T, criteria, left, right, new):\n    \"\"\" takes from LEFT where criteria is True else RIGHT \n    and creates a single new column.\n\n    :param: T: Table\n    :param: criteria: np.array(bool): \n            if True take left column\n            else take right column\n    :param left: (str) column name\n    :param right: (str) column name\n    :param new: (str) new name\n\n    :returns: T\n    \"\"\"\n    type_check(T, BaseTable)\n    if isinstance(criteria, np.ndarray):\n        if not criteria.dtype == \"bool\":\n            raise TypeError\n    else:\n        criteria = np.array(criteria, dtype='bool')\n\n    new_uq = unique_name(new, list(T.columns))\n    T.add_column(new_uq)\n    col = T[new_uq]\n\n    for start,end in Config.page_steps(len(criteria)):\n        left_values = T[left][start:end]\n        right_values = T[right][start:end]\n        new_values = np.where(criteria, left_values, right_values)\n        col.extend(new_values)\n\n    if new == right:\n        T[right] = T[new_uq]  # keep column order\n        del T[new_uq]\n        del T[left]\n    elif new == left:\n        T[left] = T[new_uq]  # keep column order\n        del T[new_uq]\n        del T[right]\n    else:\n        T[new] = T[new_uq]\n        del T[left]\n        del T[right]\n    return T\n
    "},{"location":"reference/mp_utils/","title":"Mp utils","text":""},{"location":"reference/mp_utils/#tablite.mp_utils","title":"tablite.mp_utils","text":""},{"location":"reference/mp_utils/#tablite.mp_utils-attributes","title":"Attributes","text":""},{"location":"reference/mp_utils/#tablite.mp_utils.lookup_ops","title":"tablite.mp_utils.lookup_ops = {'in': _in, 'not in': not_in, '<': operator.lt, '<=': operator.le, '>': operator.gt, '>=': operator.ge, '!=': operator.ne, '==': operator.eq} module-attribute","text":""},{"location":"reference/mp_utils/#tablite.mp_utils.filter_ops","title":"tablite.mp_utils.filter_ops = {'>': operator.gt, '>=': operator.ge, '==': operator.eq, '<': operator.lt, '<=': operator.le, '!=': operator.ne, 'in': _in} module-attribute","text":""},{"location":"reference/mp_utils/#tablite.mp_utils.filter_ops_from_text","title":"tablite.mp_utils.filter_ops_from_text = {'gt': '>', 'gteq': '>=', 'eq': '==', 'lt': '<', 'lteq': '<=', 'neq': '!=', 'in': _in} module-attribute","text":""},{"location":"reference/mp_utils/#tablite.mp_utils-classes","title":"Classes","text":""},{"location":"reference/mp_utils/#tablite.mp_utils-functions","title":"Functions","text":""},{"location":"reference/mp_utils/#tablite.mp_utils.not_in","title":"tablite.mp_utils.not_in(a, b)","text":"Source code in tablite/mp_utils.py
    def not_in(a, b):\n    return not operator.contains(str(a), str(b))\n
    "},{"location":"reference/mp_utils/#tablite.mp_utils.is_mp","title":"tablite.mp_utils.is_mp(fields: int) -> bool","text":"PARAMETER DESCRIPTION fields

    number of fields

    TYPE: int

    RETURNS DESCRIPTION bool

    bool

    Source code in tablite/mp_utils.py
    def is_mp(fields: int) -> bool:\n    \"\"\"\n\n    Args:\n        fields (int): number of fields\n\n    Returns:\n        bool\n    \"\"\"\n    if Config.MULTIPROCESSING_MODE == Config.FORCE:\n        return True\n\n    if Config.MULTIPROCESSING_MODE == Config.FALSE:\n        return False\n\n    if fields < Config.SINGLE_PROCESSING_LIMIT:\n        return False\n\n    if max(psutil.cpu_count(logical=False), 1) < 2:\n        return False\n\n    return True\n
    "},{"location":"reference/mp_utils/#tablite.mp_utils.select_processing_method","title":"tablite.mp_utils.select_processing_method(fields, sp, mp)","text":"PARAMETER DESCRIPTION fields

    number of fields

    TYPE: int

    sp

    method for single processing

    TYPE: callable

    mp

    method for multiprocessing

    TYPE: callable

    RETURNS DESCRIPTION _type_

    description

    Source code in tablite/mp_utils.py
    def select_processing_method(fields, sp, mp):\n    \"\"\"\n\n    Args:\n        fields (int): number of fields\n        sp (callable): method for single processing\n        mp (callable): method for multiprocessing\n\n    Returns:\n        _type_: _description_\n    \"\"\"\n    return mp if is_mp(fields) else sp\n
    "},{"location":"reference/mp_utils/#tablite.mp_utils.maskify","title":"tablite.mp_utils.maskify(arr)","text":"Source code in tablite/mp_utils.py
    def maskify(arr):\n    none_mask = [False] * len(arr)  # Setting the default\n\n    for i in range(len(arr)):\n        if arr[i] is None:  # Check if our value is None\n            none_mask[i] = True\n            arr[i] = 0  # Remove None from the original array\n\n    return none_mask\n
    "},{"location":"reference/mp_utils/#tablite.mp_utils.share_mem","title":"tablite.mp_utils.share_mem(inp_arr, dtype)","text":"Source code in tablite/mp_utils.py
    def share_mem(inp_arr, dtype):\n    len_ = len(inp_arr)\n    size = np.dtype(dtype).itemsize * len_\n    shape = (len_,)\n\n    out_shm = shared_memory.SharedMemory(create=True, size=size)  # the co_processors will read this.\n    out_arr_index = np.ndarray(shape, dtype=dtype, buffer=out_shm.buf)\n    out_arr_index[:] = inp_arr\n\n    return out_arr_index, out_shm\n
    "},{"location":"reference/mp_utils/#tablite.mp_utils.map_task","title":"tablite.mp_utils.map_task(data_shm_name, index_shm_name, destination_shm_name, shape, dtype, start, end)","text":"Source code in tablite/mp_utils.py
    def map_task(data_shm_name, index_shm_name, destination_shm_name, shape, dtype, start, end):\n    # connect\n    shared_data = shared_memory.SharedMemory(name=data_shm_name)\n    data = np.ndarray(shape, dtype=dtype, buffer=shared_data.buf)\n\n    shared_index = shared_memory.SharedMemory(name=index_shm_name)\n    index = np.ndarray(shape, dtype=np.int64, buffer=shared_index.buf)\n\n    shared_target = shared_memory.SharedMemory(name=destination_shm_name)\n    target = np.ndarray(shape, dtype=dtype, buffer=shared_target.buf)\n    # work\n    target[start:end] = np.take(data[start:end], index[start:end])\n    # disconnect\n    shared_data.close()\n    shared_index.close()\n    shared_target.close()\n
    "},{"location":"reference/mp_utils/#tablite.mp_utils.reindex_task","title":"tablite.mp_utils.reindex_task(src, dst, index_shm, shm_shape, start, end)","text":"Source code in tablite/mp_utils.py
    def reindex_task(src, dst, index_shm, shm_shape, start, end):\n    # connect\n    existing_shm = shared_memory.SharedMemory(name=index_shm)\n    shared_index = np.ndarray(shm_shape, dtype=np.int64, buffer=existing_shm.buf)\n    # work\n    array = load_numpy(src)\n    new = np.take(array, shared_index[start:end])\n    np.save(dst, new, allow_pickle=True, fix_imports=False)\n    # disconnect\n    existing_shm.close()\n
    "},{"location":"reference/nimlite/","title":"Nimlite","text":""},{"location":"reference/nimlite/#tablite.nimlite","title":"tablite.nimlite","text":""},{"location":"reference/nimlite/#tablite.nimlite-attributes","title":"Attributes","text":""},{"location":"reference/nimlite/#tablite.nimlite.paths","title":"tablite.nimlite.paths = sys.argv[:] module-attribute","text":""},{"location":"reference/nimlite/#tablite.nimlite.K","title":"tablite.nimlite.K = TypeVar('K', bound=BaseTable) module-attribute","text":""},{"location":"reference/nimlite/#tablite.nimlite.ValidEncoders","title":"tablite.nimlite.ValidEncoders = Literal['ENC_UTF8', 'ENC_UTF16', 'ENC_WIN1250'] module-attribute","text":""},{"location":"reference/nimlite/#tablite.nimlite.ValidQuoting","title":"tablite.nimlite.ValidQuoting = Literal['QUOTE_MINIMAL', 'QUOTE_ALL', 'QUOTE_NONNUMERIC', 'QUOTE_NONE', 'QUOTE_STRINGS', 'QUOTE_NOTNULL'] module-attribute","text":""},{"location":"reference/nimlite/#tablite.nimlite.ValidSkipEmpty","title":"tablite.nimlite.ValidSkipEmpty = Literal['NONE', 'ANY', 'ALL'] module-attribute","text":""},{"location":"reference/nimlite/#tablite.nimlite.ColumnSelectorDict","title":"tablite.nimlite.ColumnSelectorDict = TypedDict('ColumnSelectorDict', {'column': str, 'type': Literal['int', 'float', 'bool', 'str', 'date', 'time', 'datetime'], 'allow_empty': Union[bool, None], 'rename': Union[str, None]}) module-attribute","text":""},{"location":"reference/nimlite/#tablite.nimlite.FilterCriteria","title":"tablite.nimlite.FilterCriteria = Literal['>', '>=', '==', '<', '<=', '!=', 'in'] module-attribute","text":""},{"location":"reference/nimlite/#tablite.nimlite.FilterType","title":"tablite.nimlite.FilterType = Literal['all', 'any'] module-attribute","text":""},{"location":"reference/nimlite/#tablite.nimlite.FilterDict","title":"tablite.nimlite.FilterDict = TypedDict('FilterDict', {'column1': str, 'value1': Union[str, None], 'criteria': FilterCriteria, 'column2': str, 'value2': Union[str, None]}) module-attribute","text":""},{"location":"reference/nimlite/#tablite.nimlite-classes","title":"Classes","text":""},{"location":"reference/nimlite/#tablite.nimlite-functions","title":"Functions","text":""},{"location":"reference/nimlite/#tablite.nimlite.get_headers","title":"tablite.nimlite.get_headers(path: Union[str, Path], encoding: ValidEncoders = 'ENC_UTF8', *, header_row_index: int = 0, newline: str = '\\n', delimiter: str = ',', text_qualifier: str = '\"', quoting: ValidQuoting, strip_leading_and_tailing_whitespace: bool = True, linecount: int = 10) -> list[list[str]]","text":"Source code in tablite/nimlite.py
    def get_headers(\n    path: Union[str, Path],\n    encoding: ValidEncoders =\"ENC_UTF8\",\n    *,\n    header_row_index: int=0,\n    newline: str='\\n', delimiter: str=',', text_qualifier: str='\"',\n    quoting: ValidQuoting, strip_leading_and_tailing_whitespace: bool=True,\n    linecount: int = 10\n) -> list[list[str]]:\n    return nl.get_headers(\n            path=str(path),\n            encoding=encoding,\n            newline=newline, delimiter=delimiter, text_qualifier=text_qualifier,\n            strip_leading_and_tailing_whitespace=strip_leading_and_tailing_whitespace,\n            header_row_index=header_row_index,\n            quoting=quoting,\n            linecount=linecount\n        )\n
    "},{"location":"reference/nimlite/#tablite.nimlite.text_reader","title":"tablite.nimlite.text_reader(T: Type[K], pid: str, path: Union[str, Path], encoding: ValidEncoders = 'ENC_UTF8', *, first_row_has_headers: bool = True, header_row_index: int = 0, columns: List[Union[str, None]] = None, start: Union[str, None] = None, limit: Union[str, None] = None, guess_datatypes: bool = False, newline: str = '\\n', delimiter: str = ',', text_qualifier: str = '\"', quoting: ValidQuoting, strip_leading_and_tailing_whitespace: bool = True, skip_empty: ValidSkipEmpty = 'NONE', tqdm=_tqdm) -> K","text":"Source code in tablite/nimlite.py
    def text_reader(\n    T: Type[K],\n    pid: str, path: Union[str, Path],\n    encoding: ValidEncoders =\"ENC_UTF8\",\n    *,\n    first_row_has_headers: bool=True, header_row_index: int=0,\n    columns: List[Union[str, None]]=None,\n    start: Union[str, None] = None, limit: Union[str, None]=None,\n    guess_datatypes: bool =False,\n    newline: str='\\n', delimiter: str=',', text_qualifier: str='\"',\n    quoting: ValidQuoting, strip_leading_and_tailing_whitespace: bool=True, skip_empty: ValidSkipEmpty = \"NONE\",\n    tqdm=_tqdm\n) -> K:\n    assert isinstance(path, Path)\n    assert isinstance(pid, Path)\n    with tqdm(total=10, desc=f\"importing file\") as pbar:\n        table = nl.text_reader(\n            pid=str(pid),\n            path=str(path),\n            encoding=encoding,\n            first_row_has_headers=first_row_has_headers, header_row_index=header_row_index,\n            columns=columns,\n            start=start, limit=limit,\n            guess_datatypes=guess_datatypes,\n            newline=newline, delimiter=delimiter, text_qualifier=text_qualifier,\n            quoting=quoting,\n            strip_leading_and_tailing_whitespace=strip_leading_and_tailing_whitespace,\n            skip_empty=skip_empty,\n            page_size=Config.PAGE_SIZE\n        )\n\n        pbar.update(1)\n\n        task_info = table[\"task\"]\n        task_columns = table[\"columns\"]\n\n        ti_tasks = task_info[\"tasks\"]\n        ti_import_field_names = task_info[\"import_field_names\"]\n\n        is_windows = platform.system() == \"Windows\"\n        use_logical = False if is_windows else True\n\n        cpus = max(psutil.cpu_count(logical=use_logical), 1)\n\n        pbar_step = 4 / max(len(ti_tasks), 1)\n\n        class WrapUpdate:\n            def update(self, n):\n                pbar.update(n * pbar_step)\n\n        wrapped_pbar = WrapUpdate()\n\n        def next_task(task: Task, page_info):\n            wrapped_pbar.update(1)\n            return Task(\n                nl.text_reader_task,\n                *task.args, **task.kwargs, page_info=page_info\n            )\n\n        tasks = [\n            TaskChain(\n                Task(\n                    nl.collect_text_reader_page_info_task,\n                    task=t,\n                    task_info=task_info\n                ), next_task=next_task\n            ) for t in ti_tasks\n        ]\n\n        is_sp = False\n\n        if Config.MULTIPROCESSING_MODE == Config.FALSE:\n            is_sp = True\n        elif Config.MULTIPROCESSING_MODE == Config.FORCE:\n            is_sp = False\n        elif Config.MULTIPROCESSING_MODE == Config.AUTO and cpus <= 1 or len(tasks) <= 1:\n            is_sp = True\n\n        if is_sp:\n            res = []\n\n            for task in tasks:\n                page = task.execute()\n\n                res.append(page)\n        else:\n            with TaskManager(cpus, error_mode=\"exception\") as tm:\n                res = tm.execute(tasks, pbar=wrapped_pbar)\n\n        col_path = pid\n        column_dict = {\n            cols: Column(col_path)\n            for cols in ti_import_field_names\n        }\n\n        for res_pages in res:\n            col_map = {\n                n: res_pages[i]\n                for i, n in enumerate(ti_import_field_names)\n            }\n\n            for k, c in column_dict.items():\n                c.pages.append(col_map[k])\n\n        if columns is None:\n            columns = [c[\"name\"] for c in task_columns]\n\n        table_dict = {\n            a[\"name\"]: column_dict[b]\n            for a, b in zip(task_columns, columns)\n        }\n\n        pbar.update(pbar.total - pbar.n)\n\n        table = T(columns=table_dict)\n\n    return table\n
    "},{"location":"reference/nimlite/#tablite.nimlite.wrap","title":"tablite.nimlite.wrap(str_: str) -> str","text":"Source code in tablite/nimlite.py
    def wrap(str_: str) -> str:\n    return '\"' + str_.replace('\"', '\\\\\"').replace(\"'\", \"\\\\'\").replace(\"\\n\", \"\\\\n\").replace(\"\\t\", \"\\\\t\") + '\"'\n
    "},{"location":"reference/nimlite/#tablite.nimlite.column_select","title":"tablite.nimlite.column_select(table: K, cols: list[ColumnSelectorDict], tqdm=_tqdm, TaskManager=TaskManager) -> Tuple[K, K]","text":"Source code in tablite/nimlite.py
    def column_select(table: K, cols: list[ColumnSelectorDict], tqdm=_tqdm, TaskManager=TaskManager) -> Tuple[K, K]:\n    with tqdm(total=100, desc=\"column select\", bar_format='{desc}: {percentage:.1f}%|{bar}{r_bar}') as pbar:\n        T = type(table)\n        dir_pid = Config.workdir / Config.pid\n\n        col_infos = nl.collect_column_select_info(table, cols, str(dir_pid), pbar)\n\n        columns = col_infos[\"columns\"]\n        page_count = col_infos[\"page_count\"]\n        is_correct_type = col_infos[\"is_correct_type\"]\n        desired_column_map = col_infos[\"desired_column_map\"]\n        original_pages_map = col_infos[\"original_pages_map\"]\n        passed_column_data = col_infos[\"passed_column_data\"]\n        failed_column_data = col_infos[\"failed_column_data\"]\n        res_cols_pass = col_infos[\"res_cols_pass\"]\n        res_cols_fail = col_infos[\"res_cols_fail\"]\n        column_names = col_infos[\"column_names\"]\n        reject_reason_name = col_infos[\"reject_reason_name\"]\n\n        if all(is_correct_type.values()):\n            tbl_pass_columns = {\n                desired_name: table[desired_info[0]]\n                for desired_name, desired_info in desired_column_map.items()\n            }\n\n            tbl_fail_columns = {\n                desired_name: []\n                for desired_name in failed_column_data\n            }\n\n            tbl_pass = T(columns=tbl_pass_columns)\n            tbl_fail = T(columns=tbl_fail_columns)\n\n            return (tbl_pass, tbl_fail)\n\n        task_list_inp = (\n            _collect_cs_info(i, columns, res_cols_pass, res_cols_fail, original_pages_map)\n            for i in range(page_count)\n        )\n\n        page_size = Config.PAGE_SIZE\n\n        tasks = (\n            Task(\n                nl.do_slice_convert, str(dir_pid), page_size, columns, reject_reason_name, res_pass, res_fail, desired_column_map, column_names, is_correct_type\n            )\n            for columns, res_pass, res_fail in task_list_inp\n        )\n\n        cpu_count = max(psutil.cpu_count(), 1)\n\n        if Config.MULTIPROCESSING_MODE == Config.FORCE:\n            is_mp = True\n        elif Config.MULTIPROCESSING_MODE == Config.FALSE:\n            is_mp = False\n        elif Config.MULTIPROCESSING_MODE == Config.AUTO:\n            is_multithreaded = cpu_count > 1\n            is_multipage = page_count > 1\n\n            is_mp = is_multithreaded and is_multipage\n\n        tbl_pass = T({k: [] for k in passed_column_data})\n        tbl_fail = T({k: [] for k in failed_column_data})\n\n        converted = []\n        step_size = 45 / max(page_count, 1)\n\n        if is_mp:\n            class WrapUpdate:\n                def update(self, n):\n                    pbar.update(n * step_size)\n\n            with TaskManager(min(cpu_count, page_count), error_mode=\"exception\") as tm:\n                res = tm.execute(list(tasks), pbar=WrapUpdate())\n\n                converted.extend(res)\n        else:\n            for task in tasks:\n                res = task.f(*task.args, **task.kwargs)\n\n                converted.append(res)\n                pbar.update(step_size)\n\n        def extend_table(table, columns):\n            for (col_name, pg) in columns:\n                table[col_name].pages.append(pg)\n\n        for pg_pass, pg_fail in converted:\n            extend_table(tbl_pass, pg_pass)\n            extend_table(tbl_fail, pg_fail)\n\n        pbar.update(pbar.total - pbar.n)\n\n        return tbl_pass, tbl_fail\n
    "},{"location":"reference/nimlite/#tablite.nimlite.read_page","title":"tablite.nimlite.read_page(path: Union[str, Path]) -> np.ndarray","text":"Source code in tablite/nimlite.py
    def read_page(path: Union[str, Path]) -> np.ndarray:\n    return nl.read_page(str(path))\n
    "},{"location":"reference/nimlite/#tablite.nimlite.repaginate","title":"tablite.nimlite.repaginate(column: Column)","text":"Source code in tablite/nimlite.py
    def repaginate(column: Column):\n    nl.repaginate(column)\n
    "},{"location":"reference/nimlite/#tablite.nimlite.nearest_neighbour","title":"tablite.nimlite.nearest_neighbour(T: BaseTable, sources: Union[list[str], None], missing: Union[list, None], targets: Union[list[str], None], tqdm=_tqdm)","text":"Source code in tablite/nimlite.py
    def nearest_neighbour(T: BaseTable, sources: Union[list[str], None], missing: Union[list, None], targets: Union[list[str], None], tqdm=_tqdm):\n    return nl.nearest_neighbour(T, sources, list(missing), targets, tqdm)\n
    "},{"location":"reference/nimlite/#tablite.nimlite.filter","title":"tablite.nimlite.filter(table: BaseTable, expressions: list[FilterDict], type: FilterType, tqdm=_tqdm)","text":"Source code in tablite/nimlite.py
    def filter(table: BaseTable, expressions: list[FilterDict], type: FilterType, tqdm = _tqdm):\n    return nl.filter(table, expressions, type, tqdm)\n
    "},{"location":"reference/pivots/","title":"Pivots","text":""},{"location":"reference/pivots/#tablite.pivots","title":"tablite.pivots","text":""},{"location":"reference/pivots/#tablite.pivots-classes","title":"Classes","text":""},{"location":"reference/pivots/#tablite.pivots-functions","title":"Functions","text":""},{"location":"reference/pivots/#tablite.pivots.pivot","title":"tablite.pivots.pivot(T, rows, columns, functions, values_as_rows=True, tqdm=_tqdm, pbar=None)","text":"

    param: rows: column names to keep as rows param: columns: column names to keep as columns param: functions: aggregation functions from the Groupby class as

    example:

    >>> t.show()\n+=====+=====+=====+\n|  A  |  B  |  C  |\n| int | int | int |\n+-----+-----+-----+\n|    1|    1|    6|\n|    1|    2|    5|\n|    2|    3|    4|\n|    2|    4|    3|\n|    3|    5|    2|\n|    3|    6|    1|\n|    1|    1|    6|\n|    1|    2|    5|\n|    2|    3|    4|\n|    2|    4|    3|\n|    3|    5|    2|\n|    3|    6|    1|\n+=====+=====+=====+\n\n>>> t2 = t.pivot(rows=['C'], columns=['A'], functions=[('B', gb.sum)])\n>>> t2.show()\n+===+===+========+=====+=====+=====+\n| # | C |function|(A=1)|(A=2)|(A=3)|\n|row|int|  str   |mixed|mixed|mixed|\n+---+---+--------+-----+-----+-----+\n|0  |  6|Sum(B)  |    2|None |None |\n|1  |  5|Sum(B)  |    4|None |None |\n|2  |  4|Sum(B)  |None |    6|None |\n|3  |  3|Sum(B)  |None |    8|None |\n|4  |  2|Sum(B)  |None |None |   10|\n|5  |  1|Sum(B)  |None |None |   12|\n+===+===+========+=====+=====+=====+\n
    Source code in tablite/pivots.py
    def pivot(T, rows, columns, functions, values_as_rows=True, tqdm=_tqdm, pbar=None):\n    \"\"\"\n    param: rows: column names to keep as rows\n    param: columns: column names to keep as columns\n    param: functions: aggregation functions from the Groupby class as\n\n    example:\n    ```\n    >>> t.show()\n    +=====+=====+=====+\n    |  A  |  B  |  C  |\n    | int | int | int |\n    +-----+-----+-----+\n    |    1|    1|    6|\n    |    1|    2|    5|\n    |    2|    3|    4|\n    |    2|    4|    3|\n    |    3|    5|    2|\n    |    3|    6|    1|\n    |    1|    1|    6|\n    |    1|    2|    5|\n    |    2|    3|    4|\n    |    2|    4|    3|\n    |    3|    5|    2|\n    |    3|    6|    1|\n    +=====+=====+=====+\n\n    >>> t2 = t.pivot(rows=['C'], columns=['A'], functions=[('B', gb.sum)])\n    >>> t2.show()\n    +===+===+========+=====+=====+=====+\n    | # | C |function|(A=1)|(A=2)|(A=3)|\n    |row|int|  str   |mixed|mixed|mixed|\n    +---+---+--------+-----+-----+-----+\n    |0  |  6|Sum(B)  |    2|None |None |\n    |1  |  5|Sum(B)  |    4|None |None |\n    |2  |  4|Sum(B)  |None |    6|None |\n    |3  |  3|Sum(B)  |None |    8|None |\n    |4  |  2|Sum(B)  |None |None |   10|\n    |5  |  1|Sum(B)  |None |None |   12|\n    +===+===+========+=====+=====+=====+\n    ```\n\n    \"\"\"\n    sub_cls_check(T, BaseTable)\n\n    if isinstance(rows, str):\n        rows = [rows]\n    if not all(isinstance(i, str) for i in rows):\n        raise TypeError(f\"Expected rows as a list of column names, not {[i for i in rows if not isinstance(i,str)]}\")\n\n    if isinstance(columns, str):\n        columns = [columns]\n    if not all(isinstance(i, str) for i in columns):\n        raise TypeError(\n            f\"Expected columns as a list of column names, not {[i for i in columns if not isinstance(i, str)]}\"\n        )\n\n    if not isinstance(values_as_rows, bool):\n        raise TypeError(f\"expected sum_on_rows as boolean, not {type(values_as_rows)}\")\n\n    keys = rows + columns\n    assert isinstance(keys, list)\n\n    extra_steps = 2\n\n    if pbar is None:\n        total = extra_steps\n\n        if len(functions) == 0:\n            total = total + len(keys)\n        else:\n            total = total + len(T)\n\n        pbar = tqdm(total=total, desc=\"pivot\")\n\n    grpby = groupby(T, keys, functions, tqdm=tqdm, pbar=pbar)\n    Constr = type(T)\n\n    if len(grpby) == 0:  # return empty table. This must be a test?\n        pbar.update(extra_steps)\n        return Constr()\n\n    # split keys to determine grid dimensions\n    row_key_index = {}\n    col_key_index = {}\n\n    r = len(rows)\n    c = len(columns)\n    g = len(functions)\n\n    records = defaultdict(dict)\n\n    for row in grpby.rows:\n        row_key = tuple(row[:r])\n        col_key = tuple(row[r : r + c])\n        func_key = tuple(row[r + c :])\n\n        if row_key not in row_key_index:\n            row_key_index[row_key] = len(row_key_index)  # Y\n\n        if col_key not in col_key_index:\n            col_key_index[col_key] = len(col_key_index)  # X\n\n        rix = row_key_index[row_key]\n        cix = col_key_index[col_key]\n        if cix in records:\n            if rix in records[cix]:\n                raise ValueError(\"this should be empty.\")\n        records[cix][rix] = func_key\n\n    pbar.update(1)\n    result = type(T)()\n\n    if values_as_rows:  # ---> leads to more rows.\n        # first create all columns left to right\n\n        n = r + 1  # rows keys + 1 col for function values.\n        cols = [[] for _ in range(n)]\n        for row, ix in row_key_index.items():\n            for col_name, f in functions:\n                cols[-1].append(f\"{f.__name__}({col_name})\")\n                for col_ix, v in enumerate(row):\n                    cols[col_ix].append(v)\n\n        for col_name, values in zip(rows + [\"function\"], cols):\n            col_name = unique_name(col_name, result.columns)\n            result[col_name] = values\n        col_length = len(cols[0])\n        cols.clear()\n\n        # then populate the sparse matrix.\n        for col_key, c in col_key_index.items():\n            col_name = \"(\" + \",\".join([f\"{col_name}={value}\" for col_name, value in zip(columns, col_key)]) + \")\"\n            col_name = unique_name(col_name, result.columns)\n            L = [None for _ in range(col_length)]\n            for r, funcs in records[c].items():\n                for ix, f in enumerate(funcs):\n                    L[g * r + ix] = f\n            result[col_name] = L\n\n    else:  # ---> leads to more columns.\n        n = r\n        cols = [[] for _ in range(n)]\n        for row in row_key_index:\n            for col_ix, v in enumerate(row):\n                cols[col_ix].append(v)  # write key columns.\n\n        for col_name, values in zip(rows, cols):\n            result[col_name] = values\n\n        col_length = len(row_key_index)\n\n        # now populate the sparse matrix.\n        for col_key, c in col_key_index.items():  # select column.\n            cols, names = [], []\n\n            for f, v in zip(functions, func_key):\n                agg_col, func = f\n                terms = \",\".join([agg_col] + [f\"{col_name}={value}\" for col_name, value in zip(columns, col_key)])\n                col_name = f\"{func.__name__}({terms})\"\n                col_name = unique_name(col_name, result.columns)\n                names.append(col_name)\n                cols.append([None for _ in range(col_length)])\n            for r, funcs in records[c].items():\n                for ix, f in enumerate(funcs):\n                    cols[ix][r] = f\n            for name, col in zip(names, cols):\n                result[name] = col\n\n    pbar.update(1)\n\n    return result\n
    "},{"location":"reference/pivots/#tablite.pivots.transpose","title":"tablite.pivots.transpose(T, tqdm=_tqdm)","text":"

    performs a CCW matrix rotation of the table.

    Source code in tablite/pivots.py
    def transpose(T, tqdm=_tqdm):\n    \"\"\"performs a CCW matrix rotation of the table.\"\"\"\n    sub_cls_check(T, BaseTable)\n\n    if len(T.columns) == 0:\n        return type(T)()\n\n    assert isinstance(T, BaseTable)\n    new = type(T)()\n    L = list(T.columns)\n    new[L[0]] = L[1:]\n    for row in tqdm(T.rows, desc=\"table transpose\", total=len(T)):\n        new[row[0]] = row[1:]\n    return new\n
    "},{"location":"reference/pivots/#tablite.pivots.pivot_transpose","title":"tablite.pivots.pivot_transpose(T, columns, keep=None, column_name='transpose', value_name='value', tqdm=_tqdm)","text":"

    Transpose a selection of columns to rows.

    PARAMETER DESCRIPTION columns

    column names to transpose

    TYPE: list of column names

    keep

    column names to keep (repeat)

    TYPE: list of column names DEFAULT: None

    RETURNS DESCRIPTION Table

    with columns transposed to rows

    Example

    transpose columns 1,2 and 3 and transpose the remaining columns, except sum.

    Input:

    | col1 | col2 | col3 | sun | mon | tue | ... | sat | sum  |\n|------|------|------|-----|-----|-----|-----|-----|------|\n| 1234 | 2345 | 3456 | 456 | 567 |     | ... |     | 1023 |\n| 1244 | 2445 | 4456 |     |   7 |     | ... |     |    7 |\n| ...  |      |      |     |     |     |     |     |      |\n\n>>> t.transpose(keep=[col1, col2, col3], transpose=[sun,mon,tue,wed,thu,fri,sat])`\n\nOutput:\n|col1| col2| col3| transpose| value|\n|----|-----|-----|----------|------|\n|1234| 2345| 3456| sun      |   456|\n|1234| 2345| 3456| mon      |   567|\n|1244| 2445| 4456| mon      |     7|\n
    Source code in tablite/pivots.py
    def pivot_transpose(T, columns, keep=None, column_name=\"transpose\", value_name=\"value\", tqdm=_tqdm):\n    \"\"\"Transpose a selection of columns to rows.\n\n    Args:\n        columns (list of column names): column names to transpose\n        keep (list of column names): column names to keep (repeat)\n\n    Returns:\n        Table: with columns transposed to rows\n\n    Example:\n        transpose columns 1,2 and 3 and transpose the remaining columns, except `sum`.\n\n    Input:\n    ```\n    | col1 | col2 | col3 | sun | mon | tue | ... | sat | sum  |\n    |------|------|------|-----|-----|-----|-----|-----|------|\n    | 1234 | 2345 | 3456 | 456 | 567 |     | ... |     | 1023 |\n    | 1244 | 2445 | 4456 |     |   7 |     | ... |     |    7 |\n    | ...  |      |      |     |     |     |     |     |      |\n\n    >>> t.transpose(keep=[col1, col2, col3], transpose=[sun,mon,tue,wed,thu,fri,sat])`\n\n    Output:\n    |col1| col2| col3| transpose| value|\n    |----|-----|-----|----------|------|\n    |1234| 2345| 3456| sun      |   456|\n    |1234| 2345| 3456| mon      |   567|\n    |1244| 2445| 4456| mon      |     7|\n    ```\n\n    \"\"\"\n    sub_cls_check(T, BaseTable)\n\n    if not isinstance(columns, list):\n        raise TypeError\n\n    for i in columns:\n        if not isinstance(i, str):\n            raise TypeError\n        if i not in T.columns:\n            raise ValueError\n        if columns.count(i)>1:\n            raise ValueError(f\"Column {i} appears more than once\")\n\n    if keep is None:\n        keep = []\n    for i in keep:\n        if not isinstance(i, str):\n            raise TypeError\n        if i not in T.columns:\n            raise ValueError\n\n    if column_name in keep + columns:\n        column_name = unique_name(column_name, set_of_names=keep + columns)\n    if value_name in keep + columns + [column_name]:\n        value_name = unique_name(value_name, set_of_names=keep + columns)\n\n    new = type(T)()\n    new.add_columns(*keep + [column_name, value_name])\n    news = {name: [] for name in new.columns}\n\n    n = len(keep)\n\n    with tqdm(total=len(T), desc=\"transpose\", disable=Config.TQDM_DISABLE) as pbar:\n        it = T[keep + columns].rows if len(keep + columns) > 1 else ((v, ) for v in T[keep + columns])\n\n        for ix, row in enumerate(it, start=1):\n            keeps = row[:n]\n            transposes = row[n:]\n\n            for name, value in zip(keep, keeps):\n                news[name].extend([value] * len(transposes))\n            for name, value in zip(columns, transposes):\n                news[column_name].append(name)\n                news[value_name].append(value)\n\n            if ix % Config.SINGLE_PROCESSING_LIMIT == 0:\n                for name, values in news.items():\n                    new[name].extend(values)\n                    values.clear()\n\n            pbar.update(1)\n\n    for name, values in news.items():\n        new[name].extend(np.array(values))\n        values.clear()\n    return new\n
    "},{"location":"reference/redux/","title":"Redux","text":""},{"location":"reference/redux/#tablite.redux","title":"tablite.redux","text":""},{"location":"reference/redux/#tablite.redux-attributes","title":"Attributes","text":""},{"location":"reference/redux/#tablite.redux-classes","title":"Classes","text":""},{"location":"reference/redux/#tablite.redux-functions","title":"Functions","text":""},{"location":"reference/redux/#tablite.redux.filter_all","title":"tablite.redux.filter_all(T, **kwargs)","text":"

    returns Table for rows where ALL kwargs match :param kwargs: dictionary with headers and values / boolean callable

    Examples:

    t = Table()\nt['a'] = [1,2,3,4]\nt['b'] = [10,20,30,40]\n\ndef f(x):\n    return x == 4\ndef g(x):\n    return x < 20\n\nt2 = t.any( **{\"a\":f, \"b\":g})\nassert [r for r in t2.rows] == [[1, 10], [4, 40]]\n\nt2 = t.any(a=f,b=g)\nassert [r for r in t2.rows] == [[1, 10], [4, 40]]\n\ndef h(x):\n    return x>=2\n\ndef i(x):\n    return x<=30\n\nt2 = t.all(a=h,b=i)\nassert [r for r in t2.rows] == [[2,20], [3, 30]]\n
    Source code in tablite/redux.py
    def filter_all(T, **kwargs):\n    \"\"\"\n    returns Table for rows where ALL kwargs match\n    :param kwargs: dictionary with headers and values / boolean callable\n\n    Examples:\n\n        t = Table()\n        t['a'] = [1,2,3,4]\n        t['b'] = [10,20,30,40]\n\n        def f(x):\n            return x == 4\n        def g(x):\n            return x < 20\n\n        t2 = t.any( **{\"a\":f, \"b\":g})\n        assert [r for r in t2.rows] == [[1, 10], [4, 40]]\n\n        t2 = t.any(a=f,b=g)\n        assert [r for r in t2.rows] == [[1, 10], [4, 40]]\n\n        def h(x):\n            return x>=2\n\n        def i(x):\n            return x<=30\n\n        t2 = t.all(a=h,b=i)\n        assert [r for r in t2.rows] == [[2,20], [3, 30]]\n\n\n    \"\"\"\n    sub_cls_check(T, BaseTable)\n\n    if not isinstance(kwargs, dict):\n        raise TypeError(\"did you forget to add the ** in front of your dict?\")\n    if not all([k in T.columns for k in kwargs]):\n        raise ValueError(f\"Unknown column(s): {[k for k in kwargs if k not in T.columns]}\")\n\n    mask = np.full((len(T),), True)\n    for k, v in kwargs.items():\n        col = T[k]\n        for start, end, page in col.iter_by_page():\n            data = page.get()\n            if callable(v):\n                vf = np.frompyfunc(v, 1, 1)\n                mask[start:end] = mask[start:end] & np.apply_along_axis(vf, 0, data)\n            else:\n                mask[start:end] = mask[start:end] & (data == v)\n\n    return _compress_one(T, mask)\n
    "},{"location":"reference/redux/#tablite.redux.drop","title":"tablite.redux.drop(T, *args)","text":"

    drops all rows that contain args

    PARAMETER DESCRIPTION T

    TYPE: Table

    Source code in tablite/redux.py
    def drop(T, *args):\n    \"\"\"drops all rows that contain args\n\n    Args:\n        T (Table):\n    \"\"\"\n    sub_cls_check(T, BaseTable)\n    mask = np.full((len(T),), False)\n    for name in T.columns:\n        col = T[name]\n        for start, end, page in col.iter_by_page():\n            data = page.get()\n            for arg in args:\n                mask[start:end] = mask[start:end] | (data == arg)\n\n    mask = np.invert(mask)\n    return _compress_one(T, mask)\n
    "},{"location":"reference/redux/#tablite.redux.filter_any","title":"tablite.redux.filter_any(T, **kwargs)","text":"

    returns Table for rows where ANY kwargs match :param kwargs: dictionary with headers and values / boolean callable

    Source code in tablite/redux.py
    def filter_any(T, **kwargs):\n    \"\"\"\n    returns Table for rows where ANY kwargs match\n    :param kwargs: dictionary with headers and values / boolean callable\n    \"\"\"\n    sub_cls_check(T, BaseTable)\n    if not isinstance(kwargs, dict):\n        raise TypeError(\"did you forget to add the ** in front of your dict?\")\n\n    mask = np.full((len(T),), False)\n    for k, v in kwargs.items():\n        col = T[k]\n        for start, end, page in col.iter_by_page():\n            data = page.get()\n            if callable(v):\n                vf = np.frompyfunc(v, 1, 1)\n                mask[start:end] = mask[start:end] | np.apply_along_axis(vf, 0, data)\n            else:\n                mask[start:end] = mask[start:end] | (v == data)\n\n    return _compress_one(T, mask)\n
    "},{"location":"reference/redux/#tablite.redux.filter","title":"tablite.redux.filter(T, expressions, filter_type='all', tqdm=_tqdm)","text":"

    filters table

    PARAMETER DESCRIPTION T

    Table.

    TYPE: Table subclass

    expressions

    str: filters based on an expression, such as: \"all((A==B, C!=4, 200<D))\" which is interpreted using python's compiler to:

    def _f(A,B,C,D):\n    return all((A==B, C!=4, 200<D))\n

    list of dicts: (example):

    L = [ {'column1':'A', 'criteria': \"==\", 'column2': 'B'}, {'column1':'C', 'criteria': \"!=\", \"value2\": '4'}, {'value1': 200, 'criteria': \"<\", column2: 'D' } ]

    TYPE: list or str

    accepted

    'column1', 'column2', 'criteria', 'value1', 'value2'

    TYPE: dictionary keys

    filter_type

    Ignored if expressions is str. 'all' or 'any'. Defaults to \"all\".

    TYPE: str DEFAULT: 'all'

    tqdm

    progressbar. Defaults to _tqdm.

    TYPE: tqdm DEFAULT: tqdm

    RETURNS DESCRIPTION 2xTables

    trues, falses

    Source code in tablite/redux.py
    def filter(T, expressions, filter_type=\"all\", tqdm=_tqdm):\n    \"\"\"filters table\n\n\n    Args:\n        T (Table subclass): Table.\n        expressions (list or str):\n            str:\n                filters based on an expression, such as:\n                \"all((A==B, C!=4, 200<D))\"\n                which is interpreted using python's compiler to:\n\n                def _f(A,B,C,D):\n                    return all((A==B, C!=4, 200<D))\n\n            list of dicts: (example):\n\n            L = [\n                {'column1':'A', 'criteria': \"==\", 'column2': 'B'},\n                {'column1':'C', 'criteria': \"!=\", \"value2\": '4'},\n                {'value1': 200, 'criteria': \"<\", column2: 'D' }\n            ]\n\n        accepted dictionary keys: 'column1', 'column2', 'criteria', 'value1', 'value2'\n\n        filter_type (str, optional): Ignored if expressions is str.\n            'all' or 'any'. Defaults to \"all\".\n        tqdm (tqdm, optional): progressbar. Defaults to _tqdm.\n\n    Returns:\n        2xTables: trues, falses\n    \"\"\"\n    # determine method\n    sub_cls_check(T, BaseTable)\n    if len(T) == 0:\n        return T.copy(), T.copy()\n\n    if isinstance(expressions, str):\n        with tqdm(desc=\"filter\", total=20) as pbar:\n            # TODO: make parser for expressions and use the nim implement\n            mask = _filter_using_expression(T, expressions)\n            pbar.update(10)\n            res = _compress_both(T, mask, pbar=pbar)\n            pbar.update(pbar.total - pbar.n)\n    elif isinstance(expressions, list):\n        return _filter_using_list_of_dicts(T, expressions, filter_type, tqdm)\n    else:\n        raise TypeError\n        # create new tables\n\n    return res\n
    "},{"location":"reference/reindex/","title":"Reindex","text":""},{"location":"reference/reindex/#tablite.reindex","title":"tablite.reindex","text":""},{"location":"reference/reindex/#tablite.reindex-classes","title":"Classes","text":""},{"location":"reference/reindex/#tablite.reindex-functions","title":"Functions","text":""},{"location":"reference/reindex/#tablite.reindex.reindex","title":"tablite.reindex.reindex(T, index, names=None, tqdm=_tqdm, pbar=None)","text":"

    Constant Memory helper for reindexing pages.

    Memory usage is set by datatype and Config.PAGE_SIZE

    PARAMETER DESCRIPTION T

    subclass of Table

    TYPE: Table

    index

    int64.

    TYPE: array

    names

    list of names from T to reindex.

    TYPE: (list, str) DEFAULT: None

    tqdm

    Defaults to _tqdm.

    TYPE: tqdm DEFAULT: tqdm

    pbar

    Defaults to None.

    TYPE: pbar DEFAULT: None

    RETURNS DESCRIPTION _type_

    description

    Source code in tablite/reindex.py
    def reindex(T, index, names=None, tqdm=_tqdm, pbar=None):\n    \"\"\"Constant Memory helper for reindexing pages.\n\n    Memory usage is set by datatype and Config.PAGE_SIZE\n\n    Args:\n        T (Table): subclass of Table\n        index (np.array): int64.\n        names (list, str): list of names from T to reindex.\n        tqdm (tqdm, optional): Defaults to _tqdm.\n        pbar (pbar, optional): Defaults to None.\n\n    Returns:\n        _type_: _description_\n    \"\"\"\n    if names is None:\n        names = list(T.columns.keys())\n\n    if pbar is None:\n        total = len(names)\n        pbar = tqdm(total=total, desc=\"join\", disable=Config.TQDM_DISABLE)\n\n    sub_cls_check(T, BaseTable)\n    cls = type(T)\n    result = cls()\n    for name in names:\n        result.add_column(name)\n        col = result[name]\n\n        for start, end in Config.page_steps(len(index)):\n            indices = index[start:end]\n            values = T[name].get_by_indices(indices)\n            # in these values, the index of -1 will be wrong.\n            # so if there is any -1 in the indices, they will\n            # have to be replaced with Nones\n            mask = indices == -1\n            if np.any(mask):\n                nones = np.full(index.shape, fill_value=None)\n                values = np.where(mask, nones, values)\n            col.extend(values)\n        pbar.update(1)\n\n    return result\n
    "},{"location":"reference/sort_utils/","title":"Sort utils","text":""},{"location":"reference/sort_utils/#tablite.sort_utils","title":"tablite.sort_utils","text":""},{"location":"reference/sort_utils/#tablite.sort_utils-attributes","title":"Attributes","text":""},{"location":"reference/sort_utils/#tablite.sort_utils.uca_collator","title":"tablite.sort_utils.uca_collator = Collator() module-attribute","text":""},{"location":"reference/sort_utils/#tablite.sort_utils.modes","title":"tablite.sort_utils.modes = {'alphanumeric': text_sort, 'unix': unix_sort, 'excel': excel_sort} module-attribute","text":""},{"location":"reference/sort_utils/#tablite.sort_utils-classes","title":"Classes","text":""},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict","title":"tablite.sort_utils.HashDict","text":"

    Bases: dict

    This class is just a nicity syntatic sugar for debugging. Function identically to regular dictionary, just uses tupled key.

    "},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict-functions","title":"Functions","text":""},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict.items","title":"tablite.sort_utils.HashDict.items()","text":"Source code in tablite/sort_utils.py
    def items(self):\n    return [(k, v) for (_, k), v in super().items()]\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict.keys","title":"tablite.sort_utils.HashDict.keys()","text":"Source code in tablite/sort_utils.py
    def keys(self):\n    return [k for (_, k) in super().keys()]\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict.__iter__","title":"tablite.sort_utils.HashDict.__iter__() -> Iterator","text":"Source code in tablite/sort_utils.py
    def __iter__(self) -> Iterator:\n    return (k for (_, k) in super().keys())\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict.__getitem__","title":"tablite.sort_utils.HashDict.__getitem__(key)","text":"Source code in tablite/sort_utils.py
    def __getitem__(self, key):\n    return super().__getitem__(self._get_hash(key))\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict.__setitem__","title":"tablite.sort_utils.HashDict.__setitem__(key, value)","text":"Source code in tablite/sort_utils.py
    def __setitem__(self, key, value):\n    return super().__setitem__(self._get_hash(key), value)\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict.__contains__","title":"tablite.sort_utils.HashDict.__contains__(key) -> bool","text":"Source code in tablite/sort_utils.py
    def __contains__(self, key) -> bool:\n    return super().__contains__(self._get_hash(key))\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict.__delitem__","title":"tablite.sort_utils.HashDict.__delitem__(key)","text":"Source code in tablite/sort_utils.py
    def __delitem__(self, key):\n    return super().__delitem__(self._get_hash(key))\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict.__repr__","title":"tablite.sort_utils.HashDict.__repr__() -> str","text":"Source code in tablite/sort_utils.py
    def __repr__(self) -> str:\n    return '{' + \", \".join([f\"{k}: {v}\" for k, v in self.items()]) + '}'\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict.__str__","title":"tablite.sort_utils.HashDict.__str__() -> str","text":"Source code in tablite/sort_utils.py
    def __str__(self) -> str:\n    return repr(self)\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils-functions","title":"Functions","text":""},{"location":"reference/sort_utils/#tablite.sort_utils.text_sort","title":"tablite.sort_utils.text_sort(values, reverse=False)","text":"

    Sorts everything as text.

    Source code in tablite/sort_utils.py
    def text_sort(values, reverse=False):\n    \"\"\"\n    Sorts everything as text.\n    \"\"\"\n    text = {str(i): i for i in values}\n    L = list(text.keys())\n    L.sort(key=uca_collator.sort_key, reverse=reverse)\n    d = {text[value]: ix for ix, value in enumerate(L)}\n    return d\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.unix_sort","title":"tablite.sort_utils.unix_sort(values, reverse=False)","text":"

    Unix sortation sorts by the following order:

    | rank | type | value | +------+-----------+--------------------------------------------+ | 0 | None | floating point -infinite | | 1 | bool | 0 as False, 1 as True | | 2 | int | as numeric value | | 2 | float | as numeric value | | 3 | time | \u03c4 * seconds into the day / (24 * 60 * 60) | | 4 | date | as integer days since 1970/1/1 | | 5 | datetime | as float using date (int) + time (decimal) | | 6 | timedelta | as float using date (int) + time (decimal) | | 7 | str | using unicode | +------+-----------+--------------------------------------------+

    \u03c4 = 2 * \u03c0

    Source code in tablite/sort_utils.py
    def unix_sort(values, reverse=False):\n    \"\"\"\n    Unix sortation sorts by the following order:\n\n    | rank | type      | value                                      |\n    +------+-----------+--------------------------------------------+\n    |   0  | None      | floating point -infinite                   |\n    |   1  | bool      | 0 as False, 1 as True                      |\n    |   2  | int       | as numeric value                           |\n    |   2  | float     | as numeric value                           |\n    |   3  | time      | \u03c4 * seconds into the day / (24 * 60 * 60)  |\n    |   4  | date      | as integer days since 1970/1/1             |\n    |   5  | datetime  | as float using date (int) + time (decimal) |\n    |   6  | timedelta | as float using date (int) + time (decimal) |\n    |   7  | str       | using unicode                              |\n    +------+-----------+--------------------------------------------+\n\n    \u03c4 = 2 * \u03c0\n\n    \"\"\"\n    text, non_text = [], []\n\n    # L = []\n    # text = [i for i in values if isinstance(i, str)]\n    # text.sort(key=uca_collator.sort_key, reverse=reverse)\n    # text_code = _unix_typecodes[str]\n    # L = [(text_code, ix, v) for ix, v in enumerate(text)]\n\n    for value in values:\n        if isinstance(value, str):\n            text.append(value)\n        else:\n            t = type(value)\n            TC = _unix_typecodes[t]\n            tf = _unix_value_function[t]\n            VC = tf(value)\n            non_text.append((TC, VC, value))\n    non_text.sort(reverse=reverse)\n\n    text.sort(key=uca_collator.sort_key, reverse=reverse)\n    text_code = _unix_typecodes[str]\n    text = [(text_code, ix, v) for ix, v in enumerate(text)]\n\n    d = HashDict()\n    L = non_text + text\n    for ix, (_, _, value) in enumerate(L):\n        d[value] = ix\n    return d\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.excel_sort","title":"tablite.sort_utils.excel_sort(values, reverse=False)","text":"

    Excel sortation sorts by the following order:

    | rank | type | value | +------+-----------+--------------------------------------------+ | 1 | int | as numeric value | | 1 | float | as numeric value | | 1 | time | as seconds into the day / (24 * 60 * 60) | | 1 | date | as integer days since 1900/1/1 | | 1 | datetime | as float using date (int) + time (decimal) | | (1)*| timedelta | as float using date (int) + time (decimal) | | 2 | str | using unicode | | 3 | bool | 0 as False, 1 as True | | 4 | None | floating point infinite. | +------+-----------+--------------------------------------------+

    • Excel doesn't have timedelta.
    Source code in tablite/sort_utils.py
    def excel_sort(values, reverse=False):\n    \"\"\"\n    Excel sortation sorts by the following order:\n\n    | rank | type      | value                                      |\n    +------+-----------+--------------------------------------------+\n    |   1  | int       | as numeric value                           |\n    |   1  | float     | as numeric value                           |\n    |   1  | time      | as seconds into the day / (24 * 60 * 60)   |\n    |   1  | date      | as integer days since 1900/1/1             |\n    |   1  | datetime  | as float using date (int) + time (decimal) |\n    |  (1)*| timedelta | as float using date (int) + time (decimal) |\n    |   2  | str       | using unicode                              |\n    |   3  | bool      | 0 as False, 1 as True                      |\n    |   4  | None      | floating point infinite.                   |\n    +------+-----------+--------------------------------------------+\n\n    * Excel doesn't have timedelta.\n    \"\"\"\n\n    def tup(TC, value):\n        return (TC, _excel_value_function[t](value), value)\n\n    text, numeric, booles, nones = [], [], [], []\n    for value in values:\n        t = type(value)\n        TC = _excel_typecodes[t]\n\n        if TC == 0:\n            numeric.append(tup(TC, value))\n        elif TC == 1:\n            text.append(value)  # text is processed later.\n        elif TC == 2:\n            booles.append(tup(TC, value))\n        elif TC == 3:\n            booles.append(tup(TC, value))\n        else:\n            raise TypeError(f\"no typecode for {value}\")\n\n    if text:\n        text.sort(key=uca_collator.sort_key, reverse=reverse)\n        text = [(2, ix, v) for ix, v in enumerate(text)]\n\n    numeric.sort(reverse=reverse)\n    booles.sort(reverse=reverse)\n    nones.sort(reverse=reverse)\n\n    if reverse:\n        L = nones + booles + text + numeric\n    else:\n        L = numeric + text + booles + nones\n    d = {value: ix for ix, (_, _, value) in enumerate(L)}\n    return d\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.rank","title":"tablite.sort_utils.rank(values, reverse, mode)","text":"

    values: list of values to sort. reverse: bool mode: as 'text', as 'numeric' or as 'excel' return: dict: d[value] = rank

    Source code in tablite/sort_utils.py
    def rank(values, reverse, mode):\n    \"\"\"\n    values: list of values to sort.\n    reverse: bool\n    mode: as 'text', as 'numeric' or as 'excel'\n    return: dict: d[value] = rank\n    \"\"\"\n    if mode not in modes:\n        raise ValueError(f\"{mode} not in list of modes: {list(modes)}\")\n    f = modes.get(mode)\n    return f(values, reverse)\n
    "},{"location":"reference/sortation/","title":"Sortation","text":""},{"location":"reference/sortation/#tablite.sortation","title":"tablite.sortation","text":""},{"location":"reference/sortation/#tablite.sortation-attributes","title":"Attributes","text":""},{"location":"reference/sortation/#tablite.sortation-classes","title":"Classes","text":""},{"location":"reference/sortation/#tablite.sortation-functions","title":"Functions","text":""},{"location":"reference/sortation/#tablite.sortation.sort_index","title":"tablite.sortation.sort_index(T, mapping, sort_mode='excel', tqdm=_tqdm, pbar=None)","text":"

    helper for methods sort and is_sorted

    param: sort_mode: str: \"alphanumeric\", \"unix\", or, \"excel\" (default) param: **kwargs: sort criteria. See Table.sort()

    Source code in tablite/sortation.py
    def sort_index(T, mapping, sort_mode=\"excel\", tqdm=_tqdm, pbar=None):\n    \"\"\"\n    helper for methods `sort` and `is_sorted`\n\n    param: sort_mode: str: \"alphanumeric\", \"unix\", or, \"excel\" (default)\n    param: **kwargs: sort criteria. See Table.sort()\n    \"\"\"\n\n    sub_cls_check(T, BaseTable)\n\n    if not isinstance(mapping, dict) or not mapping:\n        raise TypeError(\"Expected mapping (dict)?\")\n\n    for k, v in mapping.items():\n        if k not in T.columns:\n            raise ValueError(f\"no column {k}\")\n        if not isinstance(v, bool):\n            raise ValueError(f\"{k} was mapped to {v} - a non-boolean\")\n\n    if sort_mode not in sort_modes:\n        raise ValueError(f\"{sort_mode} not in list of sort_modes: {list(sort_modes)}\")\n\n    rank = {i: tuple() for i in range(len(T))}  # create index and empty tuple for sortation.\n\n    _pbar = tqdm(total=len(mapping.items()), desc=\"creating sort index\") if pbar is None else pbar\n\n    for key, reverse in mapping.items():\n        col = T[key][:]\n        ranks = sort_rank(values=[numpy_to_python(v) for v in multitype_set(col)], reverse=reverse, mode=sort_mode)\n        assert isinstance(ranks, dict)\n        for ix, v in enumerate(col):\n            v2 = numpy_to_python(v)\n            rank[ix] += (ranks[v2],)  # add tuple for each sortation level.\n\n        _pbar.update(1)\n\n    del col\n    del ranks\n\n    new_order = [(r, i) for i, r in rank.items()]  # tuples are listed and sort...\n    del rank  # free memory.\n\n    new_order.sort()\n    sorted_index = [i for _, i in new_order]  # new index is extracted.\n    new_order.clear()\n    return np.array(sorted_index, dtype=np.int64)\n
    "},{"location":"reference/sortation/#tablite.sortation.reindex","title":"tablite.sortation.reindex(T, index)","text":"

    index: list of integers that declare sort order.

    Examples:

    Table:  ['a','b','c','d','e','f','g','h']\nindex:  [0,2,4,6]\nresult: ['b','d','f','h']\n\nTable:  ['a','b','c','d','e','f','g','h']\nindex:  [0,2,4,6,1,3,5,7]\nresult: ['a','c','e','g','b','d','f','h']\n
    Source code in tablite/sortation.py
    def reindex(T, index):\n    \"\"\"\n    index: list of integers that declare sort order.\n\n    Examples:\n\n        Table:  ['a','b','c','d','e','f','g','h']\n        index:  [0,2,4,6]\n        result: ['b','d','f','h']\n\n        Table:  ['a','b','c','d','e','f','g','h']\n        index:  [0,2,4,6,1,3,5,7]\n        result: ['a','c','e','g','b','d','f','h']\n\n    \"\"\"\n    sub_cls_check(T, BaseTable)\n    if isinstance(index, list):\n        index = np.array(index, dtype=int)\n    type_check(index, np.ndarray)\n    if max(index) >= len(T):\n        raise IndexError(\"index out of range: max(index) > len(self)\")\n    if min(index) < -len(T):\n        raise IndexError(\"index out of range: min(index) < -len(self)\")\n\n    fields = len(T) * len(T.columns)\n    m = select_processing_method(fields, _reindex, _mp_reindex)\n    return m(T, index)\n
    "},{"location":"reference/sortation/#tablite.sortation.sort","title":"tablite.sortation.sort(T, mapping, sort_mode='excel', tqdm=_tqdm, pbar: _tqdm = None)","text":"

    Perform multi-pass sorting with precedence given order of column names. sort_mode: str: \"alphanumeric\", \"unix\", or, \"excel\" kwargs: keys: columns, values: 'reverse' as boolean.

    examples: Table.sort('A'=False) means sort by 'A' in ascending order. Table.sort('A'=True, 'B'=False) means sort 'A' in descending order, then (2nd priority) sort B in ascending order.

    Source code in tablite/sortation.py
    def sort(T, mapping, sort_mode=\"excel\", tqdm=_tqdm, pbar: _tqdm = None):\n    \"\"\"Perform multi-pass sorting with precedence given order of column names.\n    sort_mode: str: \"alphanumeric\", \"unix\", or, \"excel\"\n    kwargs:\n        keys: columns,\n        values: 'reverse' as boolean.\n\n    examples:\n    Table.sort('A'=False) means sort by 'A' in ascending order.\n    Table.sort('A'=True, 'B'=False) means sort 'A' in descending order, then (2nd priority)\n    sort B in ascending order.\n    \"\"\"\n    sub_cls_check(T, BaseTable)\n\n    index = sort_index(T, mapping, sort_mode=sort_mode, tqdm=_tqdm, pbar=pbar)\n    m = select_processing_method(len(T) * len(T.columns), _sp_reindex, _mp_reindex)\n    return m(T, index, tqdm=tqdm, pbar=pbar)\n
    "},{"location":"reference/sortation/#tablite.sortation.is_sorted","title":"tablite.sortation.is_sorted(T, mapping, sort_mode='excel')","text":"

    Performs multi-pass sorting check with precedence given order of column names.

    PARAMETER DESCRIPTION mapping

    sort criteria. See Table.sort()

    RETURNS DESCRIPTION

    bool

    Source code in tablite/sortation.py
    def is_sorted(T, mapping, sort_mode=\"excel\"):\n    \"\"\"Performs multi-pass sorting check with precedence given order of column names.\n\n    Args:\n        mapping: sort criteria. See Table.sort()\n        sort_mode = sort mode. See Table.sort()\n\n    Returns:\n        bool\n    \"\"\"\n    index = sort_index(T, mapping, sort_mode=sort_mode)\n    match = np.arange(len(T))\n    return np.all(index == match)\n
    "},{"location":"reference/tools/","title":"Tools","text":""},{"location":"reference/tools/#tablite.tools","title":"tablite.tools","text":""},{"location":"reference/tools/#tablite.tools-attributes","title":"Attributes","text":""},{"location":"reference/tools/#tablite.tools.guess","title":"tablite.tools.guess = DataTypes.guess module-attribute","text":""},{"location":"reference/tools/#tablite.tools.xround","title":"tablite.tools.xround = DataTypes.round module-attribute","text":""},{"location":"reference/tools/#tablite.tools-classes","title":"Classes","text":""},{"location":"reference/tools/#tablite.tools-functions","title":"Functions","text":""},{"location":"reference/tools/#tablite.tools.head","title":"tablite.tools.head(path, linecount=5, delimiter=None)","text":"

    Gets the head of any supported file format.

    Source code in tablite/tools.py
    def head(path, linecount=5, delimiter=None):\n    \"\"\"\n    Gets the head of any supported file format.\n    \"\"\"\n    return get_headers(path, linecount=linecount, delimiter=delimiter)\n
    "},{"location":"reference/utils/","title":"Utils","text":""},{"location":"reference/utils/#tablite.utils","title":"tablite.utils","text":""},{"location":"reference/utils/#tablite.utils-attributes","title":"Attributes","text":""},{"location":"reference/utils/#tablite.utils.letters","title":"tablite.utils.letters = string.ascii_lowercase + string.digits module-attribute","text":""},{"location":"reference/utils/#tablite.utils.NoneType","title":"tablite.utils.NoneType = type(None) module-attribute","text":""},{"location":"reference/utils/#tablite.utils.required_keys","title":"tablite.utils.required_keys = {'min', 'max', 'mean', 'median', 'stdev', 'mode', 'distinct', 'iqr_low', 'iqr_high', 'iqr', 'sum', 'summary type', 'histogram'} module-attribute","text":""},{"location":"reference/utils/#tablite.utils.summary_methods","title":"tablite.utils.summary_methods = {bool: _boolean_statistics_summary, int: _numeric_statistics_summary, float: _numeric_statistics_summary, str: _string_statistics_summary, date: _date_statistics_summary, datetime: _datetime_statistics_summary, time: _time_statistics_summary, timedelta: _timedelta_statistics_summary, type(None): _none_type_summary} module-attribute","text":""},{"location":"reference/utils/#tablite.utils-classes","title":"Classes","text":""},{"location":"reference/utils/#tablite.utils-functions","title":"Functions","text":""},{"location":"reference/utils/#tablite.utils.generate_random_string","title":"tablite.utils.generate_random_string(len)","text":"Source code in tablite/utils.py
    def generate_random_string(len):\n    return \"\".join(random.choice(letters) for i in range(len))\n
    "},{"location":"reference/utils/#tablite.utils.type_check","title":"tablite.utils.type_check(var, kind)","text":"Source code in tablite/utils.py
    def type_check(var, kind):\n    if not isinstance(var, kind):\n        raise TypeError(f\"Expected {kind}, not {type(var)}\")\n
    "},{"location":"reference/utils/#tablite.utils.sub_cls_check","title":"tablite.utils.sub_cls_check(c, kind)","text":"Source code in tablite/utils.py
    def sub_cls_check(c, kind):\n    if not issubclass(type(c), kind):\n        raise TypeError(f\"Expected {kind}, not {type(c)}\")\n
    "},{"location":"reference/utils/#tablite.utils.name_check","title":"tablite.utils.name_check(options, *names)","text":"Source code in tablite/utils.py
    def name_check(options, *names):\n    for n in names:\n        if n not in options:\n            raise ValueError(f\"{n} not in {options}\")\n
    "},{"location":"reference/utils/#tablite.utils.unique_name","title":"tablite.utils.unique_name(wanted_name, set_of_names)","text":"

    returns a wanted_name as wanted_name_i given a list of names which guarantees unique naming.

    Source code in tablite/utils.py
    def unique_name(wanted_name, set_of_names):\n    \"\"\"\n    returns a wanted_name as wanted_name_i given a list of names\n    which guarantees unique naming.\n    \"\"\"\n    if not isinstance(set_of_names, set):\n        set_of_names = set(set_of_names)\n    name, i = wanted_name, 1\n    while name in set_of_names:\n        name = f\"{wanted_name}_{i}\"\n        i += 1\n    return name\n
    "},{"location":"reference/utils/#tablite.utils.expression_interpreter","title":"tablite.utils.expression_interpreter(expression, columns)","text":"

    Interprets valid expressions such as:

    \"all((A==B, C!=4, 200<D))\"\n
    as

    def _f(A,B,C,D): return all((A==B, C!=4, 200<D))

    using python's compiler.

    Source code in tablite/utils.py
    def expression_interpreter(expression, columns):\n    \"\"\"\n    Interprets valid expressions such as:\n\n        \"all((A==B, C!=4, 200<D))\"\n\n    as:\n        def _f(A,B,C,D):\n            return all((A==B, C!=4, 200<D))\n\n    using python's compiler.\n    \"\"\"\n    if not isinstance(expression, str):\n        raise TypeError(f\"`{expression}` is not a str\")\n    if not isinstance(columns, list):\n        raise TypeError\n    if not all(isinstance(i, str) for i in columns):\n        raise TypeError\n\n    req_columns = \", \".join(i for i in columns if i in expression)\n    script = f\"def f({req_columns}):\\n    return {expression}\"\n    tree = ast.parse(script)\n    code = compile(tree, filename=\"blah\", mode=\"exec\")\n    namespace = {}\n    exec(code, namespace)\n    f = namespace[\"f\"]\n    if not callable(f):\n        raise ValueError(f\"The expression could not be parse: {expression}\")\n    return f\n
    "},{"location":"reference/utils/#tablite.utils.intercept","title":"tablite.utils.intercept(A, B)","text":"

    Enables calculation of the intercept of two range objects. Used to determine if a datablock contains a slice.

    PARAMETER DESCRIPTION A

    range

    B

    range

    RETURNS DESCRIPTION range

    The intercept of ranges A and B.

    Source code in tablite/utils.py
    def intercept(A, B):\n    \"\"\"Enables calculation of the intercept of two range objects.\n    Used to determine if a datablock contains a slice.\n\n    Args:\n        A: range\n        B: range\n\n    Returns:\n        range: The intercept of ranges A and B.\n    \"\"\"\n    type_check(A, range)\n    type_check(B, range)\n\n    if A.step < 1:\n        A = range(A.stop + 1, A.start + 1, 1)\n    if B.step < 1:\n        B = range(B.stop + 1, B.start + 1, 1)\n\n    if len(A) == 0:\n        return range(0)\n    if len(B) == 0:\n        return range(0)\n\n    if A.stop <= B.start:\n        return range(0)\n    if A.start >= B.stop:\n        return range(0)\n\n    if A.start <= B.start:\n        if A.stop <= B.stop:\n            start, end = B.start, A.stop\n        elif A.stop > B.stop:\n            start, end = B.start, B.stop\n        else:\n            raise ValueError(\"bad logic\")\n    elif A.start < B.stop:\n        if A.stop <= B.stop:\n            start, end = A.start, A.stop\n        elif A.stop > B.stop:\n            start, end = A.start, B.stop\n        else:\n            raise ValueError(\"bad logic\")\n    else:\n        raise ValueError(\"bad logic\")\n\n    a_steps = math.ceil((start - A.start) / A.step)\n    a_start = (a_steps * A.step) + A.start\n\n    b_steps = math.ceil((start - B.start) / B.step)\n    b_start = (b_steps * B.step) + B.start\n\n    if A.step == 1 or B.step == 1:\n        start = max(a_start, b_start)\n        step = max(A.step, B.step)\n        return range(start, end, step)\n    elif A.step == B.step:\n        a, b = min(A.start, B.start), max(A.start, B.start)\n        if (b - a) % A.step != 0:  # then the ranges are offset.\n            return range(0)\n        else:\n            return range(b, end, step)\n    else:\n        # determine common step size:\n        step = max(A.step, B.step) if math.gcd(A.step, B.step) != 1 else A.step * B.step\n        # examples:\n        # 119 <-- 17 if 1 != 1 else 119 <-- max(7, 17) if math.gcd(7, 17) != 1 else 7 * 17\n        #  30 <-- 30 if 3 != 1 else 90 <-- max(3, 30) if math.gcd(3, 30) != 1 else 3*30\n        if A.step < B.step:\n            for n in range(a_start, end, A.step):  # increment in smallest step to identify the first common value.\n                if n < b_start:\n                    continue\n                elif (n - b_start) % B.step == 0:\n                    return range(n, end, step)  # common value found.\n        else:\n            for n in range(b_start, end, B.step):\n                if n < a_start:\n                    continue\n                elif (n - a_start) % A.step == 0:\n                    return range(n, end, step)\n\n        return range(0)\n
    "},{"location":"reference/utils/#tablite.utils.summary_statistics","title":"tablite.utils.summary_statistics(values, counts)","text":"

    values: any type counts: integer

    returns dict with: - min (int/float, length of str, date) - max (int/float, length of str, date) - mean (int/float, length of str, date) - median (int/float, length of str, date) - stdev (int/float, length of str, date) - mode (int/float, length of str, date) - distinct (number of distinct values) - iqr (int/float, length of str, date) - sum (int/float, length of str, date) - histogram (2 arrays: values, count of each values)

    Source code in tablite/utils.py
    def summary_statistics(values, counts):\n    \"\"\"\n    values: any type\n    counts: integer\n\n    returns dict with:\n    - min (int/float, length of str, date)\n    - max (int/float, length of str, date)\n    - mean (int/float, length of str, date)\n    - median (int/float, length of str, date)\n    - stdev (int/float, length of str, date)\n    - mode (int/float, length of str, date)\n    - distinct (number of distinct values)\n    - iqr (int/float, length of str, date)\n    - sum (int/float, length of str, date)\n    - histogram (2 arrays: values, count of each values)\n    \"\"\"\n    # determine the dominant datatype:\n    dtypes = defaultdict(int)\n    most_frequent, most_frequent_dtype = 0, int\n    for v, c in zip(values, counts):\n        dtype = type(v)\n        total = dtypes[dtype] + c\n        dtypes[dtype] = total\n        if total > most_frequent:\n            most_frequent_dtype = dtype\n            most_frequent = total\n\n    if most_frequent == 0:\n        return {}\n\n    most_frequent_dtype = max(dtypes, key=dtypes.get)\n    mask = [type(v) == most_frequent_dtype for v in values]\n    v = list(compress(values, mask))\n    c = list(compress(counts, mask))\n\n    f = summary_methods.get(most_frequent_dtype, int)\n    result = f(v, c)\n    result[\"distinct\"] = len(values)\n    result[\"summary type\"] = most_frequent_dtype.__name__\n    result[\"histogram\"] = [values, counts]\n    assert set(result.keys()) == required_keys, \"Key missing!\"\n    return result\n
    "},{"location":"reference/utils/#tablite.utils.date_range","title":"tablite.utils.date_range(start, stop, step)","text":"Source code in tablite/utils.py
    def date_range(start, stop, step):\n    if not isinstance(start, datetime):\n        raise TypeError(\"start is not datetime\")\n    if not isinstance(stop, datetime):\n        raise TypeError(\"stop is not datetime\")\n    if not isinstance(step, timedelta):\n        raise TypeError(\"step is not timedelta\")\n    n = (stop - start) // step\n    return [start + step * i for i in range(n)]\n
    "},{"location":"reference/utils/#tablite.utils.dict_to_rows","title":"tablite.utils.dict_to_rows(d)","text":"Source code in tablite/utils.py
    def dict_to_rows(d):\n    type_check(d, dict)\n    rows = []\n    max_length = max(len(i) for i in d.values())\n    order = list(d.keys())\n    rows.append(order)\n    for i in range(max_length):\n        row = [d[k][i] for k in order]\n        rows.append(row)\n    return rows\n
    "},{"location":"reference/utils/#tablite.utils.calc_col_count","title":"tablite.utils.calc_col_count(letters: str)","text":"Source code in tablite/utils.py
    def calc_col_count(letters: str):\n    ord_nil = ord(\"A\") - 1\n    cols_per_letter = ord(\"Z\") - ord_nil\n    col_count = 0\n\n    for i, v in enumerate(reversed(letters)):\n        col_count = col_count + (ord(v) - ord_nil) * pow(cols_per_letter, i)\n\n    return col_count\n
    "},{"location":"reference/utils/#tablite.utils.calc_true_dims","title":"tablite.utils.calc_true_dims(sheet)","text":"Source code in tablite/utils.py
    def calc_true_dims(sheet):\n    src = sheet._get_source()\n    max_col, max_row = 0, 0\n\n    regex = re.compile(\"\\d+\")\n\n    def handleStartElement(name, attrs):\n        nonlocal max_col, max_row\n\n        if name == \"c\":\n            last_index = attrs[\"r\"]\n            idx, _ = next(regex.finditer(last_index)).span()\n            letters, digits = last_index[0:idx], int(last_index[idx:])\n\n            col_idx, row_idx = calc_col_count(letters), digits\n\n            max_col, max_row = max(max_col, col_idx), max(max_row, row_idx)\n\n    parser = expat.ParserCreate()\n    parser.buffer_text = True\n    parser.StartElementHandler = handleStartElement\n    parser.ParseFile(src)\n\n    return max_col, max_row\n
    "},{"location":"reference/utils/#tablite.utils.fixup_worksheet","title":"tablite.utils.fixup_worksheet(worksheet)","text":"Source code in tablite/utils.py
    def fixup_worksheet(worksheet):\n    try:\n        ws_cols, ws_rows = calc_true_dims(worksheet)\n\n        worksheet._max_column = ws_cols\n        worksheet._max_row = ws_rows\n    except Exception as e:\n        logging.error(f\"Failed to fetch true dimensions: {e}\")\n
    "},{"location":"reference/utils/#tablite.utils.update_access_time","title":"tablite.utils.update_access_time(path)","text":"Source code in tablite/utils.py
    def update_access_time(path):\n    path = Path(path)\n    stat = path.stat()\n    os.utime(path, (now(), stat.st_mtime))\n
    "},{"location":"reference/utils/#tablite.utils.load_numpy","title":"tablite.utils.load_numpy(path)","text":"Source code in tablite/utils.py
    def load_numpy(path):\n    update_access_time(path)\n\n    return np.load(path, allow_pickle=True, fix_imports=False)\n
    "},{"location":"reference/utils/#tablite.utils.select_type_name","title":"tablite.utils.select_type_name(dtypes: dict)","text":"Source code in tablite/utils.py
    def select_type_name(dtypes: dict):\n    dtypes = [t for t in dtypes.items() if t[0] != NoneType]\n\n    if len(dtypes) == 0:\n        return \"empty\"\n\n    (best_type, _), *_ = sorted(dtypes, key=lambda t: t[1], reverse=True)\n\n    return best_type.__name__\n
    "},{"location":"reference/utils/#tablite.utils.get_predominant_types","title":"tablite.utils.get_predominant_types(table, all_dtypes=None)","text":"Source code in tablite/utils.py
    def get_predominant_types(table, all_dtypes=None):\n    if all_dtypes is None:\n        all_dtypes = table.types()\n\n    dtypes = {\n        k: select_type_name(v)\n        for k, v in all_dtypes.items()\n    }\n\n    return dtypes\n
    "},{"location":"reference/utils/#tablite.utils.py_to_nim_encoding","title":"tablite.utils.py_to_nim_encoding(encoding: str) -> str","text":"Source code in tablite/utils.py
    def py_to_nim_encoding(encoding: str) -> str:\n    if encoding is None or encoding.lower() in [\"ascii\", \"utf8\", \"utf-8\", \"utf-8-sig\"]:\n        return \"ENC_UTF8\"\n    elif encoding.lower() in [\"utf16\", \"utf-16\"]:\n        return \"ENC_UTF16\"\n    elif encoding in Config.NIM_SUPPORTED_CONV_TYPES:\n        return f\"ENC_CONV|{encoding}\"\n\n    raise NotImplementedError(f\"encoding not implemented: {encoding}\")\n
    "},{"location":"reference/version/","title":"Version","text":""},{"location":"reference/version/#tablite.version","title":"tablite.version","text":""},{"location":"reference/version/#tablite.version-attributes","title":"Attributes","text":""},{"location":"reference/version/#tablite.version.__version_info__","title":"tablite.version.__version_info__ = (major, minor, patch) module-attribute","text":""},{"location":"reference/version/#tablite.version.__version__","title":"tablite.version.__version__ = '.'.join(str(i) for i in __version_info__) module-attribute","text":""}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Tablite","text":""},{"location":"#contents","title":"Contents","text":"
    • introduction
    • installation
    • feature overview
    • api
    • tutorial
    • latest updates
    • credits
    "},{"location":"#introduction","title":"Introduction","text":"

    Tablite seeks to be the go-to library for manipulating tabular data with an api that is as close in syntax to pure python as possible.

    "},{"location":"#even-smaller-memory-footprint","title":"Even smaller memory footprint","text":"

    Tablite uses numpys fileformat as a backend with strong abstraction, so that copy, append & repetition of data is handled in pages. This is imperative for incremental data processing.

    Tablite tests for memory footprint. One test compares the memory footprint of 10,000,000 integers where tablite will use < 1 Mb RAM in contrast to python which will require around 133.7 Mb of RAM (1M lists with 10 integers). Tablite also tests to assure that working with 1Tb of data is tolerable.

    Tablite achieves this minimal memory footprint by using a temporary storage set in config.Config.workdir as tempfile.gettempdir()/tablite-tmp. If your OS (windows/linux/mac) sits on a SSD this will benefit from high IOPS and permit slices of 9,000,000,000 rows in less than a second.

    "},{"location":"#multiprocessing-enabled-by-default","title":"Multiprocessing enabled by default","text":"

    Tablite uses numpy whereever possible and applies multiprocessing for bypassing the GIL on all major operations. CSV import is performed in C through using nims compiler and is as fast the hardware allows.

    "},{"location":"#all-algorithms-have-been-reworked-to-respect-memory-limits","title":"All algorithms have been reworked to respect memory limits","text":"

    Tablite respects the limits of free memory by tagging the free memory and defining task size before each memory intensive task is initiated (join, groupby, data import, etc). If you still run out of memory you may try to reduce the config.Config.PAGE_SIZE and rerun your program.

    "},{"location":"#100-support-for-all-python-datatypes","title":"100% support for all python datatypes","text":"

    Tablite wants to make it easy for you to work with data. tablite.Table's behave like a dict with lists:

    my_table[column name] = [... data ...].

    Tablite uses datatype mapping to native numpy types where possible and uses type mapping for non-native types such as timedelta, None, date, time\u2026 e.g. what you put in, is what you get out. This is inspired by bank python.

    "},{"location":"#light-weight","title":"Light weight","text":"

    Tablite is ~200 kB.

    "},{"location":"#helpful","title":"Helpful","text":"

    Tablite wants you to be productive, so a number of helpers are available.

    • Table.import_file to import csv*, tsv, txt, xls, xlsx, xlsm, ods, zip and logs. There is automatic type detection (see tutorial.ipynb )
    • To peek into any supported file use get_headers which shows the first 10 rows.
    • Use mytable.rows and mytable.columns to iterate over rows or columns.
    • Create multi-key .index for quick lookups.
    • Perform multi-key .sort,
    • Filter using .any and .all to select specific rows.
    • use multi-key .lookup and .join to find data across tables.
    • Perform .groupby and reorganise data as a .pivot table with max, min, sum, first, last, count, unique, average, st.deviation, median and mode
    • Append / concatenate tables with += which automatically sorts out the columns - even if they're not in perfect order.
    • Should you tables be similar but not the identical you can use .stack to \"stack\" tables on top of each other

    If you're still missing something add it to the wishlist

    "},{"location":"#installation","title":"Installation","text":"

    Get it from pypi:

    Install: pip install tablite Usage: >>> from tablite import Table

    "},{"location":"#build-test","title":"Build & test","text":"

    install nim >= 2.0.0

    run: chmod +x ./build_nim.sh run: ./build_nim.sh

    Should the default nim not be your desired taste, please use nims environment manager (atlas) and run source nim-2.0.0/activate.sh on UNIX or nim-2.0.0/activate.bat on windows.

    install python >= 3.8\npython -m venv /your/venv/dir\nactivate /your/venv/dir\npip install -r requirements.txt\npip install -r requirements_for_testing.py\npytest ./tests\n
    "},{"location":"#feature-overview","title":"Feature overview","text":"want to... this way... loop over rows [ row for row in table.rows ] loop over columns [ table[col_name] for col_name in table.columns ] slice myslice = table['A', 'B', slice(0,None,15)] get column by name my_table['A'] get row by index my_table[9_000_000_001] value update mytable['A'][2] = new value update w. list comprehension mytable['A'] = [ x*x for x in mytable['A'] if x % 2 != 0 ] join a_join = numbers.join(letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter'], kind='left') lookup travel_plan = friends.lookup(bustable, (DataTypes.time(21, 10), \"<=\", 'time'), ('stop', \"==\", 'stop')) groupby group_by = table.groupby(keys=['C', 'B'], functions=[('A', gb.count)]) pivot table my_pivot = t.pivot(rows=['C'], columns=['A'], functions=[('B', gb.sum), ('B', gb.count)], values_as_rows=False) index indices = old_table.index(*old_table.columns) sort lookup1_sorted = lookup_1.sort(**{'time': True, 'name':False, \"sort_mode\":'unix'}) filter true, false = unfiltered.filter( [{\"column1\": 'a', \"criteria\":\">=\", 'value2':3}, ... more criteria ... ], filter_type='all' ) find any any_even_rows = mytable.any('A': lambda x : x%2==0, 'B': lambda x > 0) find all all_even_rows = mytable.all('A': lambda x : x%2==0, 'B': lambda x > 0) to json json_str = my_table.to_json() from json Table.from_json(json_str)"},{"location":"#api","title":"API","text":"

    To view the detailed API see api

    "},{"location":"#tutorial","title":"Tutorial","text":"

    To learn more see the tutorial.ipynb (Jupyter notebook)

    "},{"location":"#latest-updates","title":"Latest updates","text":"

    See changelog.md

    "},{"location":"#credits","title":"Credits","text":"
    • Eugene Antonov - the api documentation.
    • Audrius Kulikajevas - Edge case testing / various bugs, Jupyter notebook integration.
    • Ovidijus Grigas - various bugs, documentation.
    • Martynas Kaunas - GroupBy functionality.
    • Sergej Sinkarenko - various bugs.
    • Lori Cooper - spell checking.
    "},{"location":"benchmarks/","title":"Benchmarks","text":"In\u00a0[2]: Copied!
    import psutil, os, gc, shutil, tempfile\nfrom pathlib import Path\nfrom time import perf_counter, time\nfrom tablite import Table\nfrom tablite.datasets import synthetic_order_data\nfrom tablite.config import Config\n\nConfig.TQDM_DISABLE = True\n
    import psutil, os, gc, shutil, tempfile from pathlib import Path from time import perf_counter, time from tablite import Table from tablite.datasets import synthetic_order_data from tablite.config import Config Config.TQDM_DISABLE = True In\u00a0[3]: Copied!
    process = psutil.Process(os.getpid())\n\ndef make_tables(sizes=[1,2,5,10,20,50]):\n    # The last tables are too big for RAM (~24Gb), so I create subtables of 1M rows and append them.\n    t = synthetic_order_data(Config.PAGE_SIZE)\n    real, flat = t.nbytes()\n    print(f\"Table {len(t):,} rows is {real/1e6:,.0f} Mb on disk\")\n\n    tables = [t]  # 1M rows.\n\n    last = 1\n    t2 = t.copy()\n    for i in sizes[1:]:\n        t2 = t2.copy()\n        for _ in range(i-last):\n            t2 += synthetic_order_data(Config.PAGE_SIZE)  # these are all unique\n        last = i\n        real, flat = t2.nbytes()\n        tables.append(t2)\n        print(f\"Table {len(t2):,} rows is {real/1e6:,.0f} Mb on disk\")\n    return tables\n\ntables = make_tables()\n
    process = psutil.Process(os.getpid()) def make_tables(sizes=[1,2,5,10,20,50]): # The last tables are too big for RAM (~24Gb), so I create subtables of 1M rows and append them. t = synthetic_order_data(Config.PAGE_SIZE) real, flat = t.nbytes() print(f\"Table {len(t):,} rows is {real/1e6:,.0f} Mb on disk\") tables = [t] # 1M rows. last = 1 t2 = t.copy() for i in sizes[1:]: t2 = t2.copy() for _ in range(i-last): t2 += synthetic_order_data(Config.PAGE_SIZE) # these are all unique last = i real, flat = t2.nbytes() tables.append(t2) print(f\"Table {len(t2):,} rows is {real/1e6:,.0f} Mb on disk\") return tables tables = make_tables()
    Table 1,000,000 rows is 256 Mb on disk\nTable 2,000,000 rows is 512 Mb on disk\nTable 5,000,000 rows is 1,280 Mb on disk\nTable 10,000,000 rows is 2,560 Mb on disk\nTable 20,000,000 rows is 5,120 Mb on disk\nTable 50,000,000 rows is 12,800 Mb on disk\n

    The values in the tables above are all unique!

    In\u00a0[4]: Copied!
    tables[-1]\n
    tables[-1] Out[4]: ~#1234567891011 0114014953182952021-10-06T00:00:0050814119375C3-4HGQ21\u00b0XYZ1.244647268201734421.367107051830455 129320231372182021-08-26T00:00:005007718568C5-5FZU0\u00b00.55294485347516132.6980406874392537 2312569602250812021-12-21T00:00:0050197029074C2-3GTK6\u00b0XYZ1.99739754559065617.513164305723787 3414012777817432021-08-23T00:00:0050818024969C4-3BYP6\u00b0XYZ0.047497125538289577.388171617130485 459426667674262021-07-31T00:00:0050307113074C5-2CCC21\u00b0ABC1.0219215027612885.21324123446987 5612186131851272021-12-01T00:00:0050484117249C5-4WGT21\u00b00.2038764258434556712.190974436133764 676070424343982021-11-29T00:00:0050578011564C2-3LUL0\u00b0XYZ2.2367835158480444.340628097363572.......................................49,999,9939999946602693775472021-09-17T00:00:005015409706C4-3AHQ21\u00b0XYZ0.083216645843125856.56780297752790549,999,9949999955709798646952021-08-01T00:00:0050149125006C1-2FWH6\u00b01.04763923662266419.50710544462706549,999,9959999963551956078252021-07-29T00:00:0050007026992C4-3GVG21\u00b02.20440816560941411.2706443974284949,999,99699999720762240577282021-10-16T00:00:0050950113339C5-4NKS0\u00b02.1593110498135494.21575620046596149,999,9979999986577247891352021-12-21T00:00:0050069114747C2-4LYGNone1.64809640191698683.094420483625827349,999,9989999999775312438842021-12-02T00:00:0050644129345C2-5DRH6\u00b02.30911421692753110.82706867207146849,999,999100000012290713920652021-08-23T00:00:0050706119732C4-5AGB6\u00b00.488871405593691630.8580085696389939 In\u00a0[5]: Copied!
    def save_load_benchmarks(tables):\n    tmp = Path(tempfile.gettempdir()) / \"junk\"\n    tmp.mkdir(exist_ok=True)\n\n    results = Table()\n    results.add_columns('rows', 'save (sec)', 'load (sec)')\n    for t in tables:\n        fn = tmp / f'{len(t)}.tpz'\n        start = perf_counter()\n        t.save(fn)\n        end = perf_counter()\n        save = round(end-start,3)\n        assert fn.exists()\n        \n        \n        start = perf_counter()\n        t2 = Table.load(fn)\n        end = perf_counter()\n        load = round(end-start,3)\n        print(f\"saving {len(t):,} rows ({fn.stat().st_size/1e6:,.0f} Mb) took {save:,.3f} seconds. loading took {load:,.3f} seconds\")\n        del t2\n        fn.unlink()\n        results.add_rows(len(t), save, load)\n    \n    r = results\n    r['save r/sec'] = [int(a/b) if b!=0  else \"nil\" for a,b in zip(r['rows'], r['save (sec)']) ]\n    r['load r/sec'] = [int(a/b) if b!=0  else \"nil\" for a,b in zip(r['rows'], r['load (sec)'])]\n\n    return results\n
    def save_load_benchmarks(tables): tmp = Path(tempfile.gettempdir()) / \"junk\" tmp.mkdir(exist_ok=True) results = Table() results.add_columns('rows', 'save (sec)', 'load (sec)') for t in tables: fn = tmp / f'{len(t)}.tpz' start = perf_counter() t.save(fn) end = perf_counter() save = round(end-start,3) assert fn.exists() start = perf_counter() t2 = Table.load(fn) end = perf_counter() load = round(end-start,3) print(f\"saving {len(t):,} rows ({fn.stat().st_size/1e6:,.0f} Mb) took {save:,.3f} seconds. loading took {load:,.3f} seconds\") del t2 fn.unlink() results.add_rows(len(t), save, load) r = results r['save r/sec'] = [int(a/b) if b!=0 else \"nil\" for a,b in zip(r['rows'], r['save (sec)']) ] r['load r/sec'] = [int(a/b) if b!=0 else \"nil\" for a,b in zip(r['rows'], r['load (sec)'])] return results In\u00a0[6]: Copied!
    slb = save_load_benchmarks(tables)\n
    slb = save_load_benchmarks(tables)
    saving 1,000,000 rows (49 Mb) took 2.148 seconds. loading took 0.922 seconds\nsaving 2,000,000 rows (98 Mb) took 4.267 seconds. loading took 1.820 seconds\nsaving 5,000,000 rows (246 Mb) took 10.618 seconds. loading took 4.482 seconds\nsaving 10,000,000 rows (492 Mb) took 21.291 seconds. loading took 8.944 seconds\nsaving 20,000,000 rows (984 Mb) took 42.603 seconds. loading took 17.821 seconds\nsaving 50,000,000 rows (2,461 Mb) took 106.644 seconds. loading took 44.600 seconds\n
    In\u00a0[7]: Copied!
    slb\n
    slb Out[7]: #rowssave (sec)load (sec)save r/secload r/sec 010000002.1480.9224655491084598 120000004.2671.824687131098901 2500000010.6184.4824708981115573 31000000021.2918.9444696821118067 42000000042.60317.8214694501122271 550000000106.64444.64688491121076

    With various compression options

    In\u00a0[8]: Copied!
    def save_compression_benchmarks(t):\n    tmp = Path(tempfile.gettempdir()) / \"junk\"\n    tmp.mkdir(exist_ok=True)\n\n    import zipfile  # https://docs.python.org/3/library/zipfile.html#zipfile.ZipFile\n    methods = [(None, zipfile.ZIP_STORED, \"zip stored\"), (None, zipfile.ZIP_LZMA, \"zip lzma\")]\n    methods += [(i, zipfile.ZIP_DEFLATED, \"zip deflated\") for i in range(0,10)]\n    methods += [(i, zipfile.ZIP_BZIP2, \"zip bzip2\") for i in range(1,10)]\n\n    results = Table()\n    results.add_columns('file size (Mb)', 'method', 'write (sec)', 'read (sec)')\n    for level, method, name in methods:\n        fn = tmp / f'{len(t)}.tpz'\n        start = perf_counter()  \n        t.save(fn, compression_method=method, compression_level=level)\n        end = perf_counter()\n        write = round(end-start,3)\n        assert fn.exists()\n        size = int(fn.stat().st_size/1e6)\n        # print(f\"{name}(level={level}): {len(t):,} rows ({size} Mb) took {write:,.3f} secconds to save\", end='')\n        \n        start = perf_counter()\n        t2 = Table.load(fn)\n        end = perf_counter()\n        read = round(end-start,3)\n        # print(f\" and {end-start:,.3} seconds to load\")\n        print(\".\", end='')\n        \n        del t2\n        fn.unlink()\n        results.add_rows(size, f\"{name}(level={level})\", write, read)\n        \n    \n    r = results\n    r.sort({'write (sec)':True})\n    r['write (rps)'] = [int(1_000_000/b) for b in r['write (sec)']]\n    r['read (rps)'] = [int(1_000_000/b) for b in r['read (sec)']]\n    return results\n
    def save_compression_benchmarks(t): tmp = Path(tempfile.gettempdir()) / \"junk\" tmp.mkdir(exist_ok=True) import zipfile # https://docs.python.org/3/library/zipfile.html#zipfile.ZipFile methods = [(None, zipfile.ZIP_STORED, \"zip stored\"), (None, zipfile.ZIP_LZMA, \"zip lzma\")] methods += [(i, zipfile.ZIP_DEFLATED, \"zip deflated\") for i in range(0,10)] methods += [(i, zipfile.ZIP_BZIP2, \"zip bzip2\") for i in range(1,10)] results = Table() results.add_columns('file size (Mb)', 'method', 'write (sec)', 'read (sec)') for level, method, name in methods: fn = tmp / f'{len(t)}.tpz' start = perf_counter() t.save(fn, compression_method=method, compression_level=level) end = perf_counter() write = round(end-start,3) assert fn.exists() size = int(fn.stat().st_size/1e6) # print(f\"{name}(level={level}): {len(t):,} rows ({size} Mb) took {write:,.3f} secconds to save\", end='') start = perf_counter() t2 = Table.load(fn) end = perf_counter() read = round(end-start,3) # print(f\" and {end-start:,.3} seconds to load\") print(\".\", end='') del t2 fn.unlink() results.add_rows(size, f\"{name}(level={level})\", write, read) r = results r.sort({'write (sec)':True}) r['write (rps)'] = [int(1_000_000/b) for b in r['write (sec)']] r['read (rps)'] = [int(1_000_000/b) for b in r['read (sec)']] return results In\u00a0[9]: Copied!
    scb = save_compression_benchmarks(tables[0])\n
    scb = save_compression_benchmarks(tables[0])
    .....................
    creating sort index:   0%|          | 0/1 [00:00<?, ?it/s]\rcreating sort index: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 1/1 [00:00<00:00, 268.92it/s]\n
    In\u00a0[10]: Copied!
    scb[0:20]\n
    scb[0:20] Out[10]: #file size (Mb)methodwrite (sec)read (sec)write (rps)read (rps) 0256zip stored(level=None)0.3960.47525252522105263 129zip lzma(level=None)95.1372.22810511448833 2256zip deflated(level=0)0.5350.59518691581680672 349zip deflated(level=1)2.150.9224651161084598 447zip deflated(level=2)2.2640.9124416961096491 543zip deflated(level=3)3.0490.833279761204819 644zip deflated(level=4)2.920.8623424651160092 742zip deflated(level=5)4.0340.8692478921150747 840zip deflated(level=6)8.5580.81168491250000 939zip deflated(level=7)13.6950.7787301912853471038zip deflated(level=8)56.9720.7921755212626261138zip deflated(level=9)122.6230.791815512642221229zip bzip2(level=1)15.1214.065661332460021329zip bzip2(level=2)16.0474.214623162373041429zip bzip2(level=3)16.8584.409593192268081529zip bzip2(level=4)17.6485.141566631945141629zip bzip2(level=5)18.6746.009535501664171729zip bzip2(level=6)19.4056.628515331508751829zip bzip2(level=7)19.9546.714501151489421929zip bzip2(level=8)20.5956.96148555143657

    Conclusions

    • Fastest: zip stored with no compression takes handles
    In\u00a0[11]: Copied!
    def to_sql_benchmark(t, rows=1_000_000):\n    t2 = t[:rows]\n    write_start = time()\n    _ = t2.to_sql(name='1')\n    write_end = time()\n    write = round(write_end-write_start,3)\n    return ( t.to_sql.__name__, write, 0, len(t2), \"\" , \"\" )\n
    def to_sql_benchmark(t, rows=1_000_000): t2 = t[:rows] write_start = time() _ = t2.to_sql(name='1') write_end = time() write = round(write_end-write_start,3) return ( t.to_sql.__name__, write, 0, len(t2), \"\" , \"\" ) In\u00a0[12]: Copied!
    def to_json_benchmark(t, rows=1_000_000):\n    t2 = t[:rows]\n\n    tmp = Path(tempfile.gettempdir()) / \"junk\"\n    tmp.mkdir(exist_ok=True)\n    path = tmp / \"1.json\" \n    \n    write_start = time()\n    bytestr = t2.to_json()\n    with path.open('w') as fo:\n        fo.write(bytestr)\n    write_end = time()\n    write = round(write_end-write_start,3)\n\n    read_start = time()\n    with path.open('r') as fi:\n        _ = Table.from_json(fi.read())  # <-- JSON\n    read_end = time()\n    read = round(read_end-read_start,3)\n\n    return ( t.to_json.__name__, write, read, len(t2), int(path.stat().st_size/1e6), \"\" )\n
    def to_json_benchmark(t, rows=1_000_000): t2 = t[:rows] tmp = Path(tempfile.gettempdir()) / \"junk\" tmp.mkdir(exist_ok=True) path = tmp / \"1.json\" write_start = time() bytestr = t2.to_json() with path.open('w') as fo: fo.write(bytestr) write_end = time() write = round(write_end-write_start,3) read_start = time() with path.open('r') as fi: _ = Table.from_json(fi.read()) # <-- JSON read_end = time() read = round(read_end-read_start,3) return ( t.to_json.__name__, write, read, len(t2), int(path.stat().st_size/1e6), \"\" ) In\u00a0[13]: Copied!
    def f(t, args):\n    rows, c1, c1_kw, c2, c2_kw = args\n    t2 = t[:rows]\n\n    call = getattr(t2, c1)\n    assert callable(call)\n\n    write_start = time()\n    call(**c1_kw)\n    write_end = time()\n    write = round(write_end-write_start,3)\n\n    for _ in range(10):\n        gc.collect()\n\n    read_start = time()\n    if callable(c2):\n        c2(**c2_kw)\n    read_end = time()\n    read = round(read_end-read_start,3)\n\n    fn = c2_kw['path']\n    assert fn.exists()\n    fs = int(fn.stat().st_size/1e6)\n    config = {k:v for k,v in c2_kw.items() if k!= 'path'}\n\n    return ( c1, write, read, len(t2), fs , str(config))\n
    def f(t, args): rows, c1, c1_kw, c2, c2_kw = args t2 = t[:rows] call = getattr(t2, c1) assert callable(call) write_start = time() call(**c1_kw) write_end = time() write = round(write_end-write_start,3) for _ in range(10): gc.collect() read_start = time() if callable(c2): c2(**c2_kw) read_end = time() read = round(read_end-read_start,3) fn = c2_kw['path'] assert fn.exists() fs = int(fn.stat().st_size/1e6) config = {k:v for k,v in c2_kw.items() if k!= 'path'} return ( c1, write, read, len(t2), fs , str(config)) In\u00a0[14]: Copied!
    def import_export_benchmarks(tables):\n    Config.PROCESSING_MODE = Config.FALSE\n        \n    t = sorted(tables, key=lambda x: len(x), reverse=True)[0]\n    \n    tmp = Path(tempfile.gettempdir()) / \"junk\"\n    tmp.mkdir(exist_ok=True)   \n\n    args = [\n        (   100_000, \"to_xlsx\", {'path': tmp/'1.xlsx'}, Table.from_file, {\"path\":tmp/'1.xlsx', \"sheet\":\"pyexcel_sheet1\"}),\n        (    50_000,  \"to_ods\",  {'path': tmp/'1.ods'}, Table.from_file, {\"path\":tmp/'1.ods', \"sheet\":\"pyexcel_sheet1\"} ),  # 50k rows, otherwise MemoryError.\n        ( 1_000_000,  \"to_csv\",  {'path': tmp/'1.csv'}, Table.from_file, {\"path\":tmp/'1.csv'}                           ),\n        ( 1_000_000,  \"to_csv\",  {'path': tmp/'1.csv'}, Table.from_file, {\"path\":tmp/'1.csv', \"guess_datatypes\":False}),\n        (10_000_000,  \"to_csv\",  {'path': tmp/'1.csv'}, Table.from_file, {\"path\":tmp/'1.csv', \"guess_datatypes\":False}),\n        ( 1_000_000,  \"to_tsv\",  {'path': tmp/'1.tsv'}, Table.from_file, {\"path\":tmp/'1.tsv'}                           ),\n        ( 1_000_000, \"to_text\",  {'path': tmp/'1.txt'}, Table.from_file, {\"path\":tmp/'1.txt'}                           ),\n        ( 1_000_000, \"to_html\", {'path': tmp/'1.html'}, Table.from_file, {\"path\":tmp/'1.html'}                          ),\n        ( 1_000_000, \"to_hdf5\", {'path': tmp/'1.hdf5'}, Table.from_file, {\"path\":tmp/'1.hdf5'}                          )\n    ]\n\n    results = Table()\n    results.add_columns('method', 'write (s)', 'read (s)', 'rows', 'size (Mb)', 'config')\n\n    results.add_rows( to_sql_benchmark(t) )\n    results.add_rows( to_json_benchmark(t) )\n\n    for arg in args:\n        if len(t)<arg[0]:\n            continue\n        print(\".\", end='')\n        try:\n            results.add_rows( f(t, arg) )\n        except MemoryError:\n            results.add_rows( arg[1], \"Memory Error\", \"NIL\", args[0], \"NIL\", \"N/A\")\n    \n    r = results\n    r['read r/sec'] = [int(a/b) if b!=0  else \"nil\" for a,b in zip(r['rows'], r['read (s)']) ]\n    r['write r/sec'] = [int(a/b) if b!=0  else \"nil\" for a,b in zip(r['rows'], r['write (s)'])]\n\n    shutil.rmtree(tmp)\n    return results\n
    def import_export_benchmarks(tables): Config.PROCESSING_MODE = Config.FALSE t = sorted(tables, key=lambda x: len(x), reverse=True)[0] tmp = Path(tempfile.gettempdir()) / \"junk\" tmp.mkdir(exist_ok=True) args = [ ( 100_000, \"to_xlsx\", {'path': tmp/'1.xlsx'}, Table.from_file, {\"path\":tmp/'1.xlsx', \"sheet\":\"pyexcel_sheet1\"}), ( 50_000, \"to_ods\", {'path': tmp/'1.ods'}, Table.from_file, {\"path\":tmp/'1.ods', \"sheet\":\"pyexcel_sheet1\"} ), # 50k rows, otherwise MemoryError. ( 1_000_000, \"to_csv\", {'path': tmp/'1.csv'}, Table.from_file, {\"path\":tmp/'1.csv'} ), ( 1_000_000, \"to_csv\", {'path': tmp/'1.csv'}, Table.from_file, {\"path\":tmp/'1.csv', \"guess_datatypes\":False}), (10_000_000, \"to_csv\", {'path': tmp/'1.csv'}, Table.from_file, {\"path\":tmp/'1.csv', \"guess_datatypes\":False}), ( 1_000_000, \"to_tsv\", {'path': tmp/'1.tsv'}, Table.from_file, {\"path\":tmp/'1.tsv'} ), ( 1_000_000, \"to_text\", {'path': tmp/'1.txt'}, Table.from_file, {\"path\":tmp/'1.txt'} ), ( 1_000_000, \"to_html\", {'path': tmp/'1.html'}, Table.from_file, {\"path\":tmp/'1.html'} ), ( 1_000_000, \"to_hdf5\", {'path': tmp/'1.hdf5'}, Table.from_file, {\"path\":tmp/'1.hdf5'} ) ] results = Table() results.add_columns('method', 'write (s)', 'read (s)', 'rows', 'size (Mb)', 'config') results.add_rows( to_sql_benchmark(t) ) results.add_rows( to_json_benchmark(t) ) for arg in args: if len(t) In\u00a0[15]: Copied!
    ieb = import_export_benchmarks(tables)\n
    ieb = import_export_benchmarks(tables)
    .........writing 12,000,000 records to /tmp/junk/1.hdf5... done\n
    In\u00a0[16]: Copied!
    ieb\n
    ieb Out[16]: #methodwrite (s)read (s)rowssize (Mb)configread r/secwrite r/sec 0to_sql12.34501000000nil81004 1to_json10.8144.406100000014222696392472 2to_xlsx10.56921.5721000009{'sheet': 'pyexcel_sheet1'}46359461 3to_ods29.17529.487500003{'sheet': 'pyexcel_sheet1'}16951713 4to_csv14.31515.7311000000108{}6356869856 5to_csv14.4388.1691000000108{'guess_datatypes': False}12241469261 6to_csv140.64599.45100000001080{'guess_datatypes': False}10055371100 7to_tsv13.83415.7631000000108{}6343972285 8to_text13.93715.6821000000108{}6376771751 9to_html12.5780.531000000228{}18867927950310to_hdf55.0112.3451000000316{}81004199600

    Conclusions

    Best:

    • to/from JSON wins with 2.3M rps read
    • to/from CSV/TSV/TEXT comes 2nd with config guess_datatypes=False with ~ 100k rps

    Worst:

    • to/from ods burst the memory footprint and hence had to be reduced to 100k rows. It also had the slowest read rate with 1450 rps.
    In\u00a0[17]: Copied!
    def contains_benchmark(table):\n    results = Table()\n    results.add_columns( \"column\", \"time (s)\" )\n    for name,col in table.columns.items():\n        n = len(col)\n        start,stop,step = int(n*0.02), int(n*0.98), int(n/100)\n        selection = col[start:stop:step]\n        total_time = 0.0\n        for v in selection:\n            start_time = perf_counter()\n            v in col  # <--- test!\n            end_time = perf_counter()\n            total_time += (end_time - start_time)\n        avg_time = total_time / len(selection)\n        results.add_rows( name, round(avg_time,3) )\n\n    return results\n
    def contains_benchmark(table): results = Table() results.add_columns( \"column\", \"time (s)\" ) for name,col in table.columns.items(): n = len(col) start,stop,step = int(n*0.02), int(n*0.98), int(n/100) selection = col[start:stop:step] total_time = 0.0 for v in selection: start_time = perf_counter() v in col # <--- test! end_time = perf_counter() total_time += (end_time - start_time) avg_time = total_time / len(selection) results.add_rows( name, round(avg_time,3) ) return results In\u00a0[18]: Copied!
    has_it = contains_benchmark(tables[-1])\nhas_it\n
    has_it = contains_benchmark(tables[-1]) has_it Out[18]: #columntime (s) 0#0.001 110.043 220.032 330.001 440.001 550.001 660.006 770.003 880.006 990.00710100.04311110.655 In\u00a0[19]: Copied!
    def slicing_benchmark(table):\n    n = len(table)\n    start,stop,step = int(0.02*n), int(0.98*n), int(n / 20)  # from 2% to 98% in 20 large steps\n    start_time = perf_counter()\n    snip = table[start:stop:step]\n    end_time = perf_counter()\n    print(f\"reading {len(table):,} rows to find {len(snip):,} rows took {end_time-start_time:.3f} sec\")\n    return snip\n
    def slicing_benchmark(table): n = len(table) start,stop,step = int(0.02*n), int(0.98*n), int(n / 20) # from 2% to 98% in 20 large steps start_time = perf_counter() snip = table[start:stop:step] end_time = perf_counter() print(f\"reading {len(table):,} rows to find {len(snip):,} rows took {end_time-start_time:.3f} sec\") return snip In\u00a0[20]: Copied!
    slice_it = slicing_benchmark(tables[-1])\n
    slice_it = slicing_benchmark(tables[-1])
    reading 50,000,000 rows to find 20 rows took 1.435 sec\n
    In\u00a0[22]: Copied!
    def column_selection_benchmark(tables):\n    results = Table()\n    results.add_columns( 'rows')\n    results.add_columns(*[f\"n cols={i}\" for i,_ in enumerate(tables[0].columns,start=1)])\n\n    for table in tables:\n        rr = [len(table)]\n        for ix, name in enumerate(table.columns):\n            cols = list(table.columns)[:ix+1]\n            start_time = perf_counter()\n            table[cols]\n            end_time = perf_counter()\n            rr.append(f\"{end_time-start_time:.5f}\")\n        results.add_rows( rr )\n    return results\n
    def column_selection_benchmark(tables): results = Table() results.add_columns( 'rows') results.add_columns(*[f\"n cols={i}\" for i,_ in enumerate(tables[0].columns,start=1)]) for table in tables: rr = [len(table)] for ix, name in enumerate(table.columns): cols = list(table.columns)[:ix+1] start_time = perf_counter() table[cols] end_time = perf_counter() rr.append(f\"{end_time-start_time:.5f}\") results.add_rows( rr ) return results In\u00a0[23]: Copied!
    csb = column_selection_benchmark(tables)\nprint(\"times below are are in seconds\")\ncsb\n
    csb = column_selection_benchmark(tables) print(\"times below are are in seconds\") csb
    times below are are in seconds\n
    Out[23]: #rowsn cols=1n cols=2n cols=3n cols=4n cols=5n cols=6n cols=7n cols=8n cols=9n cols=10n cols=11n cols=12 010000000.000010.000060.000040.000040.000040.000040.000040.000040.000040.000040.000040.00004 120000000.000010.000080.000030.000030.000030.000030.000030.000030.000030.000030.000040.00004 250000000.000010.000050.000040.000040.000040.000040.000040.000040.000040.000040.000040.00004 3100000000.000020.000050.000040.000040.000040.000040.000070.000050.000050.000050.000050.00005 4200000000.000030.000060.000050.000050.000050.000050.000060.000060.000060.000060.000060.00006 5500000000.000090.000110.000100.000090.000090.000090.000090.000090.000090.000090.000100.00009 In\u00a0[33]: Copied!
    def iterrows_benchmark(table):\n    results = Table()\n    results.add_columns( 'n columns', 'time (s)')\n\n    columns = ['1']\n    for column in list(table.columns):\n        columns.append(column)\n        snip = table[columns, slice(500_000,1_500_000)]\n        start_time = perf_counter()\n        counts = 0\n        for row in snip.rows:\n            counts += 1\n        end_time = perf_counter()\n        results.add_rows( len(columns), round(end_time-start_time,3))\n\n    return results\n
    def iterrows_benchmark(table): results = Table() results.add_columns( 'n columns', 'time (s)') columns = ['1'] for column in list(table.columns): columns.append(column) snip = table[columns, slice(500_000,1_500_000)] start_time = perf_counter() counts = 0 for row in snip.rows: counts += 1 end_time = perf_counter() results.add_rows( len(columns), round(end_time-start_time,3)) return results In\u00a0[34]: Copied!
    iterb = iterrows_benchmark(tables[-1])\niterb\n
    iterb = iterrows_benchmark(tables[-1]) iterb Out[34]: #n columnstime (s) 029.951 139.816 249.859 359.93 469.985 579.942 689.958 799.867 8109.96 9119.93210129.8311139.861 In\u00a0[35]: Copied!
    import matplotlib.pyplot as plt\nplt.plot(iterb['n columns'], iterb['time (s)'])\nplt.show()\n
    import matplotlib.pyplot as plt plt.plot(iterb['n columns'], iterb['time (s)']) plt.show() In\u00a0[28]: Copied!
    tables[-1].types()\n
    tables[-1].types() Out[28]:
    {'#': {int: 50000000},\n '1': {int: 50000000},\n '2': {str: 50000000},\n '3': {int: 50000000},\n '4': {int: 50000000},\n '5': {int: 50000000},\n '6': {str: 50000000},\n '7': {str: 50000000},\n '8': {str: 50000000},\n '9': {str: 50000000},\n '10': {float: 50000000},\n '11': {str: 50000000}}
    In\u00a0[29]: Copied!
    def dtypes_benchmark(tables):\n    dtypes_results = Table()\n    dtypes_results.add_columns(\"rows\", \"time (s)\")\n\n    for table in tables:\n        start_time = perf_counter()\n        dt = table.types()\n        end_time = perf_counter()\n        assert isinstance(dt, dict) and len(dt) != 0\n        dtypes_results.add_rows( len(table), round(end_time-start_time, 3) )\n\n    return dtypes_results\n
    def dtypes_benchmark(tables): dtypes_results = Table() dtypes_results.add_columns(\"rows\", \"time (s)\") for table in tables: start_time = perf_counter() dt = table.types() end_time = perf_counter() assert isinstance(dt, dict) and len(dt) != 0 dtypes_results.add_rows( len(table), round(end_time-start_time, 3) ) return dtypes_results In\u00a0[30]: Copied!
    dtype_b = dtypes_benchmark(tables)\ndtype_b\n
    dtype_b = dtypes_benchmark(tables) dtype_b Out[30]: #rowstime (s) 010000000.0 120000000.0 250000000.0 3100000000.0 4200000000.0 5500000000.001 In\u00a0[31]: Copied!
    def any_benchmark(tables):\n    results = Table()\n    results.add_columns(\"rows\", *list(tables[0].columns))\n\n    for table in tables:\n        tmp = [len(table)]\n        for column in list(table.columns):\n            v = table[column][0]\n            start_time = perf_counter()\n            _ = table.any(**{column: v})\n            end_time = perf_counter()           \n            tmp.append(round(end_time-start_time,3))\n\n        results.add_rows( tmp )\n    return results\n
    def any_benchmark(tables): results = Table() results.add_columns(\"rows\", *list(tables[0].columns)) for table in tables: tmp = [len(table)] for column in list(table.columns): v = table[column][0] start_time = perf_counter() _ = table.any(**{column: v}) end_time = perf_counter() tmp.append(round(end_time-start_time,3)) results.add_rows( tmp ) return results In\u00a0[32]: Copied!
    anyb = any_benchmark(tables)\nanyb\n
    anyb = any_benchmark(tables) anyb Out[32]: ~rows#1234567891011 010000000.1330.1330.1780.1330.2920.1470.1690.1430.2270.2590.1460.17 120000000.2680.2630.3430.2650.5670.2940.3350.2750.4640.5230.2890.323 250000000.6690.6530.9140.6691.4360.7230.8380.6941.1741.3350.6780.818 3100000001.3141.351.7451.3362.9021.491.6831.4142.3542.6181.3431.536 4200000002.5562.5343.3372.6025.6452.8273.2252.6464.5145.082.6933.083 5500000006.5716.4238.4556.69914.4847.9897.7986.25910.98912.486.7327.767 In\u00a0[36]: Copied!
    def all_benchmark(tables):\n    results = Table()\n    results.add_columns(\"rows\", *list(tables[0].columns))\n\n    for table in tables:\n        tmp = [len(table)]\n        for column in list(table.columns):\n            v = table[column][0]\n            start_time = perf_counter()\n            _ = table.all(**{column: v})\n            end_time = perf_counter()           \n            tmp.append(round(end_time-start_time,3))\n\n        results.add_rows( tmp )\n    return results\n
    def all_benchmark(tables): results = Table() results.add_columns(\"rows\", *list(tables[0].columns)) for table in tables: tmp = [len(table)] for column in list(table.columns): v = table[column][0] start_time = perf_counter() _ = table.all(**{column: v}) end_time = perf_counter() tmp.append(round(end_time-start_time,3)) results.add_rows( tmp ) return results In\u00a0[37]: Copied!
    allb = all_benchmark(tables)\nallb\n
    allb = all_benchmark(tables) allb Out[37]: ~rows#1234567891011 010000000.120.1210.1620.1220.2640.1380.1550.1270.2090.2370.1330.151 120000000.2370.2350.3110.2380.520.2660.2970.3410.4510.530.2610.285 250000000.6750.6980.9520.5941.6050.6590.8120.7191.2241.3530.6640.914 3100000001.3141.3321.7071.3323.0911.4631.7811.3662.3582.6381.4091.714 4200000002.5762.3133.112.3965.2072.5732.9212.4034.0414.6582.4632.808 5500000005.8965.827.735.95612.9097.457.275.98110.18311.5766.3727.414 In\u00a0[\u00a0]: Copied!
    \n
    In\u00a0[38]: Copied!
    def unique_benchmark(tables):\n    results = Table()\n    results.add_columns(\"rows\", *list(tables[0].columns))\n    \n    for table in tables:\n        length = len(table)\n\n        tmp = [len(table)]\n        for column in list(table.columns):\n            start_time = perf_counter()\n            try:\n                L = table[column].unique()\n                dt = perf_counter() - start_time\n            except MemoryError:\n                dt = -1\n            tmp.append(round(dt,3))\n            assert 0 < len(L) <= length    \n\n        results.add_rows( tmp )\n    return results\n
    def unique_benchmark(tables): results = Table() results.add_columns(\"rows\", *list(tables[0].columns)) for table in tables: length = len(table) tmp = [len(table)] for column in list(table.columns): start_time = perf_counter() try: L = table[column].unique() dt = perf_counter() - start_time except MemoryError: dt = -1 tmp.append(round(dt,3)) assert 0 < len(L) <= length results.add_rows( tmp ) return results In\u00a0[39]: Copied!
    ubm = unique_benchmark(tables)\nubm\n
    ubm = unique_benchmark(tables) ubm Out[39]: ~rows#1234567891011 010000000.0220.0810.2480.0440.0160.0610.1150.1360.0960.0850.0940.447 120000000.1760.2710.5050.0870.0310.1240.2290.2790.1980.170.3051.471 250000000.1980.4991.2630.2180.0760.3110.570.6850.4740.4250.5952.744 3100000000.5021.1232.5350.4330.1550.6151.1281.3750.960.851.3165.826 4200000000.9562.3365.0350.8830.3191.2292.2682.7481.9131.7462.73311.883 5500000002.3956.01912.4992.1780.7643.0735.6086.8194.8284.2797.09730.511 In\u00a0[40]: Copied!
    def index_benchmark(tables):\n    results = Table()\n    results.add_columns(\"rows\", *list(tables[0].columns))\n    \n    for table in tables:\n\n        tmp = [len(table)]\n        for column in list(table.columns):\n            start_time = perf_counter()\n            try:\n                _ = table.index(column)\n                dt = perf_counter() - start_time\n            except MemoryError:\n                dt = -1\n            tmp.append(round(dt,3))\n            \n        results.add_rows( tmp )\n    return results\n
    def index_benchmark(tables): results = Table() results.add_columns(\"rows\", *list(tables[0].columns)) for table in tables: tmp = [len(table)] for column in list(table.columns): start_time = perf_counter() try: _ = table.index(column) dt = perf_counter() - start_time except MemoryError: dt = -1 tmp.append(round(dt,3)) results.add_rows( tmp ) return results In\u00a0[41]: Copied!
    ibm = index_benchmark(tables)\nibm\n
    ibm = index_benchmark(tables) ibm Out[41]: ~rows#1234567891011 010000001.9491.7931.4321.1061.0511.231.3381.4931.4111.3031.9992.325 120000002.8833.5172.8562.2172.1242.4622.6762.9862.7092.6064.0494.461 250000006.3829.0497.0965.6285.3536.3126.6497.5216.716.45910.2710.747 31000000012.55318.50613.9511.33510.72412.50913.3315.05113.50212.89919.76921.999 42000000024.71737.89628.56822.66621.47226.32727.15730.06427.33225.82238.31143.399 55000000063.01697.07772.00755.60954.09961.79768.23675.0769.02266.15299.183109.969

    Multi-column index next:

    In\u00a0[42]: Copied!
    def multi_column_index_benchmark(tables):\n    \n    selection = [\"4\", \"7\", \"8\", \"9\"]\n    results = Table()\n    results.add_columns(\"rows\", *range(1,len(selection)+1))\n    \n    for table in tables:\n\n        tmp = [len(table)]\n        for index in range(1,5):\n            start_time = perf_counter()\n            try:\n                _ = table.index(*selection[:index])\n                dt = perf_counter() - start_time\n            except MemoryError:\n                dt = -1\n            tmp.append(round(dt,3))\n            print('.', end='')\n            \n        results.add_rows( tmp )\n    return results\n
    def multi_column_index_benchmark(tables): selection = [\"4\", \"7\", \"8\", \"9\"] results = Table() results.add_columns(\"rows\", *range(1,len(selection)+1)) for table in tables: tmp = [len(table)] for index in range(1,5): start_time = perf_counter() try: _ = table.index(*selection[:index]) dt = perf_counter() - start_time except MemoryError: dt = -1 tmp.append(round(dt,3)) print('.', end='') results.add_rows( tmp ) return results In\u00a0[43]: Copied!
    mcib = multi_column_index_benchmark(tables)\nmcib\n
    mcib = multi_column_index_benchmark(tables) mcib
    ........................
    Out[43]: #rows1234 010000001.0582.1333.2154.052 120000002.124.2786.5468.328 250000005.30310.8916.69320.793 31000000010.58122.40733.46241.91 42000000021.06445.95467.78184.828 55000000052.347109.551166.6211.053 In\u00a0[44]: Copied!
    def drop_duplicates_benchmark(tables):\n    results = Table()\n    results.add_columns(\"rows\", *list(tables[0].columns))\n    \n    for table in tables:\n        result = [len(table)]\n        cols = []\n        for name in list(table.columns):\n            cols.append(name)\n            start_time = perf_counter()\n            try:\n                _ = table.drop_duplicates(*cols)\n                dt = perf_counter() - start_time\n            except MemoryError:\n                dt = -1\n            result.append(round(dt,3))\n            print('.', end='')\n        \n        results.add_rows( result )\n    return results\n
    def drop_duplicates_benchmark(tables): results = Table() results.add_columns(\"rows\", *list(tables[0].columns)) for table in tables: result = [len(table)] cols = [] for name in list(table.columns): cols.append(name) start_time = perf_counter() try: _ = table.drop_duplicates(*cols) dt = perf_counter() - start_time except MemoryError: dt = -1 result.append(round(dt,3)) print('.', end='') results.add_rows( result ) return results In\u00a0[45]: Copied!
    ddb = drop_duplicates_benchmark(tables)\nddb\n
    ddb = drop_duplicates_benchmark(tables) ddb
    ........................................................................
    Out[45]: ~rows#1234567891011 010000001.7612.3583.3133.9014.6154.9615.8356.5347.4548.1088.8039.682 120000003.0114.936.9347.979.26410.26812.00613.51714.9216.63117.93219.493 250000006.82713.85318.63721.23724.54827.1131.15735.02638.99243.53146.02250.433 31000000013.23831.74641.14146.91753.17258.24167.99274.65182.7491.45897.666104.82 42000000025.93277.75100.34109.314123.514131.874148.432163.57179.121196.047208.686228.059 55000000064.237312.222364.886388.249429.724466.685494.418535.367581.666607.306634.343683.858"},{"location":"benchmarks/#benchmarks","title":"Benchmarks\u00b6","text":"

    These benchmarks seek to establish the performance of tablite as a user sees it.

    Overview

    Input/Output Various column functions Base functions Core functions - Save / Load .tpz format- Save tables to various formats- Import data from various formats - Setitem / getitem- iter- equal, not equal- copy- t += t- t *= t- contains- remove all- replace- index- unique- histogram- statistics- count - Setitem / getitem- iter / rows- equal, not equal- load- save- copy- stack- types- display_dict- show- to_dict- as_json_serializable- index - expression- filter- sort_index- reindex- drop_duplicates- sort- is_sorted- any- all- drop - replace- groupby- pivot- joins- lookup- replace missing values- transpose- pivot_transpose- diff"},{"location":"benchmarks/#input-output","title":"Input / Output\u00b6","text":""},{"location":"benchmarks/#create-tables-from-synthetic-data","title":"Create tables from synthetic data.\u00b6","text":""},{"location":"benchmarks/#save-load-tpz-format","title":"Save / Load .tpz format\u00b6","text":"

    Without default compression settings (10% slower than uncompressed, 20% of uncompressed filesize)

    "},{"location":"benchmarks/#save-load-tables-to-from-various-formats","title":"Save / load tables to / from various formats\u00b6","text":"

    The handlers for saving / export are:

    • to_sql
    • to_json
    • to_xls
    • to_ods
    • to_csv
    • to_tsv
    • to_text
    • to_html
    • to_hdf5
    "},{"location":"benchmarks/#various-column-functions","title":"Various column functions\u00b6","text":"
    • Setitem / getitem
    • iter
    • equal, not equal
    • copy
    • t += t
    • t *= t
    • contains
    • remove all
    • replace
    • index
    • unique
    • histogram
    • statistics
    • count
    "},{"location":"benchmarks/#various-table-functions","title":"Various table functions\u00b6","text":""},{"location":"benchmarks/#slicing","title":"Slicing\u00b6","text":"

    Slicing operations are used in many places.

    "},{"location":"benchmarks/#tabletypes","title":"Table.types()\u00b6","text":"

    Table.types() is implemented for near constant speed lookup.

    Here is an example:

    "},{"location":"benchmarks/#tableany","title":"Table.any\u00b6","text":""},{"location":"benchmarks/#tableall","title":"Table.all\u00b6","text":""},{"location":"benchmarks/#tablefilter","title":"Table.filter\u00b6","text":""},{"location":"benchmarks/#tableunique","title":"Table.unique\u00b6","text":""},{"location":"benchmarks/#tableindex","title":"Table.index\u00b6","text":"

    Single column index first:

    "},{"location":"benchmarks/#drop-duplicates","title":"drop duplicates\u00b6","text":""},{"location":"changelog/","title":"Changelog","text":"Version Change 2023.9.0 Adding Table.match operation. 2023.8.0 Nim backend for csv importer.Improve excel importer.Improve slicing consistency.Logical cores re-enabled on *nix based systems.Filter is now type safe.Added merge utility.Various bugfixes. 2023.6.5 Fix issues with get_headers falling back to text reading when reading 0 lines of excel, fix issue where reading excel file would ignore file count, excel file reader now has parity for linecount selection. 2023.6.4 Fix a logic bug in get_headers that caused one extra line to be returned than requested. 2023.6.3 Updated the way reference counting works. Tablite now tracks references to used pages and cleans them up based on number of references to those pages in the current process. This change allows to handle deep table clones when sending tables via processes (pickling/unpickling), whereas previous implementation would corrupt all tables using same pages due to reference counting asserting that all tables are shallow copies to the same object. 2023.6.2 Updated mplite dependency, changed to soft version requirement to prevent pipeline freezes due to small bugfixes in mplite. 2023.6.1 Major change of the backend processes. Speed up of ~6x. For more see the release notes 2022.11.19 Fixed some memory leaks. 2022.11.18 copy, filter, sort, any, all methods now properly respects the table subclass.Filter for tables with under SINGLE_PROCESSING_LIMIT rows will run on same process to reduce overhead.Errors within child processes now properly propagate to parent.Table.reset_storage(include_imports=True) now allows the user to reset the storage but exclude any imported files by setting include_imports=False during Table.reset(...).Bug: A column with 1,None,2 would be written to csv & tsv as \"1,None,2\". Now it is written \"1,,2\" where None means absent.Fix mp join producing mismatched columns lengths when different table lengths are used as an input or when join product is longer than the input table. 2022.11.17 Table.load now properly subclassess the table instead of always resulting in tablite.Table.Table.from_* methods now respect subclassess, fixed some from_* methods which were instance methods and not class methods.Fixed Table.from_dict only accepting list and tuple but not tablite.Column which is an equally valid type.Fix lookup parity in single process and multiple process outputs.Fix an issue with multiprocess lookup where no matches would throw instead of producing None.Fix an issue with filtering an empty table. 2022.11.16 Changed join to process 1M rows per task to avoid potential OOM on lower memory systems. Added mp_merge_columns to MemoryManager that merges column pages into a single column.Fix join parity in single process and multiple process outputs.Fix an issue with multiprocess join where no matches would throw instead of producing None. 2022.11.15 Bump mplite to avoid deadlock issues OS kill the process. 2022.11.14 Improve locking mechanism to allow retries when opening file as the previous solution could cause deadlocks when running multiple threads. 2022.11.13 Fix an issue with copying empty pages. 2022.11.12 Tablite now is now able to create it's own temporary directory. 2022.11.11 text_reader tqdm tracks the entire process now. text_reader properly respects free memory in *nix based systems. text_reader no longer discriminates against hyperthreaded cores. 2022.11.10 get_headers now uses plain openpyxl instead of pyexcel wrapper to speed up fetch times ~10x on certain files. 2022.11.9 get_headers can fail safe on unrecognized characters. 2022.11.8 Fix a bug with task size calculation on single core systems. 2022.11.7 Added TABLITE_TMPDIR environment variable for setting tablite work directory. Characters that fail to be read text reader due to improper encoding will be skipped. Fixed an issue where single column text files with no column delimiters would be imported as empty tables. 2022.11.6 Date inference fix 2022.11.5 Fixed negative slicing issues 2022.11.4 Transpose API changes: table.transpose(...) was renamed to table.pivot_transpose(...) new table.transpose() and table.T were added, it's functionality acts similarly to numpy.T, the column headers are used the first row in the table when transposing. 2022.11.3 Bugfix for non-ascii encoded strings during t.add_rows(...) 2022.11.2 As utf-8 is ascii compatible, the file reader utils selects utf-8 instead of ascii as a default. 2022.11.1 bugfix in datatypes.infer() where 1 was inferred as int, not float. 2022.11.0 New table features: Table.diff(other, columns=...), table.remove_duplicates_rows(), table.drop_na(*arg),table.replace(target,replacement), table.imputation(sources, targets, methods=...), table.to_pandas() and Table.from_pandas(pd.DataFrame),table.to_dict(columns, slice), Table.from_dict(),table.transpose(columns, keep, ...), New column features: Column.count(item), Column[:] is guaranteed to return a python list.Column.to_numpy(slice) returns np.ndarray. new tools library: from tablite import tools with: date_range(start,end), xround(value, multiple, up=None), and, guess as short-cut for Datatypes.guess(...). bugfixes: __eq__ was updated but missed __ne__.in operator in filter would crash if datatypes were not strings. 2022.10.11 filter now accepts any expression (str) that can be compiled by pythons compiler 2022.10.11 Bugfix for .any and .all. The code now executes much faster 2022.10.10 Bugfix for Table.import_file: import_as has been removed from keywords. 2022.10.10 All Table functions now have tqdm progressbar. 2022.10.10 More robust calculation for task size for multiprocessing. 2022.10.10 Dependency update: mplite==1.2.0 is now required. 2022.10.9 Bugfix for Table.import_file: files with duplicate header names would only have last duplicate name imported.Now the headers are made unique using name_x where x is a number. 2022.10.8 Bugfix for groupby: Where keys are empty error should have been raised.Where there are no functions, unique keypairs are returned. 2022.10.7 Bugfix for Column.statistics() for an empty column 2022.10.6 Bugfix for __setitem__: tbl['a'] = [] is now seen as tbl.add_column('a')Bugfix for __getitem__: calling a missing key raises keyerror. 2022.10.5 Bugfix for summary statistics. 2022.10.4 Bugfix for join shortcut. 2022.10.3 Bugfix for DataTypes where bool was evaluated wrongly 2022.10.0 Added ability to reindex in table.reindex(index=[0,1...,n,n-1]) 2022.9.0 Added ability to store python objects (example).Added warning when user iterates over non-rectangular dataset. 2022.8.0 Added table.export(path) which exports tablite Tables to file format given by the file extension. For example my_table.export('example.xlsx').supported formats are: json, html, xlsx, xls, csv, tsv, txt, ods and sql. 2022.7.8 Added ability to forward tqdm progressbar into Table.import_file(..., tqdm=your_tqdm), so that Jupyter notebook can use it in display-methods. 2022.7.7 Added method Table.to_sql() for export to ANSI-92 SQL enginesBugfix on to_json for timedelta. Jupyter notebook provides nice view using Table._repr_html_() JS-users can use .as_json_serializable where suitable. 2022.7.6 get_headers now takes argument (path, linecount=10) 2022.7.5 added helper Table.as_json_serializable as Jupyterkernel compat. 2022.7.4 adder helper Table.to_dict, and updated Table.to_json 2022.7.3 table.to_json now takes kwargs: row_count, columns, slice_, start_on 2022.7.2 documentation update. 2022.7.1 minor bugfix. 2022.7.0 BREAKING CHANGES- Tablite now uses HDF5 as backend. - Has multiprocessing enabled by default. - Is 20x faster. - Completely new API. 2022.6.0 DataTypes.guess([list of strings]) returns the best matching python datatype."},{"location":"tutorial/","title":"Tutorial","text":"In\u00a0[1]: Copied!
    from tablite import Table\n\n## To create a tablite table is as simple as populating a dictionary:\nt = Table({'A':[1,2,3], 'B':['a','b','c']})\n
    from tablite import Table ## To create a tablite table is as simple as populating a dictionary: t = Table({'A':[1,2,3], 'B':['a','b','c']}) In\u00a0[2]: Copied!
    ## In this notebook we can show tables in the HTML style:\nt\n
    ## In this notebook we can show tables in the HTML style: t Out[2]: #AB 01a 12b 23c In\u00a0[3]: Copied!
    ## or the ascii style:\nt.show()\n
    ## or the ascii style: t.show()
    +==+=+=+\n|# |A|B|\n+--+-+-+\n| 0|1|a|\n| 1|2|b|\n| 2|3|c|\n+==+=+=+\n
    In\u00a0[4]: Copied!
    ## or if you'd like to inspect the table, use:\nprint(str(t))\n
    ## or if you'd like to inspect the table, use: print(str(t))
    Table(2 columns, 3 rows)\n
    In\u00a0[5]: Copied!
    ## You can also add all columns at once (slower) if you prefer. \nt2 = Table(headers=('A','B'), rows=((1,'a'),(2,'b'),(3,'c')))\nassert t==t2\n
    ## You can also add all columns at once (slower) if you prefer. t2 = Table(headers=('A','B'), rows=((1,'a'),(2,'b'),(3,'c'))) assert t==t2 In\u00a0[6]: Copied!
    ## or load data:\nt3 = Table.from_file('tests/data/book1.csv')\n\n## to view any table in the notebook just let jupyter show the table. If you're using the terminal use .show(). \n## Note that show gives either first and last 7 rows or the whole table if it is less than 20 rows.\nt3\n
    ## or load data: t3 = Table.from_file('tests/data/book1.csv') ## to view any table in the notebook just let jupyter show the table. If you're using the terminal use .show(). ## Note that show gives either first and last 7 rows or the whole table if it is less than 20 rows. t3
    Collecting tasks: 'tests/data/book1.csv'\nDumping tasks: 'tests/data/book1.csv'\n
    importing file: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 1/1 [00:00<00:00, 487.82it/s]\n
    Out[6]: #abcdef 010.0606060610.0909090910.1212121210.1515151520.181818182 120.1212121210.2424242420.4848484850.969696971.939393939 230.2424242420.4848484850.969696971.9393939393.878787879 340.4848484850.969696971.9393939393.8787878797.757575758 450.969696971.9393939393.8787878797.75757575815.51515152 561.9393939393.8787878797.75757575815.5151515231.03030303 673.8787878797.75757575815.5151515231.0303030362.06060606.....................383916659267088.033318534175.066637068350.0133274000000.0266548000000.0394033318534175.066637068350.0133274000000.0266548000000.0533097000000.0404166637068350.0133274000000.0266548000000.0533097000000.01066190000000.04142133274000000.0266548000000.0533097000000.01066190000000.02132390000000.04243266548000000.0533097000000.01066190000000.02132390000000.04264770000000.04344533097000000.01066190000000.02132390000000.04264770000000.08529540000000.044451066190000000.02132390000000.04264770000000.08529540000000.017059100000000.0 In\u00a0[7]: Copied!
    ## should you however want to select the headers instead of importing everything\n## (which maybe timeconsuming), simply use get_headers(path)\nfrom tablite.tools import get_headers\nfrom pathlib import Path\npath = Path('tests/data/book1.csv')\nsample = get_headers(path, linecount=5)\nprint(f\"sample is of type {type(sample)} and has the following entries:\")\nfor k,v in sample.items():\n    print(k)\n    if isinstance(v,list):\n        for r in sample[k]:\n            print(\"\\t\", r)\n
    ## should you however want to select the headers instead of importing everything ## (which maybe timeconsuming), simply use get_headers(path) from tablite.tools import get_headers from pathlib import Path path = Path('tests/data/book1.csv') sample = get_headers(path, linecount=5) print(f\"sample is of type {type(sample)} and has the following entries:\") for k,v in sample.items(): print(k) if isinstance(v,list): for r in sample[k]: print(\"\\t\", r)
    sample is of type <class 'dict'> and has the following entries:\ndelimiter\nbook1.csv\n\t ['a', 'b', 'c', 'd', 'e', 'f']\n\t ['1', '0.060606061', '0.090909091', '0.121212121', '0.151515152', '0.181818182']\n\t ['2', '0.121212121', '0.242424242', '0.484848485', '0.96969697', '1.939393939']\n\t ['3', '0.242424242', '0.484848485', '0.96969697', '1.939393939', '3.878787879']\n\t ['4', '0.484848485', '0.96969697', '1.939393939', '3.878787879', '7.757575758']\n\t ['5', '0.96969697', '1.939393939', '3.878787879', '7.757575758', '15.51515152']\n
    In\u00a0[8]: Copied!
    ## to extend a table by adding columns, use t[new] = [new values]\nt['C'] = [4,5,6]\n## but make sure the column has the same length as the rest of the table!\nt\n
    ## to extend a table by adding columns, use t[new] = [new values] t['C'] = [4,5,6] ## but make sure the column has the same length as the rest of the table! t Out[8]: #ABC 01a4 12b5 23c6 In\u00a0[9]: Copied!
    ## should you want to mix datatypes, tablite will not complain:\nfrom datetime import datetime, date,time,timedelta\nimport numpy as np\n## What you put in ...\nt4 = Table()\nt4['mixed'] = [\n    -1,0,1,  # regular integers\n    -12345678909876543211234567890987654321,  # very very large integer\n    None,np.nan,  # null values \n    \"one\", \"\",  # strings\n    True,False,  # booleans\n    float('inf'), 0.01,  # floats\n    date(2000,1,1),   # date\n    datetime(2002,2,3,23,0,4,6660),  # datetime\n    time(12,12,12),  # time\n    timedelta(days=3, seconds=5678)  # timedelta\n]\n## ... is exactly what you get out:\nt4\n
    ## should you want to mix datatypes, tablite will not complain: from datetime import datetime, date,time,timedelta import numpy as np ## What you put in ... t4 = Table() t4['mixed'] = [ -1,0,1, # regular integers -12345678909876543211234567890987654321, # very very large integer None,np.nan, # null values \"one\", \"\", # strings True,False, # booleans float('inf'), 0.01, # floats date(2000,1,1), # date datetime(2002,2,3,23,0,4,6660), # datetime time(12,12,12), # time timedelta(days=3, seconds=5678) # timedelta ] ## ... is exactly what you get out: t4 Out[9]: #mixed 0-1 10 21 3-12345678909876543211234567890987654321 4None 5nan 6one 7 8True 9False10inf110.01122000-01-01132002-02-03 23:00:04.0066601412:12:12153 days, 1:34:38 In\u00a0[10]: Copied!
    ## also if you claim the values back as a python list:\nfor item in list(t4['mixed']):\n    print(item)\n
    ## also if you claim the values back as a python list: for item in list(t4['mixed']): print(item)
    -1\n0\n1\n-12345678909876543211234567890987654321\nNone\nnan\none\n\nTrue\nFalse\ninf\n0.01\n2000-01-01\n2002-02-03 23:00:04.006660\n12:12:12\n3 days, 1:34:38\n

    The column itself (__repr__) shows us the pid, file location and the entries, so you know exactly what you're working with.

    In\u00a0[11]: Copied!
    t4['mixed']\n
    t4['mixed'] Out[11]:
    Column(/tmp/tablite-tmp/pid-54911, [-1 0 1 -12345678909876543211234567890987654321 None nan 'one' '' True\n False inf 0.01 datetime.date(2000, 1, 1)\n datetime.datetime(2002, 2, 3, 23, 0, 4, 6660) datetime.time(12, 12, 12)\n datetime.timedelta(days=3, seconds=5678)])
    In\u00a0[12]: Copied!
    ## to view the datatypes in a column, use Column.types()\ntype_dict = t4['mixed'].types()\nfor k,v in type_dict.items():\n    print(k,v)\n
    ## to view the datatypes in a column, use Column.types() type_dict = t4['mixed'].types() for k,v in type_dict.items(): print(k,v)
    <class 'int'> 4\n<class 'NoneType'> 1\n<class 'float'> 3\n<class 'str'> 2\n<class 'bool'> 2\n<class 'datetime.date'> 1\n<class 'datetime.datetime'> 1\n<class 'datetime.time'> 1\n<class 'datetime.timedelta'> 1\n
    In\u00a0[13]: Copied!
    ## You may have noticed that all datatypes in t3 where identified as floats, despite their origin from a text type file.\n## This is because tablite guesses the most probable datatype using the `.guess` function on each column.\n## You can use the .guess function like this:\nfrom tablite import DataTypes\nt3['a'] = DataTypes.guess(t3['a'])\n## You can also convert the datatype using a list comprehension\nt3['b'] = [float(v) for v in t3['b']]\nt3\n
    ## You may have noticed that all datatypes in t3 where identified as floats, despite their origin from a text type file. ## This is because tablite guesses the most probable datatype using the `.guess` function on each column. ## You can use the .guess function like this: from tablite import DataTypes t3['a'] = DataTypes.guess(t3['a']) ## You can also convert the datatype using a list comprehension t3['b'] = [float(v) for v in t3['b']] t3 Out[13]: #abcdef 010.0606060610.0909090910.1212121210.1515151520.181818182 120.1212121210.2424242420.4848484850.969696971.939393939 230.2424242420.4848484850.969696971.9393939393.878787879 340.4848484850.969696971.9393939393.8787878797.757575758 450.969696971.9393939393.8787878797.75757575815.51515152 561.9393939393.8787878797.75757575815.5151515231.03030303 673.8787878797.75757575815.5151515231.0303030362.06060606.....................383916659267088.033318534175.066637068350.0133274000000.0266548000000.0394033318534175.066637068350.0133274000000.0266548000000.0533097000000.0404166637068350.0133274000000.0266548000000.0533097000000.01066190000000.04142133274000000.0266548000000.0533097000000.01066190000000.02132390000000.04243266548000000.0533097000000.01066190000000.02132390000000.04264770000000.04344533097000000.01066190000000.02132390000000.04264770000000.08529540000000.044451066190000000.02132390000000.04264770000000.08529540000000.017059100000000.0 In\u00a0[14]: Copied!
    t = Table()\nfor column_name in 'abcde':\n    t[column_name] =[i for i in range(5)]\n
    t = Table() for column_name in 'abcde': t[column_name] =[i for i in range(5)]

    (2) we want to add two new columns using the functions:

    In\u00a0[15]: Copied!
    def f1(a,b,c):\n    return a+b+c+1\ndef f2(b,c,d):\n    return b*c*d\n
    def f1(a,b,c): return a+b+c+1 def f2(b,c,d): return b*c*d

    (3) and we want to compute two new columns f and g:

    In\u00a0[16]: Copied!
    t.add_columns('f', 'g')\n
    t.add_columns('f', 'g')

    (4) we can now use the filter, to iterate over the table, and add the values to the two new columns:

    In\u00a0[17]: Copied!
    f,g=[],[]\nfor row in t['a', 'b', 'c', 'd'].rows:\n    a, b, c, d = row\n\n    f.append(f1(a, b, c))\n    g.append(f2(b, c, d))\nt['f'] = f\nt['g'] = g\n\nassert len(t) == 5\nassert list(t.columns) == list('abcdefg')\nt\n
    f,g=[],[] for row in t['a', 'b', 'c', 'd'].rows: a, b, c, d = row f.append(f1(a, b, c)) g.append(f2(b, c, d)) t['f'] = f t['g'] = g assert len(t) == 5 assert list(t.columns) == list('abcdefg') t Out[17]: #abcdefg 00000010 11111141 22222278 3333331027 4444441364

    Take note that if your dataset is assymmetric, a warning will be show:

    In\u00a0[18]: Copied!
    assymmetric_table = Table({'a':[1,2,3], 'b':[1,2]})\nfor row in assymmetric_table.rows:\n    print(row)\n## warning at the bottom ---v\n
    assymmetric_table = Table({'a':[1,2,3], 'b':[1,2]}) for row in assymmetric_table.rows: print(row) ## warning at the bottom ---v
    [1, 1]\n[2, 2]\n[3, None]\n
    /home/bjorn/github/tablite/tablite/base.py:1188: UserWarning: Column b has length 2 / 3. None will appear as fill value.\n  warnings.warn(f\"Column {name} has length {len(column)} / {n_max}. None will appear as fill value.\")\n
    In\u00a0[19]: Copied!
    table7 = Table(columns={\n'A': [1,1,2,2,3,4],\n'B': [1,1,2,2,30,40],\n'C': [-1,-2,-3,-4,-5,-6]\n})\nindex = table7.index('A', 'B')\nfor k, v in index.items():\n    print(\"key\", k, \"indices\", v)\n
    table7 = Table(columns={ 'A': [1,1,2,2,3,4], 'B': [1,1,2,2,30,40], 'C': [-1,-2,-3,-4,-5,-6] }) index = table7.index('A', 'B') for k, v in index.items(): print(\"key\", k, \"indices\", v)
    key (1, 1) indices [0, 1]\nkey (2, 2) indices [2, 3]\nkey (3, 30) indices [4]\nkey (4, 40) indices [5]\n

    The keys are created for each unique column-key-pair, and the value is the index where the key is found. To fetch all rows for key (2,2), we can use:

    In\u00a0[20]: Copied!
    for ix, row in enumerate(table7.rows):\n    if ix in index[(2,2)]:\n        print(row)\n
    for ix, row in enumerate(table7.rows): if ix in index[(2,2)]: print(row)
    [2, 2, -3]\n[2, 2, -4]\n
    In\u00a0[21]: Copied!
    ## to append one table to another, use + or += \nprint('length before:', len(t3))  # length before: 45\nt5 = t3 + t3  \nprint('length after +', len(t5))  # length after + 90\nt5 += t3 \nprint('length after +=', len(t5))  # length after += 135\n## if you need a lot of numbers for a test, you can repeat a table using * and *=\nt5 *= 1_000\nprint('length after +=', len(t5))  # length after += 135000\n
    ## to append one table to another, use + or += print('length before:', len(t3)) # length before: 45 t5 = t3 + t3 print('length after +', len(t5)) # length after + 90 t5 += t3 print('length after +=', len(t5)) # length after += 135 ## if you need a lot of numbers for a test, you can repeat a table using * and *= t5 *= 1_000 print('length after +=', len(t5)) # length after += 135000
    length before: 45\nlength after + 90\nlength after += 135\nlength after += 135000\n
    In\u00a0[22]: Copied!
    t5\n
    t5 Out[22]: #abcdef 010.0606060610.0909090910.1212121210.1515151520.181818182 120.1212121210.2424242420.4848484850.969696971.939393939 230.2424242420.4848484850.969696971.9393939393.878787879 340.4848484850.969696971.9393939393.8787878797.757575758 450.969696971.9393939393.8787878797.75757575815.51515152 561.9393939393.8787878797.75757575815.5151515231.03030303 673.8787878797.75757575815.5151515231.0303030362.06060606..................... 134,9933916659267088.033318534175.066637068350.0133274000000.0266548000000.0 134,9944033318534175.066637068350.0133274000000.0266548000000.0533097000000.0 134,9954166637068350.0133274000000.0266548000000.0533097000000.01066190000000.0 134,99642133274000000.0266548000000.0533097000000.01066190000000.02132390000000.0 134,99743266548000000.0533097000000.01066190000000.02132390000000.04264770000000.0 134,99844533097000000.01066190000000.02132390000000.04264770000000.08529540000000.0 134,999451066190000000.02132390000000.04264770000000.08529540000000.017059100000000.0 In\u00a0[23]: Copied!
    ## if your are in doubt whether your tables will be the same you can use .stack(other)\nassert t.columns != t2.columns  # compares list of column names.\nt6 = t.stack(t2)\nt6\n
    ## if your are in doubt whether your tables will be the same you can use .stack(other) assert t.columns != t2.columns # compares list of column names. t6 = t.stack(t2) t6 Out[23]: #abcdefgAB 00000010NoneNone 11111141NoneNone 22222278NoneNone 3333331027NoneNone 4444441364NoneNone 5NoneNoneNoneNoneNoneNoneNone1a 6NoneNoneNoneNoneNoneNoneNone2b 7NoneNoneNoneNoneNoneNoneNone3c In\u00a0[24]: Copied!
    ## As you can see above, t6['C'] is padded with \"None\" where t2 was missing the columns.\n\n## if you need a more detailed view of the columns you can iterate:\nfor name in t.columns:\n    col_from_t = t[name]\n    if name in t2.columns:\n        col_from_t2 = t2[name]\n        print(name, col_from_t == col_from_t2)\n    else:\n        print(name, \"not in t2\")\n
    ## As you can see above, t6['C'] is padded with \"None\" where t2 was missing the columns. ## if you need a more detailed view of the columns you can iterate: for name in t.columns: col_from_t = t[name] if name in t2.columns: col_from_t2 = t2[name] print(name, col_from_t == col_from_t2) else: print(name, \"not in t2\")
    a not in t2\nb not in t2\nc not in t2\nd not in t2\ne not in t2\nf not in t2\ng not in t2\n
    In\u00a0[25]: Copied!
    ## to make a copy of a table, use table.copy()\nt3_copy = t3.copy()\n\n## you can also perform multi criteria selections using getitem [ ... ]\nt3_slice = t3['a','b','d', 5:25:5]\nt3_slice\n
    ## to make a copy of a table, use table.copy() t3_copy = t3.copy() ## you can also perform multi criteria selections using getitem [ ... ] t3_slice = t3['a','b','d', 5:25:5] t3_slice Out[25]: #abd 061.9393939397.757575758 11162.06060606248.2424242 2161985.9393947943.757576 32163550.06061254200.2424 In\u00a0[26]: Copied!
    ##deleting items also works the same way:\ndel t3_slice[1:3]  # delete row number 2 & 3 \nt3_slice\n
    ##deleting items also works the same way: del t3_slice[1:3] # delete row number 2 & 3 t3_slice Out[26]: #abd 061.9393939397.757575758 12163550.06061254200.2424 In\u00a0[27]: Copied!
    ## to wipe a table, use .clear:\nt3_slice.clear()\nt3_slice\n
    ## to wipe a table, use .clear: t3_slice.clear() t3_slice Out[27]: Empty Table In\u00a0[28]: Copied!
    ## tablite uses .npy for storage because it is fast.\n## this means you can make a table persistent using .save\nlocal_file = Path(\"local_file.tpz\")\nt5.save(local_file)\n\nold_t5 = Table.load(local_file)\nprint(\"the t5 table had\", len(old_t5), \"rows\")  # the t5 table had 135000 rows\n\ndel old_t5  # only removes the in-memory object\n\nprint(\"old_t5 still exists?\", local_file.exists())\nprint(\"path:\", local_file)\n\nimport os\nos.remove(local_file)\n
    ## tablite uses .npy for storage because it is fast. ## this means you can make a table persistent using .save local_file = Path(\"local_file.tpz\") t5.save(local_file) old_t5 = Table.load(local_file) print(\"the t5 table had\", len(old_t5), \"rows\") # the t5 table had 135000 rows del old_t5 # only removes the in-memory object print(\"old_t5 still exists?\", local_file.exists()) print(\"path:\", local_file) import os os.remove(local_file)
    loading 'local_file.tpz' file:  55%|\u2588\u2588\u2588\u2588\u2588\u258d    | 9851/18000 [00:02<00:01, 4386.96it/s]
    loading 'local_file.tpz' file: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 18000/18000 [00:04<00:00, 4417.27it/s]\n
    the t5 table had 135000 rows\nold_t5 still exists? True\npath: local_file.tpz\n

    If you want to save a table from one session to another use save=True. This tells the garbage collector to leave the tablite Table on disk, so you can load it again without changing your code.

    For example:

    First time you run t = Table.import_file(....big.csv) it may take a minute or two.

    If you then add t.save=True and restart python, the second time you run t = Table.import_file(....big.csv) it will take a few milliseconds instead of minutes.

    In\u00a0[29]: Copied!
    unfiltered = Table({'a':[1,2,3,4], 'b':[10,20,30,40]})\n
    unfiltered = Table({'a':[1,2,3,4], 'b':[10,20,30,40]}) In\u00a0[30]: Copied!
    true,false = unfiltered.filter(\n    [\n        {\"column1\": 'a', \"criteria\":\">=\", 'value2':3}\n    ], filter_type='all'\n)\n
    true,false = unfiltered.filter( [ {\"column1\": 'a', \"criteria\":\">=\", 'value2':3} ], filter_type='all' ) In\u00a0[31]: Copied!
    true\n
    true Out[31]: #ab 0330 1440 In\u00a0[32]: Copied!
    false.show()  # using show here to show that terminal users can have a nice view too.\n
    false.show() # using show here to show that terminal users can have a nice view too.
    +==+=+==+\n|# |a|b |\n+--+-+--+\n| 0|1|10|\n| 1|2|20|\n+==+=+==+\n
    In\u00a0[33]: Copied!
    ty = Table({'a':[1,2,3,4],'b': [10,20,30,40]})\n
    ty = Table({'a':[1,2,3,4],'b': [10,20,30,40]}) In\u00a0[34]: Copied!
    ## typical python\nany(i > 3 for i in ty['a'])\n
    ## typical python any(i > 3 for i in ty['a']) Out[34]:
    True
    In\u00a0[35]: Copied!
    ## hereby you can do:\nany( ty.any(**{'a':lambda x:x>3}).rows )\n
    ## hereby you can do: any( ty.any(**{'a':lambda x:x>3}).rows ) Out[35]:
    True
    In\u00a0[36]: Copied!
    ## if you have multiple criteria this also works:\nall( ty.all(**{'a': lambda x:x>=2, 'b': lambda x:x<=30}).rows )\n
    ## if you have multiple criteria this also works: all( ty.all(**{'a': lambda x:x>=2, 'b': lambda x:x<=30}).rows ) Out[36]:
    True
    In\u00a0[37]: Copied!
    ## or this if you want to see the table.\nty.all(a=lambda x:x>2, b=lambda x:x<=30)\n
    ## or this if you want to see the table. ty.all(a=lambda x:x>2, b=lambda x:x<=30) Out[37]: #ab 0330 In\u00a0[38]: Copied!
    ## As `all` and `any` returns tables, this also means that you can chain operations:\nty.any(a=lambda x:x>2).any(b=30)\n
    ## As `all` and `any` returns tables, this also means that you can chain operations: ty.any(a=lambda x:x>2).any(b=30) Out[38]: #ab 0330 In\u00a0[39]: Copied!
    table = Table({\n    'A':[ 1, None, 8, 3, 4, 6,  5,  7,  9],\n    'B':[10,'100', 1, 1, 1, 1, 10, 10, 10],\n    'C':[ 0,    1, 0, 1, 0, 1,  0,  1,  0],\n})\ntable\n
    table = Table({ 'A':[ 1, None, 8, 3, 4, 6, 5, 7, 9], 'B':[10,'100', 1, 1, 1, 1, 10, 10, 10], 'C':[ 0, 1, 0, 1, 0, 1, 0, 1, 0], }) table Out[39]: #ABC 01100 1None1001 2810 3311 4410 5611 65100 77101 89100 In\u00a0[40]: Copied!
    sort_order = {'B': False, 'C': False, 'A': False}\nassert not table.is_sorted(mapping=sort_order)\n\nsorted_table = table.sort(mapping=sort_order)\nsorted_table\n
    sort_order = {'B': False, 'C': False, 'A': False} assert not table.is_sorted(mapping=sort_order) sorted_table = table.sort(mapping=sort_order) sorted_table
    creating sort index: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 3/3 [00:00<00:00, 2719.45it/s]\ncreating sort index: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 3/3 [00:00<00:00, 3434.20it/s]\njoin: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 3/3 [00:00<00:00, 1902.47it/s]\n

    Sort is reasonable effective as it uses multiprocessing above a million fields.

    Hint: You can set this limit in tablite.config, like this:

    In\u00a0[41]: Copied!
    from tablite.config import Config\nprint(f\"multiprocessing is used above {Config.SINGLE_PROCESSING_LIMIT:,} fields\")\n
    from tablite.config import Config print(f\"multiprocessing is used above {Config.SINGLE_PROCESSING_LIMIT:,} fields\")
    multiprocessing is used above 1,000,000 fields\n
    In\u00a0[42]: Copied!
    import math\nn = math.ceil(1_000_000 / (9*3))\n\ntable = Table({\n    'A':[ 1, None, 8, 3, 4, 6,  5,  7,  9]*n,\n    'B':[10,'100', 1, 1, 1, 1, 10, 10, 10]*n,\n    'C':[ 0,    1, 0, 1, 0, 1,  0,  1,  0]*n,\n})\ntable\n
    import math n = math.ceil(1_000_000 / (9*3)) table = Table({ 'A':[ 1, None, 8, 3, 4, 6, 5, 7, 9]*n, 'B':[10,'100', 1, 1, 1, 1, 10, 10, 10]*n, 'C':[ 0, 1, 0, 1, 0, 1, 0, 1, 0]*n, }) table Out[42]: #ABC 01100 1None1001 2810 3311 4410 5611 65100............ 333,335810 333,336311 333,337410 333,338611 333,3395100 333,3407101 333,3419100 In\u00a0[43]: Copied!
    import time as cputime\nstart = cputime.time()\nsort_order = {'B': False, 'C': False, 'A': False}\nsorted_table = table.sort(mapping=sort_order)  # sorts 1M values.\nprint(\"table sorting took \", round(cputime.time() - start,3), \"secs\")\nsorted_table\n
    import time as cputime start = cputime.time() sort_order = {'B': False, 'C': False, 'A': False} sorted_table = table.sort(mapping=sort_order) # sorts 1M values. print(\"table sorting took \", round(cputime.time() - start,3), \"secs\") sorted_table
    creating sort index: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 3/3 [00:00<00:00,  4.20it/s]\njoin: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 3/3 [00:00<00:00, 18.17it/s]
    table sorting took  0.913 secs\n
    \n
    In\u00a0[44]: Copied!
    n = math.ceil(1_000_000 / (9*3))\n\ntable = Table({\n    'A':[ 1, None, 8, 3, 4, 6,  5,  7,  9]*n,\n    'B':[10,'100', 1, 1, 1, 1, 10, 10, 10]*n,\n    'C':[ 0,    1, 0, 1, 0, 1,  0,  1,  0]*n,\n})\ntable\n
    n = math.ceil(1_000_000 / (9*3)) table = Table({ 'A':[ 1, None, 8, 3, 4, 6, 5, 7, 9]*n, 'B':[10,'100', 1, 1, 1, 1, 10, 10, 10]*n, 'C':[ 0, 1, 0, 1, 0, 1, 0, 1, 0]*n, }) table Out[44]: #ABC 01100 1None1001 2810 3311 4410 5611 65100............ 333,335810 333,336311 333,337410 333,338611 333,3395100 333,3407101 333,3419100 In\u00a0[45]: Copied!
    from tablite import GroupBy as gb\ngrpby = table.groupby(keys=['C', 'B'], functions=[('A', gb.count)])\ngrpby\n
    from tablite import GroupBy as gb grpby = table.groupby(keys=['C', 'B'], functions=[('A', gb.count)]) grpby
    groupby: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 333342/333342 [00:00<00:00, 427322.50it/s]\n
    Out[45]: #CBCount(A) 0010111114 1110037038 20174076 31174076 411037038

    Here is the list of groupby functions:

    class GroupBy(object):    \n    max = Max  # shortcuts to avoid having to type a long list of imports.\n    min = Min\n    sum = Sum\n    product = Product\n    first = First\n    last = Last\n    count = Count\n    count_unique = CountUnique\n    avg = Average\n    stdev = StandardDeviation\n    median = Median\n    mode = Mode\n
    In\u00a0[46]: Copied!
    t = Table({\n    'A':[1, 1, 2, 2, 3, 3] * 2,\n    'B':[1, 2, 3, 4, 5, 6] * 2,\n    'C':[6, 5, 4, 3, 2, 1] * 2,\n})\nt\n
    t = Table({ 'A':[1, 1, 2, 2, 3, 3] * 2, 'B':[1, 2, 3, 4, 5, 6] * 2, 'C':[6, 5, 4, 3, 2, 1] * 2, }) t Out[46]: #ABC 0116 1125 2234 3243 4352 5361 6116 7125 8234 92431035211361 In\u00a0[47]: Copied!
    t2 = t.pivot(rows=['C'], columns=['A'], functions=[('B', gb.sum), ('B', gb.count)], values_as_rows=False)\nt2\n
    t2 = t.pivot(rows=['C'], columns=['A'], functions=[('B', gb.sum), ('B', gb.count)], values_as_rows=False) t2
    pivot: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 14/14 [00:00<00:00, 3643.83it/s]\n
    Out[47]: #CSum(B,A=1)Count(B,A=1)Sum(B,A=2)Count(B,A=2)Sum(B,A=3)Count(B,A=3) 0622NoneNoneNoneNone 1542NoneNoneNoneNone 24NoneNone62NoneNone 33NoneNone82NoneNone 42NoneNoneNoneNone102 51NoneNoneNoneNone122 In\u00a0[48]: Copied!
    numbers = Table()\nnumbers.add_column('number', data=[      1,      2,       3,       4,   None])\nnumbers.add_column('colour', data=['black', 'blue', 'white', 'white', 'blue'])\n\nletters = Table()\nletters.add_column('letter', data=[  'a',     'b',      'c',     'd',   None])\nletters.add_column('color', data=['blue', 'white', 'orange', 'white', 'blue'])\n
    numbers = Table() numbers.add_column('number', data=[ 1, 2, 3, 4, None]) numbers.add_column('colour', data=['black', 'blue', 'white', 'white', 'blue']) letters = Table() letters.add_column('letter', data=[ 'a', 'b', 'c', 'd', None]) letters.add_column('color', data=['blue', 'white', 'orange', 'white', 'blue']) In\u00a0[49]: Copied!
    ## left join\n## SELECT number, letter FROM numbers LEFT JOIN letters ON numbers.colour == letters.color\nleft_join = numbers.left_join(letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter'])\nleft_join\n
    ## left join ## SELECT number, letter FROM numbers LEFT JOIN letters ON numbers.colour == letters.color left_join = numbers.left_join(letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter']) left_join
    join: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 2/2 [00:00<00:00, 1221.94it/s]\n
    Out[49]: #numberletter 01None 12a 22None 3Nonea 4NoneNone 53b 63d 74b 84d In\u00a0[50]: Copied!
    ## inner join\n## SELECT number, letter FROM numbers JOIN letters ON numbers.colour == letters.color\ninner_join = numbers.inner_join(letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter'])\ninner_join\n
    ## inner join ## SELECT number, letter FROM numbers JOIN letters ON numbers.colour == letters.color inner_join = numbers.inner_join(letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter']) inner_join
    join: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 2/2 [00:00<00:00, 1121.77it/s]\n
    Out[50]: #numberletter 02a 12None 2Nonea 3NoneNone 43b 53d 64b 74d In\u00a0[51]: Copied!
    # outer join\n## SELECT number, letter FROM numbers OUTER JOIN letters ON numbers.colour == letters.color\nouter_join = numbers.outer_join(letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter'])\nouter_join\n
    # outer join ## SELECT number, letter FROM numbers OUTER JOIN letters ON numbers.colour == letters.color outer_join = numbers.outer_join(letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter']) outer_join
    join: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 2/2 [00:00<00:00, 1585.15it/s]\n
    Out[51]: #numberletter 01None 12a 22None 3Nonea 4NoneNone 53b 63d 74b 84d 9Nonec

    Q: But ...I think there's a bug in the join... A: Venn diagrams do not explain joins.

    A Venn diagram is a widely-used diagram style that shows the logical relation between sets, popularised by John Venn in the 1880s. The diagrams are used to teach elementary set theory, and to illustrate simple set relationshipssource: en.wikipedia.org

    Joins operate over rows and when there are duplicate rows, these will be replicated in the output. Many beginners are surprised by this, because they didn't read the SQL standard.

    Q: So what do I do? A: If you want to get rid of duplicates using tablite, use the index functionality across all columns and pick the first row from each index. Here's the recipe that starts with plenty of duplicates:

    In\u00a0[52]: Copied!
    old_table = Table({\n'A':[1,1,1,2,2,2,3,3,3],\n'B':[1,1,4,2,2,5,3,3,6],\n})\nold_table\n
    old_table = Table({ 'A':[1,1,1,2,2,2,3,3,3], 'B':[1,1,4,2,2,5,3,3,6], }) old_table Out[52]: #AB 011 111 214 322 422 525 633 733 836 In\u00a0[53]: Copied!
    ## CREATE TABLE OF UNIQUE ENTRIES (a.k.a. DEDUPLICATE)\nnew_table = old_table.drop_duplicates()\nnew_table\n
    ## CREATE TABLE OF UNIQUE ENTRIES (a.k.a. DEDUPLICATE) new_table = old_table.drop_duplicates() new_table
    9it [00:00, 11329.15it/s]\njoin: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 2/2 [00:00<00:00, 1819.26it/s]\n
    Out[53]: #AB 011 114 222 325 433 536

    You can also use groupby; We'll get to that in a minute.

    Lookup is a special case of a search loop: Say for example you are planning a concert and want to make sure that your friends can make it home using public transport: You would have to find the first departure after the concert ends towards their home. A join would only give you a direct match on the time.

    Lookup allows you \"to iterate through a list of data and find the first match given a set of criteria.\"

    Here's an example:

    First we have our list of friends and their stops.

    In\u00a0[54]: Copied!
    friends = Table({\n\"name\":['Alice', 'Betty', 'Charlie', 'Dorethy', 'Edward', 'Fred'],\n\"stop\":['Downtown-1', 'Downtown-2', 'Hillside View', 'Hillside Crescent', 'Downtown-2', 'Chicago'],\n})\nfriends\n
    friends = Table({ \"name\":['Alice', 'Betty', 'Charlie', 'Dorethy', 'Edward', 'Fred'], \"stop\":['Downtown-1', 'Downtown-2', 'Hillside View', 'Hillside Crescent', 'Downtown-2', 'Chicago'], }) friends Out[54]: #namestop 0AliceDowntown-1 1BettyDowntown-2 2CharlieHillside View 3DorethyHillside Crescent 4EdwardDowntown-2 5FredChicago

    Next we need a list of bus routes and their time and stops. I don't have that, so I'm making one up:

    In\u00a0[55]: Copied!
    import random\nrandom.seed(11)\ntable_size = 40\n\ntimes = [DataTypes.time(random.randint(21, 23), random.randint(0, 59)) for i in range(table_size)]\nstops = ['Stadium', 'Hillside', 'Hillside View', 'Hillside Crescent', 'Downtown-1', 'Downtown-2',\n            'Central station'] * 2 + [f'Random Road-{i}' for i in range(table_size)]\nroute = [random.choice([1, 2, 3]) for i in stops]\n
    import random random.seed(11) table_size = 40 times = [DataTypes.time(random.randint(21, 23), random.randint(0, 59)) for i in range(table_size)] stops = ['Stadium', 'Hillside', 'Hillside View', 'Hillside Crescent', 'Downtown-1', 'Downtown-2', 'Central station'] * 2 + [f'Random Road-{i}' for i in range(table_size)] route = [random.choice([1, 2, 3]) for i in stops] In\u00a0[56]: Copied!
    bus_table = Table({\n\"time\":times,\n\"stop\":stops[:table_size],\n\"route\":route[:table_size],\n})\nbus_table.sort(mapping={'time': False})\n\nprint(\"Departures from Concert Hall towards ...\")\nbus_table[0:10]\n
    bus_table = Table({ \"time\":times, \"stop\":stops[:table_size], \"route\":route[:table_size], }) bus_table.sort(mapping={'time': False}) print(\"Departures from Concert Hall towards ...\") bus_table[0:10]
    creating sort index: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 1/1 [00:00<00:00, 1459.90it/s]\njoin: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 3/3 [00:00<00:00, 2421.65it/s]\n
    Departures from Concert Hall towards ...\n
    Out[56]: #timestoproute 021:02:00Random Road-62 121:05:00Hillside Crescent2 221:06:00Hillside1 321:25:00Random Road-241 421:29:00Random Road-161 521:32:00Random Road-211 621:33:00Random Road-121 721:36:00Random Road-233 821:38:00Central station2 921:38:00Random Road-82

    Let's say the concerts ends at 21:00 and it takes a 10 minutes to get to the bus-stop. Earliest departure must then be 21:10 - goodbye hugs included.

    In\u00a0[57]: Copied!
    lookup_1 = friends.lookup(bus_table, (DataTypes.time(21, 10), \"<=\", 'time'), ('stop', \"==\", 'stop'))\nlookup1_sorted = lookup_1.sorted(mapping={'time': False, 'name':False}, sort_mode='unix')\nlookup1_sorted\n
    lookup_1 = friends.lookup(bus_table, (DataTypes.time(21, 10), \"<=\", 'time'), ('stop', \"==\", 'stop')) lookup1_sorted = lookup_1.sorted(mapping={'time': False, 'name':False}, sort_mode='unix') lookup1_sorted
    100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 6/6 [00:00<00:00, 1513.92it/s]\njoin: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 3/3 [00:00<00:00, 2003.65it/s]\ncreating sort index: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 2/2 [00:00<00:00, 2589.88it/s]\njoin: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 5/5 [00:00<00:00, 2034.29it/s]\n
    Out[57]: #namestoptimestop_1route 0FredChicagoNoneNoneNone 1BettyDowntown-221:51:00Downtown-21 2EdwardDowntown-221:51:00Downtown-21 3CharlieHillside View22:19:00Hillside View2 4AliceDowntown-123:12:00Downtown-13 5DorethyHillside Crescent23:54:00Hillside Crescent1

    Lookup's ability to custom criteria is thereby far more versatile than SQL joins.

    But with great power comes great responsibility.

    In\u00a0[58]: Copied!
    materials = Table({\n    'bom_id': [1, 2, 3, 4, 5, 6, 7, 8, 9], \n    'partial_of': [1, 2, 3, 4, 5, 6, 7, 4, 6], \n    'sku': ['A', 'irrelevant', 'empty carton', 'pkd carton', 'empty pallet', 'pkd pallet', 'pkd irrelevant', 'ppkd carton', 'ppkd pallet'], \n    'material_id': [None, None, None, 3, None, 5, 3, 3, 5], \n    'quantity': [10, 20, 30, 40, 50, 60, 70, 80, 90]\n})\n    # 9 is a partially packed pallet of 6\n\n## multiple values.\nlooking_for = Table({\n    'bom_id': [3,4,6], \n    'moq': [1,2,3]\n    })\n
    materials = Table({ 'bom_id': [1, 2, 3, 4, 5, 6, 7, 8, 9], 'partial_of': [1, 2, 3, 4, 5, 6, 7, 4, 6], 'sku': ['A', 'irrelevant', 'empty carton', 'pkd carton', 'empty pallet', 'pkd pallet', 'pkd irrelevant', 'ppkd carton', 'ppkd pallet'], 'material_id': [None, None, None, 3, None, 5, 3, 3, 5], 'quantity': [10, 20, 30, 40, 50, 60, 70, 80, 90] }) # 9 is a partially packed pallet of 6 ## multiple values. looking_for = Table({ 'bom_id': [3,4,6], 'moq': [1,2,3] })

    Our goals is now to find the quantity from the materials table based on the items in the looking_for table.

    This requires two steps:

    1. lookup
    2. filter for all by dropping items that didn't match.
    In\u00a0[59]: Copied!
    ## step 1/2:\nproducts_lookup = materials.lookup(looking_for, (\"bom_id\", \"==\", \"bom_id\"), (\"partial_of\", \"==\", \"bom_id\"), all=False)   \nproducts_lookup\n
    ## step 1/2: products_lookup = materials.lookup(looking_for, (\"bom_id\", \"==\", \"bom_id\"), (\"partial_of\", \"==\", \"bom_id\"), all=False) products_lookup
    100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 9/9 [00:00<00:00, 3651.81it/s]\njoin: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 2/2 [00:00<00:00, 1625.38it/s]\n
    Out[59]: #bom_idpartial_ofskumaterial_idquantitybom_id_1moq 011ANone10NoneNone 122irrelevantNone20NoneNone 233empty cartonNone3031 344pkd carton34042 455empty palletNone50NoneNone 566pkd pallet56063 677pkd irrelevant370NoneNone 784ppkd carton38042 896ppkd pallet59063 In\u00a0[60]: Copied!
    ## step 2/2:\nproducts = products_lookup.all(bom_id_1=lambda x: x is not None)\nproducts\n
    ## step 2/2: products = products_lookup.all(bom_id_1=lambda x: x is not None) products Out[60]: #bom_idpartial_ofskumaterial_idquantitybom_id_1moq 033empty cartonNone3031 144pkd carton34042 266pkd pallet56063 384ppkd carton38042 496ppkd pallet59063

    The faster way to solve this problem is to use match!

    Here is the example:

    In\u00a0[61]: Copied!
    products_matched = materials.match(looking_for, (\"bom_id\", \"==\", \"bom_id\"), (\"partial_of\", \"==\", \"bom_id\"))\nproducts_matched\n
    products_matched = materials.match(looking_for, (\"bom_id\", \"==\", \"bom_id\"), (\"partial_of\", \"==\", \"bom_id\")) products_matched Out[61]: #bom_idpartial_ofskumaterial_idquantitybom_id_1moq 033empty cartonNone3031 144pkd carton34042 266pkd pallet56063 384ppkd carton38042 496ppkd pallet59063 In\u00a0[62]: Copied!
    assert products == products_matched\n
    assert products == products_matched In\u00a0[63]: Copied!
    from tablite import Table\nt = Table()  # create table\nt.add_columns('row','A','B','C')  # add columns\n
    from tablite import Table t = Table() # create table t.add_columns('row','A','B','C') # add columns

    The following examples are all valid and append the row (1,2,3) to the table.

    In\u00a0[64]: Copied!
    t.add_rows(1, 1, 2, 3)  # individual values\nt.add_rows([2, 1, 2, 3])  # list of values\nt.add_rows((3, 1, 2, 3))  # tuple of values\nt.add_rows(*(4, 1, 2, 3))  # unpacked tuple\nt.add_rows(row=5, A=1, B=2, C=3)   # keyword - args\nt.add_rows(**{'row': 6, 'A': 1, 'B': 2, 'C': 3})  # dict / json.\n
    t.add_rows(1, 1, 2, 3) # individual values t.add_rows([2, 1, 2, 3]) # list of values t.add_rows((3, 1, 2, 3)) # tuple of values t.add_rows(*(4, 1, 2, 3)) # unpacked tuple t.add_rows(row=5, A=1, B=2, C=3) # keyword - args t.add_rows(**{'row': 6, 'A': 1, 'B': 2, 'C': 3}) # dict / json.

    The following examples add two rows to the table

    In\u00a0[65]: Copied!
    t.add_rows((7, 1, 2, 3), (8, 4, 5, 6))  # two (or more) tuples.\nt.add_rows([9, 1, 2, 3], [10, 4, 5, 6])  # two or more lists\nt.add_rows({'row': 11, 'A': 1, 'B': 2, 'C': 3},\n          {'row': 12, 'A': 4, 'B': 5, 'C': 6})  # two (or more) dicts as args.\nt.add_rows(*[{'row': 13, 'A': 1, 'B': 2, 'C': 3},\n            {'row': 14, 'A': 1, 'B': 2, 'C': 3}])  # list of dicts.\n
    t.add_rows((7, 1, 2, 3), (8, 4, 5, 6)) # two (or more) tuples. t.add_rows([9, 1, 2, 3], [10, 4, 5, 6]) # two or more lists t.add_rows({'row': 11, 'A': 1, 'B': 2, 'C': 3}, {'row': 12, 'A': 4, 'B': 5, 'C': 6}) # two (or more) dicts as args. t.add_rows(*[{'row': 13, 'A': 1, 'B': 2, 'C': 3}, {'row': 14, 'A': 1, 'B': 2, 'C': 3}]) # list of dicts. In\u00a0[66]: Copied!
    t\n
    t Out[66]: #rowABC 01123 12123 23123 34123 45123 56123 67123 78456 89123 9104561011123111245612131231314123

    As the row incremented from 1 in the first of these examples, and finished with row: 14, you can now see the whole table above

    In\u00a0[67]: Copied!
    from pathlib import Path\npath = Path('tests/data/book1.csv')\ntx = Table.from_file(path)\ntx\n
    from pathlib import Path path = Path('tests/data/book1.csv') tx = Table.from_file(path) tx
    Collecting tasks: 'tests/data/book1.csv'\nDumping tasks: 'tests/data/book1.csv'\n
    importing file: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 1/1 [00:00<00:00, 444.08it/s]\n
    Out[67]: #abcdef 010.0606060610.0909090910.1212121210.1515151520.181818182 120.1212121210.2424242420.4848484850.969696971.939393939 230.2424242420.4848484850.969696971.9393939393.878787879 340.4848484850.969696971.9393939393.8787878797.757575758 450.969696971.9393939393.8787878797.75757575815.51515152 561.9393939393.8787878797.75757575815.5151515231.03030303 673.8787878797.75757575815.5151515231.0303030362.06060606.....................383916659267088.033318534175.066637068350.0133274000000.0266548000000.0394033318534175.066637068350.0133274000000.0266548000000.0533097000000.0404166637068350.0133274000000.0266548000000.0533097000000.01066190000000.04142133274000000.0266548000000.0533097000000.01066190000000.02132390000000.04243266548000000.0533097000000.01066190000000.02132390000000.04264770000000.04344533097000000.01066190000000.02132390000000.04264770000000.08529540000000.044451066190000000.02132390000000.04264770000000.08529540000000.017059100000000.0

    Note that you can also add start, limit and chunk_size to the file reader. Here's an example:

    In\u00a0[68]: Copied!
    path = Path('tests/data/book1.csv')\ntx2 = Table.from_file(path, start=2, limit=15)\ntx2\n
    path = Path('tests/data/book1.csv') tx2 = Table.from_file(path, start=2, limit=15) tx2
    Collecting tasks: 'tests/data/book1.csv'\n
    importing file: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 1/1 [00:00<00:00, 391.22it/s]
    Dumping tasks: 'tests/data/book1.csv'\n
    \n
    Out[68]: #abcdef 030.2424242420.4848484850.969696971.9393939393.878787879 140.4848484850.969696971.9393939393.8787878797.757575758 250.969696971.9393939393.8787878797.75757575815.51515152 361.9393939393.8787878797.75757575815.5151515231.03030303 473.8787878797.75757575815.5151515231.0303030362.06060606 587.75757575815.5151515231.0303030362.06060606124.1212121 6915.5151515231.0303030362.06060606124.1212121248.2424242 71031.0303030362.06060606124.1212121248.2424242496.4848485 81162.06060606124.1212121248.2424242496.4848485992.969697 912124.1212121248.2424242496.4848485992.9696971985.9393941013248.2424242496.4848485992.9696971985.9393943971.8787881114496.4848485992.9696971985.9393943971.8787887943.7575761215992.9696971985.9393943971.8787887943.75757615887.5151513161985.9393943971.8787887943.75757615887.5151531775.030314173971.8787887943.75757615887.5151531775.030363550.06061

    How good is the file_reader?

    I've included all formats in the test suite that are publicly available from the Alan Turing institute, dateutils) and Python's csv reader.

    What about MM-DD-YYYY formats? Some users from the US ask why the csv reader doesn't read the month-day-year format.

    The answer is simple: It's not an iso8601 format. The US month-day-year format is a locale that may be used a lot in the US, but it isn't an international standard.

    If you need to work with MM-DD-YYYY you will find that the file_reader will import the values as text (str). You can then reformat it with a custom function like:

    In\u00a0[69]: Copied!
    s = \"03-21-1998\"\nfrom datetime import date\nf = lambda s: date(int(s[-4:]), int(s[:2]), int(s[3:5]))\nf(s)\n
    s = \"03-21-1998\" from datetime import date f = lambda s: date(int(s[-4:]), int(s[:2]), int(s[3:5])) f(s) Out[69]:
    datetime.date(1998, 3, 21)
    In\u00a0[70]: Copied!
    from tablite.import_utils import file_readers\nfor k,v in file_readers.items():\n    print(k,v)\n
    from tablite.import_utils import file_readers for k,v in file_readers.items(): print(k,v)
    fods <function excel_reader at 0x7f36a3ef8c10>\njson <function excel_reader at 0x7f36a3ef8c10>\nhtml <function from_html at 0x7f36a3ef8b80>\nhdf5 <function from_hdf5 at 0x7f36a3ef8a60>\nsimple <function excel_reader at 0x7f36a3ef8c10>\nrst <function excel_reader at 0x7f36a3ef8c10>\nmediawiki <function excel_reader at 0x7f36a3ef8c10>\nxlsx <function excel_reader at 0x7f36a3ef8c10>\nxls <function excel_reader at 0x7f36a3ef8c10>\nxlsm <function excel_reader at 0x7f36a3ef8c10>\ncsv <function text_reader at 0x7f36a3ef9000>\ntsv <function text_reader at 0x7f36a3ef9000>\ntxt <function text_reader at 0x7f36a3ef9000>\nods <function ods_reader at 0x7f36a3ef8ca0>\n

    (2) define your new file reader

    In\u00a0[71]: Copied!
    def my_magic_reader(path, **kwargs):   # define your new file reader.\n    print(\"do magic with {path}\")\n    return\n
    def my_magic_reader(path, **kwargs): # define your new file reader. print(\"do magic with {path}\") return

    (3) add it to the list of readers.

    In\u00a0[72]: Copied!
    file_readers['my_special_format'] = my_magic_reader\n
    file_readers['my_special_format'] = my_magic_reader

    The file_readers are all in tablite.core so if you intend to extend the readers, I recommend that you start here.

    In\u00a0[73]: Copied!
    file = Path('example.xlsx')\ntx2.to_xlsx(file)\nos.remove(file)\n
    file = Path('example.xlsx') tx2.to_xlsx(file) os.remove(file)

    In\u00a0[74]: Copied!
    from tablite import Table\n\nt = Table({\n'a':[1, 2, 8, 3, 4, 6, 5, 7, 9],\n'b':[10, 100, 3, 4, 16, -1, 10, 10, 10],\n})\nt.sort(mapping={\"a\":False})\nt\n
    from tablite import Table t = Table({ 'a':[1, 2, 8, 3, 4, 6, 5, 7, 9], 'b':[10, 100, 3, 4, 16, -1, 10, 10, 10], }) t.sort(mapping={\"a\":False}) t
    creating sort index: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 1/1 [00:00<00:00, 1674.37it/s]\njoin: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 2/2 [00:00<00:00, 1701.89it/s]\n
    Out[74]: #ab 0110 12100 234 3416 4510 56-1 6710 783 8910 In\u00a0[75]: Copied!
    %pip install matplotlib -q\n
    %pip install matplotlib -q
    Note: you may need to restart the kernel to use updated packages.\n
    In\u00a0[76]: Copied!
    import matplotlib.pyplot as plt\nplt.plot(t['a'], t['b'])\nplt.ylabel('Hello Figure')\nplt.show()\n
    import matplotlib.pyplot as plt plt.plot(t['a'], t['b']) plt.ylabel('Hello Figure') plt.show() In\u00a0[77]: Copied!
    ## Let's monitor the memory and record the observations into a table!\nimport psutil, os, gc\nfrom time import process_time,sleep\nprocess = psutil.Process(os.getpid())\n\ndef mem_time():  # go and check taskmanagers memory usage.\n    return process.memory_info().rss, process_time()\n\ndigits = 1_000_000\n\nrecords = Table({'method':[], 'memory':[], 'time':[]})\n
    ## Let's monitor the memory and record the observations into a table! import psutil, os, gc from time import process_time,sleep process = psutil.Process(os.getpid()) def mem_time(): # go and check taskmanagers memory usage. return process.memory_info().rss, process_time() digits = 1_000_000 records = Table({'method':[], 'memory':[], 'time':[]})

    The row based format: 1 million 10-tuples

    In\u00a0[78]: Copied!
    before, start = mem_time()\nL = [tuple([11 for _ in range(10)]) for _ in range(digits)]\nafter, end = mem_time()  \ndel L\ngc.collect()\n\nrecords.add_rows(*('1e6 lists w. 10 integers', after - before, round(end-start,4)))\nrecords\n
    before, start = mem_time() L = [tuple([11 for _ in range(10)]) for _ in range(digits)] after, end = mem_time() del L gc.collect() records.add_rows(*('1e6 lists w. 10 integers', after - before, round(end-start,4))) records Out[78]: #methodmemorytime 01e6 lists w. 10 integers1190543360.5045

    The column based format: 10 columns with 1M values:

    In\u00a0[79]: Copied!
    before, start = mem_time()\nL = [[11 for i2 in range(digits)] for i1 in range(10)]\nafter,end = mem_time()\n\ndel L\ngc.collect()\nrecords.add_rows(('10 lists with 1e6 integers', after - before, round(end-start,4)))\n
    before, start = mem_time() L = [[11 for i2 in range(digits)] for i1 in range(10)] after,end = mem_time() del L gc.collect() records.add_rows(('10 lists with 1e6 integers', after - before, round(end-start,4)))

    We've thereby saved 50 Mb by avoiding the overhead from managing 1 million lists.

    Q: But why didn't I just use an array? It would have even lower memory footprint.

    A: First, array's don't handle None's and we get that frequently in dirty csv data.

    Second, Table needs even less memory.

    Let's try with an array:

    In\u00a0[80]: Copied!
    import array\n\nbefore, start = mem_time()\nL = [array.array('i', [11 for _ in range(digits)]) for _ in range(10)]\nafter,end = mem_time()\n\ndel L\ngc.collect()\nrecords.add_rows(('10 lists with 1e6 integers in arrays', after - before, round(end-start,4)))\nrecords\n
    import array before, start = mem_time() L = [array.array('i', [11 for _ in range(digits)]) for _ in range(10)] after,end = mem_time() del L gc.collect() records.add_rows(('10 lists with 1e6 integers in arrays', after - before, round(end-start,4))) records Out[80]: #methodmemorytime 01e6 lists w. 10 integers1190543360.5045 110 lists with 1e6 integers752762880.1906 210 lists with 1e6 integers in arrays398336000.3633

    Finally let's use a tablite.Table:

    In\u00a0[81]: Copied!
    before,start = mem_time()\nt = Table(columns={str(i1): [11 for i2 in range(digits)] for i1 in range(10)})\nafter,end = mem_time()\n\nrecords.add_rows(('Table with 10 columns with 1e6 integers', after - before, round(end-start,4)))\n\nbefore,start = mem_time()\nt2 = t.copy()\nafter,end = mem_time()\n\nrecords.add_rows(('2 Tables with 10 columns with 1e6 integers each', after - before, round(end-start,4)))\n\n## Let's show it, so we know nobody's cheating:\nt2\n
    before,start = mem_time() t = Table(columns={str(i1): [11 for i2 in range(digits)] for i1 in range(10)}) after,end = mem_time() records.add_rows(('Table with 10 columns with 1e6 integers', after - before, round(end-start,4))) before,start = mem_time() t2 = t.copy() after,end = mem_time() records.add_rows(('2 Tables with 10 columns with 1e6 integers each', after - before, round(end-start,4))) ## Let's show it, so we know nobody's cheating: t2 Out[81]: #0123456789 011111111111111111111 111111111111111111111 211111111111111111111 311111111111111111111 411111111111111111111 511111111111111111111 611111111111111111111................................. 999,99311111111111111111111 999,99411111111111111111111 999,99511111111111111111111 999,99611111111111111111111 999,99711111111111111111111 999,99811111111111111111111 999,99911111111111111111111 In\u00a0[82]: Copied!
    records\n
    records Out[82]: #methodmemorytime 01e6 lists w. 10 integers1190543360.5045 110 lists with 1e6 integers752762880.1906 210 lists with 1e6 integers in arrays398336000.3633 3Table with 10 columns with 1e6 integers01.9569 42 Tables with 10 columns with 1e6 integers each00.0001

    Conclusion: whilst the common worst case (1M lists with 10 integers) take up 118 Mb of RAM, Tablite's tables vanish in the noise of memory measurement.

    Pandas also permits the usage of namedtuples, which are unpacked upon entry.

    from collections import namedtuple\nPoint = namedtuple(\"Point\", \"x y\")\npoints = [Point(0, 0), Point(0, 3)]\npd.DataFrame(points)\n

    Doing that in tablite is a bit different. To unpack the named tuple, you should do so explicitly:

    t = Table({'x': [p.x for p in points], 'y': [p.y for p in points]})\n

    However should you want to keep the points as namedtuple, you can do so in tablite:

    t = Table()\nt['points'] = points\n

    Tablite will store a serialised version of the points, so your memory overhead will be close to zero.

    "},{"location":"tutorial/#tablite","title":"Tablite\u00b6","text":""},{"location":"tutorial/#introduction","title":"Introduction\u00b6","text":"

    Tablite fills the data-science space where incremental data processing based on:

    • Datasets are larger than memory.
    • You don't want to worry about datatypes.

    Tablite thereby competes with:

    • Pandas, but saves the memory overhead.
    • Numpy, but spares you from worrying about lower level data types
    • SQlite, by sheer speed.
    • Polars, by working beyond RAM.
    • Other libraries for data cleaning thanks to tablites powerful datatypes module.

    Install: pip install tablite

    Usage: >>> from tablite import Table

    Upgrade: pip install tablite --no-cache --upgrade

    "},{"location":"tutorial/#overview","title":"Overview\u00b6","text":"

    (Version 2023.6.0 and later. For older version see this)

    • Tablite handles all Python datatypes: str, float, bool, int, date, datetime, time, timedelta and None.
    • you can select:
      • all rows in a column as table['A']
      • rows across all columns as table[4:8]
      • or a slice as table['A', 'B', slice(4,8) ].
    • you to update with table['A'][2] = new value
    • you can store or send data using json, by:
      • dumping to json: json_str = table.to_json(), or
      • you can load it with Table.from_json(json_str).
    • you can iterate over rows using for row in Table.rows.
    • you can ask column_xyz in Table.colums ?
    • load from files with new_table = Table.from_file('this.csv') which has automatic datatype detection
    • perform inner, outer & left sql join between tables as simple as table_1.inner_join(table2, keys=['A', 'B'])
    • summarise using table.groupby( ... )
    • create pivot tables using groupby.pivot( ... )
    • perform multi-criteria lookup in tables using table1.lookup(table2, criteria=.....
    • and of course a large selection of tools in from tablite.tools import *
    "},{"location":"tutorial/#examples","title":"Examples\u00b6","text":"

    Here are some examples:

    "},{"location":"tutorial/#api-examples","title":"API Examples\u00b6","text":"

    In the following sections, example are given of the Tablite API's power features:

    • Iteration
    • Append
    • Sort
    • Filter
    • Index
    • Search All
    • Search Any
    • Lookup
    • Join inner, outer,
    • GroupBy
    • Pivot table
    "},{"location":"tutorial/#iteration","title":"ITERATION!\u00b6","text":"

    Iteration supports for loops and list comprehension at the speed of light:

    Just use [r for r in table.rows], or:

    for row in table.rows:\n    row ...

    Here's a more practical use case:

    (1) Imagine a table with columns a,b,c,d,e (all integers) like this:

    "},{"location":"tutorial/#create-index-indices","title":"Create Index / Indices\u00b6","text":"

    Index supports multi-key indexing using args such as: index = table.index('B','C').

    Here's an example:

    "},{"location":"tutorial/#append","title":"APPEND\u00b6","text":""},{"location":"tutorial/#save","title":"SAVE\u00b6","text":""},{"location":"tutorial/#filter","title":"FILTER!\u00b6","text":""},{"location":"tutorial/#any-all","title":"Any! All?\u00b6","text":"

    Any and All are cousins of the filter. They're there so you can use them in the same way as you'd use any and all in python - as boolean evaluators:

    "},{"location":"tutorial/#sort","title":"SORT!\u00b6","text":""},{"location":"tutorial/#groupby","title":"GROUPBY !\u00b6","text":""},{"location":"tutorial/#did-i-say-pivot-table-yes","title":"Did I say pivot table? Yes.\u00b6","text":"

    Pivot Table is included in the groupby functionality - so yes - you can pivot the groupby on any column that is used for grouping. Here's a simple example:

    "},{"location":"tutorial/#join","title":"JOIN!\u00b6","text":""},{"location":"tutorial/#lookup","title":"LOOKUP!\u00b6","text":""},{"location":"tutorial/#match","title":"Match\u00b6","text":"

    If you're looking to do a join where you afterwards remove the empty rows, match is the faster choice.

    Here is an example.

    Let's start with two tables:

    "},{"location":"tutorial/#are-there-other-ways-i-can-add-data","title":"Are there other ways I can add data?\u00b6","text":"

    Yes - but row based operations cause a lot of IO, so it'll work but be slower:

    "},{"location":"tutorial/#okay-great-how-do-i-load-data","title":"Okay, great. How do I load data?\u00b6","text":"

    Easy. Use file_reader. Here's an example:

    "},{"location":"tutorial/#sweet-what-formats-are-supported-can-i-add-my-own-file-reader","title":"Sweet. What formats are supported? Can I add my own file reader?\u00b6","text":"

    Yes! This is very good for special log files or custom json formats. Here's how you do it:

    (1) Go to all existing readers in the tablite.core and find the closest match.

    "},{"location":"tutorial/#very-nice-how-about-exporting-data","title":"Very nice. How about exporting data?\u00b6","text":"

    Just use .export

    "},{"location":"tutorial/#cool-does-it-play-well-with-plotting-packages","title":"Cool. Does it play well with plotting packages?\u00b6","text":"

    Yes. Here's an example you can copy and paste:

    "},{"location":"tutorial/#i-like-sql-can-tablite-understand-sql","title":"I like sql. Can tablite understand SQL?\u00b6","text":"

    Almost. You can use table.to_sql and tablite will return ANSI-92 compliant SQL.

    You can also create a table using Table.from_sql and tablite will consume ANSI-92 compliant SQL.

    "},{"location":"tutorial/#but-what-do-i-do-if-im-about-to-run-out-of-memory","title":"But what do I do if I'm about to run out of memory?\u00b6","text":"

    You wont. Every tablite table is backed by disk. The memory footprint of a table is only the metadata required to know the relationships between variable names and the datastructures.

    Let's do a comparison:

    "},{"location":"tutorial/#conclusions","title":"Conclusions\u00b6","text":"

    This concludes the mega-tutorial to tablite. There's nothing more to it. But oh boy it'll save a lot of time.

    Here's a summary of features:

    • Everything a list can do.
    • import csv*, fods, json, html, simple, rst, mediawiki, xlsx, xls, xlsm, csv, tsv, txt, ods using Table.from_file(...)
    • Iterate over rows or columns
    • Create multikey index, sort, use filter, any and all to select. Perform lookup across tables including using custom functions.
    • Perform multikey joins with other tables.
    • Perform groupby and reorganise data as a pivot table with max, min, sum, first, last, count, unique, average, standard deviation, median and mode.
    • Update tables with += which automatically sorts out the columns - even if they're not in perfect order.
    "},{"location":"tutorial/#faq","title":"FAQ\u00b6","text":"Question Answer I'm not in a notebook. Is there a nice way to view tables? Yes. table.show() prints the ascii version I'm looking for the equivalent to apply in pandas. Just use list comprehensions: table[column] = [f(x) for x in table[column] What about map? Just use the python function: mapping = map(f, table[column name]) Is there a where function? It's called any or all like in python: table.any(column_name > 0). I like sql and sqlite. Can I use sql? Yes. Call table.to_sql() returns ANSI-92 SQL compliant table definition.You can use this in any SQL compliant engine.

    | sometimes i need to clean up data with datetimes. Is there any tool to help with that? | Yes. Look at DataTypes.DataTypes.round(value, multiple) allows rounding of datetime.

    "},{"location":"tutorial/#coming-to-tablite-from-pandas","title":"Coming to Tablite from Pandas\u00b6","text":"

    If you're coming to Tablite from Pandas you will notice some differences.

    Here's the ultra short comparison to the documentation from Pandas called 10 minutes intro to pandas

    The tutorials provide the generic overview:

    • pandas tutorial
    • tablite tutorial

    Some key differences

    topic Tablite Viewing data Just use table.show() in print outs, or if you're in a jupyter notebook just use the variable name table Selection Slicing works both on columns and rows, and you can filter using any or all:table['A','B', 2:30:3].any(A=lambda x:x>3) to copy a table use: t2 = t.copy()This is a very fast deep copy, that has no memory overhead as tablites memory manager keeps track of the data. Missing data Tablite uses mixed column format for any format that isn't uniformTo get rid of rows with Nones and np.nans use any:table.drop_na(None, np.nan) Alternatively you can use replace: table.replace(None,5) following the syntax: table.replace_missing_values(sources, target) Operations Descriptive statistics are on a colum by column basis:table['a'].statistics() the pandas function df.apply doesn't exist in tablite. Use a list comprehension instead. For example: df.apply(np.cumsum) is just np.cumsum(t['A']) \"histogramming\" in tablite is per column: table['a'].histogram() string methods? Just use a list comprehensions: table['A', 'B'].any(A=lambda x: \"hello\" in x, B=lambda x: \"world\" in x) Merge Concatenation: Just use + or += as in t1 = t2 + t3 += t4. If the columns are out of order, tablite will sort the headers according to the order in the first table.If you're worried that the header mismatch use t1.stack(t2) Joins are ANSI92 compliant: t1.join(t2, <...args...>, join_type=...). Grouping Tablite supports multikey groupby using from tablite import Groupby as gb. table.groupby(keys, functions) Reshaping To reshape a table use transpose. to perform pivot table like operations, use: table.pivot(rows, columns, functions) subtotals aside tablite will give you everything Excels pivot table can do. Time series To convert time series use a list comprehension.t1['GMT'] = [timedelta(hours=1) + v for v in t1['date'] ] to generate a date range use:from Tablite import dateranget['date'] = date_range(start=2022/1/1, stop=2023/1/1, step=timedelta(days=1)) Categorical Pandas only seems to use this for sorting and grouping. Tablite table has .sort, .groupby and .pivot to achieve the same task. Plotting Import your favorite plotting package and feed it the values, such as:import matplotlib.pyplot as plt plt.plot(t['a'],t['b']) plt.showw() Import/Export Tablite supports the same import/export options as pandas.Tablite pegs the free memory before IO and can therefore process larger-than-RAM files. Tablite also guesses the datatypes for all ISOformats and uses multiprocessing and may therefore be faster. Should you want to inspect how guess works, use from tools import guess and try the function out. Gotchas None really. Should you come across something non-pythonic, then please post it on the issue list."},{"location":"reference/base/","title":"Base","text":""},{"location":"reference/base/#tablite.base","title":"tablite.base","text":""},{"location":"reference/base/#tablite.base-attributes","title":"Attributes","text":""},{"location":"reference/base/#tablite.base.log","title":"tablite.base.log = logging.getLogger(__name__) module-attribute","text":""},{"location":"reference/base/#tablite.base.file_registry","title":"tablite.base.file_registry = set() module-attribute","text":""},{"location":"reference/base/#tablite.base-classes","title":"Classes","text":""},{"location":"reference/base/#tablite.base.SimplePage","title":"tablite.base.SimplePage(id, path, len, py_dtype)","text":"

    Bases: object

    Source code in tablite/base.py
    def __init__(self, id, path, len, py_dtype) -> None:\n    self.path = Path(path) / \"pages\" / f\"{id}.npy\"\n    self.len = len\n    self.dtype = py_dtype\n\n    self._incr_refcount()\n
    "},{"location":"reference/base/#tablite.base.SimplePage-attributes","title":"Attributes","text":""},{"location":"reference/base/#tablite.base.SimplePage.ids","title":"tablite.base.SimplePage.ids = count(start=1) class-attribute instance-attribute","text":""},{"location":"reference/base/#tablite.base.SimplePage.refcounts","title":"tablite.base.SimplePage.refcounts = {} class-attribute instance-attribute","text":""},{"location":"reference/base/#tablite.base.SimplePage.autocleanup","title":"tablite.base.SimplePage.autocleanup = True class-attribute instance-attribute","text":""},{"location":"reference/base/#tablite.base.SimplePage.path","title":"tablite.base.SimplePage.path = Path(path) / 'pages' / f'{id}.npy' instance-attribute","text":""},{"location":"reference/base/#tablite.base.SimplePage.len","title":"tablite.base.SimplePage.len = len instance-attribute","text":""},{"location":"reference/base/#tablite.base.SimplePage.dtype","title":"tablite.base.SimplePage.dtype = py_dtype instance-attribute","text":""},{"location":"reference/base/#tablite.base.SimplePage-functions","title":"Functions","text":""},{"location":"reference/base/#tablite.base.SimplePage.__setstate__","title":"tablite.base.SimplePage.__setstate__(state)","text":"

    when an object is unpickled, say in a case of multi-processing, object.setstate(state) is called instead of init, this means we need to update page refcount as if constructor had been called

    Source code in tablite/base.py
    def __setstate__(self, state):\n    \"\"\"\n    when an object is unpickled, say in a case of multi-processing,\n    object.__setstate__(state) is called instead of __init__, this means\n    we need to update page refcount as if constructor had been called\n    \"\"\"\n    self.__dict__.update(state)\n\n    self._incr_refcount()\n
    "},{"location":"reference/base/#tablite.base.SimplePage.next_id","title":"tablite.base.SimplePage.next_id(path) classmethod","text":"Source code in tablite/base.py
    @classmethod\ndef next_id(cls, path):\n    path = Path(path)\n\n    while True:\n        _id = f\"{os.getpid()}-{next(cls.ids)}\"\n        _path = path / \"pages\" / f\"{_id}.npy\"\n\n        if not _path.exists():\n            break  # make sure we don't override existing pages if they are created outside of main thread\n\n    return _id\n
    "},{"location":"reference/base/#tablite.base.SimplePage.__len__","title":"tablite.base.SimplePage.__len__()","text":"Source code in tablite/base.py
    def __len__(self):\n    return self.len\n
    "},{"location":"reference/base/#tablite.base.SimplePage.__repr__","title":"tablite.base.SimplePage.__repr__() -> str","text":"Source code in tablite/base.py
    def __repr__(self) -> str:\n    try:\n        return f\"{self.__class__.__name__}({self.path}, {self.get()})\"\n    except FileNotFoundError as e:\n        return f\"{self.__class__.__name__}({self.path}, <{type(e).__name__}>)\"\n    except Exception as e:\n        return f\"{self.__class__.__name__}({self.path}, <{e}>)\"\n
    "},{"location":"reference/base/#tablite.base.SimplePage.__hash__","title":"tablite.base.SimplePage.__hash__() -> int","text":"Source code in tablite/base.py
    def __hash__(self) -> int:\n    return hash(self.path)\n
    "},{"location":"reference/base/#tablite.base.SimplePage.owns","title":"tablite.base.SimplePage.owns()","text":"Source code in tablite/base.py
    def owns(self):\n    parts = self.path.parts\n\n    return all((p in parts for p in Path(Config.pid).parts))\n
    "},{"location":"reference/base/#tablite.base.SimplePage.__del__","title":"tablite.base.SimplePage.__del__()","text":"

    When python's reference count for an object is 0, python uses it's garbage collector to remove the object and free the memory. As tablite tables have columns and columns have page and pages have data stored on disk, the space on disk must be freed up as well. This del override assures the cleanup of stored data.

    Source code in tablite/base.py
    def __del__(self):\n    \"\"\"When python's reference count for an object is 0, python uses\n    it's garbage collector to remove the object and free the memory.\n    As tablite tables have columns and columns have page and pages have\n    data stored on disk, the space on disk must be freed up as well.\n    This __del__ override assures the cleanup of stored data.\n    \"\"\"\n    if not self.owns():\n        return\n\n    refcount = self.refcounts[self.path] = max(\n        self.refcounts.get(self.path, 0) - 1, 0\n    )\n\n    if refcount > 0:\n        return\n\n    if self.autocleanup:\n        self.path.unlink(True)\n\n    del self.refcounts[self.path]\n
    "},{"location":"reference/base/#tablite.base.SimplePage.get","title":"tablite.base.SimplePage.get()","text":"

    loads stored data

    RETURNS DESCRIPTION

    np.ndarray: stored data.

    Source code in tablite/base.py
    def get(self):\n    \"\"\"loads stored data\n\n    Returns:\n        np.ndarray: stored data.\n    \"\"\"\n    array = load_numpy(self.path)\n    return MetaArray(array, array.dtype, py_dtype=self.dtype)\n
    "},{"location":"reference/base/#tablite.base.Page","title":"tablite.base.Page(path, array)","text":"

    Bases: SimplePage

    PARAMETER DESCRIPTION path

    working directory.

    TYPE: Path

    array

    data

    TYPE: array

    Source code in tablite/base.py
    def __init__(self, path, array) -> None:\n    \"\"\"\n    Args:\n        path (Path): working directory.\n        array (np.array): data\n    \"\"\"\n    _id = self.next_id(path)\n\n    type_check(array, np.ndarray)\n\n    if Config.DISK_LIMIT <= 0:\n        pass\n    else:\n        _, _, free = shutil.disk_usage(path)\n        if free - array.nbytes < Config.DISK_LIMIT:\n            msg = \"\\n\".join(\n                [\n                    f\"Disk limit reached: Config.DISK_LIMIT = {Config.DISK_LIMIT:,} bytes.\",\n                    f\"array requires {array.nbytes:,} bytes, but only {free:,} bytes are free.\",\n                    \"To disable this check, use:\",\n                    \">>> from tablite.config import Config\",\n                    \">>> Config.DISK_LIMIT = 0\",\n                    \"To free space, clean up Config.workdir:\",\n                    f\"{Config.workdir}\",\n                ]\n            )\n            raise OSError(msg)\n\n    _len = len(array)\n    # type_check(array, MetaArray)\n    if not hasattr(array, \"metadata\"):\n        raise ValueError\n    _dtype = array.metadata[\"py_dtype\"]\n\n    super().__init__(_id, path, _len, _dtype)\n\n    np.save(self.path, array, allow_pickle=True, fix_imports=False)\n    log.debug(f\"Page saved: {self.path}\")\n
    "},{"location":"reference/base/#tablite.base.Page-attributes","title":"Attributes","text":""},{"location":"reference/base/#tablite.base.Page.ids","title":"tablite.base.Page.ids = count(start=1) class-attribute instance-attribute","text":""},{"location":"reference/base/#tablite.base.Page.refcounts","title":"tablite.base.Page.refcounts = {} class-attribute instance-attribute","text":""},{"location":"reference/base/#tablite.base.Page.autocleanup","title":"tablite.base.Page.autocleanup = True class-attribute instance-attribute","text":""},{"location":"reference/base/#tablite.base.Page.path","title":"tablite.base.Page.path = Path(path) / 'pages' / f'{id}.npy' instance-attribute","text":""},{"location":"reference/base/#tablite.base.Page.len","title":"tablite.base.Page.len = len instance-attribute","text":""},{"location":"reference/base/#tablite.base.Page.dtype","title":"tablite.base.Page.dtype = py_dtype instance-attribute","text":""},{"location":"reference/base/#tablite.base.Page-functions","title":"Functions","text":""},{"location":"reference/base/#tablite.base.Page.__setstate__","title":"tablite.base.Page.__setstate__(state)","text":"

    when an object is unpickled, say in a case of multi-processing, object.setstate(state) is called instead of init, this means we need to update page refcount as if constructor had been called

    Source code in tablite/base.py
    def __setstate__(self, state):\n    \"\"\"\n    when an object is unpickled, say in a case of multi-processing,\n    object.__setstate__(state) is called instead of __init__, this means\n    we need to update page refcount as if constructor had been called\n    \"\"\"\n    self.__dict__.update(state)\n\n    self._incr_refcount()\n
    "},{"location":"reference/base/#tablite.base.Page.next_id","title":"tablite.base.Page.next_id(path) classmethod","text":"Source code in tablite/base.py
    @classmethod\ndef next_id(cls, path):\n    path = Path(path)\n\n    while True:\n        _id = f\"{os.getpid()}-{next(cls.ids)}\"\n        _path = path / \"pages\" / f\"{_id}.npy\"\n\n        if not _path.exists():\n            break  # make sure we don't override existing pages if they are created outside of main thread\n\n    return _id\n
    "},{"location":"reference/base/#tablite.base.Page.__len__","title":"tablite.base.Page.__len__()","text":"Source code in tablite/base.py
    def __len__(self):\n    return self.len\n
    "},{"location":"reference/base/#tablite.base.Page.__repr__","title":"tablite.base.Page.__repr__() -> str","text":"Source code in tablite/base.py
    def __repr__(self) -> str:\n    try:\n        return f\"{self.__class__.__name__}({self.path}, {self.get()})\"\n    except FileNotFoundError as e:\n        return f\"{self.__class__.__name__}({self.path}, <{type(e).__name__}>)\"\n    except Exception as e:\n        return f\"{self.__class__.__name__}({self.path}, <{e}>)\"\n
    "},{"location":"reference/base/#tablite.base.Page.__hash__","title":"tablite.base.Page.__hash__() -> int","text":"Source code in tablite/base.py
    def __hash__(self) -> int:\n    return hash(self.path)\n
    "},{"location":"reference/base/#tablite.base.Page.owns","title":"tablite.base.Page.owns()","text":"Source code in tablite/base.py
    def owns(self):\n    parts = self.path.parts\n\n    return all((p in parts for p in Path(Config.pid).parts))\n
    "},{"location":"reference/base/#tablite.base.Page.__del__","title":"tablite.base.Page.__del__()","text":"

    When python's reference count for an object is 0, python uses it's garbage collector to remove the object and free the memory. As tablite tables have columns and columns have page and pages have data stored on disk, the space on disk must be freed up as well. This del override assures the cleanup of stored data.

    Source code in tablite/base.py
    def __del__(self):\n    \"\"\"When python's reference count for an object is 0, python uses\n    it's garbage collector to remove the object and free the memory.\n    As tablite tables have columns and columns have page and pages have\n    data stored on disk, the space on disk must be freed up as well.\n    This __del__ override assures the cleanup of stored data.\n    \"\"\"\n    if not self.owns():\n        return\n\n    refcount = self.refcounts[self.path] = max(\n        self.refcounts.get(self.path, 0) - 1, 0\n    )\n\n    if refcount > 0:\n        return\n\n    if self.autocleanup:\n        self.path.unlink(True)\n\n    del self.refcounts[self.path]\n
    "},{"location":"reference/base/#tablite.base.Page.get","title":"tablite.base.Page.get()","text":"

    loads stored data

    RETURNS DESCRIPTION

    np.ndarray: stored data.

    Source code in tablite/base.py
    def get(self):\n    \"\"\"loads stored data\n\n    Returns:\n        np.ndarray: stored data.\n    \"\"\"\n    array = load_numpy(self.path)\n    return MetaArray(array, array.dtype, py_dtype=self.dtype)\n
    "},{"location":"reference/base/#tablite.base.Column","title":"tablite.base.Column(path, value=None)","text":"

    Bases: object

    Create Column

    PARAMETER DESCRIPTION path

    path of table.yml (defaults: Config.pid_dir)

    TYPE: Path

    value

    Data to store. Defaults to None.

    TYPE: Iterable DEFAULT: None

    Source code in tablite/base.py
    def __init__(self, path, value=None) -> None:\n    \"\"\"Create Column\n\n    Args:\n        path (Path): path of table.yml (defaults: Config.pid_dir)\n        value (Iterable, optional): Data to store. Defaults to None.\n    \"\"\"\n    self.path = path\n    self.pages = []  # keeps pointers to instances of Page\n    if value is not None:\n        self.extend(value)\n
    "},{"location":"reference/base/#tablite.base.Column-attributes","title":"Attributes","text":""},{"location":"reference/base/#tablite.base.Column.path","title":"tablite.base.Column.path = path instance-attribute","text":""},{"location":"reference/base/#tablite.base.Column.pages","title":"tablite.base.Column.pages = [] instance-attribute","text":""},{"location":"reference/base/#tablite.base.Column-functions","title":"Functions","text":""},{"location":"reference/base/#tablite.base.Column.__len__","title":"tablite.base.Column.__len__()","text":"Source code in tablite/base.py
    def __len__(self):\n    return sum(len(p) for p in self.pages)\n
    "},{"location":"reference/base/#tablite.base.Column.__repr__","title":"tablite.base.Column.__repr__()","text":"Source code in tablite/base.py
    def __repr__(self):\n    return f\"{self.__class__.__name__}({self.path}, {self[:]})\"\n
    "},{"location":"reference/base/#tablite.base.Column.repaginate","title":"tablite.base.Column.repaginate()","text":"

    resizes pages to Config.PAGE_SIZE

    Source code in tablite/base.py
    def repaginate(self):\n    \"\"\"resizes pages to Config.PAGE_SIZE\"\"\"\n    from tablite.nimlite import repaginate as _repaginate\n\n    _repaginate(self)\n
    "},{"location":"reference/base/#tablite.base.Column.extend","title":"tablite.base.Column.extend(value)","text":"

    extends the column.

    PARAMETER DESCRIPTION value

    data

    TYPE: ndarray

    Source code in tablite/base.py
    def extend(self, value):  # USER FUNCTION.\n    \"\"\"extends the column.\n\n    Args:\n        value (np.ndarray): data\n    \"\"\"\n    if isinstance(value, Column):\n        self.pages.extend(value.pages[:])\n        return\n    elif isinstance(value, np.ndarray):\n        pass\n    elif isinstance(value, (list, tuple)):\n        value = list_to_np_array(value)\n    else:\n        raise TypeError(f\"Cannot extend Column with {type(value)}\")\n    type_check(value, np.ndarray)\n    for array in self._paginate(value):\n        self.pages.append(Page(path=self.path, array=array))\n
    "},{"location":"reference/base/#tablite.base.Column.clear","title":"tablite.base.Column.clear()","text":"

    clears the column. Like list().clear()

    Source code in tablite/base.py
    def clear(self):\n    \"\"\"\n    clears the column. Like list().clear()\n    \"\"\"\n    self.pages.clear()\n
    "},{"location":"reference/base/#tablite.base.Column.getpages","title":"tablite.base.Column.getpages(item)","text":"

    public non-user function to identify any pages + slices of data to be retrieved given a slice (item)

    PARAMETER DESCRIPTION item

    target slice of data

    TYPE: (int, slice)

    RETURNS DESCRIPTION

    list of pages/np.ndarrays.

    Example: [Page(1), Page(2), np.ndarray([4,5,6], int64)] This helps, for example when creating a copy, as the copy can reference the pages 1 and 2 and only need to store the np.ndarray that is unique to it.

    Source code in tablite/base.py
    def getpages(self, item):\n    \"\"\"public non-user function to identify any pages + slices\n    of data to be retrieved given a slice (item)\n\n    Args:\n        item (int,slice): target slice of data\n\n    Returns:\n        list of pages/np.ndarrays.\n\n    Example: [Page(1), Page(2), np.ndarray([4,5,6], int64)]\n    This helps, for example when creating a copy, as the copy\n    can reference the pages 1 and 2 and only need to store\n    the np.ndarray that is unique to it.\n    \"\"\"\n    # internal function\n    if isinstance(item, int):\n        if item < 0:\n            item = len(self) + item\n        item = slice(item, item + 1, 1)\n\n    type_check(item, slice)\n    is_reversed = False if (item.step is None or item.step > 0) else True\n\n    length = len(self)\n    scan_item = slice(*item.indices(length))\n    range_item = range(*item.indices(length))\n\n    pages = []\n    start, end = 0, 0\n    for page in self.pages:\n        start, end = end, end + page.len\n        if is_reversed:\n            if start > scan_item.start:\n                break\n            if end < scan_item.stop:\n                continue\n        else:\n            if start > scan_item.stop:\n                break\n            if end < scan_item.start:\n                continue\n        ro = intercept(range(start, end), range_item)\n        if len(ro) == 0:\n            continue\n        elif len(ro) == page.len:  # share the whole immutable page\n            pages.append(page)\n        else:  # fetch the slice and filter it.\n            search_slice = slice(ro.start - start, ro.stop - start, ro.step)\n            np_arr = load_numpy(page.path)\n            match = np_arr[search_slice]\n            pages.append(match)\n\n    if is_reversed:\n        pages.reverse()\n        for ix, page in enumerate(pages):\n            if isinstance(page, SimplePage):\n                data = page.get()\n                pages[ix] = np.flip(data)\n            else:\n                pages[ix] = np.flip(page)\n\n    return pages\n
    "},{"location":"reference/base/#tablite.base.Column.iter_by_page","title":"tablite.base.Column.iter_by_page()","text":"

    iterates over the column, page by page. This method minimizes the number of reads.

    RETURNS DESCRIPTION

    generator of tuple: start: int end: int data: np.ndarray

    Source code in tablite/base.py
    def iter_by_page(self):\n    \"\"\"iterates over the column, page by page.\n    This method minimizes the number of reads.\n\n    Returns:\n        generator of tuple:\n            start: int\n            end: int\n            data: np.ndarray\n    \"\"\"\n    start, end = 0, 0\n    for page in self.pages:\n        start, end = end, end + page.len\n        yield start, end, page\n
    "},{"location":"reference/base/#tablite.base.Column.__getitem__","title":"tablite.base.Column.__getitem__(item)","text":"

    gets numpy array.

    PARAMETER DESCRIPTION item

    slice of column

    TYPE: int OR slice

    RETURNS DESCRIPTION

    np.ndarray: results as numpy array.

    Remember:

    >>> R = np.array([0,1,2,3,4,5])\n>>> R[3]\n3\n>>> R[3:4]\narray([3])\n
    Source code in tablite/base.py
    def __getitem__(self, item):  # USER FUNCTION.\n    \"\"\"gets numpy array.\n\n    Args:\n        item (int OR slice): slice of column\n\n    Returns:\n        np.ndarray: results as numpy array.\n\n    Remember:\n    ```\n    >>> R = np.array([0,1,2,3,4,5])\n    >>> R[3]\n    3\n    >>> R[3:4]\n    array([3])\n    ```\n    \"\"\"\n    result = []\n    for element in self.getpages(item):\n        if isinstance(element, SimplePage):\n            result.append(element.get())\n        else:\n            result.append(element)\n\n    if result:\n        arr = np_type_unify(result)\n    else:\n        arr = np.array([])\n\n    if isinstance(item, int):\n        if len(arr) == 0:\n            raise IndexError(\n                f\"index {item} is out of bounds for axis 0 with size {len(self)}\"\n            )\n        return numpy_to_python(arr[0])\n    else:\n        return arr\n
    "},{"location":"reference/base/#tablite.base.Column.__setitem__","title":"tablite.base.Column.__setitem__(key, value)","text":"

    sets values.

    PARAMETER DESCRIPTION key

    selector

    TYPE: (int, slice)

    value

    values to insert

    TYPE: any

    RAISES DESCRIPTION KeyError

    Following normal slicing rules

    Source code in tablite/base.py
    def __setitem__(self, key, value):  # USER FUNCTION.\n    \"\"\"sets values.\n\n    Args:\n        key (int,slice): selector\n        value (any): values to insert\n\n    Raises:\n        KeyError: Following normal slicing rules\n    \"\"\"\n    if isinstance(key, int):\n        self._setitem_integer_key(key, value)\n\n    elif isinstance(key, slice):\n        if not isinstance(value, np.ndarray):\n            value = list_to_np_array(value)\n        type_check(value, np.ndarray)\n\n        if key.start is None and key.stop is None and key.step in (None, 1):\n            self._setitem_replace_all(key, value)\n        elif key.start is not None and key.stop is None and key.step in (None, 1):\n            self._setitem_extend(key, value)\n        elif key.stop is not None and key.start is None and key.step in (None, 1):\n            self._setitem_prextend(key, value)\n        elif (\n            key.step in (None, 1) and key.start is not None and key.stop is not None\n        ):\n            self._setitem_insert(key, value)\n        elif key.step not in (None, 1):\n            self._setitem_update(key, value)\n        else:\n            raise KeyError(f\"bad key: {key}\")\n    else:\n        raise KeyError(f\"bad key: {key}\")\n
    "},{"location":"reference/base/#tablite.base.Column.__delitem__","title":"tablite.base.Column.__delitem__(key)","text":"

    deletes items selected by key

    PARAMETER DESCRIPTION key

    selector

    TYPE: (int, slice)

    RAISES DESCRIPTION KeyError

    following normal slicing rules.

    Source code in tablite/base.py
    def __delitem__(self, key):  # USER FUNCTION\n    \"\"\"deletes items selected by key\n\n    Args:\n        key (int,slice): selector\n\n    Raises:\n        KeyError: following normal slicing rules.\n    \"\"\"\n    if isinstance(key, int):\n        self._del_by_int(key)\n    elif isinstance(key, slice):\n        self._del_by_slice(key)\n    else:\n        raise KeyError(f\"bad key: {key}\")\n
    "},{"location":"reference/base/#tablite.base.Column.get_by_indices","title":"tablite.base.Column.get_by_indices(indices: Union[List[int], np.ndarray]) -> np.ndarray","text":"

    retrieves values from column given a set of indices.

    PARAMETER DESCRIPTION indices

    targets

    TYPE: array

    This method uses np.take, is faster than iterating over rows. Examples:

    >>> indices = np.array(list(range(3,700_700, 426)))\n>>> arr = np.array(list(range(2_000_000)))\nPythonic:\n>>> [v for i,v in enumerate(arr) if i in indices]\nNumpyionic:\n>>> np.take(arr, indices)\n
    Source code in tablite/base.py
    def get_by_indices(self, indices: Union[List[int], np.ndarray]) -> np.ndarray:\n    \"\"\"retrieves values from column given a set of indices.\n\n    Args:\n        indices (np.array): targets\n\n    This method uses np.take, is faster than iterating over rows.\n    Examples:\n    ```\n    >>> indices = np.array(list(range(3,700_700, 426)))\n    >>> arr = np.array(list(range(2_000_000)))\n    Pythonic:\n    >>> [v for i,v in enumerate(arr) if i in indices]\n    Numpyionic:\n    >>> np.take(arr, indices)\n    ```\n    \"\"\"\n    type_check(indices, np.ndarray)\n\n    dtypes = set()\n    values = np.empty(\n        indices.shape, dtype=object\n    )  # placeholder for the indexed values.\n\n    for start, end, page in self.iter_by_page():\n        range_match = np.asarray(((indices >= start) & (indices < end)) | (indices == -1)).nonzero()[0]\n        if len(range_match):\n            # only fetch the data if there's a range match!\n            data = page.get() \n            sub_index = np.take(indices, range_match)\n            # sub_index2 otherwise will raise index error where len(data) > (-1 - start)\n            # so the clause below is required:\n            if len(data) > (-1 - start):\n                sub_index = np.where(sub_index == -1, -1, sub_index - start)\n            arr = np.take(data, sub_index)\n            dtypes.add(arr.dtype)\n            np.put(values, range_match, arr)\n\n    if len(dtypes) == 1:  # simplify the datatype\n        dtype = next(iter(dtypes))\n        values = np.array(values, dtype=dtype)\n    return values\n
    "},{"location":"reference/base/#tablite.base.Column.__iter__","title":"tablite.base.Column.__iter__()","text":"Source code in tablite/base.py
    def __iter__(self):  # USER FUNCTION.\n    for page in self.pages:\n        data = page.get()\n        for value in data:\n            yield value\n
    "},{"location":"reference/base/#tablite.base.Column.__eq__","title":"tablite.base.Column.__eq__(other)","text":"

    compares two columns. Like list1 == list2

    Source code in tablite/base.py
    def __eq__(self, other):  # USER FUNCTION.\n    \"\"\"\n    compares two columns. Like `list1 == list2`\n    \"\"\"\n    if len(self) != len(other):  # quick cheap check.\n        return False\n\n    if isinstance(other, (list, tuple)):\n        return all(a == b for a, b in zip(self[:], other))\n\n    elif isinstance(other, Column):\n        if self.pages == other.pages:  # special case.\n            return True\n\n        # are the pages of same size?\n        if len(self.pages) == len(other.pages):\n            if [p.len for p in self.pages] == [p.len for p in other.pages]:\n                for a, b in zip(self.pages, other.pages):\n                    if not (a.get() == b.get()).all():\n                        return False\n                return True\n        # to bad. Element comparison it is then:\n        for a, b in zip(iter(self), iter(other)):\n            if a != b:\n                return False\n        return True\n\n    elif isinstance(other, np.ndarray):\n        start, end = 0, 0\n        for p in self.pages:\n            start, end = end, end + p.len\n            if not (p.get() == other[start:end]).all():\n                return False\n        return True\n    else:\n        raise TypeError(f\"Cannot compare {self.__class__} with {type(other)}\")\n
    "},{"location":"reference/base/#tablite.base.Column.__ne__","title":"tablite.base.Column.__ne__(other)","text":"

    compares two columns. Like list1 != list2

    Source code in tablite/base.py
    def __ne__(self, other):  # USER FUNCTION\n    \"\"\"\n    compares two columns. Like `list1 != list2`\n    \"\"\"\n    if len(self) != len(other):  # quick cheap check.\n        return True\n\n    if isinstance(other, (list, tuple)):\n        return any(a != b for a, b in zip(self[:], other))\n\n    elif isinstance(other, Column):\n        if self.pages == other.pages:  # special case.\n            return False\n\n        # are the pages of same size?\n        if len(self.pages) == len(other.pages):\n            if [p.len for p in self.pages] == [p.len for p in other.pages]:\n                for a, b in zip(self.pages, other.pages):\n                    if not (a.get() == b.get()).all():\n                        return True\n                return False\n        # to bad. Element comparison it is then:\n        for a, b in zip(iter(self), iter(other)):\n            if a != b:\n                return True\n        return False\n\n    elif isinstance(other, np.ndarray):\n        start, end = 0, 0\n        for p in self.pages:\n            start, end = end, end + p.len\n            if (p.get() != other[start:end]).any():\n                return True\n        return False\n    else:\n        raise TypeError(f\"Cannot compare {self.__class__} with {type(other)}\")\n
    "},{"location":"reference/base/#tablite.base.Column.copy","title":"tablite.base.Column.copy()","text":"

    returns deep=copy of Column

    RETURNS DESCRIPTION

    Column

    Source code in tablite/base.py
    def copy(self):\n    \"\"\"returns deep=copy of Column\n\n    Returns:\n        Column\n    \"\"\"\n    cp = Column(path=self.path)\n    cp.pages = self.pages[:]\n    return cp\n
    "},{"location":"reference/base/#tablite.base.Column.__copy__","title":"tablite.base.Column.__copy__()","text":"

    see copy

    Source code in tablite/base.py
    def __copy__(self):\n    \"\"\"see copy\"\"\"\n    return self.copy()\n
    "},{"location":"reference/base/#tablite.base.Column.__imul__","title":"tablite.base.Column.__imul__(other)","text":"

    Repeats instance of column N times. Like list() * N

    Example:

    >>> one = Column(data=[1,2])\n>>> one *= 5\n>>> one\n[1,2, 1,2, 1,2, 1,2, 1,2]\n
    Source code in tablite/base.py
    def __imul__(self, other):\n    \"\"\"\n    Repeats instance of column N times. Like list() * N\n\n    Example:\n    ```\n    >>> one = Column(data=[1,2])\n    >>> one *= 5\n    >>> one\n    [1,2, 1,2, 1,2, 1,2, 1,2]\n    ```\n    \"\"\"\n    if not (isinstance(other, int) and other > 0):\n        raise TypeError(\n            f\"a column can be repeated an integer number of times, not {type(other)} number of times\"\n        )\n    self.pages = self.pages[:] * other\n    return self\n
    "},{"location":"reference/base/#tablite.base.Column.__mul__","title":"tablite.base.Column.__mul__(other)","text":"

    Repeats instance of column N times. Like list() * N

    Example:

    >>> one = Column(data=[1,2])\n>>> two = one * 5\n>>> two\n[1,2, 1,2, 1,2, 1,2, 1,2]\n
    Source code in tablite/base.py
    def __mul__(self, other):\n    \"\"\"\n    Repeats instance of column N times. Like list() * N\n\n    Example:\n    ```\n    >>> one = Column(data=[1,2])\n    >>> two = one * 5\n    >>> two\n    [1,2, 1,2, 1,2, 1,2, 1,2]\n    ```\n    \"\"\"\n    if not isinstance(other, int):\n        raise TypeError(\n            f\"a column can be repeated an integer number of times, not {type(other)} number of times\"\n        )\n    cp = self.copy()\n    cp *= other\n    return cp\n
    "},{"location":"reference/base/#tablite.base.Column.__iadd__","title":"tablite.base.Column.__iadd__(other)","text":"Source code in tablite/base.py
    def __iadd__(self, other):\n    if isinstance(other, (list, tuple)):\n        other = list_to_np_array(other)\n        self.extend(other)\n    elif isinstance(other, Column):\n        self.pages.extend(other.pages[:])\n    else:\n        raise TypeError(f\"{type(other)} not supported.\")\n    return self\n
    "},{"location":"reference/base/#tablite.base.Column.__contains__","title":"tablite.base.Column.__contains__(item)","text":"

    determines if item is in the Column. Similar to 'x' in ['a','b','c'] returns boolean

    PARAMETER DESCRIPTION item

    value to search for

    TYPE: any

    RETURNS DESCRIPTION bool

    True if item exists in column.

    Source code in tablite/base.py
    def __contains__(self, item):\n    \"\"\"determines if item is in the Column.\n    Similar to `'x' in ['a','b','c']`\n    returns boolean\n\n    Args:\n        item (any): value to search for\n\n    Returns:\n        bool: True if item exists in column.\n    \"\"\"\n    for page in set(self.pages):\n        if item in page.get():  # x in np.ndarray([...]) uses np.any(arr, value)\n            return True\n    return False\n
    "},{"location":"reference/base/#tablite.base.Column.remove_all","title":"tablite.base.Column.remove_all(*values)","text":"

    removes all values of values

    Source code in tablite/base.py
    def remove_all(self, *values):\n    \"\"\"\n    removes all values of `values`\n    \"\"\"\n    type_check(values, tuple)\n    if isinstance(values[0], tuple):\n        values = values[0]\n    to_remove = list_to_np_array(values)\n    for index, page in enumerate(self.pages):\n        data = page.get()\n        bitmask = np.isin(data, to_remove)  # identify elements to remove.\n        if bitmask.any():\n            bitmask = np.invert(bitmask)  # turn bitmask around to keep.\n            new_data = np.compress(bitmask, data)\n            new_page = Page(self.path, new_data)\n            self.pages[index] = new_page\n
    "},{"location":"reference/base/#tablite.base.Column.replace","title":"tablite.base.Column.replace(mapping)","text":"

    replaces values using a mapping.

    PARAMETER DESCRIPTION mapping

    {value to replace: new value, ...}

    TYPE: dict

    Example:

    >>> t = Table(columns={'A': [1,2,3,4]})\n>>> t['A'].replace({2:20,4:40})\n>>> t[:]\nnp.ndarray([1,20,3,40])\n
    Source code in tablite/base.py
    def replace(self, mapping):\n    \"\"\"\n    replaces values using a mapping.\n\n    Args:\n        mapping (dict): {value to replace: new value, ...}\n\n    Example:\n    ```\n    >>> t = Table(columns={'A': [1,2,3,4]})\n    >>> t['A'].replace({2:20,4:40})\n    >>> t[:]\n    np.ndarray([1,20,3,40])\n    ```\n    \"\"\"\n    type_check(mapping, dict)\n    to_replace = np.array(list(mapping.keys()))\n    for index, page in enumerate(self.pages):\n        data = page.get()\n        bitmask = np.isin(data, to_replace)  # identify elements to replace.\n        if bitmask.any():\n            warray = np.compress(bitmask, data)\n            py_dtype = page.dtype\n            for ix, v in enumerate(warray):\n                old_py_val = numpy_to_python(v)\n                new_py_val = mapping[old_py_val]\n                old_dt = type(old_py_val)\n                new_dt = type(new_py_val)\n\n                warray[ix] = new_py_val\n\n                py_dtype[new_dt] = py_dtype.get(new_dt, 0) + 1\n                py_dtype[old_dt] = py_dtype.get(old_dt, 0) - 1\n\n                if py_dtype[old_dt] <= 0:\n                    del py_dtype[old_dt]\n\n            data[bitmask] = warray\n            self.pages[index] = Page(path=self.path, array=data)\n
    "},{"location":"reference/base/#tablite.base.Column.types","title":"tablite.base.Column.types()","text":"

    returns dict with python datatypes

    RETURNS DESCRIPTION dict

    frequency of occurrence of python datatypes

    Source code in tablite/base.py
    def types(self):\n    \"\"\"\n    returns dict with python datatypes\n\n    Returns:\n        dict: frequency of occurrence of python datatypes\n    \"\"\"\n    d = Counter()\n    for page in self.pages:\n        assert isinstance(page.dtype, dict)\n        d += page.dtype\n    return dict(d)\n
    "},{"location":"reference/base/#tablite.base.Column.index","title":"tablite.base.Column.index()","text":"

    returns dict with { unique entry : list of indices }

    example:

    >>> c = Column(data=['a','b','a','c','b'])\n>>> c.index()\n{'a':[0,2], 'b': [1,4], 'c': [3]}\n
    Source code in tablite/base.py
    def index(self):\n    \"\"\"\n    returns dict with { unique entry : list of indices }\n\n    example:\n    ```\n    >>> c = Column(data=['a','b','a','c','b'])\n    >>> c.index()\n    {'a':[0,2], 'b': [1,4], 'c': [3]}\n    ```\n    \"\"\"\n    d = defaultdict(list)\n    for ix, v in enumerate(self.__iter__()):\n        d[v].append(ix)\n    return dict(d)\n
    "},{"location":"reference/base/#tablite.base.Column.unique","title":"tablite.base.Column.unique()","text":"

    returns unique list of values.

    example:

    >>> c = Column(data=['a','b','a','c','b'])\n>>> c.unqiue()\n['a','b','c']\n
    Source code in tablite/base.py
    def unique(self):\n    \"\"\"\n    returns unique list of values.\n\n    example:\n    ```\n    >>> c = Column(data=['a','b','a','c','b'])\n    >>> c.unqiue()\n    ['a','b','c']\n    ```\n    \"\"\"\n    arrays = []\n    for page in set(self.pages):\n        try:  # when it works, numpy is fast...\n            arrays.append(np.unique(page.get()))\n        except TypeError:  # ...but np.unique cannot handle Nones.\n            arrays.append(multitype_set(page.get()))\n    union = np_type_unify(arrays)\n    try:\n        return np.unique(union)\n    except MemoryError:\n        return np.array(set(union))\n    except TypeError:\n        return multitype_set(union)\n
    "},{"location":"reference/base/#tablite.base.Column.histogram","title":"tablite.base.Column.histogram()","text":"

    returns 2 arrays: unique elements and count of each element

    example:

    >>> c = Column(data=['a','b','a','c','b'])\n>>> c.histogram()\n{'a':2,'b':2,'c':1}\n
    Source code in tablite/base.py
    def histogram(self):\n    \"\"\"\n    returns 2 arrays: unique elements and count of each element\n\n    example:\n    ```\n    >>> c = Column(data=['a','b','a','c','b'])\n    >>> c.histogram()\n    {'a':2,'b':2,'c':1}\n    ```\n    \"\"\"\n    d = defaultdict(int)\n    for page in self.pages:\n        try:\n            uarray, carray = np.unique(page.get(), return_counts=True)\n        except TypeError:\n            uarray = page.get()\n            carray = repeat(1, len(uarray))\n\n        for i, c in zip(uarray, carray):\n            v = numpy_to_python(i)\n            d[(type(v), v)] += numpy_to_python(c)\n    u = [v for _, v in d.keys()]\n    c = list(d.values())\n    return u, c  # unique, counts\n
    "},{"location":"reference/base/#tablite.base.Column.statistics","title":"tablite.base.Column.statistics()","text":"

    provides summary statistics.

    RETURNS DESCRIPTION dict

    returns dict with:

    • min (int/float, length of str, date)
    • max (int/float, length of str, date)
    • mean (int/float, length of str, date)
    • median (int/float, length of str, date)
    • stdev (int/float, length of str, date)
    • mode (int/float, length of str, date)
    • distinct (int/float, length of str, date)
    • iqr (int/float, length of str, date)
    • sum (int/float, length of str, date)
    • histogram (see .histogram)
    Source code in tablite/base.py
    def statistics(self):\n    \"\"\"provides summary statistics.\n\n    Returns:\n        dict: returns dict with:\n        - min (int/float, length of str, date)\n        - max (int/float, length of str, date)\n        - mean (int/float, length of str, date)\n        - median (int/float, length of str, date)\n        - stdev (int/float, length of str, date)\n        - mode (int/float, length of str, date)\n        - distinct (int/float, length of str, date)\n        - iqr (int/float, length of str, date)\n        - sum (int/float, length of str, date)\n        - histogram (see .histogram)\n    \"\"\"\n    values, counts = self.histogram()\n    return summary_statistics(values, counts)\n
    "},{"location":"reference/base/#tablite.base.Column.count","title":"tablite.base.Column.count(item)","text":"

    counts appearances of item in column.

    Note that in python, True == 1 and False == 0, whereby the following difference occurs:

    in python:

    >>> L = [1, True]\n>>> L.count(True)\n2\n

    in tablite:

    >>> t = Table({'L': [1,True]})\n>>> t['L'].count(True)\n1\n
    PARAMETER DESCRIPTION item

    target item

    TYPE: Any

    RETURNS DESCRIPTION int

    number of occurrences of item.

    Source code in tablite/base.py
    def count(self, item):\n    \"\"\"counts appearances of item in column.\n\n    Note that in python, `True == 1` and `False == 0`,\n    whereby the following difference occurs:\n\n    in python:\n    ```\n    >>> L = [1, True]\n    >>> L.count(True)\n    2\n    ```\n    in tablite:\n    ```\n    >>> t = Table({'L': [1,True]})\n    >>> t['L'].count(True)\n    1\n    ```\n\n    Args:\n        item (Any): target item\n\n    Returns:\n        int: number of occurrences of item.\n    \"\"\"\n    result = 0\n    for page in self.pages:\n        data = page.get()\n        if data.dtype != \"O\":\n            result += np.nonzero(page.get() == item)[0].shape[0]\n            # what happens here ---^ below:\n            # arr = page.get()\n            # >>> arr\n            # array([1,2,3,4,3], int64)\n            # >>> (arr == 3)\n            # array([False, False,  True, False,  True])\n            # >>> np.nonzero(arr==3)\n            # (array([2,4], dtype=int64), )  <-- tuple!\n            # >>> np.nonzero(page.get() == item)[0]\n            # array([2,4])\n            # >>> np.nonzero(page.get() == item)[0].shape\n            # (2, )\n            # >>> np.nonzero(page.get() == item)[0].shape[0]\n            # 2\n        else:\n            result += sum(1 for i in data if type(i) == type(item) and i == item)\n    return result\n
    "},{"location":"reference/base/#tablite.base.BaseTable","title":"tablite.base.BaseTable(columns: [dict, None] = None, headers: [list, None] = None, rows: [list, None] = None, _path: [Path, None] = None)","text":"

    Bases: object

    creates Table

    PARAMETER DESCRIPTION EITHER

    columns (dict, optional): dict with column names as keys, values as lists. Example: t = Table(columns={\"a\": [1, 2], \"b\": [3, 4]})

    _path

    path to main process working directory.

    TYPE: Path DEFAULT: None

    Source code in tablite/base.py
    def __init__(\n    self,\n    columns: [dict, None] = None,\n    headers: [list, None] = None,\n    rows: [list, None] = None,\n    _path: [Path, None] = None,\n) -> None:\n    \"\"\"creates Table\n\n    Args:\n        EITHER:\n            columns (dict, optional): dict with column names as keys, values as lists.\n            Example: t = Table(columns={\"a\": [1, 2], \"b\": [3, 4]})\n        OR\n            headers (list of strings, optional): list of column names.\n            rows (list of tuples or lists, optional): values for columns\n            Example: t = Table(headers=[\"a\", \"b\"], rows=[[1,3], [2,4]])\n\n        _path (pathlib.Path, optional): path to main process working directory.\n    \"\"\"\n    if _path is None:\n        if self._pid_dir is None:\n            self._pid_dir = Path(Config.workdir) / Config.pid\n            if not self._pid_dir.exists():\n                self._pid_dir.mkdir()\n                (self._pid_dir / \"pages\").mkdir()\n            register(self._pid_dir)\n\n        _path = Path(self._pid_dir)\n        # if path exists under the given PID it will be overwritten.\n        # this can only happen if the process previously was SIGKILLed.\n    type_check(_path, Path)\n    self.path = _path  # filename used during multiprocessing.\n    self.columns = {}  # maps colunn names to instances of Column.\n\n    # user friendly features.\n    if columns and any((headers, rows)):\n        raise ValueError(\"Either columns as dict OR headers and rows. Not both.\")\n\n    if headers and rows:\n        rotated = list(zip(*rows))\n        columns = {k: v for k, v in zip(headers, rotated)}\n\n    if columns:\n        type_check(columns, dict)\n        for k, v in columns.items():\n            self.__setitem__(k, v)\n
    "},{"location":"reference/base/#tablite.base.BaseTable-attributes","title":"Attributes","text":""},{"location":"reference/base/#tablite.base.BaseTable.path","title":"tablite.base.BaseTable.path = _path instance-attribute","text":""},{"location":"reference/base/#tablite.base.BaseTable.columns","title":"tablite.base.BaseTable.columns = {} instance-attribute","text":""},{"location":"reference/base/#tablite.base.BaseTable.rows","title":"tablite.base.BaseTable.rows property","text":"

    enables row based iteration in python types.

    Example:

    for row in Table.rows:\n    print(row)\n

    Yields: tuple: values is same order as columns.

    "},{"location":"reference/base/#tablite.base.BaseTable-functions","title":"Functions","text":""},{"location":"reference/base/#tablite.base.BaseTable.__str__","title":"tablite.base.BaseTable.__str__()","text":"Source code in tablite/base.py
    def __str__(self):  # USER FUNCTION.\n    return f\"{self.__class__.__name__}({len(self.columns):,} columns, {len(self):,} rows)\"\n
    "},{"location":"reference/base/#tablite.base.BaseTable.__repr__","title":"tablite.base.BaseTable.__repr__()","text":"Source code in tablite/base.py
    def __repr__(self):\n    return self.__str__()\n
    "},{"location":"reference/base/#tablite.base.BaseTable.nbytes","title":"tablite.base.BaseTable.nbytes()","text":"

    finds the total bytes of the table on disk

    RETURNS DESCRIPTION tuple

    int: real bytes used on disk int: total bytes used if flattened

    Source code in tablite/base.py
    def nbytes(self):  # USER FUNCTION.\n    \"\"\"finds the total bytes of the table on disk\n\n    Returns:\n        tuple:\n            int: real bytes used on disk\n            int: total bytes used if flattened\n    \"\"\"\n    real = {}\n    total = 0\n    for column in self.columns.values():\n        for page in set(column.pages):\n            real[page] = page.path.stat().st_size\n        for page in column.pages:\n            total += real[page]\n    return sum(real.values()), total\n
    "},{"location":"reference/base/#tablite.base.BaseTable.items","title":"tablite.base.BaseTable.items()","text":"

    returns table as dict

    RETURNS DESCRIPTION dict

    Table as dict {column_name: [values], ...}

    Source code in tablite/base.py
    def items(self):  # USER FUNCTION.\n    \"\"\"returns table as dict\n\n    Returns:\n        dict: Table as dict `{column_name: [values], ...}`\n    \"\"\"\n    return {\n        name: column[:].tolist() for name, column in self.columns.items()\n    }.items()\n
    "},{"location":"reference/base/#tablite.base.BaseTable.__delitem__","title":"tablite.base.BaseTable.__delitem__(key)","text":"

    Examples:

    >>> del table['a']  # removes column 'a'\n>>> del table[-3:]  # removes last 3 rows from all columns.\n
    Source code in tablite/base.py
    def __delitem__(self, key):  # USER FUNCTION.\n    \"\"\"\n    Examples:\n    ```\n    >>> del table['a']  # removes column 'a'\n    >>> del table[-3:]  # removes last 3 rows from all columns.\n    ```\n    \"\"\"\n    if isinstance(key, (int, slice)):\n        for column in self.columns.values():\n            del column[key]\n    elif key in self.columns:\n        del self.columns[key]\n    else:\n        raise KeyError(f\"Key not found: {key}\")\n
    "},{"location":"reference/base/#tablite.base.BaseTable.__setitem__","title":"tablite.base.BaseTable.__setitem__(key, value)","text":"

    table behaves like a dict. Args: key (str or hashable): column name value (iterable): list, tuple or nd.array with values.

    As Table now accepts the keyword columns as a dict:

    >>> t = Table(columns={'b':[4,5,6], 'c':[7,8,9]})\n

    and the header/data combinations:

    >>> t = Table(header=['b','c'], data=[[4,5,6],[7,8,9]])\n

    This has the side-benefit that tuples now can be used as headers.

    Source code in tablite/base.py
    def __setitem__(self, key, value):  # USER FUNCTION\n    \"\"\"table behaves like a dict.\n    Args:\n        key (str or hashable): column name\n        value (iterable): list, tuple or nd.array with values.\n\n    As Table now accepts the keyword `columns` as a dict:\n    ```\n    >>> t = Table(columns={'b':[4,5,6], 'c':[7,8,9]})\n    ```\n    and the header/data combinations:\n    ```\n    >>> t = Table(header=['b','c'], data=[[4,5,6],[7,8,9]])\n    ```\n    This has the side-benefit that tuples now can be used as headers.\n    \"\"\"\n    if value is None:\n        self.columns[key] = Column(self.path, value=None)\n    elif isinstance(value, (list, tuple)):\n        value = list_to_np_array(value)\n        self.columns[key] = Column(self.path, value)\n    elif isinstance(value, (np.ndarray)):\n        self.columns[key] = Column(self.path, value)\n    elif isinstance(value, Column):\n        self.columns[key] = value\n    else:\n        raise TypeError(f\"{type(value)} not supported.\")\n
    "},{"location":"reference/base/#tablite.base.BaseTable.__getitem__","title":"tablite.base.BaseTable.__getitem__(keys)","text":"

    Enables selection of columns and rows

    PARAMETER DESCRIPTION keys

    TYPE: column name, integer or slice

    Examples

    >>>

    10] selects first 10 rows from all columns

    TYPE: table[

    >>>

    20:3] selects column 'b' and 'c' and 'a' twice for a slice.

    TYPE: table['b', 'a', 'a', 'c', 2

    Raises: KeyError: if key is not found. TypeError: if key is not a string, integer or slice.

    RETURNS DESCRIPTION Table

    returns columns in same order as selection.

    Source code in tablite/base.py
    def __getitem__(self, keys):  # USER FUNCTION\n    \"\"\"\n    Enables selection of columns and rows\n\n    Args:\n        keys (column name, integer or slice):\n        Examples:\n        ```\n        >>> table['a']                        selects column 'a'\n        >>> table[3]                          selects row 3 as a tuple.\n        >>> table[:10]                        selects first 10 rows from all columns\n        >>> table['a','b', slice(3,20,2)]     selects a slice from columns 'a' and 'b'\n        >>> table['b', 'a', 'a', 'c', 2:20:3] selects column 'b' and 'c' and 'a' twice for a slice.\n        >>> table[('b', 'a', 'a', 'c')]       selects columns 'b', 'a', 'a', and 'c' using a tuple.\n        ```\n    Raises:\n        KeyError: if key is not found.\n        TypeError: if key is not a string, integer or slice.\n\n    Returns:\n        Table: returns columns in same order as selection.\n    \"\"\"\n\n    if not isinstance(keys, tuple):\n        if isinstance(keys, list):\n            keys = tuple(keys)\n        else:\n            keys = (keys,)\n    if isinstance(keys[0], tuple):\n        keys = tuple(list(chain(*keys)))\n\n    integers = [i for i in keys if isinstance(i, int)]\n    if len(integers) == len(keys) == 1:  # return a single tuple.\n        keys = [slice(keys[0])]\n\n    column_names = [i for i in keys if isinstance(i, str)]\n    column_names = list(self.columns) if not column_names else column_names\n    not_found = [name for name in column_names if name not in self.columns]\n    if not_found:\n        raise KeyError(f\"keys not found: {', '.join(not_found)}\")\n\n    slices = [i for i in keys if isinstance(i, slice)]\n    slc = slice(0, len(self)) if not slices else slices[0]\n\n    if (\n        len(slices) == 0 and len(column_names) == 1\n    ):  # e.g. tbl['a'] or tbl['a'][:10]\n        col = self.columns[column_names[0]]\n        if slices:\n            return col[slc]  # return slice from column as list of values\n        else:\n            return col  # return whole column\n\n    elif len(integers) == 1:  # return a single tuple.\n        row_no = integers[0]\n        slc = slice(row_no, row_no + 1)\n        return tuple(self.columns[name][slc].tolist()[0] for name in column_names)\n\n    elif not slices:  # e.g. new table with N whole columns.\n        return self.__class__(\n            columns={name: self.columns[name] for name in column_names}\n        )\n\n    else:  # e.g. new table from selection of columns and slices.\n        t = self.__class__()\n        for name in column_names:\n            column = self.columns[name]\n\n            new_column = Column(t.path)  # create new Column.\n            for item in column.getpages(slc):\n                if isinstance(item, np.ndarray):\n                    new_column.extend(item)  # extend subslice (expensive)\n                elif isinstance(item, SimplePage):\n                    new_column.pages.append(item)  # extend page (cheap)\n                else:\n                    raise TypeError(f\"Bad item: {item}\")\n\n            # below:\n            # set the new column directly on t.columns.\n            # Do not use t[name] as that triggers __setitem__ again.\n            t.columns[name] = new_column\n\n        return t\n
    "},{"location":"reference/base/#tablite.base.BaseTable.__len__","title":"tablite.base.BaseTable.__len__()","text":"Source code in tablite/base.py
    def __len__(self):  # USER FUNCTION.\n    if not self.columns:\n        return 0\n    return max(len(c) for c in self.columns.values())\n
    "},{"location":"reference/base/#tablite.base.BaseTable.__eq__","title":"tablite.base.BaseTable.__eq__(other) -> bool","text":"

    Determines if two tables have identical content.

    PARAMETER DESCRIPTION other

    table for comparison

    TYPE: Table

    RETURNS DESCRIPTION bool

    True if tables are identical.

    TYPE: bool

    Source code in tablite/base.py
    def __eq__(self, other) -> bool:  # USER FUNCTION.\n    \"\"\"Determines if two tables have identical content.\n\n    Args:\n        other (Table): table for comparison\n\n    Returns:\n        bool: True if tables are identical.\n    \"\"\"\n    if isinstance(other, dict):\n        return self.items() == other.items()\n    if not isinstance(other, BaseTable):\n        return False\n    if id(self) == id(other):\n        return True\n    if len(self) != len(other):\n        return False\n    if len(self) == len(other) == 0:\n        return True\n    if self.columns.keys() != other.columns.keys():\n        return False\n    for name, col in self.columns.items():\n        if not (col == other.columns[name]):\n            return False\n    return True\n
    "},{"location":"reference/base/#tablite.base.BaseTable.clear","title":"tablite.base.BaseTable.clear()","text":"

    clears the table. Like dict().clear()

    Source code in tablite/base.py
    def clear(self):  # USER FUNCTION.\n    \"\"\"clears the table. Like dict().clear()\"\"\"\n    self.columns.clear()\n
    "},{"location":"reference/base/#tablite.base.BaseTable.save","title":"tablite.base.BaseTable.save(path, compression_method=zipfile.ZIP_DEFLATED, compression_level=1)","text":"

    saves table to compressed tpz file.

    PARAMETER DESCRIPTION path

    file destination.

    TYPE: Path

    compression_method

    See zipfile compression methods. Defaults to ZIP_DEFLATED.

    DEFAULT: ZIP_DEFLATED

    compression_level

    See zipfile compression levels. Defaults to 1.

    DEFAULT: 1

    The file format is as follows: .tpz is a gzip archive with table metadata captured as table.yml and the necessary set of pages saved as .npy files.

    The zip contains table.yml which provides an overview of the data:

    --------------------------------------\n%YAML 1.2                              yaml version\ncolumns:                               start of columns section.\n    name: \u201c\u5217 1\u201d                       name of column 1.\n        pages: [p1b1, p1b2]            list of pages in column 1.\n    name: \u201c\u5217 2\u201d                       name of column 2\n        pages: [p2b1, p2b2]            list of pages in column 2.\n----------------------------------------\n
    Source code in tablite/base.py
    def save(\n    self, path, compression_method=zipfile.ZIP_DEFLATED, compression_level=1\n):  # USER FUNCTION.\n    \"\"\"saves table to compressed tpz file.\n\n    Args:\n        path (Path): file destination.\n        compression_method: See zipfile compression methods. Defaults to ZIP_DEFLATED.\n        compression_level: See zipfile compression levels. Defaults to 1.\n        The default settings produce 80% compression at 10% slowdown.\n\n    The file format is as follows:\n    .tpz is a gzip archive with table metadata captured as table.yml\n    and the necessary set of pages saved as .npy files.\n\n    The zip contains table.yml which provides an overview of the data:\n    ```\n    --------------------------------------\n    %YAML 1.2                              yaml version\n    columns:                               start of columns section.\n        name: \u201c\u5217 1\u201d                       name of column 1.\n            pages: [p1b1, p1b2]            list of pages in column 1.\n        name: \u201c\u5217 2\u201d                       name of column 2\n            pages: [p2b1, p2b2]            list of pages in column 2.\n    ----------------------------------------\n    ```\n    \"\"\"\n    if isinstance(path, str):\n        path = Path(path)\n    type_check(path, Path)\n    if path.is_dir():\n        raise TypeError(f\"filename needed: {path}\")\n    if path.suffix != \".tpz\":\n        path = path.parent / (path.parts[-1] + \".tpz\")\n\n    # create yaml document\n    _page_counter = 0\n    d = {}\n    cols = {}\n    for name, col in self.columns.items():\n        type_check(col, Column)\n        cols[name] = {\"pages\": [p.path.name for p in col.pages]}\n        _page_counter += len(col.pages)\n    d[\"columns\"] = cols\n    yml = yaml.safe_dump(\n        d, sort_keys=False, allow_unicode=True, default_flow_style=None\n    )\n\n    _file_counter = 0\n    with zipfile.ZipFile(\n        path, \"w\", compression=compression_method, compresslevel=compression_level\n    ) as f:\n        log.debug(f\"writing .tpz to {path} with\\n{yml}\")\n        f.writestr(\"table.yml\", yml)\n        for name, col in self.columns.items():\n            for page in set(\n                col.pages\n            ):  # set of pages! remember t *= 1000 repeats t 1000x\n                with open(page.path, \"rb\", buffering=0) as raw_io:\n                    f.writestr(page.path.name, raw_io.read())\n                _file_counter += 1\n                log.debug(f\"adding Page {page.path}\")\n\n        _fields = len(self) * len(self.columns)\n        _avg = _fields // _page_counter\n        log.debug(\n            f\"Wrote {_fields:,} on {_page_counter:,} pages in {_file_counter} files: {_avg} fields/page\"\n        )\n
    "},{"location":"reference/base/#tablite.base.BaseTable.load","title":"tablite.base.BaseTable.load(path, tqdm=_tqdm) classmethod","text":"

    loads a table from .tpz file. See also Table.save for details on the file format.

    PARAMETER DESCRIPTION path

    source file

    TYPE: Path

    RETURNS DESCRIPTION Table

    table in read-only mode.

    Source code in tablite/base.py
    @classmethod\ndef load(cls, path, tqdm=_tqdm):  # USER FUNCTION.\n    \"\"\"loads a table from .tpz file.\n    See also Table.save for details on the file format.\n\n    Args:\n        path (Path): source file\n\n    Returns:\n        Table: table in read-only mode.\n    \"\"\"\n    path = Path(path)\n    log.debug(f\"loading {path}\")\n    with zipfile.ZipFile(path, \"r\") as f:\n        yml = f.read(\"table.yml\")\n        metadata = yaml.safe_load(yml)\n        t = cls()\n\n        page_count = sum([len(c[\"pages\"]) for c in metadata[\"columns\"].values()])\n\n        with tqdm(\n            total=page_count,\n            desc=f\"loading '{path.name}' file\",\n            disable=Config.TQDM_DISABLE,\n        ) as pbar:\n            for name, d in metadata[\"columns\"].items():\n                column = Column(t.path)\n                for page in d[\"pages\"]:\n                    bytestream = io.BytesIO(f.read(page))\n                    data = np.load(bytestream, allow_pickle=True, fix_imports=False)\n                    column.extend(data)\n                    pbar.update(1)\n                t.columns[name] = column\n    update_access_time(path)\n    return t\n
    "},{"location":"reference/base/#tablite.base.BaseTable.copy","title":"tablite.base.BaseTable.copy()","text":"Source code in tablite/base.py
    def copy(self):\n    cls = type(self)\n    t = cls()\n    for name, column in self.columns.items():\n        new = Column(t.path)\n        new.pages = column.pages[:]\n        t.columns[name] = new\n    return t\n
    "},{"location":"reference/base/#tablite.base.BaseTable.__imul__","title":"tablite.base.BaseTable.__imul__(other)","text":"

    Repeats instance of table N times.

    Like list: t = t * N

    PARAMETER DESCRIPTION other

    multiplier

    TYPE: int

    Source code in tablite/base.py
    def __imul__(self, other):\n    \"\"\"Repeats instance of table N times.\n\n    Like list: `t = t * N`\n\n    Args:\n        other (int): multiplier\n    \"\"\"\n    if not (isinstance(other, int) and other > 0):\n        raise TypeError(\n            f\"a table can be repeated an integer number of times, not {type(other)} number of times\"\n        )\n    for col in self.columns.values():\n        col *= other\n    return self\n
    "},{"location":"reference/base/#tablite.base.BaseTable.__mul__","title":"tablite.base.BaseTable.__mul__(other)","text":"

    Repeat table N times. Like list: new = old * N

    PARAMETER DESCRIPTION other

    multiplier

    TYPE: int

    RETURNS DESCRIPTION

    Table

    Source code in tablite/base.py
    def __mul__(self, other):\n    \"\"\"Repeat table N times.\n    Like list: `new = old * N`\n\n    Args:\n        other (int): multiplier\n\n    Returns:\n        Table\n    \"\"\"\n    new = self.copy()\n    return new.__imul__(other)\n
    "},{"location":"reference/base/#tablite.base.BaseTable.__iadd__","title":"tablite.base.BaseTable.__iadd__(other)","text":"

    Concatenates tables with same column names.

    Like list: table_1 += table_2

    RAISES DESCRIPTION ValueError

    If column names don't match.

    RETURNS DESCRIPTION None

    self is updated.

    Source code in tablite/base.py
    def __iadd__(self, other):\n    \"\"\"Concatenates tables with same column names.\n\n    Like list: `table_1 += table_2`\n\n    Args:\n        other (Table)\n\n    Raises:\n        ValueError: If column names don't match.\n\n    Returns:\n        None: self is updated.\n    \"\"\"\n    type_check(other, BaseTable)\n    for name in self.columns.keys():\n        if name not in other.columns:\n            raise ValueError(f\"{name} not in other\")\n    for name in other.columns.keys():\n        if name not in self.columns:\n            raise ValueError(f\"{name} missing from self\")\n\n    for name, column in self.columns.items():\n        other_col = other.columns.get(name, None)\n        column.pages.extend(other_col.pages[:])\n    return self\n
    "},{"location":"reference/base/#tablite.base.BaseTable.__add__","title":"tablite.base.BaseTable.__add__(other)","text":"

    Concatenates tables with same column names.

    Like list: table_3 = table_1 + table_2

    RAISES DESCRIPTION ValueError

    If column names don't match.

    RETURNS DESCRIPTION

    Table

    Source code in tablite/base.py
    def __add__(self, other):\n    \"\"\"Concatenates tables with same column names.\n\n    Like list: `table_3 = table_1 + table_2`\n\n    Args:\n        other (Table)\n\n    Raises:\n        ValueError: If column names don't match.\n\n    Returns:\n        Table\n    \"\"\"\n    type_check(other, BaseTable)\n    cp = self.copy()\n    cp += other\n    return cp\n
    "},{"location":"reference/base/#tablite.base.BaseTable.add_rows","title":"tablite.base.BaseTable.add_rows(*args, **kwargs)","text":"

    its more efficient to add many rows at once.

    if both args and kwargs, then args are added first, followed by kwargs.

    supported cases:

    >>> t = Table()\n>>> t.add_columns('row','A','B','C')\n>>> t.add_rows(1, 1, 2, 3)                              # (1) individual values as args\n>>> t.add_rows([2, 1, 2, 3])                            # (2) list of values as args\n>>> t.add_rows((3, 1, 2, 3))                            # (3) tuple of values as args\n>>> t.add_rows(*(4, 1, 2, 3))                           # (4) unpacked tuple becomes arg like (1)\n>>> t.add_rows(row=5, A=1, B=2, C=3)                    # (5) kwargs\n>>> t.add_rows(**{'row': 6, 'A': 1, 'B': 2, 'C': 3})    # (6) dict / json interpreted a kwargs\n>>> t.add_rows((7, 1, 2, 3), (8, 4, 5, 6))              # (7) two (or more) tuples as args\n>>> t.add_rows([9, 1, 2, 3], [10, 4, 5, 6])             # (8) two or more lists as rgs\n>>> t.add_rows(\n    {'row': 11, 'A': 1, 'B': 2, 'C': 3},\n    {'row': 12, 'A': 4, 'B': 5, 'C': 6}\n    )                                                   # (9) two (or more) dicts as args - roughly comma sep'd json.\n>>> t.add_rows( *[\n    {'row': 13, 'A': 1, 'B': 2, 'C': 3},\n    {'row': 14, 'A': 1, 'B': 2, 'C': 3}\n    ])                                                  # (10) list of dicts as args\n>>> t.add_rows(row=[15,16], A=[1,1], B=[2,2], C=[3,3])  # (11) kwargs with lists as values\n
    Source code in tablite/base.py
    def add_rows(self, *args, **kwargs):\n    \"\"\"its more efficient to add many rows at once.\n\n    if both args and kwargs, then args are added first, followed by kwargs.\n\n    supported cases:\n    ```\n    >>> t = Table()\n    >>> t.add_columns('row','A','B','C')\n    >>> t.add_rows(1, 1, 2, 3)                              # (1) individual values as args\n    >>> t.add_rows([2, 1, 2, 3])                            # (2) list of values as args\n    >>> t.add_rows((3, 1, 2, 3))                            # (3) tuple of values as args\n    >>> t.add_rows(*(4, 1, 2, 3))                           # (4) unpacked tuple becomes arg like (1)\n    >>> t.add_rows(row=5, A=1, B=2, C=3)                    # (5) kwargs\n    >>> t.add_rows(**{'row': 6, 'A': 1, 'B': 2, 'C': 3})    # (6) dict / json interpreted a kwargs\n    >>> t.add_rows((7, 1, 2, 3), (8, 4, 5, 6))              # (7) two (or more) tuples as args\n    >>> t.add_rows([9, 1, 2, 3], [10, 4, 5, 6])             # (8) two or more lists as rgs\n    >>> t.add_rows(\n        {'row': 11, 'A': 1, 'B': 2, 'C': 3},\n        {'row': 12, 'A': 4, 'B': 5, 'C': 6}\n        )                                                   # (9) two (or more) dicts as args - roughly comma sep'd json.\n    >>> t.add_rows( *[\n        {'row': 13, 'A': 1, 'B': 2, 'C': 3},\n        {'row': 14, 'A': 1, 'B': 2, 'C': 3}\n        ])                                                  # (10) list of dicts as args\n    >>> t.add_rows(row=[15,16], A=[1,1], B=[2,2], C=[3,3])  # (11) kwargs with lists as values\n    ```\n\n    \"\"\"\n    if not BaseTable._add_row_slow_warning:\n        warnings.warn(\n            \"add_rows is slow. Consider using add_columns and then assigning values to the columns directly.\"\n        )\n        BaseTable._add_row_slow_warning = True\n\n    if args:\n        if not all(isinstance(i, (list, tuple, dict)) for i in args):  # 1,4\n            args = [args]\n\n        if all(isinstance(i, (list, tuple, dict)) for i in args):  # 2,3,7,8\n            # 1. turn the data into columns:\n\n            d = {n: [] for n in self.columns}\n            for arg in args:\n                if len(arg) != len(self.columns):\n                    raise ValueError(\n                        f\"len({arg})== {len(arg)}, but there are {len(self.columns)} columns\"\n                    )\n\n                if isinstance(arg, dict):\n                    for k, v in arg.items():  # 7,8\n                        d[k].append(v)\n\n                elif isinstance(arg, (list, tuple)):  # 2,3\n                    for n, v in zip(self.columns, arg):\n                        d[n].append(v)\n\n                else:\n                    raise TypeError(f\"{arg}?\")\n            # 2. extend the columns\n            for n, values in d.items():\n                col = self.columns[n]\n                col.extend(list_to_np_array(values))\n\n    if kwargs:\n        if isinstance(kwargs, dict):\n            if all(isinstance(v, (list, tuple)) for v in kwargs.values()):\n                for k, v in kwargs.items():\n                    col = self.columns[k]\n                    col.extend(list_to_np_array(v))\n            else:\n                for k, v in kwargs.items():\n                    col = self.columns[k]\n                    col.extend(np.array([v]))\n        else:\n            raise ValueError(f\"format not recognised: {kwargs}\")\n\n    return\n
    "},{"location":"reference/base/#tablite.base.BaseTable.add_columns","title":"tablite.base.BaseTable.add_columns(*names)","text":"

    Adds column names to table.

    Source code in tablite/base.py
    def add_columns(self, *names):\n    \"\"\"Adds column names to table.\"\"\"\n    for name in names:\n        self.columns[name] = Column(self.path)\n
    "},{"location":"reference/base/#tablite.base.BaseTable.add_column","title":"tablite.base.BaseTable.add_column(name, data=None)","text":"

    verbose alias for table[name] = data, that checks if name already exists

    PARAMETER DESCRIPTION name

    column name

    TYPE: str

    data

    values. Defaults to None.

    TYPE: list,tuple) DEFAULT: None

    RAISES DESCRIPTION TypeError

    name isn't string

    ValueError

    name already exists

    Source code in tablite/base.py
    def add_column(self, name, data=None):\n    \"\"\"verbose alias for table[name] = data, that checks if name already exists\n\n    Args:\n        name (str): column name\n        data ((list,tuple), optional): values. Defaults to None.\n\n    Raises:\n        TypeError: name isn't string\n        ValueError: name already exists\n    \"\"\"\n    if not isinstance(name, str):\n        raise TypeError(\"expected name as string\")\n    if name in self.columns:\n        raise ValueError(f\"{name} already in {self.columns}\")\n    self.__setitem__(name, data)\n
    "},{"location":"reference/base/#tablite.base.BaseTable.stack","title":"tablite.base.BaseTable.stack(other)","text":"

    returns the joint stack of tables with overlapping column names. Example:

    | Table A|  +  | Table B| = |  Table AB |\n| A| B| C|     | A| B| D|   | A| B| C| -|\n                            | A| B| -| D|\n
    Source code in tablite/base.py
    def stack(self, other):\n    \"\"\"\n    returns the joint stack of tables with overlapping column names.\n    Example:\n    ```\n    | Table A|  +  | Table B| = |  Table AB |\n    | A| B| C|     | A| B| D|   | A| B| C| -|\n                                | A| B| -| D|\n    ```\n    \"\"\"\n    if not isinstance(other, BaseTable):\n        raise TypeError(f\"stack only works for Table, not {type(other)}\")\n\n    cp = self.copy()\n    for name, col2 in other.columns.items():\n        if name not in cp.columns:\n            cp[name] = [None] * len(self)\n        cp[name].pages.extend(col2.pages[:])\n\n    for name in self.columns:\n        if name not in other.columns:\n            if len(cp) > 0:\n                cp[name].extend(np.array([None] * len(other)))\n    return cp\n
    "},{"location":"reference/base/#tablite.base.BaseTable.types","title":"tablite.base.BaseTable.types()","text":"

    returns nested dict of data types in the form: {column name: {python type class: number of instances }, ... }

    example:

    >>> t.types()\n{\n    'A': {<class 'str'>: 7},\n    'B': {<class 'int'>: 7}\n}\n
    Source code in tablite/base.py
    def types(self):\n    \"\"\"\n    returns nested dict of data types in the form:\n    `{column name: {python type class: number of instances }, ... }`\n\n    example:\n    ```\n    >>> t.types()\n    {\n        'A': {<class 'str'>: 7},\n        'B': {<class 'int'>: 7}\n    }\n    ```\n    \"\"\"\n    d = {}\n    for name, col in self.columns.items():\n        assert isinstance(col, Column)\n        d[name] = col.types()\n    return d\n
    "},{"location":"reference/base/#tablite.base.BaseTable.display_dict","title":"tablite.base.BaseTable.display_dict(slice_=None, blanks=None, dtype=False)","text":"

    helper for creating dict for display.

    PARAMETER DESCRIPTION slice_

    python slice. Defaults to None.

    TYPE: slice DEFAULT: None

    blanks

    fill value for None. Defaults to None.

    TYPE: optional DEFAULT: None

    dtype

    Adds datatype to each column. Defaults to False.

    TYPE: bool DEFAULT: False

    RAISES DESCRIPTION TypeError

    slice_ must be None or slice.

    RETURNS DESCRIPTION dict

    from Table.

    Source code in tablite/base.py
    def display_dict(self, slice_=None, blanks=None, dtype=False):\n    \"\"\"helper for creating dict for display.\n\n    Args:\n        slice_ (slice, optional): python slice. Defaults to None.\n        blanks (optional): fill value for `None`. Defaults to None.\n        dtype (bool, optional): Adds datatype to each column. Defaults to False.\n\n    Raises:\n        TypeError: slice_ must be None or slice.\n\n    Returns:\n        dict: from Table.\n    \"\"\"\n    if not self.columns:\n        print(\"Empty Table\")\n        return\n\n    def datatype(col):  # PRIVATE\n        \"\"\"creates label for column datatype.\"\"\"\n        types = col.types()\n        if len(types) == 0:\n            typ = \"empty\"\n        elif len(types) == 1:\n            dt, _ = types.popitem()\n            typ = dt.__name__\n        else:\n            typ = \"mixed\"\n        return typ\n\n    row_count_tags = [\"#\", \"~\", \"*\"]\n    cols = set(self.columns)\n    for n, tag in product(range(1, 6), row_count_tags):\n        if n * tag not in cols:\n            tag = n * tag\n            break\n\n    if not isinstance(slice_, (slice, type(None))):\n        raise TypeError(f\"slice_ must be None or slice, not {type(slice_)}\")\n    if isinstance(slice_, slice):\n        slc = slice_\n    if slice_ is None:\n        if len(self) <= 20:\n            slc = slice(0, 20, 1)\n        else:\n            slc = None\n\n    n = len(self)\n    if slc:  # either we want slc or we want everything.\n        row_no = list(range(*slc.indices(len(self))))\n        data = {tag: [f\"{i:,}\".rjust(2) for i in row_no]}\n        for name, col in self.columns.items():\n            data[name] = list(chain(iter(col), repeat(blanks, times=n - len(col))))[\n                slc\n            ]\n    else:\n        data = {}\n        j = int(math.ceil(math.log10(n)) / 3) + len(str(n))\n        row_no = (\n            [f\"{i:,}\".rjust(j) for i in range(7)]\n            + [\"...\"]\n            + [f\"{i:,}\".rjust(j) for i in range(n - 7, n)]\n        )\n        data = {tag: row_no}\n\n        for name, col in self.columns.items():\n            if len(col) == n:\n                row = col[:7].tolist() + [\"...\"] + col[-7:].tolist()\n            else:\n                empty = [blanks] * 7\n                head = (col[:7].tolist() + empty)[:7]\n                tail = (col[n - 7 :].tolist() + empty)[-7:]\n                row = head + [\"...\"] + tail\n            data[name] = row\n\n    if dtype:\n        for name, values in data.items():\n            if name in self.columns:\n                col = self.columns[name]\n                values.insert(0, datatype(col))\n            else:\n                values.insert(0, \"row\")\n\n    return data\n
    "},{"location":"reference/base/#tablite.base.BaseTable.to_ascii","title":"tablite.base.BaseTable.to_ascii(slice_=None, blanks=None, dtype=False)","text":"

    returns ascii view of table as string.

    PARAMETER DESCRIPTION slice_

    slice to determine table snippet.

    TYPE: slice DEFAULT: None

    blanks

    value for whitespace. Defaults to None.

    TYPE: str DEFAULT: None

    dtype

    adds subheader with datatype for column. Defaults to False.

    TYPE: bool DEFAULT: False

    Source code in tablite/base.py
    def to_ascii(self, slice_=None, blanks=None, dtype=False):\n    \"\"\"returns ascii view of table as string.\n\n    Args:\n        slice_ (slice, optional): slice to determine table snippet.\n        blanks (str, optional): value for whitespace. Defaults to None.\n        dtype (bool, optional): adds subheader with datatype for column. Defaults to False.\n    \"\"\"\n\n    def adjust(v, length):  # PRIVATE FUNCTION\n        \"\"\"whitespace justifies field values based on datatype\"\"\"\n        if v is None:\n            return str(blanks).ljust(length)\n        elif isinstance(v, str):\n            return v.ljust(length)\n        else:\n            return str(v).rjust(length)\n\n    if not self.columns:\n        return str(self)\n\n    d = {}\n    for name, values in self.display_dict(\n        slice_=slice_, blanks=blanks, dtype=dtype\n    ).items():\n        as_text = [str(v) for v in values] + [str(name)]\n        width = max(len(i) for i in as_text)\n        new_name = name.center(width, \" \")\n        if dtype:\n            values[0] = values[0].center(width, \" \")\n        d[new_name] = [adjust(v, width) for v in values]\n\n    rows = dict_to_rows(d)\n    s = []\n    s.append(\"+\" + \"+\".join([\"=\" * len(n) for n in rows[0]]) + \"+\")\n    s.append(\"|\" + \"|\".join(rows[0]) + \"|\")  # column names\n    start = 1\n    if dtype:\n        s.append(\"|\" + \"|\".join(rows[1]) + \"|\")  # datatypes\n        start = 2\n\n    s.append(\"+\" + \"+\".join([\"-\" * len(n) for n in rows[0]]) + \"+\")\n    for row in rows[start:]:\n        s.append(\"|\" + \"|\".join(row) + \"|\")\n    s.append(\"+\" + \"+\".join([\"=\" * len(n) for n in rows[0]]) + \"+\")\n\n    if len(set(len(c) for c in self.columns.values())) != 1:\n        warning = f\"Warning: Columns have different lengths. {blanks} is used as fill value.\"\n        s.append(warning)\n\n    return \"\\n\".join(s)\n
    "},{"location":"reference/base/#tablite.base.BaseTable.show","title":"tablite.base.BaseTable.show(slice_=None, blanks=None, dtype=False)","text":"

    prints ascii view of table.

    PARAMETER DESCRIPTION slice_

    slice to determine table snippet.

    TYPE: slice DEFAULT: None

    blanks

    value for whitespace. Defaults to None.

    TYPE: str DEFAULT: None

    dtype

    adds subheader with datatype for column. Defaults to False.

    TYPE: bool DEFAULT: False

    Source code in tablite/base.py
    def show(self, slice_=None, blanks=None, dtype=False):\n    \"\"\"prints ascii view of table.\n\n    Args:\n        slice_ (slice, optional): slice to determine table snippet.\n        blanks (str, optional): value for whitespace. Defaults to None.\n        dtype (bool, optional): adds subheader with datatype for column. Defaults to False.\n    \"\"\"\n    print(self.to_ascii(slice_=slice_, blanks=blanks, dtype=dtype))\n
    "},{"location":"reference/base/#tablite.base.BaseTable.to_dict","title":"tablite.base.BaseTable.to_dict(columns=None, slice_=None)","text":"

    columns: list of column names. Default is None == all columns. slice_: slice. Default is None == all rows.

    returns: dict with columns as keys and lists of values.

    Example:

    >>> t.show()\n+===+===+===+\n| # | a | b |\n|row|int|int|\n+---+---+---+\n| 0 |  1|  3|\n| 1 |  2|  4|\n+===+===+===+\n>>> t.to_dict()\n{'a':[1,2], 'b':[3,4]}\n
    Source code in tablite/base.py
    def to_dict(self, columns=None, slice_=None):\n    \"\"\"\n    columns: list of column names. Default is None == all columns.\n    slice_: slice. Default is None == all rows.\n\n    returns: dict with columns as keys and lists of values.\n\n    Example:\n    ```\n    >>> t.show()\n    +===+===+===+\n    | # | a | b |\n    |row|int|int|\n    +---+---+---+\n    | 0 |  1|  3|\n    | 1 |  2|  4|\n    +===+===+===+\n    >>> t.to_dict()\n    {'a':[1,2], 'b':[3,4]}\n    ```\n\n    \"\"\"\n    if slice_ is None:\n        slice_ = slice(0, len(self))\n    assert isinstance(slice_, slice)\n\n    if columns is None:\n        columns = list(self.columns.keys())\n    if not isinstance(columns, list):\n        raise TypeError(\"expected columns as list of strings\")\n\n    return {name: list(self.columns[name][slice_]) for name in columns}\n
    "},{"location":"reference/base/#tablite.base.BaseTable.as_json_serializable","title":"tablite.base.BaseTable.as_json_serializable(row_count='row id', start_on=1, columns=None, slice_=None)","text":"

    provides a JSON compatible format of the table.

    PARAMETER DESCRIPTION row_count

    Label for row counts. Defaults to \"row id\".

    TYPE: str DEFAULT: 'row id'

    start_on

    row counts starts by default on 1.

    TYPE: int DEFAULT: 1

    columns

    Column names. Defaults to None which returns all columns.

    TYPE: list of str DEFAULT: None

    slice_

    selector. Defaults to None which returns [:]

    TYPE: slice DEFAULT: None

    RETURNS DESCRIPTION

    JSON serializable dict: All python datatypes have been converted to JSON compliant data.

    Source code in tablite/base.py
    def as_json_serializable(\n    self, row_count=\"row id\", start_on=1, columns=None, slice_=None\n):\n    \"\"\"provides a JSON compatible format of the table.\n\n    Args:\n        row_count (str, optional): Label for row counts. Defaults to \"row id\".\n        start_on (int, optional): row counts starts by default on 1.\n        columns (list of str, optional): Column names.\n            Defaults to None which returns all columns.\n        slice_ (slice, optional): selector. Defaults to None which returns [:]\n\n    Returns:\n        JSON serializable dict: All python datatypes have been converted to JSON compliant data.\n    \"\"\"\n    if slice_ is None:\n        slice_ = slice(0, len(self))\n\n    assert isinstance(slice_, slice)\n    new = {\"columns\": {}, \"total_rows\": len(self)}\n    if row_count is not None:\n        new[\"columns\"][row_count] = [\n            i + start_on for i in range(*slice_.indices(len(self)))\n        ]\n\n    d = self.to_dict(columns, slice_=slice_)\n    for k, data in d.items():\n        new_k = unique_name(\n            k, new[\"columns\"]\n        )  # used to avoid overwriting the `row id` key.\n        new[\"columns\"][new_k] = [\n            DataTypes.to_json(v) for v in data\n        ]  # deal with non-json datatypes.\n    return new\n
    "},{"location":"reference/base/#tablite.base.BaseTable.index","title":"tablite.base.BaseTable.index(*args)","text":"

    param: *args: column names returns multikey index on the columns as d[(key tuple, )] = {index1, index2, ...}

    Examples:

    >>> table6 = Table()\n>>> table6['A'] = ['Alice', 'Bob', 'Bob', 'Ben', 'Charlie', 'Ben','Albert']\n>>> table6['B'] = ['Alison', 'Marley', 'Dylan', 'Affleck', 'Hepburn', 'Barnes', 'Einstein']\n
    >>> table6.index('A')  # single key.\n{('Alice',): [0],\n ('Bob',): [1, 2],\n ('Ben',): [3, 5],\n ('Charlie',): [4],\n ('Albert',): [6]})\n
    >>> table6.index('A', 'B')  # multiple keys.\n{('Alice', 'Alison'): [0],\n ('Bob', 'Marley'): [1],\n ('Bob', 'Dylan'): [2],\n ('Ben', 'Affleck'): [3],\n ('Charlie', 'Hepburn'): [4],\n ('Ben', 'Barnes'): [5],\n ('Albert', 'Einstein'): [6]})\n
    Source code in tablite/base.py
    def index(self, *args):\n    \"\"\"\n    param: *args: column names\n    returns multikey index on the columns as d[(key tuple, )] = {index1, index2, ...}\n\n    Examples:\n        ```\n        >>> table6 = Table()\n        >>> table6['A'] = ['Alice', 'Bob', 'Bob', 'Ben', 'Charlie', 'Ben','Albert']\n        >>> table6['B'] = ['Alison', 'Marley', 'Dylan', 'Affleck', 'Hepburn', 'Barnes', 'Einstein']\n        ```\n\n        ```\n        >>> table6.index('A')  # single key.\n        {('Alice',): [0],\n         ('Bob',): [1, 2],\n         ('Ben',): [3, 5],\n         ('Charlie',): [4],\n         ('Albert',): [6]})\n        ```\n\n        ```\n        >>> table6.index('A', 'B')  # multiple keys.\n        {('Alice', 'Alison'): [0],\n         ('Bob', 'Marley'): [1],\n         ('Bob', 'Dylan'): [2],\n         ('Ben', 'Affleck'): [3],\n         ('Charlie', 'Hepburn'): [4],\n         ('Ben', 'Barnes'): [5],\n         ('Albert', 'Einstein'): [6]})\n        ```\n\n    \"\"\"\n    idx = defaultdict(list)\n    iterators = [iter(self.columns[c]) for c in args]\n    for ix, key in enumerate(zip(*iterators)):\n        key = tuple(numpy_to_python(k) for k in key)\n        idx[key].append(ix)\n    return idx\n
    "},{"location":"reference/base/#tablite.base.BaseTable.unique_index","title":"tablite.base.BaseTable.unique_index(*args, tqdm=_tqdm)","text":"

    generates the index of unique rows given a list of column names

    PARAMETER DESCRIPTION *args

    columns names

    TYPE: any DEFAULT: ()

    tqdm

    Defaults to _tqdm.

    TYPE: tqdm DEFAULT: tqdm

    RETURNS DESCRIPTION

    np.array(int64): indices of unique records.

    Source code in tablite/base.py
    def unique_index(self, *args, tqdm=_tqdm):\n    \"\"\"generates the index of unique rows given a list of column names\n\n    Args:\n        *args (any): columns names\n        tqdm (tqdm, optional): Defaults to _tqdm.\n\n    Returns:\n        np.array(int64): indices of unique records.\n    \"\"\"\n    if not args:\n        raise ValueError(\"*args (column names) is required\")\n    seen = set()\n    unique = set()\n    iterators = [iter(self.columns[c]) for c in args]\n    for ix, key in tqdm(enumerate(zip(*iterators)), disable=Config.TQDM_DISABLE):\n        key_hash = hash(tuple(numpy_to_python(k) for k in key))\n        if key_hash in seen:\n            continue\n        else:\n            seen.add(key_hash)\n            unique.add(ix)\n    return np.array(sorted(unique))\n
    "},{"location":"reference/base/#tablite.base-functions","title":"Functions","text":""},{"location":"reference/base/#tablite.base.register","title":"tablite.base.register(path)","text":"

    registers path in file_registry

    The method is used by Table during init when the working directory path is set, so that python can clean all temporary files up at exit.

    PARAMETER DESCRIPTION path

    typically tmp/tablite-tmp/PID-{os.getpid()}

    TYPE: Path

    Source code in tablite/base.py
    def register(path):\n    \"\"\"registers path in file_registry\n\n    The method is used by Table during init when the working directory path\n    is set, so that python can clean all temporary files up at exit.\n\n    Args:\n        path (Path): typically tmp/tablite-tmp/PID-{os.getpid()}\n    \"\"\"\n    global file_registry\n    file_registry.add(path)\n
    "},{"location":"reference/base/#tablite.base.shutdown","title":"tablite.base.shutdown()","text":"

    method to clean up temporary files triggered at shutdown.

    Source code in tablite/base.py
    def shutdown():\n    \"\"\"method to clean up temporary files triggered at shutdown.\"\"\"\n    for path in file_registry:\n        if Config.pid in str(path):  # safety feature to prevent rm -rf /\n            log.debug(f\"shutdown: running rmtree({path})\")\n            shutil.rmtree(path)\n
    "},{"location":"reference/config/","title":"Config","text":""},{"location":"reference/config/#tablite.config","title":"tablite.config","text":""},{"location":"reference/config/#tablite.config-classes","title":"Classes","text":""},{"location":"reference/config/#tablite.config.Config","title":"tablite.config.Config","text":"

    Bases: object

    Config class for Tablite Tables.

    The default location for the storage is loaded as

    Config.workdir = pathlib.Path(os.environ.get(\"TABLITE_TMPDIR\", f\"{tempfile.gettempdir()}/tablite-tmp\"))\n

    to overwrite, first import the config class, then set the new workdir.

    >>> from tablite import config\n>>> from pathlib import Path\n>>> config.workdir = Path(\"/this/new/location\")\n

    the new path will now be used for every new table.

    PAGE_SIZE = 1_000_000 sets the page size limit.

    Multiprocessing is enabled in one of three modes: AUTO = \"auto\" FALSE = \"sp\" FORCE = \"mp\"

    MULTIPROCESSING_MODE = AUTO is default.

    SINGLE_PROCESSING_LIMIT = 1_000_000 when the number of fields (rows x columns) exceed this value, multiprocessing is used.

    "},{"location":"reference/config/#tablite.config.Config-attributes","title":"Attributes","text":""},{"location":"reference/config/#tablite.config.Config.USE_NIMPORTER","title":"tablite.config.Config.USE_NIMPORTER = os.environ.get('USE_NIMPORTER', 'true').lower() in ['1', 't', 'true', 'y', 'yes'] class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.ALLOW_CSV_READER_FALLTHROUGH","title":"tablite.config.Config.ALLOW_CSV_READER_FALLTHROUGH = os.environ.get('ALLOW_CSV_READER_FALLTHROUGH', 'true').lower() in ['1', 't', 'true', 'y', 'yes'] class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.NIM_SUPPORTED_CONV_TYPES","title":"tablite.config.Config.NIM_SUPPORTED_CONV_TYPES = ['Windows-1252', 'ISO-8859-1'] class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.workdir","title":"tablite.config.Config.workdir = pathlib.Path(os.environ.get('TABLITE_TMPDIR', f'{tempfile.gettempdir()}/tablite-tmp')) class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.pid","title":"tablite.config.Config.pid = f'pid-{os.getpid()}' class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.PAGE_SIZE","title":"tablite.config.Config.PAGE_SIZE = 1000000 class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.ENCODING","title":"tablite.config.Config.ENCODING = 'UTF-8' class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.DISK_LIMIT","title":"tablite.config.Config.DISK_LIMIT = int(10000000000.0) class-attribute instance-attribute","text":"

    10e9 (10Gb) on 100 Gb disk means raise at 90 Gb disk usage. if DISK_LIMIT <= 0, the check is turned off.

    "},{"location":"reference/config/#tablite.config.Config.SINGLE_PROCESSING_LIMIT","title":"tablite.config.Config.SINGLE_PROCESSING_LIMIT = 1000000 class-attribute instance-attribute","text":"

    when the number of fields (rows x columns) exceed this value, multiprocessing is used.

    "},{"location":"reference/config/#tablite.config.Config.vpus","title":"tablite.config.Config.vpus = max(os.cpu_count() - 1, 1) class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.AUTO","title":"tablite.config.Config.AUTO = 'auto' class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.FALSE","title":"tablite.config.Config.FALSE = 'sp' class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.FORCE","title":"tablite.config.Config.FORCE = 'mp' class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.MULTIPROCESSING_MODE","title":"tablite.config.Config.MULTIPROCESSING_MODE = AUTO class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config.TQDM_DISABLE","title":"tablite.config.Config.TQDM_DISABLE = False class-attribute instance-attribute","text":""},{"location":"reference/config/#tablite.config.Config-functions","title":"Functions","text":""},{"location":"reference/config/#tablite.config.Config.reset","title":"tablite.config.Config.reset() classmethod","text":"

    Resets the config class to original values.

    Source code in tablite/config.py
    @classmethod\ndef reset(cls):\n    \"\"\"Resets the config class to original values.\"\"\"\n    for k, v in _default_values.items():\n        setattr(Config, k, v)\n
    "},{"location":"reference/config/#tablite.config.Config.page_steps","title":"tablite.config.Config.page_steps(length) classmethod","text":"

    an iterator that yield start and end in page sizes

    YIELDS DESCRIPTION tuple

    start:int, end:int

    Source code in tablite/config.py
    @classmethod\ndef page_steps(cls, length):\n    \"\"\"an iterator that yield start and end in page sizes\n\n    Yields:\n        tuple: start:int, end:int\n    \"\"\"\n    start, end = 0, 0\n    for _ in range(0, length + 1, cls.PAGE_SIZE):\n        start, end = end, min(end + cls.PAGE_SIZE, length)\n        yield start, end\n        if end == length:\n            return\n
    "},{"location":"reference/core/","title":"Core","text":""},{"location":"reference/core/#tablite.core","title":"tablite.core","text":""},{"location":"reference/core/#tablite.core-attributes","title":"Attributes","text":""},{"location":"reference/core/#tablite.core.log","title":"tablite.core.log = logging.getLogger(__name__) module-attribute","text":""},{"location":"reference/core/#tablite.core-classes","title":"Classes","text":""},{"location":"reference/core/#tablite.core.Table","title":"tablite.core.Table(columns=None, headers=None, rows=None, _path=None)","text":"

    Bases: BaseTable

    creates Table

    PARAMETER DESCRIPTION EITHER

    columns (dict, optional): dict with column names as keys, values as lists. Example: t = Table(columns={\"a\": [1, 2], \"b\": [3, 4]})

    Source code in tablite/core.py
    def __init__(self, columns=None, headers=None, rows=None, _path=None) -> None:\n    \"\"\"creates Table\n\n    Args:\n        EITHER:\n            columns (dict, optional): dict with column names as keys, values as lists.\n            Example: t = Table(columns={\"a\": [1, 2], \"b\": [3, 4]})\n        OR\n            headers (list of strings, optional): list of column names.\n            rows (list of tuples or lists, optional): values for columns\n            Example: t = Table(headers=[\"a\", \"b\"], rows=[[1,3], [2,4]])\n    \"\"\"\n    super().__init__(columns, headers, rows, _path)\n
    "},{"location":"reference/core/#tablite.core.Table-attributes","title":"Attributes","text":""},{"location":"reference/core/#tablite.core.Table.path","title":"tablite.core.Table.path = _path instance-attribute","text":""},{"location":"reference/core/#tablite.core.Table.columns","title":"tablite.core.Table.columns = {} instance-attribute","text":""},{"location":"reference/core/#tablite.core.Table.rows","title":"tablite.core.Table.rows property","text":"

    enables row based iteration in python types.

    Example:

    for row in Table.rows:\n    print(row)\n

    Yields: tuple: values is same order as columns.

    "},{"location":"reference/core/#tablite.core.Table-functions","title":"Functions","text":""},{"location":"reference/core/#tablite.core.Table.__str__","title":"tablite.core.Table.__str__()","text":"Source code in tablite/base.py
    def __str__(self):  # USER FUNCTION.\n    return f\"{self.__class__.__name__}({len(self.columns):,} columns, {len(self):,} rows)\"\n
    "},{"location":"reference/core/#tablite.core.Table.__repr__","title":"tablite.core.Table.__repr__()","text":"Source code in tablite/base.py
    def __repr__(self):\n    return self.__str__()\n
    "},{"location":"reference/core/#tablite.core.Table.nbytes","title":"tablite.core.Table.nbytes()","text":"

    finds the total bytes of the table on disk

    RETURNS DESCRIPTION tuple

    int: real bytes used on disk int: total bytes used if flattened

    Source code in tablite/base.py
    def nbytes(self):  # USER FUNCTION.\n    \"\"\"finds the total bytes of the table on disk\n\n    Returns:\n        tuple:\n            int: real bytes used on disk\n            int: total bytes used if flattened\n    \"\"\"\n    real = {}\n    total = 0\n    for column in self.columns.values():\n        for page in set(column.pages):\n            real[page] = page.path.stat().st_size\n        for page in column.pages:\n            total += real[page]\n    return sum(real.values()), total\n
    "},{"location":"reference/core/#tablite.core.Table.items","title":"tablite.core.Table.items()","text":"

    returns table as dict

    RETURNS DESCRIPTION dict

    Table as dict {column_name: [values], ...}

    Source code in tablite/base.py
    def items(self):  # USER FUNCTION.\n    \"\"\"returns table as dict\n\n    Returns:\n        dict: Table as dict `{column_name: [values], ...}`\n    \"\"\"\n    return {\n        name: column[:].tolist() for name, column in self.columns.items()\n    }.items()\n
    "},{"location":"reference/core/#tablite.core.Table.__delitem__","title":"tablite.core.Table.__delitem__(key)","text":"

    Examples:

    >>> del table['a']  # removes column 'a'\n>>> del table[-3:]  # removes last 3 rows from all columns.\n
    Source code in tablite/base.py
    def __delitem__(self, key):  # USER FUNCTION.\n    \"\"\"\n    Examples:\n    ```\n    >>> del table['a']  # removes column 'a'\n    >>> del table[-3:]  # removes last 3 rows from all columns.\n    ```\n    \"\"\"\n    if isinstance(key, (int, slice)):\n        for column in self.columns.values():\n            del column[key]\n    elif key in self.columns:\n        del self.columns[key]\n    else:\n        raise KeyError(f\"Key not found: {key}\")\n
    "},{"location":"reference/core/#tablite.core.Table.__setitem__","title":"tablite.core.Table.__setitem__(key, value)","text":"

    table behaves like a dict. Args: key (str or hashable): column name value (iterable): list, tuple or nd.array with values.

    As Table now accepts the keyword columns as a dict:

    >>> t = Table(columns={'b':[4,5,6], 'c':[7,8,9]})\n

    and the header/data combinations:

    >>> t = Table(header=['b','c'], data=[[4,5,6],[7,8,9]])\n

    This has the side-benefit that tuples now can be used as headers.

    Source code in tablite/base.py
    def __setitem__(self, key, value):  # USER FUNCTION\n    \"\"\"table behaves like a dict.\n    Args:\n        key (str or hashable): column name\n        value (iterable): list, tuple or nd.array with values.\n\n    As Table now accepts the keyword `columns` as a dict:\n    ```\n    >>> t = Table(columns={'b':[4,5,6], 'c':[7,8,9]})\n    ```\n    and the header/data combinations:\n    ```\n    >>> t = Table(header=['b','c'], data=[[4,5,6],[7,8,9]])\n    ```\n    This has the side-benefit that tuples now can be used as headers.\n    \"\"\"\n    if value is None:\n        self.columns[key] = Column(self.path, value=None)\n    elif isinstance(value, (list, tuple)):\n        value = list_to_np_array(value)\n        self.columns[key] = Column(self.path, value)\n    elif isinstance(value, (np.ndarray)):\n        self.columns[key] = Column(self.path, value)\n    elif isinstance(value, Column):\n        self.columns[key] = value\n    else:\n        raise TypeError(f\"{type(value)} not supported.\")\n
    "},{"location":"reference/core/#tablite.core.Table.__getitem__","title":"tablite.core.Table.__getitem__(keys)","text":"

    Enables selection of columns and rows

    PARAMETER DESCRIPTION keys

    TYPE: column name, integer or slice

    Examples

    >>>

    10] selects first 10 rows from all columns

    TYPE: table[

    >>>

    20:3] selects column 'b' and 'c' and 'a' twice for a slice.

    TYPE: table['b', 'a', 'a', 'c', 2

    Raises: KeyError: if key is not found. TypeError: if key is not a string, integer or slice.

    RETURNS DESCRIPTION Table

    returns columns in same order as selection.

    Source code in tablite/base.py
    def __getitem__(self, keys):  # USER FUNCTION\n    \"\"\"\n    Enables selection of columns and rows\n\n    Args:\n        keys (column name, integer or slice):\n        Examples:\n        ```\n        >>> table['a']                        selects column 'a'\n        >>> table[3]                          selects row 3 as a tuple.\n        >>> table[:10]                        selects first 10 rows from all columns\n        >>> table['a','b', slice(3,20,2)]     selects a slice from columns 'a' and 'b'\n        >>> table['b', 'a', 'a', 'c', 2:20:3] selects column 'b' and 'c' and 'a' twice for a slice.\n        >>> table[('b', 'a', 'a', 'c')]       selects columns 'b', 'a', 'a', and 'c' using a tuple.\n        ```\n    Raises:\n        KeyError: if key is not found.\n        TypeError: if key is not a string, integer or slice.\n\n    Returns:\n        Table: returns columns in same order as selection.\n    \"\"\"\n\n    if not isinstance(keys, tuple):\n        if isinstance(keys, list):\n            keys = tuple(keys)\n        else:\n            keys = (keys,)\n    if isinstance(keys[0], tuple):\n        keys = tuple(list(chain(*keys)))\n\n    integers = [i for i in keys if isinstance(i, int)]\n    if len(integers) == len(keys) == 1:  # return a single tuple.\n        keys = [slice(keys[0])]\n\n    column_names = [i for i in keys if isinstance(i, str)]\n    column_names = list(self.columns) if not column_names else column_names\n    not_found = [name for name in column_names if name not in self.columns]\n    if not_found:\n        raise KeyError(f\"keys not found: {', '.join(not_found)}\")\n\n    slices = [i for i in keys if isinstance(i, slice)]\n    slc = slice(0, len(self)) if not slices else slices[0]\n\n    if (\n        len(slices) == 0 and len(column_names) == 1\n    ):  # e.g. tbl['a'] or tbl['a'][:10]\n        col = self.columns[column_names[0]]\n        if slices:\n            return col[slc]  # return slice from column as list of values\n        else:\n            return col  # return whole column\n\n    elif len(integers) == 1:  # return a single tuple.\n        row_no = integers[0]\n        slc = slice(row_no, row_no + 1)\n        return tuple(self.columns[name][slc].tolist()[0] for name in column_names)\n\n    elif not slices:  # e.g. new table with N whole columns.\n        return self.__class__(\n            columns={name: self.columns[name] for name in column_names}\n        )\n\n    else:  # e.g. new table from selection of columns and slices.\n        t = self.__class__()\n        for name in column_names:\n            column = self.columns[name]\n\n            new_column = Column(t.path)  # create new Column.\n            for item in column.getpages(slc):\n                if isinstance(item, np.ndarray):\n                    new_column.extend(item)  # extend subslice (expensive)\n                elif isinstance(item, SimplePage):\n                    new_column.pages.append(item)  # extend page (cheap)\n                else:\n                    raise TypeError(f\"Bad item: {item}\")\n\n            # below:\n            # set the new column directly on t.columns.\n            # Do not use t[name] as that triggers __setitem__ again.\n            t.columns[name] = new_column\n\n        return t\n
    "},{"location":"reference/core/#tablite.core.Table.__len__","title":"tablite.core.Table.__len__()","text":"Source code in tablite/base.py
    def __len__(self):  # USER FUNCTION.\n    if not self.columns:\n        return 0\n    return max(len(c) for c in self.columns.values())\n
    "},{"location":"reference/core/#tablite.core.Table.__eq__","title":"tablite.core.Table.__eq__(other) -> bool","text":"

    Determines if two tables have identical content.

    PARAMETER DESCRIPTION other

    table for comparison

    TYPE: Table

    RETURNS DESCRIPTION bool

    True if tables are identical.

    TYPE: bool

    Source code in tablite/base.py
    def __eq__(self, other) -> bool:  # USER FUNCTION.\n    \"\"\"Determines if two tables have identical content.\n\n    Args:\n        other (Table): table for comparison\n\n    Returns:\n        bool: True if tables are identical.\n    \"\"\"\n    if isinstance(other, dict):\n        return self.items() == other.items()\n    if not isinstance(other, BaseTable):\n        return False\n    if id(self) == id(other):\n        return True\n    if len(self) != len(other):\n        return False\n    if len(self) == len(other) == 0:\n        return True\n    if self.columns.keys() != other.columns.keys():\n        return False\n    for name, col in self.columns.items():\n        if not (col == other.columns[name]):\n            return False\n    return True\n
    "},{"location":"reference/core/#tablite.core.Table.clear","title":"tablite.core.Table.clear()","text":"

    clears the table. Like dict().clear()

    Source code in tablite/base.py
    def clear(self):  # USER FUNCTION.\n    \"\"\"clears the table. Like dict().clear()\"\"\"\n    self.columns.clear()\n
    "},{"location":"reference/core/#tablite.core.Table.save","title":"tablite.core.Table.save(path, compression_method=zipfile.ZIP_DEFLATED, compression_level=1)","text":"

    saves table to compressed tpz file.

    PARAMETER DESCRIPTION path

    file destination.

    TYPE: Path

    compression_method

    See zipfile compression methods. Defaults to ZIP_DEFLATED.

    DEFAULT: ZIP_DEFLATED

    compression_level

    See zipfile compression levels. Defaults to 1.

    DEFAULT: 1

    The file format is as follows: .tpz is a gzip archive with table metadata captured as table.yml and the necessary set of pages saved as .npy files.

    The zip contains table.yml which provides an overview of the data:

    --------------------------------------\n%YAML 1.2                              yaml version\ncolumns:                               start of columns section.\n    name: \u201c\u5217 1\u201d                       name of column 1.\n        pages: [p1b1, p1b2]            list of pages in column 1.\n    name: \u201c\u5217 2\u201d                       name of column 2\n        pages: [p2b1, p2b2]            list of pages in column 2.\n----------------------------------------\n
    Source code in tablite/base.py
    def save(\n    self, path, compression_method=zipfile.ZIP_DEFLATED, compression_level=1\n):  # USER FUNCTION.\n    \"\"\"saves table to compressed tpz file.\n\n    Args:\n        path (Path): file destination.\n        compression_method: See zipfile compression methods. Defaults to ZIP_DEFLATED.\n        compression_level: See zipfile compression levels. Defaults to 1.\n        The default settings produce 80% compression at 10% slowdown.\n\n    The file format is as follows:\n    .tpz is a gzip archive with table metadata captured as table.yml\n    and the necessary set of pages saved as .npy files.\n\n    The zip contains table.yml which provides an overview of the data:\n    ```\n    --------------------------------------\n    %YAML 1.2                              yaml version\n    columns:                               start of columns section.\n        name: \u201c\u5217 1\u201d                       name of column 1.\n            pages: [p1b1, p1b2]            list of pages in column 1.\n        name: \u201c\u5217 2\u201d                       name of column 2\n            pages: [p2b1, p2b2]            list of pages in column 2.\n    ----------------------------------------\n    ```\n    \"\"\"\n    if isinstance(path, str):\n        path = Path(path)\n    type_check(path, Path)\n    if path.is_dir():\n        raise TypeError(f\"filename needed: {path}\")\n    if path.suffix != \".tpz\":\n        path = path.parent / (path.parts[-1] + \".tpz\")\n\n    # create yaml document\n    _page_counter = 0\n    d = {}\n    cols = {}\n    for name, col in self.columns.items():\n        type_check(col, Column)\n        cols[name] = {\"pages\": [p.path.name for p in col.pages]}\n        _page_counter += len(col.pages)\n    d[\"columns\"] = cols\n    yml = yaml.safe_dump(\n        d, sort_keys=False, allow_unicode=True, default_flow_style=None\n    )\n\n    _file_counter = 0\n    with zipfile.ZipFile(\n        path, \"w\", compression=compression_method, compresslevel=compression_level\n    ) as f:\n        log.debug(f\"writing .tpz to {path} with\\n{yml}\")\n        f.writestr(\"table.yml\", yml)\n        for name, col in self.columns.items():\n            for page in set(\n                col.pages\n            ):  # set of pages! remember t *= 1000 repeats t 1000x\n                with open(page.path, \"rb\", buffering=0) as raw_io:\n                    f.writestr(page.path.name, raw_io.read())\n                _file_counter += 1\n                log.debug(f\"adding Page {page.path}\")\n\n        _fields = len(self) * len(self.columns)\n        _avg = _fields // _page_counter\n        log.debug(\n            f\"Wrote {_fields:,} on {_page_counter:,} pages in {_file_counter} files: {_avg} fields/page\"\n        )\n
    "},{"location":"reference/core/#tablite.core.Table.load","title":"tablite.core.Table.load(path, tqdm=_tqdm) classmethod","text":"

    loads a table from .tpz file. See also Table.save for details on the file format.

    PARAMETER DESCRIPTION path

    source file

    TYPE: Path

    RETURNS DESCRIPTION Table

    table in read-only mode.

    Source code in tablite/base.py
    @classmethod\ndef load(cls, path, tqdm=_tqdm):  # USER FUNCTION.\n    \"\"\"loads a table from .tpz file.\n    See also Table.save for details on the file format.\n\n    Args:\n        path (Path): source file\n\n    Returns:\n        Table: table in read-only mode.\n    \"\"\"\n    path = Path(path)\n    log.debug(f\"loading {path}\")\n    with zipfile.ZipFile(path, \"r\") as f:\n        yml = f.read(\"table.yml\")\n        metadata = yaml.safe_load(yml)\n        t = cls()\n\n        page_count = sum([len(c[\"pages\"]) for c in metadata[\"columns\"].values()])\n\n        with tqdm(\n            total=page_count,\n            desc=f\"loading '{path.name}' file\",\n            disable=Config.TQDM_DISABLE,\n        ) as pbar:\n            for name, d in metadata[\"columns\"].items():\n                column = Column(t.path)\n                for page in d[\"pages\"]:\n                    bytestream = io.BytesIO(f.read(page))\n                    data = np.load(bytestream, allow_pickle=True, fix_imports=False)\n                    column.extend(data)\n                    pbar.update(1)\n                t.columns[name] = column\n    update_access_time(path)\n    return t\n
    "},{"location":"reference/core/#tablite.core.Table.copy","title":"tablite.core.Table.copy()","text":"Source code in tablite/base.py
    def copy(self):\n    cls = type(self)\n    t = cls()\n    for name, column in self.columns.items():\n        new = Column(t.path)\n        new.pages = column.pages[:]\n        t.columns[name] = new\n    return t\n
    "},{"location":"reference/core/#tablite.core.Table.__imul__","title":"tablite.core.Table.__imul__(other)","text":"

    Repeats instance of table N times.

    Like list: t = t * N

    PARAMETER DESCRIPTION other

    multiplier

    TYPE: int

    Source code in tablite/base.py
    def __imul__(self, other):\n    \"\"\"Repeats instance of table N times.\n\n    Like list: `t = t * N`\n\n    Args:\n        other (int): multiplier\n    \"\"\"\n    if not (isinstance(other, int) and other > 0):\n        raise TypeError(\n            f\"a table can be repeated an integer number of times, not {type(other)} number of times\"\n        )\n    for col in self.columns.values():\n        col *= other\n    return self\n
    "},{"location":"reference/core/#tablite.core.Table.__mul__","title":"tablite.core.Table.__mul__(other)","text":"

    Repeat table N times. Like list: new = old * N

    PARAMETER DESCRIPTION other

    multiplier

    TYPE: int

    RETURNS DESCRIPTION

    Table

    Source code in tablite/base.py
    def __mul__(self, other):\n    \"\"\"Repeat table N times.\n    Like list: `new = old * N`\n\n    Args:\n        other (int): multiplier\n\n    Returns:\n        Table\n    \"\"\"\n    new = self.copy()\n    return new.__imul__(other)\n
    "},{"location":"reference/core/#tablite.core.Table.__iadd__","title":"tablite.core.Table.__iadd__(other)","text":"

    Concatenates tables with same column names.

    Like list: table_1 += table_2

    RAISES DESCRIPTION ValueError

    If column names don't match.

    RETURNS DESCRIPTION None

    self is updated.

    Source code in tablite/base.py
    def __iadd__(self, other):\n    \"\"\"Concatenates tables with same column names.\n\n    Like list: `table_1 += table_2`\n\n    Args:\n        other (Table)\n\n    Raises:\n        ValueError: If column names don't match.\n\n    Returns:\n        None: self is updated.\n    \"\"\"\n    type_check(other, BaseTable)\n    for name in self.columns.keys():\n        if name not in other.columns:\n            raise ValueError(f\"{name} not in other\")\n    for name in other.columns.keys():\n        if name not in self.columns:\n            raise ValueError(f\"{name} missing from self\")\n\n    for name, column in self.columns.items():\n        other_col = other.columns.get(name, None)\n        column.pages.extend(other_col.pages[:])\n    return self\n
    "},{"location":"reference/core/#tablite.core.Table.__add__","title":"tablite.core.Table.__add__(other)","text":"

    Concatenates tables with same column names.

    Like list: table_3 = table_1 + table_2

    RAISES DESCRIPTION ValueError

    If column names don't match.

    RETURNS DESCRIPTION

    Table

    Source code in tablite/base.py
    def __add__(self, other):\n    \"\"\"Concatenates tables with same column names.\n\n    Like list: `table_3 = table_1 + table_2`\n\n    Args:\n        other (Table)\n\n    Raises:\n        ValueError: If column names don't match.\n\n    Returns:\n        Table\n    \"\"\"\n    type_check(other, BaseTable)\n    cp = self.copy()\n    cp += other\n    return cp\n
    "},{"location":"reference/core/#tablite.core.Table.add_rows","title":"tablite.core.Table.add_rows(*args, **kwargs)","text":"

    its more efficient to add many rows at once.

    if both args and kwargs, then args are added first, followed by kwargs.

    supported cases:

    >>> t = Table()\n>>> t.add_columns('row','A','B','C')\n>>> t.add_rows(1, 1, 2, 3)                              # (1) individual values as args\n>>> t.add_rows([2, 1, 2, 3])                            # (2) list of values as args\n>>> t.add_rows((3, 1, 2, 3))                            # (3) tuple of values as args\n>>> t.add_rows(*(4, 1, 2, 3))                           # (4) unpacked tuple becomes arg like (1)\n>>> t.add_rows(row=5, A=1, B=2, C=3)                    # (5) kwargs\n>>> t.add_rows(**{'row': 6, 'A': 1, 'B': 2, 'C': 3})    # (6) dict / json interpreted a kwargs\n>>> t.add_rows((7, 1, 2, 3), (8, 4, 5, 6))              # (7) two (or more) tuples as args\n>>> t.add_rows([9, 1, 2, 3], [10, 4, 5, 6])             # (8) two or more lists as rgs\n>>> t.add_rows(\n    {'row': 11, 'A': 1, 'B': 2, 'C': 3},\n    {'row': 12, 'A': 4, 'B': 5, 'C': 6}\n    )                                                   # (9) two (or more) dicts as args - roughly comma sep'd json.\n>>> t.add_rows( *[\n    {'row': 13, 'A': 1, 'B': 2, 'C': 3},\n    {'row': 14, 'A': 1, 'B': 2, 'C': 3}\n    ])                                                  # (10) list of dicts as args\n>>> t.add_rows(row=[15,16], A=[1,1], B=[2,2], C=[3,3])  # (11) kwargs with lists as values\n
    Source code in tablite/base.py
    def add_rows(self, *args, **kwargs):\n    \"\"\"its more efficient to add many rows at once.\n\n    if both args and kwargs, then args are added first, followed by kwargs.\n\n    supported cases:\n    ```\n    >>> t = Table()\n    >>> t.add_columns('row','A','B','C')\n    >>> t.add_rows(1, 1, 2, 3)                              # (1) individual values as args\n    >>> t.add_rows([2, 1, 2, 3])                            # (2) list of values as args\n    >>> t.add_rows((3, 1, 2, 3))                            # (3) tuple of values as args\n    >>> t.add_rows(*(4, 1, 2, 3))                           # (4) unpacked tuple becomes arg like (1)\n    >>> t.add_rows(row=5, A=1, B=2, C=3)                    # (5) kwargs\n    >>> t.add_rows(**{'row': 6, 'A': 1, 'B': 2, 'C': 3})    # (6) dict / json interpreted a kwargs\n    >>> t.add_rows((7, 1, 2, 3), (8, 4, 5, 6))              # (7) two (or more) tuples as args\n    >>> t.add_rows([9, 1, 2, 3], [10, 4, 5, 6])             # (8) two or more lists as rgs\n    >>> t.add_rows(\n        {'row': 11, 'A': 1, 'B': 2, 'C': 3},\n        {'row': 12, 'A': 4, 'B': 5, 'C': 6}\n        )                                                   # (9) two (or more) dicts as args - roughly comma sep'd json.\n    >>> t.add_rows( *[\n        {'row': 13, 'A': 1, 'B': 2, 'C': 3},\n        {'row': 14, 'A': 1, 'B': 2, 'C': 3}\n        ])                                                  # (10) list of dicts as args\n    >>> t.add_rows(row=[15,16], A=[1,1], B=[2,2], C=[3,3])  # (11) kwargs with lists as values\n    ```\n\n    \"\"\"\n    if not BaseTable._add_row_slow_warning:\n        warnings.warn(\n            \"add_rows is slow. Consider using add_columns and then assigning values to the columns directly.\"\n        )\n        BaseTable._add_row_slow_warning = True\n\n    if args:\n        if not all(isinstance(i, (list, tuple, dict)) for i in args):  # 1,4\n            args = [args]\n\n        if all(isinstance(i, (list, tuple, dict)) for i in args):  # 2,3,7,8\n            # 1. turn the data into columns:\n\n            d = {n: [] for n in self.columns}\n            for arg in args:\n                if len(arg) != len(self.columns):\n                    raise ValueError(\n                        f\"len({arg})== {len(arg)}, but there are {len(self.columns)} columns\"\n                    )\n\n                if isinstance(arg, dict):\n                    for k, v in arg.items():  # 7,8\n                        d[k].append(v)\n\n                elif isinstance(arg, (list, tuple)):  # 2,3\n                    for n, v in zip(self.columns, arg):\n                        d[n].append(v)\n\n                else:\n                    raise TypeError(f\"{arg}?\")\n            # 2. extend the columns\n            for n, values in d.items():\n                col = self.columns[n]\n                col.extend(list_to_np_array(values))\n\n    if kwargs:\n        if isinstance(kwargs, dict):\n            if all(isinstance(v, (list, tuple)) for v in kwargs.values()):\n                for k, v in kwargs.items():\n                    col = self.columns[k]\n                    col.extend(list_to_np_array(v))\n            else:\n                for k, v in kwargs.items():\n                    col = self.columns[k]\n                    col.extend(np.array([v]))\n        else:\n            raise ValueError(f\"format not recognised: {kwargs}\")\n\n    return\n
    "},{"location":"reference/core/#tablite.core.Table.add_columns","title":"tablite.core.Table.add_columns(*names)","text":"

    Adds column names to table.

    Source code in tablite/base.py
    def add_columns(self, *names):\n    \"\"\"Adds column names to table.\"\"\"\n    for name in names:\n        self.columns[name] = Column(self.path)\n
    "},{"location":"reference/core/#tablite.core.Table.add_column","title":"tablite.core.Table.add_column(name, data=None)","text":"

    verbose alias for table[name] = data, that checks if name already exists

    PARAMETER DESCRIPTION name

    column name

    TYPE: str

    data

    values. Defaults to None.

    TYPE: list,tuple) DEFAULT: None

    RAISES DESCRIPTION TypeError

    name isn't string

    ValueError

    name already exists

    Source code in tablite/base.py
    def add_column(self, name, data=None):\n    \"\"\"verbose alias for table[name] = data, that checks if name already exists\n\n    Args:\n        name (str): column name\n        data ((list,tuple), optional): values. Defaults to None.\n\n    Raises:\n        TypeError: name isn't string\n        ValueError: name already exists\n    \"\"\"\n    if not isinstance(name, str):\n        raise TypeError(\"expected name as string\")\n    if name in self.columns:\n        raise ValueError(f\"{name} already in {self.columns}\")\n    self.__setitem__(name, data)\n
    "},{"location":"reference/core/#tablite.core.Table.stack","title":"tablite.core.Table.stack(other)","text":"

    returns the joint stack of tables with overlapping column names. Example:

    | Table A|  +  | Table B| = |  Table AB |\n| A| B| C|     | A| B| D|   | A| B| C| -|\n                            | A| B| -| D|\n
    Source code in tablite/base.py
    def stack(self, other):\n    \"\"\"\n    returns the joint stack of tables with overlapping column names.\n    Example:\n    ```\n    | Table A|  +  | Table B| = |  Table AB |\n    | A| B| C|     | A| B| D|   | A| B| C| -|\n                                | A| B| -| D|\n    ```\n    \"\"\"\n    if not isinstance(other, BaseTable):\n        raise TypeError(f\"stack only works for Table, not {type(other)}\")\n\n    cp = self.copy()\n    for name, col2 in other.columns.items():\n        if name not in cp.columns:\n            cp[name] = [None] * len(self)\n        cp[name].pages.extend(col2.pages[:])\n\n    for name in self.columns:\n        if name not in other.columns:\n            if len(cp) > 0:\n                cp[name].extend(np.array([None] * len(other)))\n    return cp\n
    "},{"location":"reference/core/#tablite.core.Table.types","title":"tablite.core.Table.types()","text":"

    returns nested dict of data types in the form: {column name: {python type class: number of instances }, ... }

    example:

    >>> t.types()\n{\n    'A': {<class 'str'>: 7},\n    'B': {<class 'int'>: 7}\n}\n
    Source code in tablite/base.py
    def types(self):\n    \"\"\"\n    returns nested dict of data types in the form:\n    `{column name: {python type class: number of instances }, ... }`\n\n    example:\n    ```\n    >>> t.types()\n    {\n        'A': {<class 'str'>: 7},\n        'B': {<class 'int'>: 7}\n    }\n    ```\n    \"\"\"\n    d = {}\n    for name, col in self.columns.items():\n        assert isinstance(col, Column)\n        d[name] = col.types()\n    return d\n
    "},{"location":"reference/core/#tablite.core.Table.display_dict","title":"tablite.core.Table.display_dict(slice_=None, blanks=None, dtype=False)","text":"

    helper for creating dict for display.

    PARAMETER DESCRIPTION slice_

    python slice. Defaults to None.

    TYPE: slice DEFAULT: None

    blanks

    fill value for None. Defaults to None.

    TYPE: optional DEFAULT: None

    dtype

    Adds datatype to each column. Defaults to False.

    TYPE: bool DEFAULT: False

    RAISES DESCRIPTION TypeError

    slice_ must be None or slice.

    RETURNS DESCRIPTION dict

    from Table.

    Source code in tablite/base.py
    def display_dict(self, slice_=None, blanks=None, dtype=False):\n    \"\"\"helper for creating dict for display.\n\n    Args:\n        slice_ (slice, optional): python slice. Defaults to None.\n        blanks (optional): fill value for `None`. Defaults to None.\n        dtype (bool, optional): Adds datatype to each column. Defaults to False.\n\n    Raises:\n        TypeError: slice_ must be None or slice.\n\n    Returns:\n        dict: from Table.\n    \"\"\"\n    if not self.columns:\n        print(\"Empty Table\")\n        return\n\n    def datatype(col):  # PRIVATE\n        \"\"\"creates label for column datatype.\"\"\"\n        types = col.types()\n        if len(types) == 0:\n            typ = \"empty\"\n        elif len(types) == 1:\n            dt, _ = types.popitem()\n            typ = dt.__name__\n        else:\n            typ = \"mixed\"\n        return typ\n\n    row_count_tags = [\"#\", \"~\", \"*\"]\n    cols = set(self.columns)\n    for n, tag in product(range(1, 6), row_count_tags):\n        if n * tag not in cols:\n            tag = n * tag\n            break\n\n    if not isinstance(slice_, (slice, type(None))):\n        raise TypeError(f\"slice_ must be None or slice, not {type(slice_)}\")\n    if isinstance(slice_, slice):\n        slc = slice_\n    if slice_ is None:\n        if len(self) <= 20:\n            slc = slice(0, 20, 1)\n        else:\n            slc = None\n\n    n = len(self)\n    if slc:  # either we want slc or we want everything.\n        row_no = list(range(*slc.indices(len(self))))\n        data = {tag: [f\"{i:,}\".rjust(2) for i in row_no]}\n        for name, col in self.columns.items():\n            data[name] = list(chain(iter(col), repeat(blanks, times=n - len(col))))[\n                slc\n            ]\n    else:\n        data = {}\n        j = int(math.ceil(math.log10(n)) / 3) + len(str(n))\n        row_no = (\n            [f\"{i:,}\".rjust(j) for i in range(7)]\n            + [\"...\"]\n            + [f\"{i:,}\".rjust(j) for i in range(n - 7, n)]\n        )\n        data = {tag: row_no}\n\n        for name, col in self.columns.items():\n            if len(col) == n:\n                row = col[:7].tolist() + [\"...\"] + col[-7:].tolist()\n            else:\n                empty = [blanks] * 7\n                head = (col[:7].tolist() + empty)[:7]\n                tail = (col[n - 7 :].tolist() + empty)[-7:]\n                row = head + [\"...\"] + tail\n            data[name] = row\n\n    if dtype:\n        for name, values in data.items():\n            if name in self.columns:\n                col = self.columns[name]\n                values.insert(0, datatype(col))\n            else:\n                values.insert(0, \"row\")\n\n    return data\n
    "},{"location":"reference/core/#tablite.core.Table.to_ascii","title":"tablite.core.Table.to_ascii(slice_=None, blanks=None, dtype=False)","text":"

    returns ascii view of table as string.

    PARAMETER DESCRIPTION slice_

    slice to determine table snippet.

    TYPE: slice DEFAULT: None

    blanks

    value for whitespace. Defaults to None.

    TYPE: str DEFAULT: None

    dtype

    adds subheader with datatype for column. Defaults to False.

    TYPE: bool DEFAULT: False

    Source code in tablite/base.py
    def to_ascii(self, slice_=None, blanks=None, dtype=False):\n    \"\"\"returns ascii view of table as string.\n\n    Args:\n        slice_ (slice, optional): slice to determine table snippet.\n        blanks (str, optional): value for whitespace. Defaults to None.\n        dtype (bool, optional): adds subheader with datatype for column. Defaults to False.\n    \"\"\"\n\n    def adjust(v, length):  # PRIVATE FUNCTION\n        \"\"\"whitespace justifies field values based on datatype\"\"\"\n        if v is None:\n            return str(blanks).ljust(length)\n        elif isinstance(v, str):\n            return v.ljust(length)\n        else:\n            return str(v).rjust(length)\n\n    if not self.columns:\n        return str(self)\n\n    d = {}\n    for name, values in self.display_dict(\n        slice_=slice_, blanks=blanks, dtype=dtype\n    ).items():\n        as_text = [str(v) for v in values] + [str(name)]\n        width = max(len(i) for i in as_text)\n        new_name = name.center(width, \" \")\n        if dtype:\n            values[0] = values[0].center(width, \" \")\n        d[new_name] = [adjust(v, width) for v in values]\n\n    rows = dict_to_rows(d)\n    s = []\n    s.append(\"+\" + \"+\".join([\"=\" * len(n) for n in rows[0]]) + \"+\")\n    s.append(\"|\" + \"|\".join(rows[0]) + \"|\")  # column names\n    start = 1\n    if dtype:\n        s.append(\"|\" + \"|\".join(rows[1]) + \"|\")  # datatypes\n        start = 2\n\n    s.append(\"+\" + \"+\".join([\"-\" * len(n) for n in rows[0]]) + \"+\")\n    for row in rows[start:]:\n        s.append(\"|\" + \"|\".join(row) + \"|\")\n    s.append(\"+\" + \"+\".join([\"=\" * len(n) for n in rows[0]]) + \"+\")\n\n    if len(set(len(c) for c in self.columns.values())) != 1:\n        warning = f\"Warning: Columns have different lengths. {blanks} is used as fill value.\"\n        s.append(warning)\n\n    return \"\\n\".join(s)\n
    "},{"location":"reference/core/#tablite.core.Table.show","title":"tablite.core.Table.show(slice_=None, blanks=None, dtype=False)","text":"

    prints ascii view of table.

    PARAMETER DESCRIPTION slice_

    slice to determine table snippet.

    TYPE: slice DEFAULT: None

    blanks

    value for whitespace. Defaults to None.

    TYPE: str DEFAULT: None

    dtype

    adds subheader with datatype for column. Defaults to False.

    TYPE: bool DEFAULT: False

    Source code in tablite/base.py
    def show(self, slice_=None, blanks=None, dtype=False):\n    \"\"\"prints ascii view of table.\n\n    Args:\n        slice_ (slice, optional): slice to determine table snippet.\n        blanks (str, optional): value for whitespace. Defaults to None.\n        dtype (bool, optional): adds subheader with datatype for column. Defaults to False.\n    \"\"\"\n    print(self.to_ascii(slice_=slice_, blanks=blanks, dtype=dtype))\n
    "},{"location":"reference/core/#tablite.core.Table.to_dict","title":"tablite.core.Table.to_dict(columns=None, slice_=None)","text":"

    columns: list of column names. Default is None == all columns. slice_: slice. Default is None == all rows.

    returns: dict with columns as keys and lists of values.

    Example:

    >>> t.show()\n+===+===+===+\n| # | a | b |\n|row|int|int|\n+---+---+---+\n| 0 |  1|  3|\n| 1 |  2|  4|\n+===+===+===+\n>>> t.to_dict()\n{'a':[1,2], 'b':[3,4]}\n
    Source code in tablite/base.py
    def to_dict(self, columns=None, slice_=None):\n    \"\"\"\n    columns: list of column names. Default is None == all columns.\n    slice_: slice. Default is None == all rows.\n\n    returns: dict with columns as keys and lists of values.\n\n    Example:\n    ```\n    >>> t.show()\n    +===+===+===+\n    | # | a | b |\n    |row|int|int|\n    +---+---+---+\n    | 0 |  1|  3|\n    | 1 |  2|  4|\n    +===+===+===+\n    >>> t.to_dict()\n    {'a':[1,2], 'b':[3,4]}\n    ```\n\n    \"\"\"\n    if slice_ is None:\n        slice_ = slice(0, len(self))\n    assert isinstance(slice_, slice)\n\n    if columns is None:\n        columns = list(self.columns.keys())\n    if not isinstance(columns, list):\n        raise TypeError(\"expected columns as list of strings\")\n\n    return {name: list(self.columns[name][slice_]) for name in columns}\n
    "},{"location":"reference/core/#tablite.core.Table.as_json_serializable","title":"tablite.core.Table.as_json_serializable(row_count='row id', start_on=1, columns=None, slice_=None)","text":"

    provides a JSON compatible format of the table.

    PARAMETER DESCRIPTION row_count

    Label for row counts. Defaults to \"row id\".

    TYPE: str DEFAULT: 'row id'

    start_on

    row counts starts by default on 1.

    TYPE: int DEFAULT: 1

    columns

    Column names. Defaults to None which returns all columns.

    TYPE: list of str DEFAULT: None

    slice_

    selector. Defaults to None which returns [:]

    TYPE: slice DEFAULT: None

    RETURNS DESCRIPTION

    JSON serializable dict: All python datatypes have been converted to JSON compliant data.

    Source code in tablite/base.py
    def as_json_serializable(\n    self, row_count=\"row id\", start_on=1, columns=None, slice_=None\n):\n    \"\"\"provides a JSON compatible format of the table.\n\n    Args:\n        row_count (str, optional): Label for row counts. Defaults to \"row id\".\n        start_on (int, optional): row counts starts by default on 1.\n        columns (list of str, optional): Column names.\n            Defaults to None which returns all columns.\n        slice_ (slice, optional): selector. Defaults to None which returns [:]\n\n    Returns:\n        JSON serializable dict: All python datatypes have been converted to JSON compliant data.\n    \"\"\"\n    if slice_ is None:\n        slice_ = slice(0, len(self))\n\n    assert isinstance(slice_, slice)\n    new = {\"columns\": {}, \"total_rows\": len(self)}\n    if row_count is not None:\n        new[\"columns\"][row_count] = [\n            i + start_on for i in range(*slice_.indices(len(self)))\n        ]\n\n    d = self.to_dict(columns, slice_=slice_)\n    for k, data in d.items():\n        new_k = unique_name(\n            k, new[\"columns\"]\n        )  # used to avoid overwriting the `row id` key.\n        new[\"columns\"][new_k] = [\n            DataTypes.to_json(v) for v in data\n        ]  # deal with non-json datatypes.\n    return new\n
    "},{"location":"reference/core/#tablite.core.Table.index","title":"tablite.core.Table.index(*args)","text":"

    param: *args: column names returns multikey index on the columns as d[(key tuple, )] = {index1, index2, ...}

    Examples:

    >>> table6 = Table()\n>>> table6['A'] = ['Alice', 'Bob', 'Bob', 'Ben', 'Charlie', 'Ben','Albert']\n>>> table6['B'] = ['Alison', 'Marley', 'Dylan', 'Affleck', 'Hepburn', 'Barnes', 'Einstein']\n
    >>> table6.index('A')  # single key.\n{('Alice',): [0],\n ('Bob',): [1, 2],\n ('Ben',): [3, 5],\n ('Charlie',): [4],\n ('Albert',): [6]})\n
    >>> table6.index('A', 'B')  # multiple keys.\n{('Alice', 'Alison'): [0],\n ('Bob', 'Marley'): [1],\n ('Bob', 'Dylan'): [2],\n ('Ben', 'Affleck'): [3],\n ('Charlie', 'Hepburn'): [4],\n ('Ben', 'Barnes'): [5],\n ('Albert', 'Einstein'): [6]})\n
    Source code in tablite/base.py
    def index(self, *args):\n    \"\"\"\n    param: *args: column names\n    returns multikey index on the columns as d[(key tuple, )] = {index1, index2, ...}\n\n    Examples:\n        ```\n        >>> table6 = Table()\n        >>> table6['A'] = ['Alice', 'Bob', 'Bob', 'Ben', 'Charlie', 'Ben','Albert']\n        >>> table6['B'] = ['Alison', 'Marley', 'Dylan', 'Affleck', 'Hepburn', 'Barnes', 'Einstein']\n        ```\n\n        ```\n        >>> table6.index('A')  # single key.\n        {('Alice',): [0],\n         ('Bob',): [1, 2],\n         ('Ben',): [3, 5],\n         ('Charlie',): [4],\n         ('Albert',): [6]})\n        ```\n\n        ```\n        >>> table6.index('A', 'B')  # multiple keys.\n        {('Alice', 'Alison'): [0],\n         ('Bob', 'Marley'): [1],\n         ('Bob', 'Dylan'): [2],\n         ('Ben', 'Affleck'): [3],\n         ('Charlie', 'Hepburn'): [4],\n         ('Ben', 'Barnes'): [5],\n         ('Albert', 'Einstein'): [6]})\n        ```\n\n    \"\"\"\n    idx = defaultdict(list)\n    iterators = [iter(self.columns[c]) for c in args]\n    for ix, key in enumerate(zip(*iterators)):\n        key = tuple(numpy_to_python(k) for k in key)\n        idx[key].append(ix)\n    return idx\n
    "},{"location":"reference/core/#tablite.core.Table.unique_index","title":"tablite.core.Table.unique_index(*args, tqdm=_tqdm)","text":"

    generates the index of unique rows given a list of column names

    PARAMETER DESCRIPTION *args

    columns names

    TYPE: any DEFAULT: ()

    tqdm

    Defaults to _tqdm.

    TYPE: tqdm DEFAULT: tqdm

    RETURNS DESCRIPTION

    np.array(int64): indices of unique records.

    Source code in tablite/base.py
    def unique_index(self, *args, tqdm=_tqdm):\n    \"\"\"generates the index of unique rows given a list of column names\n\n    Args:\n        *args (any): columns names\n        tqdm (tqdm, optional): Defaults to _tqdm.\n\n    Returns:\n        np.array(int64): indices of unique records.\n    \"\"\"\n    if not args:\n        raise ValueError(\"*args (column names) is required\")\n    seen = set()\n    unique = set()\n    iterators = [iter(self.columns[c]) for c in args]\n    for ix, key in tqdm(enumerate(zip(*iterators)), disable=Config.TQDM_DISABLE):\n        key_hash = hash(tuple(numpy_to_python(k) for k in key))\n        if key_hash in seen:\n            continue\n        else:\n            seen.add(key_hash)\n            unique.add(ix)\n    return np.array(sorted(unique))\n
    "},{"location":"reference/core/#tablite.core.Table.from_file","title":"tablite.core.Table.from_file(path, columns=None, first_row_has_headers=True, header_row_index=0, encoding=None, start=0, limit=sys.maxsize, sheet=None, guess_datatypes=True, newline='\\n', text_qualifier=None, delimiter=None, strip_leading_and_tailing_whitespace=True, text_escape_openings='', text_escape_closures='', skip_empty: ValidSkipEmpty = 'NONE', tqdm=_tqdm) -> Table classmethod","text":"
        reads path and imports 1 or more tables\n\n    REQUIRED\n    --------\n    path: pathlib.Path or str\n        selection of filereader uses path.suffix.\n        See `filereaders`.\n\n    OPTIONAL\n    --------\n    columns:\n        None: (default) All columns will be imported.\n        List: only column names from list will be imported (if present in file)\n              e.g. ['A', 'B', 'C', 'D']\n\n              datatype is detected using Datatypes.guess(...)\n              You can try it out with:\n              >> from tablite.datatypes import DataTypes\n              >> DataTypes.guess(['001','100'])\n              [1,100]\n\n              if the format cannot be achieved the read type is kept.\n        Excess column names are ignored.\n\n        HINT: To get the head of file use:\n        >>> from tablite.tools import head\n        >>> head = head(path)\n\n    first_row_has_headers: boolean\n        True: (default) first row is used as column names.\n        False: integers are used as column names.\n\n    encoding: str. Defaults to None (autodetect using n bytes).\n        n is declared in filereader_utils as ENCODING_GUESS_BYTES\n\n    start: the first line to be read (default: 0)\n\n    limit: the number of lines to be read from start (default sys.maxint ~ 2**63)\n\n    OPTIONAL FOR EXCEL AND ODS READERS\n    ----------------------------------\n\n    sheet: sheet name to import  (applicable to excel- and ods-reader only)\n        e.g. 'sheet_1'\n        sheets not found excess names are ignored.\n\n    OPTIONAL FOR TEXT READERS\n    -------------------------\n    guess_datatype: bool\n        True: (default) datatypes are guessed using DataTypes.guess(...)\n        False: all data is imported as strings.\n\n    newline: newline character (applicable to text_reader only)\n        str: '\n

    ' (default) or ' '

        text_qualifier: character (applicable to text_reader only)\n        None: No text qualifier is used.\n        str: \" or '\n\n    delimiter: character (applicable to text_reader only)\n        None: file suffix is used to determine field delimiter:\n            .txt: \"|\"\n            .csv: \",\",\n            .ssv: \";\"\n            .tsv: \" \" (tab)\n\n    strip_leading_and_tailing_whitespace: bool:\n        True: default\n\n    text_escape_openings: (applicable to text_reader only)\n        None: default\n        str: list of characters such as ([{\n\n    text_escape_closures: (applicable to text_reader only)\n        None: default\n        str: list of characters such as }])\n
    Source code in tablite/core.py
    @classmethod\ndef from_file(\n    cls,\n    path,\n    columns=None,\n    first_row_has_headers=True,\n    header_row_index=0,\n    encoding=None,\n    start=0,\n    limit=sys.maxsize,\n    sheet=None,\n    guess_datatypes=True,\n    newline=\"\\n\",\n    text_qualifier=None,\n    delimiter=None,\n    strip_leading_and_tailing_whitespace=True,\n    text_escape_openings=\"\",\n    text_escape_closures=\"\",\n    skip_empty: ValidSkipEmpty=\"NONE\",\n    tqdm=_tqdm,\n) -> \"Table\":\n    \"\"\"\n    reads path and imports 1 or more tables\n\n    REQUIRED\n    --------\n    path: pathlib.Path or str\n        selection of filereader uses path.suffix.\n        See `filereaders`.\n\n    OPTIONAL\n    --------\n    columns:\n        None: (default) All columns will be imported.\n        List: only column names from list will be imported (if present in file)\n              e.g. ['A', 'B', 'C', 'D']\n\n              datatype is detected using Datatypes.guess(...)\n              You can try it out with:\n              >> from tablite.datatypes import DataTypes\n              >> DataTypes.guess(['001','100'])\n              [1,100]\n\n              if the format cannot be achieved the read type is kept.\n        Excess column names are ignored.\n\n        HINT: To get the head of file use:\n        >>> from tablite.tools import head\n        >>> head = head(path)\n\n    first_row_has_headers: boolean\n        True: (default) first row is used as column names.\n        False: integers are used as column names.\n\n    encoding: str. Defaults to None (autodetect using n bytes).\n        n is declared in filereader_utils as ENCODING_GUESS_BYTES\n\n    start: the first line to be read (default: 0)\n\n    limit: the number of lines to be read from start (default sys.maxint ~ 2**63)\n\n    OPTIONAL FOR EXCEL AND ODS READERS\n    ----------------------------------\n\n    sheet: sheet name to import  (applicable to excel- and ods-reader only)\n        e.g. 'sheet_1'\n        sheets not found excess names are ignored.\n\n    OPTIONAL FOR TEXT READERS\n    -------------------------\n    guess_datatype: bool\n        True: (default) datatypes are guessed using DataTypes.guess(...)\n        False: all data is imported as strings.\n\n    newline: newline character (applicable to text_reader only)\n        str: '\\n' (default) or '\\r\\n'\n\n    text_qualifier: character (applicable to text_reader only)\n        None: No text qualifier is used.\n        str: \" or '\n\n    delimiter: character (applicable to text_reader only)\n        None: file suffix is used to determine field delimiter:\n            .txt: \"|\"\n            .csv: \",\",\n            .ssv: \";\"\n            .tsv: \"\\t\" (tab)\n\n    strip_leading_and_tailing_whitespace: bool:\n        True: default\n\n    text_escape_openings: (applicable to text_reader only)\n        None: default\n        str: list of characters such as ([{\n\n    text_escape_closures: (applicable to text_reader only)\n        None: default\n        str: list of characters such as }])\n\n    \"\"\"\n    if isinstance(path, str):\n        path = Path(path)\n    type_check(path, Path)\n\n    if not path.exists():\n        raise FileNotFoundError(f\"file not found: {path}\")\n\n    if not isinstance(start, int) or not 0 <= start <= sys.maxsize:\n        raise ValueError(f\"start {start} not in range(0,{sys.maxsize})\")\n\n    if not isinstance(limit, int) or not 0 < limit <= sys.maxsize:\n        raise ValueError(f\"limit {limit} not in range(0,{sys.maxsize})\")\n\n    if not isinstance(first_row_has_headers, bool):\n        raise TypeError(\"first_row_has_headers is not bool\")\n\n    import_as = path.suffix\n    if import_as.startswith(\".\"):\n        import_as = import_as[1:]\n\n    reader = import_utils.file_readers.get(import_as, None)\n    if reader is None:\n        raise ValueError(f\"{import_as} is not in supported format: {import_utils.valid_readers}\")\n\n    additional_configs = {\"tqdm\": tqdm}\n    if reader == import_utils.text_reader:\n        # here we inject tqdm, if tqdm is not provided, use generic iterator\n        # fmt:off\n        config = (path, columns, first_row_has_headers, header_row_index, encoding, start, limit, newline,\n                  guess_datatypes, text_qualifier, strip_leading_and_tailing_whitespace, skip_empty,\n                  delimiter, text_escape_openings, text_escape_closures)\n        # fmt:on\n\n    elif reader == import_utils.from_html:\n        config = (path,)\n    elif reader == import_utils.from_hdf5:\n        config = (path,)\n\n    elif reader == import_utils.excel_reader:\n        # config = path, first_row_has_headers, sheet, columns, start, limit\n        config = (\n            path,\n            first_row_has_headers,\n            header_row_index,\n            sheet,\n            columns,\n            skip_empty,\n            start,\n            limit,\n        )  # if file length changes - re-import.\n\n    if reader == import_utils.ods_reader:\n        # path, first_row_has_headers=True, sheet=None, columns=None, start=0, limit=sys.maxsize,\n        config = (\n            str(path),\n            first_row_has_headers,\n            header_row_index,\n            sheet,\n            columns,\n            skip_empty,\n            start,\n            limit,\n        )  # if file length changes - re-import.\n\n    # At this point the import config seems valid.\n    # Now we check if the file already has been imported.\n\n    # publish the settings\n    return reader(cls, *config, **additional_configs)\n
    "},{"location":"reference/core/#tablite.core.Table.from_pandas","title":"tablite.core.Table.from_pandas(df) classmethod","text":"

    Creates Table using pd.to_dict('list')

    similar to:

    >>> import pandas as pd\n>>> df = pd.DataFrame({'a':[1,2,3], 'b':[4,5,6]})\n>>> df\n    a  b\n    0  1  4\n    1  2  5\n    2  3  6\n>>> df.to_dict('list')\n{'a': [1, 2, 3], 'b': [4, 5, 6]}\n>>> t = Table.from_dict(df.to_dict('list))\n>>> t.show()\n    +===+===+===+\n    | # | a | b |\n    |row|int|int|\n    +---+---+---+\n    | 0 |  1|  4|\n    | 1 |  2|  5|\n    | 2 |  3|  6|\n    +===+===+===+\n
    Source code in tablite/core.py
    @classmethod\ndef from_pandas(cls, df):\n    \"\"\"\n    Creates Table using pd.to_dict('list')\n\n    similar to:\n    ```\n    >>> import pandas as pd\n    >>> df = pd.DataFrame({'a':[1,2,3], 'b':[4,5,6]})\n    >>> df\n        a  b\n        0  1  4\n        1  2  5\n        2  3  6\n    >>> df.to_dict('list')\n    {'a': [1, 2, 3], 'b': [4, 5, 6]}\n    >>> t = Table.from_dict(df.to_dict('list))\n    >>> t.show()\n        +===+===+===+\n        | # | a | b |\n        |row|int|int|\n        +---+---+---+\n        | 0 |  1|  4|\n        | 1 |  2|  5|\n        | 2 |  3|  6|\n        +===+===+===+\n    ```\n    \"\"\"\n    return import_utils.from_pandas(cls, df)\n
    "},{"location":"reference/core/#tablite.core.Table.from_hdf5","title":"tablite.core.Table.from_hdf5(path) classmethod","text":"

    imports an exported hdf5 table.

    Source code in tablite/core.py
    @classmethod\ndef from_hdf5(cls, path):\n    \"\"\"\n    imports an exported hdf5 table.\n    \"\"\"\n    return import_utils.from_hdf5(cls, path)\n
    "},{"location":"reference/core/#tablite.core.Table.from_json","title":"tablite.core.Table.from_json(jsn) classmethod","text":"

    Imports table exported using .to_json

    Source code in tablite/core.py
    @classmethod\ndef from_json(cls, jsn):\n    \"\"\"\n    Imports table exported using .to_json\n    \"\"\"\n    return import_utils.from_json(cls, jsn)\n
    "},{"location":"reference/core/#tablite.core.Table.to_hdf5","title":"tablite.core.Table.to_hdf5(path)","text":"

    creates a copy of the table as hdf5

    Source code in tablite/core.py
    def to_hdf5(self, path):\n    \"\"\"\n    creates a copy of the table as hdf5\n    \"\"\"\n    export_utils.to_hdf5(self, path)\n
    "},{"location":"reference/core/#tablite.core.Table.to_pandas","title":"tablite.core.Table.to_pandas()","text":"

    returns pandas.DataFrame

    Source code in tablite/core.py
    def to_pandas(self):\n    \"\"\"\n    returns pandas.DataFrame\n    \"\"\"\n    return export_utils.to_pandas(self)\n
    "},{"location":"reference/core/#tablite.core.Table.to_sql","title":"tablite.core.Table.to_sql(name)","text":"

    generates ANSI-92 compliant SQL.

    Source code in tablite/core.py
    def to_sql(self, name):\n    \"\"\"\n    generates ANSI-92 compliant SQL.\n    \"\"\"\n    return export_utils.to_sql(self, name)  # remove after update to test suite.\n
    "},{"location":"reference/core/#tablite.core.Table.to_json","title":"tablite.core.Table.to_json()","text":"

    returns JSON

    Source code in tablite/core.py
    def to_json(self):\n    \"\"\"\n    returns JSON\n    \"\"\"\n    return export_utils.to_json(self)\n
    "},{"location":"reference/core/#tablite.core.Table.to_xlsx","title":"tablite.core.Table.to_xlsx(path)","text":"

    exports table to path

    Source code in tablite/core.py
    def to_xlsx(self, path):\n    \"\"\"\n    exports table to path\n    \"\"\"\n    export_utils.path_suffix_check(path, \".xlsx\")\n    export_utils.excel_writer(self, path)\n
    "},{"location":"reference/core/#tablite.core.Table.to_ods","title":"tablite.core.Table.to_ods(path)","text":"

    exports table to path

    Source code in tablite/core.py
    def to_ods(self, path):\n    \"\"\"\n    exports table to path\n    \"\"\"\n    export_utils.path_suffix_check(path, \".ods\")\n    export_utils.excel_writer(self, path)\n
    "},{"location":"reference/core/#tablite.core.Table.to_csv","title":"tablite.core.Table.to_csv(path)","text":"

    exports table to path

    Source code in tablite/core.py
    def to_csv(self, path):\n    \"\"\"\n    exports table to path\n    \"\"\"\n    export_utils.path_suffix_check(path, \".csv\")\n    export_utils.text_writer(self, path)\n
    "},{"location":"reference/core/#tablite.core.Table.to_tsv","title":"tablite.core.Table.to_tsv(path)","text":"

    exports table to path

    Source code in tablite/core.py
    def to_tsv(self, path):\n    \"\"\"\n    exports table to path\n    \"\"\"\n    export_utils.path_suffix_check(path, \".tsv\")\n    export_utils.text_writer(self, path)\n
    "},{"location":"reference/core/#tablite.core.Table.to_text","title":"tablite.core.Table.to_text(path)","text":"

    exports table to path

    Source code in tablite/core.py
    def to_text(self, path):\n    \"\"\"\n    exports table to path\n    \"\"\"\n    export_utils.path_suffix_check(path, \".txt\")\n    export_utils.text_writer(self, path)\n
    "},{"location":"reference/core/#tablite.core.Table.to_html","title":"tablite.core.Table.to_html(path)","text":"

    exports table to path

    Source code in tablite/core.py
    def to_html(self, path):\n    \"\"\"\n    exports table to path\n    \"\"\"\n    export_utils.path_suffix_check(path, \".html\")\n    export_utils.to_html(self, path)\n
    "},{"location":"reference/core/#tablite.core.Table.expression","title":"tablite.core.Table.expression(expression)","text":"

    filters based on an expression, such as:

    \"all((A==B, C!=4, 200<D))\"\n

    which is interpreted using python's compiler to:

    def _f(A,B,C,D):\n    return all((A==B, C!=4, 200<D))\n
    Source code in tablite/core.py
    def expression(self, expression):\n    \"\"\"\n    filters based on an expression, such as:\n\n        \"all((A==B, C!=4, 200<D))\"\n\n    which is interpreted using python's compiler to:\n\n        def _f(A,B,C,D):\n            return all((A==B, C!=4, 200<D))\n    \"\"\"\n    return redux._filter_using_expression(self, expression)\n
    "},{"location":"reference/core/#tablite.core.Table.filter","title":"tablite.core.Table.filter(expressions, filter_type='all', tqdm=_tqdm)","text":"

    enables filtering across columns for multiple criteria.

    expressions:

    str: Expression that can be compiled and executed row by row.\n    exampLe: \"all((A==B and C!=4 and 200<D))\"\n\nlist of dicts: (example):\n\n    L = [\n        {'column1':'A', 'criteria': \"==\", 'column2': 'B'},\n        {'column1':'C', 'criteria': \"!=\", \"value2\": '4'},\n        {'value1': 200, 'criteria': \"<\", column2: 'D' }\n    ]\n\naccepted dictionary keys: 'column1', 'column2', 'criteria', 'value1', 'value2'\n

    filter_type: 'all' or 'any'

    Source code in tablite/core.py
    def filter(self, expressions, filter_type=\"all\", tqdm=_tqdm):\n    \"\"\"\n    enables filtering across columns for multiple criteria.\n\n    expressions:\n\n        str: Expression that can be compiled and executed row by row.\n            exampLe: \"all((A==B and C!=4 and 200<D))\"\n\n        list of dicts: (example):\n\n            L = [\n                {'column1':'A', 'criteria': \"==\", 'column2': 'B'},\n                {'column1':'C', 'criteria': \"!=\", \"value2\": '4'},\n                {'value1': 200, 'criteria': \"<\", column2: 'D' }\n            ]\n\n        accepted dictionary keys: 'column1', 'column2', 'criteria', 'value1', 'value2'\n\n    filter_type: 'all' or 'any'\n    \"\"\"\n    return redux.filter(self, expressions, filter_type, tqdm)\n
    "},{"location":"reference/core/#tablite.core.Table.sort_index","title":"tablite.core.Table.sort_index(sort_mode='excel', tqdm=_tqdm, pbar=None, **kwargs)","text":"

    helper for methods sort and is_sorted

    param: sort_mode: str: \"alphanumeric\", \"unix\", or, \"excel\" (default) param: **kwargs: sort criteria. See Table.sort()

    Source code in tablite/core.py
    def sort_index(self, sort_mode=\"excel\", tqdm=_tqdm, pbar=None, **kwargs):\n    \"\"\"\n    helper for methods `sort` and `is_sorted`\n\n    param: sort_mode: str: \"alphanumeric\", \"unix\", or, \"excel\" (default)\n    param: **kwargs: sort criteria. See Table.sort()\n    \"\"\"\n    return sortation.sort_index(self, sort_mode, tqdm=tqdm, pbar=pbar, **kwargs)\n
    "},{"location":"reference/core/#tablite.core.Table.reindex","title":"tablite.core.Table.reindex(index)","text":"

    index: list of integers that declare sort order.

    Examples:

    Table:  ['a','b','c','d','e','f','g','h']\nindex:  [0,2,4,6]\nresult: ['b','d','f','h']\n\nTable:  ['a','b','c','d','e','f','g','h']\nindex:  [0,2,4,6,1,3,5,7]\nresult: ['a','c','e','g','b','d','f','h']\n
    Source code in tablite/core.py
    def reindex(self, index):\n    \"\"\"\n    index: list of integers that declare sort order.\n\n    Examples:\n\n        Table:  ['a','b','c','d','e','f','g','h']\n        index:  [0,2,4,6]\n        result: ['b','d','f','h']\n\n        Table:  ['a','b','c','d','e','f','g','h']\n        index:  [0,2,4,6,1,3,5,7]\n        result: ['a','c','e','g','b','d','f','h']\n\n    \"\"\"\n    if isinstance(index, list):\n        index = np.array(index)\n    return _reindex.reindex(self, index)\n
    "},{"location":"reference/core/#tablite.core.Table.drop_duplicates","title":"tablite.core.Table.drop_duplicates(*args)","text":"

    removes duplicate rows based on column names

    args: (optional) column_names if no args, all columns are used.

    Source code in tablite/core.py
    def drop_duplicates(self, *args):\n    \"\"\"\n    removes duplicate rows based on column names\n\n    args: (optional) column_names\n    if no args, all columns are used.\n    \"\"\"\n    if not args:\n        args = self.columns\n    index = self.unique_index(*args)\n    return self.reindex(index)\n
    "},{"location":"reference/core/#tablite.core.Table.sort","title":"tablite.core.Table.sort(mapping, sort_mode='excel', tqdm=_tqdm, pbar: _tqdm = None)","text":"

    Perform multi-pass sorting with precedence given order of column names.

    PARAMETER DESCRIPTION mapping

    keys as columns, values as boolean for 'reverse'

    TYPE: dict

    sort_mode

    str: \"alphanumeric\", \"unix\", or, \"excel\"

    DEFAULT: 'excel'

    RETURNS DESCRIPTION None

    Table.sort is sorted inplace

    Examples: Table.sort(mappinp={A':False}) means sort by 'A' in ascending order. Table.sort(mapping={'A':True, 'B':False}) means sort 'A' in descending order, then (2nd priority) sort B in ascending order.

    Source code in tablite/core.py
    def sort(self, mapping, sort_mode=\"excel\", tqdm=_tqdm, pbar: _tqdm = None):\n    \"\"\"Perform multi-pass sorting with precedence given order of column names.\n\n    Args:\n        mapping (dict): keys as columns,\n                        values as boolean for 'reverse'\n        sort_mode: str: \"alphanumeric\", \"unix\", or, \"excel\"\n\n    Returns:\n        None: Table.sort is sorted inplace\n\n    Examples:\n    Table.sort(mappinp={A':False}) means sort by 'A' in ascending order.\n    Table.sort(mapping={'A':True, 'B':False}) means sort 'A' in descending order, then (2nd priority)\n    sort B in ascending order.\n    \"\"\"\n    new = sortation.sort(self, mapping, sort_mode, tqdm=tqdm, pbar=pbar)\n    self.columns = new.columns\n
    "},{"location":"reference/core/#tablite.core.Table.sorted","title":"tablite.core.Table.sorted(mapping, sort_mode='excel', tqdm=_tqdm, pbar: _tqdm = None)","text":"

    See sort. Sorted returns a new table in contrast to \"sort\", which is in-place.

    RETURNS DESCRIPTION

    Table.

    Source code in tablite/core.py
    def sorted(self, mapping, sort_mode=\"excel\", tqdm=_tqdm, pbar: _tqdm = None):\n    \"\"\"See sort.\n    Sorted returns a new table in contrast to \"sort\", which is in-place.\n\n    Returns:\n        Table.\n    \"\"\"\n    return sortation.sort(self, mapping, sort_mode, tqdm=tqdm, pbar=pbar)\n
    "},{"location":"reference/core/#tablite.core.Table.is_sorted","title":"tablite.core.Table.is_sorted(mapping, sort_mode='excel')","text":"

    Performs multi-pass sorting check with precedence given order of column names. **kwargs: optional: sort criteria. See Table.sort() :return bool

    Source code in tablite/core.py
    def is_sorted(self, mapping, sort_mode=\"excel\"):\n    \"\"\"Performs multi-pass sorting check with precedence given order of column names.\n    **kwargs: optional: sort criteria. See Table.sort()\n    :return bool\n    \"\"\"\n    return sortation.is_sorted(self, mapping, sort_mode)\n
    "},{"location":"reference/core/#tablite.core.Table.any","title":"tablite.core.Table.any(**kwargs)","text":"

    returns Table for rows where ANY kwargs match :param kwargs: dictionary with headers and values / boolean callable

    Source code in tablite/core.py
    def any(self, **kwargs):\n    \"\"\"\n    returns Table for rows where ANY kwargs match\n    :param kwargs: dictionary with headers and values / boolean callable\n    \"\"\"\n    return redux.filter_any(self, **kwargs)\n
    "},{"location":"reference/core/#tablite.core.Table.all","title":"tablite.core.Table.all(**kwargs)","text":"

    returns Table for rows where ALL kwargs match :param kwargs: dictionary with headers and values / boolean callable

    Examples:

    t = Table()\nt['a'] = [1,2,3,4]\nt['b'] = [10,20,30,40]\n\ndef f(x):\n    return x == 4\ndef g(x):\n    return x < 20\n\nt2 = t.any( **{\"a\":f, \"b\":g})\nassert [r for r in t2.rows] == [[1, 10], [4, 40]]\n\nt2 = t.any(a=f,b=g)\nassert [r for r in t2.rows] == [[1, 10], [4, 40]]\n\ndef h(x):\n    return x>=2\n\ndef i(x):\n    return x<=30\n\nt2 = t.all(a=h,b=i)\nassert [r for r in t2.rows] == [[2,20], [3, 30]]\n
    Source code in tablite/core.py
    def all(self, **kwargs):\n    \"\"\"\n    returns Table for rows where ALL kwargs match\n    :param kwargs: dictionary with headers and values / boolean callable\n\n    Examples:\n\n        t = Table()\n        t['a'] = [1,2,3,4]\n        t['b'] = [10,20,30,40]\n\n        def f(x):\n            return x == 4\n        def g(x):\n            return x < 20\n\n        t2 = t.any( **{\"a\":f, \"b\":g})\n        assert [r for r in t2.rows] == [[1, 10], [4, 40]]\n\n        t2 = t.any(a=f,b=g)\n        assert [r for r in t2.rows] == [[1, 10], [4, 40]]\n\n        def h(x):\n            return x>=2\n\n        def i(x):\n            return x<=30\n\n        t2 = t.all(a=h,b=i)\n        assert [r for r in t2.rows] == [[2,20], [3, 30]]\n\n\n    \"\"\"\n    return redux.filter_all(self, **kwargs)\n
    "},{"location":"reference/core/#tablite.core.Table.drop","title":"tablite.core.Table.drop(*args)","text":"

    removes all rows where args are present.

    Exmaple:

    t = Table() t['A'] = [1,2,3,None] t['B'] = [None,2,3,4] t2 = t.drop(None) t2'A', t2'B' ([2,3], [2,3])

    Source code in tablite/core.py
    def drop(self, *args):\n    \"\"\"\n    removes all rows where args are present.\n\n    Exmaple:\n    >>> t = Table()\n    >>> t['A'] = [1,2,3,None]\n    >>> t['B'] = [None,2,3,4]\n    >>> t2 = t.drop(None)\n    >>> t2['A'][:], t2['B'][:]\n    ([2,3], [2,3])\n\n    \"\"\"\n    if not args:\n        raise ValueError(\"What to drop? None? np.nan? \")\n    return redux.drop(self, *args)\n
    "},{"location":"reference/core/#tablite.core.Table.replace","title":"tablite.core.Table.replace(mapping, columns=None, tqdm=_tqdm, pbar=None)","text":"

    replaces all mapped keys with values from named columns

    PARAMETER DESCRIPTION mapping

    keys are targets for replacement, values are replacements.

    TYPE: dict

    columns

    target columns. Defaults to None (all columns)

    TYPE: list or str DEFAULT: None

    RAISES DESCRIPTION ValueError

    description

    Source code in tablite/core.py
    def replace(self, mapping, columns=None, tqdm=_tqdm, pbar=None):\n    \"\"\"replaces all mapped keys with values from named columns\n\n    Args:\n        mapping (dict): keys are targets for replacement,\n                        values are replacements.\n        columns (list or str, optional): target columns.\n            Defaults to None (all columns)\n\n    Raises:\n        ValueError: _description_\n    \"\"\"\n    if columns is None:\n        columns = list(self.columns)\n    if not isinstance(columns, list) and columns in self.columns:\n        columns = [columns]\n    type_check(columns, list)\n    for n in columns:\n        if n not in self.columns:\n            raise ValueError(f\"column not found: {n}\")\n\n    if pbar is None:\n        total = len(columns)\n        pbar = tqdm(total=total, desc=\"replace\", disable=Config.TQDM_DISABLE)\n\n    for name in columns:\n        col = self.columns[name]\n        col.replace(mapping)\n        pbar.update(1)\n
    "},{"location":"reference/core/#tablite.core.Table.groupby","title":"tablite.core.Table.groupby(keys, functions, tqdm=_tqdm, pbar=None)","text":"

    keys: column names for grouping. functions: [optional] list of column names and group functions (See GroupyBy class) returns: table

    Example:

    t = Table()\nt.add_column('A', data=[1, 1, 2, 2, 3, 3] * 2)\nt.add_column('B', data=[1, 2, 3, 4, 5, 6] * 2)\nt.add_column('C', data=[6, 5, 4, 3, 2, 1] * 2)\n\nt.show()\n+=====+=====+=====+\n|  A  |  B  |  C  |\n| int | int | int |\n+-----+-----+-----+\n|    1|    1|    6|\n|    1|    2|    5|\n|    2|    3|    4|\n|    2|    4|    3|\n|    3|    5|    2|\n|    3|    6|    1|\n|    1|    1|    6|\n|    1|    2|    5|\n|    2|    3|    4|\n|    2|    4|    3|\n|    3|    5|    2|\n|    3|    6|    1|\n+=====+=====+=====+\n\ng = t.groupby(keys=['A', 'C'], functions=[('B', gb.sum)])\ng.show()\n+===+===+===+======+\n| # | A | C |Sum(B)|\n|row|int|int| int  |\n+---+---+---+------+\n|0  |  1|  6|     2|\n|1  |  1|  5|     4|\n|2  |  2|  4|     6|\n|3  |  2|  3|     8|\n|4  |  3|  2|    10|\n|5  |  3|  1|    12|\n+===+===+===+======+\n

    Cheat sheet:

    list of unique values

    >>> g1 = t.groupby(keys=['A'], functions=[])\n>>> g1['A'][:]\n[1,2,3]\n

    alternatively:

    t['A'].unique() [1,2,3]

    list of unique values, grouped by longest combination.

    >>> g2 = t.groupby(keys=['A', 'B'], functions=[])\n>>> g2['A'][:], g2['B'][:]\n([1,1,2,2,3,3], [1,2,3,4,5,6])\n

    alternatively:

    >>> list(zip(*t.index('A', 'B').keys()))\n[(1,1,2,2,3,3) (1,2,3,4,5,6)]\n

    A key (unique values) and count hereof.

    >>> g3 = t.groupby(keys=['A'], functions=[('A', gb.count)])\n>>> g3['A'][:], g3['Count(A)'][:]\n([1,2,3], [4,4,4])\n

    alternatively:

    >>> t['A'].histogram()\n([1,2,3], [4,4,4])\n

    for more exmaples see: https://github.com/root-11/tablite/blob/master/tests/test_groupby.py

    Source code in tablite/core.py
    def groupby(self, keys, functions, tqdm=_tqdm, pbar=None):\n    \"\"\"\n    keys: column names for grouping.\n    functions: [optional] list of column names and group functions (See GroupyBy class)\n    returns: table\n\n    Example:\n    ```\n    t = Table()\n    t.add_column('A', data=[1, 1, 2, 2, 3, 3] * 2)\n    t.add_column('B', data=[1, 2, 3, 4, 5, 6] * 2)\n    t.add_column('C', data=[6, 5, 4, 3, 2, 1] * 2)\n\n    t.show()\n    +=====+=====+=====+\n    |  A  |  B  |  C  |\n    | int | int | int |\n    +-----+-----+-----+\n    |    1|    1|    6|\n    |    1|    2|    5|\n    |    2|    3|    4|\n    |    2|    4|    3|\n    |    3|    5|    2|\n    |    3|    6|    1|\n    |    1|    1|    6|\n    |    1|    2|    5|\n    |    2|    3|    4|\n    |    2|    4|    3|\n    |    3|    5|    2|\n    |    3|    6|    1|\n    +=====+=====+=====+\n\n    g = t.groupby(keys=['A', 'C'], functions=[('B', gb.sum)])\n    g.show()\n    +===+===+===+======+\n    | # | A | C |Sum(B)|\n    |row|int|int| int  |\n    +---+---+---+------+\n    |0  |  1|  6|     2|\n    |1  |  1|  5|     4|\n    |2  |  2|  4|     6|\n    |3  |  2|  3|     8|\n    |4  |  3|  2|    10|\n    |5  |  3|  1|    12|\n    +===+===+===+======+\n    ```\n    Cheat sheet:\n\n    list of unique values\n    ```\n    >>> g1 = t.groupby(keys=['A'], functions=[])\n    >>> g1['A'][:]\n    [1,2,3]\n    ```\n    alternatively:\n    >>> t['A'].unique()\n    [1,2,3]\n\n    list of unique values, grouped by longest combination.\n    ```\n    >>> g2 = t.groupby(keys=['A', 'B'], functions=[])\n    >>> g2['A'][:], g2['B'][:]\n    ([1,1,2,2,3,3], [1,2,3,4,5,6])\n    ```\n    alternatively:\n    ```\n    >>> list(zip(*t.index('A', 'B').keys()))\n    [(1,1,2,2,3,3) (1,2,3,4,5,6)]\n    ```\n    A key (unique values) and count hereof.\n    ```\n    >>> g3 = t.groupby(keys=['A'], functions=[('A', gb.count)])\n    >>> g3['A'][:], g3['Count(A)'][:]\n    ([1,2,3], [4,4,4])\n    ```\n    alternatively:\n    ```\n    >>> t['A'].histogram()\n    ([1,2,3], [4,4,4])\n    ```\n    for more exmaples see:\n        https://github.com/root-11/tablite/blob/master/tests/test_groupby.py\n\n    \"\"\"\n    return _groupby(self, keys, functions, tqdm)\n
    "},{"location":"reference/core/#tablite.core.Table.pivot","title":"tablite.core.Table.pivot(rows, columns, functions, values_as_rows=True, tqdm=_tqdm, pbar=None)","text":"

    param: rows: column names to keep as rows param: columns: column names to keep as columns param: functions: aggregation functions from the Groupby class as

    example:

    t.show()\n+=====+=====+=====+\n|  A  |  B  |  C  |\n| int | int | int |\n+-----+-----+-----+\n|    1|    1|    6|\n|    1|    2|    5|\n|    2|    3|    4|\n|    2|    4|    3|\n|    3|    5|    2|\n|    3|    6|    1|\n|    1|    1|    6|\n|    1|    2|    5|\n|    2|    3|    4|\n|    2|    4|    3|\n|    3|    5|    2|\n|    3|    6|    1|\n+=====+=====+=====+\n\nt2 = t.pivot(rows=['C'], columns=['A'], functions=[('B', gb.sum)])\nt2.show()\n+===+===+========+=====+=====+=====+\n| # | C |function|(A=1)|(A=2)|(A=3)|\n|row|int|  str   |mixed|mixed|mixed|\n+---+---+--------+-----+-----+-----+\n|0  |  6|Sum(B)  |    2|None |None |\n|1  |  5|Sum(B)  |    4|None |None |\n|2  |  4|Sum(B)  |None |    6|None |\n|3  |  3|Sum(B)  |None |    8|None |\n|4  |  2|Sum(B)  |None |None |   10|\n|5  |  1|Sum(B)  |None |None |   12|\n+===+===+========+=====+=====+=====+\n
    Source code in tablite/core.py
    def pivot(self, rows, columns, functions, values_as_rows=True, tqdm=_tqdm, pbar=None):\n    \"\"\"\n    param: rows: column names to keep as rows\n    param: columns: column names to keep as columns\n    param: functions: aggregation functions from the Groupby class as\n\n    example:\n    ```\n    t.show()\n    +=====+=====+=====+\n    |  A  |  B  |  C  |\n    | int | int | int |\n    +-----+-----+-----+\n    |    1|    1|    6|\n    |    1|    2|    5|\n    |    2|    3|    4|\n    |    2|    4|    3|\n    |    3|    5|    2|\n    |    3|    6|    1|\n    |    1|    1|    6|\n    |    1|    2|    5|\n    |    2|    3|    4|\n    |    2|    4|    3|\n    |    3|    5|    2|\n    |    3|    6|    1|\n    +=====+=====+=====+\n\n    t2 = t.pivot(rows=['C'], columns=['A'], functions=[('B', gb.sum)])\n    t2.show()\n    +===+===+========+=====+=====+=====+\n    | # | C |function|(A=1)|(A=2)|(A=3)|\n    |row|int|  str   |mixed|mixed|mixed|\n    +---+---+--------+-----+-----+-----+\n    |0  |  6|Sum(B)  |    2|None |None |\n    |1  |  5|Sum(B)  |    4|None |None |\n    |2  |  4|Sum(B)  |None |    6|None |\n    |3  |  3|Sum(B)  |None |    8|None |\n    |4  |  2|Sum(B)  |None |None |   10|\n    |5  |  1|Sum(B)  |None |None |   12|\n    +===+===+========+=====+=====+=====+\n    ```\n    \"\"\"\n    return pivots.pivot(self, rows, columns, functions, values_as_rows, tqdm=tqdm, pbar=pbar)\n
    "},{"location":"reference/core/#tablite.core.Table.merge","title":"tablite.core.Table.merge(left, right, new, criteria)","text":"

    takes from LEFT where criteria is True else RIGHT. :param: T: Table :param: criteria: np.array(bool): if True take left column else take right column :param left: (str) column name :param right: (str) column name :param new: (str) new name

    :returns: T

    Example:

    >>> c.show()\n+==+====+====+====+====+\n| #| A  | B  | C  | D  |\n+--+----+----+----+----+\n| 0|   1|  10|   1|  11|\n| 1|   2|  20|   2|  12|\n| 2|   3|None|   3|  13|\n| 3|None|  40|None|None|\n| 4|   5|  50|None|None|\n| 5|None|None|   6|  16|\n| 6|None|None|   7|  17|\n+==+====+====+====+====+\n\n>>> c.merge(\"A\", \"C\", new=\"E\", criteria=[v != None for v in c['A']])\n>>> c.show()\n+==+====+====+====+\n| #| B  | D  | E  |\n+--+----+----+----+\n| 0|  10|  11|   1|\n| 1|  20|  12|   2|\n| 2|None|  13|   3|\n| 3|  40|None|None|\n| 4|  50|None|   5|\n| 5|None|  16|   6|\n| 6|None|  17|   7|\n+==+====+====+====+\n
    Source code in tablite/core.py
    def merge(self, left, right, new, criteria):\n    \"\"\" takes from LEFT where criteria is True else RIGHT.\n    :param: T: Table\n    :param: criteria: np.array(bool): \n            if True take left column\n            else take right column\n    :param left: (str) column name\n    :param right: (str) column name\n    :param new: (str) new name\n\n    :returns: T\n\n    Example:\n    ```\n    >>> c.show()\n    +==+====+====+====+====+\n    | #| A  | B  | C  | D  |\n    +--+----+----+----+----+\n    | 0|   1|  10|   1|  11|\n    | 1|   2|  20|   2|  12|\n    | 2|   3|None|   3|  13|\n    | 3|None|  40|None|None|\n    | 4|   5|  50|None|None|\n    | 5|None|None|   6|  16|\n    | 6|None|None|   7|  17|\n    +==+====+====+====+====+\n\n    >>> c.merge(\"A\", \"C\", new=\"E\", criteria=[v != None for v in c['A']])\n    >>> c.show()\n    +==+====+====+====+\n    | #| B  | D  | E  |\n    +--+----+----+----+\n    | 0|  10|  11|   1|\n    | 1|  20|  12|   2|\n    | 2|None|  13|   3|\n    | 3|  40|None|None|\n    | 4|  50|None|   5|\n    | 5|None|  16|   6|\n    | 6|None|  17|   7|\n    +==+====+====+====+\n    ```\n    \"\"\"\n    return merge.where(self, criteria,left,right,new)\n
    "},{"location":"reference/core/#tablite.core.Table.column_select","title":"tablite.core.Table.column_select(cols: list[ColumnSelectorDict], tqdm=_tqdm, TaskManager=_TaskManager)","text":"

    type-casts columns from a given table to specified type(s)

    cols

    list of dicts: (example):

    cols = [\n    {'column':'A', 'type': 'bool'},\n    {'column':'B', 'type': 'int', 'allow_empty': True},\n    {'column':'B', 'type': 'float', 'allow_empty': False, 'rename': 'C'},\n]\n

    'column' : column name of the input table that we want to type-cast 'type' : type that we want to type-cast the specified column to 'allow_empty': should we allow empty values (None, str('')) through (Default: False) 'rename' : new name of the column, if None will keep the original name, in case of duplicates suffix will be added (Default: None)

    supported types: 'bool', 'int', 'float', 'str', 'date', 'time', 'datetime'

    if any of the columns is rejected, entire row is rejected

    tqdm: progressbar constructor TaskManager: TaskManager constructor

    (TABLE, TABLE) DESCRIPTION

    first table contains the rows that were successfully cast to desired types

    second table contains rows that failed to cast + rejection reason

    Source code in tablite/core.py
    def column_select(self, cols: list[ColumnSelectorDict], tqdm=_tqdm, TaskManager=_TaskManager):\n    \"\"\"\n    type-casts columns from a given table to specified type(s)\n\n    cols:\n        list of dicts: (example):\n\n            cols = [\n                {'column':'A', 'type': 'bool'},\n                {'column':'B', 'type': 'int', 'allow_empty': True},\n                {'column':'B', 'type': 'float', 'allow_empty': False, 'rename': 'C'},\n            ]\n\n        'column'     : column name of the input table that we want to type-cast\n        'type'       : type that we want to type-cast the specified column to\n        'allow_empty': should we allow empty values (None, str('')) through (Default: False)\n        'rename'     : new name of the column, if None will keep the original name, in case of duplicates suffix will be added (Default: None)\n\n        supported types: 'bool', 'int', 'float', 'str', 'date', 'time', 'datetime'\n\n        if any of the columns is rejected, entire row is rejected\n\n    tqdm: progressbar constructor\n    TaskManager: TaskManager constructor\n\n    returns: (Table, Table)\n        first table contains the rows that were successfully cast to desired types\n        second table contains rows that failed to cast + rejection reason\n    \"\"\"\n    return _column_select(self, cols, tqdm, TaskManager)\n
    "},{"location":"reference/core/#tablite.core.Table.join","title":"tablite.core.Table.join(other, left_keys, right_keys, left_columns=None, right_columns=None, kind='inner', merge_keys=False, tqdm=_tqdm, pbar=None)","text":"

    short-cut for all join functions. kind: 'inner', 'left', 'outer', 'cross'

    Source code in tablite/core.py
    def join(self, other, left_keys, right_keys, left_columns=None, right_columns=None, kind=\"inner\", merge_keys=False, tqdm=_tqdm, pbar=None):\n    \"\"\"\n    short-cut for all join functions.\n    kind: 'inner', 'left', 'outer', 'cross'\n    \"\"\"\n    kinds = {\n        \"inner\": self.inner_join,\n        \"left\": self.left_join,\n        \"outer\": self.outer_join,\n        \"cross\": self.cross_join,\n    }\n    if kind not in kinds:\n        raise ValueError(f\"join type unknown: {kind}\")\n    f = kinds.get(kind, None)\n    return f(other, left_keys, right_keys, left_columns, right_columns, merge_keys=merge_keys, tqdm=tqdm, pbar=pbar)\n
    "},{"location":"reference/core/#tablite.core.Table.left_join","title":"tablite.core.Table.left_join(other, left_keys, right_keys, left_columns=None, right_columns=None, merge_keys=False, tqdm=_tqdm, pbar=None)","text":"

    :param other: self, other = (left, right) :param left_keys: list of keys for the join :param right_keys: list of keys for the join :param left_columns: list of left columns to retain, if None, all are retained. :param right_columns: list of right columns to retain, if None, all are retained. :return: new Table Example:

    SQL:   SELECT number, letter FROM numbers LEFT JOIN letters ON numbers.colour == letters.color\nTablite: left_join = numbers.left_join(\n    letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter']\n)\n
    Source code in tablite/core.py
    def left_join(self, other, left_keys, right_keys, left_columns=None, right_columns=None, merge_keys=False, tqdm=_tqdm, pbar=None):\n    \"\"\"\n    :param other: self, other = (left, right)\n    :param left_keys: list of keys for the join\n    :param right_keys: list of keys for the join\n    :param left_columns: list of left columns to retain, if None, all are retained.\n    :param right_columns: list of right columns to retain, if None, all are retained.\n    :return: new Table\n    Example:\n    ```\n    SQL:   SELECT number, letter FROM numbers LEFT JOIN letters ON numbers.colour == letters.color\n    Tablite: left_join = numbers.left_join(\n        letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter']\n    )\n    ```\n    \"\"\"\n    return joins.left_join(self, other, left_keys, right_keys, left_columns, right_columns, merge_keys=merge_keys, tqdm=tqdm, pbar=pbar)\n
    "},{"location":"reference/core/#tablite.core.Table.inner_join","title":"tablite.core.Table.inner_join(other, left_keys, right_keys, left_columns=None, right_columns=None, merge_keys=False, tqdm=_tqdm, pbar=None)","text":"

    :param other: self, other = (left, right) :param left_keys: list of keys for the join :param right_keys: list of keys for the join :param left_columns: list of left columns to retain, if None, all are retained. :param right_columns: list of right columns to retain, if None, all are retained. :return: new Table Example:

    SQL:   SELECT number, letter FROM numbers JOIN letters ON numbers.colour == letters.color\nTablite: inner_join = numbers.inner_join(\n    letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter']\n    )\n
    Source code in tablite/core.py
    def inner_join(self, other, left_keys, right_keys, left_columns=None, right_columns=None, merge_keys=False, tqdm=_tqdm, pbar=None):\n    \"\"\"\n    :param other: self, other = (left, right)\n    :param left_keys: list of keys for the join\n    :param right_keys: list of keys for the join\n    :param left_columns: list of left columns to retain, if None, all are retained.\n    :param right_columns: list of right columns to retain, if None, all are retained.\n    :return: new Table\n    Example:\n    ```\n    SQL:   SELECT number, letter FROM numbers JOIN letters ON numbers.colour == letters.color\n    Tablite: inner_join = numbers.inner_join(\n        letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter']\n        )\n    ```\n    \"\"\"\n    return joins.inner_join(self, other, left_keys, right_keys, left_columns, right_columns, merge_keys=merge_keys, tqdm=tqdm, pbar=pbar)\n
    "},{"location":"reference/core/#tablite.core.Table.outer_join","title":"tablite.core.Table.outer_join(other, left_keys, right_keys, left_columns=None, right_columns=None, merge_keys=False, tqdm=_tqdm, pbar=None)","text":"

    :param other: self, other = (left, right) :param left_keys: list of keys for the join :param right_keys: list of keys for the join :param left_columns: list of left columns to retain, if None, all are retained. :param right_columns: list of right columns to retain, if None, all are retained. :return: new Table Example:

    SQL:   SELECT number, letter FROM numbers OUTER JOIN letters ON numbers.colour == letters.color\nTablite: outer_join = numbers.outer_join(\n    letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter']\n    )\n
    Source code in tablite/core.py
    def outer_join(self, other, left_keys, right_keys, left_columns=None, right_columns=None, merge_keys=False, tqdm=_tqdm, pbar=None):\n    \"\"\"\n    :param other: self, other = (left, right)\n    :param left_keys: list of keys for the join\n    :param right_keys: list of keys for the join\n    :param left_columns: list of left columns to retain, if None, all are retained.\n    :param right_columns: list of right columns to retain, if None, all are retained.\n    :return: new Table\n    Example:\n    ```\n    SQL:   SELECT number, letter FROM numbers OUTER JOIN letters ON numbers.colour == letters.color\n    Tablite: outer_join = numbers.outer_join(\n        letters, left_keys=['colour'], right_keys=['color'], left_columns=['number'], right_columns=['letter']\n        )\n    ```\n    \"\"\"\n    return joins.outer_join(self, other, left_keys, right_keys, left_columns, right_columns, merge_keys=merge_keys, tqdm=tqdm, pbar=pbar)\n
    "},{"location":"reference/core/#tablite.core.Table.cross_join","title":"tablite.core.Table.cross_join(other, left_keys, right_keys, left_columns=None, right_columns=None, merge_keys=False, tqdm=_tqdm, pbar=None)","text":"

    CROSS JOIN returns the Cartesian product of rows from tables in the join. In other words, it will produce rows which combine each row from the first table with each row from the second table

    Source code in tablite/core.py
    def cross_join(self, other, left_keys, right_keys, left_columns=None, right_columns=None, merge_keys=False, tqdm=_tqdm, pbar=None):\n    \"\"\"\n    CROSS JOIN returns the Cartesian product of rows from tables in the join.\n    In other words, it will produce rows which combine each row from the first table\n    with each row from the second table\n    \"\"\"\n    return joins.cross_join(self, other, left_keys, right_keys, left_columns, right_columns, merge_keys=merge_keys, tqdm=tqdm, pbar=pbar)\n
    "},{"location":"reference/core/#tablite.core.Table.lookup","title":"tablite.core.Table.lookup(other, *criteria, all=True, tqdm=_tqdm)","text":"

    function for looking up values in other according to criteria in ascending order. :param: other: Table sorted in ascending search order. :param: criteria: Each criteria must be a tuple with value comparisons in the form: (LEFT, OPERATOR, RIGHT) :param: all: boolean: True=ALL, False=Any

    OPERATOR must be a callable that returns a boolean LEFT must be a value that the OPERATOR can compare. RIGHT must be a value that the OPERATOR can compare.

    Examples:

    ('column A', \"==\", 'column B')  # comparison of two columns\n('Date', \"<\", DataTypes.date(24,12) )  # value from column 'Date' is before 24/12.\nf = lambda L,R: all( ord(L) < ord(R) )  # uses custom function.\n('text 1', f, 'text 2') value from column 'text 1' is compared with value from column 'text 2'\n
    Source code in tablite/core.py
    def lookup(self, other, *criteria, all=True, tqdm=_tqdm):\n    \"\"\"function for looking up values in `other` according to criteria in ascending order.\n    :param: other: Table sorted in ascending search order.\n    :param: criteria: Each criteria must be a tuple with value comparisons in the form:\n        (LEFT, OPERATOR, RIGHT)\n    :param: all: boolean: True=ALL, False=Any\n\n    OPERATOR must be a callable that returns a boolean\n    LEFT must be a value that the OPERATOR can compare.\n    RIGHT must be a value that the OPERATOR can compare.\n\n    Examples:\n    ```\n    ('column A', \"==\", 'column B')  # comparison of two columns\n    ('Date', \"<\", DataTypes.date(24,12) )  # value from column 'Date' is before 24/12.\n    f = lambda L,R: all( ord(L) < ord(R) )  # uses custom function.\n    ('text 1', f, 'text 2') value from column 'text 1' is compared with value from column 'text 2'\n    ```\n    \"\"\"\n    return lookup.lookup(self, other, *criteria, all=all, tqdm=tqdm)\n
    "},{"location":"reference/core/#tablite.core.Table.match","title":"tablite.core.Table.match(other, *criteria, keep_left=None, keep_right=None)","text":"

    performs inner join where T matches other and removes rows that do not match.

    :param: T: Table :param: other: Table :param: criteria: Each criteria must be a tuple with value comparisons in the form:

    (LEFT, OPERATOR, RIGHT), where operator must be \"==\"\n\nExample:\n    ('column A', \"==\", 'column B')\n\nThis syntax follows the lookup syntax. See Lookup for details.\n

    :param: keep_left: list of columns to keep. :param: keep_right: list of right columns to keep.

    Source code in tablite/core.py
    def match(self, other, *criteria, keep_left=None, keep_right=None):\n    \"\"\"\n    performs inner join where `T` matches `other` and removes rows that do not match.\n\n    :param: T: Table\n    :param: other: Table\n    :param: criteria: Each criteria must be a tuple with value comparisons in the form:\n\n        (LEFT, OPERATOR, RIGHT), where operator must be \"==\"\n\n        Example:\n            ('column A', \"==\", 'column B')\n\n        This syntax follows the lookup syntax. See Lookup for details.\n\n    :param: keep_left: list of columns to keep.\n    :param: keep_right: list of right columns to keep.\n    \"\"\"\n    return match.match(self, other, *criteria, keep_left=keep_left, keep_right=keep_right)\n
    "},{"location":"reference/core/#tablite.core.Table.replace_missing_values","title":"tablite.core.Table.replace_missing_values(*args, **kwargs)","text":"Source code in tablite/core.py
    def replace_missing_values(self, *args, **kwargs):\n    raise AttributeError(\"See imputation\")\n
    "},{"location":"reference/core/#tablite.core.Table.imputation","title":"tablite.core.Table.imputation(targets, missing=None, method='carry forward', sources=None, tqdm=_tqdm)","text":"

    In statistics, imputation is the process of replacing missing data with substituted values.

    See more: https://en.wikipedia.org/wiki/Imputation_(statistics)

    PARAMETER DESCRIPTION table

    source table.

    TYPE: Table

    targets

    column names to find and replace missing values

    TYPE: str or list of strings

    missing

    values to be replaced.

    TYPE: None or iterable DEFAULT: None

    method

    method to be used for replacement. Options:

    'carry forward': takes the previous value, and carries forward into fields where values are missing. +: quick. Realistic on time series. -: Can produce strange outliers.

    'mean': calculates the column mean (exclude missing) and copies the mean in as replacement. +: quick -: doesn't work on text. Causes data set to drift towards the mean.

    'mode': calculates the column mode (exclude missing) and copies the mean in as replacement. +: quick -: most frequent value becomes over-represented in the sample

    'nearest neighbour': calculates normalised distance between items in source columns selects nearest neighbour and copies value as replacement. +: works for any datatype. -: computationally intensive (e.g. slow)

    TYPE: str DEFAULT: 'carry forward'

    sources

    NEAREST NEIGHBOUR ONLY column names to be used during imputation. if None or empty, all columns will be used.

    TYPE: list of strings DEFAULT: None

    RETURNS DESCRIPTION table

    table with replaced values.

    Source code in tablite/core.py
    def imputation(self, targets, missing=None, method=\"carry forward\", sources=None, tqdm=_tqdm):\n    \"\"\"\n    In statistics, imputation is the process of replacing missing data with substituted values.\n\n    See more: https://en.wikipedia.org/wiki/Imputation_(statistics)\n\n    Args:\n        table (Table): source table.\n\n        targets (str or list of strings): column names to find and\n            replace missing values\n\n        missing (None or iterable): values to be replaced.\n\n        method (str): method to be used for replacement. Options:\n\n            'carry forward':\n                takes the previous value, and carries forward into fields\n                where values are missing.\n                +: quick. Realistic on time series.\n                -: Can produce strange outliers.\n\n            'mean':\n                calculates the column mean (exclude `missing`) and copies\n                the mean in as replacement.\n                +: quick\n                -: doesn't work on text. Causes data set to drift towards the mean.\n\n            'mode':\n                calculates the column mode (exclude `missing`) and copies\n                the mean in as replacement.\n                +: quick\n                -: most frequent value becomes over-represented in the sample\n\n            'nearest neighbour':\n                calculates normalised distance between items in source columns\n                selects nearest neighbour and copies value as replacement.\n                +: works for any datatype.\n                -: computationally intensive (e.g. slow)\n\n        sources (list of strings): NEAREST NEIGHBOUR ONLY\n            column names to be used during imputation.\n            if None or empty, all columns will be used.\n\n    Returns:\n        table: table with replaced values.\n    \"\"\"\n    return imputation.imputation(self, targets, missing, method, sources, tqdm=tqdm)\n
    "},{"location":"reference/core/#tablite.core.Table.transpose","title":"tablite.core.Table.transpose(tqdm=_tqdm)","text":"Source code in tablite/core.py
    def transpose(self, tqdm=_tqdm):\n    return pivots.transpose(self, tqdm)\n
    "},{"location":"reference/core/#tablite.core.Table.pivot_transpose","title":"tablite.core.Table.pivot_transpose(columns, keep=None, column_name='transpose', value_name='value', tqdm=_tqdm)","text":"

    Transpose a selection of columns to rows.

    PARAMETER DESCRIPTION columns

    column names to transpose

    TYPE: list of column names

    keep

    column names to keep (repeat)

    TYPE: list of column names DEFAULT: None

    RETURNS DESCRIPTION Table

    with columns transposed to rows

    Example

    transpose columns 1,2 and 3 and transpose the remaining columns, except sum.

    Input:

    | col1 | col2 | col3 | sun | mon | tue | ... | sat | sum  |\n|------|------|------|-----|-----|-----|-----|-----|------|\n| 1234 | 2345 | 3456 | 456 | 567 |     | ... |     | 1023 |\n| 1244 | 2445 | 4456 |     |   7 |     | ... |     |    7 |\n| ...  |      |      |     |     |     |     |     |      |\n\nt.transpose(keep=[col1, col2, col3], transpose=[sun,mon,tue,wed,thu,fri,sat])`\n\nOutput:\n\n|col1| col2| col3| transpose| value|\n|----|-----|-----|----------|------|\n|1234| 2345| 3456| sun      |   456|\n|1234| 2345| 3456| mon      |   567|\n|1244| 2445| 4456| mon      |     7|\n
    Source code in tablite/core.py
    def pivot_transpose(self, columns, keep=None, column_name=\"transpose\", value_name=\"value\", tqdm=_tqdm):\n    \"\"\"Transpose a selection of columns to rows.\n\n    Args:\n        columns (list of column names): column names to transpose\n        keep (list of column names): column names to keep (repeat)\n\n    Returns:\n        Table: with columns transposed to rows\n\n    Example:\n        transpose columns 1,2 and 3 and transpose the remaining columns, except `sum`.\n\n    Input:\n    ```\n    | col1 | col2 | col3 | sun | mon | tue | ... | sat | sum  |\n    |------|------|------|-----|-----|-----|-----|-----|------|\n    | 1234 | 2345 | 3456 | 456 | 567 |     | ... |     | 1023 |\n    | 1244 | 2445 | 4456 |     |   7 |     | ... |     |    7 |\n    | ...  |      |      |     |     |     |     |     |      |\n\n    t.transpose(keep=[col1, col2, col3], transpose=[sun,mon,tue,wed,thu,fri,sat])`\n\n    Output:\n\n    |col1| col2| col3| transpose| value|\n    |----|-----|-----|----------|------|\n    |1234| 2345| 3456| sun      |   456|\n    |1234| 2345| 3456| mon      |   567|\n    |1244| 2445| 4456| mon      |     7|\n    ```\n    \"\"\"\n    return pivots.pivot_transpose(self, columns, keep, column_name, value_name, tqdm=tqdm)\n
    "},{"location":"reference/core/#tablite.core.Table.diff","title":"tablite.core.Table.diff(other, columns=None)","text":"

    compares table self with table other

    PARAMETER DESCRIPTION self

    Table

    TYPE: Table

    other

    Table

    TYPE: Table

    columns

    list of column names to include in comparison. Defaults to None.

    TYPE: List DEFAULT: None

    RETURNS DESCRIPTION Table

    diff of self and other with diff in columns 1st and 2nd.

    Source code in tablite/core.py
    def diff(self, other, columns=None):\n    \"\"\"compares table self with table other\n\n    Args:\n        self (Table): Table\n        other (Table): Table\n        columns (List, optional): list of column names to include in comparison. Defaults to None.\n\n    Returns:\n        Table: diff of self and other with diff in columns 1st and 2nd.\n    \"\"\"\n    return diff.diff(self, other, columns)\n
    "},{"location":"reference/core/#tablite.core-functions","title":"Functions","text":""},{"location":"reference/core/#tablite.core-modules","title":"Modules","text":""},{"location":"reference/datasets/","title":"Datasets","text":""},{"location":"reference/datasets/#tablite.datasets","title":"tablite.datasets","text":""},{"location":"reference/datasets/#tablite.datasets-classes","title":"Classes","text":""},{"location":"reference/datasets/#tablite.datasets-functions","title":"Functions","text":""},{"location":"reference/datasets/#tablite.datasets.synthetic_order_data","title":"tablite.datasets.synthetic_order_data(rows=100000)","text":"

    Creates a synthetic dataset for testing that looks like this: (depending on number of rows)

    +=========+=======+=============+===================+=====+===+=====+====+===+=====+=====+===================+==================+\n|    ~    |   #   |      1      |         2         |  3  | 4 |  5  | 6  | 7 |  8  |  9  |         10        |        11        |\n|   row   |  int  |     int     |      datetime     | int |int| int |str |str|mixed|mixed|       float       |      float       |\n+---------+-------+-------------+-------------------+-----+---+-----+----+---+-----+-----+-------------------+------------------+\n|0        |      1|1478158906743|2021-10-27 00:00:00|50764|  1|29990|C4-5|APP|21\u00b0  |None | 2.0434376837650046|1.3371665497020444|\n|1        |      2|2271295805011|2021-09-13 00:00:00|50141|  0|10212|C4-5|TAE|None |None |  1.010318612835485| 20.94821610676901|\n|2        |      3|1598726492913|2021-08-19 00:00:00|50527|  0|19416|C3-5|QPV|21\u00b0  |None |  1.463459515469516|  17.4133659842749|\n|3        |      4|1413615572689|2021-11-05 00:00:00|50181|  1|18637|C4-2|GCL|6\u00b0   |ABC  |  2.084002469706324| 0.489481411683505|\n|4        |      5| 245266998048|2021-09-25 00:00:00|50378|  0|29756|C5-4|LGY|6\u00b0   |XYZ  | 0.5141579343276079| 8.550780816571438|\n|5        |      6| 947994853644|2021-10-14 00:00:00|50511|  0| 7890|C2-4|BET|0\u00b0   |XYZ  | 1.1725893606177542| 7.447314130260951|\n|6        |      7|2230693047809|2021-10-07 00:00:00|50987|  1|26742|C1-3|CFP|0\u00b0   |XYZ  | 1.0921267279498004|11.009210185311993|\n|...      |...    |...          |...                |...  |...|...  |... |...|...  |...  |...                |...               |\n|7,999,993|7999994|2047223556745|2021-09-03 00:00:00|50883|  1|15687|C3-1|RFR|None |XYZ  | 1.3467185981566827|17.023443485654845|\n|7,999,994|7999995|1814140654790|2021-08-02 00:00:00|50152|  0|16556|C4-2|WTC|None |ABC  | 1.1517593924478968| 8.201818634721487|\n|7,999,995|7999996| 155308171103|2021-10-14 00:00:00|50008|  1|14590|C1-3|WYM|0\u00b0   |None | 2.1273836233717978|23.295943554889195|\n|7,999,996|7999997|1620451532911|2021-12-12 00:00:00|50173|  1|20744|C2-1|ZYO|6\u00b0   |ABC  |  2.482509134693724| 22.25375464857266|\n|7,999,997|7999998|1248987682094|2021-12-20 00:00:00|50052|  1|28298|C5-4|XAW|None |XYZ  |0.17923757926558143|23.728160892974252|\n|7,999,998|7999999|1382206732187|2021-11-13 00:00:00|50993|  1|24832|C5-2|UDL|None |ABC  |0.08425329763360942|12.707735293126758|\n|7,999,999|8000000| 600688069780|2021-09-28 00:00:00|50510|  0|15819|C3-4|IGY|None |ABC  |  1.066241687256579|13.862069804070295|\n+=========+=======+=============+===================+=====+===+=====+====+===+=====+=====+===================+==================+\n
    PARAMETER DESCRIPTION rows

    number of rows wanted. Defaults to 100_000.

    TYPE: int DEFAULT: 100000

    RETURNS DESCRIPTION Table

    Populated table.

    TYPE: Table

    Source code in tablite/datasets.py
    def synthetic_order_data(rows=100_000):\n    \"\"\"Creates a synthetic dataset for testing that looks like this:\n    (depending on number of rows)\n\n    ```\n    +=========+=======+=============+===================+=====+===+=====+====+===+=====+=====+===================+==================+\n    |    ~    |   #   |      1      |         2         |  3  | 4 |  5  | 6  | 7 |  8  |  9  |         10        |        11        |\n    |   row   |  int  |     int     |      datetime     | int |int| int |str |str|mixed|mixed|       float       |      float       |\n    +---------+-------+-------------+-------------------+-----+---+-----+----+---+-----+-----+-------------------+------------------+\n    |0        |      1|1478158906743|2021-10-27 00:00:00|50764|  1|29990|C4-5|APP|21\u00b0  |None | 2.0434376837650046|1.3371665497020444|\n    |1        |      2|2271295805011|2021-09-13 00:00:00|50141|  0|10212|C4-5|TAE|None |None |  1.010318612835485| 20.94821610676901|\n    |2        |      3|1598726492913|2021-08-19 00:00:00|50527|  0|19416|C3-5|QPV|21\u00b0  |None |  1.463459515469516|  17.4133659842749|\n    |3        |      4|1413615572689|2021-11-05 00:00:00|50181|  1|18637|C4-2|GCL|6\u00b0   |ABC  |  2.084002469706324| 0.489481411683505|\n    |4        |      5| 245266998048|2021-09-25 00:00:00|50378|  0|29756|C5-4|LGY|6\u00b0   |XYZ  | 0.5141579343276079| 8.550780816571438|\n    |5        |      6| 947994853644|2021-10-14 00:00:00|50511|  0| 7890|C2-4|BET|0\u00b0   |XYZ  | 1.1725893606177542| 7.447314130260951|\n    |6        |      7|2230693047809|2021-10-07 00:00:00|50987|  1|26742|C1-3|CFP|0\u00b0   |XYZ  | 1.0921267279498004|11.009210185311993|\n    |...      |...    |...          |...                |...  |...|...  |... |...|...  |...  |...                |...               |\n    |7,999,993|7999994|2047223556745|2021-09-03 00:00:00|50883|  1|15687|C3-1|RFR|None |XYZ  | 1.3467185981566827|17.023443485654845|\n    |7,999,994|7999995|1814140654790|2021-08-02 00:00:00|50152|  0|16556|C4-2|WTC|None |ABC  | 1.1517593924478968| 8.201818634721487|\n    |7,999,995|7999996| 155308171103|2021-10-14 00:00:00|50008|  1|14590|C1-3|WYM|0\u00b0   |None | 2.1273836233717978|23.295943554889195|\n    |7,999,996|7999997|1620451532911|2021-12-12 00:00:00|50173|  1|20744|C2-1|ZYO|6\u00b0   |ABC  |  2.482509134693724| 22.25375464857266|\n    |7,999,997|7999998|1248987682094|2021-12-20 00:00:00|50052|  1|28298|C5-4|XAW|None |XYZ  |0.17923757926558143|23.728160892974252|\n    |7,999,998|7999999|1382206732187|2021-11-13 00:00:00|50993|  1|24832|C5-2|UDL|None |ABC  |0.08425329763360942|12.707735293126758|\n    |7,999,999|8000000| 600688069780|2021-09-28 00:00:00|50510|  0|15819|C3-4|IGY|None |ABC  |  1.066241687256579|13.862069804070295|\n    +=========+=======+=============+===================+=====+===+=====+====+===+=====+=====+===================+==================+\n    ```\n\n    Args:\n        rows (int, optional): number of rows wanted. Defaults to 100_000.\n\n    Returns:\n        Table (Table): Populated table.\n    \"\"\"  # noqa\n    rows = int(rows)\n\n    L1 = [\"None\", \"0\u00b0\", \"6\u00b0\", \"21\u00b0\"]\n    L2 = [\"ABC\", \"XYZ\", \"\"]\n\n    t = Table()\n    assert isinstance(t, Table)\n    for page_n in range(math.ceil(rows / Config.PAGE_SIZE)):  # n pages\n        start = (page_n * Config.PAGE_SIZE)\n        end = min(start + Config.PAGE_SIZE, rows)\n        ro = range(start, end)\n\n        t2 = Table()\n        t2[\"#\"] = [v+1 for v in ro]\n        # 1 - mock orderid\n        t2[\"1\"] = [random.randint(18_778_628_504, 2277_772_117_504) for i in ro]\n        # 2 - mock delivery date.\n        t2[\"2\"] = [datetime.fromordinal(random.randint(738000, 738150)).isoformat() for i in ro]\n        # 3 - mock store id.\n        t2[\"3\"] = [random.randint(50000, 51000) for _ in ro]\n        # 4 - random bit.\n        t2[\"4\"] = [random.randint(0, 1) for _ in ro]\n        # 5 - mock product id\n        t2[\"5\"] = [random.randint(3000, 30000) for _ in ro]\n        # 6 - random weird string\n        t2[\"6\"] = [f\"C{random.randint(1, 5)}-{random.randint(1, 5)}\" for _ in ro]\n        # 7 - # random category\n        t2[\"7\"] = [\"\".join(random.choice(ascii_uppercase) for _ in range(3)) for _ in ro]\n        # 8 -random temperature group.\n        t2[\"8\"] = [random.choice(L1) for _ in ro]\n        # 9 - random choice of category\n        t2[\"9\"] = [random.choice(L2) for _ in ro]\n        # 10 - volume?\n        t2[\"10\"] = [random.uniform(0.01, 2.5) for _ in ro]\n        # 11 - units?\n        t2[\"11\"] = [f\"{random.uniform(0.1, 25)}\" for _ in ro]\n\n        if len(t) == 0:\n            t = t2\n        else:\n            t += t2\n\n    return t\n
    "},{"location":"reference/datatypes/","title":"Datatypes","text":""},{"location":"reference/datatypes/#tablite.datatypes","title":"tablite.datatypes","text":""},{"location":"reference/datatypes/#tablite.datatypes-attributes","title":"Attributes","text":""},{"location":"reference/datatypes/#tablite.datatypes.matched_types","title":"tablite.datatypes.matched_types = {int: DataTypes._infer_int, str: DataTypes._infer_str, float: DataTypes._infer_float, bool: DataTypes._infer_bool, date: DataTypes._infer_date, datetime: DataTypes._infer_datetime, time: DataTypes._infer_time} module-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes-classes","title":"Classes","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes","title":"tablite.datatypes.DataTypes","text":"

    Bases: object

    DataTypes is the conversion library for all datatypes.

    It supports any / all python datatypes.

    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes-attributes","title":"Attributes","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.int","title":"tablite.datatypes.DataTypes.int = int class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.str","title":"tablite.datatypes.DataTypes.str = str class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.float","title":"tablite.datatypes.DataTypes.float = float class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.bool","title":"tablite.datatypes.DataTypes.bool = bool class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.date","title":"tablite.datatypes.DataTypes.date = date class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.datetime","title":"tablite.datatypes.DataTypes.datetime = datetime class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.time","title":"tablite.datatypes.DataTypes.time = time class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.timedelta","title":"tablite.datatypes.DataTypes.timedelta = timedelta class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.numeric_types","title":"tablite.datatypes.DataTypes.numeric_types = {int, float, date, time, datetime} class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.epoch","title":"tablite.datatypes.DataTypes.epoch = datetime(2000, 1, 1, 0, 0, 0, 0, timezone.utc) class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.epoch_no_tz","title":"tablite.datatypes.DataTypes.epoch_no_tz = datetime(2000, 1, 1, 0, 0, 0, 0) class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.digits","title":"tablite.datatypes.DataTypes.digits = '1234567890' class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.decimals","title":"tablite.datatypes.DataTypes.decimals = set('1234567890-+eE.') class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.integers","title":"tablite.datatypes.DataTypes.integers = set('1234567890-+') class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.nones","title":"tablite.datatypes.DataTypes.nones = {'null', 'Null', 'NULL', '#N/A', '#n/a', '', 'None', None, np.nan} class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.none_type","title":"tablite.datatypes.DataTypes.none_type = type(None) class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.bytes_functions","title":"tablite.datatypes.DataTypes.bytes_functions = {type(None): b_none, bool: b_bool, int: b_int, float: b_float, str: b_str, bytes: b_bytes, datetime: b_datetime, date: b_date, time: b_time, timedelta: b_timedelta} class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.type_code_functions","title":"tablite.datatypes.DataTypes.type_code_functions = {1: _none, 2: _bool, 3: _int, 4: _float, 5: _str, 6: _bytes, 7: _datetime, 8: _date, 9: _time, 10: _timedelta, 11: _unpickle} class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.pytype_from_type_code","title":"tablite.datatypes.DataTypes.pytype_from_type_code = {1: type(None), 2: bool, 3: int, 4: float, 5: str, 6: bytes, 7: datetime, 8: date, 9: time, 10: timedelta, 11: 'pickled object'} class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.date_formats","title":"tablite.datatypes.DataTypes.date_formats = {'NNNN-NN-NN': lambda x: date(*int(i) for i in x.split('-')), 'NNNN-N-NN': lambda x: date(*int(i) for i in x.split('-')), 'NNNN-NN-N': lambda x: date(*int(i) for i in x.split('-')), 'NNNN-N-N': lambda x: date(*int(i) for i in x.split('-')), 'NN-NN-NNNN': lambda x: date(*[int(i) for i in x.split('-')][::-1]), 'N-NN-NNNN': lambda x: date(*[int(i) for i in x.split('-')][::-1]), 'NN-N-NNNN': lambda x: date(*[int(i) for i in x.split('-')][::-1]), 'N-N-NNNN': lambda x: date(*[int(i) for i in x.split('-')][::-1]), 'NNNN.NN.NN': lambda x: date(*int(i) for i in x.split('.')), 'NNNN.N.NN': lambda x: date(*int(i) for i in x.split('.')), 'NNNN.NN.N': lambda x: date(*int(i) for i in x.split('.')), 'NNNN.N.N': lambda x: date(*int(i) for i in x.split('.')), 'NN.NN.NNNN': lambda x: date(*[int(i) for i in x.split('.')][::-1]), 'N.NN.NNNN': lambda x: date(*[int(i) for i in x.split('.')][::-1]), 'NN.N.NNNN': lambda x: date(*[int(i) for i in x.split('.')][::-1]), 'N.N.NNNN': lambda x: date(*[int(i) for i in x.split('.')][::-1]), 'NNNN/NN/NN': lambda x: date(*int(i) for i in x.split('/')), 'NNNN/N/NN': lambda x: date(*int(i) for i in x.split('/')), 'NNNN/NN/N': lambda x: date(*int(i) for i in x.split('/')), 'NNNN/N/N': lambda x: date(*int(i) for i in x.split('/')), 'NN/NN/NNNN': lambda x: date(*[int(i) for i in x.split('/')][::-1]), 'N/NN/NNNN': lambda x: date(*[int(i) for i in x.split('/')][::-1]), 'NN/N/NNNN': lambda x: date(*[int(i) for i in x.split('/')][::-1]), 'N/N/NNNN': lambda x: date(*[int(i) for i in x.split('/')][::-1]), 'NNNN NN NN': lambda x: date(*int(i) for i in x.split(' ')), 'NNNN N NN': lambda x: date(*int(i) for i in x.split(' ')), 'NNNN NN N': lambda x: date(*int(i) for i in x.split(' ')), 'NNNN N N': lambda x: date(*int(i) for i in x.split(' ')), 'NN NN NNNN': lambda x: date(*[int(i) for i in x.split(' ')][::-1]), 'N N NNNN': lambda x: date(*[int(i) for i in x.split(' ')][::-1]), 'NN N NNNN': lambda x: date(*[int(i) for i in x.split(' ')][::-1]), 'N NN NNNN': lambda x: date(*[int(i) for i in x.split(' ')][::-1]), 'NNNNNNNN': lambda x: date(*(int(x[:4]), int(x[4:6]), int(x[6:])))} class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.datetime_formats","title":"tablite.datatypes.DataTypes.datetime_formats = {'NNNN-NN-NNTNN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x), 'NNNN-NN-NNTNN:NN': lambda x: DataTypes.pattern_to_datetime(x), 'NNNN-NN-NN NN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, T=' '), 'NNNN-NN-NN NN:NN': lambda x: DataTypes.pattern_to_datetime(x, T=' '), 'NNNN/NN/NNTNN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/'), 'NNNN/NN/NNTNN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/'), 'NNNN/NN/NN NN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/', T=' '), 'NNNN/NN/NN NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/', T=' '), 'NNNN NN NNTNN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd=' '), 'NNNN NN NNTNN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd=' '), 'NNNN NN NN NN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd=' ', T=' '), 'NNNN NN NN NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd=' ', T=' '), 'NNNN.NN.NNTNN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='.'), 'NNNN.NN.NNTNN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='.'), 'NNNN.NN.NN NN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='.', T=' '), 'NNNN.NN.NN NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='.', T=' '), 'NN-NN-NNNNTNN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='-', T=' ', day_first=True), 'NN-NN-NNNNTNN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='-', T=' ', day_first=True), 'NN-NN-NNNN NN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='-', T=' ', day_first=True), 'NN-NN-NNNN NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='-', T=' ', day_first=True), 'NN/NN/NNNNTNN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/', day_first=True), 'NN/NN/NNNNTNN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/', day_first=True), 'NN/NN/NNNN NN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/', T=' ', day_first=True), 'NN/NN/NNNN NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/', T=' ', day_first=True), 'NN NN NNNNTNN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/', day_first=True), 'NN NN NNNNTNN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/', day_first=True), 'NN NN NNNN NN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/', day_first=True), 'NN NN NNNN NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='/', day_first=True), 'NN.NN.NNNNTNN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='.', day_first=True), 'NN.NN.NNNNTNN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='.', day_first=True), 'NN.NN.NNNN NN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='.', day_first=True), 'NN.NN.NNNN NN:NN': lambda x: DataTypes.pattern_to_datetime(x, ymd='.', day_first=True), 'NNNNNNNNTNNNNNN': lambda x: DataTypes.pattern_to_datetime(x, compact=1), 'NNNNNNNNTNNNN': lambda x: DataTypes.pattern_to_datetime(x, compact=1), 'NNNNNNNNTNN': lambda x: DataTypes.pattern_to_datetime(x, compact=1), 'NNNNNNNNNN': lambda x: DataTypes.pattern_to_datetime(x, compact=2), 'NNNNNNNNNNNN': lambda x: DataTypes.pattern_to_datetime(x, compact=2), 'NNNNNNNNNNNNNN': lambda x: DataTypes.pattern_to_datetime(x, compact=2), 'NNNNNNNNTNN:NN:NN': lambda x: DataTypes.pattern_to_datetime(x, compact=3)} class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.types","title":"tablite.datatypes.DataTypes.types = [datetime, date, time, int, bool, float, str] class-attribute instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes-functions","title":"Functions","text":""},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.type_code","title":"tablite.datatypes.DataTypes.type_code(value) classmethod","text":"Source code in tablite/datatypes.py
    @classmethod\ndef type_code(cls, value):\n    if type(value) in cls._type_codes:\n        return cls._type_codes[type(value)]\n    elif hasattr(value, \"dtype\"):\n        dtype = pytype(value)\n        return cls._type_codes[dtype]\n    else:\n        return cls._type_codes[\"pickle\"]\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_none","title":"tablite.datatypes.DataTypes.b_none(v)","text":"Source code in tablite/datatypes.py
    def b_none(v):\n    return b\"None\"\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_bool","title":"tablite.datatypes.DataTypes.b_bool(v)","text":"Source code in tablite/datatypes.py
    def b_bool(v):\n    return bytes(str(v), encoding=\"utf-8\")\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_int","title":"tablite.datatypes.DataTypes.b_int(v)","text":"Source code in tablite/datatypes.py
    def b_int(v):\n    return bytes(str(v), encoding=\"utf-8\")\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_float","title":"tablite.datatypes.DataTypes.b_float(v)","text":"Source code in tablite/datatypes.py
    def b_float(v):\n    return bytes(str(v), encoding=\"utf-8\")\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_str","title":"tablite.datatypes.DataTypes.b_str(v)","text":"Source code in tablite/datatypes.py
    def b_str(v):\n    return v.encode(\"utf-8\")\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_bytes","title":"tablite.datatypes.DataTypes.b_bytes(v)","text":"Source code in tablite/datatypes.py
    def b_bytes(v):\n    return v\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_datetime","title":"tablite.datatypes.DataTypes.b_datetime(v)","text":"Source code in tablite/datatypes.py
    def b_datetime(v):\n    return bytes(v.isoformat(), encoding=\"utf-8\")\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_date","title":"tablite.datatypes.DataTypes.b_date(v)","text":"Source code in tablite/datatypes.py
    def b_date(v):\n    return bytes(v.isoformat(), encoding=\"utf-8\")\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_time","title":"tablite.datatypes.DataTypes.b_time(v)","text":"Source code in tablite/datatypes.py
    def b_time(v):\n    return bytes(v.isoformat(), encoding=\"utf-8\")\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_timedelta","title":"tablite.datatypes.DataTypes.b_timedelta(v)","text":"Source code in tablite/datatypes.py
    def b_timedelta(v):\n    return bytes(str(float(v.days + (v.seconds / (24 * 60 * 60)))), \"utf-8\")\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.b_pickle","title":"tablite.datatypes.DataTypes.b_pickle(v)","text":"Source code in tablite/datatypes.py
    def b_pickle(v):\n    return pickle.dumps(v, protocol=0)\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.to_bytes","title":"tablite.datatypes.DataTypes.to_bytes(v) classmethod","text":"Source code in tablite/datatypes.py
    @classmethod\ndef to_bytes(cls, v):\n    if type(v) in cls.bytes_functions:  # it's a python native type\n        f = cls.bytes_functions[type(v)]\n    elif hasattr(v, \"dtype\"):  # it's a numpy/c type.\n        dtype = pytype(v)\n        f = cls.bytes_functions[dtype]\n    else:\n        f = cls.b_pickle\n    return f(v)\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.from_type_code","title":"tablite.datatypes.DataTypes.from_type_code(value, code) classmethod","text":"Source code in tablite/datatypes.py
    @classmethod\ndef from_type_code(cls, value, code):\n    f = cls.type_code_functions[code]\n    return f(value)\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.pattern_to_datetime","title":"tablite.datatypes.DataTypes.pattern_to_datetime(iso_string, ymd=None, T=None, compact=0, day_first=False) staticmethod","text":"Source code in tablite/datatypes.py
    @staticmethod\ndef pattern_to_datetime(iso_string, ymd=None, T=None, compact=0, day_first=False):\n    assert isinstance(iso_string, str)\n    if compact:\n        s = iso_string\n        if compact == 1:  # has T\n            slices = [\n                (0, 4, \"-\"),\n                (4, 6, \"-\"),\n                (6, 8, \"T\"),\n                (9, 11, \":\"),\n                (11, 13, \":\"),\n                (13, len(s), \"\"),\n            ]\n        elif compact == 2:  # has no T.\n            slices = [\n                (0, 4, \"-\"),\n                (4, 6, \"-\"),\n                (6, 8, \"T\"),\n                (8, 10, \":\"),\n                (10, 12, \":\"),\n                (12, len(s), \"\"),\n            ]\n        elif compact == 3:  # has T and :\n            slices = [\n                (0, 4, \"-\"),\n                (4, 6, \"-\"),\n                (6, 8, \"T\"),\n                (9, 11, \":\"),\n                (12, 14, \":\"),\n                (15, len(s), \"\"),\n            ]\n        else:\n            raise TypeError\n        iso_string = \"\".join([s[a:b] + c for a, b, c in slices if b <= len(s)])\n        iso_string = iso_string.rstrip(\":\")\n\n    if day_first:\n        s = iso_string\n        iso_string = \"\".join((s[6:10], \"-\", s[3:5], \"-\", s[0:2], s[10:]))\n\n    if \",\" in iso_string:\n        iso_string = iso_string.replace(\",\", \".\")\n\n    dot = iso_string[::-1].find(\".\")\n    if 0 < dot < 10:\n        ix = len(iso_string) - dot\n        microsecond = int(float(f\"0{iso_string[ix - 1:]}\") * 10**6)\n        # fmt:off\n        iso_string = iso_string[: len(iso_string) - dot] + str(microsecond).rjust(6, \"0\")\n        # fmt:on\n    if ymd:\n        iso_string = iso_string.replace(ymd, \"-\", 2)\n    if T:\n        iso_string = iso_string.replace(T, \"T\")\n    return datetime.fromisoformat(iso_string)\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.round","title":"tablite.datatypes.DataTypes.round(value, multiple, up=None) classmethod","text":"

    a nicer way to round numbers.

    PARAMETER DESCRIPTION value

    value to be rounded

    TYPE: (float, integer, datetime)

    multiple

    value to be used as the based of rounding. 1) multiple = 1 is the same as rounding to whole integers. 2) multiple = 0.001 is the same as rounding to 3 digits precision. 3) mulitple = 3.1415 is rounding to nearest multiplier of 3.1415 4) value = datetime(2022,8,18,11,14,53,440) 5) multiple = timedelta(hours=0.5) 6) xround(value,multiple) is datetime(2022,8,18,11,0)

    TYPE: (float, integer, timedelta)

    up

    None (default) or boolean rounds half, up or down. round(1.6, 1) rounds to 2. round(1.4, 1) rounds to 1. round(1.5, 1, up=True) rounds to 2. round(1.5, 1, up=False) rounds to 1.

    TYPE: (None, bool) DEFAULT: None

    RETURNS DESCRIPTION

    float,integer,datetime: rounded value in same type as input.

    Source code in tablite/datatypes.py
    @classmethod\ndef round(cls, value, multiple, up=None):\n    \"\"\"a nicer way to round numbers.\n\n    Args:\n        value (float,integer,datetime): value to be rounded\n\n        multiple (float,integer,timedelta): value to be used as the based of rounding.\n            1) multiple = 1 is the same as rounding to whole integers.\n            2) multiple = 0.001 is the same as rounding to 3 digits precision.\n            3) mulitple = 3.1415 is rounding to nearest multiplier of 3.1415\n            4) value = datetime(2022,8,18,11,14,53,440)\n            5) multiple = timedelta(hours=0.5)\n            6) xround(value,multiple) is datetime(2022,8,18,11,0)\n\n        up (None, bool, optional):\n            None (default) or boolean rounds half, up or down.\n            round(1.6, 1) rounds to 2.\n            round(1.4, 1) rounds to 1.\n            round(1.5, 1, up=True) rounds to 2.\n            round(1.5, 1, up=False) rounds to 1.\n\n    Returns:\n        float,integer,datetime: rounded value in same type as input.\n    \"\"\"\n    epoch = 0\n    if isinstance(value, (datetime)) and isinstance(multiple, timedelta):\n        if value.tzinfo is None:\n            epoch = cls.epoch_no_tz\n        else:\n            epoch = cls.epoch\n\n    value2 = value - epoch\n    if value2 == 0:\n        return value2\n\n    low = (value2 // multiple) * multiple\n    high = low + multiple\n    if up is True:\n        return high + epoch\n    elif up is False:\n        return low + epoch\n    else:\n        if abs((high + epoch) - value) < abs(value - (low + epoch)):\n            return high + epoch\n        else:\n            return low + epoch\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.to_json","title":"tablite.datatypes.DataTypes.to_json(v) staticmethod","text":"

    converts any python type to json.

    PARAMETER DESCRIPTION v

    value to convert to json

    TYPE: any

    RETURNS DESCRIPTION

    json compatible value from v

    Source code in tablite/datatypes.py
    @staticmethod\ndef to_json(v):\n    \"\"\"converts any python type to json.\n\n    Args:\n        v (any): value to convert to json\n\n    Returns:\n        json compatible value from v\n    \"\"\"\n    if hasattr(v, \"dtype\"):\n        v = numpy_to_python(v)\n    if v is None:\n        return v\n    elif v is False:\n        # using isinstance(v, bool): won't work as False also is int of zero.\n        return str(v)\n    elif v is True:\n        return str(v)\n    elif isinstance(v, int):\n        return v\n    elif isinstance(v, str):\n        return v\n    elif isinstance(v, float):\n        return v\n    elif isinstance(v, datetime):\n        return v.isoformat()\n    elif isinstance(v, time):\n        return v.isoformat()\n    elif isinstance(v, date):\n        return v.isoformat()\n    elif isinstance(v, timedelta):\n        return f\"P{v.days}DT{v.seconds + (v.microseconds / 1e6)}S\"\n    else:\n        raise TypeError(f\"The datatype {type(v)} is not supported.\")\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.from_json","title":"tablite.datatypes.DataTypes.from_json(v, dtype) staticmethod","text":"

    converts json to python datatype

    PARAMETER DESCRIPTION v

    value

    TYPE: any

    dtype

    any python type

    TYPE: python type

    RETURNS DESCRIPTION

    python type of value v

    Source code in tablite/datatypes.py
    @staticmethod\ndef from_json(v, dtype):\n    \"\"\"converts json to python datatype\n\n    Args:\n        v (any): value\n        dtype (python type): any python type\n\n    Returns:\n        python type of value v\n    \"\"\"\n    if v in DataTypes.nones:\n        if dtype is str and v == \"\":\n            return \"\"\n        else:\n            return None\n    if dtype is int:\n        return int(v)\n    elif dtype is str:\n        return str(v)\n    elif dtype is float:\n        return float(v)\n    elif dtype is bool:\n        if v == \"False\":\n            return False\n        elif v == \"True\":\n            return True\n        else:\n            raise ValueError(v)\n    elif dtype is date:\n        return date.fromisoformat(v)\n    elif dtype is datetime:\n        return datetime.fromisoformat(v)\n    elif dtype is time:\n        return time.fromisoformat(v)\n    elif dtype is timedelta:\n        L = v.split(\"DT\")\n        days = int(L[0].lstrip(\"P\"))\n        seconds = float(L[1].rstrip(\"S\"))\n        return timedelta(days, seconds)\n    else:\n        raise TypeError(f\"The datatype {str(dtype)} is not supported.\")\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.guess_types","title":"tablite.datatypes.DataTypes.guess_types(*values) staticmethod","text":"

    Attempts to guess the datatype for *values returns dict with matching datatypes and probabilities

    RETURNS DESCRIPTION dict

    {key: type, value: probability}

    Source code in tablite/datatypes.py
    @staticmethod\ndef guess_types(*values):\n    \"\"\"Attempts to guess the datatype for *values\n    returns dict with matching datatypes and probabilities\n\n    Returns:\n        dict: {key: type, value: probability}\n    \"\"\"\n    d = defaultdict(int)\n    probability = Rank(DataTypes.types[:])\n\n    for value in values:\n        if hasattr(value, \"dtype\"):\n            value = numpy_to_python(value)\n\n        for dtype in probability:\n            try:\n                _ = DataTypes.infer(value, dtype)\n                d[dtype] += 1\n                probability.match(dtype)\n                break\n            except (ValueError, TypeError):\n                pass\n    if not d:\n        d[str] = len(values)\n    return {k: round(v / len(values), 3) for k, v in d.items()}\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.guess","title":"tablite.datatypes.DataTypes.guess(*values) staticmethod","text":"

    Makes a best guess the datatype for *values returns list of native python values

    RETURNS DESCRIPTION list

    list of native python values

    Source code in tablite/datatypes.py
    @staticmethod\ndef guess(*values):\n    \"\"\"Makes a best guess the datatype for *values\n    returns list of native python values\n\n    Returns:\n        list: list of native python values\n    \"\"\"\n    probability = Rank(*DataTypes.types[:])\n    matches = [None for _ in values[0]]\n\n    for ix, value in enumerate(values[0]):\n        if hasattr(value, \"dtype\"):\n            value = numpy_to_python(value)\n        for dtype in probability:\n            try:\n                matches[ix] = DataTypes.infer(value, dtype)\n                probability.match(dtype)\n                break\n            except (ValueError, TypeError):\n                pass\n    return matches\n
    "},{"location":"reference/datatypes/#tablite.datatypes.DataTypes.infer","title":"tablite.datatypes.DataTypes.infer(v, dtype) classmethod","text":"Source code in tablite/datatypes.py
    @classmethod\ndef infer(cls, v, dtype):\n    if isinstance(v, str) and dtype == str:\n        # we got a string, we're trying to infer it to string, we shouldn't check for None-ness\n        return v\n\n    if v in DataTypes.nones:\n        return None\n\n    if dtype not in matched_types:\n        raise TypeError(f\"The datatype {str(dtype)} is not supported.\")\n\n    return matched_types[dtype](v)\n
    "},{"location":"reference/datatypes/#tablite.datatypes.Rank","title":"tablite.datatypes.Rank(*items)","text":"

    Bases: object

    Source code in tablite/datatypes.py
    def __init__(self, *items):\n    self.items = {i: ix for i, ix in zip(items, range(len(items)))}\n    self.ranks = [0 for _ in items]\n    self.items_list = [i for i in items]\n
    "},{"location":"reference/datatypes/#tablite.datatypes.Rank-attributes","title":"Attributes","text":""},{"location":"reference/datatypes/#tablite.datatypes.Rank.items","title":"tablite.datatypes.Rank.items = {i: ixfor (i, ix) in zip(items, range(len(items)))} instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.Rank.ranks","title":"tablite.datatypes.Rank.ranks = [0 for _ in items] instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.Rank.items_list","title":"tablite.datatypes.Rank.items_list = [i for i in items] instance-attribute","text":""},{"location":"reference/datatypes/#tablite.datatypes.Rank-functions","title":"Functions","text":""},{"location":"reference/datatypes/#tablite.datatypes.Rank.match","title":"tablite.datatypes.Rank.match(k)","text":"Source code in tablite/datatypes.py
    def match(self, k):  # k+=1\n    ix = self.items[k]\n    r = self.ranks\n    r[ix] += 1\n\n    if ix > 0:\n        p = self.items_list\n        while (\n            r[ix] > r[ix - 1] and ix > 0\n        ):  # use a simple bubble sort to maintain rank\n            r[ix], r[ix - 1] = r[ix - 1], r[ix]\n            p[ix], p[ix - 1] = p[ix - 1], p[ix]\n            old = p[ix]\n            self.items[old] = ix\n            self.items[k] = ix - 1\n            ix -= 1\n
    "},{"location":"reference/datatypes/#tablite.datatypes.Rank.__iter__","title":"tablite.datatypes.Rank.__iter__()","text":"Source code in tablite/datatypes.py
    def __iter__(self):\n    return iter(self.items_list)\n
    "},{"location":"reference/datatypes/#tablite.datatypes.MetaArray","title":"tablite.datatypes.MetaArray","text":"

    Bases: ndarray

    Array with metadata.

    "},{"location":"reference/datatypes/#tablite.datatypes.MetaArray-functions","title":"Functions","text":""},{"location":"reference/datatypes/#tablite.datatypes.MetaArray.__new__","title":"tablite.datatypes.MetaArray.__new__(array, dtype=None, order=None, **kwargs)","text":"Source code in tablite/datatypes.py
    def __new__(cls, array, dtype=None, order=None, **kwargs):\n    obj = np.asarray(array, dtype=dtype, order=order).view(cls)\n    obj.metadata = kwargs\n    return obj\n
    "},{"location":"reference/datatypes/#tablite.datatypes.MetaArray.__array_finalize__","title":"tablite.datatypes.MetaArray.__array_finalize__(obj)","text":"Source code in tablite/datatypes.py
    def __array_finalize__(self, obj):\n    if obj is None:\n        return\n    self.metadata = getattr(obj, \"metadata\", None)\n
    "},{"location":"reference/datatypes/#tablite.datatypes-functions","title":"Functions","text":""},{"location":"reference/datatypes/#tablite.datatypes.numpy_to_python","title":"tablite.datatypes.numpy_to_python(obj: Any) -> Any","text":"

    Converts numpy types to python types.

    See https://numpy.org/doc/stable/reference/arrays.scalars.html

    PARAMETER DESCRIPTION obj

    A numpy object

    TYPE: Any

    RETURNS DESCRIPTION Any

    python object: A python object

    Source code in tablite/datatypes.py
    def numpy_to_python(obj: Any) -> Any:\n    \"\"\"Converts numpy types to python types.\n\n    See https://numpy.org/doc/stable/reference/arrays.scalars.html\n\n    Args:\n        obj (Any): A numpy object\n\n    Returns:\n        python object: A python object\n    \"\"\"\n    if isinstance(obj, np.generic):\n        return obj.item()\n    return obj\n
    "},{"location":"reference/datatypes/#tablite.datatypes.pytype","title":"tablite.datatypes.pytype(obj)","text":"

    Returns the python type of any object

    PARAMETER DESCRIPTION obj

    any numpy or python object

    TYPE: Any

    RETURNS DESCRIPTION type

    type of obj

    Source code in tablite/datatypes.py
    def pytype(obj):\n    \"\"\"Returns the python type of any object\n\n    Args:\n        obj (Any): any numpy or python object\n\n    Returns:\n        type: type of obj\n    \"\"\"\n    if isinstance(obj, np.generic):\n        return type(obj.item())\n    return type(obj)\n
    "},{"location":"reference/datatypes/#tablite.datatypes.pytype_from_iterable","title":"tablite.datatypes.pytype_from_iterable(iterable: {tuple, list}) -> {np.dtype, dict}","text":"

    helper to make correct np array from python types.

    PARAMETER DESCRIPTION iterable

    values to be converted to numpy array.

    TYPE: (tuple, list)

    RAISES DESCRIPTION NotImplementedError

    if datatype is not supported.

    RETURNS DESCRIPTION {dtype, dict}

    np.dtype: python type of the iterable.

    Source code in tablite/datatypes.py
    def pytype_from_iterable(iterable: {tuple, list}) -> {np.dtype, dict}:\n    \"\"\"helper to make correct np array from python types.\n\n    Args:\n        iterable (tuple,list): values to be converted to numpy array.\n\n    Raises:\n        NotImplementedError: if datatype is not supported.\n\n    Returns:\n        np.dtype: python type of the iterable.\n    \"\"\"\n    py_types = {}\n    if isinstance(iterable, (tuple, list)):\n        type_counter = Counter((pytype(v) for v in iterable))\n\n        for k, v in type_counter.items():\n            py_types[k] = v\n\n        if len(py_types) == 0:\n            np_dtype, py_dtype = object, bool\n        elif len(py_types) == 1:\n            py_dtype = list(py_types.keys())[0]\n            if py_dtype == datetime:\n                np_dtype = np.datetime64\n            elif py_dtype == date:\n                np_dtype = np.datetime64\n            elif py_dtype == timedelta:\n                np_dtype = np.timedelta64\n            else:\n                np_dtype = None\n        else:\n            np_dtype = object\n    elif isinstance(iterable, np.ndarray):\n        if iterable.dtype == object:\n            np_dtype = object\n            py_types = dict(Counter((pytype(v) for v in iterable)))\n        else:\n            np_dtype = iterable.dtype\n            if len(iterable) > 0:\n                py_types = {pytype(iterable[0]): len(iterable)}\n            else:\n                py_types = {pytype(np_dtype.type()): len(iterable)}\n    else:\n        raise NotImplementedError(f\"No handler for {type(iterable)}\")\n\n    return np_dtype, py_types\n
    "},{"location":"reference/datatypes/#tablite.datatypes.list_to_np_array","title":"tablite.datatypes.list_to_np_array(iterable)","text":"

    helper to make correct np array from python types. Example of problem where numpy turns mixed types into strings.

    np.array([4, '5']) np.ndarray(['4', '5'])

    RETURNS DESCRIPTION

    np.array

    datatypes

    Source code in tablite/datatypes.py
    def list_to_np_array(iterable):\n    \"\"\"helper to make correct np array from python types.\n    Example of problem where numpy turns mixed types into strings.\n    >>> np.array([4, '5'])\n    np.ndarray(['4', '5'])\n\n    returns:\n        np.array\n        datatypes\n    \"\"\"\n    np_dtype, py_dtype = pytype_from_iterable(iterable)\n\n    value = MetaArray(iterable, dtype=np_dtype, py_dtype=py_dtype)\n    return value\n
    "},{"location":"reference/datatypes/#tablite.datatypes.np_type_unify","title":"tablite.datatypes.np_type_unify(arrays)","text":"

    unifies numpy types.

    PARAMETER DESCRIPTION arrays

    List of numpy arrays

    TYPE: list

    RETURNS DESCRIPTION

    np.ndarray: numpy array of a single type.

    Source code in tablite/datatypes.py
    def np_type_unify(arrays):\n    \"\"\"unifies numpy types.\n\n    Args:\n        arrays (list): List of numpy arrays\n\n    Returns:\n        np.ndarray: numpy array of a single type.\n    \"\"\"\n    dtypes = {arr.dtype: len(arr) for arr in arrays}\n    if len(dtypes) == 1:\n        dtype, _ = dtypes.popitem()\n    else:\n        for ix, arr in enumerate(arrays):\n            arrays[ix] = np.array(arr, dtype=object)\n        dtype = object\n    return np.concatenate(arrays, dtype=dtype)\n
    "},{"location":"reference/datatypes/#tablite.datatypes.multitype_set","title":"tablite.datatypes.multitype_set(arr)","text":"

    prevents loss of True, False when calling sets.

    python looses values when called returning a set. Example:

    {1, True, 0, False}

    PARAMETER DESCRIPTION arr

    iterable of mixed types.

    TYPE: Iterable

    RETURNS DESCRIPTION

    np.array: with unique values.

    Source code in tablite/datatypes.py
    def multitype_set(arr):\n    \"\"\"prevents loss of True, False when calling sets.\n\n    python looses values when called returning a set. Example:\n    >>> {1, True, 0, False}\n    {0,1}\n\n    Args:\n        arr (Iterable): iterable of mixed types.\n\n    Returns:\n        np.array: with unique values.\n    \"\"\"\n    L = [(type(v), v) for v in arr]\n    L = list(set(L))\n    L = [v for _, v in L]\n    return np.array(L, dtype=object)\n
    "},{"location":"reference/diff/","title":"Diff","text":""},{"location":"reference/diff/#tablite.diff","title":"tablite.diff","text":""},{"location":"reference/diff/#tablite.diff-classes","title":"Classes","text":""},{"location":"reference/diff/#tablite.diff-functions","title":"Functions","text":""},{"location":"reference/diff/#tablite.diff.diff","title":"tablite.diff.diff(T, other, columns=None)","text":"

    compares table self with table other

    PARAMETER DESCRIPTION self

    Table

    TYPE: Table

    other

    Table

    TYPE: Table

    columns

    list of column names to include in comparison. Defaults to None.

    TYPE: List DEFAULT: None

    RETURNS DESCRIPTION Table

    diff of self and other with diff in columns 1st and 2nd.

    Source code in tablite/diff.py
    def diff(T, other, columns=None):\n    \"\"\"compares table self with table other\n\n    Args:\n        self (Table): Table\n        other (Table): Table\n        columns (List, optional): list of column names to include in comparison. Defaults to None.\n\n    Returns:\n        Table: diff of self and other with diff in columns 1st and 2nd.\n    \"\"\"\n    sub_cls_check(T, BaseTable)\n    sub_cls_check(other, BaseTable)\n    if columns is None:\n        columns = [name for name in T.columns if name in other.columns]\n    elif isinstance(columns, list) and all(isinstance(i, str) for i in columns):\n        for name in columns:\n            if name not in T.columns:\n                raise ValueError(f\"column '{name}' not found\")\n            if name not in other.columns:\n                raise ValueError(f\"column '{name}' not found\")\n    else:\n        raise TypeError(\"Expected list of column names\")\n\n    t1 = T[columns]\n    if issubclass(type(t1), BaseTable):\n        t1 = [tuple(r) for r in T.rows]\n    else:\n        t1 = list(T)\n    t2 = other[columns]\n    if issubclass(type(t2), BaseTable):\n        t2 = [tuple(r) for r in other.rows]\n    else:\n        t2 = list(other)\n\n    sm = difflib.SequenceMatcher(None, t1, t2)\n    new = type(T)()\n    first = unique_name(\"1st\", columns)\n    second = unique_name(\"2nd\", columns)\n    new.add_columns(*columns + [first, second])\n\n    news = {n: [] for n in new.columns}  # Cache for Work in progress.\n\n    for opc, t1a, t1b, t2a, t2b in sm.get_opcodes():\n        if opc == \"insert\":\n            for name, col in zip(columns, zip(*t2[t2a:t2b])):\n                news[name].extend(col)\n            news[first] += [\"-\"] * (t2b - t2a)\n            news[second] += [\"+\"] * (t2b - t2a)\n\n        elif opc == \"delete\":\n            for name, col in zip(columns, zip(*t1[t1a:t1b])):\n                news[name].extend(col)\n            news[first] += [\"+\"] * (t1b - t1a)\n            news[second] += [\"-\"] * (t1b - t1a)\n\n        elif opc == \"equal\":\n            for name, col in zip(columns, zip(*t2[t2a:t2b])):\n                news[name].extend(col)\n            news[first] += [\"=\"] * (t2b - t2a)\n            news[second] += [\"=\"] * (t2b - t2a)\n\n        elif opc == \"replace\":\n            for name, col in zip(columns, zip(*t2[t2a:t2b])):\n                news[name].extend(col)\n            news[first] += [\"r\"] * (t2b - t2a)\n            news[second] += [\"r\"] * (t2b - t2a)\n\n        else:\n            pass\n\n        # Clear cache to free up memory.\n        if len(news[first]) > Config.PAGE_SIZE == 0:\n            for name, L in news.items():\n                new[name].extend(np.array(L))\n                L.clear()\n\n    for name, L in news.items():\n        new[name].extend(np.array(L))\n        L.clear()\n    return new\n
    "},{"location":"reference/export_utils/","title":"Export utils","text":""},{"location":"reference/export_utils/#tablite.export_utils","title":"tablite.export_utils","text":""},{"location":"reference/export_utils/#tablite.export_utils-classes","title":"Classes","text":""},{"location":"reference/export_utils/#tablite.export_utils-functions","title":"Functions","text":""},{"location":"reference/export_utils/#tablite.export_utils.to_sql","title":"tablite.export_utils.to_sql(table, name)","text":"

    generates ANSI-92 compliant SQL.

    PARAMETER DESCRIPTION name

    name of SQL table.

    TYPE: str

    Source code in tablite/export_utils.py
    def to_sql(table, name):\n    \"\"\"\n    generates ANSI-92 compliant SQL.\n\n    args:\n        name (str): name of SQL table.\n    \"\"\"\n    sub_cls_check(table, BaseTable)\n    type_check(name, str)\n\n    prefix = name\n    name = \"T1\"\n    create_table = \"\"\"CREATE TABLE {} ({})\"\"\"\n    columns = []\n    for name, col in table.columns.items():\n        dtype = col.types()\n        if len(dtype) == 1:\n            dtype, _ = dtype.popitem()\n            if dtype is int:\n                dtype = \"INTEGER\"\n            elif dtype is float:\n                dtype = \"REAL\"\n            else:\n                dtype = \"TEXT\"\n        else:\n            dtype = \"TEXT\"\n        definition = f\"{name} {dtype}\"\n        columns.append(definition)\n\n    create_table = create_table.format(prefix, \", \".join(columns))\n\n    # return create_table\n    row_inserts = []\n    for row in table.rows:\n        row_inserts.append(str(tuple([i if i is not None else \"NULL\" for i in row])))\n    row_inserts = f\"INSERT INTO {prefix} VALUES \" + \",\".join(row_inserts)\n    return \"begin; {}; {}; commit;\".format(create_table, row_inserts)\n
    "},{"location":"reference/export_utils/#tablite.export_utils.to_pandas","title":"tablite.export_utils.to_pandas(table)","text":"

    returns pandas.DataFrame

    Source code in tablite/export_utils.py
    def to_pandas(table):\n    \"\"\"\n    returns pandas.DataFrame\n    \"\"\"\n    sub_cls_check(table, BaseTable)\n    try:\n        return pd.DataFrame(table.to_dict())  # noqa\n    except ImportError:\n        import pandas as pd  # noqa\n    return pd.DataFrame(table.to_dict())  # noqa\n
    "},{"location":"reference/export_utils/#tablite.export_utils.to_hdf5","title":"tablite.export_utils.to_hdf5(table, path)","text":"

    creates a copy of the table as hdf5

    Note that some loss of type information is to be expected in columns of mixed type:

    t.show(dtype=True) +===+===+=====+=====+====+=====+=====+===================+==========+========+===============+===+=========================+=====+===+ | # | A | B | C | D | E | F | G | H | I | J | K | L | M | O | |row|int|mixed|float|str |mixed| bool| datetime | date | time | timedelta |str| int |float|int| +---+---+-----+-----+----+-----+-----+-------------------+----------+--------+---------------+---+-------------------------+-----+---+ | 0 | -1|None | -1.1| |None |False|2023-06-09 09:12:06|2023-06-09|09:12:06| 1 day, 0:00:00|b |-100000000000000000000000| inf| 11| | 1 | 1| 1| 1.1|1000|1 | True|2023-06-09 09:12:06|2023-06-09|09:12:06|2 days, 0:06:40|\u55e8 | 100000000000000000000000| -inf|-11| +===+===+=====+=====+====+=====+=====+===================+==========+========+===============+===+=========================+=====+===+ t.to_hdf5(filename) t2 = Table.from_hdf5(filename) t2.show(dtype=True) +===+===+=====+=====+=====+=====+=====+===================+===================+========+===============+===+=========================+=====+===+ | # | A | B | C | D | E | F | G | H | I | J | K | L | M | O | |row|int|mixed|float|mixed|mixed| bool| datetime | datetime | time | str |str| int |float|int| +---+---+-----+-----+-----+-----+-----+-------------------+-------------------+--------+---------------+---+-------------------------+-----+---+ | 0 | -1|None | -1.1|None |None |False|2023-06-09 09:12:06|2023-06-09 00:00:00|09:12:06|1 day, 0:00:00 |b |-100000000000000000000000| inf| 11| | 1 | 1| 1| 1.1| 1000| 1| True|2023-06-09 09:12:06|2023-06-09 00:00:00|09:12:06|2 days, 0:06:40|\u55e8 | 100000000000000000000000| -inf|-11| +===+===+=====+=====+=====+=====+=====+===================+===================+========+===============+===+=========================+=====+===+

    Source code in tablite/export_utils.py
    def to_hdf5(table, path):\n    # fmt: off\n    \"\"\"\n    creates a copy of the table as hdf5\n\n    Note that some loss of type information is to be expected in columns of mixed type:\n    >>> t.show(dtype=True)\n    +===+===+=====+=====+====+=====+=====+===================+==========+========+===============+===+=========================+=====+===+\n    | # | A |  B  |  C  | D  |  E  |  F  |         G         |    H     |   I    |       J       | K |            L            |  M  | O |\n    |row|int|mixed|float|str |mixed| bool|      datetime     |   date   |  time  |   timedelta   |str|           int           |float|int|\n    +---+---+-----+-----+----+-----+-----+-------------------+----------+--------+---------------+---+-------------------------+-----+---+\n    | 0 | -1|None | -1.1|    |None |False|2023-06-09 09:12:06|2023-06-09|09:12:06| 1 day, 0:00:00|b  |-100000000000000000000000|  inf| 11|\n    | 1 |  1|    1|  1.1|1000|1    | True|2023-06-09 09:12:06|2023-06-09|09:12:06|2 days, 0:06:40|\u55e8  | 100000000000000000000000| -inf|-11|\n    +===+===+=====+=====+====+=====+=====+===================+==========+========+===============+===+=========================+=====+===+\n    >>> t.to_hdf5(filename)\n    >>> t2 = Table.from_hdf5(filename)\n    >>> t2.show(dtype=True)\n    +===+===+=====+=====+=====+=====+=====+===================+===================+========+===============+===+=========================+=====+===+\n    | # | A |  B  |  C  |  D  |  E  |  F  |         G         |         H         |   I    |       J       | K |            L            |  M  | O |\n    |row|int|mixed|float|mixed|mixed| bool|      datetime     |      datetime     |  time  |      str      |str|           int           |float|int|\n    +---+---+-----+-----+-----+-----+-----+-------------------+-------------------+--------+---------------+---+-------------------------+-----+---+\n    | 0 | -1|None | -1.1|None |None |False|2023-06-09 09:12:06|2023-06-09 00:00:00|09:12:06|1 day, 0:00:00 |b  |-100000000000000000000000|  inf| 11|\n    | 1 |  1|    1|  1.1| 1000|    1| True|2023-06-09 09:12:06|2023-06-09 00:00:00|09:12:06|2 days, 0:06:40|\u55e8  | 100000000000000000000000| -inf|-11|\n    +===+===+=====+=====+=====+=====+=====+===================+===================+========+===============+===+=========================+=====+===+\n    \"\"\"\n    # fmt: in\n    import h5py\n\n    sub_cls_check(table, BaseTable)\n    type_check(path, Path)\n\n    total = f\"{len(table.columns) * len(table):,}\"  # noqa\n    print(f\"writing {total} records to {path}\", end=\"\")\n\n    with h5py.File(path, \"w\") as f:\n        n = 0\n        for name, col in table.items():\n            try:\n                f.create_dataset(name, data=col[:])  # stored in hdf5 as '/name'\n            except TypeError:\n                f.create_dataset(name, data=[str(i) for i in col[:]])  # stored in hdf5 as '/name'\n            n += 1\n    print(\"... done\")\n
    "},{"location":"reference/export_utils/#tablite.export_utils.excel_writer","title":"tablite.export_utils.excel_writer(table, path)","text":"

    writer for excel files.

    This can create xlsx files beyond Excels. If you're using pyexcel to read the data, you'll see the data is there. If you're using Excel, Excel will stop loading after 1,048,576 rows.

    See pyexcel for more details: http://docs.pyexcel.org/

    Source code in tablite/export_utils.py
    def excel_writer(table, path):\n    \"\"\"\n    writer for excel files.\n\n    This can create xlsx files beyond Excels.\n    If you're using pyexcel to read the data, you'll see the data is there.\n    If you're using Excel, Excel will stop loading after 1,048,576 rows.\n\n    See pyexcel for more details:\n    http://docs.pyexcel.org/\n    \"\"\"\n    import pyexcel\n\n    sub_cls_check(table, BaseTable)\n    type_check(path, Path)\n\n    def gen(table):  # local helper\n        yield table.columns\n        for row in table.rows:\n            yield row\n\n    data = list(gen(table))\n    if path.suffix in [\".xls\", \".ods\"]:\n        data = [\n            [str(v) if (isinstance(v, (int, float)) and abs(v) > 2**32 - 1) else DataTypes.to_json(v) for v in row]\n            for row in data\n        ]\n\n    pyexcel.save_as(array=data, dest_file_name=str(path))\n
    "},{"location":"reference/export_utils/#tablite.export_utils.to_json","title":"tablite.export_utils.to_json(table, *args, **kwargs)","text":"Source code in tablite/export_utils.py
    def to_json(table, *args, **kwargs):\n    import json\n\n    sub_cls_check(table, BaseTable)\n    return json.dumps(table.as_json_serializable())\n
    "},{"location":"reference/export_utils/#tablite.export_utils.path_suffix_check","title":"tablite.export_utils.path_suffix_check(path, kind)","text":"Source code in tablite/export_utils.py
    def path_suffix_check(path, kind):\n    if not path.suffix == kind:\n        raise ValueError(f\"Suffix mismatch: Expected {kind}, got {path.suffix} in {path.name}\")\n    if not path.parent.exists():\n        raise FileNotFoundError(f\"directory {path.parent} not found.\")\n
    "},{"location":"reference/export_utils/#tablite.export_utils.text_writer","title":"tablite.export_utils.text_writer(table, path, tqdm=_tqdm)","text":"

    exports table to csv, tsv or txt dependening on path suffix. follows the JSON norm. text escape is ON for all strings.

    "},{"location":"reference/export_utils/#tablite.export_utils.text_writer--note","title":"Note:","text":"

    If the delimiter is present in a string when the string is exported, text-escape is required, as the format otherwise is corrupted. When the file is being written, it is unknown whether any string in a column contrains the delimiter. As text escaping the few strings that may contain the delimiter would lead to an assymmetric format, the safer guess is to text escape all strings.

    Source code in tablite/export_utils.py
    def text_writer(table, path, tqdm=_tqdm):\n    \"\"\"exports table to csv, tsv or txt dependening on path suffix.\n    follows the JSON norm. text escape is ON for all strings.\n\n    Note:\n    ----------------------\n    If the delimiter is present in a string when the string is exported,\n    text-escape is required, as the format otherwise is corrupted.\n    When the file is being written, it is unknown whether any string in\n    a column contrains the delimiter. As text escaping the few strings\n    that may contain the delimiter would lead to an assymmetric format,\n    the safer guess is to text escape all strings.\n    \"\"\"\n    sub_cls_check(table, BaseTable)\n    type_check(path, Path)\n\n    def txt(value):  # helper for text writer\n        if value is None:\n            return \"\"  # A column with 1,None,2 must be \"1,,2\".\n        elif isinstance(value, str):\n            # if not (value.startswith('\"') and value.endswith('\"')):\n            #     return f'\"{value}\"'  # this must be escape: \"the quick fox, jumped over the comma\"\n            # else:\n            return value  # this would for example be an empty string: \"\"\n        else:\n            return str(DataTypes.to_json(value))  # this handles datetimes, timedelta, etc.\n\n    delimiters = {\".csv\": \",\", \".tsv\": \"\\t\", \".txt\": \"|\"}\n    delimiter = delimiters.get(path.suffix)\n\n    with path.open(\"w\", encoding=\"utf-8\") as fo:\n        fo.write(delimiter.join(c for c in table.columns) + \"\\n\")\n        for row in tqdm(table.rows, total=len(table), disable=Config.TQDM_DISABLE):\n            fo.write(delimiter.join(txt(c) for c in row) + \"\\n\")\n
    "},{"location":"reference/export_utils/#tablite.export_utils.sql_writer","title":"tablite.export_utils.sql_writer(table, path)","text":"Source code in tablite/export_utils.py
    def sql_writer(table, path):\n    type_check(table, BaseTable)\n    type_check(path, Path)\n    with path.open(\"w\", encoding=\"utf-8\") as fo:\n        fo.write(to_sql(table))\n
    "},{"location":"reference/export_utils/#tablite.export_utils.json_writer","title":"tablite.export_utils.json_writer(table, path)","text":"Source code in tablite/export_utils.py
    def json_writer(table, path):\n    type_check(table, BaseTable)\n    type_check(path, Path)\n    with path.open(\"w\") as fo:\n        fo.write(to_json(table))\n
    "},{"location":"reference/export_utils/#tablite.export_utils.to_html","title":"tablite.export_utils.to_html(table, path)","text":"Source code in tablite/export_utils.py
    def to_html(table, path):\n    type_check(table, BaseTable)\n    type_check(path, Path)\n    with path.open(\"w\", encoding=\"utf-8\") as fo:\n        fo.write(table._repr_html_(slice(0, len(table))))\n
    "},{"location":"reference/file_reader_utils/","title":"File reader utils","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils","title":"tablite.file_reader_utils","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils-attributes","title":"Attributes","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.ENCODING_GUESS_BYTES","title":"tablite.file_reader_utils.ENCODING_GUESS_BYTES = 10000 module-attribute","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.header_readers","title":"tablite.file_reader_utils.header_readers = {'fods': excel_reader_headers, 'json': excel_reader_headers, 'simple': excel_reader_headers, 'rst': excel_reader_headers, 'mediawiki': excel_reader_headers, 'xlsx': excel_reader_headers, 'xlsm': excel_reader_headers, 'csv': text_reader_headers, 'tsv': text_reader_headers, 'txt': text_reader_headers, 'ods': ods_reader_headers} module-attribute","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils-classes","title":"Classes","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.TextEscape","title":"tablite.file_reader_utils.TextEscape(openings='({[', closures=']})', text_qualifier='\"', delimiter=',', strip_leading_and_tailing_whitespace=False)","text":"

    Bases: object

    enables parsing of CSV with respecting brackets and text marks.

    Example: text_escape = TextEscape() # set up the instance. for line in somefile.readlines(): list_of_words = text_escape(line) # use the instance. ...

    As an example, the Danes and Germans use \" for inches and ' for feet, so we will see data that contains nail (75 x 4 mm, 3\" x 3/12\"), so for this case ( and ) are valid escapes, but \" and ' aren't.

    Source code in tablite/file_reader_utils.py
    def __init__(\n    self,\n    openings=\"({[\",\n    closures=\"]})\",\n    text_qualifier='\"',\n    delimiter=\",\",\n    strip_leading_and_tailing_whitespace=False,\n):\n    \"\"\"\n    As an example, the Danes and Germans use \" for inches and ' for feet,\n    so we will see data that contains nail (75 x 4 mm, 3\" x 3/12\"), so\n    for this case ( and ) are valid escapes, but \" and ' aren't.\n\n    \"\"\"\n    if openings is None:\n        openings = [None]\n    elif isinstance(openings, str):\n        self.openings = {c for c in openings}\n    else:\n        raise TypeError(f\"expected str, got {type(openings)}\")\n\n    if closures is None:\n        closures = [None]\n    elif isinstance(closures, str):\n        self.closures = {c for c in closures}\n    else:\n        raise TypeError(f\"expected str, got {type(closures)}\")\n\n    if not isinstance(delimiter, str):\n        raise TypeError(f\"expected str, got {type(delimiter)}\")\n    self.delimiter = delimiter\n    self._delimiter_length = len(delimiter)\n    self.strip_leading_and_tailing_whitespace = strip_leading_and_tailing_whitespace\n\n    if text_qualifier is None:\n        pass\n    elif text_qualifier in openings + closures:\n        raise ValueError(\"It's a bad idea to have qoute character appears in openings or closures.\")\n    else:\n        self.qoute = text_qualifier\n\n    if not text_qualifier:\n        if not self.strip_leading_and_tailing_whitespace:\n            self.c = self._call_1\n        else:\n            self.c = self._call_2\n    else:\n        self.c = self._call_3\n
    "},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.TextEscape-attributes","title":"Attributes","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.TextEscape.openings","title":"tablite.file_reader_utils.TextEscape.openings = {c for c in openings} instance-attribute","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.TextEscape.closures","title":"tablite.file_reader_utils.TextEscape.closures = {c for c in closures} instance-attribute","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.TextEscape.delimiter","title":"tablite.file_reader_utils.TextEscape.delimiter = delimiter instance-attribute","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.TextEscape.strip_leading_and_tailing_whitespace","title":"tablite.file_reader_utils.TextEscape.strip_leading_and_tailing_whitespace = strip_leading_and_tailing_whitespace instance-attribute","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.TextEscape.qoute","title":"tablite.file_reader_utils.TextEscape.qoute = text_qualifier instance-attribute","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.TextEscape.c","title":"tablite.file_reader_utils.TextEscape.c = self._call_1 instance-attribute","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.TextEscape-functions","title":"Functions","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.TextEscape.__call__","title":"tablite.file_reader_utils.TextEscape.__call__(s)","text":"Source code in tablite/file_reader_utils.py
    def __call__(self, s):\n    return self.c(s)\n
    "},{"location":"reference/file_reader_utils/#tablite.file_reader_utils-functions","title":"Functions","text":""},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.split_by_sequence","title":"tablite.file_reader_utils.split_by_sequence(text, sequence)","text":"

    helper to split text according to a split sequence.

    Source code in tablite/file_reader_utils.py
    def split_by_sequence(text, sequence):\n    \"\"\"helper to split text according to a split sequence.\"\"\"\n    chunks = tuple()\n    for element in sequence:\n        idx = text.find(element)\n        if idx < 0:\n            raise ValueError(f\"'{element}' not in row\")\n        chunk, text = text[:idx], text[len(element) + idx :]\n        chunks += (chunk,)\n    chunks += (text,)  # the remaining text.\n    return chunks\n
    "},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.detect_seperator","title":"tablite.file_reader_utils.detect_seperator(text)","text":"

    :param path: pathlib.Path objects :param encoding: file encoding. :return: 1 character.

    Source code in tablite/file_reader_utils.py
    def detect_seperator(text):\n    \"\"\"\n    :param path: pathlib.Path objects\n    :param encoding: file encoding.\n    :return: 1 character.\n    \"\"\"\n    # After reviewing the logic in the CSV sniffer, I concluded that all it\n    # really does is to look for a non-text character. As the separator is\n    # determined by the first line, which almost always is a line of headers,\n    # the text characters will be utf-8,16 or ascii letters plus white space.\n    # This leaves the characters ,;:| and \\t as potential separators, with one\n    # exception: files that use whitespace as separator. My logic is therefore\n    # to (1) find the set of characters that intersect with ',;:|\\t' which in\n    # practice is a single character, unless (2) it is empty whereby it must\n    # be whitespace.\n    if len(text) == 0:\n        return None\n    seps = {\",\", \"\\t\", \";\", \":\", \"|\"}.intersection(text)\n    if not seps:\n        if \" \" in text:\n            return \" \"\n        if \"\\n\" in text:\n            return \"\\n\"\n        else:\n            raise ValueError(\"separator not detected\")\n    if len(seps) == 1:\n        return seps.pop()\n    else:\n        frq = [(text.count(i), i) for i in seps]\n        frq.sort(reverse=True)  # most frequent first.\n        return frq[0][-1]\n
    "},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.text_reader_headers","title":"tablite.file_reader_utils.text_reader_headers(path, delimiter, header_row_index, text_qualifier, linecount)","text":"Source code in tablite/file_reader_utils.py
    def text_reader_headers(path, delimiter, header_row_index, text_qualifier, linecount):\n    d = {}\n    delimiters = {\n        \".csv\": \",\",\n        \".tsv\": \"\\t\",\n        \".txt\": None,\n    }\n\n    try:\n        with path.open(\"rb\") as fi:\n            rawdata = fi.read(ENCODING_GUESS_BYTES)\n            encoding = chardet.detect(rawdata)[\"encoding\"]\n\n        if delimiter is None:\n            with path.open(\"r\", encoding=encoding, errors=\"ignore\") as fi:\n                lines = []\n                for n, line in enumerate(fi, -header_row_index):\n                    if n < 0:\n                        continue\n                    line = line.rstrip(\"\\n\")\n                    lines.append(line)\n                    if n >= linecount:\n                        break  # break on first\n                try:\n                    d[\"delimiter\"] = delimiter = detect_seperator(\"\\n\".join(lines))\n                except ValueError as e:\n                    if e.args == (\"separator not detected\", ):\n                        d[\"delimiter\"] = delimiter = None # this will handle the case of 1 column, 1 row\n                    else:\n                        raise e\n\n        if delimiter is None:\n            d[\"delimiter\"] = delimiter = delimiters[path.suffix]  # pickup the default one\n            d[path.name] = [lines]\n            d[\"is_empty\"] = True  # mark as empty to return an empty table instead of throwing\n        else:\n            kwargs = {}\n\n            if text_qualifier is not None:\n                kwargs[\"text_qualifier\"] = text_qualifier\n                kwargs[\"quoting\"] = \"QUOTE_MINIMAL\"\n            else:\n                kwargs[\"quoting\"] = \"QUOTE_NONE\"\n\n            d[path.name] = _get_headers(\n                str(path), py_to_nim_encoding(encoding), header_row_index=header_row_index,\n                delimiter=delimiter,\n                linecount=linecount,\n                **kwargs\n            )\n        return d\n    except Exception as e:\n        raise ValueError(f\"can't read {path.suffix}\")\n
    "},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.excel_reader_headers","title":"tablite.file_reader_utils.excel_reader_headers(path, delimiter, header_row_index, text_qualifier, linecount)","text":"Source code in tablite/file_reader_utils.py
    def excel_reader_headers(path, delimiter, header_row_index, text_qualifier, linecount):\n    d = {}\n    book = openpyxl.open(str(path), read_only=True)\n\n    try:\n        all_sheets = book.sheetnames\n\n        for sheet_name, sheet in ((name, book[name]) for name in all_sheets):\n            fixup_worksheet(sheet)\n            if sheet.max_row is None:\n                max_rows = 0\n            else:\n                max_rows = min(sheet.max_row, linecount + 1)\n            container = [None] * max_rows\n            padding_ends = 0\n            max_column = sheet.max_column\n\n            for i, row_data in enumerate(sheet.iter_rows(0, header_row_index + max_rows, values_only=True), start=-header_row_index):\n                if i < 0:\n                    # NOTE: for some reason `iter_rows` specifying a start row starts reading cells as binary, instead skip the rows that are before our first read row\n                    continue\n\n                # NOTE: text readers do not cast types and give back strings, neither should xlsx reader, can't find documentation if it's possible to ignore this via `iter_rows` instead of casting back to string\n                container[i] = [DataTypes.to_json(v) for v in row_data]\n\n                for j, cell in enumerate(reversed(row_data)):\n                    if cell is None:\n                        continue\n\n                    padding_ends = max(padding_ends, max_column - j)\n\n                    break\n\n            d[sheet_name] = [None if c is None else c[0:padding_ends] for c in container]\n            d[\"delimiter\"] = None\n    finally:\n        book.close()\n\n    return d\n
    "},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.ods_reader_headers","title":"tablite.file_reader_utils.ods_reader_headers(path, delimiter, header_row_index, text_qualifier, linecount)","text":"Source code in tablite/file_reader_utils.py
    def ods_reader_headers(path, delimiter, header_row_index, text_qualifier, linecount):\n    d = {\n        \"delimiter\": None\n    }\n    sheets = pyexcel.get_book_dict(file_name=str(path))\n\n    for sheet_name, data in sheets.items():\n        lines = [[DataTypes.to_json(v) for v in row] for row in data[header_row_index:header_row_index+linecount]]\n\n        d[sheet_name] = lines\n\n    return d\n
    "},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.get_headers","title":"tablite.file_reader_utils.get_headers(path, delimiter=None, header_row_index=0, text_qualifier=None, linecount=10)","text":"

    file format definition csv comma separated values tsv tab separated values csvz a zip file that contains one or many csv files tsvz a zip file that contains one or many tsv files xls a spreadsheet file format created by MS-Excel 97-2003 xlsx MS-Excel Extensions to the Office Open XML SpreadsheetML File Format. xlsm an MS-Excel Macro-Enabled Workbook file ods open document spreadsheet fods flat open document spreadsheet json java script object notation html html table of the data structure simple simple presentation rst rStructured Text presentation of the data mediawiki media wiki table

    Source code in tablite/file_reader_utils.py
    def get_headers(path, delimiter=None, header_row_index=0, text_qualifier=None, linecount=10):\n    \"\"\"\n    file format\tdefinition\n    csv\t    comma separated values\n    tsv\t    tab separated values\n    csvz\ta zip file that contains one or many csv files\n    tsvz\ta zip file that contains one or many tsv files\n    xls\t    a spreadsheet file format created by MS-Excel 97-2003\n    xlsx\tMS-Excel Extensions to the Office Open XML SpreadsheetML File Format.\n    xlsm\tan MS-Excel Macro-Enabled Workbook file\n    ods\t    open document spreadsheet\n    fods\tflat open document spreadsheet\n    json\tjava script object notation\n    html\thtml table of the data structure\n    simple\tsimple presentation\n    rst\t    rStructured Text presentation of the data\n    mediawiki\tmedia wiki table\n    \"\"\"\n    if isinstance(path, str):\n        path = Path(path)\n    if not isinstance(path, Path):\n        raise TypeError(\"expected pathlib path.\")\n    if not path.exists():\n        raise FileNotFoundError(str(path))\n    if delimiter is not None:\n        if not isinstance(delimiter, str):\n            raise TypeError(f\"expected str or None, not {type(delimiter)}\")\n\n    kwargs = {\n        \"path\": path,\n        \"delimiter\": delimiter,\n        \"header_row_index\": header_row_index,\n        \"text_qualifier\": text_qualifier,\n        \"linecount\": linecount\n   }\n\n    reader = header_readers.get(path.suffix[1:], None)\n\n    if reader is None:\n        raise TypeError(f\"file format for headers not supported: {path.suffix}\")\n\n    result = reader(**kwargs)\n\n    return result\n
    "},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.get_encoding","title":"tablite.file_reader_utils.get_encoding(path, nbytes=ENCODING_GUESS_BYTES)","text":"Source code in tablite/file_reader_utils.py
    def get_encoding(path, nbytes=ENCODING_GUESS_BYTES):\n    nbytes = min(nbytes, path.stat().st_size)\n    with path.open(\"rb\") as fi:\n        rawdata = fi.read(nbytes)\n        encoding = chardet.detect(rawdata)[\"encoding\"]\n        if encoding == \"ascii\":  # utf-8 is backwards compatible with ascii\n            return \"utf-8\"  # --   so should the first 10k chars not be enough,\n        return encoding  # --      the utf-8 encoding will still get it right.\n
    "},{"location":"reference/file_reader_utils/#tablite.file_reader_utils.get_delimiter","title":"tablite.file_reader_utils.get_delimiter(path, encoding)","text":"Source code in tablite/file_reader_utils.py
    def get_delimiter(path, encoding):\n    with path.open(\"r\", encoding=encoding, errors=\"ignore\") as fi:\n        lines = []\n        for n, line in enumerate(fi):\n            line = line.rstrip(\"\\n\")\n            lines.append(line)\n            if n > 10:\n                break  # break on first\n        delimiter = detect_seperator(\"\\n\".join(lines))\n        if delimiter is None:\n            raise ValueError(\"Delimiter could not be determined\")\n        return delimiter\n
    "},{"location":"reference/groupby_utils/","title":"Groupby utils","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils","title":"tablite.groupby_utils","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils-classes","title":"Classes","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy","title":"tablite.groupby_utils.GroupBy","text":"

    Bases: object

    "},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy-attributes","title":"Attributes","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.max","title":"tablite.groupby_utils.GroupBy.max = 'max' class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.min","title":"tablite.groupby_utils.GroupBy.min = 'min' class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.sum","title":"tablite.groupby_utils.GroupBy.sum = 'sum' class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.product","title":"tablite.groupby_utils.GroupBy.product = 'product' class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.first","title":"tablite.groupby_utils.GroupBy.first = 'first' class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.last","title":"tablite.groupby_utils.GroupBy.last = 'last' class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.count","title":"tablite.groupby_utils.GroupBy.count = 'count' class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.count_unique","title":"tablite.groupby_utils.GroupBy.count_unique = 'count_unique' class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.avg","title":"tablite.groupby_utils.GroupBy.avg = 'avg' class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.stdev","title":"tablite.groupby_utils.GroupBy.stdev = 'stdev' class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.median","title":"tablite.groupby_utils.GroupBy.median = 'median' class-attribute instance-attribute","text":""},{"location":"reference/groupby_utils/#tablite.groupby_utils.GroupBy.mode","title":"tablite.groupby_utils.GroupBy.mode = 'mode' class-attribute instance-attribute","text":""},{"location":"reference/import_utils/","title":"Import utils","text":""},{"location":"reference/import_utils/#tablite.import_utils","title":"tablite.import_utils","text":""},{"location":"reference/import_utils/#tablite.import_utils-attributes","title":"Attributes","text":""},{"location":"reference/import_utils/#tablite.import_utils.file_readers","title":"tablite.import_utils.file_readers = {'fods': excel_reader, 'json': excel_reader, 'html': from_html, 'hdf5': from_hdf5, 'simple': excel_reader, 'rst': excel_reader, 'mediawiki': excel_reader, 'xlsx': excel_reader, 'xls': excel_reader, 'xlsm': excel_reader, 'csv': text_reader, 'tsv': text_reader, 'txt': text_reader, 'ods': ods_reader} module-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.valid_readers","title":"tablite.import_utils.valid_readers = ','.join(list(file_readers.keys())) module-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils-classes","title":"Classes","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig","title":"tablite.import_utils.TRconfig(source, destination, start, end, guess_datatypes, delimiter, text_qualifier, text_escape_openings, text_escape_closures, strip_leading_and_tailing_whitespace, encoding, newline_offsets, fields)","text":"

    Bases: object

    Source code in tablite/import_utils.py
    def __init__(\n    self,\n    source,\n    destination,\n    start,\n    end,\n    guess_datatypes,\n    delimiter,\n    text_qualifier,\n    text_escape_openings,\n    text_escape_closures,\n    strip_leading_and_tailing_whitespace,\n    encoding,\n    newline_offsets,\n    fields\n) -> None:\n    self.source = source\n    self.destination = destination\n    self.start = start\n    self.end = end\n    self.guess_datatypes = guess_datatypes\n    self.delimiter = delimiter\n    self.text_qualifier = text_qualifier\n    self.text_escape_openings = text_escape_openings\n    self.text_escape_closures = text_escape_closures\n    self.strip_leading_and_tailing_whitespace = strip_leading_and_tailing_whitespace\n    self.encoding = encoding\n    self.newline_offsets = newline_offsets\n    self.fields = fields\n    type_check(start, int),\n    type_check(end, int),\n    type_check(delimiter, str),\n    type_check(text_qualifier, (str, type(None))),\n    type_check(text_escape_openings, str),\n    type_check(text_escape_closures, str),\n    type_check(encoding, str),\n    type_check(strip_leading_and_tailing_whitespace, bool),\n    type_check(newline_offsets, list)\n    type_check(fields, dict)\n
    "},{"location":"reference/import_utils/#tablite.import_utils.TRconfig-attributes","title":"Attributes","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.source","title":"tablite.import_utils.TRconfig.source = source instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.destination","title":"tablite.import_utils.TRconfig.destination = destination instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.start","title":"tablite.import_utils.TRconfig.start = start instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.end","title":"tablite.import_utils.TRconfig.end = end instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.guess_datatypes","title":"tablite.import_utils.TRconfig.guess_datatypes = guess_datatypes instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.delimiter","title":"tablite.import_utils.TRconfig.delimiter = delimiter instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.text_qualifier","title":"tablite.import_utils.TRconfig.text_qualifier = text_qualifier instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.text_escape_openings","title":"tablite.import_utils.TRconfig.text_escape_openings = text_escape_openings instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.text_escape_closures","title":"tablite.import_utils.TRconfig.text_escape_closures = text_escape_closures instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.strip_leading_and_tailing_whitespace","title":"tablite.import_utils.TRconfig.strip_leading_and_tailing_whitespace = strip_leading_and_tailing_whitespace instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.encoding","title":"tablite.import_utils.TRconfig.encoding = encoding instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.newline_offsets","title":"tablite.import_utils.TRconfig.newline_offsets = newline_offsets instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.fields","title":"tablite.import_utils.TRconfig.fields = fields instance-attribute","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig-functions","title":"Functions","text":""},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.copy","title":"tablite.import_utils.TRconfig.copy()","text":"Source code in tablite/import_utils.py
    def copy(self):\n    return TRconfig(**self.dict())\n
    "},{"location":"reference/import_utils/#tablite.import_utils.TRconfig.dict","title":"tablite.import_utils.TRconfig.dict()","text":"Source code in tablite/import_utils.py
    def dict(self):\n    return {k: v for k, v in self.__dict__.items() if not (k.startswith(\"_\") or callable(v))}\n
    "},{"location":"reference/import_utils/#tablite.import_utils-functions","title":"Functions","text":""},{"location":"reference/import_utils/#tablite.import_utils.from_pandas","title":"tablite.import_utils.from_pandas(T, df)","text":"

    Creates Table using pd.to_dict('list')

    similar to:

    import pandas as pd df = pd.DataFrame({'a':[1,2,3], 'b':[4,5,6]}) df a b 0 1 4 1 2 5 2 3 6 df.to_dict('list')

    t = Table.from_dict(df.to_dict('list)) t.show() +===+===+===+ | # | a | b | |row|int|int| +---+---+---+ | 0 | 1| 4| | 1 | 2| 5| | 2 | 3| 6| +===+===+===+

    Source code in tablite/import_utils.py
    def from_pandas(T, df):\n    \"\"\"\n    Creates Table using pd.to_dict('list')\n\n    similar to:\n    >>> import pandas as pd\n    >>> df = pd.DataFrame({'a':[1,2,3], 'b':[4,5,6]})\n    >>> df\n        a  b\n        0  1  4\n        1  2  5\n        2  3  6\n    >>> df.to_dict('list')\n    {'a': [1, 2, 3], 'b': [4, 5, 6]}\n\n    >>> t = Table.from_dict(df.to_dict('list))\n    >>> t.show()\n        +===+===+===+\n        | # | a | b |\n        |row|int|int|\n        +---+---+---+\n        | 0 |  1|  4|\n        | 1 |  2|  5|\n        | 2 |  3|  6|\n        +===+===+===+\n    \"\"\"\n    if not issubclass(T, BaseTable):\n        raise TypeError(\"Expected subclass of Table\")\n\n    return T(columns=df.to_dict(\"list\"))  # noqa\n
    "},{"location":"reference/import_utils/#tablite.import_utils.from_hdf5","title":"tablite.import_utils.from_hdf5(T, path, tqdm=_tqdm, pbar=None)","text":"

    imports an exported hdf5 table.

    Note that some loss of type information is to be expected in columns of mixed type:

    t.show(dtype=True) +===+===+=====+=====+====+=====+=====+===================+==========+========+===============+===+=========================+=====+===+ | # | A | B | C | D | E | F | G | H | I | J | K | L | M | O | |row|int|mixed|float|str |mixed| bool| datetime | date | time | timedelta |str| int |float|int| +---+---+-----+-----+----+-----+-----+-------------------+----------+--------+---------------+---+-------------------------+-----+---+ | 0 | -1|None | -1.1| |None |False|2023-06-09 09:12:06|2023-06-09|09:12:06| 1 day, 0:00:00|b |-100000000000000000000000| inf| 11| | 1 | 1| 1| 1.1|1000|1 | True|2023-06-09 09:12:06|2023-06-09|09:12:06|2 days, 0:06:40|\u55e8 | 100000000000000000000000| -inf|-11| +===+===+=====+=====+====+=====+=====+===================+==========+========+===============+===+=========================+=====+===+ t.to_hdf5(filename) t2 = Table.from_hdf5(filename) t2.show(dtype=True) +===+===+=====+=====+=====+=====+=====+===================+===================+========+===============+===+=========================+=====+===+ | # | A | B | C | D | E | F | G | H | I | J | K | L | M | O | |row|int|mixed|float|mixed|mixed| bool| datetime | datetime | time | str |str| int |float|int| +---+---+-----+-----+-----+-----+-----+-------------------+-------------------+--------+---------------+---+-------------------------+-----+---+ | 0 | -1|None | -1.1|None |None |False|2023-06-09 09:12:06|2023-06-09 00:00:00|09:12:06|1 day, 0:00:00 |b |-100000000000000000000000| inf| 11| | 1 | 1| 1| 1.1| 1000| 1| True|2023-06-09 09:12:06|2023-06-09 00:00:00|09:12:06|2 days, 0:06:40|\u55e8 | 100000000000000000000000| -inf|-11| +===+===+=====+=====+=====+=====+=====+===================+===================+========+===============+===+=========================+=====+===+

    Source code in tablite/import_utils.py
    def from_hdf5(T, path, tqdm=_tqdm, pbar=None):\n    \"\"\"\n    imports an exported hdf5 table.\n\n    Note that some loss of type information is to be expected in columns of mixed type:\n    >>> t.show(dtype=True)\n    +===+===+=====+=====+====+=====+=====+===================+==========+========+===============+===+=========================+=====+===+\n    | # | A |  B  |  C  | D  |  E  |  F  |         G         |    H     |   I    |       J       | K |            L            |  M  | O |\n    |row|int|mixed|float|str |mixed| bool|      datetime     |   date   |  time  |   timedelta   |str|           int           |float|int|\n    +---+---+-----+-----+----+-----+-----+-------------------+----------+--------+---------------+---+-------------------------+-----+---+\n    | 0 | -1|None | -1.1|    |None |False|2023-06-09 09:12:06|2023-06-09|09:12:06| 1 day, 0:00:00|b  |-100000000000000000000000|  inf| 11|\n    | 1 |  1|    1|  1.1|1000|1    | True|2023-06-09 09:12:06|2023-06-09|09:12:06|2 days, 0:06:40|\u55e8 | 100000000000000000000000| -inf|-11|\n    +===+===+=====+=====+====+=====+=====+===================+==========+========+===============+===+=========================+=====+===+\n    >>> t.to_hdf5(filename)\n    >>> t2 = Table.from_hdf5(filename)\n    >>> t2.show(dtype=True)\n    +===+===+=====+=====+=====+=====+=====+===================+===================+========+===============+===+=========================+=====+===+\n    | # | A |  B  |  C  |  D  |  E  |  F  |         G         |         H         |   I    |       J       | K |            L            |  M  | O |\n    |row|int|mixed|float|mixed|mixed| bool|      datetime     |      datetime     |  time  |      str      |str|           int           |float|int|\n    +---+---+-----+-----+-----+-----+-----+-------------------+-------------------+--------+---------------+---+-------------------------+-----+---+\n    | 0 | -1|None | -1.1|None |None |False|2023-06-09 09:12:06|2023-06-09 00:00:00|09:12:06|1 day, 0:00:00 |b  |-100000000000000000000000|  inf| 11|\n    | 1 |  1|    1|  1.1| 1000|    1| True|2023-06-09 09:12:06|2023-06-09 00:00:00|09:12:06|2 days, 0:06:40|\u55e8 | 100000000000000000000000| -inf|-11|\n    +===+===+=====+=====+=====+=====+=====+===================+===================+========+===============+===+=========================+=====+===+\n    \"\"\"\n    if not issubclass(T, BaseTable):\n        raise TypeError(\"Expected subclass of Table\")\n    import h5py\n\n    type_check(path, Path)\n    t = T()\n    with h5py.File(path, \"r\") as h5:\n        for col_name in h5.keys():\n            dset = h5[col_name]\n            arr = np.array(dset[:])\n            if arr.dtype == object:\n                arr = np.array(DataTypes.guess([v.decode(\"utf-8\") for v in arr]))\n            t[col_name] = arr\n    return t\n
    "},{"location":"reference/import_utils/#tablite.import_utils.from_json","title":"tablite.import_utils.from_json(T, jsn)","text":"

    Imports tables exported using .to_json

    Source code in tablite/import_utils.py
    def from_json(T, jsn):\n    \"\"\"\n    Imports tables exported using .to_json\n    \"\"\"\n    if not issubclass(T, BaseTable):\n        raise TypeError(\"Expected subclass of Table\")\n    import json\n\n    type_check(jsn, str)\n    d = json.loads(jsn)\n    return T(columns=d[\"columns\"])\n
    "},{"location":"reference/import_utils/#tablite.import_utils.from_html","title":"tablite.import_utils.from_html(T, path, tqdm=_tqdm, pbar=None)","text":"Source code in tablite/import_utils.py
    def from_html(T, path, tqdm=_tqdm, pbar=None):\n    if not issubclass(T, BaseTable):\n        raise TypeError(\"Expected subclass of Table\")\n    type_check(path, Path)\n\n    if pbar is None:\n        total = path.stat().st_size\n        pbar = tqdm(total=total, desc=\"from_html\", disable=Config.TQDM_DISABLE)\n\n    row_start, row_end = \"<tr>\", \"</tr>\"\n    value_start, value_end = \"<th>\", \"</th>\"\n    chunk = \"\"\n    t = None  # will be T()\n    start, end = 0, 0\n    data = {}\n    with path.open(\"r\") as fi:\n        while True:\n            start = chunk.find(row_start, start)  # row tag start\n            end = chunk.find(row_end, end)  # row tag end\n            if start == -1 or end == -1:\n                new = fi.read(100_000)\n                pbar.update(len(new))\n                if new == \"\":\n                    break\n                chunk += new\n                continue\n            # get indices from chunk\n            row = chunk[start + len(row_start) : end]\n            fields = [v.rstrip(value_end) for v in row.split(value_start)]\n            if not data:\n                headers = fields[:]\n                data = {f: [] for f in headers}\n                continue\n            else:\n                for field, header in zip(fields, headers):\n                    data[header].append(field)\n\n            chunk = chunk[end + len(row_end) :]\n\n            if len(data[headers[0]]) == Config.PAGE_SIZE:\n                if t is None:\n                    t = T(columns=data)\n                else:\n                    for k, v in data.items():\n                        t[k].extend(DataTypes.guess(v))\n                data = {f: [] for f in headers}\n\n    for k, v in data.items():\n        t[k].extend(DataTypes.guess(v))\n    return t\n
    "},{"location":"reference/import_utils/#tablite.import_utils.excel_reader","title":"tablite.import_utils.excel_reader(T, path, first_row_has_headers=True, header_row_index=0, sheet=None, columns=None, skip_empty='NONE', start=0, limit=sys.maxsize, tqdm=_tqdm, **kwargs)","text":"

    returns Table from excel

    **kwargs are excess arguments that are ignored.

    Source code in tablite/import_utils.py
    def excel_reader(T, path, first_row_has_headers=True, header_row_index=0, sheet=None, columns=None, skip_empty=\"NONE\", start=0, limit=sys.maxsize, tqdm=_tqdm, **kwargs):\n    \"\"\"\n    returns Table from excel\n\n    **kwargs are excess arguments that are ignored.\n    \"\"\"\n    if not issubclass(T, BaseTable):\n        raise TypeError(\"Expected subclass of Table\")\n\n    book = openpyxl.load_workbook(path, read_only=True, data_only=True)\n\n    if sheet is None:  # help the user.\n        \"\"\"\n            If no sheet specified, assume first sheet.\n\n            Reasoning:\n                Pandas ODS reader does that, so this preserves parity and it might be expected by users.\n                If we don't know the sheet name but only have single sheet,\n                    we would need to take extra steps to find out the name of the sheet.\n                We already make assumptions in case of column selection,\n                    when columns are None, we import all of them.\n        \"\"\"\n        sheet = book.sheetnames[0]\n    elif sheet not in book.sheetnames:\n        raise ValueError(f\"sheet not found: {sheet}\")\n\n    if not (isinstance(start, int) and start >= 0):\n        raise ValueError(\"expected start as an integer >=0\")\n    if not (isinstance(limit, int) and limit > 0):\n        raise ValueError(\"expected limit as integer > 0\")\n\n    worksheet = book[sheet]\n    fixup_worksheet(worksheet)\n\n    try:\n        it_header = worksheet.iter_rows(min_row=header_row_index + 1)\n        while True:\n            # get the first row to know our headers or the number of columns\n            row = [c.value for c in next(it_header)]\n            break\n        fields = [str(c) if c is not None else \"\" for c in row] # excel is offset by 1\n    except StopIteration:\n        # excel was empty, return empty table\n        return T()\n\n    if not first_row_has_headers:\n        # since the first row did not contain headers, we use the column count to populate header names\n        fields = [str(i) for i in range(len(fields))]\n\n    if columns is None:\n        # no columns were specified by user to import, that means we import all of the them\n        columns = []\n\n        for f in fields:\n            # fixup the duplicate column names\n            columns.append(unique_name(f, columns))\n\n        field_dict = {k: i for i, k in enumerate(columns)}\n    else:\n        field_dict = {}\n\n        for k, i in ((k, fields.index(k)) for k in columns):\n            # fixup the duplicate column names\n            field_dict[unique_name(k, field_dict.keys())] = i\n\n    # calculate our data rows iterator offset\n    it_offset = start + (1 if first_row_has_headers else 0) + header_row_index + 1\n\n    # attempt to fetch number of rows in the sheet\n    total_rows = worksheet.max_row\n    real_tqdm = True\n\n    if total_rows is None:\n        # i don't know what causes it but max_row can be None in some cases, so we don't know how large the dataset is\n        total_rows = it_offset + limit\n        real_tqdm = False\n\n    # create the actual data rows iterator\n    it_rows = worksheet.iter_rows(min_row=it_offset, max_row=min(it_offset+limit, total_rows))\n    it_used_indices = list(field_dict.values())\n\n    # filter columns that we're not going to use\n    it_rows_filtered = ([row[idx].value for idx in it_used_indices] for row in it_rows)\n\n    # create page directory\n    workdir = Path(Config.workdir) / Config.pid\n    pagesdir = workdir/\"pages\"\n    pagesdir.mkdir(exist_ok=True, parents=True)\n\n    field_names = list(field_dict.keys())\n    column_count = len(field_names)\n\n    page_fhs = None\n\n    # prepopulate the table with columns\n    table = T()\n    for name in field_names:\n        table[name] = Column(table.path)\n\n    pbar_fname = path.name\n    if len(pbar_fname) > 20:\n        pbar_fname = pbar_fname[0:10] + \"...\" + pbar_fname[-7:]\n\n    if real_tqdm:\n        # we can create a true tqdm progress bar, make one\n        tqdm_iter = tqdm(it_rows_filtered, total=total_rows, desc=f\"importing excel: {pbar_fname}\")\n    else:\n        \"\"\"\n            openpyxls was unable to precalculate the size of the excel for whatever reason\n            forcing recalc would require parsing entire file\n            drop the progress bar in that case, just show iterations\n\n            as an alternative we can use \u03a3=1/x but it just doesn't look good, show iterations per second instead\n        \"\"\"\n        tqdm_iter = tqdm(it_rows_filtered, desc=f\"importing excel: {pbar_fname}\")\n\n    tqdm_iter = iter(tqdm_iter)\n\n    idx = 0\n\n    while True:\n        try:\n            row = next(tqdm_iter)\n        except StopIteration:\n            break # because in some cases we can't know the size of excel to set the upper iterator limit we loop until stop iteration is encountered\n\n        if skip_empty == \"ALL\" and all(v is None for v in row):\n            continue\n        elif skip_empty == \"ANY\" and any(v is None for v in row):\n            continue\n\n        if idx % Config.PAGE_SIZE == 0:\n            if page_fhs is not None:\n                # we reached the max page file size, fix the pages\n                [_fix_xls_page(table, c, fh) for c, fh in zip(field_names, page_fhs)]\n\n            page_fhs = [None] * column_count\n\n            for cidx in range(column_count):\n                # allocate new pages\n                pg_path = pagesdir / f\"{next(Page.ids)}.npy\"\n                page_fhs[cidx] = open(pg_path, \"wb\")\n\n        for fh, value in zip(page_fhs, row):\n            \"\"\"\n                since excel types are already cast into appropriate type we're going to do two passes per page\n\n                we create our temporary custom format:\n                packed type|packed byte count|packed bytes|...\n\n                available types:\n                    * q - int64\n                    * d - float64\n                    * s - string\n                    * b - boolean\n                    * n - none\n                    * p - pickled (date, time, datetime)\n            \"\"\"\n            dtype = type(value)\n\n            if dtype == int:\n                ptype, bytes_ = b'q', struct.pack('q', value) # pack int as int64\n            elif dtype == float:\n                ptype, bytes_ = b'd', struct.pack('d', value) # pack float as float64\n            elif dtype == str:\n                ptype, bytes_ = b's', value.encode(\"utf-8\")   # pack string\n            elif dtype == bool:\n                ptype, bytes_ = b'b', b'1' if value else b'0' # pack boolean\n            elif value is None:\n                ptype, bytes_ = b'n', b''                     # pack none\n            elif dtype in [date, time, datetime]:\n                ptype, bytes_ = b'p', pkl.dumps(value)        # pack object types via pickle\n            else:\n                raise NotImplementedError()\n\n            byte_count = struct.pack('I', len(bytes_))        # pack our payload size, i doubt payload size can be over uint32\n\n            # dump object to file\n            fh.write(ptype)\n            fh.write(byte_count)\n            fh.write(bytes_)\n\n        idx = idx + 1\n\n    if page_fhs is not None:\n        # we reached end of the loop, fix the pages\n        [_fix_xls_page(table, c, fh) for c, fh in zip(field_names, page_fhs)]\n\n    return table\n
    "},{"location":"reference/import_utils/#tablite.import_utils.ods_reader","title":"tablite.import_utils.ods_reader(T, path, first_row_has_headers=True, header_row_index=0, sheet=None, columns=None, skip_empty='NONE', start=0, limit=sys.maxsize, **kwargs)","text":"

    returns Table from .ODS

    Source code in tablite/import_utils.py
    def ods_reader(T, path, first_row_has_headers=True, header_row_index=0, sheet=None, columns=None, skip_empty=\"NONE\", start=0, limit=sys.maxsize, **kwargs):\n    \"\"\"\n    returns Table from .ODS\n    \"\"\"\n    if not issubclass(T, BaseTable):\n        raise TypeError(\"Expected subclass of Table\")\n\n    if sheet is None:\n        data = read_excel(str(path), header=None) # selects first sheet\n    else:\n        data = read_excel(str(path), sheet_name=sheet, header=None)\n\n    data[isna(data)] = None  # convert any empty cells to None\n    data = data.to_numpy().tolist() # convert pandas to list\n\n    if skip_empty == \"ALL\" or skip_empty == \"ANY\":\n        \"\"\" filter out all rows based on predicate that come after header row \"\"\"\n        fn_filter = any if skip_empty == \"ALL\" else all # this is intentional\n        data = [\n            row\n            for ridx, row in enumerate(data)\n            if ridx < header_row_index + (1 if first_row_has_headers else 0) or fn_filter(not (v is None or isinstance(v, str) and len(v) == 0) for v in row)\n        ]\n\n    data = np.array(data, dtype=np.object_) # cast back to numpy array for slicing but don't try to convert datatypes\n\n    if not (isinstance(start, int) and start >= 0):\n        raise ValueError(\"expected start as an integer >=0\")\n    if not (isinstance(limit, int) and limit > 0):\n        raise ValueError(\"expected limit as integer > 0\")\n\n    t = T()\n\n    used_columns_names = set()\n    for ix, value in enumerate(data[header_row_index]):\n        if first_row_has_headers:\n            header, start_row_pos = \"\" if value is None else str(value), (1 + header_row_index)\n        else:\n            header, start_row_pos = f\"_{ix + 1}\", (0 + header_row_index)\n\n        if columns is not None:\n            if header not in columns:\n                continue\n\n        unique_column_name = unique_name(str(header), used_columns_names)\n        used_columns_names.add(unique_column_name)\n\n        column_values = data[start_row_pos : start_row_pos + limit, ix]\n\n        t[unique_column_name] = column_values\n    return t\n
    "},{"location":"reference/import_utils/#tablite.import_utils.text_reader_task","title":"tablite.import_utils.text_reader_task(source, destination, start, end, guess_datatypes, delimiter, text_qualifier, text_escape_openings, text_escape_closures, strip_leading_and_tailing_whitespace, encoding, newline_offsets, fields)","text":"

    PARALLEL TASK FUNCTION reads columnsname + path[start:limit] into hdf5.

    source: csv or txt file destination: filename for page. start: int: start of page. end: int: end of page. guess_datatypes: bool: if True datatypes will be inferred by datatypes.Datatypes.guess delimiter: ',' ';' or '|' text_qualifier: str: commonly \" text_escape_openings: str: default: \"({[ text_escape_closures: str: default: ]})\" strip_leading_and_tailing_whitespace: bool encoding: chardet encoding ('utf-8, 'ascii', ..., 'ISO-22022-CN')

    Source code in tablite/import_utils.py
    def text_reader_task(\n    source,\n    destination,\n    start,\n    end,\n    guess_datatypes,\n    delimiter,\n    text_qualifier,\n    text_escape_openings,\n    text_escape_closures,\n    strip_leading_and_tailing_whitespace,\n    encoding,\n    newline_offsets,\n    fields\n):\n    \"\"\"PARALLEL TASK FUNCTION\n    reads columnsname + path[start:limit] into hdf5.\n\n    source: csv or txt file\n    destination: filename for page.\n    start: int: start of page.\n    end: int: end of page.\n    guess_datatypes: bool: if True datatypes will be inferred by datatypes.Datatypes.guess\n    delimiter: ',' ';' or '|'\n    text_qualifier: str: commonly \\\"\n    text_escape_openings: str: default: \"({[\n    text_escape_closures: str: default: ]})\"\n    strip_leading_and_tailing_whitespace: bool\n    encoding: chardet encoding ('utf-8, 'ascii', ..., 'ISO-22022-CN')\n    \"\"\"\n    if isinstance(source, str):\n        source = Path(source)\n    type_check(source, Path)\n    if not source.exists():\n        raise FileNotFoundError(f\"File not found: {source}\")\n    type_check(destination, list)\n\n    # declare CSV dialect.\n    delim = delimiter\n\n    class Dialect(csv.Dialect):\n        delimiter = delim\n        quotechar = '\"' if text_qualifier is None else text_qualifier\n        escapechar = '\\\\'\n        doublequote = True\n        quoting = csv.QUOTE_MINIMAL\n        skipinitialspace = False if strip_leading_and_tailing_whitespace is None else strip_leading_and_tailing_whitespace\n        lineterminator = \"\\n\"\n\n    with source.open(\"r\", encoding=encoding, errors=\"ignore\") as fi:  # --READ\n        fi.seek(newline_offsets[start])\n        reader = csv.reader(fi, dialect=Dialect)\n\n        # if there's an issue with file handlers on windows, we can make a special case for windows where the file is opened on demand and appended instead of opening all handlers at once\n        page_file_handlers = [open(f, mode=\"wb\") for f in destination]\n\n        # identify longest str\n        longest_str = [1 for _ in range(len(destination))]\n        for row in (next(reader) for _ in range(end - start)):\n            for idx, c in ((fields[idx], c) for idx, c in filter(lambda t: t[0] in fields, enumerate(row))):\n                longest_str[idx] = max(longest_str[idx], len(c))\n\n        column_formats = [f\"<U{i}\" for i in longest_str]\n        for idx, cf in enumerate(column_formats):\n            _create_numpy_header(cf, (end - start, ), page_file_handlers[idx])\n\n        # write page arrays to files\n        fi.seek(newline_offsets[start])\n        for row in (next(reader) for _ in range(end - start)):\n            for idx, c in ((fields[idx], c) for idx, c in filter(lambda t: t[0] in fields, enumerate(row))):\n                cbytes = np.asarray(c, dtype=column_formats[idx]).tobytes()\n                page_file_handlers[idx].write(cbytes)\n\n        [phf.close() for phf in page_file_handlers]\n
    "},{"location":"reference/import_utils/#tablite.import_utils.text_reader","title":"tablite.import_utils.text_reader(T, path, columns, first_row_has_headers, header_row_index, encoding, start, limit, newline, guess_datatypes, text_qualifier, strip_leading_and_tailing_whitespace, skip_empty, delimiter, text_escape_openings, text_escape_closures, tqdm=_tqdm, **kwargs)","text":"Source code in tablite/import_utils.py
    def text_reader(\n    T,\n    path,\n    columns,\n    first_row_has_headers,\n    header_row_index,\n    encoding,\n    start,\n    limit,\n    newline,\n    guess_datatypes,\n    text_qualifier,\n    strip_leading_and_tailing_whitespace,\n    skip_empty,\n    delimiter,\n    text_escape_openings,\n    text_escape_closures,\n    tqdm=_tqdm,\n    **kwargs,\n):\n    if encoding is None:\n        encoding = get_encoding(path, nbytes=ENCODING_GUESS_BYTES)\n\n    enc = py_to_nim_encoding(encoding)\n    pid = Config.workdir / Config.pid\n    kwargs = {}\n\n    if first_row_has_headers is not None:\n        kwargs[\"first_row_has_headers\"] = first_row_has_headers\n    if header_row_index is not None:\n        kwargs[\"header_row_index\"] = header_row_index\n    if columns is not None:\n        kwargs[\"columns\"] = columns\n    if start is not None:\n        kwargs[\"start\"] = start\n    if limit is not None and limit != sys.maxsize:\n        kwargs[\"limit\"] = limit\n    if guess_datatypes is not None:\n        kwargs[\"guess_datatypes\"] = guess_datatypes\n    if newline is not None:\n        kwargs[\"newline\"] = newline\n    if delimiter is not None:\n        kwargs[\"delimiter\"] = delimiter\n    if text_qualifier is not None:\n        kwargs[\"text_qualifier\"] = text_qualifier\n        kwargs[\"quoting\"] = \"QUOTE_MINIMAL\"\n    else:\n        kwargs[\"quoting\"] = \"QUOTE_NONE\"\n    if strip_leading_and_tailing_whitespace is not None:\n        kwargs[\"strip_leading_and_tailing_whitespace\"] = strip_leading_and_tailing_whitespace\n\n    if skip_empty is None:\n        kwargs[\"skip_empty\"] = \"NONE\"\n    else:\n        kwargs[\"skip_empty\"] = skip_empty\n\n    return nimlite.text_reader(\n        T, pid, path, enc,\n        **kwargs,\n        tqdm=tqdm\n    )\n
    "},{"location":"reference/import_utils/#tablite.import_utils-modules","title":"Modules","text":""},{"location":"reference/imputation/","title":"Imputation","text":""},{"location":"reference/imputation/#tablite.imputation","title":"tablite.imputation","text":""},{"location":"reference/imputation/#tablite.imputation-classes","title":"Classes","text":""},{"location":"reference/imputation/#tablite.imputation-functions","title":"Functions","text":""},{"location":"reference/imputation/#tablite.imputation.imputation","title":"tablite.imputation.imputation(T, targets, missing=None, method='carry forward', sources=None, tqdm=_tqdm, pbar=None)","text":"

    In statistics, imputation is the process of replacing missing data with substituted values.

    See more: https://en.wikipedia.org/wiki/Imputation_(statistics)

    PARAMETER DESCRIPTION table

    source table.

    TYPE: Table

    targets

    column names to find and replace missing values

    TYPE: str or list of strings

    missing

    values to be replaced.

    TYPE: None or iterable DEFAULT: None

    method

    method to be used for replacement. Options:

    'carry forward': takes the previous value, and carries forward into fields where values are missing. +: quick. Realistic on time series. -: Can produce strange outliers.

    'mean': calculates the column mean (exclude missing) and copies the mean in as replacement. +: quick -: doesn't work on text. Causes data set to drift towards the mean.

    'mode': calculates the column mode (exclude missing) and copies the mean in as replacement. +: quick -: most frequent value becomes over-represented in the sample

    'nearest neighbour': calculates normalised distance between items in source columns selects nearest neighbour and copies value as replacement. +: works for any datatype. -: computationally intensive (e.g. slow)

    TYPE: str DEFAULT: 'carry forward'

    sources

    NEAREST NEIGHBOUR ONLY column names to be used during imputation. if None or empty, all columns will be used.

    TYPE: list of strings DEFAULT: None

    RETURNS DESCRIPTION table

    table with replaced values.

    Source code in tablite/imputation.py
    def imputation(T, targets, missing=None, method=\"carry forward\", sources=None, tqdm=_tqdm, pbar=None):\n    \"\"\"\n    In statistics, imputation is the process of replacing missing data with substituted values.\n\n    See more: https://en.wikipedia.org/wiki/Imputation_(statistics)\n\n    Args:\n        table (Table): source table.\n\n        targets (str or list of strings): column names to find and\n            replace missing values\n\n        missing (None or iterable): values to be replaced.\n\n        method (str): method to be used for replacement. Options:\n\n            'carry forward':\n                takes the previous value, and carries forward into fields\n                where values are missing.\n                +: quick. Realistic on time series.\n                -: Can produce strange outliers.\n\n            'mean':\n                calculates the column mean (exclude `missing`) and copies\n                the mean in as replacement.\n                +: quick\n                -: doesn't work on text. Causes data set to drift towards the mean.\n\n            'mode':\n                calculates the column mode (exclude `missing`) and copies\n                the mean in as replacement.\n                +: quick\n                -: most frequent value becomes over-represented in the sample\n\n            'nearest neighbour':\n                calculates normalised distance between items in source columns\n                selects nearest neighbour and copies value as replacement.\n                +: works for any datatype.\n                -: computationally intensive (e.g. slow)\n\n        sources (list of strings): NEAREST NEIGHBOUR ONLY\n            column names to be used during imputation.\n            if None or empty, all columns will be used.\n\n    Returns:\n        table: table with replaced values.\n    \"\"\"\n    sub_cls_check(T, BaseTable)\n\n    if isinstance(targets, str) and targets not in T.columns:\n        targets = [targets]\n    if isinstance(targets, list):\n        for name in targets:\n            if not isinstance(name, str):\n                raise TypeError(f\"expected str, not {type(name)}\")\n            if name not in T.columns:\n                raise ValueError(f\"target item {name} not a column name in T.columns:\\n{T.columns}\")\n    else:\n        raise TypeError(\"Expected source as list of column names\")\n\n    if missing is None:\n        missing = {None}\n    else:\n        missing = set(missing)\n\n    if method == \"nearest neighbour\":\n        if sources in (None, []):\n            sources = list(T.columns)\n        if isinstance(sources, str):\n            sources = [sources]\n        if isinstance(sources, list):\n            for name in sources:\n                if not isinstance(name, str):\n                    raise TypeError(f\"expected str, not {type(name)}\")\n                if name not in T.columns:\n                    raise ValueError(f\"source item {name} not a column name in T.columns:\\n{T.columns}\")\n        else:\n            raise TypeError(\"Expected source as list of column names\")\n\n    methods = [\"nearest neighbour\", \"mean\", \"mode\", \"carry forward\"]\n\n    if method == \"carry forward\":\n        return carry_forward(T, targets, missing, tqdm=tqdm, pbar=pbar)\n    elif method in {\"mean\", \"mode\"}:\n        return stats_method(T, targets, missing, method, tqdm=tqdm, pbar=pbar)\n    elif method == \"nearest neighbour\":\n        return nearest_neighbour(T, sources, missing, targets, tqdm=tqdm)\n    else:\n        raise ValueError(f\"method {method} not recognised amonst known methods: {list(methods)})\")\n
    "},{"location":"reference/imputation/#tablite.imputation.carry_forward","title":"tablite.imputation.carry_forward(T, targets, missing, tqdm=_tqdm, pbar=None)","text":"Source code in tablite/imputation.py
    def carry_forward(T, targets, missing, tqdm=_tqdm, pbar=None):\n    assert isinstance(missing, set)\n\n    if pbar is None:\n        total = len(targets) * len(T)\n        pbar = tqdm(total=total, desc=\"imputation.carry_forward\", disable=Config.TQDM_DISABLE)\n\n    new = T.copy()\n    for name in T.columns:\n        if name in targets:\n            data = T[name][:]  # create copy\n            last_value = None\n            for ix, v in enumerate(data):\n                if v in missing:  # perform replacement\n                    data[ix] = last_value\n                else:  # keep last value.\n                    last_value = v\n                pbar.update(1)\n            new[name] = data\n        else:\n            new[name] = T[name]\n\n    return new\n
    "},{"location":"reference/imputation/#tablite.imputation.stats_method","title":"tablite.imputation.stats_method(T, targets, missing, method, tqdm=_tqdm, pbar=None)","text":"Source code in tablite/imputation.py
    def stats_method(T, targets, missing, method, tqdm=_tqdm, pbar=None):\n    assert isinstance(missing, set)\n\n    if pbar is None:\n        total = len(targets)\n        pbar = tqdm(total=total, desc=f\"imputation.{method}\", disable=Config.TQDM_DISABLE)\n\n    new = T.copy()\n    for name in T.columns:\n        if name in targets:\n            col = T.columns[name]\n            assert isinstance(col, Column)\n\n            hist_values, hist_counts = col.histogram()\n\n            for m in missing:\n                try:\n                    idx = hist_values.index(m)\n                    hist_counts[idx] = 0\n                except ValueError:\n                    pass\n\n            stats = summary_statistics(hist_values, hist_counts)\n\n            new_value = stats[method]\n            col.replace(mapping={m: new_value for m in missing})\n            new[name] = col\n            pbar.update(1)\n        else:\n            new[name] = T[name]  # no entropy, keep as is.\n\n    return new\n
    "},{"location":"reference/imputation/#tablite.imputation-modules","title":"Modules","text":""},{"location":"reference/joins/","title":"Joins","text":""},{"location":"reference/joins/#tablite.joins","title":"tablite.joins","text":""},{"location":"reference/joins/#tablite.joins-classes","title":"Classes","text":""},{"location":"reference/joins/#tablite.joins-functions","title":"Functions","text":""},{"location":"reference/joins/#tablite.joins.join","title":"tablite.joins.join(T: BaseTable, other: BaseTable, left_keys: List[str], right_keys: List[str], left_columns: Union[List[str], None], right_columns: Union[List[str], None], kind: str = 'inner', merge_keys: bool = False, tqdm=_tqdm, pbar=None)","text":"

    short-cut for all join functions.

    PARAMETER DESCRIPTION T

    left table

    TYPE: Table

    other

    right table

    TYPE: Table

    left_keys

    list of keys for the join from left table.

    TYPE: list

    right_keys

    list of keys for the join from right table.

    TYPE: list

    left_columns

    list of columns names to retain from left table. If None, all are retained.

    TYPE: list

    right_columns

    list of columns names to retain from right table. If None, all are retained.

    TYPE: list

    kind

    'inner', 'left', 'outer', 'cross'. Defaults to \"inner\".

    TYPE: str DEFAULT: 'inner'

    tqdm

    tqdm progress counter. Defaults to _tqdm.

    TYPE: tqdm DEFAULT: tqdm

    pbar

    tqdm.progressbar. Defaults to None.

    TYPE: pbar DEFAULT: None

    RAISES DESCRIPTION ValueError

    if join type is unknown.

    RETURNS DESCRIPTION Table

    joined table.

    Example: \"inner\"

    SQL:   SELECT number, letter FROM numbers JOIN letters ON numbers.colour == letters.color\n

    Tablite:

    >>> inner_join = numbers.inner_join(\n    letters, \n    left_keys=['colour'], \n    right_keys=['color'], \n    left_columns=['number'], \n    right_columns=['letter']\n)\n

    Example: \"left\"

    SQL:   SELECT number, letter FROM numbers LEFT JOIN letters ON numbers.colour == letters.color\n

    Tablite:

    >>> left_join = numbers.left_join(\n    letters, \n    left_keys=['colour'], \n    right_keys=['color'], \n    left_columns=['number'], \n    right_columns=['letter']\n)\n

    Example: \"outer\"

    SQL:   SELECT number, letter FROM numbers OUTER JOIN letters ON numbers.colour == letters.color\n

    Tablite:

    >>> outer_join = numbers.outer_join(\n    letters, \n    left_keys=['colour'], \n    right_keys=['color'], \n    left_columns=['number'], \n    right_columns=['letter']\n    )\n

    Example: \"cross\"

    CROSS JOIN returns the Cartesian product of rows from tables in the join. In other words, it will produce rows which combine each row from the first table with each row from the second table

    Source code in tablite/joins.py
    def join(\n    T: BaseTable,\n    other: BaseTable,\n    left_keys: List[str],\n    right_keys: List[str],\n    left_columns: Union[List[str], None],\n    right_columns: Union[List[str], None],\n    kind: str = \"inner\",\n    merge_keys: bool = False,\n    tqdm=_tqdm,\n    pbar=None,\n):\n    \"\"\"short-cut for all join functions.\n\n    Args:\n        T (Table): left table\n        other (Table): right table\n        left_keys (list): list of keys for the join from left table.\n        right_keys (list): list of keys for the join from right table.\n        left_columns (list): list of columns names to retain from left table.\n            If None, all are retained.\n        right_columns (list): list of columns names to retain from right table.\n            If None, all are retained.\n        kind (str, optional): 'inner', 'left', 'outer', 'cross'. Defaults to \"inner\".\n        tqdm (tqdm, optional): tqdm progress counter. Defaults to _tqdm.\n        pbar (tqdm.pbar, optional): tqdm.progressbar. Defaults to None.\n\n    Raises:\n        ValueError: if join type is unknown.\n\n    Returns:\n        Table: joined table.\n\n    Example: \"inner\"\n    ```\n    SQL:   SELECT number, letter FROM numbers JOIN letters ON numbers.colour == letters.color\n    ```\n    Tablite: \n    ```\n    >>> inner_join = numbers.inner_join(\n        letters, \n        left_keys=['colour'], \n        right_keys=['color'], \n        left_columns=['number'], \n        right_columns=['letter']\n    )\n    ```\n\n    Example: \"left\" \n    ```\n    SQL:   SELECT number, letter FROM numbers LEFT JOIN letters ON numbers.colour == letters.color\n    ```\n    Tablite: \n    ```\n    >>> left_join = numbers.left_join(\n        letters, \n        left_keys=['colour'], \n        right_keys=['color'], \n        left_columns=['number'], \n        right_columns=['letter']\n    )\n    ```\n\n    Example: \"outer\"\n    ```\n    SQL:   SELECT number, letter FROM numbers OUTER JOIN letters ON numbers.colour == letters.color\n    ```\n\n    Tablite: \n    ```\n    >>> outer_join = numbers.outer_join(\n        letters, \n        left_keys=['colour'], \n        right_keys=['color'], \n        left_columns=['number'], \n        right_columns=['letter']\n        )\n    ```\n\n    Example: \"cross\"\n\n    CROSS JOIN returns the Cartesian product of rows from tables in the join.\n    In other words, it will produce rows which combine each row from the first table\n    with each row from the second table\n    \"\"\"\n    if left_columns is None:\n        left_columns = list(T.columns)\n    if right_columns is None:\n        right_columns = list(other.columns)\n    assert merge_keys in {True,False}\n\n    _jointype_check(T, other, left_keys, right_keys, left_columns, right_columns)\n\n    return _join(kind, T,other,left_keys, right_keys, left_columns, right_columns, merge_keys=merge_keys,\n             tqdm=tqdm, pbar=pbar)\n
    "},{"location":"reference/joins/#tablite.joins.inner_join","title":"tablite.joins.inner_join(T: BaseTable, other: BaseTable, left_keys: List[str], right_keys: List[str], left_columns: Union[List[str], None], right_columns: Union[List[str], None], merge_keys: bool = False, tqdm=_tqdm, pbar=None)","text":"Source code in tablite/joins.py
    def inner_join(T: BaseTable, other: BaseTable, left_keys: List[str], right_keys: List[str], \n              left_columns: Union[List[str], None], right_columns: Union[List[str], None],\n              merge_keys: bool = False, tqdm=_tqdm, pbar=None):\n    return join(T, other, left_keys, right_keys, left_columns, right_columns, kind=\"inner\", merge_keys=merge_keys, tqdm=tqdm,pbar=pbar)\n
    "},{"location":"reference/joins/#tablite.joins.left_join","title":"tablite.joins.left_join(T: BaseTable, other: BaseTable, left_keys: List[str], right_keys: List[str], left_columns: Union[List[str], None], right_columns: Union[List[str], None], merge_keys: bool = False, tqdm=_tqdm, pbar=None)","text":"Source code in tablite/joins.py
    def left_join(T: BaseTable, other: BaseTable, left_keys: List[str], right_keys: List[str], \n              left_columns: Union[List[str], None], right_columns: Union[List[str], None],\n              merge_keys: bool = False, tqdm=_tqdm, pbar=None):\n    return join(T, other, left_keys, right_keys, left_columns, right_columns, kind=\"left\", merge_keys=merge_keys, tqdm=tqdm,pbar=pbar)\n
    "},{"location":"reference/joins/#tablite.joins.outer_join","title":"tablite.joins.outer_join(T: BaseTable, other: BaseTable, left_keys: List[str], right_keys: List[str], left_columns: Union[List[str], None], right_columns: Union[List[str], None], merge_keys: bool = False, tqdm=_tqdm, pbar=None)","text":"Source code in tablite/joins.py
    def outer_join(T: BaseTable, other: BaseTable, left_keys: List[str], right_keys: List[str], \n              left_columns: Union[List[str], None], right_columns: Union[List[str], None],\n              merge_keys: bool = False, tqdm=_tqdm, pbar=None):\n    return join(T, other, left_keys, right_keys, left_columns, right_columns, kind=\"outer\", merge_keys=merge_keys, tqdm=tqdm,pbar=pbar)\n
    "},{"location":"reference/joins/#tablite.joins.cross_join","title":"tablite.joins.cross_join(T: BaseTable, other: BaseTable, left_keys: List[str], right_keys: List[str], left_columns: Union[List[str], None], right_columns: Union[List[str], None], merge_keys: bool = False, tqdm=_tqdm, pbar=None)","text":"Source code in tablite/joins.py
    def cross_join(T: BaseTable, other: BaseTable, left_keys: List[str], right_keys: List[str], \n              left_columns: Union[List[str], None], right_columns: Union[List[str], None],\n              merge_keys: bool = False, tqdm=_tqdm, pbar=None):\n    return join(T, other, left_keys, right_keys, left_columns, right_columns, kind=\"cross\", merge_keys=merge_keys, tqdm=tqdm,pbar=pbar)\n
    "},{"location":"reference/lookup/","title":"Lookup","text":""},{"location":"reference/lookup/#tablite.lookup","title":"tablite.lookup","text":""},{"location":"reference/lookup/#tablite.lookup-attributes","title":"Attributes","text":""},{"location":"reference/lookup/#tablite.lookup-classes","title":"Classes","text":""},{"location":"reference/lookup/#tablite.lookup-functions","title":"Functions","text":""},{"location":"reference/lookup/#tablite.lookup.lookup","title":"tablite.lookup.lookup(T, other, *criteria, all=True, tqdm=_tqdm)","text":"

    function for looking up values in other according to criteria in ascending order. :param: T: Table :param: other: Table sorted in ascending search order. :param: criteria: Each criteria must be a tuple with value comparisons in the form: (LEFT, OPERATOR, RIGHT) :param: all: boolean: True=ALL, False=ANY

    OPERATOR must be a callable that returns a boolean LEFT must be a value that the OPERATOR can compare. RIGHT must be a value that the OPERATOR can compare.

    Examples:

    comparison of two columns:

    ('column A', \"==\", 'column B')\n

    compare value from column 'Date' with date 24/12.

    ('Date', \"<\", DataTypes.date(24,12) )\n

    uses custom function to compare value from column 'text 1' with value from column 'text 2'

    f = lambda L,R: all( ord(L) < ord(R) )\n('text 1', f, 'text 2')\n
    Source code in tablite/lookup.py
    def lookup(T, other, *criteria, all=True, tqdm=_tqdm):\n    \"\"\"function for looking up values in `other` according to criteria in ascending order.\n    :param: T: Table \n    :param: other: Table sorted in ascending search order.\n    :param: criteria: Each criteria must be a tuple with value comparisons in the form:\n        (LEFT, OPERATOR, RIGHT)\n    :param: all: boolean: True=ALL, False=ANY\n\n    OPERATOR must be a callable that returns a boolean\n    LEFT must be a value that the OPERATOR can compare.\n    RIGHT must be a value that the OPERATOR can compare.\n\n    Examples:\n        comparison of two columns:\n\n            ('column A', \"==\", 'column B')\n\n        compare value from column 'Date' with date 24/12.\n\n            ('Date', \"<\", DataTypes.date(24,12) )\n\n        uses custom function to compare value from column\n        'text 1' with value from column 'text 2'\n\n            f = lambda L,R: all( ord(L) < ord(R) )\n            ('text 1', f, 'text 2')\n\n    \"\"\"\n    sub_cls_check(T, BaseTable)\n    sub_cls_check(other, BaseTable)\n\n    all = all\n    any = not all\n\n    ops = lookup_ops\n\n    functions, left_criteria, right_criteria = [], set(), set()\n\n    for left, op, right in criteria:\n        left_criteria.add(left)\n        right_criteria.add(right)\n        if callable(op):\n            pass  # it's a custom function.\n        else:\n            op = ops.get(op, None)\n            if not callable(op):\n                raise ValueError(f\"{op} not a recognised operator for comparison.\")\n\n        functions.append((op, left, right))\n    left_columns = [n for n in left_criteria if n in T.columns]\n    right_columns = [n for n in right_criteria if n in other.columns]\n\n    result_index = np.empty(shape=(len(T)), dtype=np.int64)\n    cache = {}\n    left = T[left_columns]\n    Constr = type(T)\n    if isinstance(left, Column):\n        tmp, left = left, Constr()\n        left[left_columns[0]] = tmp\n    right = other[right_columns]\n    if isinstance(right, Column):\n        tmp, right = right, Constr()\n        right[right_columns[0]] = tmp\n    assert isinstance(left, BaseTable)\n    assert isinstance(right, BaseTable)\n\n    for ix, row1 in tqdm(enumerate(left.rows), total=len(T), disable=Config.TQDM_DISABLE):\n        row1_tup = tuple(row1)\n        row1d = {name: value for name, value in zip(left_columns, row1)}\n        row1_hash = hash(row1_tup)\n\n        match_found = True if row1_hash in cache else False\n\n        if not match_found:  # search.\n            for row2ix, row2 in enumerate(right.rows):\n                row2d = {name: value for name, value in zip(right_columns, row2)}\n\n                evaluations = {op(row1d.get(left, left), row2d.get(right, right)) for op, left, right in functions}\n                # The evaluations above does a neat trick:\n                # as L is a dict, L.get(left, L) will return a value\n                # from the columns IF left is a column name. If it isn't\n                # the function will treat left as a value.\n                # The same applies to right.\n                all_ = all and (False not in evaluations)\n                any_ = any and True in evaluations\n                if all_ or any_:\n                    match_found = True\n                    cache[row1_hash] = row2ix\n                    break\n\n        if not match_found:  # no match found.\n            cache[row1_hash] = -1  # -1 is replacement for None in the index as numpy can't handle Nones.\n\n        result_index[ix] = cache[row1_hash]\n\n    f = select_processing_method(2 * max(len(T), len(other)), _sp_lookup, _mp_lookup)\n    return f(T, other, result_index)\n
    "},{"location":"reference/match/","title":"Match","text":""},{"location":"reference/match/#tablite.match","title":"tablite.match","text":""},{"location":"reference/match/#tablite.match-classes","title":"Classes","text":""},{"location":"reference/match/#tablite.match-functions","title":"Functions","text":""},{"location":"reference/match/#tablite.match.match","title":"tablite.match.match(T, other, *criteria, keep_left=None, keep_right=None)","text":"

    performs inner join where T matches other and removes rows that do not match.

    :param: T: Table :param: other: Table :param: criteria: Each criteria must be a tuple with value comparisons in the form:

    (LEFT, OPERATOR, RIGHT), where operator must be \"==\"\n\nExample:\n    ('column A', \"==\", 'column B')\n\nThis syntax follows the lookup syntax. See Lookup for details.\n

    :param: keep_left: list of columns to keep. :param: keep_right: list of right columns to keep.

    Source code in tablite/match.py
    def match(T, other, *criteria, keep_left=None, keep_right=None):  # lookup and filter combined - drops unmatched rows.\n    \"\"\"\n    performs inner join where `T` matches `other` and removes rows that do not match.\n\n    :param: T: Table\n    :param: other: Table\n    :param: criteria: Each criteria must be a tuple with value comparisons in the form:\n\n        (LEFT, OPERATOR, RIGHT), where operator must be \"==\"\n\n        Example:\n            ('column A', \"==\", 'column B')\n\n        This syntax follows the lookup syntax. See Lookup for details.\n\n    :param: keep_left: list of columns to keep.\n    :param: keep_right: list of right columns to keep.\n    \"\"\"\n    assert isinstance(T, BaseTable)\n    assert isinstance(other, BaseTable)\n    if keep_left is None:\n        keep_left = [n for n in T.columns]\n    else:\n        type_check(keep_left, list)\n        name_check(T.columns, *keep_left)\n\n    if keep_right is None:\n        keep_right = [n for n in other.columns]\n    else:\n        type_check(keep_right, list)\n        name_check(other.columns, *keep_right)\n\n    indices = np.full(shape=(len(T),), fill_value=-1, dtype=np.int64)\n    for arg in criteria:\n        b,_,a = arg\n        if _ != \"==\":\n            raise ValueError(\"match requires A == B. For other logic visit `lookup`\")\n        if b not in T.columns:\n            raise ValueError(f\"Column {b} not found in T for criteria: {arg}\")\n        if a not in other.columns:\n            raise ValueError(f\"Column {a} not found in T for criteria: {arg}\")\n\n        index_update = find_indices(other[a][:], T[b][:], fill_value=-1)\n        indices = merge_indices(indices, index_update)\n\n    cls = type(T)\n    new = cls()\n    for name in T.columns:\n        if name in keep_left:\n            new[name] = np.compress(indices != -1, T[name][:])\n\n    for name in other.columns:\n        if name in keep_right:\n            new_name = unique_name(name, new.columns)\n            primary = np.compress(indices != -1, indices)\n            new[new_name] = np.take(other[name][:], primary)\n\n    return new\n
    "},{"location":"reference/match/#tablite.match.find_indices","title":"tablite.match.find_indices(x, y, fill_value=-1)","text":"

    finds index of y in x

    Source code in tablite/match.py
    def find_indices(x,y, fill_value=-1):  # fast.\n    \"\"\"\n    finds index of y in x\n    \"\"\"\n    # disassembly of numpy:\n    # import numpy as np\n    # x = np.array([3, 5, 7,  1,   9, 8, 6, 6])\n    # y = np.array([2, 1, 5, 10, 100, 6])\n    index = np.argsort(x)  # array([3, 0, 1, 6, 7, 2, 5, 4])\n    sorted_x = x[index]  # array([1, 3, 5, 6, 6, 7, 8, 9])\n    sorted_index = np.searchsorted(sorted_x, y)  # array([1, 0, 2, 8, 8, 3])\n    yindex = np.take(index, sorted_index, mode=\"clip\")  # array([0, 3, 1, 4, 4, 6])\n    mask = x[yindex] != y  # array([ True, False, False,  True,  True, False])\n    indices = np.ma.array(yindex, mask=mask, fill_value=fill_value)  \n    # masked_array(data=[--, 3, 1, --, --, 6], mask=[ True, False, False,  True,  True, False], fill_value=999999)\n    # --: y[0] not in x\n    # 3 : y[1] == x[3]\n    # 1 : y[2] == x[1]\n    # --: y[3] not in x\n    # --: y[4] not in x\n    # --: y[5] == x[6]\n    result = np.where(~indices.mask, indices.data, -1)  \n    return result  # array([-1,  3,  1, -1, -1,  6])\n
    "},{"location":"reference/match/#tablite.match.merge_indices","title":"tablite.match.merge_indices(x1, *args, fill_value=-1)","text":"

    merges x1 and x2 where

    Source code in tablite/match.py
    def merge_indices(x1, *args, fill_value=-1):\n    \"\"\"\n    merges x1 and x2 where \n    \"\"\"\n    # dis:\n    # >>> AA = array([-1,  3, -1, 5])\n    # >>> BB = array([-1, -1,  4, 5])\n    new = x1[:]  # = AA\n    for arg in args:\n        mask = (new == fill_value)  # array([True, False, True, False])\n        new = np.where(mask, arg, new)  # array([-1, 3, 4, 5])\n    return new   # array([-1, 3, 4, 5])\n
    "},{"location":"reference/merge/","title":"Merge","text":""},{"location":"reference/merge/#tablite.merge","title":"tablite.merge","text":""},{"location":"reference/merge/#tablite.merge-classes","title":"Classes","text":""},{"location":"reference/merge/#tablite.merge-functions","title":"Functions","text":""},{"location":"reference/merge/#tablite.merge.where","title":"tablite.merge.where(T, criteria, left, right, new)","text":"

    takes from LEFT where criteria is True else RIGHT and creates a single new column.

    :param: T: Table :param: criteria: np.array(bool): if True take left column else take right column :param left: (str) column name :param right: (str) column name :param new: (str) new name

    :returns: T

    Source code in tablite/merge.py
    def where(T, criteria, left, right, new):\n    \"\"\" takes from LEFT where criteria is True else RIGHT \n    and creates a single new column.\n\n    :param: T: Table\n    :param: criteria: np.array(bool): \n            if True take left column\n            else take right column\n    :param left: (str) column name\n    :param right: (str) column name\n    :param new: (str) new name\n\n    :returns: T\n    \"\"\"\n    type_check(T, BaseTable)\n    if isinstance(criteria, np.ndarray):\n        if not criteria.dtype == \"bool\":\n            raise TypeError\n    else:\n        criteria = np.array(criteria, dtype='bool')\n\n    new_uq = unique_name(new, list(T.columns))\n    T.add_column(new_uq)\n    col = T[new_uq]\n\n    for start,end in Config.page_steps(len(criteria)):\n        left_values = T[left][start:end]\n        right_values = T[right][start:end]\n        new_values = np.where(criteria, left_values, right_values)\n        col.extend(new_values)\n\n    if new == right:\n        T[right] = T[new_uq]  # keep column order\n        del T[new_uq]\n        del T[left]\n    elif new == left:\n        T[left] = T[new_uq]  # keep column order\n        del T[new_uq]\n        del T[right]\n    else:\n        T[new] = T[new_uq]\n        del T[left]\n        del T[right]\n    return T\n
    "},{"location":"reference/mp_utils/","title":"Mp utils","text":""},{"location":"reference/mp_utils/#tablite.mp_utils","title":"tablite.mp_utils","text":""},{"location":"reference/mp_utils/#tablite.mp_utils-attributes","title":"Attributes","text":""},{"location":"reference/mp_utils/#tablite.mp_utils.lookup_ops","title":"tablite.mp_utils.lookup_ops = {'in': _in, 'not in': not_in, '<': operator.lt, '<=': operator.le, '>': operator.gt, '>=': operator.ge, '!=': operator.ne, '==': operator.eq} module-attribute","text":""},{"location":"reference/mp_utils/#tablite.mp_utils.filter_ops","title":"tablite.mp_utils.filter_ops = {'>': operator.gt, '>=': operator.ge, '==': operator.eq, '<': operator.lt, '<=': operator.le, '!=': operator.ne, 'in': _in} module-attribute","text":""},{"location":"reference/mp_utils/#tablite.mp_utils.filter_ops_from_text","title":"tablite.mp_utils.filter_ops_from_text = {'gt': '>', 'gteq': '>=', 'eq': '==', 'lt': '<', 'lteq': '<=', 'neq': '!=', 'in': _in} module-attribute","text":""},{"location":"reference/mp_utils/#tablite.mp_utils-classes","title":"Classes","text":""},{"location":"reference/mp_utils/#tablite.mp_utils-functions","title":"Functions","text":""},{"location":"reference/mp_utils/#tablite.mp_utils.not_in","title":"tablite.mp_utils.not_in(a, b)","text":"Source code in tablite/mp_utils.py
    def not_in(a, b):\n    return not operator.contains(str(a), str(b))\n
    "},{"location":"reference/mp_utils/#tablite.mp_utils.is_mp","title":"tablite.mp_utils.is_mp(fields: int) -> bool","text":"PARAMETER DESCRIPTION fields

    number of fields

    TYPE: int

    RETURNS DESCRIPTION bool

    bool

    Source code in tablite/mp_utils.py
    def is_mp(fields: int) -> bool:\n    \"\"\"\n\n    Args:\n        fields (int): number of fields\n\n    Returns:\n        bool\n    \"\"\"\n    if Config.MULTIPROCESSING_MODE == Config.FORCE:\n        return True\n\n    if Config.MULTIPROCESSING_MODE == Config.FALSE:\n        return False\n\n    if fields < Config.SINGLE_PROCESSING_LIMIT:\n        return False\n\n    if max(psutil.cpu_count(logical=False), 1) < 2:\n        return False\n\n    return True\n
    "},{"location":"reference/mp_utils/#tablite.mp_utils.select_processing_method","title":"tablite.mp_utils.select_processing_method(fields, sp, mp)","text":"PARAMETER DESCRIPTION fields

    number of fields

    TYPE: int

    sp

    method for single processing

    TYPE: callable

    mp

    method for multiprocessing

    TYPE: callable

    RETURNS DESCRIPTION _type_

    description

    Source code in tablite/mp_utils.py
    def select_processing_method(fields, sp, mp):\n    \"\"\"\n\n    Args:\n        fields (int): number of fields\n        sp (callable): method for single processing\n        mp (callable): method for multiprocessing\n\n    Returns:\n        _type_: _description_\n    \"\"\"\n    return mp if is_mp(fields) else sp\n
    "},{"location":"reference/mp_utils/#tablite.mp_utils.maskify","title":"tablite.mp_utils.maskify(arr)","text":"Source code in tablite/mp_utils.py
    def maskify(arr):\n    none_mask = [False] * len(arr)  # Setting the default\n\n    for i in range(len(arr)):\n        if arr[i] is None:  # Check if our value is None\n            none_mask[i] = True\n            arr[i] = 0  # Remove None from the original array\n\n    return none_mask\n
    "},{"location":"reference/mp_utils/#tablite.mp_utils.share_mem","title":"tablite.mp_utils.share_mem(inp_arr, dtype)","text":"Source code in tablite/mp_utils.py
    def share_mem(inp_arr, dtype):\n    len_ = len(inp_arr)\n    size = np.dtype(dtype).itemsize * len_\n    shape = (len_,)\n\n    out_shm = shared_memory.SharedMemory(create=True, size=size)  # the co_processors will read this.\n    out_arr_index = np.ndarray(shape, dtype=dtype, buffer=out_shm.buf)\n    out_arr_index[:] = inp_arr\n\n    return out_arr_index, out_shm\n
    "},{"location":"reference/mp_utils/#tablite.mp_utils.map_task","title":"tablite.mp_utils.map_task(data_shm_name, index_shm_name, destination_shm_name, shape, dtype, start, end)","text":"Source code in tablite/mp_utils.py
    def map_task(data_shm_name, index_shm_name, destination_shm_name, shape, dtype, start, end):\n    # connect\n    shared_data = shared_memory.SharedMemory(name=data_shm_name)\n    data = np.ndarray(shape, dtype=dtype, buffer=shared_data.buf)\n\n    shared_index = shared_memory.SharedMemory(name=index_shm_name)\n    index = np.ndarray(shape, dtype=np.int64, buffer=shared_index.buf)\n\n    shared_target = shared_memory.SharedMemory(name=destination_shm_name)\n    target = np.ndarray(shape, dtype=dtype, buffer=shared_target.buf)\n    # work\n    target[start:end] = np.take(data[start:end], index[start:end])\n    # disconnect\n    shared_data.close()\n    shared_index.close()\n    shared_target.close()\n
    "},{"location":"reference/mp_utils/#tablite.mp_utils.reindex_task","title":"tablite.mp_utils.reindex_task(src, dst, index_shm, shm_shape, start, end)","text":"Source code in tablite/mp_utils.py
    def reindex_task(src, dst, index_shm, shm_shape, start, end):\n    # connect\n    existing_shm = shared_memory.SharedMemory(name=index_shm)\n    shared_index = np.ndarray(shm_shape, dtype=np.int64, buffer=existing_shm.buf)\n    # work\n    array = load_numpy(src)\n    new = np.take(array, shared_index[start:end])\n    np.save(dst, new, allow_pickle=True, fix_imports=False)\n    # disconnect\n    existing_shm.close()\n
    "},{"location":"reference/nimlite/","title":"Nimlite","text":""},{"location":"reference/nimlite/#tablite.nimlite","title":"tablite.nimlite","text":""},{"location":"reference/nimlite/#tablite.nimlite-attributes","title":"Attributes","text":""},{"location":"reference/nimlite/#tablite.nimlite.paths","title":"tablite.nimlite.paths = sys.argv[:] module-attribute","text":""},{"location":"reference/nimlite/#tablite.nimlite.K","title":"tablite.nimlite.K = TypeVar('K', bound=BaseTable) module-attribute","text":""},{"location":"reference/nimlite/#tablite.nimlite.ValidEncoders","title":"tablite.nimlite.ValidEncoders = Literal['ENC_UTF8', 'ENC_UTF16', 'ENC_WIN1250'] module-attribute","text":""},{"location":"reference/nimlite/#tablite.nimlite.ValidQuoting","title":"tablite.nimlite.ValidQuoting = Literal['QUOTE_MINIMAL', 'QUOTE_ALL', 'QUOTE_NONNUMERIC', 'QUOTE_NONE', 'QUOTE_STRINGS', 'QUOTE_NOTNULL'] module-attribute","text":""},{"location":"reference/nimlite/#tablite.nimlite.ValidSkipEmpty","title":"tablite.nimlite.ValidSkipEmpty = Literal['NONE', 'ANY', 'ALL'] module-attribute","text":""},{"location":"reference/nimlite/#tablite.nimlite.ColumnSelectorDict","title":"tablite.nimlite.ColumnSelectorDict = TypedDict('ColumnSelectorDict', {'column': str, 'type': Literal['int', 'float', 'bool', 'str', 'date', 'time', 'datetime'], 'allow_empty': Union[bool, None], 'rename': Union[str, None]}) module-attribute","text":""},{"location":"reference/nimlite/#tablite.nimlite.FilterCriteria","title":"tablite.nimlite.FilterCriteria = Literal['>', '>=', '==', '<', '<=', '!=', 'in'] module-attribute","text":""},{"location":"reference/nimlite/#tablite.nimlite.FilterType","title":"tablite.nimlite.FilterType = Literal['all', 'any'] module-attribute","text":""},{"location":"reference/nimlite/#tablite.nimlite.FilterDict","title":"tablite.nimlite.FilterDict = TypedDict('FilterDict', {'column1': str, 'value1': Union[str, None], 'criteria': FilterCriteria, 'column2': str, 'value2': Union[str, None]}) module-attribute","text":""},{"location":"reference/nimlite/#tablite.nimlite-classes","title":"Classes","text":""},{"location":"reference/nimlite/#tablite.nimlite-functions","title":"Functions","text":""},{"location":"reference/nimlite/#tablite.nimlite.get_headers","title":"tablite.nimlite.get_headers(path: Union[str, Path], encoding: ValidEncoders = 'ENC_UTF8', *, header_row_index: int = 0, newline: str = '\\n', delimiter: str = ',', text_qualifier: str = '\"', quoting: ValidQuoting, strip_leading_and_tailing_whitespace: bool = True, linecount: int = 10) -> list[list[str]]","text":"Source code in tablite/nimlite.py
    def get_headers(\n    path: Union[str, Path],\n    encoding: ValidEncoders =\"ENC_UTF8\",\n    *,\n    header_row_index: int=0,\n    newline: str='\\n', delimiter: str=',', text_qualifier: str='\"',\n    quoting: ValidQuoting, strip_leading_and_tailing_whitespace: bool=True,\n    linecount: int = 10\n) -> list[list[str]]:\n    return nl.get_headers(\n            path=str(path),\n            encoding=encoding,\n            newline=newline, delimiter=delimiter, text_qualifier=text_qualifier,\n            strip_leading_and_tailing_whitespace=strip_leading_and_tailing_whitespace,\n            header_row_index=header_row_index,\n            quoting=quoting,\n            linecount=linecount\n        )\n
    "},{"location":"reference/nimlite/#tablite.nimlite.text_reader","title":"tablite.nimlite.text_reader(T: Type[K], pid: str, path: Union[str, Path], encoding: ValidEncoders = 'ENC_UTF8', *, first_row_has_headers: bool = True, header_row_index: int = 0, columns: List[Union[str, None]] = None, start: Union[str, None] = None, limit: Union[str, None] = None, guess_datatypes: bool = False, newline: str = '\\n', delimiter: str = ',', text_qualifier: str = '\"', quoting: ValidQuoting, strip_leading_and_tailing_whitespace: bool = True, skip_empty: ValidSkipEmpty = 'NONE', tqdm=_tqdm) -> K","text":"Source code in tablite/nimlite.py
    def text_reader(\n    T: Type[K],\n    pid: str, path: Union[str, Path],\n    encoding: ValidEncoders =\"ENC_UTF8\",\n    *,\n    first_row_has_headers: bool=True, header_row_index: int=0,\n    columns: List[Union[str, None]]=None,\n    start: Union[str, None] = None, limit: Union[str, None]=None,\n    guess_datatypes: bool =False,\n    newline: str='\\n', delimiter: str=',', text_qualifier: str='\"',\n    quoting: ValidQuoting, strip_leading_and_tailing_whitespace: bool=True, skip_empty: ValidSkipEmpty = \"NONE\",\n    tqdm=_tqdm\n) -> K:\n    assert isinstance(path, Path)\n    assert isinstance(pid, Path)\n    with tqdm(total=10, desc=f\"importing file\") as pbar:\n        table = nl.text_reader(\n            pid=str(pid),\n            path=str(path),\n            encoding=encoding,\n            first_row_has_headers=first_row_has_headers, header_row_index=header_row_index,\n            columns=columns,\n            start=start, limit=limit,\n            guess_datatypes=guess_datatypes,\n            newline=newline, delimiter=delimiter, text_qualifier=text_qualifier,\n            quoting=quoting,\n            strip_leading_and_tailing_whitespace=strip_leading_and_tailing_whitespace,\n            skip_empty=skip_empty,\n            page_size=Config.PAGE_SIZE\n        )\n\n        pbar.update(1)\n\n        task_info = table[\"task\"]\n        task_columns = table[\"columns\"]\n\n        ti_tasks = task_info[\"tasks\"]\n        ti_import_field_names = task_info[\"import_field_names\"]\n\n        is_windows = platform.system() == \"Windows\"\n        use_logical = False if is_windows else True\n\n        cpus = max(psutil.cpu_count(logical=use_logical), 1)\n\n        pbar_step = 4 / max(len(ti_tasks), 1)\n\n        class WrapUpdate:\n            def update(self, n):\n                pbar.update(n * pbar_step)\n\n        wrapped_pbar = WrapUpdate()\n\n        def next_task(task: Task, page_info):\n            wrapped_pbar.update(1)\n            return Task(\n                nl.text_reader_task,\n                *task.args, **task.kwargs, page_info=page_info\n            )\n\n        tasks = [\n            TaskChain(\n                Task(\n                    nl.collect_text_reader_page_info_task,\n                    task=t,\n                    task_info=task_info\n                ), next_task=next_task\n            ) for t in ti_tasks\n        ]\n\n        is_sp = False\n\n        if Config.MULTIPROCESSING_MODE == Config.FALSE:\n            is_sp = True\n        elif Config.MULTIPROCESSING_MODE == Config.FORCE:\n            is_sp = False\n        elif Config.MULTIPROCESSING_MODE == Config.AUTO and cpus <= 1 or len(tasks) <= 1:\n            is_sp = True\n\n        if is_sp:\n            res = []\n\n            for task in tasks:\n                page = task.execute()\n\n                res.append(page)\n        else:\n            with TaskManager(cpus, error_mode=\"exception\") as tm:\n                res = tm.execute(tasks, pbar=wrapped_pbar)\n\n        col_path = pid\n        column_dict = {\n            cols: Column(col_path)\n            for cols in ti_import_field_names\n        }\n\n        for res_pages in res:\n            col_map = {\n                n: res_pages[i]\n                for i, n in enumerate(ti_import_field_names)\n            }\n\n            for k, c in column_dict.items():\n                c.pages.append(col_map[k])\n\n        if columns is None:\n            columns = [c[\"name\"] for c in task_columns]\n\n        table_dict = {\n            a[\"name\"]: column_dict[b]\n            for a, b in zip(task_columns, columns)\n        }\n\n        pbar.update(pbar.total - pbar.n)\n\n        table = T(columns=table_dict)\n\n    return table\n
    "},{"location":"reference/nimlite/#tablite.nimlite.wrap","title":"tablite.nimlite.wrap(str_: str) -> str","text":"Source code in tablite/nimlite.py
    def wrap(str_: str) -> str:\n    return '\"' + str_.replace('\"', '\\\\\"').replace(\"'\", \"\\\\'\").replace(\"\\n\", \"\\\\n\").replace(\"\\t\", \"\\\\t\") + '\"'\n
    "},{"location":"reference/nimlite/#tablite.nimlite.column_select","title":"tablite.nimlite.column_select(table: K, cols: list[ColumnSelectorDict], tqdm=_tqdm, TaskManager=TaskManager) -> Tuple[K, K]","text":"Source code in tablite/nimlite.py
    def column_select(table: K, cols: list[ColumnSelectorDict], tqdm=_tqdm, TaskManager=TaskManager) -> Tuple[K, K]:\n    with tqdm(total=100, desc=\"column select\", bar_format='{desc}: {percentage:.1f}%|{bar}{r_bar}') as pbar:\n        T = type(table)\n        dir_pid = Config.workdir / Config.pid\n\n        col_infos = nl.collect_column_select_info(table, cols, str(dir_pid), pbar)\n\n        columns = col_infos[\"columns\"]\n        page_count = col_infos[\"page_count\"]\n        is_correct_type = col_infos[\"is_correct_type\"]\n        desired_column_map = col_infos[\"desired_column_map\"]\n        original_pages_map = col_infos[\"original_pages_map\"]\n        passed_column_data = col_infos[\"passed_column_data\"]\n        failed_column_data = col_infos[\"failed_column_data\"]\n        res_cols_pass = col_infos[\"res_cols_pass\"]\n        res_cols_fail = col_infos[\"res_cols_fail\"]\n        column_names = col_infos[\"column_names\"]\n        reject_reason_name = col_infos[\"reject_reason_name\"]\n\n        if all(is_correct_type.values()):\n            tbl_pass_columns = {\n                desired_name: table[desired_info[0]]\n                for desired_name, desired_info in desired_column_map.items()\n            }\n\n            tbl_fail_columns = {\n                desired_name: []\n                for desired_name in failed_column_data\n            }\n\n            tbl_pass = T(columns=tbl_pass_columns)\n            tbl_fail = T(columns=tbl_fail_columns)\n\n            return (tbl_pass, tbl_fail)\n\n        task_list_inp = (\n            _collect_cs_info(i, columns, res_cols_pass, res_cols_fail, original_pages_map)\n            for i in range(page_count)\n        )\n\n        page_size = Config.PAGE_SIZE\n\n        tasks = (\n            Task(\n                nl.do_slice_convert, str(dir_pid), page_size, columns, reject_reason_name, res_pass, res_fail, desired_column_map, column_names, is_correct_type\n            )\n            for columns, res_pass, res_fail in task_list_inp\n        )\n\n        cpu_count = max(psutil.cpu_count(), 1)\n\n        if Config.MULTIPROCESSING_MODE == Config.FORCE:\n            is_mp = True\n        elif Config.MULTIPROCESSING_MODE == Config.FALSE:\n            is_mp = False\n        elif Config.MULTIPROCESSING_MODE == Config.AUTO:\n            is_multithreaded = cpu_count > 1\n            is_multipage = page_count > 1\n\n            is_mp = is_multithreaded and is_multipage\n\n        tbl_pass = T({k: [] for k in passed_column_data})\n        tbl_fail = T({k: [] for k in failed_column_data})\n\n        converted = []\n        step_size = 45 / max(page_count, 1)\n\n        if is_mp:\n            class WrapUpdate:\n                def update(self, n):\n                    pbar.update(n * step_size)\n\n            with TaskManager(min(cpu_count, page_count), error_mode=\"exception\") as tm:\n                res = tm.execute(list(tasks), pbar=WrapUpdate())\n\n                converted.extend(res)\n        else:\n            for task in tasks:\n                res = task.f(*task.args, **task.kwargs)\n\n                converted.append(res)\n                pbar.update(step_size)\n\n        def extend_table(table, columns):\n            for (col_name, pg) in columns:\n                table[col_name].pages.append(pg)\n\n        for pg_pass, pg_fail in converted:\n            extend_table(tbl_pass, pg_pass)\n            extend_table(tbl_fail, pg_fail)\n\n        pbar.update(pbar.total - pbar.n)\n\n        return tbl_pass, tbl_fail\n
    "},{"location":"reference/nimlite/#tablite.nimlite.read_page","title":"tablite.nimlite.read_page(path: Union[str, Path]) -> np.ndarray","text":"Source code in tablite/nimlite.py
    def read_page(path: Union[str, Path]) -> np.ndarray:\n    return nl.read_page(str(path))\n
    "},{"location":"reference/nimlite/#tablite.nimlite.repaginate","title":"tablite.nimlite.repaginate(column: Column)","text":"Source code in tablite/nimlite.py
    def repaginate(column: Column):\n    nl.repaginate(column)\n
    "},{"location":"reference/nimlite/#tablite.nimlite.nearest_neighbour","title":"tablite.nimlite.nearest_neighbour(T: BaseTable, sources: Union[list[str], None], missing: Union[list, None], targets: Union[list[str], None], tqdm=_tqdm)","text":"Source code in tablite/nimlite.py
    def nearest_neighbour(T: BaseTable, sources: Union[list[str], None], missing: Union[list, None], targets: Union[list[str], None], tqdm=_tqdm):\n    return nl.nearest_neighbour(T, sources, list(missing), targets, tqdm)\n
    "},{"location":"reference/nimlite/#tablite.nimlite.groupby","title":"tablite.nimlite.groupby(T, keys, functions, tqdm=_tqdm)","text":"Source code in tablite/nimlite.py
    def groupby(T, keys, functions, tqdm=_tqdm):\n    return nl.groupby(T, keys, functions, tqdm)\n
    "},{"location":"reference/nimlite/#tablite.nimlite.filter","title":"tablite.nimlite.filter(table: BaseTable, expressions: list[FilterDict], type: FilterType, tqdm=_tqdm)","text":"Source code in tablite/nimlite.py
    def filter(table: BaseTable, expressions: list[FilterDict], type: FilterType, tqdm = _tqdm):\n    return nl.filter(table, expressions, type, tqdm)\n
    "},{"location":"reference/pivots/","title":"Pivots","text":""},{"location":"reference/pivots/#tablite.pivots","title":"tablite.pivots","text":""},{"location":"reference/pivots/#tablite.pivots-classes","title":"Classes","text":""},{"location":"reference/pivots/#tablite.pivots-functions","title":"Functions","text":""},{"location":"reference/pivots/#tablite.pivots.acc2Name","title":"tablite.pivots.acc2Name(acc: str) -> str","text":"Source code in tablite/pivots.py
    def acc2Name(acc: str) -> str:\n    arr = [\"max\", \"min\", \"sum\", \"product\", \"first\", \"last\", \"count\", \"median\", \"mode\"]\n    if acc in arr:\n        return acc.capitalize()\n    elif acc == \"count_unique\":\n        return \"CountUnique\"\n    elif acc == \"avg\":\n        return \"Average\"\n    elif acc == \"stdev\":\n        return \"StandardDeviation\"\n    else:\n        raise ValueError(f\"unknown accumulator - {acc}\")\n
    "},{"location":"reference/pivots/#tablite.pivots.pivot","title":"tablite.pivots.pivot(T, rows, columns, functions, values_as_rows=True, tqdm=_tqdm, pbar=None)","text":"

    param: rows: column names to keep as rows param: columns: column names to keep as columns param: functions: aggregation functions from the Groupby class as

    example:

    >>> t.show()\n+=====+=====+=====+\n|  A  |  B  |  C  |\n| int | int | int |\n+-----+-----+-----+\n|    1|    1|    6|\n|    1|    2|    5|\n|    2|    3|    4|\n|    2|    4|    3|\n|    3|    5|    2|\n|    3|    6|    1|\n|    1|    1|    6|\n|    1|    2|    5|\n|    2|    3|    4|\n|    2|    4|    3|\n|    3|    5|    2|\n|    3|    6|    1|\n+=====+=====+=====+\n\n>>> t2 = t.pivot(rows=['C'], columns=['A'], functions=[('B', gb.sum)])\n>>> t2.show()\n+===+===+========+=====+=====+=====+\n| # | C |function|(A=1)|(A=2)|(A=3)|\n|row|int|  str   |mixed|mixed|mixed|\n+---+---+--------+-----+-----+-----+\n|0  |  6|Sum(B)  |    2|None |None |\n|1  |  5|Sum(B)  |    4|None |None |\n|2  |  4|Sum(B)  |None |    6|None |\n|3  |  3|Sum(B)  |None |    8|None |\n|4  |  2|Sum(B)  |None |None |   10|\n|5  |  1|Sum(B)  |None |None |   12|\n+===+===+========+=====+=====+=====+\n
    Source code in tablite/pivots.py
    def pivot(T, rows, columns, functions, values_as_rows=True, tqdm=_tqdm, pbar=None):\n    \"\"\"\n    param: rows: column names to keep as rows\n    param: columns: column names to keep as columns\n    param: functions: aggregation functions from the Groupby class as\n\n    example:\n    ```\n    >>> t.show()\n    +=====+=====+=====+\n    |  A  |  B  |  C  |\n    | int | int | int |\n    +-----+-----+-----+\n    |    1|    1|    6|\n    |    1|    2|    5|\n    |    2|    3|    4|\n    |    2|    4|    3|\n    |    3|    5|    2|\n    |    3|    6|    1|\n    |    1|    1|    6|\n    |    1|    2|    5|\n    |    2|    3|    4|\n    |    2|    4|    3|\n    |    3|    5|    2|\n    |    3|    6|    1|\n    +=====+=====+=====+\n\n    >>> t2 = t.pivot(rows=['C'], columns=['A'], functions=[('B', gb.sum)])\n    >>> t2.show()\n    +===+===+========+=====+=====+=====+\n    | # | C |function|(A=1)|(A=2)|(A=3)|\n    |row|int|  str   |mixed|mixed|mixed|\n    +---+---+--------+-----+-----+-----+\n    |0  |  6|Sum(B)  |    2|None |None |\n    |1  |  5|Sum(B)  |    4|None |None |\n    |2  |  4|Sum(B)  |None |    6|None |\n    |3  |  3|Sum(B)  |None |    8|None |\n    |4  |  2|Sum(B)  |None |None |   10|\n    |5  |  1|Sum(B)  |None |None |   12|\n    +===+===+========+=====+=====+=====+\n    ```\n\n    \"\"\"\n    sub_cls_check(T, BaseTable)\n\n    if isinstance(rows, str):\n        rows = [rows]\n    if not all(isinstance(i, str) for i in rows):\n        raise TypeError(f\"Expected rows as a list of column names, not {[i for i in rows if not isinstance(i,str)]}\")\n\n    if isinstance(columns, str):\n        columns = [columns]\n    if not all(isinstance(i, str) for i in columns):\n        raise TypeError(\n            f\"Expected columns as a list of column names, not {[i for i in columns if not isinstance(i, str)]}\"\n        )\n\n    if not isinstance(values_as_rows, bool):\n        raise TypeError(f\"expected sum_on_rows as boolean, not {type(values_as_rows)}\")\n\n    keys = rows + columns\n    assert isinstance(keys, list)\n\n    extra_steps = 2\n\n    if pbar is None:\n        total = extra_steps\n\n        if len(functions) == 0:\n            total = total + len(keys)\n        else:\n            total = total + len(T)\n\n        pbar = tqdm(total=total, desc=\"pivot\")\n\n    grpby = groupby(T, keys, functions, tqdm=tqdm)\n    Constr = type(T)\n\n    if len(grpby) == 0:  # return empty table. This must be a test?\n        pbar.update(extra_steps)\n        return Constr()\n\n    # split keys to determine grid dimensions\n    row_key_index = {}\n    col_key_index = {}\n\n    r = len(rows)\n    c = len(columns)\n    g = len(functions)\n\n    records = defaultdict(dict)\n\n    for row in grpby.rows:\n        row_key = tuple(row[:r])\n        col_key = tuple(row[r : r + c])\n        func_key = tuple(row[r + c :])\n\n        if row_key not in row_key_index:\n            row_key_index[row_key] = len(row_key_index)  # Y\n\n        if col_key not in col_key_index:\n            col_key_index[col_key] = len(col_key_index)  # X\n\n        rix = row_key_index[row_key]\n        cix = col_key_index[col_key]\n        if cix in records:\n            if rix in records[cix]:\n                raise ValueError(\"this should be empty.\")\n        records[cix][rix] = func_key\n\n    pbar.update(1)\n    result = type(T)()\n\n    if values_as_rows:  # ---> leads to more rows.\n        # first create all columns left to right\n\n        n = r + 1  # rows keys + 1 col for function values.\n        cols = [[] for _ in range(n)]\n        for row, ix in row_key_index.items():\n            for col_name, f in functions:\n                cols[-1].append(f\"{acc2Name(f)}({col_name})\")\n                for col_ix, v in enumerate(row):\n                    cols[col_ix].append(v)\n\n        for col_name, values in zip(rows + [\"function\"], cols):\n            col_name = unique_name(col_name, result.columns)\n            result[col_name] = values\n        col_length = len(cols[0])\n        cols.clear()\n\n        # then populate the sparse matrix.\n        for col_key, c in col_key_index.items():\n            col_name = \"(\" + \",\".join([f\"{col_name}={value}\" for col_name, value in zip(columns, col_key)]) + \")\"\n            col_name = unique_name(col_name, result.columns)\n            L = [None for _ in range(col_length)]\n            for r, funcs in records[c].items():\n                for ix, f in enumerate(funcs):\n                    L[g * r + ix] = f\n            result[col_name] = L\n\n    else:  # ---> leads to more columns.\n        n = r\n        cols = [[] for _ in range(n)]\n        for row in row_key_index:\n            for col_ix, v in enumerate(row):\n                cols[col_ix].append(v)  # write key columns.\n\n        for col_name, values in zip(rows, cols):\n            result[col_name] = values\n\n        col_length = len(row_key_index)\n\n        # now populate the sparse matrix.\n        for col_key, c in col_key_index.items():  # select column.\n            cols, names = [], []\n\n            for f, v in zip(functions, func_key):\n                agg_col, func = f\n                terms = \",\".join([agg_col] + [f\"{col_name}={value}\" for col_name, value in zip(columns, col_key)])\n                col_name = f\"{acc2Name(func)}({terms})\"\n                col_name = unique_name(col_name, result.columns)\n                names.append(col_name)\n                cols.append([None for _ in range(col_length)])\n            for r, funcs in records[c].items():\n                for ix, f in enumerate(funcs):\n                    cols[ix][r] = f\n            for name, col in zip(names, cols):\n                result[name] = col\n\n    pbar.update(1)\n\n    return result\n
    "},{"location":"reference/pivots/#tablite.pivots.transpose","title":"tablite.pivots.transpose(T, tqdm=_tqdm)","text":"

    performs a CCW matrix rotation of the table.

    Source code in tablite/pivots.py
    def transpose(T, tqdm=_tqdm):\n    \"\"\"performs a CCW matrix rotation of the table.\"\"\"\n    sub_cls_check(T, BaseTable)\n\n    if len(T.columns) == 0:\n        return type(T)()\n\n    assert isinstance(T, BaseTable)\n    new = type(T)()\n    L = list(T.columns)\n    new[L[0]] = L[1:]\n    for row in tqdm(T.rows, desc=\"table transpose\", total=len(T)):\n        new[row[0]] = row[1:]\n    return new\n
    "},{"location":"reference/pivots/#tablite.pivots.pivot_transpose","title":"tablite.pivots.pivot_transpose(T, columns, keep=None, column_name='transpose', value_name='value', tqdm=_tqdm)","text":"

    Transpose a selection of columns to rows.

    PARAMETER DESCRIPTION columns

    column names to transpose

    TYPE: list of column names

    keep

    column names to keep (repeat)

    TYPE: list of column names DEFAULT: None

    RETURNS DESCRIPTION Table

    with columns transposed to rows

    Example

    transpose columns 1,2 and 3 and transpose the remaining columns, except sum.

    Input:

    | col1 | col2 | col3 | sun | mon | tue | ... | sat | sum  |\n|------|------|------|-----|-----|-----|-----|-----|------|\n| 1234 | 2345 | 3456 | 456 | 567 |     | ... |     | 1023 |\n| 1244 | 2445 | 4456 |     |   7 |     | ... |     |    7 |\n| ...  |      |      |     |     |     |     |     |      |\n\n>>> t.transpose(keep=[col1, col2, col3], transpose=[sun,mon,tue,wed,thu,fri,sat])`\n\nOutput:\n|col1| col2| col3| transpose| value|\n|----|-----|-----|----------|------|\n|1234| 2345| 3456| sun      |   456|\n|1234| 2345| 3456| mon      |   567|\n|1244| 2445| 4456| mon      |     7|\n
    Source code in tablite/pivots.py
    def pivot_transpose(T, columns, keep=None, column_name=\"transpose\", value_name=\"value\", tqdm=_tqdm):\n    \"\"\"Transpose a selection of columns to rows.\n\n    Args:\n        columns (list of column names): column names to transpose\n        keep (list of column names): column names to keep (repeat)\n\n    Returns:\n        Table: with columns transposed to rows\n\n    Example:\n        transpose columns 1,2 and 3 and transpose the remaining columns, except `sum`.\n\n    Input:\n    ```\n    | col1 | col2 | col3 | sun | mon | tue | ... | sat | sum  |\n    |------|------|------|-----|-----|-----|-----|-----|------|\n    | 1234 | 2345 | 3456 | 456 | 567 |     | ... |     | 1023 |\n    | 1244 | 2445 | 4456 |     |   7 |     | ... |     |    7 |\n    | ...  |      |      |     |     |     |     |     |      |\n\n    >>> t.transpose(keep=[col1, col2, col3], transpose=[sun,mon,tue,wed,thu,fri,sat])`\n\n    Output:\n    |col1| col2| col3| transpose| value|\n    |----|-----|-----|----------|------|\n    |1234| 2345| 3456| sun      |   456|\n    |1234| 2345| 3456| mon      |   567|\n    |1244| 2445| 4456| mon      |     7|\n    ```\n\n    \"\"\"\n    sub_cls_check(T, BaseTable)\n\n    if not isinstance(columns, list):\n        raise TypeError\n\n    for i in columns:\n        if not isinstance(i, str):\n            raise TypeError\n        if i not in T.columns:\n            raise ValueError\n        if columns.count(i)>1:\n            raise ValueError(f\"Column {i} appears more than once\")\n\n    if keep is None:\n        keep = []\n    for i in keep:\n        if not isinstance(i, str):\n            raise TypeError\n        if i not in T.columns:\n            raise ValueError\n\n    if column_name in keep + columns:\n        column_name = unique_name(column_name, set_of_names=keep + columns)\n    if value_name in keep + columns + [column_name]:\n        value_name = unique_name(value_name, set_of_names=keep + columns)\n\n    new = type(T)()\n    new.add_columns(*keep + [column_name, value_name])\n    news = {name: [] for name in new.columns}\n\n    n = len(keep)\n\n    with tqdm(total=len(T), desc=\"transpose\", disable=Config.TQDM_DISABLE) as pbar:\n        it = T[keep + columns].rows if len(keep + columns) > 1 else ((v, ) for v in T[keep + columns])\n\n        for ix, row in enumerate(it, start=1):\n            keeps = row[:n]\n            transposes = row[n:]\n\n            for name, value in zip(keep, keeps):\n                news[name].extend([value] * len(transposes))\n            for name, value in zip(columns, transposes):\n                news[column_name].append(name)\n                news[value_name].append(value)\n\n            if ix % Config.SINGLE_PROCESSING_LIMIT == 0:\n                for name, values in news.items():\n                    new[name].extend(values)\n                    values.clear()\n\n            pbar.update(1)\n\n    for name, values in news.items():\n        new[name].extend(np.array(values))\n        values.clear()\n    return new\n
    "},{"location":"reference/redux/","title":"Redux","text":""},{"location":"reference/redux/#tablite.redux","title":"tablite.redux","text":""},{"location":"reference/redux/#tablite.redux-attributes","title":"Attributes","text":""},{"location":"reference/redux/#tablite.redux-classes","title":"Classes","text":""},{"location":"reference/redux/#tablite.redux-functions","title":"Functions","text":""},{"location":"reference/redux/#tablite.redux.filter_all","title":"tablite.redux.filter_all(T, **kwargs)","text":"

    returns Table for rows where ALL kwargs match :param kwargs: dictionary with headers and values / boolean callable

    Examples:

    t = Table()\nt['a'] = [1,2,3,4]\nt['b'] = [10,20,30,40]\n\ndef f(x):\n    return x == 4\ndef g(x):\n    return x < 20\n\nt2 = t.any( **{\"a\":f, \"b\":g})\nassert [r for r in t2.rows] == [[1, 10], [4, 40]]\n\nt2 = t.any(a=f,b=g)\nassert [r for r in t2.rows] == [[1, 10], [4, 40]]\n\ndef h(x):\n    return x>=2\n\ndef i(x):\n    return x<=30\n\nt2 = t.all(a=h,b=i)\nassert [r for r in t2.rows] == [[2,20], [3, 30]]\n
    Source code in tablite/redux.py
    def filter_all(T, **kwargs):\n    \"\"\"\n    returns Table for rows where ALL kwargs match\n    :param kwargs: dictionary with headers and values / boolean callable\n\n    Examples:\n\n        t = Table()\n        t['a'] = [1,2,3,4]\n        t['b'] = [10,20,30,40]\n\n        def f(x):\n            return x == 4\n        def g(x):\n            return x < 20\n\n        t2 = t.any( **{\"a\":f, \"b\":g})\n        assert [r for r in t2.rows] == [[1, 10], [4, 40]]\n\n        t2 = t.any(a=f,b=g)\n        assert [r for r in t2.rows] == [[1, 10], [4, 40]]\n\n        def h(x):\n            return x>=2\n\n        def i(x):\n            return x<=30\n\n        t2 = t.all(a=h,b=i)\n        assert [r for r in t2.rows] == [[2,20], [3, 30]]\n\n\n    \"\"\"\n    sub_cls_check(T, BaseTable)\n\n    if not isinstance(kwargs, dict):\n        raise TypeError(\"did you forget to add the ** in front of your dict?\")\n    if not all([k in T.columns for k in kwargs]):\n        raise ValueError(f\"Unknown column(s): {[k for k in kwargs if k not in T.columns]}\")\n\n    mask = np.full((len(T),), True)\n    for k, v in kwargs.items():\n        col = T[k]\n        for start, end, page in col.iter_by_page():\n            data = page.get()\n            if callable(v):\n                vf = np.frompyfunc(v, 1, 1)\n                mask[start:end] = mask[start:end] & np.apply_along_axis(vf, 0, data)\n            else:\n                mask[start:end] = mask[start:end] & (data == v)\n\n    return _compress_one(T, mask)\n
    "},{"location":"reference/redux/#tablite.redux.drop","title":"tablite.redux.drop(T, *args)","text":"

    drops all rows that contain args

    PARAMETER DESCRIPTION T

    TYPE: Table

    Source code in tablite/redux.py
    def drop(T, *args):\n    \"\"\"drops all rows that contain args\n\n    Args:\n        T (Table):\n    \"\"\"\n    sub_cls_check(T, BaseTable)\n    mask = np.full((len(T),), False)\n    for name in T.columns:\n        col = T[name]\n        for start, end, page in col.iter_by_page():\n            data = page.get()\n            for arg in args:\n                mask[start:end] = mask[start:end] | (data == arg)\n\n    mask = np.invert(mask)\n    return _compress_one(T, mask)\n
    "},{"location":"reference/redux/#tablite.redux.filter_any","title":"tablite.redux.filter_any(T, **kwargs)","text":"

    returns Table for rows where ANY kwargs match :param kwargs: dictionary with headers and values / boolean callable

    Source code in tablite/redux.py
    def filter_any(T, **kwargs):\n    \"\"\"\n    returns Table for rows where ANY kwargs match\n    :param kwargs: dictionary with headers and values / boolean callable\n    \"\"\"\n    sub_cls_check(T, BaseTable)\n    if not isinstance(kwargs, dict):\n        raise TypeError(\"did you forget to add the ** in front of your dict?\")\n\n    mask = np.full((len(T),), False)\n    for k, v in kwargs.items():\n        col = T[k]\n        for start, end, page in col.iter_by_page():\n            data = page.get()\n            if callable(v):\n                vf = np.frompyfunc(v, 1, 1)\n                mask[start:end] = mask[start:end] | np.apply_along_axis(vf, 0, data)\n            else:\n                mask[start:end] = mask[start:end] | (v == data)\n\n    return _compress_one(T, mask)\n
    "},{"location":"reference/redux/#tablite.redux.filter","title":"tablite.redux.filter(T, expressions, filter_type='all', tqdm=_tqdm)","text":"

    filters table

    PARAMETER DESCRIPTION T

    Table.

    TYPE: Table subclass

    expressions

    str: filters based on an expression, such as: \"all((A==B, C!=4, 200<D))\" which is interpreted using python's compiler to:

    def _f(A,B,C,D):\n    return all((A==B, C!=4, 200<D))\n

    list of dicts: (example):

    L = [ {'column1':'A', 'criteria': \"==\", 'column2': 'B'}, {'column1':'C', 'criteria': \"!=\", \"value2\": '4'}, {'value1': 200, 'criteria': \"<\", column2: 'D' } ]

    TYPE: list or str

    accepted

    'column1', 'column2', 'criteria', 'value1', 'value2'

    TYPE: dictionary keys

    filter_type

    Ignored if expressions is str. 'all' or 'any'. Defaults to \"all\".

    TYPE: str DEFAULT: 'all'

    tqdm

    progressbar. Defaults to _tqdm.

    TYPE: tqdm DEFAULT: tqdm

    RETURNS DESCRIPTION 2xTables

    trues, falses

    Source code in tablite/redux.py
    def filter(T, expressions, filter_type=\"all\", tqdm=_tqdm):\n    \"\"\"filters table\n\n\n    Args:\n        T (Table subclass): Table.\n        expressions (list or str):\n            str:\n                filters based on an expression, such as:\n                \"all((A==B, C!=4, 200<D))\"\n                which is interpreted using python's compiler to:\n\n                def _f(A,B,C,D):\n                    return all((A==B, C!=4, 200<D))\n\n            list of dicts: (example):\n\n            L = [\n                {'column1':'A', 'criteria': \"==\", 'column2': 'B'},\n                {'column1':'C', 'criteria': \"!=\", \"value2\": '4'},\n                {'value1': 200, 'criteria': \"<\", column2: 'D' }\n            ]\n\n        accepted dictionary keys: 'column1', 'column2', 'criteria', 'value1', 'value2'\n\n        filter_type (str, optional): Ignored if expressions is str.\n            'all' or 'any'. Defaults to \"all\".\n        tqdm (tqdm, optional): progressbar. Defaults to _tqdm.\n\n    Returns:\n        2xTables: trues, falses\n    \"\"\"\n    # determine method\n    sub_cls_check(T, BaseTable)\n    if len(T) == 0:\n        return T.copy(), T.copy()\n\n    if isinstance(expressions, str):\n        with tqdm(desc=\"filter\", total=20) as pbar:\n            # TODO: make parser for expressions and use the nim implement\n            mask = _filter_using_expression(T, expressions)\n            pbar.update(10)\n            res = _compress_both(T, mask, pbar=pbar)\n            pbar.update(pbar.total - pbar.n)\n    elif isinstance(expressions, list):\n        return _filter_using_list_of_dicts(T, expressions, filter_type, tqdm)\n    else:\n        raise TypeError\n        # create new tables\n\n    return res\n
    "},{"location":"reference/reindex/","title":"Reindex","text":""},{"location":"reference/reindex/#tablite.reindex","title":"tablite.reindex","text":""},{"location":"reference/reindex/#tablite.reindex-classes","title":"Classes","text":""},{"location":"reference/reindex/#tablite.reindex-functions","title":"Functions","text":""},{"location":"reference/reindex/#tablite.reindex.reindex","title":"tablite.reindex.reindex(T, index, names=None, tqdm=_tqdm, pbar=None)","text":"

    Constant Memory helper for reindexing pages.

    Memory usage is set by datatype and Config.PAGE_SIZE

    PARAMETER DESCRIPTION T

    subclass of Table

    TYPE: Table

    index

    int64.

    TYPE: array

    names

    list of names from T to reindex.

    TYPE: (list, str) DEFAULT: None

    tqdm

    Defaults to _tqdm.

    TYPE: tqdm DEFAULT: tqdm

    pbar

    Defaults to None.

    TYPE: pbar DEFAULT: None

    RETURNS DESCRIPTION _type_

    description

    Source code in tablite/reindex.py
    def reindex(T, index, names=None, tqdm=_tqdm, pbar=None):\n    \"\"\"Constant Memory helper for reindexing pages.\n\n    Memory usage is set by datatype and Config.PAGE_SIZE\n\n    Args:\n        T (Table): subclass of Table\n        index (np.array): int64.\n        names (list, str): list of names from T to reindex.\n        tqdm (tqdm, optional): Defaults to _tqdm.\n        pbar (pbar, optional): Defaults to None.\n\n    Returns:\n        _type_: _description_\n    \"\"\"\n    if names is None:\n        names = list(T.columns.keys())\n\n    if pbar is None:\n        total = len(names)\n        pbar = tqdm(total=total, desc=\"join\", disable=Config.TQDM_DISABLE)\n\n    sub_cls_check(T, BaseTable)\n    cls = type(T)\n    result = cls()\n    for name in names:\n        result.add_column(name)\n        col = result[name]\n\n        for start, end in Config.page_steps(len(index)):\n            indices = index[start:end]\n            values = T[name].get_by_indices(indices)\n            # in these values, the index of -1 will be wrong.\n            # so if there is any -1 in the indices, they will\n            # have to be replaced with Nones\n            mask = indices == -1\n            if np.any(mask):\n                nones = np.full(index.shape, fill_value=None)\n                values = np.where(mask, nones, values)\n            col.extend(values)\n        pbar.update(1)\n\n    return result\n
    "},{"location":"reference/sort_utils/","title":"Sort utils","text":""},{"location":"reference/sort_utils/#tablite.sort_utils","title":"tablite.sort_utils","text":""},{"location":"reference/sort_utils/#tablite.sort_utils-attributes","title":"Attributes","text":""},{"location":"reference/sort_utils/#tablite.sort_utils.uca_collator","title":"tablite.sort_utils.uca_collator = Collator() module-attribute","text":""},{"location":"reference/sort_utils/#tablite.sort_utils.modes","title":"tablite.sort_utils.modes = {'alphanumeric': text_sort, 'unix': unix_sort, 'excel': excel_sort} module-attribute","text":""},{"location":"reference/sort_utils/#tablite.sort_utils-classes","title":"Classes","text":""},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict","title":"tablite.sort_utils.HashDict","text":"

    Bases: dict

    This class is just a nicity syntatic sugar for debugging. Function identically to regular dictionary, just uses tupled key.

    "},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict-functions","title":"Functions","text":""},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict.items","title":"tablite.sort_utils.HashDict.items()","text":"Source code in tablite/sort_utils.py
    def items(self):\n    return [(k, v) for (_, k), v in super().items()]\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict.keys","title":"tablite.sort_utils.HashDict.keys()","text":"Source code in tablite/sort_utils.py
    def keys(self):\n    return [k for (_, k) in super().keys()]\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict.__iter__","title":"tablite.sort_utils.HashDict.__iter__() -> Iterator","text":"Source code in tablite/sort_utils.py
    def __iter__(self) -> Iterator:\n    return (k for (_, k) in super().keys())\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict.__getitem__","title":"tablite.sort_utils.HashDict.__getitem__(key)","text":"Source code in tablite/sort_utils.py
    def __getitem__(self, key):\n    return super().__getitem__(self._get_hash(key))\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict.__setitem__","title":"tablite.sort_utils.HashDict.__setitem__(key, value)","text":"Source code in tablite/sort_utils.py
    def __setitem__(self, key, value):\n    return super().__setitem__(self._get_hash(key), value)\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict.__contains__","title":"tablite.sort_utils.HashDict.__contains__(key) -> bool","text":"Source code in tablite/sort_utils.py
    def __contains__(self, key) -> bool:\n    return super().__contains__(self._get_hash(key))\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict.__delitem__","title":"tablite.sort_utils.HashDict.__delitem__(key)","text":"Source code in tablite/sort_utils.py
    def __delitem__(self, key):\n    return super().__delitem__(self._get_hash(key))\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict.__repr__","title":"tablite.sort_utils.HashDict.__repr__() -> str","text":"Source code in tablite/sort_utils.py
    def __repr__(self) -> str:\n    return '{' + \", \".join([f\"{k}: {v}\" for k, v in self.items()]) + '}'\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.HashDict.__str__","title":"tablite.sort_utils.HashDict.__str__() -> str","text":"Source code in tablite/sort_utils.py
    def __str__(self) -> str:\n    return repr(self)\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils-functions","title":"Functions","text":""},{"location":"reference/sort_utils/#tablite.sort_utils.text_sort","title":"tablite.sort_utils.text_sort(values, reverse=False)","text":"

    Sorts everything as text.

    Source code in tablite/sort_utils.py
    def text_sort(values, reverse=False):\n    \"\"\"\n    Sorts everything as text.\n    \"\"\"\n    text = {str(i): i for i in values}\n    L = list(text.keys())\n    L.sort(key=uca_collator.sort_key, reverse=reverse)\n    d = {text[value]: ix for ix, value in enumerate(L)}\n    return d\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.unix_sort","title":"tablite.sort_utils.unix_sort(values, reverse=False)","text":"

    Unix sortation sorts by the following order:

    | rank | type | value | +------+-----------+--------------------------------------------+ | 0 | None | floating point -infinite | | 1 | bool | 0 as False, 1 as True | | 2 | int | as numeric value | | 2 | float | as numeric value | | 3 | time | \u03c4 * seconds into the day / (24 * 60 * 60) | | 4 | date | as integer days since 1970/1/1 | | 5 | datetime | as float using date (int) + time (decimal) | | 6 | timedelta | as float using date (int) + time (decimal) | | 7 | str | using unicode | +------+-----------+--------------------------------------------+

    \u03c4 = 2 * \u03c0

    Source code in tablite/sort_utils.py
    def unix_sort(values, reverse=False):\n    \"\"\"\n    Unix sortation sorts by the following order:\n\n    | rank | type      | value                                      |\n    +------+-----------+--------------------------------------------+\n    |   0  | None      | floating point -infinite                   |\n    |   1  | bool      | 0 as False, 1 as True                      |\n    |   2  | int       | as numeric value                           |\n    |   2  | float     | as numeric value                           |\n    |   3  | time      | \u03c4 * seconds into the day / (24 * 60 * 60)  |\n    |   4  | date      | as integer days since 1970/1/1             |\n    |   5  | datetime  | as float using date (int) + time (decimal) |\n    |   6  | timedelta | as float using date (int) + time (decimal) |\n    |   7  | str       | using unicode                              |\n    +------+-----------+--------------------------------------------+\n\n    \u03c4 = 2 * \u03c0\n\n    \"\"\"\n    text, non_text = [], []\n\n    # L = []\n    # text = [i for i in values if isinstance(i, str)]\n    # text.sort(key=uca_collator.sort_key, reverse=reverse)\n    # text_code = _unix_typecodes[str]\n    # L = [(text_code, ix, v) for ix, v in enumerate(text)]\n\n    for value in values:\n        if isinstance(value, str):\n            text.append(value)\n        else:\n            t = type(value)\n            TC = _unix_typecodes[t]\n            tf = _unix_value_function[t]\n            VC = tf(value)\n            non_text.append((TC, VC, value))\n    non_text.sort(reverse=reverse)\n\n    text.sort(key=uca_collator.sort_key, reverse=reverse)\n    text_code = _unix_typecodes[str]\n    text = [(text_code, ix, v) for ix, v in enumerate(text)]\n\n    d = HashDict()\n    L = non_text + text\n    for ix, (_, _, value) in enumerate(L):\n        d[value] = ix\n    return d\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.excel_sort","title":"tablite.sort_utils.excel_sort(values, reverse=False)","text":"

    Excel sortation sorts by the following order:

    | rank | type | value | +------+-----------+--------------------------------------------+ | 1 | int | as numeric value | | 1 | float | as numeric value | | 1 | time | as seconds into the day / (24 * 60 * 60) | | 1 | date | as integer days since 1900/1/1 | | 1 | datetime | as float using date (int) + time (decimal) | | (1)*| timedelta | as float using date (int) + time (decimal) | | 2 | str | using unicode | | 3 | bool | 0 as False, 1 as True | | 4 | None | floating point infinite. | +------+-----------+--------------------------------------------+

    • Excel doesn't have timedelta.
    Source code in tablite/sort_utils.py
    def excel_sort(values, reverse=False):\n    \"\"\"\n    Excel sortation sorts by the following order:\n\n    | rank | type      | value                                      |\n    +------+-----------+--------------------------------------------+\n    |   1  | int       | as numeric value                           |\n    |   1  | float     | as numeric value                           |\n    |   1  | time      | as seconds into the day / (24 * 60 * 60)   |\n    |   1  | date      | as integer days since 1900/1/1             |\n    |   1  | datetime  | as float using date (int) + time (decimal) |\n    |  (1)*| timedelta | as float using date (int) + time (decimal) |\n    |   2  | str       | using unicode                              |\n    |   3  | bool      | 0 as False, 1 as True                      |\n    |   4  | None      | floating point infinite.                   |\n    +------+-----------+--------------------------------------------+\n\n    * Excel doesn't have timedelta.\n    \"\"\"\n\n    def tup(TC, value):\n        return (TC, _excel_value_function[t](value), value)\n\n    text, numeric, booles, nones = [], [], [], []\n    for value in values:\n        t = type(value)\n        TC = _excel_typecodes[t]\n\n        if TC == 0:\n            numeric.append(tup(TC, value))\n        elif TC == 1:\n            text.append(value)  # text is processed later.\n        elif TC == 2:\n            booles.append(tup(TC, value))\n        elif TC == 3:\n            booles.append(tup(TC, value))\n        else:\n            raise TypeError(f\"no typecode for {value}\")\n\n    if text:\n        text.sort(key=uca_collator.sort_key, reverse=reverse)\n        text = [(2, ix, v) for ix, v in enumerate(text)]\n\n    numeric.sort(reverse=reverse)\n    booles.sort(reverse=reverse)\n    nones.sort(reverse=reverse)\n\n    if reverse:\n        L = nones + booles + text + numeric\n    else:\n        L = numeric + text + booles + nones\n    d = {value: ix for ix, (_, _, value) in enumerate(L)}\n    return d\n
    "},{"location":"reference/sort_utils/#tablite.sort_utils.rank","title":"tablite.sort_utils.rank(values, reverse, mode)","text":"

    values: list of values to sort. reverse: bool mode: as 'text', as 'numeric' or as 'excel' return: dict: d[value] = rank

    Source code in tablite/sort_utils.py
    def rank(values, reverse, mode):\n    \"\"\"\n    values: list of values to sort.\n    reverse: bool\n    mode: as 'text', as 'numeric' or as 'excel'\n    return: dict: d[value] = rank\n    \"\"\"\n    if mode not in modes:\n        raise ValueError(f\"{mode} not in list of modes: {list(modes)}\")\n    f = modes.get(mode)\n    return f(values, reverse)\n
    "},{"location":"reference/sortation/","title":"Sortation","text":""},{"location":"reference/sortation/#tablite.sortation","title":"tablite.sortation","text":""},{"location":"reference/sortation/#tablite.sortation-attributes","title":"Attributes","text":""},{"location":"reference/sortation/#tablite.sortation-classes","title":"Classes","text":""},{"location":"reference/sortation/#tablite.sortation-functions","title":"Functions","text":""},{"location":"reference/sortation/#tablite.sortation.sort_index","title":"tablite.sortation.sort_index(T, mapping, sort_mode='excel', tqdm=_tqdm, pbar=None)","text":"

    helper for methods sort and is_sorted

    param: sort_mode: str: \"alphanumeric\", \"unix\", or, \"excel\" (default) param: **kwargs: sort criteria. See Table.sort()

    Source code in tablite/sortation.py
    def sort_index(T, mapping, sort_mode=\"excel\", tqdm=_tqdm, pbar=None):\n    \"\"\"\n    helper for methods `sort` and `is_sorted`\n\n    param: sort_mode: str: \"alphanumeric\", \"unix\", or, \"excel\" (default)\n    param: **kwargs: sort criteria. See Table.sort()\n    \"\"\"\n\n    sub_cls_check(T, BaseTable)\n\n    if not isinstance(mapping, dict) or not mapping:\n        raise TypeError(\"Expected mapping (dict)?\")\n\n    for k, v in mapping.items():\n        if k not in T.columns:\n            raise ValueError(f\"no column {k}\")\n        if not isinstance(v, bool):\n            raise ValueError(f\"{k} was mapped to {v} - a non-boolean\")\n\n    if sort_mode not in sort_modes:\n        raise ValueError(f\"{sort_mode} not in list of sort_modes: {list(sort_modes)}\")\n\n    rank = {i: tuple() for i in range(len(T))}  # create index and empty tuple for sortation.\n\n    _pbar = tqdm(total=len(mapping.items()), desc=\"creating sort index\") if pbar is None else pbar\n\n    for key, reverse in mapping.items():\n        col = T[key][:]\n        ranks = sort_rank(values=[numpy_to_python(v) for v in multitype_set(col)], reverse=reverse, mode=sort_mode)\n        assert isinstance(ranks, dict)\n        for ix, v in enumerate(col):\n            v2 = numpy_to_python(v)\n            rank[ix] += (ranks[v2],)  # add tuple for each sortation level.\n\n        _pbar.update(1)\n\n    del col\n    del ranks\n\n    new_order = [(r, i) for i, r in rank.items()]  # tuples are listed and sort...\n    del rank  # free memory.\n\n    new_order.sort()\n    sorted_index = [i for _, i in new_order]  # new index is extracted.\n    new_order.clear()\n    return np.array(sorted_index, dtype=np.int64)\n
    "},{"location":"reference/sortation/#tablite.sortation.reindex","title":"tablite.sortation.reindex(T, index)","text":"

    index: list of integers that declare sort order.

    Examples:

    Table:  ['a','b','c','d','e','f','g','h']\nindex:  [0,2,4,6]\nresult: ['b','d','f','h']\n\nTable:  ['a','b','c','d','e','f','g','h']\nindex:  [0,2,4,6,1,3,5,7]\nresult: ['a','c','e','g','b','d','f','h']\n
    Source code in tablite/sortation.py
    def reindex(T, index):\n    \"\"\"\n    index: list of integers that declare sort order.\n\n    Examples:\n\n        Table:  ['a','b','c','d','e','f','g','h']\n        index:  [0,2,4,6]\n        result: ['b','d','f','h']\n\n        Table:  ['a','b','c','d','e','f','g','h']\n        index:  [0,2,4,6,1,3,5,7]\n        result: ['a','c','e','g','b','d','f','h']\n\n    \"\"\"\n    sub_cls_check(T, BaseTable)\n    if isinstance(index, list):\n        index = np.array(index, dtype=int)\n    type_check(index, np.ndarray)\n    if max(index) >= len(T):\n        raise IndexError(\"index out of range: max(index) > len(self)\")\n    if min(index) < -len(T):\n        raise IndexError(\"index out of range: min(index) < -len(self)\")\n\n    fields = len(T) * len(T.columns)\n    m = select_processing_method(fields, _reindex, _mp_reindex)\n    return m(T, index)\n
    "},{"location":"reference/sortation/#tablite.sortation.sort","title":"tablite.sortation.sort(T, mapping, sort_mode='excel', tqdm=_tqdm, pbar: _tqdm = None)","text":"

    Perform multi-pass sorting with precedence given order of column names. sort_mode: str: \"alphanumeric\", \"unix\", or, \"excel\" kwargs: keys: columns, values: 'reverse' as boolean.

    examples: Table.sort('A'=False) means sort by 'A' in ascending order. Table.sort('A'=True, 'B'=False) means sort 'A' in descending order, then (2nd priority) sort B in ascending order.

    Source code in tablite/sortation.py
    def sort(T, mapping, sort_mode=\"excel\", tqdm=_tqdm, pbar: _tqdm = None):\n    \"\"\"Perform multi-pass sorting with precedence given order of column names.\n    sort_mode: str: \"alphanumeric\", \"unix\", or, \"excel\"\n    kwargs:\n        keys: columns,\n        values: 'reverse' as boolean.\n\n    examples:\n    Table.sort('A'=False) means sort by 'A' in ascending order.\n    Table.sort('A'=True, 'B'=False) means sort 'A' in descending order, then (2nd priority)\n    sort B in ascending order.\n    \"\"\"\n    sub_cls_check(T, BaseTable)\n\n    index = sort_index(T, mapping, sort_mode=sort_mode, tqdm=_tqdm, pbar=pbar)\n    m = select_processing_method(len(T) * len(T.columns), _sp_reindex, _mp_reindex)\n    return m(T, index, tqdm=tqdm, pbar=pbar)\n
    "},{"location":"reference/sortation/#tablite.sortation.is_sorted","title":"tablite.sortation.is_sorted(T, mapping, sort_mode='excel')","text":"

    Performs multi-pass sorting check with precedence given order of column names.

    PARAMETER DESCRIPTION mapping

    sort criteria. See Table.sort()

    RETURNS DESCRIPTION

    bool

    Source code in tablite/sortation.py
    def is_sorted(T, mapping, sort_mode=\"excel\"):\n    \"\"\"Performs multi-pass sorting check with precedence given order of column names.\n\n    Args:\n        mapping: sort criteria. See Table.sort()\n        sort_mode = sort mode. See Table.sort()\n\n    Returns:\n        bool\n    \"\"\"\n    index = sort_index(T, mapping, sort_mode=sort_mode)\n    match = np.arange(len(T))\n    return np.all(index == match)\n
    "},{"location":"reference/tools/","title":"Tools","text":""},{"location":"reference/tools/#tablite.tools","title":"tablite.tools","text":""},{"location":"reference/tools/#tablite.tools-attributes","title":"Attributes","text":""},{"location":"reference/tools/#tablite.tools.guess","title":"tablite.tools.guess = DataTypes.guess module-attribute","text":""},{"location":"reference/tools/#tablite.tools.xround","title":"tablite.tools.xround = DataTypes.round module-attribute","text":""},{"location":"reference/tools/#tablite.tools-classes","title":"Classes","text":""},{"location":"reference/tools/#tablite.tools-functions","title":"Functions","text":""},{"location":"reference/tools/#tablite.tools.head","title":"tablite.tools.head(path, linecount=5, delimiter=None)","text":"

    Gets the head of any supported file format.

    Source code in tablite/tools.py
    def head(path, linecount=5, delimiter=None):\n    \"\"\"\n    Gets the head of any supported file format.\n    \"\"\"\n    return get_headers(path, linecount=linecount, delimiter=delimiter)\n
    "},{"location":"reference/utils/","title":"Utils","text":""},{"location":"reference/utils/#tablite.utils","title":"tablite.utils","text":""},{"location":"reference/utils/#tablite.utils-attributes","title":"Attributes","text":""},{"location":"reference/utils/#tablite.utils.letters","title":"tablite.utils.letters = string.ascii_lowercase + string.digits module-attribute","text":""},{"location":"reference/utils/#tablite.utils.NoneType","title":"tablite.utils.NoneType = type(None) module-attribute","text":""},{"location":"reference/utils/#tablite.utils.required_keys","title":"tablite.utils.required_keys = {'min', 'max', 'mean', 'median', 'stdev', 'mode', 'distinct', 'iqr_low', 'iqr_high', 'iqr', 'sum', 'summary type', 'histogram'} module-attribute","text":""},{"location":"reference/utils/#tablite.utils.summary_methods","title":"tablite.utils.summary_methods = {bool: _boolean_statistics_summary, int: _numeric_statistics_summary, float: _numeric_statistics_summary, str: _string_statistics_summary, date: _date_statistics_summary, datetime: _datetime_statistics_summary, time: _time_statistics_summary, timedelta: _timedelta_statistics_summary, type(None): _none_type_summary} module-attribute","text":""},{"location":"reference/utils/#tablite.utils-classes","title":"Classes","text":""},{"location":"reference/utils/#tablite.utils-functions","title":"Functions","text":""},{"location":"reference/utils/#tablite.utils.generate_random_string","title":"tablite.utils.generate_random_string(len)","text":"Source code in tablite/utils.py
    def generate_random_string(len):\n    return \"\".join(random.choice(letters) for i in range(len))\n
    "},{"location":"reference/utils/#tablite.utils.type_check","title":"tablite.utils.type_check(var, kind)","text":"Source code in tablite/utils.py
    def type_check(var, kind):\n    if not isinstance(var, kind):\n        raise TypeError(f\"Expected {kind}, not {type(var)}\")\n
    "},{"location":"reference/utils/#tablite.utils.sub_cls_check","title":"tablite.utils.sub_cls_check(c, kind)","text":"Source code in tablite/utils.py
    def sub_cls_check(c, kind):\n    if not issubclass(type(c), kind):\n        raise TypeError(f\"Expected {kind}, not {type(c)}\")\n
    "},{"location":"reference/utils/#tablite.utils.name_check","title":"tablite.utils.name_check(options, *names)","text":"Source code in tablite/utils.py
    def name_check(options, *names):\n    for n in names:\n        if n not in options:\n            raise ValueError(f\"{n} not in {options}\")\n
    "},{"location":"reference/utils/#tablite.utils.unique_name","title":"tablite.utils.unique_name(wanted_name, set_of_names)","text":"

    returns a wanted_name as wanted_name_i given a list of names which guarantees unique naming.

    Source code in tablite/utils.py
    def unique_name(wanted_name, set_of_names):\n    \"\"\"\n    returns a wanted_name as wanted_name_i given a list of names\n    which guarantees unique naming.\n    \"\"\"\n    if not isinstance(set_of_names, set):\n        set_of_names = set(set_of_names)\n    name, i = wanted_name, 1\n    while name in set_of_names:\n        name = f\"{wanted_name}_{i}\"\n        i += 1\n    return name\n
    "},{"location":"reference/utils/#tablite.utils.expression_interpreter","title":"tablite.utils.expression_interpreter(expression, columns)","text":"

    Interprets valid expressions such as:

    \"all((A==B, C!=4, 200<D))\"\n
    as

    def _f(A,B,C,D): return all((A==B, C!=4, 200<D))

    using python's compiler.

    Source code in tablite/utils.py
    def expression_interpreter(expression, columns):\n    \"\"\"\n    Interprets valid expressions such as:\n\n        \"all((A==B, C!=4, 200<D))\"\n\n    as:\n        def _f(A,B,C,D):\n            return all((A==B, C!=4, 200<D))\n\n    using python's compiler.\n    \"\"\"\n    if not isinstance(expression, str):\n        raise TypeError(f\"`{expression}` is not a str\")\n    if not isinstance(columns, list):\n        raise TypeError\n    if not all(isinstance(i, str) for i in columns):\n        raise TypeError\n\n    req_columns = \", \".join(i for i in columns if i in expression)\n    script = f\"def f({req_columns}):\\n    return {expression}\"\n    tree = ast.parse(script)\n    code = compile(tree, filename=\"blah\", mode=\"exec\")\n    namespace = {}\n    exec(code, namespace)\n    f = namespace[\"f\"]\n    if not callable(f):\n        raise ValueError(f\"The expression could not be parse: {expression}\")\n    return f\n
    "},{"location":"reference/utils/#tablite.utils.intercept","title":"tablite.utils.intercept(A, B)","text":"

    Enables calculation of the intercept of two range objects. Used to determine if a datablock contains a slice.

    PARAMETER DESCRIPTION A

    range

    B

    range

    RETURNS DESCRIPTION range

    The intercept of ranges A and B.

    Source code in tablite/utils.py
    def intercept(A, B):\n    \"\"\"Enables calculation of the intercept of two range objects.\n    Used to determine if a datablock contains a slice.\n\n    Args:\n        A: range\n        B: range\n\n    Returns:\n        range: The intercept of ranges A and B.\n    \"\"\"\n    type_check(A, range)\n    type_check(B, range)\n\n    if A.step < 1:\n        A = range(A.stop + 1, A.start + 1, 1)\n    if B.step < 1:\n        B = range(B.stop + 1, B.start + 1, 1)\n\n    if len(A) == 0:\n        return range(0)\n    if len(B) == 0:\n        return range(0)\n\n    if A.stop <= B.start:\n        return range(0)\n    if A.start >= B.stop:\n        return range(0)\n\n    if A.start <= B.start:\n        if A.stop <= B.stop:\n            start, end = B.start, A.stop\n        elif A.stop > B.stop:\n            start, end = B.start, B.stop\n        else:\n            raise ValueError(\"bad logic\")\n    elif A.start < B.stop:\n        if A.stop <= B.stop:\n            start, end = A.start, A.stop\n        elif A.stop > B.stop:\n            start, end = A.start, B.stop\n        else:\n            raise ValueError(\"bad logic\")\n    else:\n        raise ValueError(\"bad logic\")\n\n    a_steps = math.ceil((start - A.start) / A.step)\n    a_start = (a_steps * A.step) + A.start\n\n    b_steps = math.ceil((start - B.start) / B.step)\n    b_start = (b_steps * B.step) + B.start\n\n    if A.step == 1 or B.step == 1:\n        start = max(a_start, b_start)\n        step = max(A.step, B.step)\n        return range(start, end, step)\n    elif A.step == B.step:\n        a, b = min(A.start, B.start), max(A.start, B.start)\n        if (b - a) % A.step != 0:  # then the ranges are offset.\n            return range(0)\n        else:\n            return range(b, end, step)\n    else:\n        # determine common step size:\n        step = max(A.step, B.step) if math.gcd(A.step, B.step) != 1 else A.step * B.step\n        # examples:\n        # 119 <-- 17 if 1 != 1 else 119 <-- max(7, 17) if math.gcd(7, 17) != 1 else 7 * 17\n        #  30 <-- 30 if 3 != 1 else 90 <-- max(3, 30) if math.gcd(3, 30) != 1 else 3*30\n        if A.step < B.step:\n            for n in range(a_start, end, A.step):  # increment in smallest step to identify the first common value.\n                if n < b_start:\n                    continue\n                elif (n - b_start) % B.step == 0:\n                    return range(n, end, step)  # common value found.\n        else:\n            for n in range(b_start, end, B.step):\n                if n < a_start:\n                    continue\n                elif (n - a_start) % A.step == 0:\n                    return range(n, end, step)\n\n        return range(0)\n
    "},{"location":"reference/utils/#tablite.utils.summary_statistics","title":"tablite.utils.summary_statistics(values, counts)","text":"

    values: any type counts: integer

    returns dict with: - min (int/float, length of str, date) - max (int/float, length of str, date) - mean (int/float, length of str, date) - median (int/float, length of str, date) - stdev (int/float, length of str, date) - mode (int/float, length of str, date) - distinct (number of distinct values) - iqr (int/float, length of str, date) - sum (int/float, length of str, date) - histogram (2 arrays: values, count of each values)

    Source code in tablite/utils.py
    def summary_statistics(values, counts):\n    \"\"\"\n    values: any type\n    counts: integer\n\n    returns dict with:\n    - min (int/float, length of str, date)\n    - max (int/float, length of str, date)\n    - mean (int/float, length of str, date)\n    - median (int/float, length of str, date)\n    - stdev (int/float, length of str, date)\n    - mode (int/float, length of str, date)\n    - distinct (number of distinct values)\n    - iqr (int/float, length of str, date)\n    - sum (int/float, length of str, date)\n    - histogram (2 arrays: values, count of each values)\n    \"\"\"\n    # determine the dominant datatype:\n    dtypes = defaultdict(int)\n    most_frequent, most_frequent_dtype = 0, int\n    for v, c in zip(values, counts):\n        dtype = type(v)\n        total = dtypes[dtype] + c\n        dtypes[dtype] = total\n        if total > most_frequent:\n            most_frequent_dtype = dtype\n            most_frequent = total\n\n    if most_frequent == 0:\n        return {}\n\n    most_frequent_dtype = max(dtypes, key=dtypes.get)\n    mask = [type(v) == most_frequent_dtype for v in values]\n    v = list(compress(values, mask))\n    c = list(compress(counts, mask))\n\n    f = summary_methods.get(most_frequent_dtype, int)\n    result = f(v, c)\n    result[\"distinct\"] = len(values)\n    result[\"summary type\"] = most_frequent_dtype.__name__\n    result[\"histogram\"] = [values, counts]\n    assert set(result.keys()) == required_keys, \"Key missing!\"\n    return result\n
    "},{"location":"reference/utils/#tablite.utils.date_range","title":"tablite.utils.date_range(start, stop, step)","text":"Source code in tablite/utils.py
    def date_range(start, stop, step):\n    if not isinstance(start, datetime):\n        raise TypeError(\"start is not datetime\")\n    if not isinstance(stop, datetime):\n        raise TypeError(\"stop is not datetime\")\n    if not isinstance(step, timedelta):\n        raise TypeError(\"step is not timedelta\")\n    n = (stop - start) // step\n    return [start + step * i for i in range(n)]\n
    "},{"location":"reference/utils/#tablite.utils.dict_to_rows","title":"tablite.utils.dict_to_rows(d)","text":"Source code in tablite/utils.py
    def dict_to_rows(d):\n    type_check(d, dict)\n    rows = []\n    max_length = max(len(i) for i in d.values())\n    order = list(d.keys())\n    rows.append(order)\n    for i in range(max_length):\n        row = [d[k][i] for k in order]\n        rows.append(row)\n    return rows\n
    "},{"location":"reference/utils/#tablite.utils.calc_col_count","title":"tablite.utils.calc_col_count(letters: str)","text":"Source code in tablite/utils.py
    def calc_col_count(letters: str):\n    ord_nil = ord(\"A\") - 1\n    cols_per_letter = ord(\"Z\") - ord_nil\n    col_count = 0\n\n    for i, v in enumerate(reversed(letters)):\n        col_count = col_count + (ord(v) - ord_nil) * pow(cols_per_letter, i)\n\n    return col_count\n
    "},{"location":"reference/utils/#tablite.utils.calc_true_dims","title":"tablite.utils.calc_true_dims(sheet)","text":"Source code in tablite/utils.py
    def calc_true_dims(sheet):\n    src = sheet._get_source()\n    max_col, max_row = 0, 0\n\n    regex = re.compile(\"\\d+\")\n\n    def handleStartElement(name, attrs):\n        nonlocal max_col, max_row\n\n        if name == \"c\":\n            last_index = attrs[\"r\"]\n            idx, _ = next(regex.finditer(last_index)).span()\n            letters, digits = last_index[0:idx], int(last_index[idx:])\n\n            col_idx, row_idx = calc_col_count(letters), digits\n\n            max_col, max_row = max(max_col, col_idx), max(max_row, row_idx)\n\n    parser = expat.ParserCreate()\n    parser.buffer_text = True\n    parser.StartElementHandler = handleStartElement\n    parser.ParseFile(src)\n\n    return max_col, max_row\n
    "},{"location":"reference/utils/#tablite.utils.fixup_worksheet","title":"tablite.utils.fixup_worksheet(worksheet)","text":"Source code in tablite/utils.py
    def fixup_worksheet(worksheet):\n    try:\n        ws_cols, ws_rows = calc_true_dims(worksheet)\n\n        worksheet._max_column = ws_cols\n        worksheet._max_row = ws_rows\n    except Exception as e:\n        logging.error(f\"Failed to fetch true dimensions: {e}\")\n
    "},{"location":"reference/utils/#tablite.utils.update_access_time","title":"tablite.utils.update_access_time(path)","text":"Source code in tablite/utils.py
    def update_access_time(path):\n    path = Path(path)\n    stat = path.stat()\n    os.utime(path, (now(), stat.st_mtime))\n
    "},{"location":"reference/utils/#tablite.utils.load_numpy","title":"tablite.utils.load_numpy(path)","text":"Source code in tablite/utils.py
    def load_numpy(path):\n    update_access_time(path)\n\n    return np.load(path, allow_pickle=True, fix_imports=False)\n
    "},{"location":"reference/utils/#tablite.utils.select_type_name","title":"tablite.utils.select_type_name(dtypes: dict)","text":"Source code in tablite/utils.py
    def select_type_name(dtypes: dict):\n    dtypes = [t for t in dtypes.items() if t[0] != NoneType]\n\n    if len(dtypes) == 0:\n        return \"empty\"\n\n    (best_type, _), *_ = sorted(dtypes, key=lambda t: t[1], reverse=True)\n\n    return best_type.__name__\n
    "},{"location":"reference/utils/#tablite.utils.get_predominant_types","title":"tablite.utils.get_predominant_types(table, all_dtypes=None)","text":"Source code in tablite/utils.py
    def get_predominant_types(table, all_dtypes=None):\n    if all_dtypes is None:\n        all_dtypes = table.types()\n\n    dtypes = {\n        k: select_type_name(v)\n        for k, v in all_dtypes.items()\n    }\n\n    return dtypes\n
    "},{"location":"reference/utils/#tablite.utils.py_to_nim_encoding","title":"tablite.utils.py_to_nim_encoding(encoding: str) -> str","text":"Source code in tablite/utils.py
    def py_to_nim_encoding(encoding: str) -> str:\n    if encoding is None or encoding.lower() in [\"ascii\", \"utf8\", \"utf-8\", \"utf-8-sig\"]:\n        return \"ENC_UTF8\"\n    elif encoding.lower() in [\"utf16\", \"utf-16\"]:\n        return \"ENC_UTF16\"\n    elif encoding in Config.NIM_SUPPORTED_CONV_TYPES:\n        return f\"ENC_CONV|{encoding}\"\n\n    raise NotImplementedError(f\"encoding not implemented: {encoding}\")\n
    "},{"location":"reference/version/","title":"Version","text":""},{"location":"reference/version/#tablite.version","title":"tablite.version","text":""},{"location":"reference/version/#tablite.version-attributes","title":"Attributes","text":""},{"location":"reference/version/#tablite.version.__version_info__","title":"tablite.version.__version_info__ = (major, minor, patch) module-attribute","text":""},{"location":"reference/version/#tablite.version.__version__","title":"tablite.version.__version__ = '.'.join(str(i) for i in __version_info__) module-attribute","text":""}]} \ No newline at end of file diff --git a/master/sitemap.xml b/master/sitemap.xml index 058b25af..dc824927 100644 --- a/master/sitemap.xml +++ b/master/sitemap.xml @@ -2,152 +2,147 @@ https://root-11.github.io/tablite/master/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/benchmarks/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/changelog/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/tutorial/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/base/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/config/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/core/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/datasets/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/datatypes/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/diff/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/export_utils/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/file_reader_utils/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/groupby_utils/ - 2024-04-04 - daily - - - https://root-11.github.io/tablite/master/reference/groupbys/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/import_utils/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/imputation/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/joins/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/lookup/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/match/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/merge/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/mp_utils/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/nimlite/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/pivots/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/redux/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/reindex/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/sort_utils/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/sortation/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/tools/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/utils/ - 2024-04-04 + 2024-04-05 daily https://root-11.github.io/tablite/master/reference/version/ - 2024-04-04 + 2024-04-05 daily \ No newline at end of file diff --git a/master/sitemap.xml.gz b/master/sitemap.xml.gz index 380250ac1c84f2eda1c5d1fa3f8ee3f42f991a57..0d1ff140d08b62192cd36c10dda39650d8e30f21 100644 GIT binary patch literal 395 zcmV;60d)Q!iwFoEs1IfW|8r?{Wo=<_E_iKh0M(e!Zi6rk#_xR!#J!}gHfftEz3mCM za}~fq)+Ejxr+?mlp{=IgryU$c0vz+{r%zU5sCQ>)iz84p=(pv%T9pO(4twLrZTb1} zzI-Wn&7w|ZBXE(EecYB4b9htgbUIbhFgP7zg*2+zP5B|Jdr+;a*Roj@dCUU0yX&Ye zcFn93vkW2fa=orb!&7Q2gNk))vxLf845Vr`r$4@(;|}{~v)Vi@S9w0?lzZ$Ut(&wT zVIcT!dTs2bRu7RImip!T6JeV@o1CUY6aahEwKxi3<-|xvTLGv915mC5P_2$23b;c* zm{Axq$?~|@oXdivIB32E5Jqi=K@>2Y0}}5O8!OA{!C2T6=pINQ{YaQXdl5)(oX~>F z;3;n@x!Dwj{e{NMGTI^@QV@mgG`mj zt^$~lHN=_Y^v~NbwAIx6w1cBafMY)W^vOyL&F(zA;s_Ki`fa(c>aqae<6ynnmY*N* ztCw=uJ~fG41TJ#2kK1x$4sS}GPNynbhFOPLAsH3>DL+JY530I)E!(Fek44~icO8wx zzFkyemLWu5t=E;YJf*I(s91L{OQ@{HK&nwo`s3Rdo`2US>|Y#~#x9NqYk$ z!FM}o>n@FYh}^K$FV~+4yX@ICYdS;$us227U{9b2Ac6FTFoo_Sms}RKorP9; z22Xh(%pI~Q>@T!lmeCIJkb)@WS#v+hA_g+DTBkV(+%&wMuM3ixgFT|K4wYa?XJN$F p4c>X{0^^(8)+aHvvb|-2T000V~ySe}X diff --git a/master/tablite/core.py b/master/tablite/core.py index b784a51a..31cbd823 100644 --- a/master/tablite/core.py +++ b/master/tablite/core.py @@ -17,12 +17,12 @@ from tablite import lookup from tablite import match from tablite import sortation -from tablite import groupbys from tablite import pivots from tablite import imputation from tablite import diff from tablite.config import Config from tablite.nimlite import column_select as _column_select, ColumnSelectorDict, ValidSkipEmpty +from tablite.nimlite import groupby as _groupby from mplite import TaskManager as _TaskManager logging.getLogger("lml").propagate = False @@ -611,7 +611,7 @@ def groupby(self, keys, functions, tqdm=_tqdm, pbar=None): https://github.com/root-11/tablite/blob/master/tests/test_groupby.py """ - return groupbys.groupby(self, keys, functions, tqdm=tqdm, pbar=pbar) + return _groupby(self, keys, functions, tqdm) def pivot(self, rows, columns, functions, values_as_rows=True, tqdm=_tqdm, pbar=None): """ diff --git a/master/tablite/groupby_utils.py b/master/tablite/groupby_utils.py index 9f38c833..3b6defcb 100644 --- a/master/tablite/groupby_utils.py +++ b/master/tablite/groupby_utils.py @@ -1,201 +1,13 @@ -from collections import defaultdict -from datetime import date, time, datetime, timedelta # noqa - - -class GroupbyFunction(object): - pass - - -class Limit(GroupbyFunction): - def __init__(self): - self.value = None - self.f = None - - def update(self, value): - if value is None: - pass - elif self.value is None: - self.value = value - else: - self.value = self.f((value, self.value)) - - -class Max(Limit): - def __init__(self): - super().__init__() - self.f = max - - -class Min(Limit): - def __init__(self): - super().__init__() - self.f = min - - -class Sum(GroupbyFunction): - def __init__(self): - self.value = 0 - - def update(self, value): - if isinstance(value, (type(None), date, time, datetime, str)): - raise ValueError(f"Sum of {type(value)} doesn't make sense.") - self.value += value - - -class Product(GroupbyFunction): - def __init__(self) -> None: - self.value = 1 - - def update(self, value): - self.value *= value - - -class First(GroupbyFunction): - empty = (None,) - # we will never receive a tuple, so using (None,) as the initial - # value will assure that IF None is the first value, then it can - # be captured correctly. - - def __init__(self): - self.value = self.empty - - def update(self, value): - if self.value is First.empty: - self.value = value - - -class Last(GroupbyFunction): - def __init__(self): - self.value = None - - def update(self, value): - self.value = value - - -class Count(GroupbyFunction): - def __init__(self): - self.value = 0 - - def update(self, value): - self.value += 1 - - -class CountUnique(GroupbyFunction): - def __init__(self): - self.items = set() - self.value = None - - def update(self, value): - self.items.add(value) - self.value = len(self.items) - - -class Average(GroupbyFunction): - def __init__(self): - self.sum = 0 - self.count = 0 - self.value = 0 - - def update(self, value): - if isinstance(value, (date, time, datetime, str)): - raise ValueError(f"Sum of {type(value)} doesn't make sense.") - if value is not None: - self.sum += value - self.count += 1 - self.value = self.sum / self.count - - -class StandardDeviation(GroupbyFunction): - """ - Uses J.P. Welfords (1962) algorithm. - For details see https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm - """ - - def __init__(self): - self.count = 0 - self.mean = 0 - self.c = 0.0 - - def update(self, value): - if isinstance(value, (date, time, datetime, str)): - raise ValueError(f"Std.dev. of {type(value)} doesn't make sense.") - if value is not None: - self.count += 1 - dt = value - self.mean - self.mean += dt / self.count - self.c += dt * (value - self.mean) - - @property - def value(self): - if self.count <= 1: - return 0.0 - variance = self.c / (self.count - 1) - return variance ** (1 / 2) - - -class Histogram(GroupbyFunction): - def __init__(self): - self.hist = defaultdict(int) - - def update(self, value): - self.hist[value] += 1 - - -class Median(Histogram): - def __init__(self): - super().__init__() - - @property - def value(self): - if not self.hist: - raise ValueError("No data.") - - keys = len(self.hist.keys()) - if keys == 1: - for k in self.hist: - return k - elif keys % 2 == 0: - A, B, total, midpoint = None, None, 0, sum(self.hist.values()) / 2 - for k, v in sorted(self.hist.items()): - total += v - A, B = B, k - if total > midpoint: - return (A + B) / 2 - else: - midpoint = sum(self.hist.values()) / 2 - total = 0 - for k, v in sorted(self.hist.items()): - total += v - if total > midpoint: - return k - - -class Mode(Histogram): - def __init__(self): - super().__init__() - - @property - def value(self): - L = [(v, k) for k, v in self.hist.items()] - L.sort(reverse=True) - _, most_frequent = L[0] # top of the list. - return most_frequent - - class GroupBy(object): - max = Max # shortcuts to avoid having to type a long list of imports. - min = Min - sum = Sum - product = Product - first = First - last = Last - count = Count - count_unique = CountUnique - avg = Average - stdev = StandardDeviation - median = Median - mode = Mode - - functions = [Max, Min, Sum, First, Last, Product, Count, CountUnique, Average, StandardDeviation, Median, Mode] - - function_names = {f.__name__: f for f in functions} + max = "max" + min = "min" + sum = "sum" + product = "product" + first = "first" + last = "last" + count = "count" + count_unique = "count_unique" + avg = "avg" + stdev = "stdev" + median = "median" + mode = "mode" \ No newline at end of file diff --git a/master/tablite/groupbys.py b/master/tablite/groupbys.py deleted file mode 100644 index c7f2352e..00000000 --- a/master/tablite/groupbys.py +++ /dev/null @@ -1,201 +0,0 @@ -from collections import defaultdict - -from tablite.config import Config -from tablite.base import BaseTable, Column -from tablite.groupby_utils import GroupBy, GroupbyFunction -from tablite.utils import unique_name - -from tqdm import tqdm as _tqdm - - -def groupby( - T, keys, functions, tqdm=_tqdm, pbar=None -): # TODO: This is single core code. - """ - keys: column names for grouping. - functions: [optional] list of column names and group functions (See GroupyBy class) - returns: table - - Example: - ``` - >>> t = Table() - >>> t.add_column('A', data=[1, 1, 2, 2, 3, 3] * 2) - >>> t.add_column('B', data=[1, 2, 3, 4, 5, 6] * 2) - >>> t.add_column('C', data=[6, 5, 4, 3, 2, 1] * 2) - >>> t.show() - +=====+=====+=====+ - | A | B | C | - | int | int | int | - +-----+-----+-----+ - | 1| 1| 6| - | 1| 2| 5| - | 2| 3| 4| - | 2| 4| 3| - | 3| 5| 2| - | 3| 6| 1| - | 1| 1| 6| - | 1| 2| 5| - | 2| 3| 4| - | 2| 4| 3| - | 3| 5| 2| - | 3| 6| 1| - +=====+=====+=====+ - >>> g = t.groupby(keys=['A', 'C'], functions=[('B', gb.sum)]) - >>> g.show() - +===+===+===+======+ - | # | A | C |Sum(B)| - |row|int|int| int | - +---+---+---+------+ - |0 | 1| 6| 2| - |1 | 1| 5| 4| - |2 | 2| 4| 6| - |3 | 2| 3| 8| - |4 | 3| 2| 10| - |5 | 3| 1| 12| - +===+===+===+======+ - ``` - - Cheat sheet: - - list of unique values - ``` - >>> g1 = t.groupby(keys=['A'], functions=[]) - >>> g1['A'][:] - [1,2,3] - ``` - alternatively: - ``` - >>> t['A'].unique() - [1,2,3] - ``` - list of unique values, grouped by longest combination. - ``` - >>> g2 = t.groupby(keys=['A', 'B'], functions=[]) - >>> g2['A'][:], g2['B'][:] - ([1,1,2,2,3,3], [1,2,3,4,5,6]) - ``` - alternatively use: - ``` - >>> list(zip(*t.index('A', 'B').keys())) - [(1,1,2,2,3,3) (1,2,3,4,5,6)] - ``` - - A key (unique values) and count hereof. - ``` - >>> g3 = t.groupby(keys=['A'], functions=[('A', gb.count)]) - >>> g3['A'][:], g3['Count(A)'][:] - ([1,2,3], [4,4,4]) - ``` - alternatively use: - ``` - >>> t['A'].histogram() - ([1,2,3], [4,4,4]) - ``` - for more examples see: https://github.com/root-11/tablite/blob/master/tests/test_groupby.py - - """ - if not isinstance(keys, list): - raise TypeError("expected keys as a list of column names") - - if keys: - if len(set(keys)) != len(keys): - duplicates = [k for k in keys if keys.count(k) > 1] - s = "" if len(duplicates) > 1 else "s" - raise ValueError( - f"duplicate key{s} found across rows and columns: {duplicates}" - ) - - if not isinstance(functions, list): - raise TypeError( - f"Expected functions to be a list of tuples. Got {type(functions)}" - ) - - if not keys + functions: - raise ValueError("No keys or functions?") - - if not all(len(i) == 2 for i in functions): - raise ValueError( - f"Expected each tuple in functions to be of length 2. \nGot {functions}" - ) - - if not all(isinstance(a, str) for a, _ in functions): - L = [(a, type(a)) for a, _ in functions if not isinstance(a, str)] - raise ValueError( - f"Expected column names in functions to be strings. Found: {L}" - ) - - if not all( - issubclass(b, GroupbyFunction) and b in GroupBy.functions for _, b in functions - ): - L = [b for _, b in functions if b not in GroupBy._functions] - if len(L) == 1: - singular = f"function {L[0]} is not in GroupBy.functions" - raise ValueError(singular) - else: - plural = f"the functions {L} are not in GroupBy.functions" - raise ValueError(plural) - - # only keys will produce unique values for each key group. - if keys and not functions: - cols = list(zip(*T.index(*keys))) - result = T.__class__() - - pbar = tqdm(total=len(keys), desc="groupby") if pbar is None else pbar - - for col_name, col in zip(keys, cols): - result[col_name] = col - - pbar.update(1) - return result - - # grouping is required... - # 1. Aggregate data. - aggregation_functions = defaultdict(dict) - cols = keys + [col_name for col_name, _ in functions] - seen, L = set(), [] - for c in cols: # maintains order of appearance. - if c not in seen: - seen.add(c) - L.append(c) - - # there's a table of values. - data = T[L] - if isinstance(data, Column): - tbl = BaseTable() - tbl[L[0]] = data - else: - tbl = data - - pbar = ( - tqdm(desc="groupby", total=len(tbl), disable=Config.TQDM_DISABLE) - if pbar is None - else pbar - ) - - for row in tbl.rows: - d = {col_name: value for col_name, value in zip(L, row)} - key = tuple([d[k] for k in keys]) - agg_functions = aggregation_functions.get(key) - if not agg_functions: - aggregation_functions[key] = agg_functions = [ - (col_name, f()) for col_name, f in functions - ] - for col_name, f in agg_functions: - f.update(d[col_name]) - - pbar.update(1) - - # 2. make dense table. - cols = [[] for _ in cols] - for key_tuple, funcs in aggregation_functions.items(): - for ix, key_value in enumerate(key_tuple): - cols[ix].append(key_value) - for ix, (_, f) in enumerate(funcs, start=len(keys)): - cols[ix].append(f.value) - - new_names = keys + [f"{f.__name__}({col_name})" for col_name, f in functions] - result = type(T)() # New Table. - for ix, (col_name, data) in enumerate(zip(new_names, cols)): - revised_name = unique_name(col_name, result.columns) - result[revised_name] = data - return result diff --git a/master/tablite/nimlite.py b/master/tablite/nimlite.py index 162b90da..92e711db 100644 --- a/master/tablite/nimlite.py +++ b/master/tablite/nimlite.py @@ -307,5 +307,8 @@ def repaginate(column: Column): def nearest_neighbour(T: BaseTable, sources: Union[list[str], None], missing: Union[list, None], targets: Union[list[str], None], tqdm=_tqdm): return nl.nearest_neighbour(T, sources, list(missing), targets, tqdm) +def groupby(T, keys, functions, tqdm=_tqdm): + return nl.groupby(T, keys, functions, tqdm) + def filter(table: BaseTable, expressions: list[FilterDict], type: FilterType, tqdm = _tqdm): - return nl.filter(table, expressions, type, tqdm) \ No newline at end of file + return nl.filter(table, expressions, type, tqdm) diff --git a/master/tablite/pivots.py b/master/tablite/pivots.py index 0c99f9fc..65de8a95 100644 --- a/master/tablite/pivots.py +++ b/master/tablite/pivots.py @@ -3,12 +3,26 @@ from tablite.base import BaseTable from tablite.utils import unique_name, sub_cls_check -from tablite.groupbys import groupby +from tablite.nimlite import groupby from tablite.config import Config from tqdm import tqdm as _tqdm +def acc2Name(acc: str) -> str: + arr = ["max", "min", "sum", "product", "first", "last", "count", "median", "mode"] + if acc in arr: + return acc.capitalize() + elif acc == "count_unique": + return "CountUnique" + elif acc == "avg": + return "Average" + elif acc == "stdev": + return "StandardDeviation" + else: + raise ValueError(f"unknown accumulator - {acc}") + + def pivot(T, rows, columns, functions, values_as_rows=True, tqdm=_tqdm, pbar=None): """ param: rows: column names to keep as rows @@ -84,7 +98,7 @@ def pivot(T, rows, columns, functions, values_as_rows=True, tqdm=_tqdm, pbar=Non pbar = tqdm(total=total, desc="pivot") - grpby = groupby(T, keys, functions, tqdm=tqdm, pbar=pbar) + grpby = groupby(T, keys, functions, tqdm=tqdm) Constr = type(T) if len(grpby) == 0: # return empty table. This must be a test? @@ -129,7 +143,7 @@ def pivot(T, rows, columns, functions, values_as_rows=True, tqdm=_tqdm, pbar=Non cols = [[] for _ in range(n)] for row, ix in row_key_index.items(): for col_name, f in functions: - cols[-1].append(f"{f.__name__}({col_name})") + cols[-1].append(f"{acc2Name(f)}({col_name})") for col_ix, v in enumerate(row): cols[col_ix].append(v) @@ -168,7 +182,7 @@ def pivot(T, rows, columns, functions, values_as_rows=True, tqdm=_tqdm, pbar=Non for f, v in zip(functions, func_key): agg_col, func = f terms = ",".join([agg_col] + [f"{col_name}={value}" for col_name, value in zip(columns, col_key)]) - col_name = f"{func.__name__}({terms})" + col_name = f"{acc2Name(func)}({terms})" col_name = unique_name(col_name, result.columns) names.append(col_name) cols.append([None for _ in range(col_length)]) diff --git a/master/tests/test_groupby_and_pivot.py b/master/tests/test_groupby_and_pivot.py index 7390584b..0e5d10a9 100644 --- a/master/tests/test_groupby_and_pivot.py +++ b/master/tests/test_groupby_and_pivot.py @@ -1,8 +1,9 @@ from tablite.core import Table -from tablite.groupbys import GroupBy as gb +from tablite.groupby_utils import GroupBy as gb from random import seed, choice import numpy as np import pytest +import statistics @pytest.fixture(autouse=True) # this resets the HDF5 file for every test. @@ -89,8 +90,10 @@ def test_groupby_missing_args(): t.add_column("B", data=[1, 2, 3, 4, 5, 6] * 2) try: _ = t.groupby(keys=[], functions=[]) # value error. DONE. - assert False, "the line above should raise ValueError" - except ValueError: + assert False + except Exception as e: + assert type(e).__name__ == "ValueError" + assert type(e).__module__ == "nimpy" assert True g0 = t.groupby(keys=[], functions=[("A", gb.sum)]) @@ -380,3 +383,128 @@ def test_reverse_pivot(): # +===+=====+===================+================+================+================+ assert len(t2) == 5 and len(t2.columns) == 5 assert t2["Count(ahe,ahe=e)"][0] is None + +def test_groupby_funcs(): + # ======== MEDIAN ========== + + def createTable(valArr): + return Table({ + 'k': [1 for _ in valArr], + 'v': valArr, + }) + + t = createTable([1, 2, 3, 4, 5]).groupby(keys=['k'], functions=[('v', gb.median)]) + assert t["Median(v)"] == [3] + + t = createTable([1, 2, 3, 6, 7, 8]).groupby(keys=['k'], functions=[('v', gb.median)]) + assert t["Median(v)"] == [4.5] + + t = createTable([3]).groupby(keys=['k'], functions=[('v', gb.median)]) + assert t["Median(v)"] == [3] + + t = createTable([3, 3]).groupby(keys=['k'], functions=[('v', gb.median)]) + assert t["Median(v)"] == [3] + + t = createTable([3, 3, 3]).groupby(keys=['k'], functions=[('v', gb.median)]) + assert t["Median(v)"] == [3] + + t = createTable([3, 3, 6, 6, 9, 9]).groupby(keys=['k'], functions=[('v', gb.median)]) + assert t["Median(v)"] == [6] + + t = createTable([3, 3, 3, 9, 9, 9]).groupby(keys=['k'], functions=[('v', gb.median)]) + assert t["Median(v)"] == [6] + + t = createTable([-1, -1, 0, 1, 1]).groupby(keys=['k'], functions=[('v', gb.median)]) + assert t["Median(v)"] == [0] + + t = createTable([-1, -1, 0, 0, 1, 1]).groupby(keys=['k'], functions=[('v', gb.median)]) + assert t["Median(v)"] == [0] + + t = createTable([5, 4, 6, 3, 7, 2, 8, 1, 9]).groupby(keys=['k'], functions=[('v', gb.median)]) + assert t["Median(v)"] == [5] + + t = createTable([i / 10 for i in range(10)]).groupby(keys=['k'], functions=[('v', gb.median)]) + assert t["Median(v)"] == [0.45] + + t = createTable([i / 10 for i in range(1, 10)]).groupby(keys=['k'], functions=[('v', gb.median)]) + assert t["Median(v)"] == [0.5] + # ========================== + + # ========== MAX =========== + t = createTable([-2, -1, 0, 1, 2, 3]).groupby(keys=['k'], functions=[('v', gb.max)]) + assert t["Max(v)"] == [3] + # ========================== + + # ========== MIN =========== + t = createTable([-2, -1, 0, 1, 2, 3]).groupby(keys=['k'], functions=[('v', gb.min)]) + assert t["Min(v)"] == [-2] + # ========================== + + # ========== SUM =========== + t = createTable([-2, -1, 0, 1, 2, 3]).groupby(keys=['k'], functions=[('v', gb.sum)]) + assert t["Sum(v)"] == [3] + # ========================== + + # ======== PRODUCT ========= + L = [1, 2, 3, 4, 5] + x = 1 + for i in L: + x *= i + t = createTable([1, 2, 3, 4, 5]).groupby(keys=['k'], functions=[('v', gb.product)]) + assert t["Product(v)"] == [x] + # ========================== + + # ========= FIRST ========== + t = createTable([-2, -1, 0, 1, 2, 3]).groupby(keys=['k'], functions=[('v', gb.first)]) + assert t["First(v)"] == [-2] + # ========================== + + # ========== LAST ========== + t = createTable([-2, -1, 0, 1, 2, 3]).groupby(keys=['k'], functions=[('v', gb.last)]) + assert t["Last(v)"] == [3] + # ========================== + + # ========= COUNT ========== + t = createTable([1, 1, 2, 2]).groupby(keys=['k'], functions=[('v', gb.count)]) + assert t["Count(v)"] == [4] + # ========================== + + # ===== COUNT UNIQUE ======= + t = createTable([1, 1, 2, 2]).groupby(keys=['k'], functions=[('v', gb.count_unique)]) + assert t["CountUnique(v)"] == [2] + # ========================== + + # ======== AVERAGE ========= + L = [-2, -1, 0, 1, 2, 3] + t = createTable(L).groupby(keys=['k'], functions=[('v', gb.avg)]) + assert t["Average(v)"] == [sum(L) / len(L)] + + L = [0] + t = createTable([0]).groupby(keys=['k'], functions=[('v', gb.avg)]) + assert t["Average(v)"] == [sum(L) / len(L)] + # ========================== + + # ========= STDEV ========== + L = [1, 1] + t = createTable(L).groupby(keys=['k'], functions=[('v', gb.stdev)]) + assert t["StandardDeviation(v)"] == [0] + + L = [1, 1, 2, 2] + t = createTable(L).groupby(keys=['k'], functions=[('v', gb.stdev)]) + assert t["StandardDeviation(v)"] == [statistics.stdev(L)] + # ========================== + + # ========== MODE ========== + t = createTable([1]).groupby(keys=['k'], functions=[('v', gb.mode)]) + assert t["Mode(v)"] == [1] + + t = createTable([1, 1, 2]).groupby(keys=['k'], functions=[('v', gb.mode)]) + assert t["Mode(v)"] == [1] + + t = createTable([1, 1, 2, 3, 3]).groupby(keys=['k'], functions=[('v', gb.mode)]) + assert t["Mode(v)"] == [3] + + t = createTable([1, 1, 2, 2, 3, 3]).groupby(keys=['k'], functions=[('v', gb.mode)]) + assert t["Mode(v)"] == [3] + # ========================== + diff --git a/master/tests/test_groupby_utils.py b/master/tests/test_groupby_utils.py deleted file mode 100644 index 3e1be71b..00000000 --- a/master/tests/test_groupby_utils.py +++ /dev/null @@ -1,121 +0,0 @@ -from tablite.groupby_utils import GroupBy as gb -import statistics - - -def test_median(): - def median(values): - m = gb.median() - for v in values: - m.update(v) - return m.value - - assert median([1, 2, 3, 4, 5]) == 3 - assert median([1, 2, 3, 6, 7, 8]) == 4.5 - assert median([3]) == 3 - assert median([3, 3]) == 3 - assert median([3, 3, 3]) == 3 - assert median([3, 3, 6, 6, 9, 9]) == 6 - assert median([3, 3, 3, 9, 9, 9]) == 6 - assert median([-1, -1, 0, 1, 1]) == 0 - assert median([-1, -1, 0, 0, 1, 1]) == 0 - assert median([5, 4, 6, 3, 7, 2, 8, 1, 9]) == 5 - assert median([i / 10 for i in range(10)]) == 0.45 - assert median([i / 10 for i in range(1, 10)]) == 0.5 - - -def test_max(): - m = gb.max() - for i in [-2, -1, 0, 1, 2, 3]: - m.update(i) - assert m.value == 3 - - -def test_min(): - m = gb.min() - for i in [-2, -1, 0, 1, 2, 3]: - m.update(i) - assert m.value == -2 - - -def test_sum(): - m = gb.sum() - L = [-2, -1, 0, 1, 2, 3] - for i in L: - m.update(i) - assert sum(L) == m.value - - -def test_product(): - m = gb.product() - L = [1, 2, 3, 4, 5] - x = 1 - for i in L: - m.update(i) - x *= i - assert x == m.value - - -def test_first_last(): - a = gb.first() - b = gb.last() - L = [-2, -1, 0, 1, 2, 3] - for i in L: - a.update(i) - b.update(i) - assert a.value == -2 - assert b.value == 3 - - -def test_count(): - c = gb.count() - cu = gb.count_unique() - for i in [1, 1, 2, 2]: - c.update(i) - cu.update(i) - assert c.value == 4 - assert cu.value == 2 - - -def test_average(): - avg = gb.avg() - L = [-2, -1, 0, 1, 2, 3] - for i in L: - avg.update(i) - assert avg.value == sum(L) / len(L) - - -def test_average2(): - avg = gb.avg() - L = [0] - for i in L: - avg.update(i) - assert avg.value == sum(L) / len(L) - - -def test_stdev(): - m = gb.stdev() - L = [1, 1] - for i in L: - m.update(i) - assert m.value == 0 - - m = gb.stdev() - L = [1, 1, 2, 2] - for i in L: - m.update(i) - assert m.value == statistics.stdev(L) - - -def test_mode(): - def mode(values): - m = gb.mode() - for i in values: - m.update(i) - return m.value - - assert mode([1]) == 1 - assert mode([1, 1, 2]) == 1 - assert mode([1, 1, 2, 3, 3]) == 3 - assert mode([1, 1, 2, 2, 3, 3]) == 3 - - # raise NotImplementedError("the functions above need verification") diff --git a/master/tutorial/index.html b/master/tutorial/index.html index 0dfc0836..38294706 100644 --- a/master/tutorial/index.html +++ b/master/tutorial/index.html @@ -783,8 +783,6 @@ - - @@ -1016,27 +1014,6 @@ -
  • - - - - - Groupbys - - - - -
  • - - - - - - - - - -
  • diff --git a/versions.json b/versions.json index 18dba4c4..a814d2a2 100644 --- a/versions.json +++ b/versions.json @@ -2,14 +2,14 @@ { "version": "master", "title": "master", - "aliases": [] + "aliases": [ + "latest" + ] }, { "version": "2023.10.15", "title": "2023.10.15", - "aliases": [ - "latest" - ] + "aliases": [] }, { "version": "2023.10.14",