Spaces:
Sleeping
Sleeping
File size: 6,425 Bytes
1901948 | 1 2 3 4 5 6 7 8 9 10 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 | """Per-column honesty readout + ordering guard for numeric uploads.
The basis fits ALONG THE ROW AXIS, so it only carries meaning when a column is
sequentially ordered (time-series / sensor / signal / sorted series). This module
makes that explicit per column, so the demo never oversells:
- lag-1 autocorrelation -> is the column row-ordered at all? If a column has no
row structure (autocorrelation near zero), shuffling its rows would not change
it, so an along-row polynomial fit is meaningless and the COARSE (analytics)
archive must NOT be trusted to preserve it -- only the lossless archive is safe.
- R^2 of the COARSE reconstruction -> given that it is ordered, how much of the
column does the smooth fit actually capture? High = compresses and the analytics
archive preserves it; low = noisy, passes through near 1x.
Verdict per column: compresses / partial / passthrough. An aggregate "ordering
guard" flags uploads that look like unordered tabular data (most columns have no row
structure), where the storage win still holds losslessly but the analytics / context
projection should be read with care. No basis-family details are exposed.
"""
import html
import numpy as np
AUTOCORR_ORDERED = 0.3 # lag-1 autocorrelation above which a column is "row-ordered"
R2_SMOOTH = 0.9 # COARSE R^2 above which an ordered column "compresses"
_MAX_ROWS_SHOWN = 16
def _lag1_autocorr(x: np.ndarray) -> float:
x = x - x.mean()
denom = float(np.dot(x, x))
if denom < 1e-12:
return 1.0 # constant column: perfectly "ordered" and trivially compressible
return float(np.dot(x[:-1], x[1:]) / denom)
def per_column_stats(r: dict) -> list[dict] | None:
"""Per-column (autocorr, R^2, verdict). None when not a 2-D numeric result."""
orig = np.asarray(r.get("original")) if r else None
coarse = np.asarray(r.get("coarse")) if r else None
if orig is None or orig.ndim != 2 or coarse is None or coarse.shape != orig.shape:
return None
names = r.get("columns")
out = []
for j in range(orig.shape[1]):
name = names[j] if names and j < len(names) else f"col {j}"
col = orig[:, j].astype(float)
rec = coarse[:, j].astype(float)
ss_tot = float(np.sum((col - col.mean()) ** 2))
constant = ss_tot < 1e-12
r2 = 1.0 if constant else 1.0 - float(np.sum((col - rec) ** 2)) / ss_tot
ac = 1.0 if constant else _lag1_autocorr(col)
ordered = ac >= AUTOCORR_ORDERED
if not ordered:
verdict, note = "passthrough", "no row structure"
elif constant:
verdict, note = "compresses", "constant"
elif r2 >= R2_SMOOTH:
verdict, note = "compresses", "smooth"
else:
verdict, note = "partial", "noisy"
out.append(
{"col": j, "name": name, "autocorr": ac, "r2": r2, "ordered": ordered,
"verdict": verdict, "note": note},
)
return out
def mostly_structured(r: dict) -> bool | None:
"""True/False if a STRICT majority of columns are row-ordered; None if n/a.
Strict so an exact 50/50 split reads as unstructured: the guard banner shows
and the context projection is withheld, consistently. Ties err conservative.
"""
stats = per_column_stats(r)
if not stats:
return None
return sum(s["ordered"] for s in stats) > 0.5 * len(stats)
_BADGE = {
"compresses": ("#0f6e56", "compresses"),
"partial": ("#b96a0a", "partial"),
"passthrough": ("#6464a0", "passthrough ~1x"),
}
def readout_html(r: dict) -> str:
"""Per-column table + ordering-guard banner. Empty string when not applicable."""
stats = per_column_stats(r)
if not stats:
return ""
n_ordered = sum(s["ordered"] for s in stats)
guard = ""
# Same strict-majority rule as mostly_structured: at an exact 50/50 split the
# guard shows AND the cost panel withholds the context projection.
if n_ordered <= 0.5 * len(stats):
guard = (
'<div class="ds-note" style="border-left:3px solid #b96a0a;'
'padding-left:10px;margin-bottom:10px;"><b>Ordering guard:</b> most '
"columns show no row structure -- this looks like unordered tabular data. "
"Row order carries no signal, so the analytics / context archive may not "
"preserve meaning. The lossless storage saving still holds exactly.</div>"
)
rows = []
for s in stats[:_MAX_ROWS_SHOWN]:
color, label = _BADGE[s["verdict"]]
# CSV headers are user input; escape them before they land in the page.
rows.append(
f"<tr><td>{html.escape(str(s['name']))}</td>"
f"<td>{'yes' if s['ordered'] else 'no'}</td>"
f"<td>{max(s['r2'], 0.0):.2f}</td>"
f"<td style='color:{color};font-weight:600'>{label}</td>"
f"<td style='color:#555579'>{s['note']}</td></tr>",
)
more = (
f"<tr><td colspan='5' style='color:#555579'>and {len(stats) - _MAX_ROWS_SHOWN} "
"more columns</td></tr>"
if len(stats) > _MAX_ROWS_SHOWN
else ""
)
return f"""
<div class="ds-summary-block">
<h3>Per-column readout</h3>
{guard}
<table style="width:100%;border-collapse:collapse;font-size:0.92em">
<thead><tr style="text-align:left;color:#6464a0">
<th>column</th><th>row-ordered</th><th>fit R²</th><th>verdict</th><th></th>
</tr></thead>
<tbody>{"".join(rows)}{more}</tbody>
</table>
<div class="ds-note">Smooth, row-ordered columns compress and keep their analytics
value; noisy or unordered columns pass through near 1x -- honestly, per column. Every
column still round-trips losslessly.</div>
</div>
""".strip()
if __name__ == "__main__":
n = 400
t = np.arange(n)
smooth = np.cumsum(np.random.randn(n)) + 0.01 * t # ordered, smooth
noise = np.random.randn(n) # ordered index but high-freq -> low R^2
shuffled = noise.copy()
np.random.shuffle(shuffled) # no row structure
orig = np.stack([smooth, noise, shuffled], axis=1)
# Fake a coarse reconstruction: smooth captured well, noise/shuffled poorly.
flat_noise = np.full(n, noise.mean())
coarse = np.stack([smooth, flat_noise, np.full(n, shuffled.mean())], axis=1)
r = {"original": orig, "coarse": coarse}
for s in per_column_stats(r):
print(s)
print("mostly_structured:", mostly_structured(r))
|