• Thibault Hallouin's avatar
    allow masking conditions to be specified on predictions · f31664dd
    Thibault Hallouin authored
    An earlier implementation of the masking conditions assumed that the
    conditions on streamflow would only be on the observations, but this is
    not always the case. For example, reliability scores cannot be done on
    the observed streamflow and need to be performed on the predicted
    streamflow. So this is now possible as the condition syntax is changed
    and now *q_obs*/*q_prd_median*/*q_prd_mean* in place of *q*.
    f31664dd
test_determinist.cpp 10.51 KiB
#include <fstream>
#include <vector>
#include <array>
#include <gtest/gtest.h>
#include <xtensor/xtensor.hpp>
#include <xtensor/xview.hpp>
#include <xtensor/xmanipulation.hpp>
#include <xtensor/xcsv.hpp>
#include "evalhyd/evald.hpp"
using namespace xt::placeholders;  // required for `_` to work
TEST(DeterministTests, TestMetrics2D)
    // read in data
    std::ifstream ifs;
    ifs.open("./data/q_obs.csv");
    xt::xtensor<double, 2> observed =xt::load_csv<int>(ifs);
    ifs.close();
    ifs.open("./data/q_prd.csv");
    xt::xtensor<double, 2> predicted = xt::view(
            xt::load_csv<double>(ifs), xt::range(0, 5), xt::all()
    ifs.close();
    // compute scores (with 2D tensors)
    std::vector<xt::xarray<double>> metrics =
            evalhyd::evald<2>(
                    observed, predicted, {"RMSE", "NSE", "KGE", "KGEPRIME"}
    // check results on all metrics
    xt::xtensor<double, 2> rmse =
            {{777.034272},
             {776.878479},
             {777.800217},
             {778.151082},
             {778.61487}};
    EXPECT_TRUE(xt::allclose(metrics[0], rmse));
    xt::xtensor<double, 2> nse =
            {{0.718912},
             {0.719025},
             {0.718358},
             {0.718104},
             {0.717767}};
    EXPECT_TRUE(xt::allclose(metrics[1], nse));
    xt::xtensor<double, 2> kge =
            {{0.748088},
             {0.746106},
             {0.744111},
             {0.743011},
             {0.741768}};
    EXPECT_TRUE(xt::allclose(metrics[2], kge));
    xt::xtensor<double, 2> kgeprime =
            {{0.813141},
             {0.812775},
             {0.812032},
             {0.811787},
             {0.811387}};
    EXPECT_TRUE(xt::allclose(metrics[3], kgeprime));
TEST(DeterministTests, TestMetrics1D)
    // read in data
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
std::ifstream ifs; ifs.open("./data/q_obs.csv"); xt::xtensor<double, 1> observed = xt::squeeze(xt::load_csv<int>(ifs)); ifs.close(); ifs.open("./data/q_prd.csv"); xt::xtensor<double, 1> predicted = xt::row( xt::view(xt::load_csv<double>(ifs), xt::range(0, 5), xt::all()), 0 ); ifs.close(); // compute scores (with 1D tensors) std::vector<xt::xarray<double>> metrics = evalhyd::evald<1>( observed, predicted, {"RMSE", "NSE", "KGE", "KGEPRIME"} ); // check results on all metrics xt::xtensor<double, 1> rmse = {777.034272}; EXPECT_TRUE(xt::allclose(metrics[0], rmse)); xt::xtensor<double, 1> nse = {0.718912}; EXPECT_TRUE(xt::allclose(metrics[1], nse)); xt::xtensor<double, 1> kge = {0.748088}; EXPECT_TRUE(xt::allclose(metrics[2], kge)); xt::xtensor<double, 1> kgeprime = {0.813141}; EXPECT_TRUE(xt::allclose(metrics[3], kgeprime)); } TEST(DeterministTests, TestTransform) { // read in data std::ifstream ifs; ifs.open("./data/q_obs.csv"); xt::xtensor<double, 2> observed =xt::load_csv<int>(ifs); ifs.close(); ifs.open("./data/q_prd.csv"); xt::xtensor<double, 2> predicted = xt::view( xt::load_csv<double>(ifs), xt::range(0, 5), xt::all() ); ifs.close(); // compute and check results on square-rooted streamflow series std::vector<xt::xarray<double>> metrics = evalhyd::evald<2>(observed, predicted, {"NSE"}, "sqrt"); xt::xtensor<double, 2> nse_sqrt = {{0.882817}, {0.883023}, {0.883019}, {0.883029}, {0.882972}}; EXPECT_TRUE(xt::allclose(metrics[0], nse_sqrt)); // compute and check results on inverted streamflow series metrics = evalhyd::evald<2>(observed, predicted, {"NSE"}, "inv"); xt::xtensor<double, 2> nse_inv = {{0.737323}, {0.737404}, {0.737429}, {0.737546}, {0.737595}}; EXPECT_TRUE(xt::allclose(metrics[0], nse_inv)); // compute and check results on square-rooted streamflow series metrics = evalhyd::evald<2>(observed, predicted, {"NSE"}, "log");
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
xt::xtensor<double, 2> nse_log = {{0.893344}, {0.893523}, {0.893585}, {0.893758}, {0.893793}}; EXPECT_TRUE(xt::allclose(metrics[0], nse_log)); // compute and check results on power-transformed streamflow series metrics = evalhyd::evald<2>(observed, predicted, {"NSE"}, "pow", 0.2); xt::xtensor<double, 2> nse_pow = {{0.899207}, {0.899395}, {0.899451}, {0.899578}, {0.899588}}; EXPECT_TRUE(xt::allclose(metrics[0], nse_pow)); } TEST(DeterministTests, TestMasks) { // read in data std::ifstream ifs; ifs.open("./data/q_obs.csv"); xt::xtensor<double, 2> observed = xt::load_csv<int>(ifs); ifs.close(); ifs.open("./data/q_prd.csv"); xt::xtensor<double, 2> predicted = xt::load_csv<double>(ifs); ifs.close(); // generate temporal subset by dropping 20 first time steps xt::xtensor<double, 2> masks = xt::ones<bool>({std::size_t {1}, std::size_t {observed.size()}}); xt::view(masks, 0, xt::range(0, 20)) = 0; // compute scores using masks to subset whole record std::vector<std::string> metrics = {"RMSE", "NSE", "KGE", "KGEPRIME"}; std::vector<xt::xarray<double>> metrics_masked = evalhyd::evald<2>(observed, predicted, metrics, "none", 1, -9, masks); // compute scores on pre-computed subset of whole record xt::xtensor<double, 2> obs = xt::view(observed, xt::all(), xt::range(20, _)); xt::xtensor<double, 2> prd = xt::view(predicted, xt::all(), xt::range(20, _)); std::vector<xt::xarray<double>> metrics_subset = evalhyd::evald<2>(obs, prd, metrics); // check results are identical for (int m = 0; m < metrics.size(); m++) { EXPECT_TRUE(xt::allclose(metrics_masked[m], metrics_subset[m])) << "Failure for (" << metrics[m] << ")"; } } TEST(DeterministTests, TestMaskingConditions) { std::vector<std::string> metrics = {"RMSE", "NSE", "KGE", "KGEPRIME"}; // read in data std::ifstream ifs; ifs.open("./data/q_obs.csv"); xt::xtensor<double, 2> observed = xt::load_csv<int>(ifs);
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
ifs.close(); ifs.open("./data/q_prd.csv"); xt::xtensor<double, 2> predicted = xt::load_csv<double>(ifs); ifs.close(); // generate dummy empty masks required to access next optional argument xt::xtensor<bool, 2> masks; // conditions on streamflow ________________________________________________ // compute scores using masking conditions on streamflow to subset whole record xt::xtensor<std::array<char, 32>, 2> q_conditions ={ {{"q_obs{<2000,>3000}"}} }; std::vector<xt::xarray<double>> metrics_q_conditioned = evalhyd::evald<2>( observed, predicted, metrics, "none", 1, -9, masks, q_conditions ); // compute scores using "NaN-ed" time indices where conditions on streamflow met std::vector<xt::xarray<double>> metrics_q_preconditioned = evalhyd::evald<2>( xt::where((observed < 2000) | (observed > 3000), observed, NAN), predicted, metrics ); // check results are identical for (int m = 0; m < metrics.size(); m++) { EXPECT_TRUE( xt::allclose( metrics_q_conditioned[m], metrics_q_preconditioned[m] ) ) << "Failure for (" << metrics[m] << ")"; } // conditions on temporal indices __________________________________________ // compute scores using masking conditions on time indices to subset whole record xt::xtensor<std::array<char, 32>, 2> t_conditions = { {{"t{0,1,2,3,4,5:97,97,98,99}"}} }; std::vector<xt::xarray<double>> metrics_t_conditioned = evalhyd::evald<2>( observed, predicted, metrics, "none", 1, -9, masks, t_conditions ); // compute scores on already subset time series std::vector<xt::xarray<double>> metrics_t_subset = evalhyd::evald<2>( xt::view(observed, xt::all(), xt::range(0, 100)), xt::view(predicted, xt::all(), xt::range(0, 100)), metrics ); // check results are identical for (int m = 0; m < metrics.size(); m++) { EXPECT_TRUE( xt::allclose( metrics_t_conditioned[m], metrics_t_subset[m] ) ) << "Failure for (" << metrics[m] << ")"; }
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
} TEST(DeterministTests, TestMissingData) { std::vector<std::string> metrics = {"RMSE", "NSE", "KGE", "KGEPRIME"}; // read in data std::ifstream ifs; ifs.open("./data/q_obs.csv"); xt::xtensor<double, 2> observed = xt::load_csv<int>(ifs); ifs.close(); ifs.open("./data/q_prd.csv"); xt::xtensor<double, 2> predicted = xt::view( xt::load_csv<double>(ifs), xt::range(0, 5), xt::all() ); ifs.close(); // add some missing observations artificially by assigning NaN values xt::view(observed, xt::all(), xt::range(0, 20)) = NAN; // add some missing predictions artificially by assigning NaN values xt::view(observed, 0, xt::range(20, 23)) = NAN; xt::view(observed, 1, xt::range(20, 26)) = NAN; xt::view(observed, 2, xt::range(20, 29)) = NAN; xt::view(observed, 3, xt::range(20, 32)) = NAN; xt::view(observed, 4, xt::range(20, 35)) = NAN; // compute metrics with observations containing NaN values std::vector<xt::xarray<double>> metrics_nan = evalhyd::evald<2>(observed, predicted, metrics); for (int m = 0; m < metrics.size(); m++) { for (int p = 0; p < predicted.shape(0); p++) { // compute metrics on subset of observations and predictions (i.e. // eliminating region with NaN in observations or predictions manually) xt::xtensor<double, 1> obs = xt::view(observed, 0, xt::range(20+(3*(p+1)), _)); xt::xtensor<double, 1> prd = xt::view(predicted, p, xt::range(20+(3*(p+1)), _)); std::vector<xt::xarray<double>> metrics_sbs = evalhyd::evald<1>(obs, prd, {metrics[m]}); // compare to check results are the same EXPECT_TRUE( xt::allclose( xt::view(metrics_nan[m], p), metrics_sbs[0] ) ) << "Failure for (" << metrics[m] << ")"; } } }