stats/summarise.jl

changeset 13
2cb7f20654e6
child 16
841a0d255db3
equal deleted inserted replaced
12:a0d61ac3719e 13:2cb7f20654e6
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()

mercurial