ak.combinations
---------------

Defined in `awkward.operations.structure <https://github.com/scikit-hep/awkward-1.0/blob/80bbef0738a6b7928333d7c705ee1b359991de5b/src/awkward/operations/structure.py>`__ on `line 3679 <https://github.com/scikit-hep/awkward-1.0/blob/80bbef0738a6b7928333d7c705ee1b359991de5b/src/awkward/operations/structure.py#L3679>`__.

.. py:function:: ak.combinations(array, n, replacement=False, axis=1, fields=None, parameters=None, with_name=None, highlevel=True, behavior=None)


    :param array: Array from which to choose ``n`` items without replacement.
    :param n: The number of items to choose in each list: ``2`` chooses
          unique pairs, ``3`` chooses unique triples, etc.
    :type n: int
    :param replacement: If True, combinations that include the same
                    item more than once are allowed; otherwise each item in a
                    combinations is strictly unique.
    :type replacement: bool
    :param axis: The dimension at which this operation is applied. The
             outermost dimension is ``0``, followed by ``1``, etc., and negative
             values count backward from the innermost: ``-1`` is the innermost
             dimension, ``-2`` is the next level up, etc.
    :type axis: int
    :param fields: If None, the pairs/triples/etc. are
               tuples with unnamed fields; otherwise, these ``fields`` name the
               fields. The number of ``fields`` must be equal to ``n``.
    :type fields: None or list of str
    :param parameters: Parameters for the new
                   :py:obj:`ak.layout.RecordArray` node that is created by this operation.
    :type parameters: None or dict
    :param with_name: Assigns a ``"__record__"`` name to the new
                  :py:obj:`ak.layout.RecordArray` node that is created by this operation
                  (overriding ``parameters``, if necessary).
    :type with_name: None or str
    :param highlevel: If True, return an :py:obj:`ak.Array`; otherwise, return
                  a low-level :py:obj:`ak.layout.Content` subclass.
    :type highlevel: bool
    :param behavior: Custom :py:obj:`ak.behavior` for the output array, if
                 high-level.
    :type behavior: None or dict

Computes a Cartesian product (i.e. cross product) of ``array`` with itself
that is restricted to combinations sampled without replacement. If the
normal Cartesian product is thought of as an ``n`` dimensional tensor, these
represent the "upper triangle" of sets without repetition. If
``replacement=True``, the diagonal of this "upper triangle" is included.

As a simple example with ``axis=0``, consider the following ``array``

.. code-block:: python


    ak.Array(["a", "b", "c", "d", "e"])

The combinations choose ``2`` are:

.. code-block:: python


    >>> ak.to_list(ak.combinations(array, 2, axis=0))
    [('a', 'b'), ('a', 'c'), ('a', 'd'), ('a', 'e'),
                 ('b', 'c'), ('b', 'd'), ('b', 'e'),
                             ('c', 'd'), ('c', 'e'),
                                         ('d', 'e')]

Including the diagonal allows pairs like ``('a', 'a')``.

.. code-block:: python


    >>> ak.to_list(ak.combinations(array, 2, axis=0, replacement=True))
    [('a', 'a'), ('a', 'b'), ('a', 'c'), ('a', 'd'), ('a', 'e'),
                 ('b', 'b'), ('b', 'c'), ('b', 'd'), ('b', 'e'),
                             ('c', 'c'), ('c', 'd'), ('c', 'e'),
                                         ('d', 'd'), ('d', 'e'),
                                                     ('e', 'e')]

The combinations choose ``3`` can't be easily arranged as a triangle
in two dimensions.

.. code-block:: python


    >>> ak.to_list(ak.combinations(array, 3, axis=0))
    [('a', 'b', 'c'), ('a', 'b', 'd'), ('a', 'b', 'e'), ('a', 'c', 'd'), ('a', 'c', 'e'),
     ('a', 'd', 'e'), ('b', 'c', 'd'), ('b', 'c', 'e'), ('b', 'd', 'e'), ('c', 'd', 'e')]

Including the (three-dimensional) diagonal allows triples like
``('a', 'a', 'a')``, but also ``('a', 'a', 'b')``, ``('a', 'b', 'b')``, etc.,
but not ``('a', 'b', 'a')``. All combinations are in the same order as
the original array.

.. code-block:: python


    >>> ak.to_list(ak.combinations(array, 3, axis=0, replacement=True))
    [('a', 'a', 'a'), ('a', 'a', 'b'), ('a', 'a', 'c'), ('a', 'a', 'd'), ('a', 'a', 'e'),
     ('a', 'b', 'b'), ('a', 'b', 'c'), ('a', 'b', 'd'), ('a', 'b', 'e'), ('a', 'c', 'c'),
     ('a', 'c', 'd'), ('a', 'c', 'e'), ('a', 'd', 'd'), ('a', 'd', 'e'), ('a', 'e', 'e'),
     ('b', 'b', 'b'), ('b', 'b', 'c'), ('b', 'b', 'd'), ('b', 'b', 'e'), ('b', 'c', 'c'),
     ('b', 'c', 'd'), ('b', 'c', 'e'), ('b', 'd', 'd'), ('b', 'd', 'e'), ('b', 'e', 'e'),
     ('c', 'c', 'c'), ('c', 'c', 'd'), ('c', 'c', 'e'), ('c', 'd', 'd'), ('c', 'd', 'e'),
     ('c', 'e', 'e'), ('d', 'd', 'd'), ('d', 'd', 'e'), ('d', 'e', 'e'), ('e', 'e', 'e')]

The primary purpose of this function, however, is to compute a different
set of combinations for each element of an array: in other words, ``axis=1``.
The following ``array`` has a different number of items in each element.

.. code-block:: python


    ak.Array([[1, 2, 3, 4], [], [5], [6, 7, 8]])

There are 6 ways to choose pairs from 4 elements, 0 ways to choose pairs
from 0 elements, 0 ways to choose pairs from 1 element, and 3 ways to
choose pairs from 3 elements.

.. code-block:: python


    >>> ak.to_list(ak.combinations(array, 2))
    [
     [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)],
     [],
     [],
     [(6, 7), (6, 8), (7, 8)]
    ]

Note, however, that the combinatorics isn't determined by equality of
the data themselves, but by their placement in the array. For example,
even if all elements of an array are equal, the output has the same
structure.

.. code-block:: python


    >>> same = ak.Array([[7, 7, 7, 7], [], [7], [7, 7, 7]])
    >>> ak.to_list(ak.combinations(same, 2))
    [
     [(7, 7), (7, 7), (7, 7), (7, 7), (7, 7), (7, 7)],
     [],
     [],
     [(7, 7), (7, 7), (7, 7)]
    ]

To get records instead of tuples, pass a set of field names to ``fields``.

.. code-block:: python


    >>> ak.to_list(ak.combinations(array, 2, fields=["x", "y"]))
    [
     [{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 1, 'y': 4},
                        {'x': 2, 'y': 3}, {'x': 2, 'y': 4},
                                          {'x': 3, 'y': 4}],
     [],
     [],
     [{'x': 6, 'y': 7}, {'x': 6, 'y': 8},
                        {'x': 7, 'y': 8}]]

This operation can be constructed from :py:obj:`ak.argcartesian` and other
primitives:

.. code-block:: python


    >>> left, right = ak.unzip(ak.argcartesian([array, array]))
    >>> keep = left < right
    >>> result = ak.zip([array[left][keep], array[right][keep]])
    >>> ak.to_list(result)
    [
     [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)],
     [],
     [],
     [(6, 7), (6, 8), (7, 8)]]

but it is frequently needed for data analysis, and the logic of which
indexes to ``keep`` (above) gets increasingly complicated for large ``n``.

To get list index positions in the tuples/records, rather than data from
the original ``array``, use :py:obj:`ak.argcombinations` instead of :py:obj:`ak.combinations`.
The :py:obj:`ak.argcombinations` form can be particularly useful as nested indexing
in :py:obj:`ak.Array.__getitem__`.

