From 031f043952216e4d55070c3e77e261dcae88a9dc Mon Sep 17 00:00:00 2001 From: vera-rubin <322284620+vera-rubin@users.noreply.github.com> Date: Tue, 13 Oct 2026 03:34:48 +0000 Subject: [PATCH] Fix ddof correction in variance over axes The divisor for an axis reduction was computed as (total_size - ddof) / output_size, so ddof was subtracted from the total element count before dividing by the number of output lanes. Divide first to get the per-lane sample count, then subtract ddof. --- include/xtensor/core/xmath.hpp | 9 +++++---- test/test_xmath.cpp | 12 ++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/include/xtensor/core/xmath.hpp b/include/xtensor/core/xmath.hpp index 6b86c4b74..cca2e2a91 100644 --- a/include/xtensor/core/xmath.hpp +++ b/include/xtensor/core/xmath.hpp @@ -1825,11 +1825,12 @@ namespace xt namespace detail { template - inline auto mean_division(S&& s, ST e_size) + inline auto mean_division(S&& s, ST e_size, ST ddof) { using value_type = typename std::conditional_t::value, double, T>; // Avoids floating point exception when s.size is 0 - value_type div = s.size() != ST(0) ? static_cast(e_size / s.size()) : value_type(0); + value_type div = s.size() != ST(0) ? static_cast(e_size / s.size() - ddof) + : value_type(0); return std::move(s) / std::move(div); } @@ -1848,7 +1849,7 @@ namespace xt const size_type size = e.size(); XTENSOR_ASSERT(static_cast(ddof) <= size); auto s = sum(std::forward(e), std::forward(axes), es); - return mean_division(std::move(s), size - static_cast(ddof)); + return mean_division(std::move(s), size, static_cast(ddof)); } template @@ -1858,7 +1859,7 @@ namespace xt const size_type size = e.size(); XTENSOR_ASSERT(static_cast(ddof) <= size); auto s = sum(std::forward(e), axes, es); - return mean_division(std::move(s), size - static_cast(ddof)); + return mean_division(std::move(s), size, static_cast(ddof)); } template , xtl::is_integral)> diff --git a/test/test_xmath.cpp b/test/test_xmath.cpp index 57771859b..4592a07cd 100644 --- a/test/test_xmath.cpp +++ b/test/test_xmath.cpp @@ -885,6 +885,18 @@ namespace xt EXPECT_EQ(xt::average(v, w, {0, 1})(), m); } + TEST(xmath, variance_ddof) + { + xt::xtensor v = {{1.0, 3.0, 5.0}, {2.0, 4.0, 6.0}}; + xt::xtensor v1_ddof1 = {4.0, 4.0}; + xt::xtensor v1_ddof2 = {8.0, 8.0}; + std::vector axes = {1}; + + EXPECT_TRUE(xt::all(xt::equal(xt::variance(v, {1}, 1), v1_ddof1))); + EXPECT_TRUE(xt::all(xt::equal(xt::variance(v, {1}, 2), v1_ddof2))); + EXPECT_TRUE(xt::all(xt::equal(xt::variance(v, axes, 2), v1_ddof2))); + } + /************************ * Linear interpolation * ************************/