Massive work on text output and col widths

Solid beta for 2.0.0
This commit is contained in:
Stephen Mildenhall
2025-06-01 22:49:35 +01:00
parent f354ef7b10
commit e10fe1f98f
4 changed files with 1045 additions and 434 deletions
+106
View File
@@ -0,0 +1,106 @@
Here's how you subclass.
```python
class sGT(GT):
"""
Example standard GT with Steve House-Style defaults.
Each application can create its own defaults by subclassing GT
in this way.
"""
def __init__(self, df, caption="", guess_years=True, ratio_regex='lr|roe|coc', **kwargs):
"""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:
ratio_cols = kwargs['ratio_cols']
else:
if ratio_regex != '' and ncolumns == 1:
ratio_cols = df.filter(regex=ratio_regex).columns.to_list()
else:
ratio_cols = None
if guess_years:
year_cols = sGT.guess_years(df)
else:
year_cols = kwargs.get('year_cols', None)
# rule sizes
hrule_widths = (1.5, 1, 0) if nindex > 1 else None
vrule_widths = (1.5, 1, 0.5) if ncolumns > 1 else None
table_hrule_width = 1 if nindex == 1 else 2
table_vrule_width = 1 if ncolumns == 1 else (
1.5 if ncolumns == 2 else 2)
# padding
nr, nc = df.shape
if 'padding_trbl' in kwargs:
padding_trbl = kwargs['padding_trbl']
else:
pad_tb = 4 if nr < 16 else (2 if nr < 25 else 1)
pad_lr = 10 if nc < 9 else (5 if nc < 13 else 2)
padding_trbl = (pad_tb, pad_lr, pad_tb, pad_lr)
font_body = 0.9 if nr < 25 else (0.8 if nr < 41 else 0.7)
font_caption = np.round(1.1 * font_body, 2)
font_head = np.round(1.1 * font_body, 2)
pef_lower = -3
pef_upper = 6
pef_precision = 3
defaults = {
'ratio_cols': ratio_cols,
'year_cols': year_cols,
'default_integer_str': '{x:,.0f}',
'default_float_str': '{x:,.3f}',
'default_date_str': '%Y-%m-%d',
'default_ratio_str': '{x:.1%}',
'cast_to_floats': True,
'table_hrule_width': table_hrule_width,
'table_vrule_width': table_vrule_width,
'hrule_widths': hrule_widths,
'vrule_widths': vrule_widths,
'sparsify': True,
'sparsify_columns': True,
'padding_trbl': padding_trbl,
'font_body': font_body,
'font_head': font_head,
'font_caption': font_caption,
'pef_precision': pef_precision,
'pef_lower': pef_lower,
'pef_upper': pef_upper,
'debug': False
}
defaults.update(kwargs)
super().__init__(df, caption=caption, **defaults)
@staticmethod
def guess_years(df):
"""Try to guess which columns (body or index) are years.
A column is considered a year if:
- It is numeric (integer or convertible to integer)
- All values are within a reasonable range (e.g., 18002100)
"""
year_columns = []
df = df.reset_index(drop=False, col_level=df.columns.nlevels - 1)
for i, col in enumerate(df.columns):
try:
series = pd.to_numeric(df[col], errors='coerce').dropna()
if series.dtype.kind in 'iu' and series.between(1800, 2100).all():
year_columns.append(col)
except Exception:
continue
return year_columns
```
File diff suppressed because it is too large Load Diff
+173
View File
@@ -0,0 +1,173 @@
"""Make test tables. Couple of approaches. GPT."""
from datetime import datetime, timedelta
import random
from random import randint, uniform, sample
import pandas as pd
from faker import Faker
# Simulate a list of words for column name generation
words = [
"transaction", "identifier", "processing", "timestamp", "user", "account", "description",
"amount", "balance", "location", "currency", "status", "failure", "note", "reference",
"operation", "duration", "estimate", "category", "filename", "extension", "type", "project",
"client", "supplier", "remark", "address", "email", "comment", "entry", "premium",
"loss ratio", 'expense ratio', "combined ratio", 'loss date'
]
fake = Faker()
def make_column_name():
# choices with replacement -> sample
return " ".join(random.sample(words, k=random.randint(1, 5)))
def make_text_blob():
return " ".join(sample(words, randint(10, 25)))
def make_test_dataframe(n_rows, n_cols):
col_types = random.choices(["int", "float", "str", "date"], k=n_cols)
data = {}
for _ in range(n_cols):
dtype = col_types.pop(0)
col_name = make_column_name() + f' ({dtype})'
if dtype == "int":
data[col_name] = [random.randint(0, 10000) if random.random() > 0.1 else None for _ in range(n_rows)]
elif dtype == "float":
data[col_name] = [round(random.uniform(0, 1e4), 3) if random.random() > 0.1 else None for _ in range(n_rows)]
elif dtype == "str":
data[col_name] = [fake.sentence(nb_words=random.randint(2, 8)) if random.random() > 0.1 else None for _ in range(n_rows)]
elif dtype == "date":
start = datetime(2015, 1, 1)
data[col_name] = [
(start + timedelta(days=random.randint(0, 4000))).date().isoformat()
if random.random() > 0.1 else None for _ in range(n_rows)
]
return pd.DataFrame(data)
def make_dataframe_set(n):
"""Sample dataframes with n rows."""
def rand_date():
start = datetime(2000, 1, 1)
return [(start + timedelta(days=randint(0, 10000))).strftime("%Y-%m-%d") for _ in range(n)]
def rand_float():
return [f"{uniform(0, 10000):.3f}" for _ in range(n)]
def rand_int():
return [str(randint(0, 5000)) for _ in range(n)]
def rand_text():
return [make_text_blob() for _ in range(n)]
def rand_filename():
return [f"{'_'.join(sample(words, randint(2, 5)))}.pdf" for _ in range(n)]
def col(colfunc, allow_missing=False):
vals = colfunc()
if allow_missing:
for i in range(randint(1, 3)):
vals[randint(0, len(vals) - 1)] = ''
return vals
dfs = {}
dfs["floats dates filenames"] = pd.DataFrame({
make_column_name(): col(rand_float),
make_column_name(): col(rand_date),
make_column_name(): col(rand_filename),
make_column_name(): col(rand_int),
make_column_name(): col(rand_float, allow_missing=True),
})
dfs["dense text and numbers"] = pd.DataFrame({
make_column_name(): col(rand_text),
make_column_name(): col(rand_float),
make_column_name(): col(rand_int),
make_column_name(): col(rand_text),
make_column_name(): col(rand_date),
make_column_name(): col(rand_float, allow_missing=True),
})
dfs["mixed data with missing"] = pd.DataFrame({
make_column_name(): col(rand_float, allow_missing=True),
make_column_name(): col(rand_text, allow_missing=True),
make_column_name(): col(rand_int, allow_missing=True),
make_column_name(): col(rand_date, allow_missing=True),
make_column_name(): col(rand_filename, allow_missing=True),
})
dfs["long header names"] = pd.DataFrame({
"Detailed Instrumentation Configuration Summary": col(rand_text),
"Archive Metadata Extraction Date Field": col(rand_date),
"Overview Record Approximation Notes": col(rand_text),
"Velocity Gradient Approximation Float": col(rand_float),
"Pressure Summary Int Field": col(rand_int),
})
dfs["file-centric record"] = pd.DataFrame({
make_column_name(): col(rand_filename),
make_column_name(): col(rand_date),
make_column_name(): col(rand_text),
make_column_name(): col(rand_float),
make_column_name(): col(rand_int),
make_column_name(): col(rand_date),
make_column_name(): col(rand_filename, allow_missing=True),
})
return dfs
def make_manual_tests():
"""Five handwritten test tables."""
df1 = pd.DataFrame({
"Consideration of Consequences": ["A rather long text value that could wrap badly.", "Short", "A second problematic entry with spaces."],
"Probability": ["Likely", "Unlikely", "Moderate"],
"Expected Value": ["High", "Low", "Moderate"]
})
df2 = pd.DataFrame({
"event_date": ["2024-12-28", "2025-01-05", "2031-06-21"],
"timestamp": ["2024-12-28T14:23:00", "2025-01-05T09:12:45", "2031-06-21T23:59:59"],
"transaction_code": ["ABC-1001-ZZ", "XYZ-2048-AA", "LONG-CODE-2025-EXTREME"]
})
df3 = pd.DataFrame({
"notes": [
"Item 1: delivered; ready for invoice.",
"Warning -- unit may be faulty?",
"Check: power supply (see page 42)"
],
"status": ["", "", ""],
"path": [
"/usr/local/bin/run.sh",
"C:\\Program Files\\App\\main.exe",
"~/Documents/projects/final-report.pdf"
]
})
df4 = pd.DataFrame({
"Serial": ["A123B456", "X987Y654", "Z000Z111"],
"MD5 Hash": [
"a5c3e1d7f2b9c3d6f1e4a9b3c7d1e2f3",
"9f1c4d3e7a6b2d5c8e3f9a1b7c6d4e5f",
"ffb1a2c3d4e5f67890123456789abcdef"
],
"Unwrapped": ["SingleLineValue", "AnotherOne", "NoBreaksHere"]
})
arrays = [
["Simulation", "Simulation", "Input", "Input", "Output"],
["ID", "Date Generated", "Model Name", "Parameters", "Result Summary"]
]
columns = pd.MultiIndex.from_arrays(arrays)
df5 = pd.DataFrame([
[1, "2024-11-15", "RiskModelV2", "α=0.95, β=3.2", "Stable. 5 iterations. RMSE=0.003"],
[2, "2025-02-04", "SuperModel", "α=0.99, β=2.1", "Converged quickly. RMSE=0.001"],
[3, "2026-08-12", "LongModelNameWithDetails", "α=0.90, β=4.0, γ=1.0", "Diverged on step 4. RMSE=N/A"]
], columns=columns)
return [df1, df2, df3, df4, df5]
+7 -1
View File
@@ -10,7 +10,13 @@ authors = [{name = "Stephen J Mildehall", email = "mynl@me.com" }]
readme = "README.md"
license = { text = "MIT" }
requires-python = ">=3.10"
dependencies = ["pandas", "bs4", "IPython"]
dependencies = [
"bs4",
"cachetools",
"faker",
"IPython",
"pandas",
]
classifiers = [
"Development Status :: 4 - Beta",
"License :: OSI Approved :: MIT License",