stats/summarise.jl

changeset 13
2cb7f20654e6
child 16
841a0d255db3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stats/summarise.jl	Sun Oct 04 16:30:24 2026 -0500
@@ -0,0 +1,186 @@
+#!/usr/bin/env julia
+
+using CSV
+using DataFrames
+using Statistics
+using Distributions
+
+# --------------------------------------------------------------------------
+# CONFIGURATION
+#
+# Map from CSV file name (as it appears inside each parameter directory) to
+# the list of data columns in that file that should be summarized. Files may
+# have different columns from one another; the `iter` column is assumed to
+# be present in all of them and is NOT listed here.
+# --------------------------------------------------------------------------
+const LOG_COLS = ["value", "relative_value", "discrepancy", "aux_discrepancy", "joint_discrepancy"];
+const AUX_COLS = ["k", "c1", "c2", "k_dist", "c_dist"]
+
+const FILES_COLUMNS = Dict(
+    "aux_radon_fb_log.txt" => LOG_COLS,
+    "aux_radon_sliding_fb_log.txt" => LOG_COLS,
+    "aux2_radon_fb_log.txt" => LOG_COLS,
+    "aux2_radon_sliding_fb_log.txt" => LOG_COLS,
+    "aux_radon_fb_aux.csv" => AUX_COLS,
+    "aux_radon_sliding_fb_aux.csv" => AUX_COLS,
+    "aux2_radon_fb_aux.csv" => AUX_COLS,
+    "aux2_radon_sliding_fb_aux.csv" => AUX_COLS,
+)
+
+const ITER_COL = :iter
+const CONF_LEVEL = 0.95
+
+# --------------------------------------------------------------------------
+# Compute the half-width of a 95% (or CONF_LEVEL) confidence interval for
+# the mean of `values`, using the t-distribution. Falls back gracefully for
+# degenerate sample sizes.
+# --------------------------------------------------------------------------
+function conf_margin(values::AbstractVector{<:Real})
+    n = length(values)
+    if n < 2
+        return 0.0
+    end
+    s = std(values)
+    if s == 0
+        return 0.0
+    end
+    tdist = TDist(n - 1)
+    tcrit = quantile(tdist, 1 - (1 - CONF_LEVEL) / 2)
+    return tcrit * s / sqrt(n)
+end
+
+# --------------------------------------------------------------------------
+# Read one configured file from one parameter directory, if it exists.
+# Returns a DataFrame with at least `iter` and the configured columns, or
+# `nothing` if the file is missing or unreadable.
+# --------------------------------------------------------------------------
+function read_dir_file(dir::AbstractString, filename::AbstractString, cols::Vector{String})
+    path = joinpath(dir, filename)
+    if !isfile(path)
+        @warn "Missing file, skipping" path
+        return nothing
+    end
+
+    local df
+    try
+        df = CSV.read(path, DataFrame)
+    catch e
+        @warn "Could not read CSV, skipping" path exception = e
+        return nothing
+    end
+
+    if !hasproperty(df, ITER_COL)
+        @warn "File has no '$(ITER_COL)' column, skipping" path
+        return nothing
+    end
+
+    present_cols = String[]
+    for c in cols
+        if hasproperty(df, Symbol(c))
+            push!(present_cols, c)
+        else
+            @warn "Column '$c' not found in file, skipping that column for this directory" path
+        end
+    end
+    if isempty(present_cols)
+        return nothing
+    end
+
+    keep = vcat([ITER_COL], Symbol.(present_cols))
+    return df[:, keep]
+end
+
+# --------------------------------------------------------------------------
+# Build the summary DataFrame for one configured file, across all `dirs`.
+# --------------------------------------------------------------------------
+function summarize_file(dirs::Vector{String}, filename::AbstractString, cols::Vector{String})
+    parts = DataFrame[]
+    for dir in dirs
+        d = read_dir_file(dir, filename, cols)
+        d === nothing || push!(parts, d)
+    end
+
+    if isempty(parts)
+        @warn "No data found for file across any directory, skipping summary" filename
+        return nothing
+    end
+
+    combined = vcat(parts...; cols=:union)
+    grouped = groupby(combined, ITER_COL)
+
+    iters = sort(collect(keys(grouped)))
+    summary = DataFrame(iter=[k[ITER_COL] for k in iters])
+
+    for col in cols
+        colsym = Symbol(col)
+        mins = Vector{Union{Missing,Float64}}(undef, length(iters))
+        maxs = Vector{Union{Missing,Float64}}(undef, length(iters))
+        means = Vector{Union{Missing,Float64}}(undef, length(iters))
+        confmin = Vector{Union{Missing,Float64}}(undef, length(iters))
+        confmax = Vector{Union{Missing,Float64}}(undef, length(iters))
+
+        for (i, key) in enumerate(iters)
+            sub = grouped[key]
+            if !hasproperty(sub, colsym)
+                mins[i] = maxs[i] = means[i] = confmin[i] = confmax[i] = missing
+                continue
+            end
+            vals = collect(skipmissing(sub[!, colsym]))
+            if isempty(vals)
+                mins[i] = maxs[i] = means[i] = confmin[i] = confmax[i] = missing
+                continue
+            end
+            m = mean(vals)
+            margin = conf_margin(vals)
+            mins[i] = minimum(vals)
+            maxs[i] = maximum(vals)
+            means[i] = m
+            confmin[i] = m - margin
+            confmax[i] = m + margin
+        end
+
+        summary[!, Symbol(col, "_min")] = mins
+        summary[!, Symbol(col, "_max")] = maxs
+        summary[!, Symbol(col, "_confmax")] = confmax
+        summary[!, Symbol(col, "_confmin")] = confmin
+        summary[!, Symbol(col, "_mean")] = means
+    end
+
+    return summary
+end
+
+# --------------------------------------------------------------------------
+# Main
+# --------------------------------------------------------------------------
+function main()
+    if isempty(ARGS)
+        println(stderr, "Usage: julia summarize.jl DIR1 [DIR2 DIR3 ...]")
+        exit(1)
+    end
+
+    dirs = String[]
+    for d in ARGS
+        if isdir(d)
+            push!(dirs, d)
+        else
+            @warn "Not a directory, skipping" d
+        end
+    end
+
+    if isempty(dirs)
+        println(stderr, "No valid directories given.")
+        exit(1)
+    end
+
+    for (filename, cols) in FILES_COLUMNS
+        summary = summarize_file(dirs, filename, cols)
+        summary === nothing && continue
+
+        stem = first(splitext(filename))
+        outpath = joinpath(pwd(), "$(stem)_summary.csv")
+        CSV.write(outpath, summary)
+        println("Wrote $outpath ($(nrow(summary)) rows)")
+    end
+end
+
+main()

mercurial