table float format; tabs; markdown input

This commit is contained in:
Stephen Mildenhall
2025-03-17 08:10:04 +00:00
parent bc5906a170
commit c79ab86a74
10 changed files with 8322 additions and 1726 deletions
+2
View File
@@ -7,6 +7,8 @@ __pycache__/
*.so
.idea/*
tests/tables_files/*
tests/quarto_doc/*
tests/*.quarto_ipynb
tests/.*
# Distribution / packaging
.Python
+36
View File
@@ -0,0 +1,36 @@
.. image:: https://img.shields.io/readthedocs/greater_tables_project
:alt: Read the Docs
Release Notes
===============
1.0.0
------
* Allow input via list of lists, or markdown table
* Specify overall float format for whole table
* Specify column alingment with 'llrc' style string
* ``show_index`` option
* Added more tests
* Docs updated
* Set tabs for width; use of width in HTML format.
0.6.0
------
* Initial release
Early development
-------------------
* 0.1.0 - 0.5.0: Early development
* tikz code from great.pres_manager
TODO
=====
* Index aligners
https://shields.io/badges/read-the-docs
+1 -1
View File
@@ -1,4 +1,4 @@
__version__ = '0.6.0'
__version__ = '1.0.0'
__project__ = 'greater_tables'
__author__ = 'Stephen J Mildenhall'
+321 -134
View File
@@ -49,10 +49,12 @@ class GT(object):
aligners=None,
ratio_cols=None,
year_cols=None,
show_index=True,
default_integer_str='{x:,d}',
default_float_str='{x:,.3f}',
default_date_str='%Y-%m-%d',
default_ratio_str='{x:.1%}',
table_float_format=None,
table_hrule_width=1,
table_vrule_width=1,
hrule_widths=None,
@@ -69,6 +71,9 @@ class GT(object):
pef_lower=-3,
pef_upper=6,
cast_to_floats=True,
header_row=True,
tabs=None,
equal=False,
debug=False):
"""
Create a greater_tables formatting object.
@@ -79,15 +84,17 @@ class GT(object):
Recommended usage is to derive from GT and set defaults suitable to your particular application.
In that way you can maintain a "house-style"
:param df: target DataFrame
:param df: target DataFrame or list of lists or markdown table string
:param caption: table caption, optional
:param aligners: None or dict (type or colname) -> left | center | right
:param ratio_cols: None, or "all" or list of column names treated as ratios. Set defaults in derived class suitable to application.
:param year_cols: None, or "all" or list of column names treated as years (no commas, no decimals). Set defaults in derived class suitable to application.
:param show_index: if True, show the index columns, default True
:param default_integer_str: format f-string for integers, default '{x:,d}'
:param default_float_str: format f-string for floats, default '{x:,.3f}'
:param default_date_str: format f-string for dates, default '%Y-%m-%d'. NOTE: no braces or x!
:param default_ratio_str: format f-string for ratios, default '{x:.1%}'
:param table_float_format: None or format string for floats in the table format function, applied to entire table, default None
:param cast_to_floats: if True, try to cast all non-integer, non-date columns to floats
:param table_hrule_width: width of the table top, botton and header hrule, default 1
:param table_vrule_width: width of the table vrule, separating the index from the body, default 1
@@ -104,8 +111,32 @@ class GT(object):
:param pef_precision: precision (digits after period) for pandas engineering format, default 3.
:param pef_lower: apply engineering format to floats with absolute value < 10**pef_lower; default -3.
:param pef_upper: apply engineering format to floats with absolute value > 10**pef_upper; default 6.
:param header_row: True: use first row as headers; False no headings. Default True
:param tabs: None or list of column widths in characters or a common int or float width. (It is converted into em; one character is about 0.5em on average; digits are exactly 0.5em.) If None, will be calculated. Default None.
:param equal: if True, set all column widths equal. Default False.
:param debug: if True, add id to caption and use colored lines in table, default False.
"""
# deal with alternative input modes
if isinstance(df, pd.DataFrame):
# usual use case
pass
elif isinstance(df, pd.Series):
df = df.to_frame()
elif isinstance(df, list):
df = pd.DataFrame(df)
# override this selection come what may
show_index = False
if header_row:
# Set first row as column names
df.columns = df.iloc[0]
# Drop first row and reset index
df = df[1:].reset_index(drop=True)
elif isinstance(df, str):
df, aligners = GT.md_to_df(df)
show_index = False
else:
raise ValueError('df must be a DataFrame, a list of lists, or a markdown table string')
if not df.columns.is_unique:
raise ValueError('df column names are not unique')
self.df = df.copy(deep=True) # the object being formatted
@@ -118,14 +149,16 @@ class GT(object):
self.caption = caption + (' (id: ' + self.df_id + ')' if self.debug else '')
# before messing
self.nindex = self.df.index.nlevels
self.show_index = show_index
self.nindex = self.df.index.nlevels if self.show_index else 0
self.ncolumns = self.df.columns.nlevels
self.ncols = self.df.shape[1]
self.dt = self.df.dtypes
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=pd.errors.PerformanceWarning)
self.df = self.df.reset_index(drop=False, col_level=self.df.columns.nlevels - 1)
if self.show_index:
warnings.simplefilter("ignore", category=pd.errors.PerformanceWarning)
self.df = self.df.reset_index(drop=False, col_level=self.df.columns.nlevels - 1)
# want the new index to be ints - that is not default if old was multiindex
self.df.index = np.arange(self.df.shape[0], dtype=int)
self.index_change_level = GT.changed_column(self.df.iloc[:, :self.nindex])
@@ -203,16 +236,20 @@ class GT(object):
if aligners is None:
# not using
aligners = []
elif isinstance(aligners, str):
# lrc for each column
aligners = {c: a for c, a in zip(self.df.columns, aligners)}
self.df_aligners = []
lrc = {'l': 'grt-left', 'r': 'grt-right', 'c': 'grt-center'}
# FIX INDEX ALIGNERS HERE
# TODO: index aligners
for i, c in enumerate(self.df.columns):
if i < self.nindex:
# test aligners BEFORE index!
if c in aligners:
self.df_aligners.append(lrc.get(aligners[c], 'grt-center'))
elif i < self.nindex:
# index -> left
self.df_aligners.append('grt-left')
elif c in aligners:
self.df_aligners.append(lrc.get(aligners[c], 'grt-center'))
elif c in self.ratio_cols or i in self.float_col_indices or i in self.integer_col_indices:
# number -> right
self.df_aligners.append('grt-right')
@@ -236,6 +273,8 @@ class GT(object):
self.pef_lower = pef_lower
self.pef_upper = pef_upper
self._pef = None
self.table_float_format = table_float_format
self.default_float_formatter = None
self.hrule_widths = hrule_widths or (0, 0, 0)
self.vrule_widths = vrule_widths or (0, 0, 0)
self.table_hrule_width = table_hrule_width
@@ -245,6 +284,15 @@ class GT(object):
self.font_caption = font_caption
self.font_bold_index = font_bold_index
self.sparsify_columns = sparsify_columns
if tabs is None:
self.tabs = None
elif isinstance(tabs, (int, float)):
self.tabs = (tabs,)
elif isinstance(tabs, (np.ndarray, list, tuple)):
self.tabs = tabs # Already iterable, self.tabs = as is
else:
self.tabs = [tabs] # Fallback for anything else
self.equal = equal
if padding_trbl is None:
if spacing == 'tight':
@@ -313,12 +361,17 @@ class GT(object):
def default_formatter(self, x):
"""Universal formatter for other types."""
try:
i = int(x)
f = float(x)
if self.default_float_formatter:
return self.default_float_formatter(f)
try:
i = int(x)
except ValueError:
i = int(f)
if i == f:
return self.default_integer_str.format(x=i)
else:
# TODo BEEF UP?
# TODO BEEF UP?
return self.default_float_str.format(x=f)
except (TypeError, ValueError):
return str(x)
@@ -373,10 +426,10 @@ class GT(object):
return fmt.format(x=x)
# well and good but results in ugly differences
# by entries in a column
if x == int(x) and np.abs(x) < pu:
return f'{x:,.0f}.'
else:
return fmt.format(x=x)
# if x == int(x) and np.abs(x) < pu:
# return f'{x:,.0f}.'
# else:
# return fmt.format(x=x)
except (ValueError, TypeError):
return str(x)
return ff
@@ -391,6 +444,32 @@ class GT(object):
each column.
"""
# because of non-unique indexes, index by position not name
if self.table_float_format is not None:
if callable(self.table_float_format):
# wrap in error protections
def ff(x):
try:
return self.table_float_format(x=x)
except ValueError:
return str(x)
except Exception as e:
logger.error(f'Custom float function raised {e=}')
self.default_float_formatter = ff
else:
if type(self.table_float_format) != str:
raise ValueError('table_float_format must be a string or a function')
fmt = self.table_float_format
def ff(x):
try:
return fmt.format(x=x)
except ValueError:
return str(x)
except Exception as e:
logger.error(f'Custom float format string raised {e=}')
self.default_float_formatter = ff
else:
self.default_float_formatter = False
if self._df_formatters is None:
self._df_formatters = []
for i, c in enumerate(self.df.columns):
@@ -408,7 +487,7 @@ class GT(object):
self._df_formatters.append(self.default_integer_formatter)
elif i in self.float_col_indices:
# trickier approach...
self._df_formatters.append(self.make_float_formatter(self.df.iloc[:, i]))
self._df_formatters.append(self.default_float_formatter or self.make_float_formatter(self.df.iloc[:, i]))
else:
# print(f'{i} default')
self._df_formatters.append(self.default_formatter)
@@ -429,7 +508,8 @@ class GT(object):
"""
return self.html
def make_style(self):
def make_style(self, tabs):
"""Write out custom CSS for the table."""
if self.debug:
head_tb = '#0ff'
body_b = '#f0f'
@@ -457,13 +537,16 @@ class GT(object):
# for local use
padt, padr, padb, padl = self.padt, self.padr, self.padb, self.padl
style = f'''
style = [f'''
<style>
#{self.df_id} {{
border-collapse: collapse;
font-family: "Roboto", "Open Sans Condensed", "Arial", 'Segoe UI', sans-serif;
font-size: {self.font_body}em;
width: auto;
/* tb and lr
width: fit-content; */
margin: 10px auto;
border: none;
overflow: auto;
margin-left: auto;
@@ -543,12 +626,15 @@ class GT(object):
#{self.df_id} .grt-bold {{
font-weight: bold;
}}
</style>
'''
return style
''']
for i, w in enumerate(tabs):
style.append(f' #{self.df_id} .grt-c-{i} {{ width: {w}em; }}')
style.append('</style>')
logger.info('CREATED CSS')
return '\n'.join(style)
def make_html(self):
"""Convert a pandas DataFrame to an HTML table with sparsification."""
"""Convert a pandas DataFrame to an HTML table."""
index_name_to_level = dict(zip(self.raw_df.index.names, range(self.nindex)))
index_change_level = self.index_change_level.map(index_name_to_level)
# this is easier and computed in the init
@@ -564,6 +650,36 @@ class GT(object):
idx_header = bit.iloc[:self.nindex, :self.ncolumns]
columns = bit.iloc[self.nindex:, :self.ncolumns]
colw, tabs = GT.estimate_column_widths(self.df, nc_index=self.nindex, scale=1, equal=self.equal)
if self.debug:
print(f'Input {self.tabs=}\nComputed {tabs=}')
if self.tabs is not None:
if len(tabs) == len(self.tabs):
tabs = self.tabs
elif len(self.tabs) == 1:
tabs = self.tabs * len(tabs)
else:
logger.error(f'{self.tabs=} must be None, a single number, or a list of numbers of the correct length. Ignoring.')
# print('HTML ' + ', '.join([f'{c:,.2f}' for c in tabs]))
# set column widths; tabs returns lengths of strings in each column
# for proportional fonts, average char is 0.4 to 0.5 em but numbers with
# tabular-nums are fixed 0.5, so use that
# scale: want tables about 150-200 char wide, 1 char = 0.5 px size of font
# so what 75-100 em wide in total
# add the padding
# TODO FONT SIZE
# /4 works well for the tests (handles dates) but seems a bit illogical...
tabs = np.array(tabs) + (self.padl + self.padr) / 12 # guessing font size...
# em_per_char = 0.5; true exactly for tabular-nums
em_per_char = 0.6
tabs = tabs * em_per_char
# this gets stripped out by quarto, so make part of style
html.append('<colgroup>')
for w in tabs:
html.append(f'<col style="width: {w * em_per_char}em;">')
html.append('</colgroup>')
# TODO Add header aligners
# this is TRANSPOSED!!
if self.sparsify_columns:
@@ -571,10 +687,10 @@ class GT(object):
for i in range(self.ncolumns):
# one per row of columns m index, usually only 1
html.append("<tr>")
for j, r in enumerate(idx_header.iloc[:, i]):
# columns one per level of index
html.append(f'<th class="grt-left">{r}</th>')
cum_col = 0 # keep track of where we are up to
if self.show_index:
for j, r in enumerate(idx_header.iloc[:, i]):
# columns one per level of index
html.append(f'<th class="grt-left">{r}</th>')
# if not for col span issue you could just to this:
# for j in range(self.ncols):
# hrule = f'grt-bhrule-{i}' if i < self.ncolumns - 1 else ''
@@ -589,6 +705,7 @@ class GT(object):
# here, the groupby needs to consider all levels at and above i
# this concats all the levels
# need :i+1 to get down to the ith level
cum_col = 0 # keep track of where we are up to
for j, (nm, g) in enumerate(groupby(columns.iloc[:, :i+1].
apply(lambda x: ':::'.join(str(i) for i in x), axis=1))):
# ::: needs to be something that does not appear in the col names
@@ -599,10 +716,16 @@ class GT(object):
colspan = sum(1 for _ in g)
if 0 < j:
vrule = f'grt-vrule-{column_change_level[cum_col]}'
elif j == 0:
# start with the first column come what may
elif j == 0 and self.show_index:
# start with the first column if showing index
vrule = f'grt-vrule-index'
html.append(f'<th colspan="{colspan}" class="grt-center {hrule} {vrule}">{nm}</th>')
else:
vrule = ''
if j == 0 and not self.show_index:
# first column, no index, left align label
html.append(f'<th colspan="{colspan}" class="grt-left {hrule} {vrule}">{nm}</th>')
else:
html.append(f'<th colspan="{colspan}" class="grt-center {hrule} {vrule}">{nm}</th>')
cum_col += colspan
html.append("</tr>")
html.append("</thead>")
@@ -611,9 +734,10 @@ class GT(object):
for i in range(self.ncolumns):
# one per row of columns m index, usually only 1
html.append("<tr>")
for j, r in enumerate(idx_header.iloc[:, i]):
# columns one per level of index
html.append(f'<th class="grt-left">{r}</th>')
if self.show_index:
for j, r in enumerate(idx_header.iloc[:, i]):
# columns one per level of index
html.append(f'<th class="grt-left">{r}</th>')
for j, r in enumerate(columns.iloc[:, i]):
# one per column of dataframe
# figure how high up mindex the vrules go
@@ -621,7 +745,7 @@ class GT(object):
hrule = f'grt-bhrule-{i}' if i < self.ncolumns - 1 else ''
if 0 < j < self.ncols and i >= column_change_level[j]:
vrule = f'grt-vrule-{column_change_level[j]}'
elif j == 0:
elif j == 0 and self.show_index:
# start with the first column come what may
vrule = f'grt-vrule-index'
else:
@@ -636,29 +760,36 @@ class GT(object):
# one per row of dataframe
html.append("<tr>")
hrule = ''
for j, c in enumerate(r.iloc[:self.nindex]):
# dx = data in index
# if this is the level that changes for this row
# will use a top rule hence omit i = 0 which already has an hrule
if i > 0 and hrule == '' and j == index_change_level[i]:
hrule = f'grt-hrule-{j}'
# html.append(f'<td class="grt-dx-r-{i} grt-dx-c-{j} {self.df_aligners[j]} {hrule}">{c}</td>')
html.append(f'<td class="{bold_idx} {self.df_aligners[j]} {hrule}">{c}</td>')
if self.show_index:
for j, c in enumerate(r.iloc[:self.nindex]):
# dx = data in index
# if this is the level that changes for this row
# will use a top rule hence omit i = 0 which already has an hrule
if i > 0 and hrule == '' and j == index_change_level[i]:
hrule = f'grt-hrule-{j}'
# html.append(f'<td class="grt-dx-r-{i} grt-dx-c-{j} {self.df_aligners[j]} {hrule}">{c}</td>')
col_id = f'grt-c-{j}'
html.append(f'<td class="{col_id} {bold_idx} {self.df_aligners[j]} {hrule}">{c}</td>')
for j, c in enumerate(r.iloc[self.nindex:]):
# first col left handled by index/body divider
if 0 < j < self.ncols:
vrule = f'grt-vrule-{column_change_level[j]}'
elif j == 0:
elif j == 0 and self.show_index:
# start with the first column come what may
vrule = f'grt-vrule-index'
else:
vrule = ''
# html.append(f'<td class="grt-data-r-{i} grt-data-c-{j} {self.df_aligners[j+self.nindex]} {hrule} {vrule}">{c}</td>')
html.append(f'<td class="{self.df_aligners[j+self.nindex]} {hrule} {vrule}">{c}</td>')
col_id = f'grt-c-{j+self.nindex}'
html.append(f'<td class="{col_id} {self.df_aligners[j+self.nindex]} {hrule} {vrule}">{c}</td>')
html.append("</tr>")
html.append("</tbody>")
text = '\n'.join(html)
text = GT.clean_html_tex(text)
self.df_html = GT.clean_html_tex(text)
logger.info('CREATED HTML')
return text
self.df_style = self.make_style(tabs)
return self.df_html
def clean_style(self, soup):
"""Minify CSS inside <style> blocks and remove /* ... */ comments."""
@@ -668,20 +799,23 @@ class GT(object):
# Remove CSS comments
cleaned_css = re.sub(r'/\*.*?\*/', '', style_tag.string, flags=re.DOTALL)
# Minify whitespace
cleaned_css = re.sub(r'\s+', ' ', cleaned_css).strip()
# cleaned_css = re.sub(r'\s+', ' ', cleaned_css).strip()
style_tag.string.replace_with(cleaned_css)
return soup
@property
def html(self):
if self._clean_html == '':
if self.df_html == '':
# makes style and html (need tabs)
self.df_html = self.make_html()
code = ["<div class='greater-table'>",
self.make_style() if self.df_style == '' else self.df_style,
self.make_html() if self.df_html == '' else self.df_html,
self.df_style,
self.df_html,
"</div>"]
soup = BeautifulSoup('\n'.join(code), 'html.parser')
soup = self.clean_style(soup)
self._clean_html = str(soup) # .prettify()
self._clean_html = str(soup) # .prettify() -> too many newlines
return self._clean_html
def _repr_latex_(self):
@@ -743,7 +877,9 @@ class GT(object):
# create the df and the index separately
data_formatters = self.df_formatters[self.nindex:]
new_body = GT.apply_formatters_work(df, data_formatters)
# now create the index
if not self.show_index:
return new_body
# else have to handle the index
index_formatters = self.df_formatters[:self.nindex]
df_index = df.reset_index(drop=False, col_level=self.df.columns.nlevels - 1).iloc[:, :self.nindex]
new_index = GT.apply_formatters_work(df_index, index_formatters)
@@ -756,15 +892,12 @@ class GT(object):
raise ValueError(f'unknown mode {mode}')
def make_tikz(self,
float_format=None,
tabs=None,
scale=0.635,
column_sep=3 / 8,
row_sep=1 / 8,
figure='figure',
extra_defs='',
hrule=None,
equal=False,
vrule=None,
post_process='',
label='',
@@ -821,7 +954,6 @@ class GT(object):
:param df:
:param fn_out:
:param float_format:
:param tabs:
:param show_index:
:param scale:
:param column_sep:
@@ -870,24 +1002,27 @@ class GT(object):
# always a good idea to do this...need to deal with underscores, %
# and it handles index types that are not strings
df = GT.clean_index(df)
if not np.all([i=='object' for i in df.dtypes]):
print('cols of df not all objects: ', df.dtypes, sep='\n')
# make sure percents are escaped, but not if already escaped
df = df.replace(r"(?<!\\)%", r"\%", regex=True)
# we are always showing the index...may regret that???
# put condition here if needed
nc_index = df.index.nlevels
# col_level puts the label at the bottom of the column m index.
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=pd.errors.PerformanceWarning)
df = df.reset_index(drop=False, col_level=df.columns.nlevels - 1)
if sparsify:
if hrule is None:
hrule = set()
for i in range(sparsify):
df.iloc[:, i], rules = GT.sparsify(df.iloc[:, i])
# don't want lines everywhere
if len(rules) < len(df) - 1:
hrule = set(hrule).union(rules)
# pres_maker.df_to_tikz code line 1931
if self.show_index:
nc_index = df.index.nlevels
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=pd.errors.PerformanceWarning)
df = df.reset_index(drop=False, col_level=df.columns.nlevels - 1)
if sparsify:
if hrule is None:
hrule = set()
for i in range(sparsify):
df.iloc[:, i], rules = GT.sparsify(df.iloc[:, i])
# don't want lines everywhere
if len(rules) < len(df) - 1:
hrule = set(hrule).union(rules)
else:
nc_index = 0
if vrule is None:
vrule = set()
@@ -902,12 +1037,22 @@ class GT(object):
# internal TeX code (same as HTML code)
matrix_name = self.df_id
# note this happens AFTER you have reset the index...need to pass number of index columns
# note this happens AFTER you have reset the index...need to pass
# number of index columns
# have also converted everything to formatted strings
# colw, mxmn, tabs = GT.guess_column_widths(df, nc_index=nc_index, float_format=wfloat_format, tabs=tabs,
colw, mxmn, tabs = GT.guess_column_widths(df, nc_index=nc_index, float_format=lambda x: x, tabs=tabs,
scale=scale, equal=equal)
# print(colw, tabs)
# estimate... originally called guess_column_widths, with more parameters
colw, tabs = GT.estimate_column_widths(df, nc_index=nc_index, scale=scale, equal=self.equal)
if self.debug:
print(f'Input {self.tabs=}\nComputed {tabs=}')
if self.tabs is not None:
if len(tabs) == len(self.tabs):
tabs = self.tabs
elif len(self.tabs) == 1:
tabs = self.tabs * len(tabs)
else:
logger.error(f'{self.tabs=} must be None, a single number, or a list of numbers of the correct length. Ignoring.')
# print('TIKZ ' + ', '.join([f'{c:,.2f}' for c in tabs]))
# print(f'TIKZ {colw=}, {tabs=}')
logger.info(f'tabs: {tabs}')
logger.info(f'colw: {colw}')
@@ -1059,7 +1204,7 @@ class GT(object):
f'([yshift={-yshift}em]{matrix_name}-{ln}-{nc+1}.south east);\n')
written = set(range(1, nc_index + 1))
if vrule:
if vrule and self.show_index:
# to left of col, 1 based, includes index
# write these first
# TODO fix madness vrule is to the left, mi_vrules are to the right...
@@ -1112,25 +1257,28 @@ class GT(object):
return self.tex
@staticmethod
def guess_column_widths(df, nc_index, float_format, tabs=None, scale=1, equal=False):
def estimate_column_widths(df, nc_index, scale=1, equal=False):
"""
estimate sensible column widths for the dataframe [in what units?]
Estimate sensible column widths for the dataframe [in what units?]
Internal variables:
mxmn affects alignment: are all columns the same width?
:param df:
:param nc_index: number of columns in the index...these are not counted as "data columns"
:param float_format:
:param tabs:
:param equal: if True, try to make all data columns the same width (hint can be rejected)
:return:
colw affects how the table is printed in the md file (actual width of data elements)
mxmn affects alignment: are all columns the same width?
colw affects how the tex is printed to ensure it "looks neat" (actual width of data elements)
tabs affects the actual output
equal if True, all try to make all data columns the same width (can be rejected)
"""
# this
# tabs from _tabs, an estimate column widths, determines the size of the table columns as displayed
# print(f'{nc_index=}, {scale=}, {equal=}')
colw = dict.fromkeys(df.columns, 0)
headw = dict.fromkeys(df.columns, 0)
_tabs = []
tabs = []
mxmn = {}
nl = nc_index
for i, c in enumerate(df.columns):
@@ -1144,12 +1292,19 @@ class GT(object):
cw = max(map(len, c.split(' ')))
# logger.info(f'leng col = {len(c)}, longest word = {cw}')
else:
# could be float etc.
# column name could be float etc. or if multi index a tuple
try:
cw = max(map(lambda x: len(float_format(x)), c))
if isinstance(c, tuple):
# multiindex: join and split into words and take length of each word
words = ' '.join(c).split(' ')
cw = max(map(lambda x: len(str(x)), words))
else:
cw = max(map(lambda x: len(str(x)), c))
# print(f'{c}: {cw=} no error')
except TypeError:
# not a MI, float or something
cw = len(str(c))
# print(f'{c}: {cw=} WITH error')
headw[c] = cw
# now figure the width of the elements in the column
# mxmn is used to determine whether to center the column (if all the same size)
@@ -1157,61 +1312,58 @@ class GT(object):
# wierdness here were some objects actually contain floats, str evaluates to NaN
# and picks up width zero
try:
# _ = list(map(lambda x: len(float_format(x)), df.iloc[:, i]))
_ = df.iloc[:, i].map(lambda x: len(float_format(x)))
colw[c] = _.max()
mxmn[c] = (_.max(), _.min())
except:
e = sys.exc_info()[0]
lens = df.iloc[:, i].map(lambda x: len(str(x)))
colw[c] = lens.max()
mxmn[c] = (lens.max(), lens.min())
except Exception as e:
logger.error(f'{c} error {e} DO SOMETHING ABOUT THIS...if it never occurs dont need the if')
colw[c] = df[c].str.len().max()
mxmn[c] = (df[c].str.len().max(), df[c].str.len().min())
else:
# _ = list(map(lambda x: len(float_format(x)), df[c]))
_ = df.iloc[:, i].map(lambda x: len(float_format(x)))
colw[c] = _.max()
mxmn[c] = (_.max(), _.min())
# debugging grief
# if c == 'p':
# print(c, df[c], colw[c], mxmn[c], list(map(len, list(map(float_format, df[c])))))
if tabs is None:
# now know all column widths...decide what to do
# are all the columns about the same width?
data_cols = np.array([colw[k] for k in df.columns[nl:]])
same_size = (data_cols.std() <= 0.1 * data_cols.mean())
common_size = 0
if same_size:
common_size = int(data_cols.mean() + data_cols.std())
logger.info(f'data cols appear same size = {common_size}')
for i, c in enumerate(df.columns):
if i < nl or not same_size:
# index columns
_tabs.append(int(max(colw[c], headw[c])))
else:
# data all seems about the same width
_tabs.append(common_size)
logger.info(f'Determined tab spacing: {_tabs}')
if equal:
# see if equal widths makes sense
dt = _tabs[nl:]
if max(dt) / sum(dt) < 4 / 3:
_tabs = _tabs[:nl] + [max(dt)] * (len(_tabs) - nl)
logger.info(f'Taking equal width hint: {_tabs}')
else:
logger.info(f'Rejecting equal width hint')
# look to rescale, shoot for width of 150 on 100 scale basis
data_width = sum(_tabs[nl:])
index_width = sum(_tabs[:nl])
target_width = 150 * scale - index_width
if data_width / target_width < 0.9:
# don't rescale above 1:1 - don't want too large
rescale = min(1 / scale, target_width / data_width)
_tabs = [w if i < nl else w * rescale for i, w in enumerate(_tabs)]
logger.info(f'Rescale {rescale} applied; tabs = {_tabs}')
tabs = _tabs
return colw, mxmn, tabs
lens = df.iloc[:, i].map(lambda x: len(str(x)))
colw[c] = lens.max()
mxmn[c] = (lens.max(), lens.min())
# print(f'{headw[c]=}, {colw[c]=}, {mxmn[c]=}, {c=}')
# now know all column widths...decide what to do
# are all the data columns about the same width?
data_cols = np.array([colw[k] for k in df.columns[nl:]])
same_size = (data_cols.std() <= 0.1 * data_cols.mean())
# print(f'same size test requires {data_cols.std()} <= {0.1 * data_cols.mean()}')
common_size = 0
if same_size:
common_size = int(data_cols.mean() + data_cols.std())
logger.info(f'data cols appear same size = {common_size}')
# print(f'data cols appear same size = {common_size}')
for i, c in enumerate(df.columns):
if i < nl or not same_size:
# index columns
tabs.append(int(max(colw[c], headw[c])))
else:
# data all seems about the same width
tabs.append(common_size)
logger.info(f'Determined tab spacing: {tabs}')
if equal:
# see if equal widths makes sense
dt = tabs[nl:]
if max(dt) / sum(dt) < 4 / 3:
tabs = tabs[:nl] + [max(dt)] * (len(tabs) - nl)
logger.info(f'Taking equal width hint: {tabs}')
# print(f'Taking equal width hint: {tabs}')
else:
logger.info(f'Rejecting equal width hint')
# print(f'Rejecting equal width hint')
# look to rescale, shoot for width of 150 on 100 scale basis
data_width = sum(tabs[nl:])
index_width = sum(tabs[:nl])
target_width = 150 * scale - index_width
if data_width / target_width < 0.9:
# don't rescale above 1:1 - don't want too large
rescale = min(1 / scale, target_width / data_width)
tabs = [w if i < nl else w * rescale for i, w in enumerate(tabs)]
logger.info(f'Rescale {rescale} applied; tabs = {tabs}')
# print(f'Rescale {rescale} applied; tabs = {tabs}')
# print(f'{colw.values()=}\n{tabs=}')
return colw, tabs
@staticmethod
def sparsify(col):
@@ -1356,6 +1508,35 @@ class GT(object):
p.write_text(soup.prettify(), encodign='utf-8')
logger.info(f'Saved to {p}')
@staticmethod
def md_to_df(txt):
"""Convert markdown text string table to DataFrame."""
# remove starting and ending | in each line (optional anyway)
txt = re.sub(r'^\||\|$', '', txt, flags=re.MULTILINE)
txt = txt.strip().replace('*', '').split('\n')
# remove the alignment row
alignment_row = txt.pop(1)
aligners = []
for t in alignment_row.split('|'):
if t[0] == ':' and t[-1] == ':':
aligners.append('c')
elif t[0] == ':':
aligners.append('l')
elif t[-1] == ':':
aligners.append('r')
else:
# no alignment info
pass
if len(aligners) == 0:
aligners = None
else:
aligners = ''.join(aligners)
txt = [[j.strip() for j in i.split('|')] for i in txt]
df = pd.DataFrame(txt).T
df = df.set_index(0)
df = df.T
return df, aligners
class sGT(GT):
"""
@@ -1365,7 +1546,13 @@ class sGT(GT):
in this way.
"""
def __init__(self, df, caption="", guess_years=True, ratio_regex='lr|roe|coc', **kwargs):
"""Create Steve House-Style Formatter."""
"""Create Steve House-Style Formatter. Does not handle list of lists input."""
if isinstance(df, str):
df, aligners_ = GT.md_to_df(df)
if 'aligners' not in kwargs:
kwargs['aligners'] = aligners_
kwargs['show_index'] = False
nindex = df.index.nlevels
ncolumns = df.columns.nlevels
if 'ratio_cols' in kwargs:
+356
View File
@@ -0,0 +1,356 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<meta name="author" content="Stephen J. Mildenhall">
<meta name="dcterms.date" content="2025-03-14">
<title>SINGLE Table</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
div.columns{display: flex; gap: min(4vw, 1.5em);}
div.column{flex: auto; overflow-x: auto;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
ul.task-list li input[type="checkbox"] {
width: 0.8em;
margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */
vertical-align: middle;
}
/* CSS for syntax highlighting */
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { display: inline-block; text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
}
pre.numberSource { margin-left: 3em; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg==" crossorigin="anonymous"></script><script src="tables_files/libs/clipboard/clipboard.min.js"></script>
<script src="tables_files/libs/quarto-html/quarto.js"></script>
<script src="tables_files/libs/quarto-html/popper.min.js"></script>
<script src="tables_files/libs/quarto-html/tippy.umd.min.js"></script>
<script src="tables_files/libs/quarto-html/anchor.min.js"></script>
<link href="tables_files/libs/quarto-html/tippy.css" rel="stylesheet">
<link href="tables_files/libs/quarto-html/quarto-syntax-highlighting-01c78b5cd655e4cd89133cf59d535862.css" rel="stylesheet" id="quarto-text-highlighting-styles">
<script src="tables_files/libs/bootstrap/bootstrap.min.js"></script>
<link href="tables_files/libs/bootstrap/bootstrap-icons.css" rel="stylesheet">
<link href="tables_files/libs/bootstrap/bootstrap-fcfb2e27d9f44eaf269ffcda1f840c64.min.css" rel="stylesheet" append-hash="true" id="quarto-bootstrap" data-mode="light">
<style>html{ scroll-behavior: smooth; }</style>
<link rel="icon" href="img/favicon.ico" type="image/x-icon">
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js" integrity="sha512-c3Nl8+7g4LMSTdrm621y7kf9v3SDPnhxLNhcjFJbKECVnmZHTdo+IRO05sNLTH/D3vA6u1X32ehoLC7WFVdheg==" crossorigin="anonymous"></script>
<script type="application/javascript">define('jquery', [],function() {return window.jQuery;})</script>
<script src="https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=es6"></script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml-full.js" type="text/javascript"></script>
<script type="text/javascript">
const typesetMath = (el) => {
if (window.MathJax) {
// MathJax Typeset
window.MathJax.typeset([el]);
} else if (window.katex) {
// KaTeX Render
var mathElements = el.getElementsByClassName("math");
var macros = [];
for (var i = 0; i < mathElements.length; i++) {
var texText = mathElements[i].firstChild;
if (mathElements[i].tagName == "SPAN") {
window.katex.render(texText.data, mathElements[i], {
displayMode: mathElements[i].classList.contains('display'),
throwOnError: false,
macros: macros,
fleqn: false
});
}
}
}
}
window.Quarto = {
typesetMath
};
</script>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h1>Table 1</h1>
<div class="greater-table">
<style>
#T25N3COST23ZV {
border-collapse: collapse;
font-family: "Roboto", "Open Sans Condensed", "Arial", 'Segoe UI', sans-serif;
font-size: 0.8em;
width: 50em;
margin: 10px auto;
border: none;
overflow: auto;
}
#T25N3COST23ZV caption { padding: 8px 10px 4px 10px; font-size: 0.88em; text-align: center; font-weight: normal; caption-side: top; }
#T25N3COST23ZV thead { border-top: 1px solid #000; border-bottom: 1px solid #000; font-size: 0.88em; }
#T25N3COST23ZV tbody { border-bottom: 1px solid #000; }
#T25N3COST23ZV th { vertical-align: bottom; padding: 8px 10px 8px 10px; }
#T25N3COST23ZV td { padding: 4px 10px 4px 10px; vertical-align: top; }
#T25N3COST23ZV .grt-hrule-0 { border-top: 0px solid #000; }
#T25N3COST23ZV .grt-hrule-1 { border-top: 0px solid #000; }
#T25N3COST23ZV .grt-hrule-2 { border-top: 0px solid #000; }
#T25N3COST23ZV .grt-bhrule-0 { border-bottom: 0px solid #000; }
#T25N3COST23ZV .grt-bhrule-1 { border-bottom: 0px solid #000; }
#T25N3COST23ZV .grt-vrule-index { border-left: 1px solid #000; }
#T25N3COST23ZV .grt-vrule-0 { border-left: 0px solid #000; }
#T25N3COST23ZV .grt-vrule-1 { border-left: 0px solid #000; }
#T25N3COST23ZV .grt-vrule-2 { border-left: 0px solid #000; }
#T25N3COST23ZV .grt-left { text-align: left; }
#T25N3COST23ZV .grt-center { text-align: center; }
#T25N3COST23ZV .grt-right { text-align: right; font-variant-numeric: tabular-nums; }
#T25N3COST23ZV .grt-col-1 {width: 3em;}
#T25N3COST23ZV .grt-col-2 {width: 3em;}
#T25N3COST23ZV .grt-col-3 {width: 15em;}
#T25N3COST23ZV .grt-col-4 {width: 4em;}
#T25N3COST23ZV .grt-col-5 {width: 4em;}
#T25N3COST23ZV .grt-col-6 {width: 4em;}
#T25N3COST23ZV .grt-head { font-family: "Times New Roman", 'Courier New'; font-size: 0.88em; }
#T25N3COST23ZV .grt-bold { font-weight: bold; }</style>
<table id="T25N3COST23ZV">
<thead>
<tr>
<th class="grt-left">index</th>
<th class="grt-center grt-vrule-index" colspan="1">level_0</th>
<th class="grt-center grt-vrule-0" colspan="1">level_1</th>
<th class="grt-center grt-vrule-0" colspan="1">2025</th>
<th class="grt-center grt-vrule-0" colspan="1">2026</th>
<th class="grt-center grt-vrule-0" colspan="1">2027</th>
</tr>
</thead>
<tbody>
<tr>
<td class="grt-col-1 grt-left">0</td>
<td class="grt-col-2 grt-left grt-vrule-index">GAAP</td>
<td class="grt-col-3 grt-left grt-vrule-0">Underwriting Result</td>
<td class="grt-col-4 grt-right grt-vrule-0"></td>
<td class="grt-col-5 grt-right grt-vrule-0">-394.81</td>
<td class="grt-col-6 grt-right grt-vrule-0"></td>
</tr>
<tr>
<td class="grt-left">1</td>
<td class="grt-left grt-vrule-index">GAAP</td>
<td class="grt-left grt-vrule-0">Net Investment Income</td>
<td class="grt-right grt-vrule-0"></td>
<td class="grt-right grt-vrule-0">60.52</td>
<td class="grt-right grt-vrule-0">66.57</td>
</tr>
<tr>
<td class="grt-left">2</td>
<td class="grt-left grt-vrule-index">GAAP</td>
<td class="grt-left grt-vrule-0">Operating Result</td>
<td class="grt-right grt-vrule-0"></td>
<td class="grt-right grt-vrule-0">-334.29</td>
<td class="grt-right grt-vrule-0">66.57</td>
</tr>
<tr>
<td class="grt-left">3</td>
<td class="grt-left grt-vrule-index">GAAP</td>
<td class="grt-left grt-vrule-0">Dividends</td>
<td class="grt-right grt-vrule-0"></td>
<td class="grt-right grt-vrule-0"></td>
<td class="grt-right grt-vrule-0"></td>
</tr>
</tbody>
</table></div>
<h1>Table 2</h1>
<p>Some text above the table.</p>
<div class="greater-table">
<style>
#TEJECQF5AYPNM {
border-collapse: collapse; font-family: "Roboto", "Open Sans Condensed", "Arial", 'Segoe UI', sans-serif;
font-size: 0.8em;
width: fit-content;
/* tb and lr */
margin: 10px auto;
}
#TEJECQF5AYPNM caption { padding: 2px 10px 1px 10px; font-size: 0.88em; text-align: center; font-weight: normal; caption-side: top; }
#TEJECQF5AYPNM thead { border-top: 1px solid #000; border-bottom: 1px solid #000; font-size: 0.88em; }
#TEJECQF5AYPNM tbody { border-bottom: 1px solid #000; }
#TEJECQF5AYPNM th { vertical-align: bottom; padding: 2px 10px 2px 10px; }
#TEJECQF5AYPNM td { padding: 1px 10px 1px 10px; vertical-align: top; }
#TEJECQF5AYPNM .grt-hrule-0 { border-top: 0px solid #000; }
#TEJECQF5AYPNM .grt-hrule-1 { border-top: 0px solid #000; }
#TEJECQF5AYPNM .grt-hrule-2 { border-top: 0px solid #000; }
#TEJECQF5AYPNM .grt-bhrule-0 { border-bottom: 1.5px solid #000; }
#TEJECQF5AYPNM .grt-bhrule-1 { border-bottom: 1px solid #000; }
#TEJECQF5AYPNM .grt-vrule-index { border-left: 1.5px solid #000; }
#TEJECQF5AYPNM .grt-vrule-0 { border-left: 1.5px solid #000; }
#TEJECQF5AYPNM .grt-vrule-1 { border-left: 1px solid #000; }
#TEJECQF5AYPNM .grt-vrule-2 { border-left: 0.5px solid #000; }
#TEJECQF5AYPNM .grt-left { text-align: left; }
#TEJECQF5AYPNM .grt-center { text-align: center; }
#TEJECQF5AYPNM .grt-right { text-align: right; font-variant-numeric: tabular-nums; }
#TEJECQF5AYPNM .grt-head { font-family: "Times New Roman", 'Courier New'; font-size: 0.88em; }
#TEJECQF5AYPNM .grt-bold { font-weight: bold; }</style>
<table id="TEJECQF5AYPNM" style="float:center">
<caption>Table 1. A table with varied column widths.</caption>
<colgroup>
<col width="250px">
<col style="width: 50px">
<col style="width: 50px">
<col style="width: 50px">
<col style="width: 50px">
<col style="width: 75px">
</colgroup>
<thead>
<tr>
<th class="grt-left"></th>
<th class="grt-center grt-bhrule-0 grt-vrule-index" colspan="2">A</th>
<th class="grt-center grt-bhrule-0 grt-vrule-0" colspan="2">B</th>
<th class="grt-center grt-bhrule-0 grt-vrule-0" colspan="1">C</th>
</tr>
<tr>
<th class="grt-left">years!</th>
<th class="grt-center grt-vrule-index" colspan="1">Int</th>
<th class="grt-center grt-vrule-1" colspan="1">Float</th>
<th class="grt-center grt-vrule-0" colspan="1">Float</th>
<th class="grt-center grt-vrule-1" colspan="1">3</th>
<th class="grt-center grt-vrule-0" colspan="1">Longer Text</th>
</tr>
</thead>
<tbody>
<tr>
<td class="grt-left">2000</td>
<td class="grt-right grt-vrule-index">-100,000</td>
<td class="grt-right grt-vrule-1"> 2.389p</td>
<td class="grt-right grt-vrule-0">-1,601.00</td>
<td class="grt-center grt-vrule-1">2025-03-14</td>
<td class="grt-left grt-vrule-0">once upon a time, once upon a time, once upon a time, once upon a time</td>
</tr>
<tr>
<td class="grt-left grt-hrule-0">2001</td>
<td class="grt-right grt-hrule-0 grt-vrule-index">-91,667</td>
<td class="grt-right grt-hrule-0 grt-vrule-1"> 22.217p</td>
<td class="grt-right grt-hrule-0 grt-vrule-0">-1,367.62</td>
<td class="grt-center grt-hrule-0 grt-vrule-1">2025-03-26</td>
<td class="grt-left grt-hrule-0 grt-vrule-0"> risk is hard to define</td>
</tr>
<tr>
<td class="grt-left grt-hrule-0">2002</td>
<td class="grt-right grt-hrule-0 grt-vrule-index">-83,333</td>
<td class="grt-right grt-hrule-0 grt-vrule-1"> 206.619p</td>
<td class="grt-right grt-hrule-0 grt-vrule-0">-1,134.25</td>
<td class="grt-center grt-hrule-0 grt-vrule-1">2025-04-07</td>
<td class="grt-left grt-hrule-0 grt-vrule-0"> not in Kansas anymore</td>
</tr>
<tr>
<td class="grt-left grt-hrule-0">2003</td>
<td class="grt-right grt-hrule-0 grt-vrule-index">-75,000</td>
<td class="grt-right grt-hrule-0 grt-vrule-1"> 1.922n</td>
<td class="grt-right grt-hrule-0 grt-vrule-0">-900.88</td>
<td class="grt-center grt-hrule-0 grt-vrule-1">2025-04-19</td>
<td class="grt-left grt-hrule-0 grt-vrule-0"> neutrinos are hard to detect</td>
</tr>
<tr>
<td class="grt-left grt-hrule-0">2004</td>
<td class="grt-right grt-hrule-0 grt-vrule-index">-66,667</td>
<td class="grt-right grt-hrule-0 grt-vrule-1"> 17.870n</td>
<td class="grt-right grt-hrule-0 grt-vrule-0">-667.50</td>
<td class="grt-center grt-hrule-0 grt-vrule-1">2025-05-01</td>
<td class="grt-left grt-hrule-0 grt-vrule-0"> Adam Smith is the father of economics</td>
</tr>
<tr>
<td class="grt-left grt-hrule-0">2005</td>
<td class="grt-right grt-hrule-0 grt-vrule-index">-58,333</td>
<td class="grt-right grt-hrule-0 grt-vrule-1"> 166.196n</td>
<td class="grt-right grt-hrule-0 grt-vrule-0">-434.12</td>
<td class="grt-center grt-hrule-0 grt-vrule-1">2025-05-13</td>
<td class="grt-left grt-hrule-0 grt-vrule-0">once upon a time</td>
</tr>
<tfoot>
<tr>
<td colspan=3>Footer 1 stuff. This is very long. This is very long. This is very long. This is very long. </td>
<td>Footer 2 stuff.</td>
</tbody>
</table>
</div>
<p>Some text below the table.</p>
</body>
</html>
+1570 -1110
View File
File diff suppressed because it is too large Load Diff
+5244
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
+177 -75
View File
@@ -13,70 +13,61 @@ tbl-align: left
number-sections: true
number-offset: 0
number-depth: 3
code-line-numbers: false
code-copy: true
code-overflow: wrap
code-fold: true # code there but in folded up mode
code-fold: true
fig-format: svg
fig-align: left
format:
html:
html-table-processing: none
theme: litera # cosmo
theme: litera
fontsize: 0.9em
css: styles.css
include-in-header: pmir-header.html
smooth-scroll: true
toc-title: 'In this chapter:'
citations-hover: true
# code-tools: true
crossrefs-hover: false
fig-responsive: true
footnotes-hover: true
lightbox: true
link-external-icon: true # arrow in a box for external links
link-external-newwindow: true # open in separate window
page-layout: article # optimized body region; full expands contents if nothing in margins
link-external-icon: true
link-external-newwindow: true
page-layout: article
page-navigation: true
reference-section-title: ' ' # title for references section, passed to pandoc
reference-section-title: ' '
page-footer:
left: "Stephen J. Mildenhall. License: [CC BY-SA 2.0](https://creativecommons.org/licenses/by-sa/2.0/)."
left: 'Stephen J. Mildenhall. License: [CC BY-SA 2.0](https://creativecommons.org/licenses/by-sa/2.0/).'
twitter-card: true
open-graph: true
toc: true
toc-depth: 3
math: mathjax
# math:
# engine: mathjax
# url: https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
pdf:
include-in-header: prefobnicate.tex
documentclass: scrartcl # report scrreprt artcile scrreprt
papersize: a4
fontsize: 12pt
keep-tex: true # keep the tex file
geometry: margin=0.8in
pdf-engine: lualatex
pdf-engine-opts:
- '-interaction=nonstopmode'
toc: false
include-in-header: prefobnicate.tex
documentclass: scrartcl
papersize: a4
fontsize: 11pt
keep-tex: true
geometry: margin=0.8in
pdf-engine: lualatex
pdf-engine-opts:
- '-interaction=nonstopmode'
toc: false
execute:
eval: true
echo: true
cache: true # this will reexecute all cells if any cell changes
cache-type: jupyter # project is notebook/python heavy
freeze: false # freezer locks the whole file, md and code; rarely what you want
cache: true
cache-type: jupyter
freeze: false
kernel: python3
engine: jupyter # use IPython kernel
engine: jupyter
daemon: 1200
jupyter:
jupytext:
formats: ipynb,qmd
formats: ipynb,qmd:quarto
text_representation:
extension: .qmd
format_name: quarto
@@ -88,10 +79,8 @@ jupyter:
name: python3
---
# Python set-up
```{python}
#| echo: true
#| echo: true
#| label: setup
from IPython.display import HTML, display
import matplotlib as mpl
@@ -112,10 +101,12 @@ gter.logger.setLevel(gter.logging.WARNING)
Second level index has mixed types. Range of magnitudes. Picking out years.
\footnotesize
```{python}
#| label: tbl-hard-rules
#| tbl-cap: Quarto generated caption
#| tbl-cap: Default display output (Quarto generated caption)
level_1 = ["A", "A", "B", "B", 'C']
level_2 = ['Int', 'Float', 'Float', 3, 'Longer Text']
@@ -139,43 +130,56 @@ hard.columns = multi_index
hard
```
`sGT` format
\normalsize
@tbl-hard-rules shows the default output and @tbl-hard-rules-2 the `sGT` format output.
```{python}
#| label: tbl-hard-rules-2
#| tbl-cap: Quarto generated caption
#| tbl-cap: Greater Tables output (Quarto generated caption)
sGT(hard, 'A table with varied columns.')
```
Illustrate some alternatives.
Here are some alternatives:
* @tbl-hard-rules-3a hrules no vrules
* @tbl-hard-rules-3b change date and integer formats and
* @tbl-hard-rules-3c change padding and debug mode.
```{python}
#| echo: fenced
#| label: tbl-hard-rules-3
#| tbl-cap: Quarto generated caption
display(sGT(hard.sample(5).sort_index(), 'No v rules, but h rules',
#| label: tbl-hard-rules-3a
#| tbl-cap: No V rules but hrules (Quarto generated caption)
display(sGT(hard.sample(5).sort_index(),
caption='GT caption No v rules, but h rules',
vrule_widths=(0,0,0),
hrule_widths=(1,0,0)))
display(sGT(hard.sample(5).sort_index(),
'Change default date and integer formats',
default_date_str='%m-%d', default_integer_str='[{x:d}]'))
display(sGT(hard.sample(5).sort_index(),
'Change padding, debug mode lines',
default_date_str='%m-%d', default_integer_str='[{x:d}]',
padding_trbl=(10, 10, 20, 20), debug=True))
```
Here is the raw output.
```{python}
#| echo: fenced
#| label: tbl-hard-rules-3b
#| tbl-cap: Change date and integer formats (Quarto generated caption)
display(sGT(hard.sample(5).sort_index(),
caption='Change default date and integer formats',
default_date_str='%m-%d', default_integer_str='[{x:d}]'))
```
```{python}
#| echo: fenced
#| label: tbl-hard-rules-3c
#| tbl-cap: Change padding and debug mode, boxes (Quarto generated caption)
display(sGT(hard.sample(5).sort_index(),
caption='Change padding, debug mode lines',
padding_trbl=(10, 10, 20, 20), debug=True))
```
Here is the raw HTML and LaTeX output.
\footnotesize
```{python}
#| label: raw-output
f = sGT(hard.head(4), debug=True)
print('HTML output\n')
print(f._repr_html_())
@@ -187,11 +191,11 @@ print(f._repr_latex_())
\normalsize
# A Table with TeX
# A Table with TeX Content
```{python}
#| label: tbl-tex
#| tbl-cap: "Quarto generated caption: table displayed by default routine."
#| tbl-cap: '(Quarto generated caption): table displayed by default routine.'
index = pd.Index(["A", "B", "$C_1$", "C_2 not tex", '$\\cos(A)$'])
tex = pd.DataFrame(
{'x': np.arange(2020, 2025, dtype=int),
@@ -208,7 +212,7 @@ tex
```{python}
#| label: tbl-tex-2
#| tbl-cap: Quarto generated caption
#| tbl-cap: GT output (Quarto generated caption)
sGT(tex, 'GT Caption')
```
@@ -221,23 +225,21 @@ tex.columns = ["A (%)", "B", "$C_1$", "C_2 not tex", '$\\cos(A)$']
sGT(tex, 'Ratio columns in A', ratio_cols='A (%)')
```
# Greater_tables Test Suite
```{python}
#| echo: true
#| echo: true
#| label: greater-tables-test
test_gen = gtu.TestDFGenerator(0, 0)
ans = test_gen.test_suite()
```
## Test Table: basic
```{python}
#| echo: fold
#| echo: true
#| label: tbl-greater-tables-test-0
#| tbl-cap: Output for test table basic
#| tbl-cap: GT output for test table basic
hrw = (0, 0, 0)
sGT(ans['basic'], "Basic", ratio_cols='z', aligners={'w': 'l'},
hrule_widths=hrw)
@@ -248,11 +250,11 @@ Comments go here.
## Test Table: timeseries
```{python}
#| echo: fold
#| echo: true
#| label: tbl-greater-tables-test-1
#| tbl-cap: Output for test table timeseries
#| tbl-cap: GT output for test table timeseries
hrw = (0, 0, 0)
sGT(ans['timeseries'], "Timeseries", ratio_cols='z', aligners={'w': 'l'},
hrule_widths=hrw)
@@ -264,11 +266,11 @@ Comments go here.
## Test Table: multiindex
```{python}
#| echo: fold
#| echo: true
#| label: tbl-greater-tables-test-2
#| tbl-cap: Output for test table multiindex
#| tbl-cap: GT output for test table multiindex
hrw = (1.5, 1.0, 0.5)
sGT(ans['multiindex'], "Multiindex", ratio_cols='z', aligners={'w': 'l'},
hrule_widths=hrw)
@@ -280,11 +282,11 @@ Comments go here.
## Test Table: multicolumns
```{python}
#| echo: fold
#| echo: true
#| label: tbl-greater-tables-test-3
#| tbl-cap: Output for test table multicolumns
#| tbl-cap: GT output for test table multicolumns
hrw = (0, 0, 0)
sGT(ans['multicolumns'], "Multicolumns", ratio_cols='z', aligners={'w': 'l'},
hrule_widths=hrw)
@@ -296,11 +298,11 @@ Comments go here.
## Test Table: complex
```{python}
#| echo: fold
#| echo: true
#| label: tbl-greater-tables-test-4
#| tbl-cap: Output for test table complex
#| tbl-cap: GT output for test table complex
hrw = (1.5, 1.0, 0.5)
sGT(ans['complex'], "Complex", ratio_cols='z', aligners={'w': 'l'},
hrule_widths=hrw)
@@ -308,3 +310,103 @@ sGT(ans['complex'], "Complex", ratio_cols='z', aligners={'w': 'l'},
Comments go here.
# Other input formats
## Markown
| **Insured group or insurance product** | **Sat** | **RP** | **RF** |
|:------------------------------------------------------------|:-------:|:------:|:------:|
| Non-standard auto | x | | |
| General liability for judgment proof corporation | x | | |
| Term life insurance | | x | |
| Catastrophe Reinsurance, outside rating agency bounds | | x | |
| High limit property per risk reinsurance | | x | |
| Personal lines for affluent individuals | x | x | |
| Small commercial lines | x | x | |
| Catastrophe reinsurance, within rating agency bounds | x | x | |
| Large account captive reinsurance | | | x |
| Structured quota share, requiring a risk transfer test | x | | x |
| Working layer casualty excess of loss | | x | x |
| Surplus relief quota share on cat exposed line | x | x | x |
| Middle market commercial lines work comp or commercial auto | x | x | x |
```{python}
#| echo: true
#| label: tbl-greater-tables-test-5
#| tbl-cap: GT from markdown table input
txt = '''
| **Insured group or insurance product** | **Sat** | **RP** | **RF** |
|:------------------------------------------------------------|:-------:|:------:|:------:|
| Non-standard auto | x | | |
| General liability for judgment proof corporation | x | | |
| Term life insurance | | x | |
| Catastrophe Reinsurance, outside rating agency bounds | | x | |
| High limit property per risk reinsurance | | x | |
| Personal lines for affluent individuals | x | x | |
| Small commercial lines | x | x | |
| Catastrophe reinsurance, within rating agency bounds | x | x | |
| Large account captive reinsurance | | | x |
| Structured quota share, requiring a risk transfer test | x | | x |
| Working layer casualty excess of loss | | x | x |
| Surplus relief quota share on cat exposed line | x | x | x |
| Middle market commercial lines work comp or commercial auto | x | x | x |
'''
GT(txt)
```
## List of lists
```{python}
x = None
if x:
print(123)
```
```{python}
#| echo: true
#| label: tbl-greater-tables-test-6
#| tbl-cap: GT output for list of lists input
lol = [['a', 'b', 'c', 'd'], ['west', 10, 20, 30], ['east', 10, 200, 30], ['north', 10, 20, 300], ['south', 100, 20, 30]]
GT(lol)
```
```{python}
f = GT(lol)
f
```
```{python}
tbl = '''
Var | Amount
:---|------:
A | 100.0
B | 0.123
C | A string
'''
def ff(x):
if abs(x) < 1:
return f'{x:.1%}'
else:
return f'{x:,.2f}'
sGT(tbl, table_float_format=ff)
```
```{python}
P = 1000 * 1.075**-10 + 120
L = 1000
ry = .1
v = 1/(1+ry)
T = 10
pv = P - v**T * L
fv = pv / v**T
pv, fv
```
+615 -406
View File
File diff suppressed because it is too large Load Diff