From d399e56e61452492399ec012fc847e09500f1493 Mon Sep 17 00:00:00 2001 From: Stephen Mildenhall Date: Sun, 9 Mar 2025 08:19:25 +0000 Subject: [PATCH] added tikz and docs --- .idea/.gitignore | 8 ++ docs/Makefile | 20 +++++ docs/conf.py | 55 ++++++++++++++ docs/index.rst | 20 +++++ docs/make.bat | 35 +++++++++ greater_tables/greater_tables.py | 126 ++++++++++++++++--------------- 6 files changed, 203 insertions(+), 61 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 docs/Makefile create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/make.bat diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..10b984c --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,55 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'greater_tables' +copyright = '2025, Stephen J Mildenhall' +author = 'Stephen J Mildenhall' + +# The full version, including alpha/beta/rc tags +release = '1.0.0' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..d0f8b91 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,20 @@ +.. greater_tables documentation master file, created by + sphinx-quickstart on Sun Mar 9 08:18:13 2025. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to greater_tables's documentation! +========================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..954237b --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/greater_tables/greater_tables.py b/greater_tables/greater_tables.py index 15a05f2..085c447 100644 --- a/greater_tables/greater_tables.py +++ b/greater_tables/greater_tables.py @@ -20,11 +20,11 @@ if logger.hasHandlers(): # Clear existing handlers logger.handlers.clear() # SET DEGBUUGER LEVEL -LEVEL = logging.DEBUG # DEBUG or INFO, WARNING, ERROR, CRITICAL +LEVEL = logging.ERROR # DEBUG or INFO, WARNING, ERROR, CRITICAL logger.setLevel(LEVEL) handler = logging.StreamHandler(sys.stderr) handler.setLevel(LEVEL) -formatter = logging.Formatter('%(asctime)s | %(levelname)s | %(funcName)-15s | %(message)s', datefmt='%H:%M:%S.%f') +formatter = logging.Formatter('%(asctime)s | %(levelname)s | %(funcName)-15s | %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) logger.info('Logger Setup; module recompiled.') @@ -39,7 +39,7 @@ class GT(object): date_default_str='{x:%Y-%m-%d}', ratio_cols=None, ratio_default_str='{x:.1%}', cast_to_floats=True, hrule_widths=None, - index_names=True, + index_names=False, pef_precision=3, pef_lower=-3, pef_upper=6, font_size=0.9): """ Create a greater_tables formatting object, setting defaults. @@ -56,7 +56,7 @@ class GT(object): :param formatters: None or dict type -> format function to override defaults. """ self.caption = caption - self.df = df.copy() # the object being formatted + self.df = df.copy(deep=True) # the object being formatted self.df_id = f'T{id(df):x}'[::2].upper() # set defaults @@ -80,12 +80,12 @@ class GT(object): self.ratio_cols = [ratio_cols] if cast_to_floats: - for i, c in enumerate(df.columns): - old_type = df.dtypes[c] - if not np.any((is_integer_dtype(df.iloc[:, i]), - is_datetime64_any_dtype(df.iloc[:, i]))): + for i, c in enumerate(self.df.columns): + old_type = self.df.dtypes[c] + if not np.any((is_integer_dtype(self.df.iloc[:, i]), + is_datetime64_any_dtype(self.df.iloc[:, i]))): try: - df.iloc[:, i] = df.iloc[:, i].astype(float) + self.df.iloc[:, i] = self.df.iloc[:, i].astype(float) logger.debug(f'coerce {i}={c} from {old_type} to float') except ValueError: logger.debug(f'coercing {i}={c} from {old_type} to float FAILED') @@ -96,20 +96,20 @@ class GT(object): self.date_col_indices = [] self.object_col_indices = [] # manage non-unique col names here - logger.info('FIGURING TYPES') + logger.debug('FIGURING TYPES') for i in range(self.df.shape[1]): - ser = df.iloc[:, i] + ser = self.df.iloc[:, i] if is_datetime64_any_dtype(ser): - logger.info(f'col {i} = {self.df.columns[i]} is DATE') + logger.debug(f'col {i} = {self.df.columns[i]} is DATE') self.date_col_indices.append(i) elif is_integer_dtype(ser): - logger.info(f'col {i} = {self.df.columns[i]} is INTEGER') + logger.debug(f'col {i} = {self.df.columns[i]} is INTEGER') self.integer_col_indices.append(i) elif is_float_dtype(ser): - logger.info(f'col {i} = {self.df.columns[i]} is FLOAT') + logger.debug(f'col {i} = {self.df.columns[i]} is FLOAT') self.float_col_indices.append(i) else: - logger.info(f'col {i} = {self.df.columns[i]} is OBJECT') + logger.debug(f'col {i} = {self.df.columns[i]} is OBJECT') self.object_col_indices.append(i) self.integer_default_str = integer_default_str @@ -168,7 +168,7 @@ class GT(object): else: # TODo BEEF UP? return self.float_default_str.format(x=f) - except ValueError: + except (TypeError, ValueError): return str(x) def pef(self, x): @@ -214,7 +214,7 @@ class GT(object): return f'{x:,.0f}.' else: return fmt.format(x=x) - except ValueError: + except (ValueError, TypeError): return str(x) return ff @@ -246,13 +246,14 @@ class GT(object): self._df_formatters.append(self.make_float_formatter(self.df.iloc[:, i])) else: # print(f'{i} default') - self._df_formatters.append(self.default) + self._df_formatters.append(self.default_formatter) # self._df_formatters is now a list of length equal to cols in df if len(self._df_formatters) != self.df.shape[1]: raise ValueError(f'Something wrong: {len(self._df_formatters)=} != {self.df.shape=}') return self._df_formatters def __repr__(self): + """Basic representation.""" return f"GreaterTable wrapping df {len(self.df)} rows, id {self.df_id}" def _repr_html_(self): @@ -267,6 +268,7 @@ class GT(object): ncols = self.df.shape[1] dt = self.df.dtypes + # call pandas built-in html converter html = self.df.to_html(table_id=self.df_id, formatters=self.df_formatters, index_names=self.index_names) # TODO USE SELF.ALIGNERS @@ -284,99 +286,98 @@ class GT(object): else: hrule_widths = (0, 0, 0) # start to build style - df_id = self.df_id # shorter style = [] style.append('\n') + # OK + if len(style) > 2: style = '\n'.join(style) else: @@ -466,7 +469,8 @@ class GT(object): html = soup.prettify() self.df_html = html # after alteration # return - logger.debug('CREATED HTML STYLE') + logger.info('CREATED HTML STYLE') + return style + html @property @@ -475,7 +479,7 @@ class GT(object): def _repr_latex_(self): """Generate a LaTeX tabular representation.""" - logger.debug('CREATED LATEX STYLE') + logger.info('CREATED LATEX STYLE') # latex = self.df.to_latex(caption=self.caption, formatters=self._df_formatters) latex = self.df_to_tikz() return latex @@ -486,7 +490,8 @@ class GT(object): Very ingenious GTP code with some SM enhancements. """ - idx = self.df.index + # otherwise you alter the actual index + idx = self.df.index.copy() idx.names = [i for i in range(idx.nlevels)] # Determine at which level the index changes index_df = idx.to_frame(index=False) # Convert MultiIndex to a DataFrame @@ -570,9 +575,10 @@ class GT(object): # local variable df = self.df +# \\begin{{{figure}}}{latex} header = """ -\\begin{{{figure}}}{latex} \\centering +\\footnotesize {extra_defs} \\begin{{tikzpicture}}[ auto, @@ -591,9 +597,9 @@ class GT(object): {post_process} \\end{{tikzpicture}} -{caption} -\\end{{{figure}}} """ +# {caption} +# \\end{{{figure}}} # make a safe float format if float_format is None: @@ -634,9 +640,6 @@ class GT(object): else: nc_index = 0 - # this is handled by the caller - # df = df.astype(float, errors='ignore') - if nc_index: if vrule is None: vrule = set() @@ -656,9 +659,10 @@ class GT(object): # note this happens AFTER you have reset the index...need to pass number of index columns colw, mxmn, tabs = GT.guess_column_widths(df, nc_index=nc_index, float_format=wfloat_format, tabs=tabs, - scale=scale, equal=equal) + scale=scale, equal=equal) # print(colw, tabs) logger.debug(f'tabs: {tabs}') + logger.debug(f'colw: {colw}') # alignment dictionaries ad = {'l': 'left', 'r': 'right', 'c': 'center'} @@ -728,7 +732,7 @@ class GT(object): for lvl in range(len(df.columns.levels)): nl = '' sparse_columns[lvl], mi_vrules[lvl] = GT._sparsify_mi(df.columns.get_level_values(lvl), - lvl == len(df.columns.levels) - 1) + lvl == len(df.columns.levels) - 1) for cn, c, al in zip(df.columns, sparse_columns[lvl], align): c = wfloat_format(c) s = f'{nl} {{cell:{ad2[al]}{colw[cn]}s}} ' @@ -1093,13 +1097,13 @@ class GT(object): ans = ef(x) return ans except ValueError as e: - logger.error(f'ValueError {e}') + logger.debug(f'ValueError {e}') return str(x) except TypeError as e: - logger.error(f'TypeError {e}') + logger.debug(f'TypeError {e}') return str(x) except AttributeError as e: - logger.error(f'AttributeError {e}') + logger.debug(f'AttributeError {e}') return str(x) # class GT_ORIGINAL(object):