diff --git a/include/evalhyd/evalp.hpp b/include/evalhyd/evalp.hpp
index 236a8768f1f34d3462bc84d11815779ca7ac130f..9dd828578453084b4b1d3ec07d525844ef953095 100644
--- a/include/evalhyd/evalp.hpp
+++ b/include/evalhyd/evalp.hpp
@@ -23,16 +23,16 @@ namespace evalhyd
     ///
     ///    q_obs: :cpp:`xt::xtensor<double, 2>`
     ///       Streamflow observations.
-    ///       shape: (1, time)
+    ///       shape: (sites, time)
     ///
-    ///    q_prd: :cpp:`xt::xtensor<double, 2>`
+    ///    q_prd: :cpp:`xt::xtensor<double, 4>`
     ///       Streamflow predictions.
-    ///       shape: (members, time)
+    ///       shape: (sites, lead times, members, time)
     ///
     ///    metrics: :cpp:`std::vector<std::string>`
     ///       The sequence of evaluation metrics to be computed.
     ///
-    ///    q_thr: :cpp:`xt::xtensor<double, 1>`, optional
+    ///    q_thr: :cpp:`xt::xtensor<double, 2>`, optional
     ///       Streamflow exceedance threshold(s).
     ///       shape: (thresholds,)
     ///
@@ -43,11 +43,11 @@ namespace evalhyd
     ///       performed and only one set of metrics is returned corresponding
     ///       to the whole time series. If provided, as many sets of metrics are
     ///       returned as they are masks provided.
-    ///       shape: (1+, time)
+    ///       shape: (subsets, time)
     ///
     /// :Returns:
     ///
-    ///    :cpp:`std::vector<xt::xarray<double>>`
+    ///    :cpp:`std::vector<xt::xtensor<double, 5>>`
     ///       The sequence of evaluation metrics computed
     ///       in the same order as given in *metrics*.
     ///
@@ -69,7 +69,7 @@ namespace evalhyd
     /// \endrst
     std::vector<xt::xarray<double>> evalp(
             const xt::xtensor<double, 2>& q_obs,
-            const xt::xtensor<double, 2>& q_prd,
+            const xt::xtensor<double, 4>& q_prd,
             const std::vector<std::string>& metrics,
             const xt::xtensor<double, 1>& q_thr = {},
             const xt::xtensor<bool, 2>& t_msk = {}
@@ -84,8 +84,22 @@ namespace evalhyd
         // check that optional parameters are given as arguments
         eh::utils::check_optionals(metrics, q_thr);
 
-        // instantiate probabilist evaluator
-        eh::probabilist::Evaluator evaluator(q_obs, q_prd, q_thr, t_msk);
+        // retrieve dimensions
+        std::size_t n_sit = q_prd.shape(0);
+        std::size_t n_ltm = q_prd.shape(1);
+        std::size_t n_mbr = q_prd.shape(2);
+        std::size_t n_thr = q_thr.size();
+        std::size_t n_msk = t_msk.size() < 1 ? 1 : t_msk.shape(0);
+
+        // register metrics number of dimensions
+        std::unordered_map<std::string, std::vector<std::size_t>> dim;
+
+        dim["BS"] = {n_sit, n_ltm, n_msk, n_thr};
+        dim["BSS"] = {n_sit, n_ltm, n_msk, n_thr};
+        dim["BS_CRD"] = {n_sit, n_ltm, n_msk, n_thr, 3};
+        dim["BS_LBD"] = {n_sit, n_ltm, n_msk, n_thr, 3};
+        dim["QS"] = {n_sit, n_ltm, n_msk, n_mbr};
+        dim["CRPS"] = {n_sit, n_ltm, n_msk};
 
         // declare maps for memoisation purposes
         std::unordered_map<std::string, std::vector<std::string>> elt;
@@ -110,76 +124,105 @@ namespace evalhyd
 
         eh::utils::find_requirements(metrics, elt, dep, req_elt, req_dep);
 
-        // pre-compute required elt
-        for (const auto& element : req_elt)
-        {
-            if ( element == "o_k" )
-                evaluator.calc_o_k();
-            else if ( element == "bar_o" )
-                evaluator.calc_bar_o();
-            else if ( element == "y_k" )
-                evaluator.calc_y_k();
-            else if ( element == "q_qnt" )
-                evaluator.calc_q_qnt();
-        }
+        // initialise data structure for outputs
+        std::vector<xt::xarray<double>> r;
+        for (const auto& metric : metrics)
+            r.emplace_back(xt::zeros<double>(dim[metric]));
 
-        // pre-compute required dep
-        for (const auto& dependency : req_dep)
+        // compute variables one site at a time to minimise memory imprint
+        for (int s = 0; s < n_sit; s++)
+            // compute variables one lead time at a time to minimise memory imprint
+            for (int l = 0; l < n_ltm; l++)
         {
-            if ( dependency == "bs" )
-                evaluator.calc_bs();
-            else if ( dependency == "qs" )
-                evaluator.calc_qs();
-            else if ( dependency == "crps" )
-                evaluator.calc_crps();
-        }
+            // instantiate probabilist evaluator
+            const auto q_obs_v = xt::view(q_obs, s, xt::all());
+            const auto q_prd_v = xt::view(q_prd, s, l, xt::all(), xt::all());
 
-        // retrieve or compute requested metrics
-        std::vector<xt::xarray<double>> r;
+            eh::probabilist::Evaluator evaluator(q_obs_v, q_prd_v, q_thr, t_msk);
 
-        for (const auto& metric : metrics)
-        {
-            if ( metric == "BS" )
+            // pre-compute required elt
+            for (const auto& element : req_elt)
             {
-                if (std::find(req_dep.begin(), req_dep.end(), metric)
-                        == req_dep.end())
-                    evaluator.calc_BS();
-                r.emplace_back(xt::squeeze(evaluator.BS));
+                if ( element == "o_k" )
+                    evaluator.calc_o_k();
+                else if ( element == "bar_o" )
+                    evaluator.calc_bar_o();
+                else if ( element == "y_k" )
+                    evaluator.calc_y_k();
+                else if ( element == "q_qnt" )
+                    evaluator.calc_q_qnt();
             }
-            else if ( metric == "BSS" )
+
+            // pre-compute required dep
+            for (const auto& dependency : req_dep)
             {
-                if (std::find(req_dep.begin(), req_dep.end(), metric)
-                        == req_dep.end())
-                    evaluator.calc_BSS();
-                r.emplace_back(xt::squeeze(evaluator.BSS));
+                if ( dependency == "bs" )
+                    evaluator.calc_bs();
+                else if ( dependency == "qs" )
+                    evaluator.calc_qs();
+                else if ( dependency == "crps" )
+                    evaluator.calc_crps();
             }
-            else if ( metric == "BS_CRD" )
+
+            // retrieve or compute requested metrics
+            for (int m = 0; m < metrics.size(); m++)
             {
-                if (std::find(req_dep.begin(), req_dep.end(), metric)
+                const auto& metric = metrics[m];
+
+                if ( metric == "BS" )
+                {
+                    if (std::find(req_dep.begin(), req_dep.end(), metric)
                         == req_dep.end())
-                    evaluator.calc_BS_CRD();
-                r.emplace_back(xt::squeeze(evaluator.BS_CRD));
-            }
-            else if ( metric == "BS_LBD" )
-            {
-                if (std::find(req_dep.begin(), req_dep.end(), metric)
+                        evaluator.calc_BS();
+                    // (sites, lead times, subsets, thresholds)
+                    xt::view(r[m], s, l, xt::all(), xt::all()) =
+                            evaluator.BS;
+                }
+                else if ( metric == "BSS" )
+                {
+                    if (std::find(req_dep.begin(), req_dep.end(), metric)
                         == req_dep.end())
-                    evaluator.calc_BS_LBD();
-                r.emplace_back(xt::squeeze(evaluator.BS_LBD));
-            }
-            else if ( metric == "QS" )
-            {
-                if (std::find(req_dep.begin(), req_dep.end(), metric)
+                        evaluator.calc_BSS();
+                    // (sites, lead times, subsets, thresholds)
+                    xt::view(r[m], s, l, xt::all(), xt::all()) =
+                            evaluator.BSS;
+                }
+                else if ( metric == "BS_CRD" )
+                {
+                    if (std::find(req_dep.begin(), req_dep.end(), metric)
                         == req_dep.end())
-                    evaluator.calc_QS();
-                r.emplace_back(xt::squeeze(evaluator.QS));
-            }
-            else if ( metric == "CRPS" )
-            {
-                if (std::find(req_dep.begin(), req_dep.end(), metric)
+                        evaluator.calc_BS_CRD();
+                    // (sites, lead times, subsets, thresholds, components)
+                    xt::view(r[m], s, l, xt::all(), xt::all(), xt::all()) =
+                            evaluator.BS_CRD;
+                }
+                else if ( metric == "BS_LBD" )
+                {
+                    if (std::find(req_dep.begin(), req_dep.end(), metric)
+                        == req_dep.end())
+                        evaluator.calc_BS_LBD();
+                    // (sites, lead times, subsets, thresholds, components)
+                    xt::view(r[m], s, l, xt::all(), xt::all(), xt::all()) =
+                            evaluator.BS_LBD;
+                }
+                else if ( metric == "QS" )
+                {
+                    if (std::find(req_dep.begin(), req_dep.end(), metric)
+                        == req_dep.end())
+                        evaluator.calc_QS();
+                    // (sites, lead times, subsets, quantiles)
+                    xt::view(r[m], s, l, xt::all(), xt::all()) =
+                            evaluator.QS;
+                }
+                else if ( metric == "CRPS" )
+                {
+                    if (std::find(req_dep.begin(), req_dep.end(), metric)
                         == req_dep.end())
-                    evaluator.calc_CRPS();
-                r.emplace_back(xt::squeeze(evaluator.CRPS));
+                        evaluator.calc_CRPS();
+                    // (sites, lead times, subsets)
+                    xt::view(r[m], s, l, xt::all()) =
+                            evaluator.CRPS;
+                }
             }
         }
 
diff --git a/src/probabilist/evaluator.h b/src/probabilist/evaluator.h
index 26f0223430efc75759aa13eaa3461a4044e86fa8..0a622cef15e98c24b4dd38e4433974830ec60656 100644
--- a/src/probabilist/evaluator.h
+++ b/src/probabilist/evaluator.h
@@ -2,6 +2,25 @@
 #define EVALHYD_PROBABILIST_EVALUATOR_H
 
 #include <xtensor/xtensor.hpp>
+#include <xtensor/xview.hpp>
+
+using view1d_xtensor2d_type = decltype(
+    xt::view(
+            std::declval<const xt::xtensor<double, 2>&>(),
+            std::declval<int>(),
+            xt::all()
+    )
+);
+
+using view2d_xtensor4d_type = decltype(
+    xt::view(
+            std::declval<const xt::xtensor<double, 4>&>(),
+            std::declval<int>(),
+            std::declval<int>(),
+            xt::all(),
+            xt::all()
+    )
+);
 
 namespace evalhyd
 {
@@ -11,8 +30,8 @@ namespace evalhyd
         {
         private:
             // members for input data
-            const xt::xtensor<double, 2>& q_obs;
-            const xt::xtensor<double, 2>& q_prd;
+            const view1d_xtensor2d_type& q_obs;
+            const view2d_xtensor4d_type& q_prd;
             const xt::xtensor<double, 1>& q_thr;
             xt::xtensor<bool, 2> t_msk;
 
@@ -30,8 +49,8 @@ namespace evalhyd
 
         public:
             // constructor method
-            Evaluator(const xt::xtensor<double, 2>& obs,
-                      const xt::xtensor<double, 2>& prd,
+            Evaluator(const view1d_xtensor2d_type& obs,
+                      const view2d_xtensor4d_type& prd,
                       const xt::xtensor<double, 1>& thr,
                       const xt::xtensor<bool, 2>& msk) :
                     q_obs{obs}, q_prd{prd}, q_thr{thr}, t_msk(msk)
@@ -39,13 +58,13 @@ namespace evalhyd
                 // initialise a mask if none provided
                 // (corresponding to no temporal subset)
                 if (msk.size() < 1)
-                    t_msk = xt::ones<bool>(obs.shape());
+                    t_msk = xt::ones<bool>({std::size_t {1}, q_obs.size()});
 
                 // determine size for recurring dimensions
-                n = obs.shape(1);
+                n = q_obs.size();
                 n_msk = t_msk.shape(0);
-                n_mbr = prd.shape(0);
-                n_thr = thr.size();
+                n_mbr = q_prd.shape(0);
+                n_thr = q_thr.size();
             };
 
             // members for intermediate evaluation metrics
diff --git a/src/probabilist/evaluator_brier.cpp b/src/probabilist/evaluator_brier.cpp
index dcc8b7e17a6d63d20f9e4a7e8c47cde04b7db6cb..c250f17d870e4d722be8fb57e322e78cfa175ec4 100644
--- a/src/probabilist/evaluator_brier.cpp
+++ b/src/probabilist/evaluator_brier.cpp
@@ -26,7 +26,7 @@ namespace evalhyd
         //     Event probability forecast.
         //     shape: (thresholds, time)
         // \assign bs:
-        //     2D array of Brier score for each threshold for each time step.
+        //     Brier score for each threshold for each time step.
         //     shape: (thresholds, time)
         void Evaluator::calc_bs()
         {
diff --git a/src/probabilist/evaluator_elements.cpp b/src/probabilist/evaluator_elements.cpp
index 5f354e30634b33d1e7687adad95446693b953312..2df49be8cf19c1e496b089ac40df26108425b6f7 100644
--- a/src/probabilist/evaluator_elements.cpp
+++ b/src/probabilist/evaluator_elements.cpp
@@ -12,7 +12,7 @@ namespace evalhyd
         //
         // \require q_obs:
         //     Streamflow observations.
-        //     shape: (1, time)
+        //     shape: (time,)
         // \require q_thr:
         //     Streamflow exceedance threshold(s).
         //     shape: (thresholds,)
@@ -22,7 +22,7 @@ namespace evalhyd
         void Evaluator::calc_o_k()
         {
             // determine observed realisation of threshold(s) exceedance
-            o_k = xt::squeeze(is_above_threshold(q_obs, q_thr));
+            o_k = q_obs >= xt::view(q_thr, xt::all(), xt::newaxis());
         }
 
         // Determine mean observed realisation of threshold(s) exceedance.
@@ -65,7 +65,8 @@ namespace evalhyd
         {
             // determine if members have exceeded threshold(s)
             xt::xtensor<double, 3> e_frc =
-                    is_above_threshold(q_prd, q_thr);
+                    q_prd
+                    >= xt::view(q_thr, xt::all(), xt::newaxis(), xt::newaxis());
 
             // calculate how many members have exceeded threshold(s)
             xt::xtensor<double, 2> n_frc = xt::sum(e_frc, 1);
diff --git a/src/probabilist/evaluator_quantiles.cpp b/src/probabilist/evaluator_quantiles.cpp
index 726d14d8f0a0a8ac0bcd2f36c485bb302e1146af..a92c76a5dd4d60dd252ed0fbd59e4e973bd2cb6d 100644
--- a/src/probabilist/evaluator_quantiles.cpp
+++ b/src/probabilist/evaluator_quantiles.cpp
@@ -15,7 +15,7 @@ namespace evalhyd
         //
         // \require q_obs:
         //     Streamflow observations.
-        //     shape: (1, time)
+        //     shape: (time,)
         // \require q_qnt:
         //     Streamflow quantiles.
         //     shape: (quantiles, time)
@@ -50,11 +50,11 @@ namespace evalhyd
         //     shape: (quantiles, time)
         // \assign QS:
         //     Quantile scores.
-        //     shape: (subsets, quantiles, 1)
+        //     shape: (subsets, quantiles)
         void Evaluator::calc_QS()
         {
             // initialise output variable
-            // shape: (subsets, quantiles, 1)
+            // shape: (subsets, quantiles)
             QS = xt::zeros<double>({n_msk, n_mbr});
 
             // compute variable one mask at a time to minimise memory imprint
@@ -103,14 +103,14 @@ namespace evalhyd
         //     shape: (subsets, time)
         // \require crps:
         //     CRPS for each time step.
-        //     shape: (quantiles, time)
+        //     shape: (1, time)
         // \assign CRPS:
         //     CRPS.
-        //     shape: (subsets, 1, 1)
+        //     shape: (subsets,)
         void Evaluator::calc_CRPS()
         {
             // initialise output variable
-            // shape: (subsets, thresholds, 1)
+            // shape: (subsets,)
             CRPS = xt::zeros<double>({n_msk});
 
             // compute variable one mask at a time to minimise memory imprint
@@ -123,7 +123,7 @@ namespace evalhyd
                 // calculate the mean over the time steps
                 // $CRPS = \frac{1}{n} \sum_{k=1}^{n} crps$
                 xt::view(CRPS, m) =
-                        xt::nanmean(crps_masked, -1);
+                        xt::squeeze(xt::nanmean(crps_masked, -1));
             }
         }
     }
diff --git a/src/probabilist/evaluator_utils.cpp b/src/probabilist/evaluator_utils.cpp
deleted file mode 100644
index 923cc3aef2ce64446d56188104e4e14861a9b74e..0000000000000000000000000000000000000000
--- a/src/probabilist/evaluator_utils.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-#include <xtensor/xtensor.hpp>
-#include <xtensor/xview.hpp>
-
-namespace evalhyd
-{
-    namespace probabilist
-    {
-        // Determine whether flows are greater than given threshold(s).
-        //
-        // \param q:
-        //     Streamflow data.
-        //     shape: (1+, time)
-        // \param thr:
-        //     Streamflow thresholds.
-        //     shape: (thresholds,)
-        // \return
-        //     Boolean-like threshold(s) exceedance.
-        //     shape: (thresholds, 1+, time)
-        xt::xtensor<double, 3> is_above_threshold(
-                const xt::xtensor<double, 2> &q,
-                const xt::xtensor<double, 1> &thr
-        )
-        {
-            return q >= xt::view(thr, xt::all(), xt::newaxis(), xt::newaxis());
-        }
-
-        // Determine whether flows are strictly lower than given threshold(s)
-        //
-        // \param q:
-        //     Streamflow data.
-        //     shape: (1+, time)
-        // \param thr:
-        //     Streamflow thresholds.
-        //     shape: (thresholds,)
-        // \return
-        //     Boolean-like threshold(s) exceedance.
-        //     shape: (thresholds, 1+, time)
-        xt::xtensor<double, 3> is_below_threshold(
-                const xt::xtensor<double, 2> &q,
-                const xt::xtensor<double, 1> &thr
-        )
-        {
-            return q < xt::view(thr, xt::all(), xt::newaxis(), xt::newaxis());
-        }
-    }
-}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index d60239f47d28c0cb28cd1b6e85e65adfb87fdaf0..a616f8fedd137ee672156b1dd1b86de0fde7b5e6 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -37,6 +37,5 @@ add_executable(
         ../src/probabilist/evaluator_brier.cpp
         ../src/probabilist/evaluator_quantiles.cpp
         ../src/probabilist/evaluator_elements.cpp
-        ../src/probabilist/evaluator_utils.cpp
 )
 target_link_libraries(evalhyd_tests gtest gtest_main)
diff --git a/tests/test_probabilist.cpp b/tests/test_probabilist.cpp
index 880993fdd5ebbc77efab7b734870fb0f6d80cfa3..ae694f1fb39ac3e1270bda5e4bd560d964416532 100644
--- a/tests/test_probabilist.cpp
+++ b/tests/test_probabilist.cpp
@@ -11,44 +11,50 @@ TEST(ProbabilistTests, TestBrier) {
     // read in data
     std::ifstream ifs;
     ifs.open("./data/q_obs.csv");
-    xt::xtensor<double, 2> observed = xt::load_csv<int>(ifs);
+    xt::xtensor<double, 1> observed = xt::squeeze(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();
 
+    // add "artificial" site dimension (size 1) to observations
+    auto obs = xt::view(observed, xt::newaxis(), xt::all());
+    // add "artificial" site dimension (size 1) and lead time dimension (size 1)
+    // to predictions
+    auto prd = xt::view(predicted, xt::newaxis(), xt::newaxis(), xt::all(), xt::all());
+
     // compute scores
     xt::xtensor<double, 1> thresholds = {690, 534, 445};
 
     std::vector<xt::xarray<double>> metrics =
             evalhyd::evalp(
-                    observed, predicted, {"BS", "BSS", "BS_CRD", "BS_LBD"},
+                    obs, prd, {"BS", "BSS", "BS_CRD", "BS_LBD"},
                     thresholds
             );
 
     // check results
     // Brier scores
-    xt::xtensor<double, 1> bs =
-            {0.10615136, 0.07395622, 0.08669186};
+    xt::xtensor<double, 4> bs =
+            {{{{0.10615136, 0.07395622, 0.08669186}}}};
     EXPECT_TRUE(xt::allclose(metrics[0], bs));
 
     // Brier skill scores
-    xt::xtensor<double, 1> bss =
-            {0.5705594, 0.6661165, 0.5635126};
+    xt::xtensor<double, 4> bss =
+            {{{{0.5705594, 0.6661165, 0.5635126}}}};
     EXPECT_TRUE(xt::allclose(metrics[1], bss));
 
     // Brier calibration-refinement decompositions
-    xt::xtensor<double, 2> bs_crd =
-            {{0.011411758, 0.1524456, 0.2471852},
-             {0.005532413, 0.1530793, 0.2215031},
-             {0.010139431, 0.1220601, 0.1986125}};
+    xt::xtensor<double, 5> bs_crd =
+            {{{{{0.011411758, 0.1524456, 0.2471852},
+                {0.005532413, 0.1530793, 0.2215031},
+                {0.010139431, 0.1220601, 0.1986125}}}}};
     EXPECT_TRUE(xt::allclose(metrics[2], bs_crd));
 
     // Brier likelihood-base rate decompositions
-    xt::xtensor<double, 2> bs_lbd =
-            {{0.012159881, 0.1506234, 0.2446149},
-             {0.008031746, 0.1473869, 0.2133114},
-             {0.017191279, 0.1048221, 0.1743227}};
+    xt::xtensor<double, 5> bs_lbd =
+            {{{{{0.012159881, 0.1506234, 0.2446149},
+                {0.008031746, 0.1473869, 0.2133114},
+                {0.017191279, 0.1048221, 0.1743227}}}}};
     EXPECT_TRUE(xt::allclose(metrics[3], bs_lbd));
 }