Skip to content

Commit 106b205

Browse files
committed
Add indexing to RMSEVariable
This commit adds indexing to the `RMSEVariable`. In other words, the struct behaves like an array. We allow indexing by both integers and strings. We also made the decision to support indexing using only a single string. For instance, `rmse_var["model_name"]` will return the row of RMSEs across the different categories for that model. However, we do not support `rmse_var[1]` because it is unclear whether this should return a row (similar to the string case) or the first value of the entry (similar to if the struct behaves like an array).
1 parent 615ca82 commit 106b205

File tree

5 files changed

+185
-1
lines changed

5 files changed

+185
-1
lines changed

NEWS.md

+12
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,18 @@ rmse_var = ClimaAnalysis.read_rmses(
343343
)
344344
```
345345

346+
#### Indexing
347+
`RMSEVariable` supports indexing by integer or string. See the example for indexing into
348+
a `RMSEVariable`.
349+
350+
```julia
351+
rmse_var["ACCESS-CM2"]
352+
rmse_var[:, "MAM"]
353+
rmse_var["ACCESS-CM2", ["ANN", "DJF", "MAM"]]
354+
rmse_var[2,5] = 11.2
355+
rmse_var[:, :]
356+
```
357+
346358
## Bug fixes
347359

348360
- Increased the default value for `warp_string` to 72.

docs/src/api.md

+4
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ Leaderboard.model_names
8181
Leaderboard.category_names
8282
Leaderboard.rmse_units
8383
Leaderboard.read_rmses
84+
Base.getindex(rmse_var::RMSEVariable, model_name, category)
85+
Base.getindex(rmse_var::RMSEVariable, model_name::String)
86+
Base.setindex!(rmse_var::RMSEVariable, rmse, model_name, category)
87+
Base.setindex!(rmse_var::RMSEVariable, rmse, model_name::String)
8488
```
8589

8690
## Utilities

docs/src/rmse_var.md

+17
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,21 @@ rmse_var = ClimaAnalysis.read_rmses(
9393
)
9494
9595
nothing # hide
96+
```
97+
98+
## Indexing
99+
100+
After loading the data, one may want to inspect, change, or manipulate the data. This is
101+
possible by the indexing functionality that `RMSEVariable` provides. Indexing into a
102+
`RMSEVariable` is similar, but not the same as indexing into an array. Indexing by
103+
integer or string is supported, but linear indexing (e.g. `rmse_var[1]`) is not supported.
104+
integer or string is supported, but linear indexing (e.g., `rmse_var[1]`) is not supported.
105+
106+
```@repl rmse_var
107+
rmse_var[:, :]
108+
rmse_var["ACCESS-CM2"]
109+
rmse_var[:, "MAM"]
110+
rmse_var["ACCESS-CM2", ["ANN", "DJF", "MAM"]]
111+
rmse_var[2,5] = 11.2;
112+
rmse_var[:, :]
96113
```

src/Leaderboard.jl

+119-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ export RMSEVariable,
77
model_names,
88
category_names,
99
rmse_units,
10-
read_rmses
10+
read_rmses,
11+
getindex,
12+
setindex!
1113

1214
"""
1315
Holding root mean squared errors over multiple categories and models for a single
@@ -304,4 +306,120 @@ function read_rmses(csv_file::String, short_name::String; units = nothing)
304306
)
305307
end
306308

309+
"""
310+
function _index_convert(key2index, key::Colon)
311+
312+
Convert the symbol colon into an index for indexing.
313+
"""
314+
function _index_convert(key2index, key::Colon)
315+
return collect(values(key2index))
316+
end
317+
318+
"""
319+
function _index_convert(key2index,
320+
indices::AbstractVector{I})
321+
where {I <: Integer}
322+
323+
Convert a string into an index for indexing.
324+
"""
325+
function _index_convert(key2index, key::AbstractString)
326+
!haskey(key2index, key) &&
327+
error("Key ($key) is not present in ($(keys(key2index)))")
328+
return key2index[key]
329+
end
330+
331+
"""
332+
function _index_convert(key2index,
333+
keys::AbstractVector{S})
334+
where {S <: AbstractString}
335+
336+
Convert a vector of strings to indices for indexing.
337+
"""
338+
function _index_convert(
339+
key2index,
340+
keys::AbstractVector{S},
341+
) where {S <: AbstractString}
342+
for key in keys
343+
!haskey(key2index, key) &&
344+
error("Key ($key) is not present in ($(keys(key2index)))")
345+
end
346+
return [key2index[key] for key in keys]
347+
end
348+
349+
"""
350+
function _index_convert(key2index,
351+
indices::AbstractVector{I})
352+
where {I <: Integer}
353+
354+
Convert an integer to an index for indexing.
355+
"""
356+
function _index_convert(key2index, index::Integer)
357+
!(index in values(key2index)) &&
358+
error("Index ($index) is not present in ($(values(key2index)))")
359+
return index
360+
end
361+
362+
"""
363+
function _index_convert(key2index,
364+
indices::AbstractVector{I})
365+
where {I <: Integer}
366+
367+
Convert a vector of integers to indices for indexing.
368+
"""
369+
function _index_convert(
370+
key2index,
371+
indices::AbstractVector{I},
372+
) where {I <: Integer}
373+
for index in indices
374+
!(index in values(key2index)) &&
375+
error("Index ($index) is not present in ($(values(key2index)))")
376+
end
377+
return indices
378+
end
379+
380+
"""
381+
Base.getindex(rmse_var::RMSEVariable, model_name, category)
382+
383+
Return a subset of the array holding the root mean square errors as specified by
384+
`model_name` and `category`. Support indexing by `String` and `Int`.
385+
"""
386+
function Base.getindex(rmse_var::RMSEVariable, model_name, category)
387+
model_idx = _index_convert(rmse_var.model2index, model_name)
388+
cat_idx = _index_convert(rmse_var.category2index, category)
389+
return rmse_var.RMSEs[model_idx, cat_idx]
390+
end
391+
392+
"""
393+
Base.getindex(rmse_var::RMSEVariable, model_name::String)
394+
395+
Return a subset of the array holding the root mean square errors as specified by
396+
`model_name`. Support indexing by `String`. Do not support linear indexing.
397+
"""
398+
function Base.getindex(rmse_var::RMSEVariable, model_name::String)
399+
return rmse_var[model_name, :]
400+
end
401+
402+
"""
403+
Base.setindex!(rmse_var::RMSEVariable, rmse, model_name, category)
404+
405+
Store a value or values from an array in the array of root mean squared errors in
406+
`rmse_var`. Support indexing by `String` and `Int`.
407+
"""
408+
function Base.setindex!(rmse_var::RMSEVariable, rmse, model_name, category)
409+
model_idx = _index_convert(rmse_var.model2index, model_name)
410+
cat_idx = _index_convert(rmse_var.category2index, category)
411+
rmse_var.RMSEs[model_idx, cat_idx] = rmse
412+
end
413+
414+
"""
415+
Base.setindex!(rmse_var::RMSEVariable, rmse, model_name::String)
416+
417+
Store a value or values from an array into the array of root mean squared errors in
418+
`rmse_var`. Support indexing by `String`. Do not support linear indexing.
419+
"""
420+
function Base.setindex!(rmse_var::RMSEVariable, rmse, model_name::String)
421+
model_idx = _index_convert(rmse_var.model2index, model_name)
422+
rmse_var.RMSEs[model_idx, :] = rmse
423+
end
424+
307425
end

test/test_Leaderboard.jl

+33
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,36 @@ end
155155
@test ClimaAnalysis.rmse_units(rmse_var) ==
156156
Dict("ACCESS-CM2" => "m", "ACCESS-ESM1-5" => "m")
157157
end
158+
159+
@testset "Indexing" begin
160+
csv_file_path = joinpath(@__DIR__, "sample_data/test_csv.csv")
161+
rmse_var = ClimaAnalysis.read_rmses(csv_file_path, "ta")
162+
163+
rmse_var[1, 1] = 100.0
164+
@test rmse_var.RMSEs[1, 1] == 100.0
165+
166+
rmse_var[1, 1:3] = [100.0, 110.0, 120.0]
167+
@test rmse_var.RMSEs[1, 1:3] == [100.0, 110.0, 120.0]
168+
169+
rmse_var[1:2, 1] = [100.0, 110.0]'
170+
@test rmse_var.RMSEs[1:2, 1] == [100.0, 110.0]
171+
172+
rmse_var["ACCESS-ESM1-5", "DJF"] = 200.0
173+
@test rmse_var["ACCESS-ESM1-5", "DJF"] == 200.0
174+
175+
rmse_var["ACCESS-ESM1-5", ["DJF", "MAM", "ANN"]] = [200.0, 210.0, 220.0]
176+
@test rmse_var["ACCESS-ESM1-5", [1, 2, 5]] == [200.0, 210.0, 220.0]
177+
178+
rmse_var["ACCESS-ESM1-5"] = [120.0, 130.0, 140.0, 150.0, 160.0]
179+
@test rmse_var["ACCESS-ESM1-5"] == [120.0, 130.0, 140.0, 150.0, 160.0]
180+
181+
# Check error handling
182+
@test_throws ErrorException rmse_var[5, 5] = 100.0
183+
@test_throws ErrorException rmse_var["do not exist"] = 100.0
184+
@test_throws ErrorException rmse_var["do not exist", "test"] = 100.0
185+
@test_throws ErrorException rmse_var["ACCESS-ESM1-5", "test"] = 100.0
186+
@test_throws ErrorException rmse_var["model1", "ANN"] = 100.0
187+
@test_throws DimensionMismatch rmse_var["ACCESS-ESM1-5"] =
188+
[120.0, 130.0, 140.0]
189+
@test_throws DimensionMismatch rmse_var[1, :] = [120.0, 130.0, 140.0]
190+
end

0 commit comments

Comments
 (0)