| |
1 #!/usr/bin/env julia |
| |
2 |
| |
3 using CSV |
| |
4 using DataFrames |
| |
5 using Statistics |
| |
6 using Distributions |
| |
7 |
| |
8 # -------------------------------------------------------------------------- |
| |
9 # CONFIGURATION |
| |
10 # |
| |
11 # Map from CSV file name (as it appears inside each parameter directory) to |
| |
12 # the list of data columns in that file that should be summarized. Files may |
| |
13 # have different columns from one another; the `iter` column is assumed to |
| |
14 # be present in all of them and is NOT listed here. |
| |
15 # -------------------------------------------------------------------------- |
| |
16 const LOG_COLS = ["value", "relative_value", "discrepancy", "aux_discrepancy", "joint_discrepancy"]; |
| |
17 const AUX_COLS = ["k", "c1", "c2", "k_dist", "c_dist"] |
| |
18 |
| |
19 const FILES_COLUMNS = Dict( |
| |
20 "aux_radon_fb_log.txt" => LOG_COLS, |
| |
21 "aux_radon_sliding_fb_log.txt" => LOG_COLS, |
| |
22 "aux2_radon_fb_log.txt" => LOG_COLS, |
| |
23 "aux2_radon_sliding_fb_log.txt" => LOG_COLS, |
| |
24 "aux_radon_fb_aux.csv" => AUX_COLS, |
| |
25 "aux_radon_sliding_fb_aux.csv" => AUX_COLS, |
| |
26 "aux2_radon_fb_aux.csv" => AUX_COLS, |
| |
27 "aux2_radon_sliding_fb_aux.csv" => AUX_COLS, |
| |
28 ) |
| |
29 |
| |
30 const ITER_COL = :iter |
| |
31 const CONF_LEVEL = 0.95 |
| |
32 |
| |
33 # -------------------------------------------------------------------------- |
| |
34 # Compute the half-width of a 95% (or CONF_LEVEL) confidence interval for |
| |
35 # the mean of `values`, using the t-distribution. Falls back gracefully for |
| |
36 # degenerate sample sizes. |
| |
37 # -------------------------------------------------------------------------- |
| |
38 function conf_margin(values::AbstractVector{<:Real}) |
| |
39 n = length(values) |
| |
40 if n < 2 |
| |
41 return 0.0 |
| |
42 end |
| |
43 s = std(values) |
| |
44 if s == 0 |
| |
45 return 0.0 |
| |
46 end |
| |
47 tdist = TDist(n - 1) |
| |
48 tcrit = quantile(tdist, 1 - (1 - CONF_LEVEL) / 2) |
| |
49 return tcrit * s / sqrt(n) |
| |
50 end |
| |
51 |
| |
52 # -------------------------------------------------------------------------- |
| |
53 # Read one configured file from one parameter directory, if it exists. |
| |
54 # Returns a DataFrame with at least `iter` and the configured columns, or |
| |
55 # `nothing` if the file is missing or unreadable. |
| |
56 # -------------------------------------------------------------------------- |
| |
57 function read_dir_file(dir::AbstractString, filename::AbstractString, cols::Vector{String}) |
| |
58 path = joinpath(dir, filename) |
| |
59 if !isfile(path) |
| |
60 @warn "Missing file, skipping" path |
| |
61 return nothing |
| |
62 end |
| |
63 |
| |
64 local df |
| |
65 try |
| |
66 df = CSV.read(path, DataFrame) |
| |
67 catch e |
| |
68 @warn "Could not read CSV, skipping" path exception = e |
| |
69 return nothing |
| |
70 end |
| |
71 |
| |
72 if !hasproperty(df, ITER_COL) |
| |
73 @warn "File has no '$(ITER_COL)' column, skipping" path |
| |
74 return nothing |
| |
75 end |
| |
76 |
| |
77 present_cols = String[] |
| |
78 for c in cols |
| |
79 if hasproperty(df, Symbol(c)) |
| |
80 push!(present_cols, c) |
| |
81 else |
| |
82 @warn "Column '$c' not found in file, skipping that column for this directory" path |
| |
83 end |
| |
84 end |
| |
85 if isempty(present_cols) |
| |
86 return nothing |
| |
87 end |
| |
88 |
| |
89 keep = vcat([ITER_COL], Symbol.(present_cols)) |
| |
90 return df[:, keep] |
| |
91 end |
| |
92 |
| |
93 # -------------------------------------------------------------------------- |
| |
94 # Build the summary DataFrame for one configured file, across all `dirs`. |
| |
95 # -------------------------------------------------------------------------- |
| |
96 function summarize_file(dirs::Vector{String}, filename::AbstractString, cols::Vector{String}) |
| |
97 parts = DataFrame[] |
| |
98 for dir in dirs |
| |
99 d = read_dir_file(dir, filename, cols) |
| |
100 d === nothing || push!(parts, d) |
| |
101 end |
| |
102 |
| |
103 if isempty(parts) |
| |
104 @warn "No data found for file across any directory, skipping summary" filename |
| |
105 return nothing |
| |
106 end |
| |
107 |
| |
108 combined = vcat(parts...; cols=:union) |
| |
109 grouped = groupby(combined, ITER_COL) |
| |
110 |
| |
111 iters = sort(collect(keys(grouped))) |
| |
112 summary = DataFrame(iter=[k[ITER_COL] for k in iters]) |
| |
113 |
| |
114 for col in cols |
| |
115 colsym = Symbol(col) |
| |
116 mins = Vector{Union{Missing,Float64}}(undef, length(iters)) |
| |
117 maxs = Vector{Union{Missing,Float64}}(undef, length(iters)) |
| |
118 means = Vector{Union{Missing,Float64}}(undef, length(iters)) |
| |
119 confmin = Vector{Union{Missing,Float64}}(undef, length(iters)) |
| |
120 confmax = Vector{Union{Missing,Float64}}(undef, length(iters)) |
| |
121 |
| |
122 for (i, key) in enumerate(iters) |
| |
123 sub = grouped[key] |
| |
124 if !hasproperty(sub, colsym) |
| |
125 mins[i] = maxs[i] = means[i] = confmin[i] = confmax[i] = missing |
| |
126 continue |
| |
127 end |
| |
128 vals = collect(skipmissing(sub[!, colsym])) |
| |
129 if isempty(vals) |
| |
130 mins[i] = maxs[i] = means[i] = confmin[i] = confmax[i] = missing |
| |
131 continue |
| |
132 end |
| |
133 m = mean(vals) |
| |
134 margin = conf_margin(vals) |
| |
135 mins[i] = minimum(vals) |
| |
136 maxs[i] = maximum(vals) |
| |
137 means[i] = m |
| |
138 confmin[i] = m - margin |
| |
139 confmax[i] = m + margin |
| |
140 end |
| |
141 |
| |
142 summary[!, Symbol(col, "_min")] = mins |
| |
143 summary[!, Symbol(col, "_max")] = maxs |
| |
144 summary[!, Symbol(col, "_confmax")] = confmax |
| |
145 summary[!, Symbol(col, "_confmin")] = confmin |
| |
146 summary[!, Symbol(col, "_mean")] = means |
| |
147 end |
| |
148 |
| |
149 return summary |
| |
150 end |
| |
151 |
| |
152 # -------------------------------------------------------------------------- |
| |
153 # Main |
| |
154 # -------------------------------------------------------------------------- |
| |
155 function main() |
| |
156 if isempty(ARGS) |
| |
157 println(stderr, "Usage: julia summarize.jl DIR1 [DIR2 DIR3 ...]") |
| |
158 exit(1) |
| |
159 end |
| |
160 |
| |
161 dirs = String[] |
| |
162 for d in ARGS |
| |
163 if isdir(d) |
| |
164 push!(dirs, d) |
| |
165 else |
| |
166 @warn "Not a directory, skipping" d |
| |
167 end |
| |
168 end |
| |
169 |
| |
170 if isempty(dirs) |
| |
171 println(stderr, "No valid directories given.") |
| |
172 exit(1) |
| |
173 end |
| |
174 |
| |
175 for (filename, cols) in FILES_COLUMNS |
| |
176 summary = summarize_file(dirs, filename, cols) |
| |
177 summary === nothing && continue |
| |
178 |
| |
179 stem = first(splitext(filename)) |
| |
180 outpath = joinpath(pwd(), "$(stem)_summary.csv") |
| |
181 CSV.write(outpath, summary) |
| |
182 println("Wrote $outpath ($(nrow(summary)) rows)") |
| |
183 end |
| |
184 end |
| |
185 |
| |
186 main() |