Repository: scikit-hep/awkward

Issue:
Row-wise weighted mean gives incorrect results
### Version of Awkward Array

2.6.9

### Description and code to reproduce

I am doing a row-wise weighted mean in an awkward array and getting wrong results.

Here's an MRE with outputs in the comments:

```python
import awkward as ak

data = ak.Array(
    [
        [1, 2, 3],
        [4, 5],
    ]
)

weight = ak.Array(
    [
        [1, 1, 2],
        [1, 10],
    ]
)

# manual row-by-row - expected results
print(ak.mean(data[0], weight=weight[0]))  # -> 2.25
print(ak.mean(data[1], weight=weight[1]))  # -> 4.909090909090909

# manual vectorized - expected results
weights_norm = weight / ak.sum(weight, axis=1)
print(ak.sum(weights_norm * data, axis=1))  # -> [2.25, 4.91]

# the most natural call I expected to work - incorrect result in the 2nd row
print(ak.mean(data, weight=weight, axis=1))  # -> [2.25, 13.5]
```

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/scikit-hep/awkward/pull/3291?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 82.17%. Comparing base [(`b749e49`)](https://app.codecov.io/gh/scikit-hep/awkward/commit/b749e49d6fe078e530ef506bf526705bab3f35f1?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep) to head [(`b01afc9`)](https://app.codecov.io/gh/scikit-hep/awkward/commit/b01afc9bd548a062ab451b058b8c8a003ba33129?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep).
> Report is 180 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


| [Files with missing lines](https://app.codecov.io/gh/scikit-hep/awkward/pull/3291?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep) | Coverage Δ | |
|---|---|---|
| [src/awkward/operations/ak\_mean.py](https://app.codecov.io/gh/scikit-hep/awkward/pull/3291?src=pr&el=tree&filepath=src%2Fawkward%2Foperations%2Fak_mean.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep#diff-c3JjL2F3a3dhcmQvb3BlcmF0aW9ucy9ha19tZWFuLnB5) | `88.46% <ø> (+1.22%)` | :arrow_up: |

... and [158 files with indirect coverage changes](https://app.codecov.io/gh/scikit-hep/awkward/pull/3291/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep)

</details>
jpivarski: @all-contributors please add @nj-vs-vh for code

Patch:
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 4f6f90c5..9d4caf43 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -501,6 +501,15 @@
       "contributions": [
         "maintenance"
       ]
+    },
+    {
+      "login": "nj-vs-vh",
+      "name": "Igor Vaiman",
+      "avatar_url": "https://avatars.githubusercontent.com/u/30616208?v=4",
+      "profile": "https://nj-vs-vh.name/",
+      "contributions": [
+        "code"
+      ]
     }
   ],
   "contributorsPerLine": 7,
diff --git a/README.md b/README.md
index e4ec709a..7cc935cc 100644
--- a/README.md
+++ b/README.md
@@ -232,6 +232,7 @@ Thanks especially to the gracious help of Awkward Array contributors (including
       <td align="center" valign="top" width="14.28%"><a href="https://github.com/maxymnaumchyk"><img src="https://avatars.githubusercontent.com/u/70752300?v=4?s=100" width="100px;" alt="maxymnaumchyk"/><br /><sub><b>maxymnaumchyk</b></sub></a><br /><a href="https://github.com/scikit-hep/awkward/commits?author=maxymnaumchyk" title="Code">💻</a></td>
       <td align="center" valign="top" width="14.28%"><a href="https://tacaswell.github.io"><img src="https://avatars.githubusercontent.com/u/199813?v=4?s=100" width="100px;" alt="Thomas A Caswell"/><br /><sub><b>Thomas A Caswell</b></sub></a><br /><a href="#maintenance-tacaswell" title="Maintenance">🚧</a></td>
       <td align="center" valign="top" width="14.28%"><a href="http://www.nijho.lt"><img src="https://avatars.githubusercontent.com/u/6897215?v=4?s=100" width="100px;" alt="Bas Nijholt"/><br /><sub><b>Bas Nijholt</b></sub></a><br /><a href="#maintenance-basnijholt" title="Maintenance">🚧</a></td>
+      <td align="center" valign="top" width="14.28%"><a href="https://nj-vs-vh.name/"><img src="https://avatars.githubusercontent.com/u/30616208?v=4?s=100" width="100px;" alt="Igor Vaiman"/><br /><sub><b>Igor Vaiman</b></sub></a><br /><a href="https://github.com/scikit-hep/awkward/commits?author=nj-vs-vh" title="Code">💻</a></td>
     </tr>
   </tbody>
 </table>
diff --git a/src/awkward/operations/ak_mean.py b/src/awkward/operations/ak_mean.py
index a9b38ce1..3b6552c5 100644
--- a/src/awkward/operations/ak_mean.py
+++ b/src/awkward/operations/ak_mean.py
@@ -225,8 +225,8 @@ def _impl(x, weight, axis, keepdims, mask_identity, highlevel, behavior, attrs):
             sumw = ak.operations.ak_sum._impl(
                 x * 0 + weight,
                 axis,
-                keepdims,
-                mask_identity,
+                keepdims=True,
+                mask_identity=True,
                 highlevel=True,
                 behavior=ctx.behavior,
                 attrs=ctx.attrs,


Test patch:
diff --git a/tests/test_3285_ak_mean_weighted_row_wise.py b/tests/test_3285_ak_mean_weighted_row_wise.py
new file mode 100644
index 00000000..2c1bc1db
--- /dev/null
+++ b/tests/test_3285_ak_mean_weighted_row_wise.py
@@ -0,0 +1,65 @@
+# BSD 3-Clause License; see https://github.com/scikit-hep/awkward/blob/main/LICENSE
+
+from __future__ import annotations
+
+import math
+
+import pytest
+
+import awkward as ak
+
+
+@pytest.mark.parametrize(
+    "keepdims, expected_result",
+    [
+        pytest.param(False, ak.Array([2.25, 6.5])),
+        pytest.param(True, ak.Array([[2.25], [6.5]])),
+    ],
+)
+def test_keepdims(keepdims: bool, expected_result: ak.Array):
+    data = ak.Array(
+        [
+            [1, 2, 3],
+            [4, 7],
+        ]
+    )
+    weight = ak.Array(
+        [
+            [1, 1, 2],
+            [1, 5],
+        ]
+    )
+    assert ak.all(
+        ak.mean(data, weight=weight, axis=1, keepdims=keepdims) == expected_result
+    )
+
+
+@pytest.mark.parametrize(
+    "mask_identity, expected_result",
+    [
+        pytest.param(False, ak.Array([1.5, math.nan, 8])),
+        pytest.param(True, ak.Array([1.5, None, 8])),
+    ],
+)
+def test_mask_identity(mask_identity: bool, expected_result: ak.Array):
+    data = ak.Array(
+        [
+            [1, 2],
+            [],
+            [6, 9],
+        ]
+    )
+    weight = ak.Array(
+        [
+            [1, 1],
+            [],
+            [1, 2],
+        ]
+    )
+    result = ak.mean(data, weight=weight, axis=1, mask_identity=mask_identity)
+    assert result[0] == expected_result[0]
+    if mask_identity:
+        assert result[1] is None
+    else:
+        assert math.isnan(result[1])  # NaN is not equal to itself per IEEE!
+    assert result[2] == expected_result[2]