File: test_3098_ak_where_with_arrays_containing_optionals.py

package info (click to toggle)
python-awkward 2.8.9-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 24,932 kB
  • sloc: python: 178,875; cpp: 33,828; sh: 432; makefile: 21; javascript: 8
file content (175 lines) | stat: -rw-r--r-- 6,341 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# BSD 3-Clause License; see https://github.com/scikit-hep/awkward/blob/main/LICENSE

from __future__ import annotations

import numpy as np

import awkward as ak
from awkward.operations import to_list


def test_ak_where_with_optional_unknowns():
    """
    This is the example from the Issue.
    In the two cases we fail, the not-selected value has type ?unknown, value None of course.

    Names here are changed a little from github issue 3098.
    """
    opt_true_cond = ak.Array([[True], [None]])[0]  # <Array [True] type='1 * ?bool'>
    true_cond = ak.Array([True])  # <Array [True] type='1 * bool'>
    none_alternative = ak.Array([None])  # <Array [None] type='1 * ?unknown'>
    zero_alternative = ak.Array([0])  # <Array [0] type='1 * int64'>
    opt_zero_alternative = ak.Array([[0], [None]])[0]  # <Array [0] type='1 * ?int64'>

    assert ak.where(opt_true_cond, 1, none_alternative).to_list() == [1]
    # Above fails at time of writing. We get [None].
    assert ak.where(opt_true_cond, 1, zero_alternative).to_list() == [1]
    assert ak.where(opt_true_cond, 1, opt_zero_alternative).to_list() == [1]

    # These assertions pass. Note that true_cond is type bool, not ?bool.
    assert ak.where(true_cond, 1, none_alternative).to_list() == [1]
    assert ak.where(true_cond, 1, zero_alternative).to_list() == [1]
    assert ak.where(true_cond, 1, opt_zero_alternative).to_list() == [1]

    # Like the first three assertions, The first one here fails.
    # This demonstrates that the problem is symmetric w/rt X and Y arrays.
    assert ak.where(~opt_true_cond, none_alternative, 1).to_list() == [1]
    # Above fails at time of writing. We're getting [None] again.
    assert ak.where(~opt_true_cond, zero_alternative, 1).to_list() == [1]
    assert ak.where(~opt_true_cond, opt_zero_alternative, 1).to_list() == [1]


def test_ak_where_with_optionals():
    """
    It turns out that we don't need to use ?unknown arrays to trigger this issue.
    We only need a None (masked value) in an element that is selected against.

    At time-of-writing (ATOW), ak.where() produces None values when:
    1. The conditional array values have OptionType (at least ?bool or ?int64), *AND*
    2. The value array element *NOT* selected has Option type and holds a None value.
    In this case regardless of the type or value of the array element that *IS* selected,
    the result for that element will, incorrectly, be None.
    """
    # This passes. Note that a condition of None creates a None in the result.
    assert to_list(
        ak.where(
            ak.Array([True, False, None]), ak.Array([1, 2, 3]), ak.Array([4, 5, 6])
        )
    ) == [1, 5, None]

    # This also passes. (The presence of None at the end forces option types to be used.)
    assert to_list(
        ak.where(
            ak.Array([True, False, None]),
            ak.Array([1, 2, None]),
            ak.Array([4, 5, None]),
        )
    ) == [1, 5, None]

    # This fails (ATOW). The presence of None forces option types to be used.
    assert to_list(
        ak.where(
            ak.Array([True, False, None]),
            ak.Array([1, 2, None]),
            ak.Array([None, 5, None]),
        )
    ) == [1, 5, None]  # ATOW we get [None, 5, None]

    # Fails ATOW. Same as above but with a None in the X argument.
    assert to_list(
        ak.where(
            ak.Array([True, False, None]),
            ak.Array([1, None, None]),
            ak.Array([4, 5, None]),
        )
    ) == [1, 5, None]  # ATOW we get [1, None, None]

    # Fails ATOW. Same as above the Y argument is not even an optional type (but X still is).
    assert to_list(
        ak.where(
            ak.Array([True, False, None]),
            ak.Array([1, None, None]),
            ak.Array([4, 5, 6]),
        )
    ) == [1, 5, None]  # ATOW we get [1, None, None]


def test_ak_where_with_optionals_multidim():
    # This needs to continue to work:
    assert to_list(
        ak.where(
            ak.Array([True, False]),
            ak.Array([[1, 2], [3, 4]]),
            ak.Array([[10, 11], [12, 13]]),
        )
    ) == [[1, 2], [12, 13]]

    # Option types only in X, not condition
    assert to_list(
        ak.where(
            ak.Array([[True, True], [False, False]]),
            ak.Array([[1, 2], None]),
            ak.Array([[10, 11], [12, 13]]),
        )
    ) == [[1, 2], [12, 13]]

    # Option types in condition and X, only one level of depth
    assert to_list(
        ak.where(
            ak.Array([[True, True], [False, False], [True, False], None]),
            ak.Array([[1, 2], None, None, [7, 8]]),
            ak.Array([[11, 12], [13, 14], [15, 16], [17, 18]]),
        )
    ) == [[1, 2], [13, 14], [None, 16], [None, None]]
    # Note: [[1, 2], [13, 14], [None, 16], None] might seem more natural,
    #       but broadcasting expands these arrays out.

    assert to_list(
        ak.where(
            ak.Array([[True, False], [True, None]]),
            ak.Array([1, 2]),
            ak.Array([None, 12]),
        )
    ) == [[1, None], [2, None]]


def test_ak_where_more_option_types():
    assert to_list(
        ak.where(
            ak.Array([False, True, None]),
            ak.Array(["this", None, "that"]),
            ak.Array(["foo", "bar", "baz"]),
        )
    ) == ["foo", None, None]

    bitmasked5 = ak.contents.BitMaskedArray(
        mask=ak.index.Index(
            np.array(
                [
                    0b10100,
                ],
                dtype=np.uint8,
            )
        ),
        content=ak.contents.NumpyArray(np.arange(5)),
        valid_when=False,
        length=5,
        lsb_order=True,
        parameters={"_my_param": "boysenberry"},
    )  # [0, 1, None, 3, None]
    unmasked5 = ak.contents.UnmaskedArray(
        ak.contents.NumpyArray(np.arange(10, 15))
    )  # [10, 11, 12, 13, 14]
    union5 = ak.Array([True, None, "two", 3, 4.4])

    mixed_result = ak.where(
        ak.Array([True, None, True, False, True]), bitmasked5, unmasked5
    )
    assert to_list(mixed_result) == [0, None, None, 13, None]
    assert (
        mixed_result.layout.parameters.get("_my_param") is None
    )  # Params not preserved.

    assert to_list(
        ak.where(ak.Array([True, True, True, False, None]), union5, unmasked5)
    ) == [True, None, "two", 13, None]