Skip to content

Instantly share code, notes, and snippets.

@louisswarren
Last active September 3, 2023 01:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save louisswarren/99df8d2677772fe32c50f55d6bd63e6d to your computer and use it in GitHub Desktop.
Save louisswarren/99df8d2677772fe32c50f55d6bd63e6d to your computer and use it in GitHub Desktop.
Tabulation in python, in place of spreadsheets
class NotApplicable:
def __repr__(self):
return 'NA'
def __str__(self):
return ''
def __add__(self, other): return self
def __sub__(self, other): return self
def __mul__(self, other): return self
def __matmul__(self, other): return self
def __truediv__(self, other): return self
def __floordiv__(self, other): return self
def __mod__(self, other): return self
def __divmod__(self, other): return self
def __pow__(self, other, modulo=None): return self
def __lshift__(self, other): return self
def __rshift__(self, other): return self
def __and__(self, other): return self
def __xor__(self, other): return self
def __or__(self, other): return self
def __radd__(self, other): return self
def __rsub__(self, other): return self
def __rmul__(self, other): return self
def __rmatmul__(self, other): return self
def __rtruediv__(self, other): return self
def __rfloordiv__(self, other): return self
def __rmod__(self, other): return self
def __rdivmod__(self, other): return self
def __rpow__(self, other, modulo=None): return self
def __rlshift__(self, other): return self
def __rrshift__(self, other): return self
def __rand__(self, other): return self
def __rxor__(self, other): return self
def __ror__(self, other): return self
def __neg__(self): return self
def __pos__(self): return self
def __abs__(self): return self
def __invert__(self): return self
def __complex__(self): return self
def __int__(self): return self
def __float__(self): return self
def __round__(self, ndigits=None): return self
def __trunc__(self): return self
def __floor__(self): return self
def __ceil__(self): return self
NA = NotApplicable()
class RowRef:
def __init__(self, colmap, rows, idx, default = NA):
self.colmap = colmap
self.rows = rows
self.idx = idx
self.default = default
def __getitem__(self, colname):
if not 0 <= self.idx < len(self.rows):
return self.default
return self.rows[self.idx][self.colmap[colname]]
def lag(self, n = 1, default = NA):
return RowRef(self.colmap, self.rows, self.idx - n, default)
def lead(self, n = 1, default = NA):
return RowRef(self.colmap, self.rows, self.idx + n, default)
def at(self, n):
return RowRef(self.colmap, self.rows, n, self.default)
def where(self, p):
for i in range(len(self.rows)):
r = RowRef(self.colmap, self.rows, i, self.default)
if p(r):
return r
def first(self):
return self.at(0)
def last(self):
return self.at(len(self.rows) - 1)
class Table:
def __init__(self, data, *colnames, formats = None):
self.rows = [list(r) for r in data]
self.colmap = {colname: i for i, colname in enumerate(colnames)}
self.colfmts = {colname: '{}' for colname in colnames}
if formats is not None:
self.colfmts.update(formats)
def __setitem__(self, colname, value):
fmt = '{}'
if isinstance(value, tuple):
fmt, func = value
else:
func = value
assert(colname not in self.colmap)
self.colmap[colname] = len(self.colmap)
for i, row in enumerate(self.rows):
robj = RowRef(self.colmap, self.rows, i)
row.append(func(robj))
self.colfmts[colname] = fmt
def tabulate(self, override_fmts = None):
bar = ' | '
fmts = {k: v for k, v in self.colfmts.items() if v is not None}
if override_fmts:
for k, v in override_fmts.items():
fmts[k] = v
if v is None:
del fmts[k]
widths = [len(fmtc) for fmtc in fmts]
outputs = []
for row in self.rows:
v = tuple(
fmts[s].format(row[self.colmap[s]])
if row[self.colmap[s]] is not None and
not isinstance(row[self.colmap[s]], NotApplicable)
else ''
for s in fmts)
outputs.append(v)
widths = [max(widths[i], len(v[i])) for i in range(len(widths))]
print(bar.join((fmtc.ljust(widths[i]) for i, fmtc in enumerate(fmts))))
print('-' * (sum(widths) + len(bar) * (len(widths) - 1)))
for out in outputs:
print(bar.join((x.rjust(widths[i]) for i, x in enumerate(out))))
class Dollars(str):
def format(self, x):
x = round(x)
return f'${x:,}'
Dollars = Dollars('Dollars')
class Percent(str):
def format(self, x):
return f'{x * 100:0.0f}%'
Percent = Percent('Percent')
class Rounded(str):
def __init__(self, places = 0):
self.str = '{:0.' + str(places) + 'f}'
def format(self, x):
return self.str.format(x)
class Sign(str):
def format(self, x):
if x > 0:
return '▲'
elif x < 0:
return '▼'
else:
return '●'
Sign = Sign('Sign')
class YearsMonths(str):
def format(self, days):
months = round(days / (365 / 12))
years = int(months / 12)
return f"{years:>2} years {round(months - 12 * years):>2} months"
YearsMonths = YearsMonths('YearsMonths')
@louisswarren
Copy link
Author

Output:

Band | Salary   | Diff    | Diff pct | Competency | Tax     | Take home | Weekly | Weekly diff
----------------------------------------------------------------------------------------------
   1 |  $76,571 |         |          |        85% | $16,188 |   $60,383 | $1,161 |            
   2 |  $79,274 |  $2,703 |       4% |        88% | $17,080 |   $62,194 | $1,196 |         $35
   3 |  $81,976 |  $2,702 |       3% |        91% | $17,972 |   $64,004 | $1,231 |         $35
   4 |  $84,679 |  $2,703 |       3% |        94% | $18,864 |   $65,815 | $1,266 |         $35
   5 |  $87,381 |  $2,702 |       3% |        97% | $19,756 |   $67,625 | $1,300 |         $35
   6 |  $90,084 |  $2,703 |       3% |       100% | $20,648 |   $69,436 | $1,335 |         $35
   7 | $103,597 | $13,513 |      15% |       115% | $25,107 |   $78,490 | $1,509 |        $174

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment