Skip to content

Commit 7115e78

Browse files
committed
Update functions computing bias/RMSE to use mask
All functions that compute bias or RMSE now have a mask parameter which take in `apply_landmask` or `apply_oceanmask`.
1 parent 20ada8c commit 7115e78

File tree

4 files changed

+194
-32
lines changed

4 files changed

+194
-32
lines changed

NEWS.md

+20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
ClimaAnalysis.jl Release Notes
22
===============================
3+
v0.5.11
4+
-------
5+
6+
## Features
7+
8+
### Applying a land/sea mask to `OutputVar`s
9+
10+
There is now support to applying a land or sea mask to `OutputVar`s though
11+
`apply_landmask(var)` and `apply_oceanmask(var)` respectively. A land mask sets all the
12+
coordinates corresponding to land to zeros in the data of the `OutputVar` and a sea mask
13+
sets all the coordinates corresponding to ocean to zeros in the data of the `OutputVar`.
14+
Furthermore, the parameter `mask` is added to the functions `bias`, `global_bias`,
15+
`squared_error`,`global_mse`, and `global_rmse` which takes either `apply_landmask` and
16+
`apply_oceanmask`. This is useful for computing these quantities only over the land or sea.
17+
18+
```julia
19+
var_no_land = ClimaAnalysis.apply_landmask(var)
20+
var_no_ocean = ClimaAnalysis.apply_oceanmask(var)
21+
```
22+
323
v0.5.10
424
-------
525

docs/src/var.md

+17-1
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ longitude and latitude. Furthermore, units must be supplied for data and dimensi
226226
and `obs` and the units for longitude and latitude should be degrees.
227227

228228
Consider the following example, where we compute the bias and RMSE between our simulation
229-
and some observations stored in "ta\_1d\_average.nc".
229+
and some observations stored in `ta_1d_average.nc`.
230230

231231
```@julia bias_and_mse
232232
julia> obs_var = OutputVar("ta_1d_average.nc"); # load in observational data
@@ -255,3 +255,19 @@ julia> global_rmse(sim, obs)
255255
julia> units(se_var)
256256
"K^2"
257257
```
258+
259+
### Masking
260+
Bias and squared error can be computed only over the land or ocean through the `mask` parameter.
261+
As of now, the mask parameter takes in `apply_oceanmask` or `apply_oceanmask`. See the
262+
example below of this usage.
263+
264+
```julia
265+
# Do not consider the ocean when computing the bias
266+
ClimaAnalysis.bias(sim_var, obs_var; mask = apply_oceanmask)
267+
ClimaAnalysis.global_bias(sim_var, obs_var; mask = apply_oceanmask)
268+
269+
# Do not consider the land when computing the squared error
270+
ClimaAnalysis.squared_error(sim_var, obs_var; mask = apply_landmask)
271+
ClimaAnalysis.global_mse(sim_var, obs_var; mask = apply_landmask)
272+
ClimaAnalysis.global_rmse(sim_var, obs_var; mask = apply_landmask)
273+
```

src/Var.jl

+46-31
Original file line numberDiff line numberDiff line change
@@ -1163,7 +1163,7 @@ function _check_sim_obs_units_consistent(sim::OutputVar, obs::OutputVar)
11631163
end
11641164

11651165
"""
1166-
bias(sim::OutputVar, obs::OutputVar)
1166+
bias(sim::OutputVar, obs::OutputVar; mask = nothing)
11671167
11681168
Return a `OutputVar` whose data is the bias (`sim.data - obs.data`) and compute the global
11691169
bias of `data` in `sim` and `obs` over longitude and latitude. The result is stored in
@@ -1175,10 +1175,13 @@ for longitude and latitude should be degrees. Resampling is done automatically b
11751175
`obs` on `sim`. Attributes in `sim` and `obs` will be thrown away. The long name and short
11761176
name of the returned `OutputVar` will be updated to reflect that a bias is computed.
11771177
1178+
The parameter `mask` is a function that masks a `OutputVar`. See [`apply_landmask`](@ref)
1179+
and [`apply_oceanmask`](@ref).
1180+
11781181
See also [`global_bias`](@ref), [`squared_error`](@ref), [`global_mse`](@ref),
11791182
[`global_rmse`](@ref).
11801183
"""
1181-
function bias(sim::OutputVar, obs::OutputVar)
1184+
function bias(sim::OutputVar, obs::OutputVar; mask = nothing)
11821185
_check_sim_obs_units_consistent(sim, obs)
11831186

11841187
# Resample obs on sim to ensure the size of data in sim and obs are the same and the
@@ -1203,24 +1206,24 @@ function bias(sim::OutputVar, obs::OutputVar)
12031206
end
12041207

12051208
# Compute global bias and store it as an attribute
1209+
!isnothing(mask) && (bias = mask(bias))
12061210
integrated_bias = integrate_lonlat(bias).data
1207-
normalization =
1208-
integrate_lonlat(
1209-
OutputVar(
1210-
bias.attributes,
1211-
bias.dims,
1212-
bias.dim_attributes,
1213-
ones(size(bias.data)),
1214-
),
1215-
).data
1211+
ones_var = OutputVar(
1212+
bias.attributes,
1213+
bias.dims,
1214+
bias.dim_attributes,
1215+
ones(size(bias.data)),
1216+
)
1217+
!isnothing(mask) && (ones_var = mask(ones_var))
1218+
normalization = integrate_lonlat(ones_var).data
12161219
# Do ./ instead of / because we are dividing between zero dimensional arrays
12171220
global_bias = integrated_bias ./ normalization
12181221
ret_attributes["global_bias"] = global_bias
12191222
return OutputVar(ret_attributes, bias.dims, bias.dim_attributes, bias.data)
12201223
end
12211224

12221225
"""
1223-
global_bias(sim::OutputVar, obs::OutputVar)
1226+
global_bias(sim::OutputVar, obs::OutputVar; mask = nothing)
12241227
12251228
Return the global bias of `data` in `sim` and `obs` over longitude and latitude.
12261229
@@ -1229,16 +1232,19 @@ longitude and latitude. Units must be supplied for data and dimensions in `sim`
12291232
The units for longitude and latitude should be degrees. Resampling is done automatically by
12301233
resampling `obs` on `sim`.
12311234
1235+
The parameter `mask` is a function that masks a `OutputVar`. See [`apply_landmask`](@ref)
1236+
and [`apply_oceanmask`](@ref).
1237+
12321238
See also [`bias`](@ref), [`squared_error`](@ref), [`global_mse`](@ref),
12331239
[`global_rmse`](@ref).
12341240
"""
1235-
function global_bias(sim::OutputVar, obs::OutputVar)
1236-
bias_var = bias(sim, obs)
1241+
function global_bias(sim::OutputVar, obs::OutputVar; mask = nothing)
1242+
bias_var = bias(sim, obs, mask = mask)
12371243
return bias_var.attributes["global_bias"]
12381244
end
12391245

12401246
"""
1241-
squared_error(sim::OutputVar, obs::OutputVar)
1247+
squared_error(sim::OutputVar, obs::OutputVar; mask = nothing)
12421248
12431249
Return a `OutputVar` whose data is the squared error (`(sim.data - obs.data)^2`) and compute
12441250
the global mean squared error (MSE) and global root mean squared error (RMSE) of `data` in
@@ -1251,9 +1257,12 @@ for longitude and latitude should be degrees. Resampling is done automatically b
12511257
`obs` on `sim`. Attributes in `sim` and `obs` will be thrown away. The long name and short
12521258
name of the returned `OutputVar` will be updated to reflect that a squared error is computed.
12531259
1260+
The parameter `mask` is a function that masks a `OutputVar`. See [`apply_landmask`](@ref)
1261+
and [`apply_oceanmask`](@ref).
1262+
12541263
See also [`global_mse`](@ref), [`global_rmse`](@ref), [`bias`](@ref), [`global_bias`](@ref).
12551264
"""
1256-
function squared_error(sim::OutputVar, obs::OutputVar)
1265+
function squared_error(sim::OutputVar, obs::OutputVar; mask = nothing)
12571266
_check_sim_obs_units_consistent(sim, obs)
12581267

12591268
# Resample obs on sim to ensure the size of data in sim and obs are the same and the
@@ -1282,16 +1291,16 @@ function squared_error(sim::OutputVar, obs::OutputVar)
12821291
end
12831292

12841293
# Compute global mse and global rmse and store it as an attribute
1294+
!isnothing(mask) && (squared_error = mask(squared_error))
12851295
integrated_squared_error = integrate_lonlat(squared_error).data
1286-
normalization =
1287-
integrate_lonlat(
1288-
OutputVar(
1289-
squared_error.attributes,
1290-
squared_error.dims,
1291-
squared_error.dim_attributes,
1292-
ones(size(squared_error.data)),
1293-
),
1294-
).data
1296+
ones_var = OutputVar(
1297+
squared_error.attributes,
1298+
squared_error.dims,
1299+
squared_error.dim_attributes,
1300+
ones(size(squared_error.data)),
1301+
)
1302+
!isnothing(mask) && (ones_var = mask(ones_var))
1303+
normalization = integrate_lonlat(ones_var).data
12951304
# Do ./ instead of / because we are dividing between zero dimensional arrays
12961305
mse = integrated_squared_error ./ normalization
12971306
ret_attributes["global_mse"] = mse
@@ -1305,7 +1314,7 @@ function squared_error(sim::OutputVar, obs::OutputVar)
13051314
end
13061315

13071316
"""
1308-
global_mse(sim::OutputVar, obs::OutputVar)
1317+
global_mse(sim::OutputVar, obs::OutputVar; mask = nothing)
13091318
13101319
Return the global mean squared error (MSE) of `data` in `sim` and `obs` over longitude and
13111320
latitude.
@@ -1315,15 +1324,18 @@ and latitude. Units must be supplied for data and dimensions in `sim` and `obs`.
13151324
for longitude and latitude should be degrees. Resampling is done automatically by resampling
13161325
`obs` on `sim`.
13171326
1327+
The parameter `mask` is a function that masks a `OutputVar`. See [`apply_landmask`](@ref)
1328+
and [`apply_oceanmask`](@ref).
1329+
13181330
See also [`squared_error`](@ref), [`global_rmse`](@ref), [`bias`](@ref), [`global_bias`](@ref).
13191331
"""
1320-
function global_mse(sim::OutputVar, obs::OutputVar)
1321-
squared_error_var = squared_error(sim, obs)
1332+
function global_mse(sim::OutputVar, obs::OutputVar; mask = nothing)
1333+
squared_error_var = squared_error(sim, obs, mask = mask)
13221334
return squared_error_var.attributes["global_mse"]
13231335
end
13241336

13251337
"""
1326-
global_rmse(sim::OutputVar, obs::OutputVar)
1338+
global_rmse(sim::OutputVar, obs::OutputVar; mask = nothing)
13271339
13281340
Return the global root mean squared error (RMSE) of `data` in `sim` and `obs` over longitude
13291341
and latitude.
@@ -1333,10 +1345,13 @@ and latitude. Units must be supplied for data and dimensions in `sim` and `obs`.
13331345
for longitude and latitude should be degrees. Resampling is done automatically by resampling
13341346
`obs` on `sim`.
13351347
1348+
The parameter `mask` is a function that masks a `OutputVar`. See [`apply_landmask`](@ref)
1349+
and [`apply_oceanmask`](@ref).
1350+
13361351
See also [`squared_error`](@ref), [`global_mse`](@ref), [`bias`](@ref), [`global_bias`](@ref).
13371352
"""
1338-
function global_rmse(sim::OutputVar, obs::OutputVar)
1339-
squared_error_var = squared_error(sim, obs)
1353+
function global_rmse(sim::OutputVar, obs::OutputVar; mask = nothing)
1354+
squared_error_var = squared_error(sim, obs, mask = mask)
13401355
return squared_error_var.attributes["global_rmse"]
13411356
end
13421357

test/test_Var.jl

+111
Original file line numberDiff line numberDiff line change
@@ -1566,3 +1566,114 @@ end
15661566
@test_throws ErrorException ClimaAnalysis.apply_landmask(var)
15671567
@test_throws ErrorException ClimaAnalysis.apply_oceanmask(var)
15681568
end
1569+
1570+
@testset "Bias and RMSE with masks" begin
1571+
# Test bias and global_bias
1572+
land_var = ClimaAnalysis.OutputVar(ClimaAnalysis.Var.LAND_MASK)
1573+
ocean_var = ClimaAnalysis.OutputVar(ClimaAnalysis.Var.OCEAN_MASK)
1574+
data_zero = zeros(land_var.data |> size)
1575+
zero_var = ClimaAnalysis.OutputVar(
1576+
land_var.attributes,
1577+
land_var.dims,
1578+
land_var.dim_attributes,
1579+
data_zero,
1580+
)
1581+
1582+
# Trim data because periodic boundary condition on the edges
1583+
@test ClimaAnalysis.bias(
1584+
land_var,
1585+
zero_var,
1586+
mask = ClimaAnalysis.apply_oceanmask,
1587+
).data[
1588+
begin:(end - 1),
1589+
:,
1590+
] == data_zero[begin:(end - 1), :]
1591+
@test ClimaAnalysis.bias(
1592+
ocean_var,
1593+
zero_var,
1594+
mask = ClimaAnalysis.apply_landmask,
1595+
).data[
1596+
begin:(end - 1),
1597+
:,
1598+
] == data_zero[begin:(end - 1), :]
1599+
1600+
# Not exactly zero because of the periodic boundary condition on the edges
1601+
# which results in some ones in the data
1602+
@test isapprox(
1603+
ClimaAnalysis.global_bias(
1604+
land_var,
1605+
zero_var,
1606+
mask = ClimaAnalysis.apply_oceanmask,
1607+
),
1608+
0.0,
1609+
atol = 1e-5,
1610+
)
1611+
@test isapprox(
1612+
ClimaAnalysis.global_bias(
1613+
ocean_var,
1614+
zero_var,
1615+
mask = ClimaAnalysis.apply_landmask,
1616+
),
1617+
0.0,
1618+
atol = 1e-5,
1619+
)
1620+
1621+
# Test squared error, global_mse, and global_rmse
1622+
# Trim data because periodic boundary condition on the edges
1623+
@test ClimaAnalysis.squared_error(
1624+
land_var,
1625+
zero_var,
1626+
mask = ClimaAnalysis.apply_oceanmask,
1627+
).data[
1628+
begin:(end - 1),
1629+
:,
1630+
] == data_zero[begin:(end - 1), :]
1631+
@test ClimaAnalysis.squared_error(
1632+
ocean_var,
1633+
zero_var,
1634+
mask = ClimaAnalysis.apply_landmask,
1635+
).data[
1636+
begin:(end - 1),
1637+
:,
1638+
] == data_zero[begin:(end - 1), :]
1639+
1640+
# Not exactly zero because of the periodic boundary condition on the edges
1641+
# which results in some ones in the data
1642+
@test isapprox(
1643+
ClimaAnalysis.global_mse(
1644+
land_var,
1645+
zero_var,
1646+
mask = ClimaAnalysis.apply_oceanmask,
1647+
),
1648+
0.0,
1649+
atol = 1e-5,
1650+
)
1651+
@test isapprox(
1652+
ClimaAnalysis.global_mse(
1653+
ocean_var,
1654+
zero_var,
1655+
mask = ClimaAnalysis.apply_landmask,
1656+
),
1657+
0.0,
1658+
atol = 1e-5,
1659+
)
1660+
1661+
@test isapprox(
1662+
ClimaAnalysis.global_rmse(
1663+
land_var,
1664+
zero_var,
1665+
mask = ClimaAnalysis.apply_oceanmask,
1666+
),
1667+
0.0,
1668+
atol = 10^(-2.5),
1669+
)
1670+
@test isapprox(
1671+
ClimaAnalysis.global_rmse(
1672+
ocean_var,
1673+
zero_var,
1674+
mask = ClimaAnalysis.apply_landmask,
1675+
),
1676+
0.0,
1677+
atol = 10^(-2.5),
1678+
)
1679+
end

0 commit comments

Comments
 (0)