Repository: scikit-hep/awkward

Issue:
Add `np.ravel`
NumPy has a `np.ravel` function that is analogous to `ak.flatten` with `axis=None`. We should overload this function to provide feature parity.



Discussion:

Resolving patch:
diff --git a/src/awkward/operations/structure.py b/src/awkward/operations/structure.py
--- a/src/awkward/operations/structure.py
+++ b/src/awkward/operations/structure.py
@@ -2095,6 +2095,58 @@ def transform(layout, depth, posaxis):
     return ak._util.maybe_wrap_like(out, array, behavior, highlevel)
 
 
+@ak._connect._numpy.implements("ravel")
+def ravel(array, highlevel=True, behavior=None):
+    """
+    Args:
+        array: Data containing nested lists to flatten
+        highlevel (bool): If True, return an #ak.Array; otherwise, return
+            a low-level #ak.layout.Content subclass.
+        behavior (None or dict): Custom #ak.behavior for the output array, if
+            high-level.
+
+    Returns an array with all level of nesting removed by erasing the
+    boundaries between consecutive lists.
+
+    This is the equivalent of NumPy's `np.ravel` for Awkward Arrays.
+
+    Consider the following doubly nested `array`.
+
+        ak.Array([[
+                   [1.1, 2.2, 3.3],
+                   [],
+                   [4.4, 5.5],
+                   [6.6]],
+                  [],
+                  [
+                   [7.7],
+                   [8.8, 9.9]
+                  ]])
+
+    Ravelling the array produces a flat array
+
+        >>> print(ak.ravel(array))
+        [1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9]
+
+    Missing values are eliminated by flattening: there is no distinction
+    between an empty list and a value of None at the level of flattening.
+    """
+    layout = ak.operations.convert.to_layout(
+        array, allow_record=False, allow_other=False
+    )
+    nplike = ak.nplike.of(layout)
+
+    out = ak._util.completely_flatten(layout)
+    assert isinstance(out, tuple) and all(isinstance(x, np.ndarray) for x in out)
+
+    if any(isinstance(x, nplike.ma.MaskedArray) for x in out):
+        out = ak.layout.NumpyArray(nplike.ma.concatenate(out))
+    else:
+        out = ak.layout.NumpyArray(nplike.concatenate(out))
+
+    return ak._util.maybe_wrap_like(out, array, behavior, highlevel)
+
+
 def _pack_layout(layout):
     nplike = ak.nplike.of(layout)
 


Test patch:
diff --git a/tests/test_0984-ravel.py b/tests/test_0984-ravel.py
new file mode 100644
--- /dev/null
+++ b/tests/test_0984-ravel.py
@@ -0,0 +1,53 @@
+# BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE
+
+from __future__ import absolute_import
+
+import pytest  # noqa: F401
+import numpy as np  # noqa: F401
+import awkward as ak  # noqa: F401
+
+content = ak.layout.NumpyArray(np.array([0, 1, 2, 3, 4, 3, 6, 5, 2, 2]))
+
+
+def test_one_level():
+    layout = ak.layout.ListOffsetArray64(
+        ak.layout.Index64(np.array([0, 3, 6, 6, 8, 10], dtype=np.int64)), content
+    )
+
+    # Test that all one level of nesting is removed
+    assert ak.to_list(ak.ravel(layout)) == [0, 1, 2, 3, 4, 3, 6, 5, 2, 2]
+
+
+def test_two_levels():
+    inner = ak.layout.ListOffsetArray64(
+        ak.layout.Index64(np.array([0, 3, 6, 6, 8, 10], dtype=np.int64)), content
+    )
+    layout = ak.layout.ListOffsetArray64(
+        ak.layout.Index64(np.array([0, 2, 4, 5], dtype=np.int64)), inner
+    )
+
+    # Test that all one level of nesting is removed
+    assert ak.to_list(ak.ravel(layout)) == [0, 1, 2, 3, 4, 3, 6, 5, 2, 2]
+
+
+def test_record():
+    x = ak.layout.ListOffsetArray64(
+        ak.layout.Index64(np.array([0, 3, 5], dtype=np.int64)), content
+    )
+    y = ak.layout.ListOffsetArray64(
+        ak.layout.Index64(np.array([5, 7, 10], dtype=np.int64)), content
+    )
+    layout = ak.layout.RecordArray((x, y), ("x", "y"))
+    assert ak.to_list(ak.ravel(layout)) == [0, 1, 2, 3, 4, 3, 6, 5, 2, 2]
+
+
+def test_option():
+    inner = ak.layout.ListOffsetArray64(
+        ak.layout.Index64(np.array([0, 3, 6, 6, 8, 10], dtype=np.int64)), content
+    )
+
+    # Test that Nones are omitted
+    layout = ak.layout.IndexedOptionArray64(
+        ak.layout.Index64(np.array([0, -1, 2, -1, 4])), inner
+    )
+    assert ak.to_list(ak.ravel(layout)) == [0, 1, 2, 2, 2]